2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Claws Mail Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include <sys/types.h>
31 #include <glib/gi18n.h>
37 #include "common/claws.h"
38 #include "common/version.h"
40 #include "common/utils.h"
45 #include "prefs_gtk.h"
47 #include "bogofilter.h"
50 #include "prefs_common.h"
51 #include "alertpanel.h"
52 #include "addr_compl.h"
54 #ifdef HAVE_SYSEXITS_H
60 #ifdef HAVE_SYS_ERRNO_H
61 #include <sys/errno.h>
66 #ifdef HAVE_SYS_TIME_H
76 #define PLUGIN_NAME (_("Bogofilter"))
78 static guint hook_id = -1;
79 static MessageCallback message_callback;
81 static BogofilterConfig config;
83 static PrefParam param[] = {
84 {"process_emails", "TRUE", &config.process_emails, P_BOOL,
86 {"receive_spam", "TRUE", &config.receive_spam, P_BOOL,
88 {"save_folder", NULL, &config.save_folder, P_STRING,
90 {"save_unsure", "FALSE", &config.save_unsure, P_BOOL,
92 {"save_unsure_folder", NULL, &config.save_unsure_folder, P_STRING,
94 {"max_size", "250", &config.max_size, P_INT,
96 {"bogopath", "bogofilter", &config.bogopath, P_STRING,
98 {"insert_header", "FALSE", &config.insert_header, P_BOOL,
100 {"whitelist_ab", "FALSE", &config.whitelist_ab, P_BOOL,
102 {"whitelist_ab_folder", "Any", &config.whitelist_ab_folder, P_STRING,
105 {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
109 * Helper function for spawn_with_input() - write an entire
119 gssize count = write (fd, buf, to_write);
135 typedef struct _BogoFilterData {
136 MailFilteringData *mail_filtering_data;
142 GSList *whitelisted_new_spams;
148 static BogoFilterData *to_filter_data = NULL;
150 static gboolean filter_th_done = FALSE;
151 static pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;
152 static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER;
153 static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER;
156 static gboolean found_in_addressbook(const gchar *address)
159 gboolean found = FALSE;
165 addr = g_strdup(address);
166 extract_address(addr);
167 num_addr = complete_address(addr);
169 /* skip first item (this is the search string itself) */
171 for (; i < num_addr && !found; i++) {
172 gchar *caddr = get_complete_address(i);
173 extract_address(caddr);
174 if (strcasecmp(caddr, addr) == 0)
183 static void bogofilter_do_filter(BogoFilterData *data)
186 gint bogo_stdin, bogo_stdout;
187 GError *error = NULL;
188 gboolean bogo_forked;
192 int total = 0, curnum = 0;
196 total = g_slist_length(data->msglist);
198 bogo_forked = g_spawn_async_with_pipes(
199 NULL, data->bogo_args,NULL, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
200 NULL, NULL, &bogo_pid, &bogo_stdin,
201 &bogo_stdout, NULL, &error);
203 if (bogo_forked == FALSE) {
204 g_warning("%s\n", error ? error->message:"ERROR???");
210 if (config.whitelist_ab) {
211 gchar *ab_folderpath;
213 if (*config.whitelist_ab_folder == '\0' ||
214 strcasecmp(config.whitelist_ab_folder, _("Any")) == 0) {
215 /* match the whole addressbook */
216 ab_folderpath = NULL;
218 /* match the specific book/folder of the addressbook */
219 ab_folderpath = config.whitelist_ab_folder;
222 start_address_completion(ab_folderpath);
225 for (cur = data->msglist; cur; cur = cur->next) {
226 gboolean whitelisted = FALSE;
227 msginfo = (MsgInfo *)cur->data;
228 debug_print("Filtering message %d (%d/%d)\n", msginfo->msgnum, curnum, total);
230 if (message_callback != NULL)
231 message_callback(NULL, total, curnum++, data->in_thread);
233 if (config.whitelist_ab && msginfo->from &&
234 found_in_addressbook(msginfo->from))
237 /* can set flags (SCANNED, ATTACHMENT) but that's ok
238 * as GUI updates are hooked not direct */
240 file = procmsg_get_message_file(msginfo);
243 gchar *tmp = g_strdup_printf("%s\n",file);
244 /* send filename to bogofilter */
245 write_all(bogo_stdin, tmp, strlen(tmp));
247 memset(buf, 0, sizeof(buf));
249 if (read(bogo_stdout, buf, sizeof(buf)-1) < 0) {
250 g_warning("bogofilter short read\n");
251 debug_print("message %d is ham\n", msginfo->msgnum);
252 data->mail_filtering_data->unfiltered = g_slist_prepend(
253 data->mail_filtering_data->unfiltered, msginfo);
254 data->new_hams = g_slist_prepend(data->new_hams, msginfo);
256 gchar **parts = NULL;
257 if (strchr(buf, '/')) {
258 tmp = strrchr(buf, '/')+1;
262 parts = g_strsplit(tmp, " ", 0);
263 debug_print("read %s\n", buf);
265 /* note the result if the header if needed */
266 if (parts && parts[0] && parts[1] && parts[2] &&
267 FOLDER_TYPE(msginfo->folder->folder) == F_MH &&
268 config.insert_header) {
269 gchar *tmpfile = get_tmp_file();
270 FILE *input = fopen(file, "r");
271 FILE *output = fopen(tmpfile, "w");
272 if (strstr(parts[2], "\n"))
273 *(strstr(parts[2], "\n")) = '\0';
274 if (input && !output)
276 else if (!input && output)
279 gchar tmpbuf[BUFFSIZE];
280 const gchar *bogosity = *parts[1] == 'S' ? "Spam":
281 (*parts[1] == 'H' ? "Ham":"Unsure");
282 gchar *tmpstr = g_strdup_printf(
283 "X-Claws-Bogosity: %s, spamicity=%s%s\n",
285 whitelisted?" [whitelisted]":"");
286 fwrite(tmpstr, 1, strlen(tmpstr), output);
287 while (fgets(tmpbuf, sizeof(buf), input))
288 fputs(tmpbuf, output);
291 move_file(tmpfile, file, TRUE);
298 if (!whitelisted && parts && parts[0] && parts[1] && *parts[1] == 'S') {
300 debug_print("message %d is spam\n", msginfo->msgnum);
301 /* Spam will be filtered away */
302 data->mail_filtering_data->filtered = g_slist_prepend(
303 data->mail_filtering_data->filtered, msginfo);
304 data->new_spams = g_slist_prepend(data->new_spams, msginfo);
306 } else if (whitelisted && parts && parts[0] && parts[1] && *parts[1] == 'S') {
308 debug_print("message %d is whitelisted spam\n", msginfo->msgnum);
309 /* Whitelisted spam will *not* be filtered away, but continue
310 * their trip through filtering as if it was ham. */
311 data->mail_filtering_data->unfiltered = g_slist_prepend(
312 data->mail_filtering_data->unfiltered, msginfo);
313 /* But it gets put in a different list, so that we
314 * can still flag it and inform the user that it is
315 * considered a spam (so that he can teach bogo that
317 data->whitelisted_new_spams = g_slist_prepend(data->whitelisted_new_spams, msginfo);
319 } else if (config.save_unsure && parts && parts[0] && parts[1] && *parts[1] == 'U') {
321 debug_print("message %d is unsure\n", msginfo->msgnum);
322 /* Spam will be filtered away */
323 data->mail_filtering_data->filtered = g_slist_prepend(
324 data->mail_filtering_data->filtered, msginfo);
325 data->new_unsure = g_slist_prepend(data->new_unsure, msginfo);
329 debug_print("message %d is ham\n", msginfo->msgnum);
330 data->mail_filtering_data->unfiltered = g_slist_prepend(
331 data->mail_filtering_data->unfiltered, msginfo);
332 data->new_hams = g_slist_prepend(data->new_hams, msginfo);
339 data->mail_filtering_data->unfiltered = g_slist_prepend(
340 data->mail_filtering_data->unfiltered, msginfo);
341 data->new_hams = g_slist_prepend(data->new_hams, msginfo);
344 if (config.whitelist_ab)
345 end_address_completion();
350 waitpid(bogo_pid, &status, 0);
351 if (!WIFEXITED(status))
354 status = WEXITSTATUS(status);
357 to_filter_data->status = status;
361 static void *bogofilter_filtering_thread(void *data)
363 while (!filter_th_done) {
364 pthread_mutex_lock(&list_mutex);
365 if (to_filter_data == NULL || to_filter_data->done == TRUE) {
366 pthread_mutex_unlock(&list_mutex);
367 debug_print("thread is waiting for something to filter\n");
368 pthread_mutex_lock(&wait_mutex);
369 pthread_cond_wait(&wait_cond, &wait_mutex);
370 pthread_mutex_unlock(&wait_mutex);
372 debug_print("thread awaken with something to filter\n");
373 to_filter_data->done = FALSE;
374 bogofilter_do_filter(to_filter_data);
375 pthread_mutex_unlock(&list_mutex);
376 to_filter_data->done = TRUE;
383 static pthread_t filter_th = 0;
385 static void bogofilter_start_thread(void)
387 filter_th_done = FALSE;
388 if (filter_th != 0 || 1)
390 if (pthread_create(&filter_th, 0,
391 bogofilter_filtering_thread,
396 debug_print("thread created\n");
399 static void bogofilter_stop_thread(void)
402 while (pthread_mutex_trylock(&list_mutex) != 0) {
406 if (filter_th != 0) {
407 filter_th_done = TRUE;
408 debug_print("waking thread up\n");
409 pthread_mutex_lock(&wait_mutex);
410 pthread_cond_broadcast(&wait_cond);
411 pthread_mutex_unlock(&wait_mutex);
412 pthread_join(filter_th, &res);
415 pthread_mutex_unlock(&list_mutex);
416 debug_print("thread done\n");
420 static gboolean mail_filtering_hook(gpointer source, gpointer data)
422 MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
423 MsgInfo *msginfo = mail_filtering_data->msginfo;
424 GSList *msglist = mail_filtering_data->msglist;
426 static gboolean warned_error = FALSE;
428 int total = 0, curnum = 0;
429 GSList *new_hams = NULL, *new_spams = NULL;
430 GSList *new_unsure, *whitelisted_new_spams = NULL;
431 gchar *bogo_exec = (config.bogopath && *config.bogopath) ? config.bogopath:"bogofilter";
433 gboolean ok_to_thread = TRUE;
435 bogo_args[0] = bogo_exec;
440 if (!config.process_emails) {
444 if (msglist == NULL && msginfo != NULL) {
445 g_warning("wrong call to bogofilter mail_filtering_hook");
449 total = g_slist_length(msglist);
451 /* we have to make sure the mails are cached - or it'll break on IMAP */
452 if (message_callback != NULL)
453 message_callback(_("Bogofilter: fetching bodies..."), total, 0, FALSE);
454 for (cur = msglist; cur; cur = cur->next) {
455 gchar *file = procmsg_get_message_file((MsgInfo *)cur->data);
457 ok_to_thread = FALSE;
458 if (message_callback != NULL)
459 message_callback(NULL, total, curnum++, FALSE);
462 if (message_callback != NULL)
463 message_callback(NULL, 0, 0, FALSE);
465 if (message_callback != NULL)
466 message_callback(_("Bogofilter: filtering messages..."), total, 0, FALSE);
469 while (pthread_mutex_trylock(&list_mutex) != 0) {
474 to_filter_data = g_new0(BogoFilterData, 1);
475 to_filter_data->msglist = msglist;
476 to_filter_data->mail_filtering_data = mail_filtering_data;
477 to_filter_data->new_hams = NULL;
478 to_filter_data->new_unsure = NULL;
479 to_filter_data->new_spams = NULL;
480 to_filter_data->whitelisted_new_spams = NULL;
481 to_filter_data->done = FALSE;
482 to_filter_data->status = -1;
483 to_filter_data->bogo_args = bogo_args;
485 to_filter_data->in_thread = (filter_th != 0 && ok_to_thread);
487 to_filter_data->in_thread = FALSE;
491 pthread_mutex_unlock(&list_mutex);
493 if (filter_th != 0 && ok_to_thread) {
494 debug_print("waking thread to let it filter things\n");
495 pthread_mutex_lock(&wait_mutex);
496 pthread_cond_broadcast(&wait_cond);
497 pthread_mutex_unlock(&wait_mutex);
499 while (!to_filter_data->done) {
505 while (pthread_mutex_trylock(&list_mutex) != 0) {
510 if (filter_th == 0 || !ok_to_thread)
511 bogofilter_do_filter(to_filter_data);
513 bogofilter_do_filter(to_filter_data);
516 new_hams = to_filter_data->new_hams;
517 new_unsure = to_filter_data->new_unsure;
518 new_spams = to_filter_data->new_spams;
519 whitelisted_new_spams = to_filter_data->whitelisted_new_spams;
520 status = to_filter_data->status;
521 g_free(to_filter_data);
522 to_filter_data = NULL;
524 pthread_mutex_unlock(&list_mutex);
529 for (cur = new_hams; cur; cur = cur->next) {
530 MsgInfo *msginfo = (MsgInfo *)cur->data;
531 procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
534 for (cur = new_unsure; cur; cur = cur->next) {
535 MsgInfo *msginfo = (MsgInfo *)cur->data;
536 procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
538 /* flag whitelisted spams */
539 for (cur = whitelisted_new_spams; cur; cur = cur->next) {
540 MsgInfo *msginfo = (MsgInfo *)cur->data;
541 procmsg_msginfo_set_flags(msginfo, MSG_SPAM, 0);
543 /* flag spams and delete them if !config.receive_spam
544 * (if config.receive_spam is set, we'll move them later) */
545 for (cur = new_spams; cur; cur = cur->next) {
546 MsgInfo *msginfo = (MsgInfo *)cur->data;
547 if (config.receive_spam) {
548 procmsg_msginfo_change_flags(msginfo, MSG_SPAM, 0, ~0, 0);
550 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
554 if (status < 0 || status > 2) { /* I/O or other errors */
558 msg = g_strdup_printf(_("The Bogofilter plugin couldn't filter "
559 "a message. The probable cause of the "
560 "error is that it didn't learn from any mail.\n"
561 "Use \"/Mark/Mark as spam\" and \"/Mark/Mark as "
562 "ham\" to train Bogofilter with a few hundred "
563 "spam and ham messages."));
565 msg = g_strdup_printf(_("The Bogofilter plugin couldn't filter "
566 "a message. the command `%s %s %s` couldn't be run."),
567 bogo_args[0], bogo_args[1], bogo_args[2]);
568 if (!prefs_common.no_recv_err_panel) {
570 alertpanel_error(msg);
574 gchar *tmp = g_strdup_printf("%s\n", msg);
580 if (status < 0 || status > 2) {
581 g_slist_free(mail_filtering_data->filtered);
582 g_slist_free(mail_filtering_data->unfiltered);
583 mail_filtering_data->filtered = NULL;
584 mail_filtering_data->unfiltered = NULL;
586 if (config.receive_spam && new_spams) {
587 FolderItem *save_folder;
589 if ((!config.save_folder) ||
590 (config.save_folder[0] == '\0') ||
591 ((save_folder = folder_find_item_from_identifier(config.save_folder)) == NULL))
592 save_folder = folder_get_default_trash();
594 for (cur = new_spams; cur; cur = cur->next) {
595 msginfo = (MsgInfo *)cur->data;
596 msginfo->is_move = TRUE;
597 msginfo->to_filter_folder = save_folder;
601 if (config.save_unsure && new_unsure) {
602 FolderItem *save_unsure_folder;
604 if ((!config.save_unsure_folder) ||
605 (config.save_unsure_folder[0] == '\0') ||
606 ((save_unsure_folder = folder_find_item_from_identifier(config.save_unsure_folder)) == NULL))
607 save_unsure_folder = folder_get_default_inbox();
608 if (save_unsure_folder) {
609 for (cur = new_unsure; cur; cur = cur->next) {
610 msginfo = (MsgInfo *)cur->data;
611 msginfo->is_move = TRUE;
612 msginfo->to_filter_folder = save_unsure_folder;
617 g_slist_free(new_hams);
618 g_slist_free(new_unsure);
619 g_slist_free(new_spams);
620 g_slist_free(whitelisted_new_spams);
622 if (message_callback != NULL)
623 message_callback(NULL, 0, 0, FALSE);
624 mail_filtering_data->filtered = g_slist_reverse(
625 mail_filtering_data->filtered);
626 mail_filtering_data->unfiltered = g_slist_reverse(
627 mail_filtering_data->unfiltered);
632 BogofilterConfig *bogofilter_get_config(void)
637 int bogofilter_learn(MsgInfo *msginfo, GSList *msglist, gboolean spam)
641 const gchar *bogo_exec = (config.bogopath && *config.bogopath) ? config.bogopath:"bogofilter";
643 if (msginfo == NULL && msglist == NULL) {
648 file = procmsg_get_message_file(msginfo);
652 if (message_callback != NULL)
653 message_callback(_("Bogofilter: learning from message..."), 0, 0, FALSE);
656 cmd = g_strdup_printf("%s -s -I '%s'", bogo_exec, file);
657 else if (MSG_IS_SPAM(msginfo->flags))
658 /* correct bogofilter, this wasn't spam */
659 cmd = g_strdup_printf("%s -Sn -I '%s'", bogo_exec, file);
662 cmd = g_strdup_printf("%s -n -I '%s'", bogo_exec, file);
664 debug_print("%s\n", cmd);
665 if ((status = execute_command_line(cmd, FALSE)) != 0)
666 log_error(_("Learning failed; `%s` returned with status %d."),
670 if (message_callback != NULL)
671 message_callback(NULL, 0, 0, FALSE);
676 GSList *cur = msglist;
678 int total = g_slist_length(msglist);
680 gboolean some_correction = FALSE, some_no_correction = FALSE;
682 if (message_callback != NULL)
683 message_callback(_("Bogofilter: learning from messages..."), total, 0, FALSE);
685 for (cur = msglist; cur && status == 0; cur = cur->next) {
686 info = (MsgInfo *)cur->data;
688 some_no_correction = TRUE;
689 else if (MSG_IS_SPAM(info->flags))
690 /* correct bogofilter, this wasn't spam */
691 some_correction = TRUE;
693 some_no_correction = TRUE;
697 if (some_correction && some_no_correction) {
698 /* we potentially have to do different stuff for every mail */
699 for (cur = msglist; cur && status == 0; cur = cur->next) {
700 info = (MsgInfo *)cur->data;
701 file = procmsg_get_message_file(info);
705 cmd = g_strdup_printf("%s -s -I '%s'", bogo_exec, file);
706 else if (MSG_IS_SPAM(info->flags))
707 /* correct bogofilter, this wasn't spam */
708 cmd = g_strdup_printf("%s -Sn -I '%s'", bogo_exec, file);
711 cmd = g_strdup_printf("%s -n -I '%s'", bogo_exec, file);
713 debug_print("%s\n", cmd);
714 if ((status = execute_command_line(cmd, FALSE)) != 0)
715 log_error(_("Learning failed; `%s` returned with status %d."),
721 if (message_callback != NULL)
722 message_callback(NULL, total, done, FALSE);
724 } else if (some_correction || some_no_correction) {
730 GError *error = NULL;
731 gboolean bogo_forked;
733 bogo_args[0] = (gchar *)bogo_exec;
734 if (some_correction && !some_no_correction)
735 bogo_args[1] = "-Sn";
736 else if (some_no_correction && !some_correction)
737 bogo_args[1] = spam ? "-s":"-n";
740 debug_print("|%s %s %s ...\n", bogo_args[0], bogo_args[1], bogo_args[2]);
741 bogo_forked = g_spawn_async_with_pipes(
742 NULL, bogo_args,NULL, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
743 NULL, NULL, &bogo_pid, &bogo_stdin,
746 while (bogo_forked && cur) {
748 info = (MsgInfo *)cur->data;
749 file = procmsg_get_message_file(info);
751 tmp = g_strdup_printf("%s\n",
753 write_all(bogo_stdin, tmp, strlen(tmp));
758 if (message_callback != NULL)
759 message_callback(NULL, total, done, FALSE);
764 waitpid(bogo_pid, &status, 0);
765 if (!WIFEXITED(status))
768 status = WEXITSTATUS(status);
770 if (!bogo_forked || status != 0) {
771 log_error(_("Learning failed; `%s %s %s` returned with error:\n%s"),
772 bogo_args[0], bogo_args[1], bogo_args[2],
773 error ? error->message:_("Unknown error"));
780 if (message_callback != NULL)
781 message_callback(NULL, 0, 0, FALSE);
787 void bogofilter_save_config(void)
792 debug_print("Saving Bogofilter Page\n");
794 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
795 pfile = prefs_write_open(rcpath);
797 if (!pfile || (prefs_set_block_label(pfile, "Bogofilter") < 0))
800 if (prefs_write_param(param, pfile->fp) < 0) {
801 g_warning("Failed to write Bogofilter configuration to file\n");
802 prefs_file_close_revert(pfile);
805 fprintf(pfile->fp, "\n");
807 prefs_file_close(pfile);
810 void bogofilter_set_message_callback(MessageCallback callback)
812 message_callback = callback;
815 gint plugin_init(gchar **error)
821 if (!check_plugin_version(MAKE_NUMERIC_VERSION(0, 9, 3, 86),
822 VERSION_NUMERIC, PLUGIN_NAME, error))
825 prefs_set_default(param);
826 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
827 prefs_read_config(param, "Bogofilter", rcpath, NULL);
830 bogofilter_gtk_init();
832 debug_print("Bogofilter plugin loaded\n");
835 bogofilter_start_thread();
838 if (config.process_emails) {
839 bogofilter_register_hook();
842 procmsg_register_spam_learner(bogofilter_learn);
843 procmsg_spam_set_folder(config.save_folder);
849 void plugin_done(void)
852 bogofilter_unregister_hook();
855 bogofilter_stop_thread();
857 g_free(config.save_folder);
858 bogofilter_gtk_done();
859 procmsg_unregister_spam_learner(bogofilter_learn);
860 procmsg_spam_set_folder(NULL);
861 debug_print("Bogofilter plugin unloaded\n");
864 const gchar *plugin_name(void)
869 const gchar *plugin_desc(void)
871 return _("This plugin can check all messages that are received from an "
872 "IMAP, LOCAL or POP account for spam using Bogofilter. "
873 "You will need Bogofilter installed locally.\n "
875 "Before Bogofilter can recognize spam messages, you have to "
876 "train it by marking a few hundred spam and ham messages. "
877 "Use \"/Mark/Mark as spam\" and \"/Mark/Mark as ham\" to "
878 "train Bogofilter.\n"
880 "When a message is identified as spam it can be deleted or "
881 "saved in a specially designated folder.\n"
883 "Options can be found in /Configuration/Preferences/Plugins/Bogofilter");
886 const gchar *plugin_type(void)
891 const gchar *plugin_licence(void)
896 const gchar *plugin_version(void)
901 struct PluginFeature *plugin_provides(void)
903 static struct PluginFeature features[] =
904 { {PLUGIN_FILTERING, N_("Spam detection")},
905 {PLUGIN_FILTERING, N_("Spam learning")},
906 {PLUGIN_NOTHING, NULL}};
910 void bogofilter_register_hook(void)
913 hook_id = hooks_register_hook(MAIL_LISTFILTERING_HOOKLIST, mail_filtering_hook, NULL);
915 g_warning("Failed to register mail filtering hook");
916 config.process_emails = FALSE;
920 void bogofilter_unregister_hook(void)
923 hooks_unregister_hook(MAIL_LISTFILTERING_HOOKLIST, hook_id);