evolution-3.6.4/modules/bogofilter/evolution-bogofilter.c

No issues found

  1 /*
  2  * evolution-bogofilter.c
  3  *
  4  * This program is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 of the License, or (at your option) version 3.
  8  *
  9  * This program is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 16  *
 17  */
 18 
 19 #ifdef HAVE_CONFIG_H
 20 #include <config.h>
 21 #endif
 22 
 23 #include <config.h>
 24 #include <sys/types.h>
 25 #include <sys/wait.h>
 26 #include <glib/gi18n-lib.h>
 27 
 28 #include <camel/camel.h>
 29 
 30 #include <libemail-engine/e-mail-junk-filter.h>
 31 
 32 /* Standard GObject macros */
 33 #define E_TYPE_BOGOFILTER \
 34 	(e_bogofilter_get_type ())
 35 #define E_BOGOFILTER(obj) \
 36 	(G_TYPE_CHECK_INSTANCE_CAST \
 37 	((obj), E_TYPE_BOGOFILTER, EBogofilter))
 38 
 39 #ifndef BOGOFILTER_BINARY
 40 #define BOGOFILTER_BINARY "/usr/bin/bogofilter"
 41 #endif
 42 
 43 #define BOGOFILTER_EXIT_STATUS_SPAM		0
 44 #define BOGOFILTER_EXIT_STATUS_HAM		1
 45 #define BOGOFILTER_EXIT_STATUS_UNSURE		2
 46 #define BOGOFILTER_EXIT_STATUS_ERROR		3
 47 
 48 typedef struct _EBogofilter EBogofilter;
 49 typedef struct _EBogofilterClass EBogofilterClass;
 50 
 51 struct _EBogofilter {
 52 	EMailJunkFilter parent;
 53 	gboolean convert_to_unicode;
 54 };
 55 
 56 struct _EBogofilterClass {
 57 	EMailJunkFilterClass parent_class;
 58 };
 59 
 60 enum {
 61 	PROP_0,
 62 	PROP_CONVERT_TO_UNICODE
 63 };
 64 
 65 /* Module Entry Points */
 66 void e_module_load (GTypeModule *type_module);
 67 void e_module_unload (GTypeModule *type_module);
 68 
 69 /* Forward Declarations */
 70 GType e_bogofilter_get_type (void);
 71 static void e_bogofilter_interface_init (CamelJunkFilterInterface *interface);
 72 
 73 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
 74 	EBogofilter,
 75 	e_bogofilter,
 76 	E_TYPE_MAIL_JUNK_FILTER, 0,
 77 	G_IMPLEMENT_INTERFACE_DYNAMIC (
 78 		CAMEL_TYPE_JUNK_FILTER,
 79 		e_bogofilter_interface_init))
 80 
 81 #ifdef G_OS_UNIX
 82 static void
 83 bogofilter_cancelled_cb (GCancellable *cancellable,
 84                          GPid *pid)
 85 {
 86 	/* XXX On UNIX-like systems we can safely assume a GPid is the
 87 	 *     process ID and use it to terminate the process via signal. */
 88 	kill (*pid, SIGTERM);
 89 }
 90 #endif
 91 
 92 static void
 93 bogofilter_exited_cb (GPid *pid,
 94                       gint status,
 95                       gpointer user_data)
 96 {
 97 	struct {
 98 		GMainLoop *loop;
 99 		gint exit_code;
100 	} *source_data = user_data;
101 
102 	if (WIFEXITED (status))
103 		source_data->exit_code = WEXITSTATUS (status);
104 	else
105 		source_data->exit_code = BOGOFILTER_EXIT_STATUS_ERROR;
106 
107 	g_main_loop_quit (source_data->loop);
108 }
109 
110 static gint
111 bogofilter_command (const gchar **argv,
112                     CamelMimeMessage *message,
113                     GCancellable *cancellable,
114                     GError **error)
115 {
116 	CamelStream *stream;
117 	GMainContext *context;
118 	GSource *source;
119 	GPid child_pid;
120 	gssize bytes_written;
121 	gint standard_input;
122 	gulong handler_id = 0;
123 	gboolean success;
124 
125 	struct {
126 		GMainLoop *loop;
127 		gint exit_code;
128 	} source_data;
129 
130 	/* Spawn Bogofilter with an open stdin pipe. */
131 	success = g_spawn_async_with_pipes (
132 		NULL,
133 		(gchar **) argv,
134 		NULL,
135 		G_SPAWN_DO_NOT_REAP_CHILD |
136 		G_SPAWN_STDOUT_TO_DEV_NULL,
137 		NULL, NULL,
138 		&child_pid,
139 		&standard_input,
140 		NULL,
141 		NULL,
142 		error);
143 
144 	if (!success) {
145 		gchar *command_line;
146 
147 		command_line = g_strjoinv (" ", (gchar **) argv);
148 		g_prefix_error (
149 			error, _("Failed to spawn Bogofilter (%s): "),
150 			command_line);
151 		g_free (command_line);
152 
153 		return BOGOFILTER_EXIT_STATUS_ERROR;
154 	}
155 
156 	/* Stream the CamelMimeMessage to Bogofilter. */
157 	stream = camel_stream_fs_new_with_fd (standard_input);
158 	bytes_written = camel_data_wrapper_write_to_stream_sync (
159 		CAMEL_DATA_WRAPPER (message), stream, cancellable, error);
160 	success = (bytes_written >= 0) &&
161 		(camel_stream_close (stream, cancellable, error) == 0);
162 	g_object_unref (stream);
163 
164 	if (!success) {
165 		g_spawn_close_pid (child_pid);
166 		g_prefix_error (
167 			error, _("Failed to stream mail "
168 			"message content to Bogofilter: "));
169 		return BOGOFILTER_EXIT_STATUS_ERROR;
170 	}
171 
172 	/* Wait for the Bogofilter process to terminate
173 	 * using GLib's main loop for better portability. */
174 
175 	context = g_main_context_new ();
176 
177 	source = g_child_watch_source_new (child_pid);
178 	g_source_set_callback (
179 		source, (GSourceFunc)
180 		bogofilter_exited_cb,
181 		&source_data, NULL);
182 	g_source_attach (source, context);
183 	g_source_unref (source);
184 
185 	source_data.loop = g_main_loop_new (context, TRUE);
186 	source_data.exit_code = 0;
187 
188 #ifdef G_OS_UNIX
189 	if (G_IS_CANCELLABLE (cancellable))
190 		handler_id = g_cancellable_connect (
191 			cancellable,
192 			G_CALLBACK (bogofilter_cancelled_cb),
193 			&child_pid, (GDestroyNotify) NULL);
194 #endif
195 
196 	g_main_loop_run (source_data.loop);
197 
198 	if (handler_id > 0)
199 		g_cancellable_disconnect (cancellable, handler_id);
200 
201 	g_main_loop_unref (source_data.loop);
202 	source_data.loop = NULL;
203 
204 	g_main_context_unref (context);
205 
206 	/* Clean up. */
207 
208 	g_spawn_close_pid (child_pid);
209 
210 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
211 		source_data.exit_code = BOGOFILTER_EXIT_STATUS_ERROR;
212 
213 	else if (source_data.exit_code == BOGOFILTER_EXIT_STATUS_ERROR)
214 		g_set_error_literal (
215 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
216 			_("Bogofilter either crashed or "
217 			"failed to process a mail message"));
218 
219 	return source_data.exit_code;
220 }
221 
222 static void
223 bogofilter_init_wordlist (EBogofilter *extension)
224 {
225 	CamelStream *stream;
226 	CamelMimeParser *parser;
227 	CamelMimeMessage *message;
228 
229 	/* Initialize the Bogofilter database with a welcome message. */
230 
231 	parser = camel_mime_parser_new ();
232 	message = camel_mime_message_new ();
233 
234 	stream = camel_stream_fs_new_with_name (
235 		WELCOME_MESSAGE, O_RDONLY, 0, NULL);
236 	camel_mime_parser_init_with_stream (parser, stream, NULL);
237 	camel_mime_parser_scan_from (parser, FALSE);
238 	g_object_unref (stream);
239 
240 	camel_mime_part_construct_from_parser_sync (
241 		CAMEL_MIME_PART (message), parser, NULL, NULL);
242 
243 	camel_junk_filter_learn_not_junk (
244 		CAMEL_JUNK_FILTER (extension), message, NULL, NULL);
245 
246 	g_object_unref (message);
247 	g_object_unref (parser);
248 }
249 
250 static gboolean
251 bogofilter_get_convert_to_unicode (EBogofilter *extension)
252 {
253 	return extension->convert_to_unicode;
254 }
255 
256 static void
257 bogofilter_set_convert_to_unicode (EBogofilter *extension,
258                                    gboolean convert_to_unicode)
259 {
260 	if (extension->convert_to_unicode == convert_to_unicode)
261 		return;
262 
263 	extension->convert_to_unicode = convert_to_unicode;
264 
265 	g_object_notify (G_OBJECT (extension), "convert-to-unicode");
266 }
267 
268 static void
269 bogofilter_set_property (GObject *object,
270                          guint property_id,
271                          const GValue *value,
272                          GParamSpec *pspec)
273 {
274 	switch (property_id) {
275 		case PROP_CONVERT_TO_UNICODE:
276 			bogofilter_set_convert_to_unicode (
277 				E_BOGOFILTER (object),
278 				g_value_get_boolean (value));
279 			return;
280 	}
281 
282 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
283 }
284 
285 static void
286 bogofilter_get_property (GObject *object,
287                          guint property_id,
288                          GValue *value,
289                          GParamSpec *pspec)
290 {
291 	switch (property_id) {
292 		case PROP_CONVERT_TO_UNICODE:
293 			g_value_set_boolean (
294 				value, bogofilter_get_convert_to_unicode (
295 				E_BOGOFILTER (object)));
296 			return;
297 	}
298 
299 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
300 }
301 
302 static gboolean
303 bogofilter_available (EMailJunkFilter *junk_filter)
304 {
305 	return g_file_test (BOGOFILTER_BINARY, G_FILE_TEST_IS_EXECUTABLE);
306 }
307 
308 static GtkWidget *
309 bogofilter_new_config_widget (EMailJunkFilter *junk_filter)
310 {
311 	GtkWidget *box;
312 	GtkWidget *widget;
313 	gchar *markup;
314 
315 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
316 
317 	markup = g_markup_printf_escaped (
318 		"<b>%s</b>", _("Bogofilter Options"));
319 	widget = gtk_label_new (markup);
320 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
321 	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
322 	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
323 	gtk_widget_show (widget);
324 	g_free (markup);
325 
326 	widget = gtk_check_button_new_with_mnemonic (
327 		_("Convert message text to _Unicode"));
328 	gtk_widget_set_margin_left (widget, 12);
329 	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
330 	gtk_widget_show (widget);
331 
332 	g_object_bind_property (
333 		junk_filter, "convert-to-unicode",
334 		widget, "active",
335 		G_BINDING_BIDIRECTIONAL |
336 		G_BINDING_SYNC_CREATE);
337 
338 	return box;
339 }
340 
341 static gboolean
342 bogofilter_classify (CamelJunkFilter *junk_filter,
343                      CamelMimeMessage *message,
344                      CamelJunkStatus *status,
345                      GCancellable *cancellable,
346                      GError **error)
347 {
348 	EBogofilter *extension = E_BOGOFILTER (junk_filter);
349 	static gboolean wordlist_initialized = FALSE;
350 	gint exit_code;
351 
352 	const gchar *argv[] = {
353 		BOGOFILTER_BINARY,
354 		NULL,  /* leave room for unicode option */
355 		NULL
356 	};
357 
358 	if (bogofilter_get_convert_to_unicode (extension))
359 		argv[1] = "--unicode=yes";
360 
361 retry:
362 	exit_code = bogofilter_command (argv, message, cancellable, error);
363 
364 	switch (exit_code) {
365 		case BOGOFILTER_EXIT_STATUS_SPAM:
366 			*status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
367 			break;
368 
369 		case BOGOFILTER_EXIT_STATUS_HAM:
370 			*status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK;
371 			break;
372 
373 		case BOGOFILTER_EXIT_STATUS_UNSURE:
374 			*status = CAMEL_JUNK_STATUS_INCONCLUSIVE;
375 			break;
376 
377 		case BOGOFILTER_EXIT_STATUS_ERROR:
378 			if (!wordlist_initialized) {
379 				wordlist_initialized = TRUE;
380 				bogofilter_init_wordlist (extension);
381 				goto retry;
382 			}
383 			break;
384 
385 		default:
386 			g_warning (
387 				"Bogofilter: Unexpected exit code (%d) "
388 				"while classifying message", exit_code);
389 			break;
390 	}
391 
392 	/* Check that the return value and GError agree. */
393 	if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR)
394 		g_warn_if_fail (error == NULL || *error == NULL);
395 	else
396 		g_warn_if_fail (error == NULL || *error != NULL);
397 
398 	return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR);
399 }
400 
401 static gboolean
402 bogofilter_learn_junk (CamelJunkFilter *junk_filter,
403                        CamelMimeMessage *message,
404                        GCancellable *cancellable,
405                        GError **error)
406 {
407 	EBogofilter *extension = E_BOGOFILTER (junk_filter);
408 	gint exit_code;
409 
410 	const gchar *argv[] = {
411 		BOGOFILTER_BINARY,
412 		"--register-spam",
413 		NULL,  /* leave room for unicode option */
414 		NULL
415 	};
416 
417 	if (bogofilter_get_convert_to_unicode (extension))
418 		argv[2] = "--unicode=yes";
419 
420 	exit_code = bogofilter_command (argv, message, cancellable, error);
421 
422 	if (exit_code != 0)
423 		g_warning (
424 			"Bogofilter: Unexpected exit code (%d) "
425 			"while registering spam", exit_code);
426 
427 	/* Check that the return value and GError agree. */
428 	if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR)
429 		g_warn_if_fail (error == NULL || *error == NULL);
430 	else
431 		g_warn_if_fail (error == NULL || *error != NULL);
432 
433 	return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR);
434 }
435 
436 static gboolean
437 bogofilter_learn_not_junk (CamelJunkFilter *junk_filter,
438                            CamelMimeMessage *message,
439                            GCancellable *cancellable,
440                            GError **error)
441 {
442 	EBogofilter *extension = E_BOGOFILTER (junk_filter);
443 	gint exit_code;
444 
445 	const gchar *argv[] = {
446 		BOGOFILTER_BINARY,
447 		"--register-ham",
448 		NULL,  /* leave room for unicode option */
449 		NULL
450 	};
451 
452 	if (bogofilter_get_convert_to_unicode (extension))
453 		argv[2] = "--unicode=yes";
454 
455 	exit_code = bogofilter_command (argv, message, cancellable, error);
456 
457 	if (exit_code != 0)
458 		g_warning (
459 			"Bogofilter: Unexpected exit code (%d) "
460 			"while registering ham", exit_code);
461 
462 	/* Check that the return value and GError agree. */
463 	if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR)
464 		g_warn_if_fail (error == NULL || *error == NULL);
465 	else
466 		g_warn_if_fail (error == NULL || *error != NULL);
467 
468 	return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR);
469 }
470 
471 static void
472 e_bogofilter_class_init (EBogofilterClass *class)
473 {
474 	GObjectClass *object_class;
475 	EMailJunkFilterClass *junk_filter_class;
476 
477 	object_class = G_OBJECT_CLASS (class);
478 	object_class->set_property = bogofilter_set_property;
479 	object_class->get_property = bogofilter_get_property;
480 
481 	junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class);
482 	junk_filter_class->filter_name = "Bogofilter";
483 	junk_filter_class->display_name = _("Bogofilter");
484 	junk_filter_class->available = bogofilter_available;
485 	junk_filter_class->new_config_widget = bogofilter_new_config_widget;
486 
487 	g_object_class_install_property (
488 		object_class,
489 		PROP_CONVERT_TO_UNICODE,
490 		g_param_spec_boolean (
491 			"convert-to-unicode",
492 			"Convert to Unicode",
493 			"Convert message text to Unicode",
494 			TRUE,
495 			G_PARAM_READWRITE));
496 }
497 
498 static void
499 e_bogofilter_class_finalize (EBogofilterClass *class)
500 {
501 }
502 
503 static void
504 e_bogofilter_interface_init (CamelJunkFilterInterface *interface)
505 {
506 	interface->classify = bogofilter_classify;
507 	interface->learn_junk = bogofilter_learn_junk;
508 	interface->learn_not_junk = bogofilter_learn_not_junk;
509 }
510 
511 static void
512 e_bogofilter_init (EBogofilter *extension)
513 {
514 	GSettings *settings;
515 
516 	settings = g_settings_new ("org.gnome.evolution.bogofilter");
517 	g_settings_bind (
518 		settings, "utf8-for-spam-filter",
519 		G_OBJECT (extension), "convert-to-unicode",
520 		G_SETTINGS_BIND_DEFAULT);
521 	g_object_unref (settings);
522 }
523 
524 G_MODULE_EXPORT void
525 e_module_load (GTypeModule *type_module)
526 {
527 	e_bogofilter_register_type (type_module);
528 }
529 
530 G_MODULE_EXPORT void
531 e_module_unload (GTypeModule *type_module)
532 {
533 }