fix some string inconsistencies
[claws.git] / src / plugins / bsfilter / bsfilter.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2009 Colin Leroy <colin@colino.net> and 
4  * the Claws Mail team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  * 
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #include "claws-features.h"
24 #endif
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28
29 #include "defs.h"
30
31 #include <sys/types.h>
32 #if HAVE_SYS_WAIT_H
33 #include <sys/wait.h>
34 #endif
35 #ifdef USE_PTHREAD
36 #include <pthread.h>
37 #endif
38 #include <errno.h>
39
40 #include <glib.h>
41
42 #include "common/claws.h"
43 #include "common/version.h"
44 #include "plugin.h"
45 #include "common/utils.h"
46 #include "hooks.h"
47 #include "procmsg.h"
48 #include "folder.h"
49 #include "prefs.h"
50 #include "prefs_gtk.h"
51 #include "utils.h"
52
53 #include "bsfilter.h"
54 #include "inc.h"
55 #include "log.h"
56 #include "prefs_common.h"
57 #include "alertpanel.h"
58 #include "addr_compl.h"
59
60 #ifdef HAVE_SYSEXITS_H
61 #include <sysexits.h>
62 #endif
63 #ifdef HAVE_ERRNO_H
64 #include <errno.h>
65 #endif
66 #ifdef HAVE_SYS_ERRNO_H
67 #include <sys/errno.h>
68 #endif
69 #ifdef HAVE_TIME_H
70 #include <time.h>
71 #endif
72 #ifdef HAVE_SYS_TIME_H
73 #include <sys/time.h>
74 #endif
75 #ifdef HAVE_SIGNAL_H
76 #include <signal.h>
77 #endif
78 #ifdef HAVE_PWD_H
79 #include <pwd.h>
80 #endif
81
82 #define PLUGIN_NAME (_("Bsfilter"))
83
84 static guint hook_id = -1;
85 static MessageCallback message_callback;
86
87 static BsfilterConfig config;
88
89 static PrefParam param[] = {
90         {"process_emails", "TRUE", &config.process_emails, P_BOOL,
91          NULL, NULL, NULL},
92         {"receive_spam", "TRUE", &config.receive_spam, P_BOOL,
93          NULL, NULL, NULL},
94         {"save_folder", NULL, &config.save_folder, P_STRING,
95          NULL, NULL, NULL},
96         {"max_size", "250", &config.max_size, P_INT,
97          NULL, NULL, NULL},
98 #ifndef G_OS_WIN32
99         {"bspath", "bsfilter", &config.bspath, P_STRING,
100          NULL, NULL, NULL},
101 #else
102         {"bspath", "bsfilterw.exe", &config.bspath, P_STRING,
103          NULL, NULL, NULL},
104 #endif
105         {"whitelist_ab", "FALSE", &config.whitelist_ab, P_BOOL,
106          NULL, NULL, NULL},
107         {"whitelist_ab_folder", N_("Any"), &config.whitelist_ab_folder, P_STRING,
108          NULL, NULL, NULL},
109         {"learn_from_whitelist", "FALSE", &config.learn_from_whitelist, P_BOOL,
110          NULL, NULL, NULL},
111         {"mark_as_read", "TRUE", &config.mark_as_read, P_BOOL,
112          NULL, NULL, NULL},
113
114         {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
115 };
116
117 typedef struct _BsFilterData {
118         MailFilteringData *mail_filtering_data;
119         gchar **bs_args;
120         MsgInfo *msginfo;
121         gboolean done;
122         int status;
123         int whitelisted;
124         gboolean in_thread;
125 } BsFilterData;
126
127 static BsFilterData *to_filter_data = NULL;
128 #ifdef USE_PTHREAD
129 static gboolean filter_th_done = FALSE;
130 static pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;
131 static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER; 
132 static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; 
133 #endif
134
135 static gboolean found_in_addressbook(const gchar *address)
136 {
137         gchar *addr = NULL;
138         gboolean found = FALSE;
139         gint num_addr = 0;
140         
141         if (!address)
142                 return FALSE;
143         
144         addr = g_strdup(address);
145         extract_address(addr);
146         num_addr = complete_address(addr);
147         if (num_addr > 1) {
148                 /* skip first item (this is the search string itself) */
149                 int i = 1;
150                 for (; i < num_addr && !found; i++) {
151                         gchar *caddr = get_complete_address(i);
152                         extract_address(caddr);
153                         if (strcasecmp(caddr, addr) == 0)
154                                 found = TRUE;
155                         g_free(caddr);
156                 }
157         }
158         g_free(addr);
159         return found;
160 }
161
162 static void bsfilter_do_filter(BsFilterData *data)
163 {
164         int status = 0;
165         gchar *file = NULL;
166         gboolean whitelisted = FALSE;
167         MsgInfo *msginfo = to_filter_data->msginfo;
168
169         if (config.whitelist_ab) {
170                 gchar *ab_folderpath;
171
172                 if (*config.whitelist_ab_folder == '\0' ||
173                         strcasecmp(config.whitelist_ab_folder, "Any") == 0) {
174                         /* match the whole addressbook */
175                         ab_folderpath = NULL;
176                 } else {
177                         /* match the specific book/folder of the addressbook */
178                         ab_folderpath = config.whitelist_ab_folder;
179                 }
180
181                 start_address_completion(ab_folderpath);
182         }
183
184         debug_print("Filtering message %d\n", msginfo->msgnum);
185
186         if (config.whitelist_ab && msginfo->from && 
187             found_in_addressbook(msginfo->from))
188                 whitelisted = TRUE;
189
190         /* can set flags (SCANNED, ATTACHMENT) but that's ok 
191          * as GUI updates are hooked not direct */
192
193         file = procmsg_get_message_file(msginfo);
194
195         if (file) {
196 #ifndef G_OS_WIN32
197                 gchar *classify = g_strconcat((config.bspath && *config.bspath) ? config.bspath:"bsfilter",
198                         " --homedir '",get_rc_dir(),"' '", file, "'", NULL);
199 #else
200                 gchar *classify = g_strconcat((config.bspath && *config.bspath) ? config.bspath:"bsfilterw.exe",
201                         " --homedir '",get_rc_dir(),"' '", file, "'", NULL);
202 #endif
203                 status = execute_command_line(classify, FALSE);
204         }
205
206         if (config.whitelist_ab)
207                 end_address_completion();
208
209         to_filter_data->status = status; 
210         to_filter_data->whitelisted = whitelisted; 
211 }
212
213 #ifdef USE_PTHREAD
214 static void *bsfilter_filtering_thread(void *data) 
215 {
216         while (!filter_th_done) {
217                 pthread_mutex_lock(&list_mutex);
218                 if (to_filter_data == NULL || to_filter_data->done == TRUE) {
219                         pthread_mutex_unlock(&list_mutex);
220                         debug_print("thread is waiting for something to filter\n");
221                         pthread_mutex_lock(&wait_mutex);
222                         pthread_cond_wait(&wait_cond, &wait_mutex);
223                         pthread_mutex_unlock(&wait_mutex);
224                 } else {
225                         debug_print("thread awaken with something to filter\n");
226                         to_filter_data->done = FALSE;
227                         bsfilter_do_filter(to_filter_data);
228                         pthread_mutex_unlock(&list_mutex);
229                         to_filter_data->done = TRUE;
230                         g_usleep(100);
231                 }
232         }
233         return NULL;
234 }
235
236 static pthread_t filter_th;
237 static int filter_th_started = 0;
238
239 static void bsfilter_start_thread(void)
240 {
241         filter_th_done = FALSE;
242         if (filter_th_started != 0)
243                 return;
244         if (pthread_create(&filter_th, 0, 
245                         bsfilter_filtering_thread, 
246                         NULL) != 0) {
247                 filter_th_started = 0;
248                 return;
249         }
250         debug_print("thread created\n");
251         filter_th_started = 1;
252 }
253
254 static void bsfilter_stop_thread(void)
255 {
256         void *res;
257         while (pthread_mutex_trylock(&list_mutex) != 0) {
258                 GTK_EVENTS_FLUSH();
259                 g_usleep(100);
260         }
261         if (filter_th_started != 0) {
262                 filter_th_done = TRUE;
263                 debug_print("waking thread up\n");
264                 pthread_mutex_lock(&wait_mutex);
265                 pthread_cond_broadcast(&wait_cond);
266                 pthread_mutex_unlock(&wait_mutex);
267                 pthread_join(filter_th, &res);
268                 filter_th_started = 0;
269         }
270         pthread_mutex_unlock(&list_mutex);
271         debug_print("thread done\n");
272 }
273 #endif
274
275 static gboolean mail_filtering_hook(gpointer source, gpointer data)
276 {
277         MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
278         MsgInfo *msginfo = mail_filtering_data->msginfo;
279         static gboolean warned_error = FALSE;
280         int status = 0, whitelisted = 0;
281 #ifndef G_OS_WIN32
282         gchar *bs_exec = (config.bspath && *config.bspath) ? config.bspath:"bsfilter";
283 #else
284         gchar *bs_exec = (config.bspath && *config.bspath) ? config.bspath:"bsfilterw.exe";
285 #endif
286         gboolean filtered = FALSE;
287         
288         if (!config.process_emails) {
289                 return filtered;
290         }
291         
292         if (msginfo == NULL) {
293                 g_warning("wrong call to bsfilter mail_filtering_hook");
294                 return filtered;
295         }
296                 
297         /* we have to make sure the mails are cached - or it'll break on IMAP */
298         if (message_callback != NULL)
299                 message_callback(_("Bsfilter: fetching body..."), 0, 0, FALSE);
300         if (msginfo) {
301                 gchar *file = procmsg_get_message_file(msginfo);
302                 g_free(file);
303         }
304         if (message_callback != NULL)
305                 message_callback(NULL, 0, 0, FALSE);
306
307         if (message_callback != NULL)
308                 message_callback(_("Bsfilter: filtering message..."), 0, 0, FALSE);
309
310 #ifdef USE_PTHREAD
311         while (pthread_mutex_trylock(&list_mutex) != 0) {
312                 GTK_EVENTS_FLUSH();
313                 g_usleep(100);
314         }
315 #endif
316
317         to_filter_data = g_new0(BsFilterData, 1);
318         to_filter_data->msginfo = msginfo;
319         to_filter_data->mail_filtering_data = mail_filtering_data;
320         to_filter_data->done = FALSE;
321         to_filter_data->status = -1;
322         to_filter_data->whitelisted = 0;
323 #ifdef USE_PTHREAD
324         to_filter_data->in_thread = (filter_th_started != 0);
325 #else
326         to_filter_data->in_thread = FALSE;
327 #endif
328
329 #ifdef USE_PTHREAD
330         pthread_mutex_unlock(&list_mutex);
331         
332         if (filter_th_started != 0) {
333                 debug_print("waking thread to let it filter things\n");
334                 pthread_mutex_lock(&wait_mutex);
335                 pthread_cond_broadcast(&wait_cond);
336                 pthread_mutex_unlock(&wait_mutex);
337
338                 while (!to_filter_data->done) {
339                         GTK_EVENTS_FLUSH();
340                         g_usleep(100);
341                 }
342         }
343
344         while (pthread_mutex_trylock(&list_mutex) != 0) {
345                 GTK_EVENTS_FLUSH();
346                 g_usleep(100);
347
348         }
349         if (filter_th_started == 0)
350                 bsfilter_do_filter(to_filter_data);
351 #else
352         bsfilter_do_filter(to_filter_data);     
353 #endif
354
355         status = to_filter_data->status;
356         whitelisted = to_filter_data->whitelisted;
357
358         g_free(to_filter_data);
359         to_filter_data = NULL;
360 #ifdef USE_PTHREAD
361         pthread_mutex_unlock(&list_mutex);
362 #endif
363
364         if (status == 1) {
365                 procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
366                 debug_print("unflagging ham: %d\n", msginfo->msgnum);
367                 filtered = FALSE;
368         } else {
369                 if (!whitelisted || (whitelisted && !config.learn_from_whitelist)) {
370                         procmsg_msginfo_set_flags(msginfo, MSG_SPAM, 0);
371                         debug_print("flagging spam: %d\n", msginfo->msgnum);
372                         filtered = TRUE;
373                 }
374                 if (whitelisted && config.learn_from_whitelist) {
375                         bsfilter_learn(msginfo, NULL, FALSE);
376                         procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
377                         debug_print("unflagging ham: %d\n", msginfo->msgnum);
378                         filtered = FALSE;
379                 }
380                 if (MSG_IS_SPAM(msginfo->flags) && config.receive_spam) {
381                         if (config.receive_spam && config.mark_as_read)
382                                 procmsg_msginfo_unset_flags(msginfo, (MSG_NEW|MSG_UNREAD), 0);
383                         if (!config.receive_spam)
384                                 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
385                         filtered = TRUE;
386                 }
387         }
388                 
389         if (status < 0 || status > 2) { /* I/O or other errors */
390                 gchar *msg = NULL;
391                 
392                 if (status == 3)
393                         msg =  g_strdup_printf(_("The Bsfilter plugin couldn't filter "
394                                            "a message. The probable cause of the "
395                                            "error is that it didn't learn from any mail.\n"
396                                            "Use \"/Mark/Mark as spam\" and \"/Mark/Mark as "
397                                            "ham\" to train Bsfilter with a few hundred "
398                                            "spam and ham messages."));
399                 else
400                         msg =  g_strdup_printf(_("The Bsfilter plugin couldn't filter "
401                                            "a message. The command `%s` couldn't be run."), 
402                                            bs_exec);
403                 if (!prefs_common.no_recv_err_panel) {
404                         if (!warned_error) {
405                                 alertpanel_error("%s", msg);
406                         }
407                         warned_error = TRUE;
408                 } else {
409                         log_error(LOG_PROTOCOL, "%s\n", msg);
410                 }
411                 g_free(msg);
412         }
413
414         if (status == 0) {
415                 if (config.receive_spam && MSG_IS_SPAM(msginfo->flags)) {
416                         FolderItem *save_folder = NULL;
417
418                         if ((!config.save_folder) ||
419                             (config.save_folder[0] == '\0') ||
420                             ((save_folder = folder_find_item_from_identifier(config.save_folder)) == NULL)) {
421                                 if (mail_filtering_data->account && mail_filtering_data->account->set_trash_folder) {
422                                         save_folder = folder_find_item_from_identifier(
423                                                 mail_filtering_data->account->trash_folder);
424                                         if (save_folder)
425                                                 debug_print("found trash folder from account's advanced settings\n");
426                                 }
427                                 if (save_folder == NULL && mail_filtering_data->account &&
428                                     mail_filtering_data->account->folder) {
429                                         save_folder = mail_filtering_data->account->folder->trash;
430                                         if (save_folder)
431                                                 debug_print("found trash folder from account's trash\n");
432                                 }
433                                 if (save_folder == NULL && mail_filtering_data->account &&
434                                     !mail_filtering_data->account->folder)  {
435                                         if (mail_filtering_data->account->inbox) {
436                                                 FolderItem *item = folder_find_item_from_identifier(
437                                                         mail_filtering_data->account->inbox);
438                                                 if (item && item->folder->trash) {
439                                                         save_folder = item->folder->trash;
440                                                         debug_print("found trash folder from account's inbox\n");
441                                                 }
442                                         } 
443                                         if (!save_folder && mail_filtering_data->account->local_inbox) {
444                                                 FolderItem *item = folder_find_item_from_identifier(
445                                                         mail_filtering_data->account->local_inbox);
446                                                 if (item && item->folder->trash) {
447                                                         save_folder = item->folder->trash;
448                                                         debug_print("found trash folder from account's local_inbox\n");
449                                                 }
450                                         }
451                                 }
452                                 if (save_folder == NULL) {
453                                         debug_print("using default trash folder\n");
454                                         save_folder = folder_get_default_trash();
455                                 }
456                         }
457                         if (save_folder) {
458                                 msginfo->filter_op = IS_MOVE;
459                                 msginfo->to_filter_folder = save_folder;
460                         }
461                 }
462         } 
463         if (message_callback != NULL)
464                 message_callback(NULL, 0, 0, FALSE);
465         
466         return filtered;
467 }
468
469 BsfilterConfig *bsfilter_get_config(void)
470 {
471         return &config;
472 }
473
474 int bsfilter_learn(MsgInfo *msginfo, GSList *msglist, gboolean spam)
475 {
476         gchar *cmd = NULL;
477         gchar *file = NULL;
478 #ifndef G_OS_WIN32
479         gchar *bs_exec = (config.bspath && *config.bspath) ? config.bspath:"bsfilter";
480 #else
481         gchar *bs_exec = (config.bspath && *config.bspath) ? config.bspath:"bsfilterw.exe";
482 #endif
483         gint status = 0;
484         gboolean free_list = FALSE;
485         GSList *cur = NULL;
486
487         if (msginfo == NULL && msglist == NULL) {
488                 return -1;
489         }
490         if (msginfo != NULL && msglist == NULL) {
491                 msglist = g_slist_append(NULL, msginfo);
492                 free_list = TRUE;
493         }
494         for (cur = msglist; cur; cur = cur->next) {
495                 msginfo = (MsgInfo *)cur->data;
496                 file = procmsg_get_message_file(msginfo);
497                 if (file == NULL) {
498                         return -1;
499                 } else {
500                         if (message_callback != NULL)
501                                 message_callback(_("Bsfilter: learning from message..."), 0, 0, FALSE);
502                         if (spam)
503                                 /* learn as spam */
504                                 cmd = g_strdup_printf("%s --homedir '%s' -su '%s'", bs_exec, get_rc_dir(), file);
505                         else 
506                                 /* learn as ham */
507                                 cmd = g_strdup_printf("%s --homedir '%s' -cu '%s'", bs_exec, get_rc_dir(), file);
508                                 
509                         debug_print("%s\n", cmd);
510                         if ((status = execute_command_line(cmd, FALSE)) != 0)
511                                 log_error(LOG_PROTOCOL, _("Learning failed; `%s` returned with status %d."),
512                                                 cmd, status);
513                         g_free(cmd);
514                         g_free(file);
515                         if (message_callback != NULL)
516                                 message_callback(NULL, 0, 0, FALSE);
517                 }
518         }
519         if (free_list)
520                 g_slist_free(msglist);
521
522         return 0;
523 }
524
525 void bsfilter_save_config(void)
526 {
527         PrefFile *pfile;
528         gchar *rcpath;
529
530         debug_print("Saving Bsfilter Page\n");
531
532         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
533         pfile = prefs_write_open(rcpath);
534         g_free(rcpath);
535         if (!pfile || (prefs_set_block_label(pfile, "Bsfilter") < 0))
536                 return;
537
538         if (prefs_write_param(param, pfile->fp) < 0) {
539                 g_warning("Failed to write Bsfilter configuration to file\n");
540                 prefs_file_close_revert(pfile);
541                 return;
542         }
543         if (fprintf(pfile->fp, "\n") < 0) {
544                 FILE_OP_ERROR(rcpath, "fprintf");
545                 prefs_file_close_revert(pfile);
546         } else
547                 prefs_file_close(pfile);
548 }
549
550 void bsfilter_set_message_callback(MessageCallback callback)
551 {
552         message_callback = callback;
553 }
554
555 gint plugin_init(gchar **error)
556 {
557         gchar *rcpath;
558         hook_id = -1;
559
560         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
561                                 VERSION_NUMERIC, PLUGIN_NAME, error))
562                 return -1;
563
564         prefs_set_default(param);
565         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
566         prefs_read_config(param, "Bsfilter", rcpath, NULL);
567         g_free(rcpath);
568
569         bsfilter_gtk_init();
570                 
571         debug_print("Bsfilter plugin loaded\n");
572
573 #ifdef USE_PTHREAD
574         bsfilter_start_thread();
575 #endif
576
577         if (config.process_emails) {
578                 bsfilter_register_hook();
579         }
580
581         procmsg_register_spam_learner(bsfilter_learn);
582         procmsg_spam_set_folder(config.save_folder, bsfilter_get_spam_folder);
583
584         return 0;
585         
586 }
587
588 FolderItem *bsfilter_get_spam_folder(MsgInfo *msginfo)
589 {
590         FolderItem *item = NULL;
591         
592         if (config.save_folder != NULL) {
593                 item = folder_find_item_from_identifier(config.save_folder);
594         }
595
596         if (item || msginfo == NULL || msginfo->folder == NULL)
597                 return item;
598
599         if (msginfo->folder->folder &&
600             msginfo->folder->folder->account && 
601             msginfo->folder->folder->account->set_trash_folder) {
602                 item = folder_find_item_from_identifier(
603                         msginfo->folder->folder->account->trash_folder);
604         }
605
606         if (item == NULL && 
607             msginfo->folder->folder &&
608             msginfo->folder->folder->trash)
609                 item = msginfo->folder->folder->trash;
610                 
611         if (item == NULL)
612                 item = folder_get_default_trash();
613                 
614         debug_print("bs spam dir: %s\n", folder_item_get_path(item));
615         return item;
616 }
617
618 gboolean plugin_done(void)
619 {
620         if (hook_id != -1) {
621                 bsfilter_unregister_hook();
622         }
623 #ifdef USE_PTHREAD
624         bsfilter_stop_thread();
625 #endif
626         g_free(config.save_folder);
627         bsfilter_gtk_done();
628         procmsg_unregister_spam_learner(bsfilter_learn);
629         procmsg_spam_set_folder(NULL, NULL);
630         debug_print("Bsfilter plugin unloaded\n");
631         return TRUE;
632 }
633
634 const gchar *plugin_name(void)
635 {
636         return PLUGIN_NAME;
637 }
638
639 const gchar *plugin_desc(void)
640 {
641         return _("This plugin can check all messages that are received from an "
642                  "IMAP, LOCAL or POP account for spam using Bsfilter. "
643                  "You will need Bsfilter installed locally.\n"
644                  "\n"
645                  "Before Bsfilter can recognize spam messages, you have to "
646                  "train it by marking a few hundred spam and ham messages "
647                  "with the use of \"/Mark/Mark as spam\" and \"/Mark/Mark as "
648                  "ham\".\n"
649                  "\n"
650                  "When a message is identified as spam it can be deleted or "
651                  "saved in a specially designated folder.\n"
652                  "\n"
653                  "Options can be found in /Configuration/Preferences/Plugins/Bsfilter");
654 }
655
656 const gchar *plugin_type(void)
657 {
658         return "GTK2";
659 }
660
661 const gchar *plugin_licence(void)
662 {
663         return "GPL3+";
664 }
665
666 const gchar *plugin_version(void)
667 {
668         return VERSION;
669 }
670
671 struct PluginFeature *plugin_provides(void)
672 {
673         static struct PluginFeature features[] = 
674                 { {PLUGIN_FILTERING, N_("Spam detection")},
675                   {PLUGIN_FILTERING, N_("Spam learning")},
676                   {PLUGIN_NOTHING, NULL}};
677         return features;
678 }
679
680 void bsfilter_register_hook(void)
681 {
682         if (hook_id == -1)
683                 hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
684         if (hook_id == -1) {
685                 g_warning("Failed to register mail filtering hook");
686                 config.process_emails = FALSE;
687         }
688 }
689
690 void bsfilter_unregister_hook(void)
691 {
692         if (hook_id != -1) {
693                 hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
694         }
695         hook_id = -1;
696 }