diff options
Diffstat (limited to 'modules/bogofilter/evolution-bogofilter.c')
-rw-r--r-- | modules/bogofilter/evolution-bogofilter.c | 524 |
1 files changed, 524 insertions, 0 deletions
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) +{ +} |