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