1 /*
2  * evolution-spamassassin.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <errno.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <glib/gstdio.h>
24 #include <glib/gi18n-lib.h>
25 
26 #include <camel/camel.h>
27 
28 #include <shell/e-shell.h>
29 #include <libemail-engine/libemail-engine.h>
30 
31 /* Standard GObject macros */
32 #define E_TYPE_SPAM_ASSASSIN \
33 	(e_spam_assassin_get_type ())
34 #define E_SPAM_ASSASSIN(obj) \
35 	(G_TYPE_CHECK_INSTANCE_CAST \
36 	((obj), E_TYPE_SPAM_ASSASSIN, ESpamAssassin))
37 
38 #define SPAM_ASSASSIN_EXIT_STATUS_SUCCESS	0
39 #define SPAM_ASSASSIN_EXIT_STATUS_ERROR		-1
40 
41 typedef struct _ESpamAssassin ESpamAssassin;
42 typedef struct _ESpamAssassinClass ESpamAssassinClass;
43 
44 struct _ESpamAssassin {
45 	EMailJunkFilter parent;
46 
47 	gboolean local_only;
48 	gchar *command;
49 	gchar *learn_command;
50 
51 	gboolean version_set;
52 	gint version;
53 };
54 
55 struct _ESpamAssassinClass {
56 	EMailJunkFilterClass parent_class;
57 };
58 
59 enum {
60 	PROP_0,
61 	PROP_LOCAL_ONLY,
62 	PROP_COMMAND,
63 	PROP_LEARN_COMMAND
64 };
65 
66 /* Module Entry Points */
67 void e_module_load (GTypeModule *type_module);
68 void e_module_unload (GTypeModule *type_module);
69 
70 /* Forward Declarations */
71 GType e_spam_assassin_get_type (void);
72 static void e_spam_assassin_interface_init (CamelJunkFilterInterface *iface);
73 
74 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
75 	ESpamAssassin,
76 	e_spam_assassin,
77 	E_TYPE_MAIL_JUNK_FILTER, 0,
78 	G_IMPLEMENT_INTERFACE_DYNAMIC (
79 		CAMEL_TYPE_JUNK_FILTER,
80 		e_spam_assassin_interface_init))
81 
82 #ifndef SPAMASSASSIN_COMMAND
83 #define SPAMASSASSIN_COMMAND "/usr/bin/spamassassin"
84 #endif
85 
86 #ifndef SA_LEARN_COMMAND
87 #define SA_LEARN_COMMAND "/usr/bin/sa-learn"
88 #endif
89 
90 static const gchar *
spam_assassin_get_command_path(ESpamAssassin * extension)91 spam_assassin_get_command_path (ESpamAssassin *extension)
92 {
93 	g_return_val_if_fail (extension != NULL, NULL);
94 
95 	if (extension->command && *extension->command)
96 		return extension->command;
97 
98 	return SPAMASSASSIN_COMMAND;
99 }
100 
101 static const gchar *
spam_assassin_get_learn_command_path(ESpamAssassin * extension)102 spam_assassin_get_learn_command_path (ESpamAssassin *extension)
103 {
104 	g_return_val_if_fail (extension != NULL, NULL);
105 
106 	if (extension->learn_command && *extension->learn_command)
107 		return extension->learn_command;
108 
109 	return SA_LEARN_COMMAND;
110 }
111 
112 #ifdef G_OS_UNIX
113 static void
spam_assassin_cancelled_cb(GCancellable * cancellable,GPid * pid)114 spam_assassin_cancelled_cb (GCancellable *cancellable,
115                             GPid *pid)
116 {
117 	/* XXX On UNIX-like systems we can safely assume a GPid is the
118 	 *     process ID and use it to terminate the process via signal. */
119 	kill (*pid, SIGTERM);
120 }
121 #endif
122 
123 static void
spam_assassin_exited_cb(GPid * pid,gint status,gpointer user_data)124 spam_assassin_exited_cb (GPid *pid,
125                          gint status,
126                          gpointer user_data)
127 {
128 	struct {
129 		GMainLoop *loop;
130 		gint exit_code;
131 	} *source_data = user_data;
132 
133 	if (WIFEXITED (status))
134 		source_data->exit_code = WEXITSTATUS (status);
135 	else
136 		source_data->exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR;
137 
138 	g_main_loop_quit (source_data->loop);
139 }
140 
141 static gint
spam_assassin_command_full(const gchar ** argv,CamelMimeMessage * message,const gchar * input_data,GByteArray * output_buffer,gboolean wait_for_termination,GCancellable * cancellable,GError ** error)142 spam_assassin_command_full (const gchar **argv,
143                             CamelMimeMessage *message,
144                             const gchar *input_data,
145                             GByteArray *output_buffer,
146                             gboolean wait_for_termination,
147                             GCancellable *cancellable,
148                             GError **error)
149 {
150 	GMainContext *context;
151 	GSpawnFlags flags = 0;
152 	GSource *source;
153 	GPid child_pid;
154 	gint standard_input;
155 	gint standard_output;
156 	gulong handler_id = 0;
157 	gboolean success;
158 
159 	struct {
160 		GMainLoop *loop;
161 		gint exit_code;
162 	} source_data;
163 
164 	if (wait_for_termination)
165 		flags |= G_SPAWN_DO_NOT_REAP_CHILD;
166 	if (output_buffer == NULL)
167 		flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
168 	flags |= G_SPAWN_STDERR_TO_DEV_NULL;
169 
170 	/* Spawn SpamAssassin with an open stdin pipe. */
171 	success = g_spawn_async_with_pipes (
172 		NULL,
173 		(gchar **) argv,
174 		NULL,
175 		flags,
176 		NULL, NULL,
177 		&child_pid,
178 		&standard_input,
179 		(output_buffer != NULL) ? &standard_output : NULL,
180 		NULL,
181 		error);
182 
183 	if (!success) {
184 		gchar *command_line;
185 
186 		command_line = g_strjoinv (" ", (gchar **) argv);
187 		g_prefix_error (
188 			error, _("Failed to spawn SpamAssassin (%s): "),
189 			command_line);
190 		g_free (command_line);
191 
192 		return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
193 	}
194 
195 	if (message != NULL) {
196 		CamelStream *stream;
197 		gssize bytes_written;
198 
199 		/* Stream the CamelMimeMessage to SpamAssassin. */
200 		stream = camel_stream_fs_new_with_fd (standard_input);
201 		bytes_written = camel_data_wrapper_write_to_stream_sync (
202 			CAMEL_DATA_WRAPPER (message),
203 			stream, cancellable, error);
204 		success = (bytes_written >= 0) &&
205 			(camel_stream_close (stream, cancellable, error) == 0);
206 		g_object_unref (stream);
207 
208 		if (!success) {
209 			g_spawn_close_pid (child_pid);
210 			g_prefix_error (
211 				error, _("Failed to stream mail "
212 				"message content to SpamAssassin: "));
213 			return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
214 		}
215 
216 	} else if (input_data != NULL) {
217 		gssize bytes_written;
218 
219 		/* Write raw data directly to SpamAssassin. */
220 		bytes_written = camel_write (
221 			standard_input, input_data,
222 			strlen (input_data), cancellable, error);
223 		success = (bytes_written >= 0);
224 
225 		close (standard_input);
226 
227 		if (!success) {
228 			g_spawn_close_pid (child_pid);
229 			g_prefix_error (
230 				error, _("Failed to write “%s” "
231 				"to SpamAssassin: "), input_data);
232 			return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
233 		}
234 	}
235 
236 	if (output_buffer != NULL) {
237 		CamelStream *input_stream;
238 		CamelStream *output_stream;
239 		gssize bytes_written;
240 
241 		input_stream = camel_stream_fs_new_with_fd (standard_output);
242 
243 		output_stream = camel_stream_mem_new ();
244 		camel_stream_mem_set_byte_array (
245 			CAMEL_STREAM_MEM (output_stream), output_buffer);
246 
247 		bytes_written = camel_stream_write_to_stream (
248 			input_stream, output_stream, cancellable, error);
249 		g_byte_array_append (output_buffer, (guint8 *) "", 1);
250 		success = (bytes_written >= 0);
251 
252 		g_object_unref (input_stream);
253 		g_object_unref (output_stream);
254 
255 		if (!success) {
256 			g_spawn_close_pid (child_pid);
257 			g_prefix_error (
258 				error, _("Failed to read "
259 				"output from SpamAssassin: "));
260 			return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
261 		}
262 	}
263 
264 	/* XXX I'm not sure if we should call g_spawn_close_pid()
265 	 *     here or not.  Only really matters on Windows anyway. */
266 	if (!wait_for_termination)
267 		return 0;
268 
269 	/* Wait for the SpamAssassin process to terminate
270 	 * using GLib's main loop for better portability. */
271 
272 	context = g_main_context_new ();
273 
274 	source = g_child_watch_source_new (child_pid);
275 	g_source_set_callback (
276 		source, (GSourceFunc)
277 		spam_assassin_exited_cb,
278 		&source_data, NULL);
279 	g_source_attach (source, context);
280 	g_source_unref (source);
281 
282 	source_data.loop = g_main_loop_new (context, TRUE);
283 	source_data.exit_code = 0;
284 
285 #ifdef G_OS_UNIX
286 	if (G_IS_CANCELLABLE (cancellable))
287 		handler_id = g_cancellable_connect (
288 			cancellable,
289 			G_CALLBACK (spam_assassin_cancelled_cb),
290 			&child_pid, (GDestroyNotify) NULL);
291 #endif
292 
293 	g_main_loop_run (source_data.loop);
294 
295 	if (handler_id > 0)
296 		g_cancellable_disconnect (cancellable, handler_id);
297 
298 	g_main_loop_unref (source_data.loop);
299 	source_data.loop = NULL;
300 
301 	g_main_context_unref (context);
302 
303 	/* Clean up. */
304 
305 	g_spawn_close_pid (child_pid);
306 
307 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
308 		source_data.exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR;
309 
310 	else if (source_data.exit_code == SPAM_ASSASSIN_EXIT_STATUS_ERROR)
311 		g_set_error_literal (
312 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
313 			_("SpamAssassin either crashed or "
314 			"failed to process a mail message"));
315 
316 	return source_data.exit_code;
317 }
318 
319 static gint
spam_assassin_command(const gchar ** argv,CamelMimeMessage * message,const gchar * input_data,GCancellable * cancellable,GError ** error)320 spam_assassin_command (const gchar **argv,
321                        CamelMimeMessage *message,
322                        const gchar *input_data,
323                        GCancellable *cancellable,
324                        GError **error)
325 {
326 	return spam_assassin_command_full (
327 		argv, message, input_data, NULL, TRUE, cancellable, error);
328 }
329 
330 static gboolean
spam_assassin_get_local_only(ESpamAssassin * extension)331 spam_assassin_get_local_only (ESpamAssassin *extension)
332 {
333 	return extension->local_only;
334 }
335 
336 static void
spam_assassin_set_local_only(ESpamAssassin * extension,gboolean local_only)337 spam_assassin_set_local_only (ESpamAssassin *extension,
338                               gboolean local_only)
339 {
340 	if (extension->local_only == local_only)
341 		return;
342 
343 	extension->local_only = local_only;
344 
345 	g_object_notify (G_OBJECT (extension), "local-only");
346 }
347 
348 static const gchar *
spam_assassin_get_command(ESpamAssassin * extension)349 spam_assassin_get_command (ESpamAssassin *extension)
350 {
351 	return extension->command;
352 }
353 
354 static void
spam_assassin_set_command(ESpamAssassin * extension,const gchar * command)355 spam_assassin_set_command (ESpamAssassin *extension,
356 			   const gchar *command)
357 {
358 	if (g_strcmp0 (extension->command, command) == 0)
359 		return;
360 
361 	g_free (extension->command);
362 	extension->command = g_strdup (command);
363 
364 	g_object_notify (G_OBJECT (extension), "command");
365 }
366 
367 static const gchar *
spam_assassin_get_learn_command(ESpamAssassin * extension)368 spam_assassin_get_learn_command (ESpamAssassin *extension)
369 {
370 	return extension->learn_command;
371 }
372 
373 static void
spam_assassin_set_learn_command(ESpamAssassin * extension,const gchar * learn_command)374 spam_assassin_set_learn_command (ESpamAssassin *extension,
375 				 const gchar *learn_command)
376 {
377 	if (g_strcmp0 (extension->learn_command, learn_command) == 0)
378 		return;
379 
380 	g_free (extension->learn_command);
381 	extension->learn_command = g_strdup (learn_command);
382 
383 	g_object_notify (G_OBJECT (extension), "learn-command");
384 }
385 
386 static void
spam_assassin_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)387 spam_assassin_set_property (GObject *object,
388                             guint property_id,
389                             const GValue *value,
390                             GParamSpec *pspec)
391 {
392 	switch (property_id) {
393 		case PROP_LOCAL_ONLY:
394 			spam_assassin_set_local_only (
395 				E_SPAM_ASSASSIN (object),
396 				g_value_get_boolean (value));
397 			return;
398 
399 		case PROP_COMMAND:
400 			spam_assassin_set_command (
401 				E_SPAM_ASSASSIN (object),
402 				g_value_get_string (value));
403 			return;
404 
405 		case PROP_LEARN_COMMAND:
406 			spam_assassin_set_learn_command (
407 				E_SPAM_ASSASSIN (object),
408 				g_value_get_string (value));
409 			return;
410 	}
411 
412 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
413 }
414 
415 static void
spam_assassin_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)416 spam_assassin_get_property (GObject *object,
417                             guint property_id,
418                             GValue *value,
419                             GParamSpec *pspec)
420 {
421 	switch (property_id) {
422 		case PROP_LOCAL_ONLY:
423 			g_value_set_boolean (
424 				value, spam_assassin_get_local_only (
425 				E_SPAM_ASSASSIN (object)));
426 			return;
427 
428 		case PROP_COMMAND:
429 			g_value_set_string (
430 				value, spam_assassin_get_command (
431 				E_SPAM_ASSASSIN (object)));
432 			return;
433 
434 		case PROP_LEARN_COMMAND:
435 			g_value_set_string (
436 				value, spam_assassin_get_learn_command (
437 				E_SPAM_ASSASSIN (object)));
438 			return;
439 	}
440 
441 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
442 }
443 
444 static void
spam_assassin_finalize(GObject * object)445 spam_assassin_finalize (GObject *object)
446 {
447 	ESpamAssassin *extension = E_SPAM_ASSASSIN (object);
448 
449 	g_free (extension->command);
450 	extension->command = NULL;
451 
452 	g_free (extension->learn_command);
453 	extension->learn_command = NULL;
454 
455 	/* Chain up to parent's method. */
456 	G_OBJECT_CLASS (e_spam_assassin_parent_class)->finalize (object);
457 }
458 
459 static gboolean
spam_assassin_get_version(ESpamAssassin * extension,gint * spam_assassin_version,GCancellable * cancellable,GError ** error)460 spam_assassin_get_version (ESpamAssassin *extension,
461                            gint *spam_assassin_version,
462                            GCancellable *cancellable,
463                            GError **error)
464 {
465 	GByteArray *output_buffer;
466 	gint exit_code;
467 	guint ii;
468 
469 	const gchar *argv[] = {
470 		spam_assassin_get_learn_command_path (extension),
471 		"--version",
472 		NULL
473 	};
474 
475 	if (extension->version_set) {
476 		if (spam_assassin_version != NULL)
477 			*spam_assassin_version = extension->version;
478 		return TRUE;
479 	}
480 
481 	output_buffer = g_byte_array_new ();
482 
483 	exit_code = spam_assassin_command_full (
484 		argv, NULL, NULL, output_buffer, TRUE, cancellable, error);
485 
486 	if (exit_code != 0) {
487 		g_byte_array_free (output_buffer, TRUE);
488 		return FALSE;
489 	}
490 
491 	for (ii = 0; ii < output_buffer->len; ii++) {
492 		if (g_ascii_isdigit (output_buffer->data[ii])) {
493 			guint8 ch = output_buffer->data[ii];
494 			extension->version = (ch - '0');
495 			extension->version_set = TRUE;
496 			break;
497 		}
498 	}
499 
500 	if (spam_assassin_version != NULL)
501 		*spam_assassin_version = extension->version;
502 
503 	g_byte_array_free (output_buffer, TRUE);
504 
505 	return TRUE;
506 }
507 
508 static gboolean
spam_assassin_available(EMailJunkFilter * junk_filter)509 spam_assassin_available (EMailJunkFilter *junk_filter)
510 {
511 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
512 	gboolean available;
513 	GError *error = NULL;
514 
515 	available = spam_assassin_get_version (extension, NULL, NULL, &error);
516 
517 	if (error != NULL) {
518 		g_debug ("%s: %s", G_STRFUNC, error->message);
519 		g_error_free (error);
520 	}
521 
522 	return available;
523 }
524 
525 static GtkWidget *
spam_assassin_new_config_widget(EMailJunkFilter * junk_filter)526 spam_assassin_new_config_widget (EMailJunkFilter *junk_filter)
527 {
528 	GtkWidget *box;
529 	GtkWidget *widget;
530 	GtkWidget *container;
531 	gchar *markup;
532 
533 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
534 
535 	markup = g_markup_printf_escaped (
536 		"<b>%s</b>", _("SpamAssassin Options"));
537 	widget = gtk_label_new (markup);
538 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
539 	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
540 	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
541 	gtk_widget_show (widget);
542 	g_free (markup);
543 
544 	widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
545 	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
546 	gtk_widget_show (widget);
547 
548 	container = widget;
549 
550 	widget = gtk_check_button_new_with_mnemonic (
551 		_("I_nclude remote tests"));
552 	gtk_widget_set_margin_left (widget, 12);
553 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
554 	gtk_widget_show (widget);
555 
556 	e_binding_bind_property (
557 		junk_filter, "local-only",
558 		widget, "active",
559 		G_BINDING_BIDIRECTIONAL |
560 		G_BINDING_SYNC_CREATE |
561 		G_BINDING_INVERT_BOOLEAN);
562 
563 	markup = g_markup_printf_escaped (
564 		"<small>%s</small>",
565 		_("This will make SpamAssassin more reliable, but slower."));
566 	widget = gtk_label_new (markup);
567 	gtk_widget_set_margin_left (widget, 36);
568 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
569 	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
570 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
571 	gtk_widget_show (widget);
572 	g_free (markup);
573 
574 	return box;
575 }
576 
577 static CamelJunkStatus
spam_assassin_classify(CamelJunkFilter * junk_filter,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)578 spam_assassin_classify (CamelJunkFilter *junk_filter,
579                         CamelMimeMessage *message,
580                         GCancellable *cancellable,
581                         GError **error)
582 {
583 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
584 	CamelJunkStatus status;
585 	const gchar *argv[7];
586 	gint exit_code;
587 	gint ii = 0;
588 
589 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
590 		return CAMEL_JUNK_STATUS_ERROR;
591 
592 	argv[ii++] = spam_assassin_get_command_path (extension);
593 	argv[ii++] = "--exit-code";
594 	if (extension->local_only)
595 		argv[ii++] = "--local";
596 	argv[ii] = NULL;
597 
598 	g_return_val_if_fail (ii < G_N_ELEMENTS (argv), CAMEL_JUNK_STATUS_ERROR);
599 
600 	exit_code = spam_assassin_command (
601 		argv, message, NULL, cancellable, error);
602 
603 	/* Check for an error while spawning the program. */
604 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_ERROR)
605 		status = CAMEL_JUNK_STATUS_ERROR;
606 
607 	/* Zero exit code means the message is ham. */
608 	else if (exit_code == 0)
609 		status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK;
610 
611 	/* Non-zero exit code means the message is spam. */
612 	else
613 		status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
614 
615 	/* Check that the return value and GError agree. */
616 	if (status != CAMEL_JUNK_STATUS_ERROR)
617 		g_warn_if_fail (error == NULL || *error == NULL);
618 	else
619 		g_warn_if_fail (error == NULL || *error != NULL);
620 
621 	return status;
622 }
623 
624 static gboolean
spam_assassin_learn_junk(CamelJunkFilter * junk_filter,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)625 spam_assassin_learn_junk (CamelJunkFilter *junk_filter,
626                           CamelMimeMessage *message,
627                           GCancellable *cancellable,
628                           GError **error)
629 {
630 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
631 	const gchar *argv[5];
632 	gint exit_code;
633 	gint ii = 0;
634 
635 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
636 		return FALSE;
637 
638 	argv[ii++] = spam_assassin_get_learn_command_path (extension);
639 	argv[ii++] = "--spam";
640 	argv[ii++] = "--no-sync";
641 	if (extension->local_only)
642 		argv[ii++] = "--local";
643 	argv[ii] = NULL;
644 
645 	g_return_val_if_fail (ii < G_N_ELEMENTS (argv), FALSE);
646 
647 	exit_code = spam_assassin_command (
648 		argv, message, NULL, cancellable, error);
649 
650 	/* Check that the return value and GError agree. */
651 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
652 		g_warn_if_fail (error == NULL || *error == NULL);
653 	else
654 		g_warn_if_fail (error == NULL || *error != NULL);
655 
656 	return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
657 }
658 
659 static gboolean
spam_assassin_learn_not_junk(CamelJunkFilter * junk_filter,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)660 spam_assassin_learn_not_junk (CamelJunkFilter *junk_filter,
661                               CamelMimeMessage *message,
662                               GCancellable *cancellable,
663                               GError **error)
664 {
665 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
666 	const gchar *argv[5];
667 	gint exit_code;
668 	gint ii = 0;
669 
670 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
671 		return FALSE;
672 
673 	argv[ii++] = spam_assassin_get_learn_command_path (extension);
674 	argv[ii++] = "--ham";
675 	argv[ii++] = "--no-sync";
676 	if (extension->local_only)
677 		argv[ii++] = "--local";
678 	argv[ii] = NULL;
679 
680 	g_return_val_if_fail (ii < G_N_ELEMENTS (argv), FALSE);
681 
682 	exit_code = spam_assassin_command (
683 		argv, message, NULL, cancellable, error);
684 
685 	/* Check that the return value and GError agree. */
686 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
687 		g_warn_if_fail (error == NULL || *error == NULL);
688 	else
689 		g_warn_if_fail (error == NULL || *error != NULL);
690 
691 	return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
692 }
693 
694 static gboolean
spam_assassin_synchronize(CamelJunkFilter * junk_filter,GCancellable * cancellable,GError ** error)695 spam_assassin_synchronize (CamelJunkFilter *junk_filter,
696                            GCancellable *cancellable,
697                            GError **error)
698 {
699 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
700 	const gchar *argv[4];
701 	gint exit_code;
702 	gint ii = 0;
703 
704 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
705 		return FALSE;
706 
707 	argv[ii++] = spam_assassin_get_learn_command_path (extension);
708 	argv[ii++] = "--sync";
709 	if (extension->local_only)
710 		argv[ii++] = "--local";
711 	argv[ii] = NULL;
712 
713 	g_return_val_if_fail (ii < G_N_ELEMENTS (argv), FALSE);
714 
715 	exit_code = spam_assassin_command (
716 		argv, NULL, NULL, cancellable, error);
717 
718 	/* Check that the return value and GError agree. */
719 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
720 		g_warn_if_fail (error == NULL || *error == NULL);
721 	else
722 		g_warn_if_fail (error == NULL || *error != NULL);
723 
724 	return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
725 }
726 
727 static void
e_spam_assassin_class_init(ESpamAssassinClass * class)728 e_spam_assassin_class_init (ESpamAssassinClass *class)
729 {
730 	GObjectClass *object_class;
731 	EMailJunkFilterClass *junk_filter_class;
732 
733 	object_class = G_OBJECT_CLASS (class);
734 	object_class->set_property = spam_assassin_set_property;
735 	object_class->get_property = spam_assassin_get_property;
736 	object_class->finalize = spam_assassin_finalize;
737 
738 	junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class);
739 	junk_filter_class->filter_name = "SpamAssassin";
740 	junk_filter_class->display_name = _("SpamAssassin");
741 	junk_filter_class->available = spam_assassin_available;
742 	junk_filter_class->new_config_widget = spam_assassin_new_config_widget;
743 
744 	g_object_class_install_property (
745 		object_class,
746 		PROP_LOCAL_ONLY,
747 		g_param_spec_boolean (
748 			"local-only",
749 			"Local Only",
750 			"Do not use tests requiring DNS lookups",
751 			TRUE,
752 			G_PARAM_READWRITE));
753 
754 	g_object_class_install_property (
755 		object_class,
756 		PROP_COMMAND,
757 		g_param_spec_string (
758 			"command",
759 			"Full Path Command",
760 			"Full path command to use to run spamassassin",
761 			"",
762 			G_PARAM_READWRITE));
763 
764 	g_object_class_install_property (
765 		object_class,
766 		PROP_LEARN_COMMAND,
767 		g_param_spec_string (
768 			"learn-command",
769 			"Full Path Command",
770 			"Full path command to use to run sa-learn",
771 			"",
772 			G_PARAM_READWRITE));
773 }
774 
775 static void
e_spam_assassin_class_finalize(ESpamAssassinClass * class)776 e_spam_assassin_class_finalize (ESpamAssassinClass *class)
777 {
778 }
779 
780 static void
e_spam_assassin_interface_init(CamelJunkFilterInterface * iface)781 e_spam_assassin_interface_init (CamelJunkFilterInterface *iface)
782 {
783 	iface->classify = spam_assassin_classify;
784 	iface->learn_junk = spam_assassin_learn_junk;
785 	iface->learn_not_junk = spam_assassin_learn_not_junk;
786 	iface->synchronize = spam_assassin_synchronize;
787 }
788 
789 static void
e_spam_assassin_init(ESpamAssassin * extension)790 e_spam_assassin_init (ESpamAssassin *extension)
791 {
792 	GSettings *settings;
793 
794 	settings = e_util_ref_settings ("org.gnome.evolution.spamassassin");
795 
796 	g_settings_bind (
797 		settings, "local-only",
798 		extension, "local-only",
799 		G_SETTINGS_BIND_DEFAULT);
800 	g_settings_bind (
801 		settings, "command",
802 		G_OBJECT (extension), "command",
803 		G_SETTINGS_BIND_DEFAULT);
804 	g_settings_bind (
805 		settings, "learn-command",
806 		G_OBJECT (extension), "learn-command",
807 		G_SETTINGS_BIND_DEFAULT);
808 
809 	g_object_unref (settings);
810 }
811 
812 G_MODULE_EXPORT void
e_module_load(GTypeModule * type_module)813 e_module_load (GTypeModule *type_module)
814 {
815 	e_spam_assassin_register_type (type_module);
816 }
817 
818 G_MODULE_EXPORT void
e_module_unload(GTypeModule * type_module)819 e_module_unload (GTypeModule *type_module)
820 {
821 }
822