diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/Makefile.am | 2 | ||||
-rw-r--r-- | modules/bogofilter/Makefile.am | 57 | ||||
-rw-r--r-- | modules/bogofilter/evolution-bogofilter.c | 524 | ||||
-rw-r--r-- | modules/bogofilter/evolution-bogofilter.schemas.in | 20 | ||||
-rw-r--r-- | modules/mail/Makefile.am | 2 | ||||
-rw-r--r-- | modules/mail/e-mail-junk-hook.c | 344 | ||||
-rw-r--r-- | modules/mail/e-mail-junk-hook.h | 66 | ||||
-rw-r--r-- | modules/mail/em-mailer-prefs.c | 164 | ||||
-rw-r--r-- | modules/mail/em-mailer-prefs.h | 6 | ||||
-rw-r--r-- | modules/mail/evolution-module-mail.c | 2 | ||||
-rw-r--r-- | modules/spamassassin/Makefile.am | 56 | ||||
-rw-r--r-- | modules/spamassassin/evolution-spamassassin.c | 1177 | ||||
-rw-r--r-- | modules/spamassassin/evolution-spamassassin.schemas.in | 33 |
13 files changed, 1888 insertions, 565 deletions
diff --git a/modules/Makefile.am b/modules/Makefile.am index 9a32c72ebc..dd363d27a3 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -24,6 +24,7 @@ endif SUBDIRS = \ addressbook \ + bogofilter \ calendar \ mail \ composer-autosave \ @@ -31,6 +32,7 @@ SUBDIRS = \ offline-alert \ plugin-lib \ plugin-manager \ + spamassassin \ startup-wizard \ $(MONO_DIR) \ $(PYTHON_DIR) \ diff --git a/modules/bogofilter/Makefile.am b/modules/bogofilter/Makefile.am new file mode 100644 index 0000000000..7803267836 --- /dev/null +++ b/modules/bogofilter/Makefile.am @@ -0,0 +1,57 @@ +module_LTLIBRARIES = libevolution-module-bogofilter.la + +libevolution_module_bogofilter_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"evolution-bogofilter\" \ + -DWELCOME_MESSAGE=\""$(privdatadir)/default/C/mail/local/Inbox"\" \ + $(GNOME_PLATFORM_CFLAGS) \ + $(EVOLUTION_MAIL_CFLAGS) + +libevolution_module_bogofilter_la_SOURCES = \ + evolution-bogofilter.c + +libevolution_module_bogofilter_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(GNOME_PLATFORM_LIBS) \ + $(EVOLUTION_MAIL_LIBS) + +libevolution_module_bogofilter_la_LDFLAGS = \ + -module -avoid-version $(NO_UNDEFINED) + +schemadir = $(GCONF_SCHEMA_FILE_DIR) +schema_in_files = evolution-bogofilter.schemas.in +schema_DATA = $(schema_in_files:.schemas.in=.schemas) + +@INTLTOOL_SCHEMAS_RULE@ + +if GCONF_SCHEMAS_INSTALL + +if OS_WIN32 +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + (echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE); \ + echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat; \ + cmd /c _temp.bat; \ + rm _temp.bat; \ + done \ + fi +else +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) \ + $(GCONFTOOL) --makefile-install-rule $$p; \ + done \ + fi +endif + +endif + +DISTCLEANFILES = $(schema_DATA) + +EXTRA_DIST = $(schema_in_files) + +-include $(top_srcdir)/git.mk diff --git a/modules/bogofilter/evolution-bogofilter.c b/modules/bogofilter/evolution-bogofilter.c new file mode 100644 index 0000000000..2cc7a64359 --- /dev/null +++ b/modules/bogofilter/evolution-bogofilter.c @@ -0,0 +1,524 @@ +/* + * evolution-bogofilter.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 <http://www.gnu.org/licenses/> + * + */ + +#include <config.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <glib/gi18n-lib.h> + +#include <camel/camel.h> + +#include <e-util/gconf-bridge.h> +#include <mail/e-mail-junk-filter.h> + +/* Standard GObject macros */ +#define E_TYPE_BOGOFILTER \ + (e_bogofilter_get_type ()) +#define E_BOGOFILTER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_BOGOFILTER, EBogofilter)) + +#ifndef BOGOFILTER_BINARY +#define BOGOFILTER_BINARY "/usr/bin/bogofilter" +#endif + +#define BOGOFILTER_EXIT_STATUS_SPAM 0 +#define BOGOFILTER_EXIT_STATUS_HAM 1 +#define BOGOFILTER_EXIT_STATUS_UNSURE 2 +#define BOGOFILTER_EXIT_STATUS_ERROR 3 + +typedef struct _EBogofilter EBogofilter; +typedef struct _EBogofilterClass EBogofilterClass; + +struct _EBogofilter { + EMailJunkFilter parent; + gboolean convert_to_unicode; +}; + +struct _EBogofilterClass { + EMailJunkFilterClass parent_class; +}; + +enum { + PROP_0, + PROP_CONVERT_TO_UNICODE +}; + +/* Module Entry Points */ +void e_module_load (GTypeModule *type_module); +void e_module_unload (GTypeModule *type_module); + +/* Forward Declarations */ +GType e_bogofilter_get_type (void); +static void e_bogofilter_interface_init (CamelJunkFilterInterface *interface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EBogofilter, + e_bogofilter, + E_TYPE_MAIL_JUNK_FILTER, 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + CAMEL_TYPE_JUNK_FILTER, + e_bogofilter_interface_init)) + +#ifdef G_OS_UNIX +static void +bogofilter_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 +bogofilter_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 = BOGOFILTER_EXIT_STATUS_ERROR; + + g_main_loop_quit (source_data->loop); +} + +static gint +bogofilter_command (const gchar **argv, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelStream *stream; + GMainContext *context; + GSource *source; + GPid child_pid; + gssize bytes_written; + gint standard_input; + gulong handler_id = 0; + gboolean success; + + struct { + GMainLoop *loop; + gint exit_code; + } source_data; + + /* Spawn Bogofilter with an open stdin pipe. */ + success = g_spawn_async_with_pipes ( + NULL, + (gchar **) argv, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_STDOUT_TO_DEV_NULL, + NULL, NULL, + &child_pid, + &standard_input, + NULL, + NULL, + error); + + if (!success) { + gchar *command_line; + + command_line = g_strjoinv (" ", (gchar **) argv); + g_prefix_error ( + error, _("Failed to spawn Bogofilter (%s): "), + command_line); + g_free (command_line); + + return BOGOFILTER_EXIT_STATUS_ERROR; + } + + /* Stream the CamelMimeMessage to Bogofilter. */ + 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 Bogofilter: ")); + return BOGOFILTER_EXIT_STATUS_ERROR; + } + + /* Wait for the Bogofilter 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) + bogofilter_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 (bogofilter_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 = BOGOFILTER_EXIT_STATUS_ERROR; + + else if (source_data.exit_code == BOGOFILTER_EXIT_STATUS_ERROR) + g_set_error_literal ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Bogofilter either crashed or " + "failed to process a mail message")); + + return source_data.exit_code; +} + +static void +bogofilter_init_wordlist (EBogofilter *extension) +{ + CamelStream *stream; + CamelMimeParser *parser; + CamelMimeMessage *message; + + /* Initialize the Bogofilter database with a welcome message. */ + + parser = camel_mime_parser_new (); + message = camel_mime_message_new (); + + stream = camel_stream_fs_new_with_name ( + WELCOME_MESSAGE, O_RDONLY, 0, NULL); + camel_mime_parser_init_with_stream (parser, stream, NULL); + camel_mime_parser_scan_from (parser, FALSE); + g_object_unref (stream); + + camel_mime_part_construct_from_parser_sync ( + CAMEL_MIME_PART (message), parser, NULL, NULL); + + camel_junk_filter_learn_not_junk ( + CAMEL_JUNK_FILTER (extension), message, NULL, NULL); + + g_object_unref (message); + g_object_unref (parser); +} + +static gboolean +bogofilter_get_convert_to_unicode (EBogofilter *extension) +{ + return extension->convert_to_unicode; +} + +static void +bogofilter_set_convert_to_unicode (EBogofilter *extension, + gboolean convert_to_unicode) +{ + extension->convert_to_unicode = convert_to_unicode; + + g_object_notify (G_OBJECT (extension), "convert-to-unicode"); +} + +static void +bogofilter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CONVERT_TO_UNICODE: + bogofilter_set_convert_to_unicode ( + E_BOGOFILTER (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +bogofilter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CONVERT_TO_UNICODE: + g_value_set_boolean ( + value, bogofilter_get_convert_to_unicode ( + E_BOGOFILTER (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static gboolean +bogofilter_available (EMailJunkFilter *junk_filter) +{ + return g_file_test (BOGOFILTER_BINARY, G_FILE_TEST_IS_EXECUTABLE); +} + +static GtkWidget * +bogofilter_new_config_widget (EMailJunkFilter *junk_filter) +{ + GtkWidget *box; + GtkWidget *widget; + gchar *markup; + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + + markup = g_markup_printf_escaped ( + "<b>%s</b>", _("Bogofilter 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_check_button_new_with_mnemonic ( + _("Convert message text to _Unicode")); + gtk_widget_set_margin_left (widget, 12); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); + + g_object_bind_property ( + junk_filter, "convert-to-unicode", + widget, "active", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + return box; +} + +static gboolean +bogofilter_classify (CamelJunkFilter *junk_filter, + CamelMimeMessage *message, + CamelJunkStatus *status, + GCancellable *cancellable, + GError **error) +{ + EBogofilter *extension = E_BOGOFILTER (junk_filter); + static gboolean wordlist_initialized = FALSE; + gint exit_code; + + const gchar *argv[] = { + BOGOFILTER_BINARY, + NULL, /* leave room for unicode option */ + NULL + }; + + if (bogofilter_get_convert_to_unicode (extension)) + argv[1] = "--unicode=yes"; + +retry: + exit_code = bogofilter_command (argv, message, cancellable, error); + + switch (exit_code) { + case BOGOFILTER_EXIT_STATUS_SPAM: + *status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK; + break; + + case BOGOFILTER_EXIT_STATUS_HAM: + *status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK; + break; + + case BOGOFILTER_EXIT_STATUS_UNSURE: + *status = CAMEL_JUNK_STATUS_INCONCLUSIVE; + break; + + case BOGOFILTER_EXIT_STATUS_ERROR: + if (!wordlist_initialized) { + wordlist_initialized = TRUE; + bogofilter_init_wordlist (extension); + goto retry; + } + break; + + default: + g_warning ( + "Bogofilter: Unexpected exit code (%d) " + "while classifying message", exit_code); + break; + } + + /* Check that the return value and GError agree. */ + if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR) + g_warn_if_fail (error == NULL || *error == NULL); + else + g_warn_if_fail (error == NULL || *error != NULL); + + return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR); +} + +static gboolean +bogofilter_learn_junk (CamelJunkFilter *junk_filter, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + EBogofilter *extension = E_BOGOFILTER (junk_filter); + gint exit_code; + + const gchar *argv[] = { + BOGOFILTER_BINARY, + "--register-spam", + NULL, /* leave room for unicode option */ + NULL + }; + + if (bogofilter_get_convert_to_unicode (extension)) + argv[2] = "--unicode=yes"; + + exit_code = bogofilter_command (argv, message, cancellable, error); + + if (exit_code != 0) + g_warning ( + "Bogofilter: Unexpected exit code (%d) " + "while registering spam", exit_code); + + /* Check that the return value and GError agree. */ + if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR) + g_warn_if_fail (error == NULL || *error == NULL); + else + g_warn_if_fail (error == NULL || *error != NULL); + + return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR); +} + +static gboolean +bogofilter_learn_not_junk (CamelJunkFilter *junk_filter, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + EBogofilter *extension = E_BOGOFILTER (junk_filter); + gint exit_code; + + const gchar *argv[] = { + BOGOFILTER_BINARY, + "--register-ham", + NULL, /* leave room for unicode option */ + NULL + }; + + if (bogofilter_get_convert_to_unicode (extension)) + argv[2] = "--unicode=yes"; + + exit_code = bogofilter_command (argv, message, cancellable, error); + + if (exit_code != 0) + g_warning ( + "Bogofilter: Unexpected exit code (%d) " + "while registering ham", exit_code); + + /* Check that the return value and GError agree. */ + if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR) + g_warn_if_fail (error == NULL || *error == NULL); + else + g_warn_if_fail (error == NULL || *error != NULL); + + return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR); +} + +static void +e_bogofilter_class_init (EBogofilterClass *class) +{ + GObjectClass *object_class; + EMailJunkFilterClass *junk_filter_class; + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = bogofilter_set_property; + object_class->get_property = bogofilter_get_property; + + junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class); + junk_filter_class->filter_name = "Bogofilter"; + junk_filter_class->display_name = _("Bogofilter"); + junk_filter_class->available = bogofilter_available; + junk_filter_class->new_config_widget = bogofilter_new_config_widget; + + g_object_class_install_property ( + object_class, + PROP_CONVERT_TO_UNICODE, + g_param_spec_boolean ( + "convert-to-unicode", + "Convert to Unicode", + "Convert message text to Unicode", + TRUE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); +} + +static void +e_bogofilter_class_finalize (EBogofilterClass *class) +{ +} + +static void +e_bogofilter_interface_init (CamelJunkFilterInterface *interface) +{ + interface->classify = bogofilter_classify; + interface->learn_junk = bogofilter_learn_junk; + interface->learn_not_junk = bogofilter_learn_not_junk; +} + +static void +e_bogofilter_init (EBogofilter *extension) +{ + gconf_bridge_bind_property ( + gconf_bridge_get (), + "/apps/evolution/mail/junk/bogofilter/unicode", + G_OBJECT (extension), "convert-to-unicode"); +} + +G_MODULE_EXPORT void +e_module_load (GTypeModule *type_module) +{ + e_bogofilter_register_type (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ +} diff --git a/modules/bogofilter/evolution-bogofilter.schemas.in b/modules/bogofilter/evolution-bogofilter.schemas.in new file mode 100644 index 0000000000..e313eb9ffc --- /dev/null +++ b/modules/bogofilter/evolution-bogofilter.schemas.in @@ -0,0 +1,20 @@ +<gconfschemafile> + <schemalist> + + <schema> + <key>/schemas/apps/evolution/mail/junk/bogofilter/unicode</key> + <applyto>/apps/evolution/mail/junk/bogofilter/unicode</applyto> + <owner>evolution-bogofilter</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Convert mail messages to Unicode</short> + <long> + Convert message text to Unicode UTF-8 to unify spam/ham tokens + coming from different character sets. + </long> + </locale> + </schema> + + </schemalist> +</gconfschemafile> diff --git a/modules/mail/Makefile.am b/modules/mail/Makefile.am index 884d050d78..0bea9ac1a2 100644 --- a/modules/mail/Makefile.am +++ b/modules/mail/Makefile.am @@ -27,8 +27,6 @@ libevolution_module_mail_la_SOURCES = \ e-mail-config-web-view.h \ e-mail-event-hook.c \ e-mail-event-hook.h \ - e-mail-junk-hook.c \ - e-mail-junk-hook.h \ e-mail-shell-backend.c \ e-mail-shell-backend.h \ e-mail-shell-content.c \ diff --git a/modules/mail/e-mail-junk-hook.c b/modules/mail/e-mail-junk-hook.c deleted file mode 100644 index 4ccc404e8d..0000000000 --- a/modules/mail/e-mail-junk-hook.c +++ /dev/null @@ -1,344 +0,0 @@ -/* - * e-mail-junk-hook.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 <http://www.gnu.org/licenses/> - * - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "e-mail-junk-hook.h" - -#include <glib/gi18n.h> - -#include "e-util/e-alert-dialog.h" -#include "shell/e-shell.h" - -#include "mail/em-junk.h" -#include "mail/em-utils.h" -#include "mail/e-mail-backend.h" -#include "mail/e-mail-session.h" - -struct _EMailJunkHookPrivate { - EMJunkInterface interface; -}; - -struct ErrorData { - const gchar *error_message; - GError *error; -}; - -static gpointer parent_class; -static GType mail_junk_hook_type; - -static gboolean -mail_junk_hook_idle_cb (struct ErrorData *data) -{ - EShell *shell; - EShellBackend *shell_backend; - - shell = e_shell_get_default (); - shell_backend = e_shell_get_backend_by_name (shell, "mail"); - - e_mail_backend_submit_alert ( - E_MAIL_BACKEND (shell_backend), - data->error_message, data->error->message, NULL); - - g_error_free (data->error); - g_slice_free (struct ErrorData, data); - - return FALSE; -} - -static void -mail_junk_hook_error (const gchar *error_message, - GError *error) -{ - struct ErrorData *data; - - g_return_if_fail (error != NULL); - - data = g_slice_new (struct ErrorData); - data->error_message = error_message; - data->error = error; - - g_idle_add ((GSourceFunc) mail_junk_hook_idle_cb, data); -} - -static const gchar * -mail_junk_hook_get_name (CamelJunkPlugin *junk_plugin) -{ - EMJunkInterface *interface; - - interface = (EMJunkInterface *) junk_plugin; - - if (!interface->hook->plugin->enabled) { - /* Translators: "None" for a junk hook name, - * when the junk plugin is not enabled. */ - return C_("mail-junk-hook", "None"); - } - - return interface->hook->plugin->name; -} - -static void -mail_junk_hook_plugin_init (CamelJunkPlugin *junk_plugin) -{ - EMJunkInterface *interface; - EPluginClass *class; - - interface = (EMJunkInterface *) junk_plugin; - - class = E_PLUGIN_GET_CLASS (interface->hook->plugin); - g_return_if_fail (class->enable != NULL); - - class->enable (interface->hook->plugin, 1); -} - -static gboolean -mail_junk_hook_check_junk (CamelJunkPlugin *junk_plugin, - CamelMimeMessage *mime_message) -{ - EMJunkTarget target = { mime_message, NULL }; - EMJunkInterface *interface; - gpointer result; - - interface = (EMJunkInterface *) junk_plugin; - - if (!interface->hook->plugin->enabled) - return FALSE; - - result = e_plugin_invoke ( - interface->hook->plugin, - interface->check_junk, &target); - - if (target.error != NULL) - mail_junk_hook_error ("mail:junk-check-error", target.error); - - return (result != NULL); -} - -static void -mail_junk_hook_report_junk (CamelJunkPlugin *junk_plugin, - CamelMimeMessage *mime_message) -{ - EMJunkTarget target = { mime_message, NULL }; - EMJunkInterface *interface; - - interface = (EMJunkInterface *) junk_plugin; - - if (!interface->hook->plugin->enabled) - return; - - e_plugin_invoke ( - interface->hook->plugin, - interface->report_junk, &target); - - if (target.error != NULL) - mail_junk_hook_error ("mail:junk-report-error", target.error); -} - -static void -mail_junk_hook_report_notjunk (CamelJunkPlugin *junk_plugin, - CamelMimeMessage *mime_message) -{ - EMJunkTarget target = { mime_message, NULL }; - EMJunkInterface *interface; - - interface = (EMJunkInterface *) junk_plugin; - - if (!interface->hook->plugin->enabled) - return; - - e_plugin_invoke ( - interface->hook->plugin, - interface->report_notjunk, &target); - - if (target.error != NULL) - mail_junk_hook_error ( - "mail:junk-not-report-error", target.error); -} - -static void -mail_junk_hook_commit_reports (CamelJunkPlugin *junk_plugin) -{ - EMJunkInterface *interface; - - interface = (EMJunkInterface *) junk_plugin; - - if (!interface->hook->plugin->enabled) - return; - - e_plugin_invoke ( - interface->hook->plugin, - interface->commit_reports, NULL); -} - -static void -mail_junk_hook_finalize (GObject *object) -{ - EMailJunkHookPrivate *priv; - - priv = E_MAIL_JUNK_HOOK (object)->priv; - - g_free (priv->interface.check_junk); - g_free (priv->interface.report_junk); - g_free (priv->interface.report_notjunk); - g_free (priv->interface.commit_reports); - g_free (priv->interface.validate_binary); - g_free (priv->interface.plugin_name); - - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static gint -mail_junk_hook_construct (EPluginHook *hook, - EPlugin *plugin, - xmlNodePtr node) -{ - EMailJunkHookPrivate *priv; - EShell *shell; - EShellBackend *shell_backend; - EMailBackend *backend; - EMailSession *session; - gchar *property; - - priv = E_MAIL_JUNK_HOOK (hook)->priv; - - /* Chain up to parent's construct() method. */ - if (E_PLUGIN_HOOK_CLASS (parent_class)->construct (hook, plugin, node) == -1) - return -1; - - if (!plugin->enabled) - return -1; - - node = xmlFirstElementChild (node); - - if (node == NULL) - return -1; - - if (g_strcmp0 ((gchar *) node->name, "interface") != 0) - return -1; - - property = e_plugin_xml_prop (node, "check_junk"); - priv->interface.check_junk = property; - - property = e_plugin_xml_prop (node, "report_junk"); - priv->interface.report_junk = property; - - property = e_plugin_xml_prop (node, "report_non_junk"); - priv->interface.report_notjunk = property; - - property = e_plugin_xml_prop (node, "commit_reports"); - priv->interface.commit_reports = property; - - property = e_plugin_xml_prop (node, "validate_binary"); - priv->interface.validate_binary = property; - - property = e_plugin_xml_prop (node, "name"); - priv->interface.plugin_name = property; - - if (priv->interface.check_junk == NULL) - return -1; - - if (priv->interface.report_junk == NULL) - return -1; - - if (priv->interface.report_notjunk == NULL) - return -1; - - if (priv->interface.commit_reports == NULL) - return -1; - - shell = e_shell_get_default (); - shell_backend = e_shell_get_backend_by_name (shell, "mail"); - - backend = E_MAIL_BACKEND (shell_backend); - session = e_mail_backend_get_session (backend); - - mail_session_add_junk_plugin ( - session, priv->interface.plugin_name, - &priv->interface.camel); - - return 0; -} - -static void -mail_junk_hook_class_init (EMailJunkHookClass *class) -{ - GObjectClass *object_class; - EPluginHookClass *plugin_hook_class; - - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMailJunkHookPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->finalize = mail_junk_hook_finalize; - - plugin_hook_class = E_PLUGIN_HOOK_CLASS (class); - plugin_hook_class->construct = mail_junk_hook_construct; - plugin_hook_class->id = "org.gnome.evolution.mail.junk:1.0"; -} - -static void -mail_junk_hook_init (EMailJunkHook *mail_junk_hook) -{ - EMJunkInterface *interface; - - mail_junk_hook->priv = G_TYPE_INSTANCE_GET_PRIVATE ( - mail_junk_hook, E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookPrivate); - - interface = &mail_junk_hook->priv->interface; - interface->camel.get_name = mail_junk_hook_get_name; - interface->camel.api_version = 1; - interface->camel.check_junk = mail_junk_hook_check_junk; - interface->camel.report_junk = mail_junk_hook_report_junk; - interface->camel.report_notjunk = mail_junk_hook_report_notjunk; - interface->camel.commit_reports = mail_junk_hook_commit_reports; - interface->camel.init = mail_junk_hook_plugin_init; - interface->hook = E_PLUGIN_HOOK (mail_junk_hook); -} - -GType -e_mail_junk_hook_get_type (void) -{ - return mail_junk_hook_type; -} - -void -e_mail_junk_hook_register_type (GTypeModule *type_module) -{ - const GTypeInfo type_info = { - sizeof (EMailJunkHookClass), - (GBaseInitFunc) NULL, - (GBaseFinalizeFunc) NULL, - (GClassInitFunc) mail_junk_hook_class_init, - (GClassFinalizeFunc) NULL, - NULL, /* class_data */ - sizeof (EMailJunkHook), - 0, /* n_preallocs */ - (GInstanceInitFunc) mail_junk_hook_init, - NULL /* value_table */ - }; - - mail_junk_hook_type = g_type_module_register_type ( - type_module, E_TYPE_PLUGIN_HOOK, - "EMailJunkHook", &type_info, 0); -} diff --git a/modules/mail/e-mail-junk-hook.h b/modules/mail/e-mail-junk-hook.h deleted file mode 100644 index f5882e66b3..0000000000 --- a/modules/mail/e-mail-junk-hook.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * e-mail-junk-hook.h - * - * 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 <http://www.gnu.org/licenses/> - * - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef E_MAIL_JUNK_HOOK_H -#define E_MAIL_JUNK_HOOK_H - -#include <e-util/e-plugin.h> - -/* Standard GObject macros */ -#define E_TYPE_MAIL_JUNK_HOOK \ - (e_mail_junk_hook_get_type ()) -#define E_MAIL_JUNK_HOOK(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHook)) -#define E_MAIL_JUNK_HOOK_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookClass)) -#define E_IS_MAIL_JUNK_HOOK(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_MAIL_JUNK_HOOK)) -#define E_IS_MAIL_JUNK_HOOK_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_MAIL_JUNK_HOOK)) -#define E_MAIL_JUNK_HOOK_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookClass)) - -G_BEGIN_DECLS - -typedef struct _EMailJunkHook EMailJunkHook; -typedef struct _EMailJunkHookClass EMailJunkHookClass; -typedef struct _EMailJunkHookPrivate EMailJunkHookPrivate; - -struct _EMailJunkHook { - EPluginHook parent; - EMailJunkHookPrivate *priv; -}; - -struct _EMailJunkHookClass { - EPluginHookClass parent_class; -}; - -GType e_mail_junk_hook_get_type (void); -void e_mail_junk_hook_register_type (GTypeModule *type_module); - -G_END_DECLS - -#endif /* E_MAIL_JUNK_HOOK_H */ diff --git a/modules/mail/em-mailer-prefs.c b/modules/mail/em-mailer-prefs.c index 8e2688e615..bbf65e36b5 100644 --- a/modules/mail/em-mailer-prefs.c +++ b/modules/mail/em-mailer-prefs.c @@ -37,18 +37,20 @@ #include "libedataserverui/e-cell-renderer-color.h" -#include "e-util/e-util.h" -#include "e-util/e-datetime-format.h" -#include "e-util/e-util-private.h" -#include "widgets/misc/e-charset-combo-box.h" -#include "shell/e-shell-utils.h" - -#include "e-mail-backend.h" -#include "e-mail-label-manager.h" -#include "e-mail-reader-utils.h" -#include "em-folder-selection-button.h" -#include "em-junk.h" -#include "em-config.h" +#include <e-util/e-util.h> +#include <e-util/e-datetime-format.h> +#include <e-util/e-util-private.h> + +#include <misc/e-charset-combo-box.h> +#include <misc/e-port-entry.h> +#include <shell/e-shell-utils.h> + +#include <mail/e-mail-backend.h> +#include <mail/e-mail-junk-options.h> +#include <mail/e-mail-label-manager.h> +#include <mail/e-mail-reader-utils.h> +#include <mail/em-folder-selection-button.h> +#include <mail/em-config.h> enum { HEADER_LIST_NAME_COLUMN, /* displayable name of the header (may be a translation) */ @@ -102,7 +104,6 @@ em_mailer_prefs_finalize (GObject *object) { EMMailerPrefs *prefs = (EMMailerPrefs *) object; - g_object_unref (prefs->session); g_object_unref (prefs->builder); if (prefs->labels_change_notify_id) { @@ -676,131 +677,8 @@ emmp_free (EConfig *ec, GSList *items, gpointer data) } static void -junk_plugin_changed (GtkWidget *combo, EMMailerPrefs *prefs) -{ - gchar *def_plugin; - const GList *plugins = mail_session_get_junk_plugins (prefs->session); - GtkTreeIter iter; - - g_return_if_fail (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)); - - def_plugin = NULL; - gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)), &iter, 0, &def_plugin, -1); - - gconf_client_set_string (prefs->gconf, "/apps/evolution/mail/junk/default_plugin", def_plugin, NULL); - while (plugins) { - EMJunkInterface *iface = plugins->data; - - if (iface->plugin_name && def_plugin && !strcmp (iface->plugin_name, def_plugin)) { - gboolean status; - - CAMEL_SESSION (prefs->session)->junk_plugin = - CAMEL_JUNK_PLUGIN (&iface->camel); - status = e_plugin_invoke (iface->hook->plugin, iface->validate_binary, NULL) != NULL; - if ((gboolean) status == TRUE) { - gchar *text, *html; - gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-info", GTK_ICON_SIZE_MENU); - text = g_strdup_printf (_("%s plugin is available and the binary is installed."), iface->plugin_name); - html = g_strdup_printf ("<i>%s</i>", text); - gtk_label_set_markup (prefs->plugin_status, html); - g_free (html); - g_free (text); - } else { - gchar *text, *html; - gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-warning", GTK_ICON_SIZE_MENU); - text = g_strdup_printf (_("%s plugin is not available. Please check whether the package is installed."), iface->plugin_name); - html = g_strdup_printf ("<i>%s</i>", text); - gtk_label_set_markup (prefs->plugin_status, html); - g_free (html); - g_free (text); - } - break; - } - plugins = plugins->next; - } - - g_free (def_plugin); -} - -static void -junk_plugin_setup (GtkComboBox *combo_box, EMMailerPrefs *prefs) -{ - GtkListStore *store; - GtkCellRenderer *cell; - gint index = 0; - gboolean def_set = FALSE; - const GList *plugins = mail_session_get_junk_plugins (prefs->session); - gchar *pdefault = gconf_client_get_string (prefs->gconf, "/apps/evolution/mail/junk/default_plugin", NULL); - - store = gtk_list_store_new (1, G_TYPE_STRING); - gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store)); - - cell = gtk_cell_renderer_text_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE); - gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell, - "text", 0, - NULL); - - if (!plugins || !g_list_length ((GList *) plugins)) { - GtkTreeIter iter; - - gtk_list_store_append (store, &iter); - gtk_list_store_set ( - store, &iter, 0, _("No junk plugin available"), -1); - gtk_combo_box_set_active (combo_box, 0); - gtk_widget_set_sensitive (GTK_WIDGET (combo_box), FALSE); - gtk_widget_hide (GTK_WIDGET (prefs->plugin_image)); - gtk_widget_hide (GTK_WIDGET (prefs->plugin_status)); - gtk_image_set_from_stock (prefs->plugin_image, NULL, 0); - g_free (pdefault); - - return; - } - - while (plugins) { - EMJunkInterface *iface = plugins->data; - GtkTreeIter iter; - - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, iface->plugin_name, -1); - if (!def_set && pdefault && iface->plugin_name && !strcmp (pdefault, iface->plugin_name)) { - gboolean status; - - def_set = TRUE; - gtk_combo_box_set_active (combo_box, index); - status = e_plugin_invoke (iface->hook->plugin, iface->validate_binary, NULL) != NULL; - if (status) { - gchar *text, *html; - gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-info", GTK_ICON_SIZE_MENU); - /* May be a better text */ - text = g_strdup_printf (_("%s plugin is available and the binary is installed."), iface->plugin_name); - html = g_strdup_printf ("<i>%s</i>", text); - gtk_label_set_markup (prefs->plugin_status, html); - g_free (html); - g_free (text); - } else { - gchar *text, *html; - gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-warning", GTK_ICON_SIZE_MENU); - /* May be a better text */ - text = g_strdup_printf (_("%s plugin is not available. Please check whether the package is installed."), iface->plugin_name); - html = g_strdup_printf ("<i>%s</i>", text); - gtk_label_set_markup (prefs->plugin_status, html); - g_free (html); - g_free (text); - } - } - plugins = plugins->next; - index++; - } - - g_signal_connect ( - combo_box, "changed", - G_CALLBACK (junk_plugin_changed), prefs); - g_free (pdefault); -} - -static void em_mailer_prefs_construct (EMMailerPrefs *prefs, + EMailSession *session, EShell *shell) { GSList *header_config_list, *header_add_list, *p; @@ -823,6 +701,7 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs, /* Make sure our custom widget classes are registered with * GType before we load the GtkBuilder definition file. */ + E_TYPE_MAIL_JUNK_OPTIONS; EM_TYPE_FOLDER_SELECTION_BUTTON; prefs->builder = gtk_builder_new (); @@ -1182,10 +1061,8 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs, G_BINDING_SYNC_CREATE); emmp_empty_junk_init (prefs, GTK_COMBO_BOX (widget)); - prefs->default_junk_plugin = GTK_COMBO_BOX (e_builder_get_widget (prefs->builder, "default_junk_plugin")); - prefs->plugin_status = GTK_LABEL (e_builder_get_widget (prefs->builder, "plugin_status")); - prefs->plugin_image = GTK_IMAGE (e_builder_get_widget (prefs->builder, "plugin_image")); - junk_plugin_setup (prefs->default_junk_plugin, prefs); + widget = e_builder_get_widget (prefs->builder, "junk-module-options"); + e_mail_junk_options_set_session (E_MAIL_JUNK_OPTIONS (widget), session); prefs->junk_header_check = (GtkToggleButton *)e_builder_get_widget (prefs->builder, "junk_header_check"); prefs->junk_header_tree = (GtkTreeView *)e_builder_get_widget (prefs->builder, "junk_header_tree"); @@ -1238,11 +1115,8 @@ em_mailer_prefs_new (EPreferencesWindow *window) new = g_object_new (EM_TYPE_MAILER_PREFS, NULL); - /* FIXME This should be a constructor property. */ - new->session = g_object_ref (session); - /* FIXME Kill this function. */ - em_mailer_prefs_construct (new, shell); + em_mailer_prefs_construct (new, session, shell); return GTK_WIDGET (new); } diff --git a/modules/mail/em-mailer-prefs.h b/modules/mail/em-mailer-prefs.h index d28fd5f5e4..35ebec3bf7 100644 --- a/modules/mail/em-mailer-prefs.h +++ b/modules/mail/em-mailer-prefs.h @@ -26,7 +26,6 @@ #include <gtk/gtk.h> #include <gconf/gconf-client.h> #include <shell/e-shell.h> -#include <mail/e-mail-session.h> #include <widgets/misc/e-preferences-window.h> /* Standard GObject macros */ @@ -56,8 +55,6 @@ typedef struct _EMMailerPrefsClass EMMailerPrefsClass; struct _EMMailerPrefs { GtkVBox parent_object; - EMailSession *session; - GtkBuilder *builder; GConfClient *gconf; @@ -95,9 +92,6 @@ struct _EMMailerPrefs { GtkToggleButton *sa_local_tests_only; GtkToggleButton *sa_use_daemon; - GtkComboBox *default_junk_plugin; - GtkLabel *plugin_status; - GtkImage *plugin_image; GtkToggleButton *junk_header_check; GtkTreeView *junk_header_tree; diff --git a/modules/mail/evolution-module-mail.c b/modules/mail/evolution-module-mail.c index ddb9d8996b..bda0db5d95 100644 --- a/modules/mail/evolution-module-mail.c +++ b/modules/mail/evolution-module-mail.c @@ -27,7 +27,6 @@ #include "e-mail-config-hook.h" #include "e-mail-event-hook.h" -#include "e-mail-junk-hook.h" #include "e-mail-shell-backend.h" #include "e-mail-shell-content.h" @@ -52,7 +51,6 @@ e_module_load (GTypeModule *type_module) e_mail_config_hook_register_type (type_module); e_mail_event_hook_register_type (type_module); - e_mail_junk_hook_register_type (type_module); e_mail_shell_backend_register_type (type_module); e_mail_shell_content_register_type (type_module); diff --git a/modules/spamassassin/Makefile.am b/modules/spamassassin/Makefile.am new file mode 100644 index 0000000000..8bce85214b --- /dev/null +++ b/modules/spamassassin/Makefile.am @@ -0,0 +1,56 @@ +module_LTLIBRARIES = libevolution-module-spamassassin.la + +libevolution_module_spamassassin_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"evolution-spamassassin\" \ + $(GNOME_PLATFORM_CFLAGS) \ + $(EVOLUTION_MAIL_CFLAGS) + +libevolution_module_spamassassin_la_SOURCES = \ + evolution-spamassassin.c + +libevolution_module_spamassassin_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(GNOME_PLATFORM_LIBS) \ + $(EVOLUTION_MAIL_LIBS) + +libevolution_module_spamassassin_la_LDFLAGS = \ + -module -avoid-version $(NO_UNDEFINED) + +schemadir = $(GCONF_SCHEMA_FILE_DIR) +schema_in_files = evolution-spamassassin.schemas.in +schema_DATA = $(schema_in_files:.schemas.in=.schemas) + +@INTLTOOL_SCHEMAS_RULE@ + +if GCONF_SCHEMAS_INSTALL + +if OS_WIN32 +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + (echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE); \ + echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat; \ + cmd /c _temp.bat; \ + rm _temp.bat; \ + done \ + fi +else +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) \ + $(GCONFTOOL) --makefile-install-rule $$p; \ + done \ + fi +endif + +endif + +DISTCLEANFILES = $(schema_DATA) + +EXTRA_DIST = $(schema_in_files) + +-include $(top_srcdir)/git.mk diff --git a/modules/spamassassin/evolution-spamassassin.c b/modules/spamassassin/evolution-spamassassin.c new file mode 100644 index 0000000000..6107694713 --- /dev/null +++ b/modules/spamassassin/evolution-spamassassin.c @@ -0,0 +1,1177 @@ +/* + * 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 <http://www.gnu.org/licenses/> + * + */ + +#include <config.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <glib/gi18n-lib.h> + +#include <camel/camel.h> + +#include <e-util/e-mktemp.h> +#include <e-util/gconf-bridge.h> +#include <mail/e-mail-junk-filter.h> + +/* 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, + GCancellable *cancellable, + GError **error) +{ + GMainContext *context; + GSpawnFlags flags; + 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; + + flags = G_SPAWN_DO_NOT_REAP_CHILD; + if (output_buffer == NULL) + flags |= G_SPAWN_STDOUT_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; + } + } + + /* 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, 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, 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 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 (argv, NULL, NULL, NULL, &error); + + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + goto exit; + } + + if (exit_code == 0) { + /* 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; + } + +exit: + g_free (pid_file); + g_free (socket_path); + + g_mutex_unlock (extension->socket_path_mutex); + + return started; +} + +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_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); + + /* If we started our own daemon, kill it. */ + spam_assassin_kill_our_own_daemon (extension); + + 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 ( + "<b>%s</b>", _("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 ( + "<small>%s</small>", + _("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_PARAM_STATIC_STRINGS)); + + 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_PARAM_STATIC_STRINGS)); + + 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_PARAM_STATIC_STRINGS)); + + 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_PARAM_STATIC_STRINGS)); + + 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 | + G_PARAM_STATIC_STRINGS)); +} + +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) +{ +} diff --git a/modules/spamassassin/evolution-spamassassin.schemas.in b/modules/spamassassin/evolution-spamassassin.schemas.in new file mode 100644 index 0000000000..b344d1bffd --- /dev/null +++ b/modules/spamassassin/evolution-spamassassin.schemas.in @@ -0,0 +1,33 @@ +<gconfschemafile> + <schemalist> + + <schema> + <key>/schemas/apps/evolution/mail/junk/sa/local_only</key> + <applyto>/apps/evolution/mail/junk/sa/local_only</applyto> + <owner>evolution-spamassassin</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Use only local spam tests.</short> + <long> + Use only the local spam tests (no DNS). + </long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/evolution/mail/junk/sa/use_daemon</key> + <applyto>/apps/evolution/mail/junk/sa/use_daemon</applyto> + <owner>evolution-spamassassin</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Use SpamAssassin daemon and client</short> + <long> + Use SpamAssassin daemon and client (spamc/spamd). + </long> + </locale> + </schema> + + </schemalist> +</gconfschemafile> |