fdd7ba3493b29b5e3fa647c4c681fb183c762b6b
[claws.git] / src / plugins / bogofilter / bogofilter.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Claws Mail Team
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include <errno.h>
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32
33 #if HAVE_LOCALE_H
34 #  include <locale.h>
35 #endif
36
37 #include "common/claws.h"
38 #include "common/version.h"
39 #include "plugin.h"
40 #include "common/utils.h"
41 #include "hooks.h"
42 #include "procmsg.h"
43 #include "folder.h"
44 #include "prefs.h"
45 #include "prefs_gtk.h"
46
47 #include "bogofilter.h"
48 #include "inc.h"
49 #include "log.h"
50 #include "prefs_common.h"
51 #include "alertpanel.h"
52
53 #ifdef HAVE_SYSEXITS_H
54 #include <sysexits.h>
55 #endif
56 #ifdef HAVE_ERRNO_H
57 #include <errno.h>
58 #endif
59 #ifdef HAVE_SYS_ERRNO_H
60 #include <sys/errno.h>
61 #endif
62 #ifdef HAVE_TIME_H
63 #include <time.h>
64 #endif
65 #ifdef HAVE_SYS_TIME_H
66 #include <sys/time.h>
67 #endif
68 #ifdef HAVE_SIGNAL_H
69 #include <signal.h>
70 #endif
71 #ifdef HAVE_PWD_H
72 #include <pwd.h>
73 #endif
74
75 static guint hook_id = -1;
76 static MessageCallback message_callback;
77
78 static BogofilterConfig config;
79
80 static PrefParam param[] = {
81         {"process_emails", "TRUE", &config.process_emails, P_BOOL,
82          NULL, NULL, NULL},
83         {"receive_spam", "TRUE", &config.receive_spam, P_BOOL,
84          NULL, NULL, NULL},
85         {"save_folder", NULL, &config.save_folder, P_STRING,
86          NULL, NULL, NULL},
87         {"max_size", "250", &config.max_size, P_INT,
88          NULL, NULL, NULL},
89         {"bogopath", "bogofilter", &config.bogopath, P_STRING,
90          NULL, NULL, NULL},
91         {"insert_header", "FALSE", &config.insert_header, P_BOOL,
92          NULL, NULL, NULL},
93
94         {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
95 };
96
97 /*
98  * Helper function for spawn_with_input() - write an entire
99  * string to a fd.
100  */
101 static gboolean
102 write_all (int         fd,
103            const char *buf,
104            gsize       to_write)
105 {
106   while (to_write > 0)
107     {
108       gssize count = write (fd, buf, to_write);
109       if (count < 0)
110         {
111           if (errno != EINTR)
112             return FALSE;
113         }
114       else
115         {
116           to_write -= count;
117           buf += count;
118         }
119     }
120
121   return TRUE;
122 }
123
124 typedef struct _BogoFilterData {
125         MailFilteringData *mail_filtering_data;
126         gchar **bogo_args;
127         GSList *msglist;
128         GSList *new_hams;
129         GSList *new_spams;
130         GSList *spams_to_receive;
131         gboolean done;
132         int status;
133         gboolean in_thread;
134 } BogoFilterData;
135
136 static BogoFilterData *to_filter_data = NULL;
137 #ifdef USE_PTHREAD
138 static gboolean filter_th_done = FALSE;
139 static pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;
140 static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER; 
141 static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; 
142 #endif
143
144 static void bogofilter_do_filter(BogoFilterData *data)
145 {
146         GPid bogo_pid;
147         gint bogo_stdin, bogo_stdout;
148         GError *error = NULL;
149         gboolean bogo_forked;
150         int status = 0;
151         MsgInfo *msginfo;
152         GSList *cur = NULL;
153         int total = 0, curnum = 0;
154         gchar *file = NULL;
155         gchar buf[BUFSIZ];
156
157         total = g_slist_length(data->msglist);
158
159         bogo_forked = g_spawn_async_with_pipes(
160                         NULL, data->bogo_args,NULL, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
161                         NULL, NULL, &bogo_pid, &bogo_stdin,
162                         &bogo_stdout, NULL, &error);
163                 
164         if (bogo_forked == FALSE) {
165                 g_warning("%s\n", error ? error->message:"ERROR???");
166                 g_error_free(error);
167                 error = NULL;
168                 status = -1;
169         } else {
170                 for (cur = data->msglist; cur; cur = cur->next) {
171                         msginfo = (MsgInfo *)cur->data;
172                         debug_print("Filtering message %d (%d/%d)\n", msginfo->msgnum, curnum, total);
173
174                         if (message_callback != NULL)
175                                 message_callback(NULL, total, curnum++, data->in_thread);
176
177                         /* can set flags (SCANNED, ATTACHMENT) but that's ok 
178                          * as GUI updates are hooked not direct */
179                         file = procmsg_get_message_file(msginfo);
180                         if (file) {
181                                 gchar *tmp = g_strdup_printf("%s\n",file);
182                                 write_all(bogo_stdin, tmp, strlen(tmp));
183                                 g_free(tmp);
184                                 memset(buf, 0, sizeof(buf));
185                                 if (read(bogo_stdout, buf, sizeof(buf)-1) < 0) {
186                                         g_warning("bogofilter short read\n");
187                                         debug_print("message %d is ham\n", msginfo->msgnum);
188                                         data->mail_filtering_data->unfiltered = g_slist_prepend(
189                                                 data->mail_filtering_data->unfiltered, msginfo);
190                                         data->new_hams = g_slist_prepend(data->new_hams, msginfo);
191                                 } else {
192                                         gchar **parts = NULL;
193                                         if (strchr(buf, '/')) {
194                                                 tmp = strrchr(buf, '/')+1;
195                                         } else {
196                                                 tmp = buf;
197                                         }
198                                         parts = g_strsplit(tmp, " ", 0);
199                                         debug_print("read %s\n", buf);
200                                         if (parts && parts[0] && parts[1] && parts[2] && 
201                                             FOLDER_TYPE(msginfo->folder->folder) == F_MH &&
202                                             config.insert_header) {
203                                                 gchar *tmpfile = get_tmp_file();
204                                                 FILE *input = fopen(file, "r");
205                                                 FILE *output = fopen(tmpfile, "w");
206                                                 if (strstr(parts[2], "\n"))
207                                                         *(strstr(parts[2], "\n")) = '\0';
208                                                 if (input && !output) 
209                                                         fclose (input);
210                                                 else if (!input && output)
211                                                         fclose (output);
212                                                 else {
213                                                         gchar tmpbuf[BUFFSIZE];
214                                                         const gchar *bogosity = *parts[1] == 'S' ? "Spam":
215                                                                                  (*parts[1] == 'H' ? "Ham":"Unsure");
216                                                         gchar *tmpstr = g_strdup_printf(
217                                                                         "X-Claws-Bogosity: %s, spamicity=%s\n",
218                                                                         bogosity, parts[2]);
219                                                         fwrite(tmpstr, 1, strlen(tmpstr), output);
220                                                         while (fgets(tmpbuf, sizeof(buf), input))
221                                                                 fputs(tmpbuf, output);
222                                                         fclose(input);
223                                                         fclose(output);
224                                                         move_file(tmpfile, file, TRUE);
225                                                         g_free(tmpstr);
226                                                 }
227                                                 g_free(tmpfile);
228                                         }
229                                         if (parts && parts[0] && parts[1] && *parts[1] == 'S') {
230                                                 debug_print("message %d is spam\n", msginfo->msgnum);
231                                                 if (config.receive_spam) {
232                                                         data->spams_to_receive = g_slist_prepend(data->spams_to_receive, msginfo);
233                                                 } 
234
235                                                 data->mail_filtering_data->filtered = g_slist_prepend(
236                                                         data->mail_filtering_data->filtered, msginfo);
237                                                 data->new_spams = g_slist_prepend(data->new_spams, msginfo);
238                                         } else {
239                                                 debug_print("message %d is ham\n", msginfo->msgnum);
240                                                 data->mail_filtering_data->unfiltered = g_slist_prepend(
241                                                         data->mail_filtering_data->unfiltered, msginfo);
242                                                 data->new_hams = g_slist_prepend(data->new_hams, msginfo);
243                                         }
244                                         g_strfreev(parts);
245                                 }
246                                 g_free(file);
247                         } else {
248                                 data->mail_filtering_data->unfiltered = g_slist_prepend(
249                                         data->mail_filtering_data->unfiltered, msginfo);
250                                 data->new_hams = g_slist_prepend(data->new_hams, msginfo);
251                         }
252                 }
253         }
254         if (status != -1) {
255                 close(bogo_stdout);
256                 close(bogo_stdin);
257                 waitpid(bogo_pid, &status, 0);
258                 if (!WIFEXITED(status))
259                         status = -1;
260                 else
261                         status = WEXITSTATUS(status);
262         }
263         
264         to_filter_data->status = status; 
265 }
266
267 #ifdef USE_PTHREAD
268 static void *bogofilter_filtering_thread(void *data) 
269 {
270         while (!filter_th_done) {
271                 pthread_mutex_lock(&list_mutex);
272                 if (to_filter_data == NULL || to_filter_data->done == TRUE) {
273                         pthread_mutex_unlock(&list_mutex);
274                         debug_print("thread is waiting for something to filter\n");
275                         pthread_mutex_lock(&wait_mutex);
276                         pthread_cond_wait(&wait_cond, &wait_mutex);
277                         pthread_mutex_unlock(&wait_mutex);
278                 } else {
279                         debug_print("thread awaken with something to filter\n");
280                         to_filter_data->done = FALSE;
281                         bogofilter_do_filter(to_filter_data);
282                         pthread_mutex_unlock(&list_mutex);
283                         to_filter_data->done = TRUE;
284                         usleep(100);
285                 }
286         }
287         return NULL;
288 }
289
290 static pthread_t filter_th = 0;
291
292 static void bogofilter_start_thread(void)
293 {
294         filter_th_done = FALSE;
295         if (filter_th != 0 || 1)
296                 return;
297         if (pthread_create(&filter_th, 0, 
298                         bogofilter_filtering_thread, 
299                         NULL) != 0) {
300                 filter_th = 0;
301                 return;
302         }
303         debug_print("thread created\n");
304 }
305
306 static void bogofilter_stop_thread(void)
307 {
308         void *res;
309         while (pthread_mutex_trylock(&list_mutex) != 0) {
310                 GTK_EVENTS_FLUSH();
311                 usleep(100);
312         }
313         if (filter_th != 0) {
314                 filter_th_done = TRUE;
315                 debug_print("waking thread up\n");
316                 pthread_mutex_lock(&wait_mutex);
317                 pthread_cond_broadcast(&wait_cond);
318                 pthread_mutex_unlock(&wait_mutex);
319                 pthread_join(filter_th, &res);
320                 filter_th = 0;
321         }
322         pthread_mutex_unlock(&list_mutex);
323         debug_print("thread done\n");
324 }
325 #endif
326
327 static gboolean mail_filtering_hook(gpointer source, gpointer data)
328 {
329         MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
330         MsgInfo *msginfo = mail_filtering_data->msginfo;
331         GSList *msglist = mail_filtering_data->msglist;
332         GSList *cur = NULL;
333         static gboolean warned_error = FALSE;
334         int status = 0;
335         int total = 0, curnum = 0;
336         GSList *spams_to_receive = NULL, *new_hams = NULL, *new_spams = NULL;
337         gchar *bogo_exec = (config.bogopath && *config.bogopath) ? config.bogopath:"bogofilter";
338         gchar *bogo_args[4];
339         gboolean ok_to_thread = TRUE;
340
341         bogo_args[0] = bogo_exec;
342         bogo_args[1] = "-T";
343         bogo_args[2] = "-b";
344         bogo_args[3] = NULL;
345         
346         if (!config.process_emails) {
347                 return FALSE;
348         }
349         
350         if (msglist == NULL && msginfo != NULL) {
351                 g_warning("wrong call to bogofilter mail_filtering_hook");
352                 return FALSE;
353         }
354         
355         total = g_slist_length(msglist);
356         
357         /* we have to make sure the mails are cached - or it'll break on IMAP */
358         if (message_callback != NULL)
359                 message_callback(_("Bogofilter: fetching bodies..."), total, 0, FALSE);
360         for (cur = msglist; cur; cur = cur->next) {
361                 gchar *file = procmsg_get_message_file((MsgInfo *)cur->data);
362                 if (file == NULL)
363                         ok_to_thread = FALSE;
364                 if (message_callback != NULL)
365                         message_callback(NULL, total, curnum++, FALSE);
366                 g_free(file);
367         }
368         if (message_callback != NULL)
369                 message_callback(NULL, 0, 0, FALSE);
370
371         if (message_callback != NULL)
372                 message_callback(_("Bogofilter: filtering messages..."), total, 0, FALSE);
373
374 #ifdef USE_PTHREAD
375         while (pthread_mutex_trylock(&list_mutex) != 0) {
376                 GTK_EVENTS_FLUSH();
377                 usleep(100);
378         }
379 #endif
380         to_filter_data = g_new0(BogoFilterData, 1);
381         to_filter_data->msglist = msglist;
382         to_filter_data->mail_filtering_data = mail_filtering_data;
383         to_filter_data->spams_to_receive = NULL;
384         to_filter_data->new_hams = NULL;
385         to_filter_data->new_spams = NULL;
386         to_filter_data->done = FALSE;
387         to_filter_data->status = -1;
388         to_filter_data->bogo_args = bogo_args;
389 #ifdef USE_PTHREAD
390         to_filter_data->in_thread = (filter_th != 0 && ok_to_thread);
391 #else
392         to_filter_data->in_thread = FALSE;
393 #endif
394
395 #ifdef USE_PTHREAD
396         pthread_mutex_unlock(&list_mutex);
397         
398         if (filter_th != 0 && ok_to_thread) {
399                 debug_print("waking thread to let it filter things\n");
400                 pthread_mutex_lock(&wait_mutex);
401                 pthread_cond_broadcast(&wait_cond);
402                 pthread_mutex_unlock(&wait_mutex);
403
404                 while (!to_filter_data->done) {
405                         GTK_EVENTS_FLUSH();
406                         usleep(100);
407                 }
408         }
409
410         while (pthread_mutex_trylock(&list_mutex) != 0) {
411                 GTK_EVENTS_FLUSH();
412                 usleep(100);
413
414         }
415         if (filter_th == 0 || !ok_to_thread)
416                 bogofilter_do_filter(to_filter_data);
417 #else
418         bogofilter_do_filter(to_filter_data);   
419 #endif
420
421         spams_to_receive = to_filter_data->spams_to_receive;
422         new_hams = to_filter_data->new_hams;
423         new_spams = to_filter_data->new_spams;
424         status = to_filter_data->status;
425         g_free(to_filter_data);
426         to_filter_data = NULL;
427 #ifdef USE_PTHREAD
428         pthread_mutex_unlock(&list_mutex);
429 #endif
430
431
432         /* flag hams */
433         for (cur = new_hams; cur; cur = cur->next) {
434                 MsgInfo *msginfo = (MsgInfo *)cur->data;
435                 procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
436         }
437         g_slist_free(new_hams);
438         /* flag spams */
439         for (cur = new_spams; cur; cur = cur->next) {
440                 MsgInfo *msginfo = (MsgInfo *)cur->data;
441                 if (config.receive_spam) {
442                         procmsg_msginfo_change_flags(msginfo, MSG_SPAM, 0, ~0, 0);
443                 } else {
444                         folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
445                 }
446         }
447         g_slist_free(new_spams);
448         
449         if (status < 0 || status > 2) { /* I/O or other errors */
450                 gchar *msg = NULL;
451                 
452                 if (status == 3)
453                         msg =  g_strdup_printf(_("The Bogofilter plugin couldn't filter "
454                                            "a message. The probable cause of the "
455                                            "error is that it didn't learn from any mail.\n"
456                                            "Use \"/Mark/Mark as spam\" and \"/Mark/Mark as "
457                                            "ham\" to train Bogofilter with a few hundred "
458                                            "spam and ham messages."));
459                 else
460                         msg =  g_strdup_printf(_("The Bogofilter plugin couldn't filter "
461                                            "a message. the command `%s %s %s` couldn't be run."), 
462                                            bogo_args[0], bogo_args[1], bogo_args[2]);
463                 if (!prefs_common.no_recv_err_panel) {
464                         if (!warned_error) {
465                                 alertpanel_error(msg);
466                         }
467                         warned_error = TRUE;
468                 } else {
469                         gchar *tmp = g_strdup_printf("%s\n", msg);
470                         log_error(tmp);
471                         g_free(tmp);
472                 }
473                 g_free(msg);
474         }
475         if (status < 0 || status > 2) {
476                 g_slist_free(mail_filtering_data->filtered);
477                 g_slist_free(mail_filtering_data->unfiltered);
478                 g_slist_free(spams_to_receive);
479                 mail_filtering_data->filtered = NULL;
480                 mail_filtering_data->unfiltered = NULL;
481         } else if (config.receive_spam && spams_to_receive) {
482                 FolderItem *save_folder;
483
484                 if ((!config.save_folder) ||
485                     (config.save_folder[0] == '\0') ||
486                     ((save_folder = folder_find_item_from_identifier(config.save_folder)) == NULL))
487                         save_folder = folder_get_default_trash();
488                 if (save_folder) {
489                         for (cur = spams_to_receive; cur; cur = cur->next) {
490                                 msginfo = (MsgInfo *)cur->data;
491                                 msginfo->is_move = TRUE;
492                                 msginfo->to_filter_folder = save_folder;
493                         }
494                 }
495         } 
496
497         if (message_callback != NULL)
498                 message_callback(NULL, 0, 0, FALSE);
499         mail_filtering_data->filtered = g_slist_reverse(
500                 mail_filtering_data->filtered);
501         mail_filtering_data->unfiltered = g_slist_reverse(
502                 mail_filtering_data->unfiltered);
503         
504         return FALSE;
505 }
506
507 BogofilterConfig *bogofilter_get_config(void)
508 {
509         return &config;
510 }
511
512 int bogofilter_learn(MsgInfo *msginfo, GSList *msglist, gboolean spam)
513 {
514         gchar *cmd = NULL;
515         gchar *file = NULL;
516         const gchar *bogo_exec = (config.bogopath && *config.bogopath) ? config.bogopath:"bogofilter";
517         gint status = 0;
518         if (msginfo == NULL && msglist == NULL) {
519                 return -1;
520         }
521
522         if (msginfo) {
523                 file = procmsg_get_message_file(msginfo);
524                 if (file == NULL) {
525                         return -1;
526                 } else {
527                         if (message_callback != NULL)
528                                 message_callback(_("Bogofilter: learning from message..."), 0, 0, FALSE);
529                         if (spam)
530                                 /* learn as spam */
531                                 cmd = g_strdup_printf("%s -s -I '%s'", bogo_exec, file);
532                         else if (MSG_IS_SPAM(msginfo->flags))
533                                 /* correct bogofilter, this wasn't spam */
534                                 cmd = g_strdup_printf("%s -Sn -I '%s'", bogo_exec, file);
535                         else 
536                                 /* learn as ham */
537                                 cmd = g_strdup_printf("%s -n -I '%s'", bogo_exec, file);
538                         if ((status = execute_command_line(cmd, FALSE)) != 0)
539                                 log_error(_("Learning failed; `%s` returned with status %d."),
540                                                 cmd, status);
541                         g_free(cmd);
542                         g_free(file);
543                         if (message_callback != NULL)
544                                 message_callback(NULL, 0, 0, FALSE);
545                         return 0;
546                 }
547         }
548         if (msglist) {
549                 GSList *cur = msglist;
550                 MsgInfo *info;
551                 int total = g_slist_length(msglist);
552                 int done = 0;
553                 gboolean some_correction = FALSE, some_no_correction = FALSE;
554         
555                 if (message_callback != NULL)
556                         message_callback(_("Bogofilter: learning from messages..."), total, 0, FALSE);
557                 
558                 for (cur = msglist; cur && status == 0; cur = cur->next) {
559                         info = (MsgInfo *)cur->data;
560                         if (spam)
561                                 some_no_correction = TRUE;
562                         else if (MSG_IS_SPAM(info->flags))
563                                 /* correct bogofilter, this wasn't spam */
564                                 some_correction = TRUE;
565                         else 
566                                 some_no_correction = TRUE;
567                         
568                 }
569                 
570                 if (some_correction && some_no_correction) {
571                         /* we potentially have to do different stuff for every mail */
572                         for (cur = msglist; cur && status == 0; cur = cur->next) {
573                                 info = (MsgInfo *)cur->data;
574                                 file = procmsg_get_message_file(info);
575
576                                 if (spam)
577                                         /* learn as spam */
578                                         cmd = g_strdup_printf("%s -s -I '%s'", bogo_exec, file);
579                                 else if (MSG_IS_SPAM(info->flags))
580                                         /* correct bogofilter, this wasn't spam */
581                                         cmd = g_strdup_printf("%s -Sn -I '%s'", bogo_exec, file);
582                                 else 
583                                         /* learn as ham */
584                                         cmd = g_strdup_printf("%s -n -I '%s'", bogo_exec, file);
585
586                                 if ((status = execute_command_line(cmd, FALSE)) != 0)
587                                         log_error(_("Learning failed; `%s` returned with status %d."),
588                                                         cmd, status);
589
590                                 g_free(cmd);
591                                 g_free(file);
592                                 done++;
593                                 if (message_callback != NULL)
594                                         message_callback(NULL, total, done, FALSE);
595                         }
596                 } else if (some_correction || some_no_correction) {
597                         cur = msglist;
598                         
599                         gchar *bogo_args[4];
600                         GPid bogo_pid;
601                         gint bogo_stdin;
602                         GError *error = NULL;
603                         gboolean bogo_forked;
604
605                         bogo_args[0] = (gchar *)bogo_exec;
606                         if (some_correction && !some_no_correction)
607                                 bogo_args[1] = "-Sn";
608                         else if (some_no_correction && !some_correction)
609                                 bogo_args[1] = spam ? "-s":"-n";
610                         bogo_args[2] = "-b";
611                         bogo_args[3] = NULL;
612
613                         bogo_forked = g_spawn_async_with_pipes(
614                                         NULL, bogo_args,NULL, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
615                                         NULL, NULL, &bogo_pid, &bogo_stdin,
616                                         NULL, NULL, &error);
617
618                         while (bogo_forked && cur) {
619                                 gchar *tmp = NULL;
620                                 info = (MsgInfo *)cur->data;
621                                 file = procmsg_get_message_file(info);
622                                 if (file) {
623                                         tmp = g_strdup_printf("%s\n", 
624                                                 file);
625                                         write_all(bogo_stdin, tmp, strlen(tmp));
626                                         g_free(tmp);
627                                 }
628                                 g_free(file);
629                                 done++;
630                                 if (message_callback != NULL)
631                                         message_callback(NULL, total, done, FALSE);
632                                 cur = cur->next;
633                         }
634                         if (bogo_forked) {
635                                 close(bogo_stdin);
636                                 waitpid(bogo_pid, &status, 0);
637                                 if (!WIFEXITED(status))
638                                         status = -1;
639                                 else
640                                         status = WEXITSTATUS(status);
641                         }
642                         if (!bogo_forked || status != 0) {
643                                 log_error(_("Learning failed; `%s %s %s` returned with error:\n%s"),
644                                                 bogo_args[0], bogo_args[1], bogo_args[2], 
645                                                 error ? error->message:_("Unknown error"));
646                                 if (error)
647                                         g_error_free(error);
648                         }
649
650                 }
651
652                 if (message_callback != NULL)
653                         message_callback(NULL, 0, 0, FALSE);
654                 return 0;
655         }
656         return -1;
657 }
658
659 void bogofilter_save_config(void)
660 {
661         PrefFile *pfile;
662         gchar *rcpath;
663
664         debug_print("Saving Bogofilter Page\n");
665
666         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
667         pfile = prefs_write_open(rcpath);
668         g_free(rcpath);
669         if (!pfile || (prefs_set_block_label(pfile, "Bogofilter") < 0))
670                 return;
671
672         if (prefs_write_param(param, pfile->fp) < 0) {
673                 g_warning("Failed to write Bogofilter configuration to file\n");
674                 prefs_file_close_revert(pfile);
675                 return;
676         }
677         fprintf(pfile->fp, "\n");
678
679         prefs_file_close(pfile);
680 }
681
682 void bogofilter_set_message_callback(MessageCallback callback)
683 {
684         message_callback = callback;
685 }
686
687 gint plugin_init(gchar **error)
688 {
689         gchar *rcpath;
690
691         hook_id = -1;
692
693         if ((claws_get_version() > VERSION_NUMERIC)) {
694                 *error = g_strdup(_("Your version of Claws Mail is newer than the version the Bogofilter plugin was built with"));
695                 return -1;
696         }
697
698         if ((claws_get_version() < MAKE_NUMERIC_VERSION(0, 9, 3, 86))) {
699                 *error = g_strdup(_("Your version of Claws Mail is too old for the Bogofilter plugin"));
700                 return -1;
701         }
702
703         prefs_set_default(param);
704         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
705         prefs_read_config(param, "Bogofilter", rcpath, NULL);
706         g_free(rcpath);
707
708         bogofilter_gtk_init();
709                 
710         debug_print("Bogofilter plugin loaded\n");
711
712 #ifdef USE_PTHREAD
713         bogofilter_start_thread();
714 #endif
715
716         if (config.process_emails) {
717                 bogofilter_register_hook();
718         }
719
720         procmsg_register_spam_learner(bogofilter_learn);
721         procmsg_spam_set_folder(config.save_folder);
722
723         return 0;
724         
725 }
726
727 void plugin_done(void)
728 {
729         if (hook_id != -1) {
730                 bogofilter_unregister_hook();
731         }
732 #ifdef USE_PTHREAD
733         bogofilter_stop_thread();
734 #endif
735         g_free(config.save_folder);
736         bogofilter_gtk_done();
737         procmsg_unregister_spam_learner(bogofilter_learn);
738         procmsg_spam_set_folder(NULL);
739         debug_print("Bogofilter plugin unloaded\n");
740 }
741
742 const gchar *plugin_name(void)
743 {
744         return _("Bogofilter");
745 }
746
747 const gchar *plugin_desc(void)
748 {
749         return _("This plugin can check all messages that are received from an "
750                  "IMAP, LOCAL or POP account for spam using Bogofilter. "
751                  "You will need Bogofilter installed locally.\n "
752                  "\n"
753                  "Before Bogofilter can recognize spam messages, you have to "
754                  "train it by marking a few hundred spam and ham messages. "
755                  "Use \"/Mark/Mark as spam\" and \"/Mark/Mark as ham\" to "
756                  "train Bogofilter.\n"
757                  "\n"
758                  "When a message is identified as spam it can be deleted or "
759                  "saved in a specially designated folder.\n"
760                  "\n"
761                  "Options can be found in /Configuration/Preferences/Plugins/Bogofilter");
762 }
763
764 const gchar *plugin_type(void)
765 {
766         return "GTK2";
767 }
768
769 const gchar *plugin_licence(void)
770 {
771         return "GPL";
772 }
773
774 const gchar *plugin_version(void)
775 {
776         return VERSION;
777 }
778
779 struct PluginFeature *plugin_provides(void)
780 {
781         static struct PluginFeature features[] = 
782                 { {PLUGIN_FILTERING, N_("Spam detection")},
783                   {PLUGIN_FILTERING, N_("Spam learning")},
784                   {PLUGIN_NOTHING, NULL}};
785         return features;
786 }
787
788 void bogofilter_register_hook(void)
789 {
790         if (hook_id == -1)
791                 hook_id = hooks_register_hook(MAIL_LISTFILTERING_HOOKLIST, mail_filtering_hook, NULL);
792         if (hook_id == -1) {
793                 g_warning("Failed to register mail filtering hook");
794                 config.process_emails = FALSE;
795         }
796 }
797
798 void bogofilter_unregister_hook(void)
799 {
800         if (hook_id != -1) {
801                 hooks_unregister_hook(MAIL_LISTFILTERING_HOOKLIST, hook_id);
802         }
803         hook_id = -1;
804 }