/* * evolution-spamassassin.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * */ #include #include #include #include #include #include #include #include #include /* Standard GObject macros */ #define E_TYPE_SPAM_ASSASSIN \ (e_spam_assassin_get_type ()) #define E_SPAM_ASSASSIN(obj) \ (G_TYPE_CHECK_INSTANCE_CAST \ ((obj), E_TYPE_SPAM_ASSASSIN, ESpamAssassin)) #ifndef SPAMASSASSIN_BINARY #define SPAMASSASSIN_BINARY "/usr/bin/spamassassin" #endif #ifndef SA_LEARN_BINARY #define SA_LEARN_BINARY "/usr/bin/sa-learn" #endif #ifndef SPAMC_BINARY #define SPAMC_BINARY "/usr/bin/spamc" #endif #ifndef SPAMD_BINARY #define SPAMD_BINARY "/usr/bin/spamd" #endif /* For starting our own daemon. */ #define DAEMON_MAX_RETRIES 100 #define DAEMON_RETRY_DELAY 0.05 /* seconds */ #define SPAM_ASSASSIN_EXIT_STATUS_SUCCESS 0 #define SPAM_ASSASSIN_EXIT_STATUS_ERROR -1 typedef struct _ESpamAssassin ESpamAssassin; typedef struct _ESpamAssassinClass ESpamAssassinClass; struct _ESpamAssassin { EMailJunkFilter parent; GMutex *socket_path_mutex; gchar *pid_file; gchar *socket_path; gchar *spamc_binary; gchar *spamd_binary; gint version; gboolean local_only; gboolean use_daemon; gboolean version_set; /* spamc/spamd state */ gboolean spamd_tested; gboolean spamd_using_allow_tell; gboolean system_spamd_available; gboolean use_spamc; }; struct _ESpamAssassinClass { EMailJunkFilterClass parent_class; }; enum { PROP_0, PROP_LOCAL_ONLY, PROP_SPAMC_BINARY, PROP_SPAMD_BINARY, PROP_SOCKET_PATH, PROP_USE_DAEMON }; /* Module Entry Points */ void e_module_load (GTypeModule *type_module); void e_module_unload (GTypeModule *type_module); /* Forward Declarations */ GType e_spam_assassin_get_type (void); static void e_spam_assassin_interface_init (CamelJunkFilterInterface *interface); G_DEFINE_DYNAMIC_TYPE_EXTENDED ( ESpamAssassin, e_spam_assassin, E_TYPE_MAIL_JUNK_FILTER, 0, G_IMPLEMENT_INTERFACE_DYNAMIC ( CAMEL_TYPE_JUNK_FILTER, e_spam_assassin_interface_init)) #ifdef G_OS_UNIX static void spam_assassin_cancelled_cb (GCancellable *cancellable, GPid *pid) { /* XXX On UNIX-like systems we can safely assume a GPid is the * process ID and use it to terminate the process via signal. */ kill (*pid, SIGTERM); } #endif static void spam_assassin_exited_cb (GPid *pid, gint status, gpointer user_data) { struct { GMainLoop *loop; gint exit_code; } *source_data = user_data; if (WIFEXITED (status)) source_data->exit_code = WEXITSTATUS (status); else source_data->exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR; g_main_loop_quit (source_data->loop); } 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) { GMainContext *context; GSpawnFlags flags = 0; GSource *source; GPid child_pid; gint standard_input; gint standard_output; gulong handler_id = 0; gboolean success; struct { GMainLoop *loop; gint exit_code; } source_data; if (wait_for_termination) flags |= G_SPAWN_DO_NOT_REAP_CHILD; if (output_buffer == NULL) flags |= G_SPAWN_STDOUT_TO_DEV_NULL; flags |= G_SPAWN_STDERR_TO_DEV_NULL; /* Spawn SpamAssassin with an open stdin pipe. */ success = g_spawn_async_with_pipes ( NULL, (gchar **) argv, NULL, flags, NULL, NULL, &child_pid, &standard_input, (output_buffer != NULL) ? &standard_output : NULL, NULL, error); if (!success) { gchar *command_line; command_line = g_strjoinv (" ", (gchar **) argv); g_prefix_error ( error, _("Failed to spawn SpamAssassin (%s): "), command_line); g_free (command_line); return SPAM_ASSASSIN_EXIT_STATUS_ERROR; } if (message != NULL) { CamelStream *stream; gssize bytes_written; /* Stream the CamelMimeMessage to SpamAssassin. */ stream = camel_stream_fs_new_with_fd (standard_input); bytes_written = camel_data_wrapper_write_to_stream_sync ( CAMEL_DATA_WRAPPER (message), stream, cancellable, error); success = (bytes_written >= 0) && (camel_stream_close (stream, cancellable, error) == 0); g_object_unref (stream); if (!success) { g_spawn_close_pid (child_pid); g_prefix_error ( error, _("Failed to stream mail " "message content to SpamAssassin: ")); return SPAM_ASSASSIN_EXIT_STATUS_ERROR; } } else if (input_data != NULL) { gssize bytes_written; /* Write raw data directly to SpamAssassin. */ bytes_written = camel_write ( standard_input, input_data, strlen (input_data), cancellable, error); success = (bytes_written >= 0); close (standard_input); if (!success) { g_spawn_close_pid (child_pid); g_prefix_error ( error, _("Failed to write '%s' " "to SpamAssassin: "), input_data); return SPAM_ASSASSIN_EXIT_STATUS_ERROR; } } if (output_buffer != NULL) { CamelStream *input_stream; CamelStream *output_stream; gssize bytes_written; input_stream = camel_stream_fs_new_with_fd (standard_output); output_stream = camel_stream_mem_new (); camel_stream_mem_set_byte_array ( CAMEL_STREAM_MEM (output_stream), output_buffer); bytes_written = camel_stream_write_to_stream ( input_stream, output_stream, cancellable, error); g_byte_array_append (output_buffer, (guint8 *) "", 1); success = (bytes_written >= 0); g_object_unref (input_stream); g_object_unref (output_stream); if (!success) { g_spawn_close_pid (child_pid); g_prefix_error ( error, _("Failed to read " "output from SpamAssassin: ")); return SPAM_ASSASSIN_EXIT_STATUS_ERROR; } } /* XXX I'm not sure if we should call g_spawn_close_pid() * here or not. Only really matters on Windows anyway. */ if (!wait_for_termination) return 0; /* Wait for the SpamAssassin process to terminate * using GLib's main loop for better portability. */ context = g_main_context_new (); source = g_child_watch_source_new (child_pid); g_source_set_callback ( source, (GSourceFunc) spam_assassin_exited_cb, &source_data, NULL); g_source_attach (source, context); g_source_unref (source); source_data.loop = g_main_loop_new (context, TRUE); source_data.exit_code = 0; #ifdef G_OS_UNIX if (G_IS_CANCELLABLE (cancellable)) handler_id = g_cancellable_connect ( cancellable, G_CALLBACK (spam_assassin_cancelled_cb), &child_pid, (GDestroyNotify) NULL); #endif g_main_loop_run (source_data.loop); if (handler_id > 0) g_cancellable_disconnect (cancellable, handler_id); g_main_loop_unref (source_data.loop); source_data.loop = NULL; g_main_context_unref (context); /* Clean up. */ g_spawn_close_pid (child_pid); if (g_cancellable_set_error_if_cancelled (cancellable, error)) source_data.exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR; else if (source_data.exit_code == SPAM_ASSASSIN_EXIT_STATUS_ERROR) g_set_error_literal ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("SpamAssassin either crashed or " "failed to process a mail message")); return source_data.exit_code; } static gint spam_assassin_command (const gchar **argv, CamelMimeMessage *message, const gchar *input_data, GCancellable *cancellable, GError **error) { return spam_assassin_command_full ( argv, message, input_data, NULL, TRUE, cancellable, error); } static gboolean spam_assassin_get_local_only (ESpamAssassin *extension) { return extension->local_only; } static void spam_assassin_set_local_only (ESpamAssassin *extension, gboolean local_only) { extension->local_only = local_only; g_object_notify (G_OBJECT (extension), "local-only"); } static const gchar * spam_assassin_get_spamc_binary (ESpamAssassin *extension) { return extension->spamc_binary; } static void spam_assassin_set_spamc_binary (ESpamAssassin *extension, const gchar *spamc_binary) { g_free (extension->spamc_binary); extension->spamc_binary = g_strdup (spamc_binary); g_object_notify (G_OBJECT (extension), "spamc-binary"); } static const gchar * spam_assassin_get_spamd_binary (ESpamAssassin *extension) { return extension->spamd_binary; } static void spam_assassin_set_spamd_binary (ESpamAssassin *extension, const gchar *spamd_binary) { g_free (extension->spamd_binary); extension->spamd_binary = g_strdup (spamd_binary); g_object_notify (G_OBJECT (extension), "spamd-binary"); } static const gchar * spam_assassin_get_socket_path (ESpamAssassin *extension) { return extension->socket_path; } static void spam_assassin_set_socket_path (ESpamAssassin *extension, const gchar *socket_path) { g_free (extension->socket_path); extension->socket_path = g_strdup (socket_path); g_object_notify (G_OBJECT (extension), "socket-path"); } static gboolean spam_assassin_get_use_daemon (ESpamAssassin *extension) { return extension->use_daemon; } static void spam_assassin_set_use_daemon (ESpamAssassin *extension, gboolean use_daemon) { extension->use_daemon = use_daemon; g_object_notify (G_OBJECT (extension), "use-daemon"); } static gboolean spam_assassin_get_version (ESpamAssassin *extension, gint *spam_assassin_version, GCancellable *cancellable, GError **error) { GByteArray *output_buffer; gint exit_code; guint ii; const gchar *argv[] = { SA_LEARN_BINARY, "--version", NULL }; if (extension->version_set) { if (spam_assassin_version != NULL) *spam_assassin_version = extension->version; return TRUE; } output_buffer = g_byte_array_new (); exit_code = spam_assassin_command_full ( argv, NULL, NULL, output_buffer, TRUE, cancellable, error); if (exit_code != 0) { g_byte_array_free (output_buffer, TRUE); return FALSE; } for (ii = 0; ii < output_buffer->len; ii++) { if (g_ascii_isdigit (output_buffer->data[ii])) { guint8 ch = output_buffer->data[ii]; extension->version = (ch - '0'); extension->version_set = TRUE; break; } } if (spam_assassin_version != NULL) *spam_assassin_version = extension->version; g_byte_array_free (output_buffer, TRUE); return TRUE; } static void spam_assassin_test_spamd_allow_tell (ESpamAssassin *extension) { gint exit_code; GError *error = NULL; const gchar *argv[] = { SPAMC_BINARY, "--learntype=forget", NULL }; /* Check if spamd is running with --allow-tell. */ exit_code = spam_assassin_command (argv, NULL, "\n", NULL, &error); extension->spamd_using_allow_tell = (exit_code == 0); if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } } static gboolean spam_assassin_test_spamd_running (ESpamAssassin *extension, gboolean system_spamd) { const gchar *argv[5]; gint exit_code; gint ii = 0; GError *error = NULL; g_mutex_lock (extension->socket_path_mutex); argv[ii++] = extension->spamc_binary; argv[ii++] = "--no-safe-fallback"; if (!system_spamd) { argv[ii++] = "--socket"; argv[ii++] = extension->socket_path; } argv[ii] = NULL; g_assert (ii < G_N_ELEMENTS (argv)); exit_code = spam_assassin_command ( argv, NULL, "From test@127.0.0.1", NULL, &error); if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } g_mutex_unlock (extension->socket_path_mutex); return (exit_code == 0); } static void spam_assassin_kill_our_own_daemon (ESpamAssassin *extension) { gint pid; gchar *contents = NULL; GError *error = NULL; g_mutex_lock (extension->socket_path_mutex); g_free (extension->socket_path); extension->socket_path = NULL; g_mutex_unlock (extension->socket_path_mutex); if (extension->pid_file == NULL) return; g_file_get_contents (extension->pid_file, &contents, NULL, &error); if (error != NULL) { g_warn_if_fail (contents == NULL); g_warning ("%s", error->message); g_error_free (error); return; } g_return_if_fail (contents != NULL); pid = atoi (contents); g_free (contents); if (pid > 0 && kill (pid, SIGTERM) == 0) waitpid (pid, NULL, 0); } static void spam_assassin_prepare_for_quit (EShell *shell, EActivity *activity, ESpamAssassin *extension) { spam_assassin_kill_our_own_daemon (extension); } static gboolean spam_assassin_start_our_own_daemon (ESpamAssassin *extension) { const gchar *argv[8]; gchar *pid_file; gchar *socket_path; gboolean started = FALSE; gint exit_code; gint ii = 0; GError *error = NULL; g_mutex_lock (extension->socket_path_mutex); pid_file = e_mktemp ("spamd-pid-file-XXXXXX"); socket_path = e_mktemp ("spamd-socket-path-XXXXXX"); argv[ii++] = extension->spamd_binary; argv[ii++] = "--socketpath"; argv[ii++] = socket_path; if (spam_assassin_get_local_only (extension)) argv[ii++] = "--local"; argv[ii++] = "--max-children=1"; argv[ii++] = "--pidfile"; argv[ii++] = pid_file; argv[ii] = NULL; g_assert (ii < G_N_ELEMENTS (argv)); exit_code = spam_assassin_command_full ( argv, NULL, NULL, NULL, FALSE, NULL, &error); if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); goto exit; } if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS) { /* Wait for the socket path to appear. */ for (ii = 0; ii < DAEMON_MAX_RETRIES; ii++) { if (g_file_test (socket_path, G_FILE_TEST_EXISTS)) { started = TRUE; break; } g_usleep (DAEMON_RETRY_DELAY * G_USEC_PER_SEC); } } /* Set these directly to avoid emitting "notify" signals. */ if (started) { g_free (extension->pid_file); extension->pid_file = pid_file; pid_file = NULL; g_free (extension->socket_path); extension->socket_path = socket_path; socket_path = NULL; /* XXX EMailSession is too prone to reference leaks to leave * this for our finalize() method. We want to be sure to * kill the spamd process we started when Evolution shuts * down, so connect to an EShell signal instead. */ g_signal_connect ( e_shell_get_default (), "prepare-for-quit", G_CALLBACK (spam_assassin_prepare_for_quit), extension); } exit: g_free (pid_file); g_free (socket_path); g_mutex_unlock (extension->socket_path_mutex); return started; } static void spam_assassin_test_spamd (ESpamAssassin *extension) { const gchar *spamd_binary; gboolean try_system_spamd; /* XXX SpamAssassin could really benefit from a D-Bus interface * these days. These tests are just needlessly painful for * clients trying to talk to an already-running spamd. */ extension->use_spamc = FALSE; spamd_binary = extension->spamd_binary; try_system_spamd = (g_strcmp0 (spamd_binary, SPAMD_BINARY) == 0); if (extension->local_only && try_system_spamd) { gint exit_code; /* Run a shell command to check for a running * spamd process with a -L/--local option or a * -p/--port option. */ const gchar *argv[] = { "/bin/sh", "-c", "ps ax | grep -v grep | " "grep -E 'spamd.*(\\-L|\\-\\-local)' | " "grep -E -v '\\ \\-p\\ |\\ \\-\\-port\\ '", NULL }; exit_code = spam_assassin_command ( argv, NULL, NULL, NULL, NULL); try_system_spamd = (exit_code == 0); } /* Try to use the system spamd first. */ if (try_system_spamd) { if (spam_assassin_test_spamd_running (extension, TRUE)) { extension->use_spamc = TRUE; extension->system_spamd_available = TRUE; } } /* If there's no system spamd running, try * to use one with a user specified socket. */ if (!extension->use_spamc && extension->socket_path != NULL) { if (spam_assassin_test_spamd_running (extension, FALSE)) { extension->use_spamc = TRUE; extension->system_spamd_available = FALSE; } } /* Still unsuccessful? Try to start our own spamd. */ if (!extension->use_spamc) { extension->use_spamc = spam_assassin_start_our_own_daemon (extension) && spam_assassin_test_spamd_running (extension, FALSE); } } static void spam_assassin_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_LOCAL_ONLY: spam_assassin_set_local_only ( E_SPAM_ASSASSIN (object), g_value_get_boolean (value)); return; case PROP_SPAMC_BINARY: spam_assassin_set_spamc_binary ( E_SPAM_ASSASSIN (object), g_value_get_string (value)); return; case PROP_SPAMD_BINARY: spam_assassin_set_spamd_binary ( E_SPAM_ASSASSIN (object), g_value_get_string (value)); return; case PROP_SOCKET_PATH: spam_assassin_set_socket_path ( E_SPAM_ASSASSIN (object), g_value_get_string (value)); return; case PROP_USE_DAEMON: spam_assassin_set_use_daemon ( E_SPAM_ASSASSIN (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void spam_assassin_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_LOCAL_ONLY: g_value_set_boolean ( value, spam_assassin_get_local_only ( E_SPAM_ASSASSIN (object))); return; case PROP_SPAMC_BINARY: g_value_set_string ( value, spam_assassin_get_spamc_binary ( E_SPAM_ASSASSIN (object))); return; case PROP_SPAMD_BINARY: g_value_set_string ( value, spam_assassin_get_spamd_binary ( E_SPAM_ASSASSIN (object))); return; case PROP_SOCKET_PATH: g_value_set_string ( value, spam_assassin_get_socket_path ( E_SPAM_ASSASSIN (object))); return; case PROP_USE_DAEMON: g_value_set_boolean ( value, spam_assassin_get_use_daemon ( E_SPAM_ASSASSIN (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void spam_assassin_finalize (GObject *object) { ESpamAssassin *extension = E_SPAM_ASSASSIN (object); g_mutex_free (extension->socket_path_mutex); g_free (extension->pid_file); g_free (extension->socket_path); g_free (extension->spamc_binary); g_free (extension->spamd_binary); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_spam_assassin_parent_class)->finalize (object); } static gboolean spam_assassin_available (EMailJunkFilter *junk_filter) { ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter); gboolean available; GError *error = NULL; available = spam_assassin_get_version (extension, NULL, NULL, &error); /* XXX These tests block like crazy so maybe this isn't the best * place to be doing this, but the first available() call is * done at startup before the UI is shown. So hopefully the * delay will not be noticeable. */ if (available && extension->use_daemon && !extension->spamd_tested) { extension->spamd_tested = TRUE; spam_assassin_test_spamd (extension); spam_assassin_test_spamd_allow_tell (extension); } if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } return available; } static GtkWidget * spam_assassin_new_config_widget (EMailJunkFilter *junk_filter) { GtkWidget *box; GtkWidget *widget; GtkWidget *container; gchar *markup; box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); markup = g_markup_printf_escaped ( "%s", _("SpamAssassin Options")); widget = gtk_label_new (markup); gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); gtk_widget_show (widget); g_free (markup); widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); gtk_widget_show (widget); container = widget; widget = gtk_check_button_new_with_mnemonic ( _("I_nclude remote tests")); gtk_widget_set_margin_left (widget, 12); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); gtk_widget_show (widget); g_object_bind_property ( junk_filter, "local-only", widget, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); markup = g_markup_printf_escaped ( "%s", _("This will make SpamAssassin more reliable, but slower.")); widget = gtk_label_new (markup); gtk_widget_set_margin_left (widget, 36); gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); gtk_widget_show (widget); g_free (markup); return box; } static gboolean spam_assassin_classify (CamelJunkFilter *junk_filter, CamelMimeMessage *message, CamelJunkStatus *status, GCancellable *cancellable, GError **error) { ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter); const gchar *argv[7]; gboolean using_spamc; gint exit_code; gint ii = 0; g_mutex_lock (extension->socket_path_mutex); using_spamc = (extension->use_spamc && extension->use_daemon); if (using_spamc) { argv[ii++] = extension->spamc_binary; argv[ii++] = "--check"; argv[ii++] = "--timeout=60"; if (!extension->system_spamd_available) { argv[ii++] = "--socket"; argv[ii++] = extension->socket_path; } } else { argv[ii++] = SPAMASSASSIN_BINARY; argv[ii++] = "--exit-code"; if (extension->local_only) argv[ii++] = "--local"; } argv[ii] = NULL; g_assert (ii < G_N_ELEMENTS (argv)); exit_code = spam_assassin_command ( argv, message, NULL, cancellable, error); /* For either program, exit code 0 means the message is ham. */ if (exit_code == 0) *status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK; /* spamassassin(1) only specifies zero and non-zero exit codes. */ else if (!using_spamc) *status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK; /* Whereas spamc(1) explicitly states exit code 1 means spam. */ else if (exit_code == 1) *status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK; /* Consider any other spamc(1) exit code to be inconclusive * since it most likely failed to process the message. */ else *status = CAMEL_JUNK_STATUS_INCONCLUSIVE; /* Check that the return value and GError agree. */ if (exit_code != SPAM_ASSASSIN_EXIT_STATUS_ERROR) g_warn_if_fail (error == NULL || *error == NULL); else g_warn_if_fail (error == NULL || *error != NULL); g_mutex_unlock (extension->socket_path_mutex); return (exit_code != SPAM_ASSASSIN_EXIT_STATUS_ERROR); } static gboolean spam_assassin_learn_junk (CamelJunkFilter *junk_filter, CamelMimeMessage *message, GCancellable *cancellable, GError **error) { ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter); const gchar *argv[5]; gint exit_code; gint ii = 0; if (extension->spamd_using_allow_tell) { argv[ii++] = extension->spamc_binary; argv[ii++] = "--learntype=spam"; } else { argv[ii++] = SA_LEARN_BINARY; argv[ii++] = "--spam"; if (extension->version >= 3) argv[ii++] = "--no-sync"; else argv[ii++] = "--no-rebuild"; if (extension->local_only) argv[ii++] = "--local"; } argv[ii] = NULL; g_assert (ii < G_N_ELEMENTS (argv)); exit_code = spam_assassin_command ( argv, message, NULL, cancellable, error); /* Check that the return value and GError agree. */ if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS) g_warn_if_fail (error == NULL || *error == NULL); else g_warn_if_fail (error == NULL || *error != NULL); return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS); } static gboolean spam_assassin_learn_not_junk (CamelJunkFilter *junk_filter, CamelMimeMessage *message, GCancellable *cancellable, GError **error) { ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter); const gchar *argv[5]; gint exit_code; gint ii = 0; if (extension->spamd_using_allow_tell) { argv[ii++] = extension->spamc_binary; argv[ii++] = "--learntype=ham"; } else { argv[ii++] = SA_LEARN_BINARY; argv[ii++] = "--ham"; if (extension->version >= 3) argv[ii++] = "--no-sync"; else argv[ii++] = "--no-rebuild"; if (extension->local_only) argv[ii++] = "--local"; } argv[ii] = NULL; g_assert (ii < G_N_ELEMENTS (argv)); exit_code = spam_assassin_command ( argv, message, NULL, cancellable, error); /* Check that the return value and GError agree. */ if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS) g_warn_if_fail (error == NULL || *error == NULL); else g_warn_if_fail (error == NULL || *error != NULL); return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS); } static gboolean spam_assassin_synchronize (CamelJunkFilter *junk_filter, GCancellable *cancellable, GError **error) { ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter); const gchar *argv[4]; gint exit_code; gint ii = 0; /* If we're using a spamd that allows learning, * there's no need to synchronize anything. */ if (extension->spamd_using_allow_tell) return TRUE; argv[ii++] = SA_LEARN_BINARY; if (extension->version >= 3) argv[ii++] = "--sync"; else argv[ii++] = "--rebuild"; if (extension->local_only) argv[ii++] = "--local"; argv[ii] = NULL; g_assert (ii < G_N_ELEMENTS (argv)); exit_code = spam_assassin_command ( argv, NULL, NULL, cancellable, error); /* Check that the return value and GError agree. */ if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS) g_warn_if_fail (error == NULL || *error == NULL); else g_warn_if_fail (error == NULL || *error != NULL); return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS); } static void e_spam_assassin_class_init (ESpamAssassinClass *class) { GObjectClass *object_class; EMailJunkFilterClass *junk_filter_class; object_class = G_OBJECT_CLASS (class); object_class->set_property = spam_assassin_set_property; object_class->get_property = spam_assassin_get_property; object_class->finalize = spam_assassin_finalize; junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class); junk_filter_class->filter_name = "SpamAssassin"; junk_filter_class->display_name = _("SpamAssassin"); junk_filter_class->available = spam_assassin_available; junk_filter_class->new_config_widget = spam_assassin_new_config_widget; /* XXX Argh, the boolean sense of the GConf key is inverted from * that of the checkbox widget. The checkbox wording is more * natural, but GConfBridge doesn't support transform functions * so the property has to match the sense of the GConf key. */ g_object_class_install_property ( object_class, PROP_LOCAL_ONLY, g_param_spec_boolean ( "local-only", "Local Only", "Do not use tests requiring DNS lookups", TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SPAMC_BINARY, g_param_spec_string ( "spamc-binary", "spamc Binary", "File path for the spamc binary", NULL, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SPAMD_BINARY, g_param_spec_string ( "spamd-binary", "spamd Binary", "File path for the spamd binary", NULL, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SOCKET_PATH, g_param_spec_string ( "socket-path", "Socket Path", "Socket path for a SpamAssassin daemon", NULL, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_USE_DAEMON, g_param_spec_boolean ( "use-daemon", "Use Daemon", "Whether to use a SpamAssassin daemon", FALSE, G_PARAM_READWRITE)); } static void e_spam_assassin_class_finalize (ESpamAssassinClass *class) { } static void e_spam_assassin_interface_init (CamelJunkFilterInterface *interface) { interface->classify = spam_assassin_classify; interface->learn_junk = spam_assassin_learn_junk; interface->learn_not_junk = spam_assassin_learn_not_junk; interface->synchronize = spam_assassin_synchronize; } static void e_spam_assassin_init (ESpamAssassin *extension) { extension->socket_path_mutex = g_mutex_new (); /* XXX Once we move to GSettings these probably don't * need to be properties anymore. GConfBridge is * just easier to deal with than GConfClient. */ gconf_bridge_bind_property ( gconf_bridge_get (), "/apps/evolution/mail/junk/sa/local_only", G_OBJECT (extension), "local-only"); gconf_bridge_bind_property ( gconf_bridge_get (), "/apps/evolution/mail/junk/sa/spamc_binary", G_OBJECT (extension), "spamc-binary"); gconf_bridge_bind_property ( gconf_bridge_get (), "/apps/evolution/mail/junk/sa/spamd_binary", G_OBJECT (extension), "spamd-binary"); gconf_bridge_bind_property ( gconf_bridge_get (), "/apps/evolution/mail/junk/sa/socket_path", G_OBJECT (extension), "socket-path"); gconf_bridge_bind_property ( gconf_bridge_get (), "/apps/evolution/mail/junk/sa/use_daemon", G_OBJECT (extension), "use-daemon"); if (extension->spamc_binary == NULL) extension->spamc_binary = g_strdup (SPAMC_BINARY); if (extension->spamd_binary == NULL) extension->spamd_binary = g_strdup (SPAMD_BINARY); } G_MODULE_EXPORT void e_module_load (GTypeModule *type_module) { e_spam_assassin_register_type (type_module); } G_MODULE_EXPORT void e_module_unload (GTypeModule *type_module) { }