2011-01-21 [colin] 3.7.8cvs39
[claws.git] / src / plugins / spamassassin / spamassassin.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2009 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 3 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, see <http://www.gnu.org/licenses/>.
17  * 
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
29 #include <glib.h>
30 #include <glib/gi18n.h>
31
32 #if HAVE_LOCALE_H
33 #  include <locale.h>
34 #endif
35
36 #include "common/claws.h"
37 #include "common/version.h"
38 #include "plugin.h"
39 #include "common/utils.h"
40 #include "hooks.h"
41 #include "procmsg.h"
42 #include "folder.h"
43 #include "prefs.h"
44 #include "prefs_gtk.h"
45
46 #include "libspamc.h"
47 #include "spamassassin.h"
48 #include "inc.h"
49 #include "log.h"
50 #include "prefs_common.h"
51 #include "alertpanel.h"
52 #include "addr_compl.h"
53
54 #ifdef HAVE_SYSEXITS_H
55 #include <sysexits.h>
56 #endif
57 #ifdef HAVE_ERRNO_H
58 #include <errno.h>
59 #endif
60 #ifdef HAVE_SYS_ERRNO_H
61 #include <sys/errno.h>
62 #endif
63 #ifdef HAVE_TIME_H
64 #include <time.h>
65 #endif
66 #ifdef HAVE_SYS_TIME_H
67 #include <sys/time.h>
68 #endif
69 #ifdef HAVE_SIGNAL_H
70 #include <signal.h>
71 #endif
72 #ifdef HAVE_PWD_H
73 #include <pwd.h>
74 #endif
75
76 #define PLUGIN_NAME (_("SpamAssassin"))
77
78 enum {
79     CHILD_RUNNING = 1 << 0,
80     TIMEOUT_RUNNING = 1 << 1,
81 };
82
83 static guint hook_id = -1;
84 static int flags = SPAMC_RAW_MODE | SPAMC_SAFE_FALLBACK | SPAMC_CHECK_ONLY;
85 static MessageCallback message_callback;
86
87 static SpamAssassinConfig config;
88
89 static PrefParam param[] = {
90         {"enable", "FALSE", &config.enable, P_BOOL,
91         NULL, NULL, NULL},
92         {"transport", "0", &config.transport, P_INT,
93          NULL, NULL, NULL},
94         {"hostname", "localhost", &config.hostname, P_STRING,
95          NULL, NULL, NULL},
96         {"port", "783", &config.port, P_INT,
97          NULL, NULL, NULL},
98         {"socket", "", &config.socket, P_STRING,
99          NULL, NULL, NULL},
100         {"process_emails", "TRUE", &config.process_emails, P_BOOL,
101          NULL, NULL, NULL},
102         {"receive_spam", "TRUE", &config.receive_spam, P_BOOL,
103          NULL, NULL, NULL},
104         {"save_folder", NULL, &config.save_folder, P_STRING,
105          NULL, NULL, NULL},
106         {"max_size", "250", &config.max_size, P_INT,
107          NULL, NULL, NULL},
108         {"timeout", "30", &config.timeout, P_INT,
109          NULL, NULL, NULL},
110         {"username", "", &config.username, P_STRING,
111          NULL, NULL, NULL},
112         {"mark_as_read", "TRUE", &config.mark_as_read, P_BOOL,
113          NULL, NULL, NULL},
114         {"whitelist_ab", "FALSE", &config.whitelist_ab, P_BOOL,
115          NULL, NULL, NULL},
116         {"whitelist_ab_folder", N_("Any"), &config.whitelist_ab_folder, P_STRING,
117          NULL, NULL, NULL},
118
119         {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
120 };
121
122 gboolean timeout_func(gpointer data)
123 {
124         gint *running = (gint *) data;
125
126         if (*running & CHILD_RUNNING)
127                 return TRUE;
128
129         *running &= ~TIMEOUT_RUNNING;
130         return FALSE;
131 }
132
133 typedef enum {
134         MSG_IS_HAM = 0,
135         MSG_IS_SPAM = 1,
136         MSG_FILTERING_ERROR = 2
137 } MsgStatus;
138
139 static MsgStatus msg_is_spam(FILE *fp)
140 {
141         struct transport trans;
142         struct message m;
143         gboolean is_spam = FALSE;
144
145         if (!config.enable)
146                 return MSG_IS_HAM;
147
148         transport_init(&trans);
149         switch (config.transport) {
150         case SPAMASSASSIN_TRANSPORT_LOCALHOST:
151                 trans.type = TRANSPORT_LOCALHOST;
152                 trans.port = config.port;
153                 break;
154         case SPAMASSASSIN_TRANSPORT_TCP:
155                 trans.type = TRANSPORT_TCP;
156                 trans.hostname = config.hostname;
157                 trans.port = config.port;
158                 break;
159         case SPAMASSASSIN_TRANSPORT_UNIX:
160                 trans.type = TRANSPORT_UNIX;
161                 trans.socketpath = config.socket;
162                 break;
163         default:
164                 return MSG_IS_HAM;
165         }
166
167         if (transport_setup(&trans, flags) != EX_OK) {
168                 log_error(LOG_PROTOCOL, _("SpamAssassin plugin couldn't connect to spamd.\n"));
169                 debug_print("failed to setup transport\n");
170                 return MSG_FILTERING_ERROR;
171         }
172
173         m.type = MESSAGE_NONE;
174         m.max_len = config.max_size * 1024;
175         m.timeout = config.timeout;
176
177         if (message_read(fileno(fp), flags, &m) != EX_OK) {
178                 debug_print("failed to read message\n");
179                 message_cleanup(&m);
180                 return MSG_FILTERING_ERROR;
181         }
182
183         if (message_filter(&trans, config.username, flags, &m) != EX_OK) {
184                 log_error(LOG_PROTOCOL, _("SpamAssassin plugin filtering failed.\n"));
185                 debug_print("filtering the message failed\n");
186                 message_cleanup(&m);
187                 return MSG_FILTERING_ERROR;
188         }
189
190         if (m.is_spam == EX_ISSPAM)
191                 is_spam = TRUE;
192
193         message_cleanup(&m);
194
195         return is_spam ? MSG_IS_SPAM:MSG_IS_HAM;
196 }
197
198 static gboolean sa_found_in_addressbook(const gchar *address)
199 {
200         gchar *addr = NULL;
201         gboolean found = FALSE;
202         gint num_addr = 0;
203         
204         if (!address)
205                 return FALSE;
206         
207         addr = g_strdup(address);
208         extract_address(addr);
209         num_addr = complete_address(addr);
210         if (num_addr > 1) {
211                 /* skip first item (this is the search string itself) */
212                 int i = 1;
213                 for (; i < num_addr && !found; i++) {
214                         gchar *caddr = get_complete_address(i);
215                         extract_address(caddr);
216                         if (strcasecmp(caddr, addr) == 0)
217                                 found = TRUE;
218                         g_free(caddr);
219                 }
220         }
221         g_free(addr);
222         return found;
223 }
224
225 static gboolean mail_filtering_hook(gpointer source, gpointer data)
226 {
227         MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
228         MsgInfo *msginfo = mail_filtering_data->msginfo;
229         gboolean is_spam = FALSE, error = FALSE;
230         static gboolean warned_error = FALSE;
231         FILE *fp = NULL;
232         int pid = 0;
233         int status;
234
235         /* SPAMASSASSIN_DISABLED : keep test for compatibility purpose */
236         if (!config.enable || config.transport == SPAMASSASSIN_DISABLED) {
237                 log_warning(LOG_PROTOCOL, _("SpamAssassin plugin is disabled by its preferences.\n"));
238                 return FALSE;
239         }
240         debug_print("Filtering message %d\n", msginfo->msgnum);
241         if (message_callback != NULL)
242                 message_callback(_("SpamAssassin: filtering message..."));
243
244         if ((fp = procmsg_open_message(msginfo)) == NULL) {
245                 debug_print("failed to open message file\n");
246                 return FALSE;
247         }
248
249         if (config.whitelist_ab) {
250                 gchar *ab_folderpath;
251                 gboolean whitelisted = FALSE;
252
253                 if (*config.whitelist_ab_folder == '\0' ||
254                         strcasecmp(config.whitelist_ab_folder, "Any") == 0) {
255                         /* match the whole addressbook */
256                         ab_folderpath = NULL;
257                 } else {
258                         /* match the specific book/folder of the addressbook */
259                         ab_folderpath = config.whitelist_ab_folder;
260                 }
261
262                 start_address_completion(ab_folderpath);
263                 if (msginfo->from && 
264                     sa_found_in_addressbook(msginfo->from))
265                                 whitelisted = TRUE;
266                 end_address_completion();
267                 
268                 if (whitelisted) {
269                         debug_print("message is ham (whitelisted)\n");
270                         fclose(fp);
271                         return FALSE;
272                 }
273         }
274         pid = fork();
275         if (pid == 0) {
276                 _exit(msg_is_spam(fp));
277         } else {
278                 gint running = 0;
279
280                 running |= CHILD_RUNNING;
281
282                 g_timeout_add(50, timeout_func, &running);
283                 running |= TIMEOUT_RUNNING;
284
285                 while(running & CHILD_RUNNING) {
286                         int ret;
287
288                         ret = waitpid(pid, &status, WNOHANG);
289                         if (ret == pid) {
290                                 if (WIFEXITED(status)) {
291                                         MsgStatus result = MSG_IS_HAM;
292                                         running &= ~CHILD_RUNNING;
293                                         result = WEXITSTATUS(status);
294                                         is_spam = (result == MSG_IS_SPAM) ? TRUE : FALSE;
295                                         error = (result == MSG_FILTERING_ERROR);
296                                 }
297                         } if (ret < 0) {
298                                 running &= ~CHILD_RUNNING;
299                         } /* ret == 0 continue */
300             
301                         g_main_context_iteration(NULL, TRUE);
302                 }
303
304                 while (running & TIMEOUT_RUNNING)
305                         g_main_context_iteration(NULL, TRUE);
306         }
307
308         fclose(fp);
309
310         if (is_spam) {
311                 debug_print("message is spam\n");
312                 procmsg_msginfo_set_flags(msginfo, MSG_SPAM, 0);
313                 if (config.receive_spam) {
314                         FolderItem *save_folder = NULL;
315
316                         if ((!config.save_folder) ||
317                             (config.save_folder[0] == '\0') ||
318                             ((save_folder = folder_find_item_from_identifier(config.save_folder)) == NULL)) {
319                                 if (mail_filtering_data->account && mail_filtering_data->account->set_trash_folder) {
320                                         save_folder = folder_find_item_from_identifier(
321                                                 mail_filtering_data->account->trash_folder);
322                                         if (save_folder)
323                                                 debug_print("found trash folder from account's advanced settings\n");
324                                 }
325                                 if (save_folder == NULL && mail_filtering_data->account &&
326                                     mail_filtering_data->account->folder) {
327                                         save_folder = mail_filtering_data->account->folder->trash;
328                                         if (save_folder)
329                                                 debug_print("found trash folder from account's trash\n");
330                                 }
331                                 if (save_folder == NULL && mail_filtering_data->account &&
332                                     !mail_filtering_data->account->folder)  {
333                                         if (mail_filtering_data->account->inbox) {
334                                                 FolderItem *item = folder_find_item_from_identifier(
335                                                         mail_filtering_data->account->inbox);
336                                                 if (item && item->folder->trash) {
337                                                         save_folder = item->folder->trash;
338                                                         debug_print("found trash folder from account's inbox\n");
339                                                 }
340                                         } 
341                                         if (!save_folder && mail_filtering_data->account->local_inbox) {
342                                                 FolderItem *item = folder_find_item_from_identifier(
343                                                         mail_filtering_data->account->local_inbox);
344                                                 if (item && item->folder->trash) {
345                                                         save_folder = item->folder->trash;
346                                                         debug_print("found trash folder from account's local_inbox\n");
347                                                 }
348                                         }
349                                 }
350                                 if (save_folder == NULL) {
351                                         debug_print("using default trash folder\n");
352                                         save_folder = folder_get_default_trash();
353                                 }
354                         }
355                         if (config.mark_as_read)
356                                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
357                         procmsg_msginfo_set_flags(msginfo, MSG_SPAM, 0);
358                         msginfo->filter_op = IS_MOVE;
359                         msginfo->to_filter_folder = save_folder;
360                 } else {
361                         folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
362                 }
363
364                 return TRUE;
365         } else {
366                 debug_print("message is ham\n");
367                 procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
368         }
369         
370         if (error) {
371                 gchar *msg = _("The SpamAssassin plugin couldn't filter "
372                                            "a message. The probable cause of the error "
373                                            "is an unreachable spamd daemon. Please make "
374                                            "sure spamd is running and accessible.");
375                 if (!prefs_common.no_recv_err_panel) {
376                         if (!warned_error) {
377                                 alertpanel_error("%s", msg);
378                         }
379                         warned_error = TRUE;
380                 } else {
381                         log_error(LOG_PROTOCOL, "%s\n", msg);
382                 }
383         }
384         
385         return FALSE;
386 }
387
388 SpamAssassinConfig *spamassassin_get_config(void)
389 {
390         return &config;
391 }
392
393 gchar* spamassassin_create_tmp_spamc_wrapper(gboolean spam)
394 {
395         gchar *contents;
396         gchar *fname = get_tmp_file();
397
398         if (fname != NULL) {
399                 contents = g_strdup_printf(
400                                                 "spamc -d %s -p %u -u %s -t %u -s %u -L %s<\"$*\";exit $?",
401                                                 config.hostname, config.port, 
402                                                 config.username, config.timeout,
403                                                 config.max_size * 1024, spam?"spam":"ham");
404                 if (str_write_to_file(contents, fname) < 0) {
405                         g_free(fname);
406                         fname = NULL;
407                 }
408                 g_free(contents);
409         }
410         /* returned pointer must be free'ed by caller */
411         return fname;
412 }
413
414 int spamassassin_learn(MsgInfo *msginfo, GSList *msglist, gboolean spam)
415 {
416         gchar *cmd = NULL;
417         gchar *file = NULL;
418         const gchar *shell = g_getenv("SHELL");
419         gchar *spamc_wrapper = NULL;
420
421         if (msginfo == NULL && msglist == NULL) {
422                 return -1;
423         }
424
425         if (config.transport == SPAMASSASSIN_TRANSPORT_TCP
426         &&  prefs_common.work_offline
427         &&  !inc_offline_should_override(TRUE,
428                 _("Claws Mail needs network access in order "
429                   "to feed this mail(s) to the remote learner."))) {
430                 return -1;
431         }
432
433         if (msginfo) {
434                 file = procmsg_get_message_file(msginfo);
435                 if (file == NULL) {
436                         return -1;
437                 }
438                 if (config.transport == SPAMASSASSIN_TRANSPORT_TCP) {
439                         spamc_wrapper = spamassassin_create_tmp_spamc_wrapper(spam);
440                         if (spamc_wrapper != NULL) {
441                                 cmd = g_strconcat(shell?shell:"sh", " ",
442                                                                 spamc_wrapper, " ", file, NULL);
443                         }
444                 } else {
445                         cmd = g_strdup_printf("sa-learn -u %s%s %s %s",
446                                                         config.username,
447                                                         prefs_common.work_offline?" -L":"",
448                                                         spam?"--spam":"--ham", file);
449                 }
450         }
451         if (msglist) {
452                 GSList *cur = msglist;
453                 MsgInfo *info;
454
455                 if (config.transport == SPAMASSASSIN_TRANSPORT_TCP) {
456                         /* execute n-times the spamc command */
457                         for (; cur; cur = cur->next) {
458                                 info = (MsgInfo *)cur->data;
459                                 gchar *tmpcmd = NULL;
460                                 gchar *tmpfile = get_tmp_file();
461
462                                 if (spamc_wrapper == NULL) {
463                                         spamc_wrapper = spamassassin_create_tmp_spamc_wrapper(spam);
464                                 }
465
466                                 if (spamc_wrapper && tmpfile &&
467                                 copy_file(procmsg_get_message_file(info), tmpfile, TRUE) == 0) {
468                                         tmpcmd = g_strconcat(shell?shell:"sh", " ", spamc_wrapper, " ",
469                                                                                 tmpfile, NULL);
470                                         debug_print("%s\n", tmpcmd);
471                                         execute_command_line(tmpcmd, FALSE);
472                                         g_free(tmpcmd);
473                                 }
474                                 g_free(tmpfile);
475                         }
476                         g_free(spamc_wrapper);
477                         return 0;
478                 } else {
479                         cmd = g_strdup_printf("sa-learn -u %s%s %s",
480                                         config.username,
481                                         prefs_common.work_offline?" -L":"",
482                                         spam?"--spam":"--ham");
483
484                         /* concatenate all message tmpfiles to the sa-learn command-line */
485                         for (; cur; cur = cur->next) {
486                                 info = (MsgInfo *)cur->data;
487                                 gchar *tmpcmd = NULL;
488                                 gchar *tmpfile = get_tmp_file();
489
490                                 if (tmpfile &&
491                                 copy_file(procmsg_get_message_file(info), tmpfile, TRUE) == 0) {                        
492                                         tmpcmd = g_strconcat(cmd, " ", tmpfile, NULL);
493                                         g_free(cmd);
494                                         cmd = tmpcmd;
495                                 }
496                                 g_free(tmpfile);
497                         }
498                 }
499         }
500         if (cmd == NULL) {
501                 return -1;
502         }
503         debug_print("%s\n", cmd);
504         /* only run sync calls to sa-learn/spamc to prevent system lockdown */
505         execute_command_line(cmd, FALSE);
506         g_free(cmd);
507         g_free(spamc_wrapper);
508
509         return 0;
510 }
511
512 void spamassassin_save_config(void)
513 {
514         PrefFile *pfile;
515         gchar *rcpath;
516
517         debug_print("Saving SpamAssassin Page\n");
518
519         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
520         pfile = prefs_write_open(rcpath);
521         g_free(rcpath);
522         if (!pfile || (prefs_set_block_label(pfile, "SpamAssassin") < 0))
523                 return;
524
525         if (prefs_write_param(param, pfile->fp) < 0) {
526                 g_warning("Failed to write SpamAssassin configuration to file\n");
527                 prefs_file_close_revert(pfile);
528                 return;
529         }
530         if (fprintf(pfile->fp, "\n") < 0) {
531                 FILE_OP_ERROR(rcpath, "fprintf");
532                 prefs_file_close_revert(pfile);
533         } else
534                 prefs_file_close(pfile);
535 }
536
537 gboolean spamassassin_check_username(void)
538 {
539         if (config.username == NULL || config.username[0] == '\0') {
540                 config.username = (gchar*)g_get_user_name();
541                 if (config.username == NULL) {
542                         if (hook_id != -1) {
543                                 spamassassin_unregister_hook();
544                         }
545                         procmsg_unregister_spam_learner(spamassassin_learn);
546                         procmsg_spam_set_folder(NULL, NULL);
547                         return FALSE;
548                 }
549         }
550         return TRUE;
551 }
552
553 void spamassassin_set_message_callback(MessageCallback callback)
554 {
555         message_callback = callback;
556 }
557
558 gint plugin_init(gchar **error)
559 {
560         gchar *rcpath;
561
562         hook_id = -1;
563
564         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
565                                 VERSION_NUMERIC, PLUGIN_NAME, error))
566                 return -1;
567
568         prefs_set_default(param);
569         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
570         prefs_read_config(param, "SpamAssassin", rcpath, NULL);
571         g_free(rcpath);
572         if (!spamassassin_check_username()) {
573                 *error = g_strdup(_("Failed to get username"));
574                 return -1;
575         }
576         spamassassin_gtk_init();
577                 
578         debug_print("SpamAssassin plugin loaded\n");
579
580         if (config.process_emails) {
581                 spamassassin_register_hook();
582         }
583
584         if (!config.enable || config.transport == SPAMASSASSIN_DISABLED) {
585                 log_warning(LOG_PROTOCOL, _("SpamAssassin plugin is loaded but disabled by its preferences.\n"));
586         }
587         else {
588                 if (config.transport == SPAMASSASSIN_TRANSPORT_TCP)
589                         debug_print("Enabling learner with a remote spamassassin server requires spamc/spamd 3.1.x\n");
590                 procmsg_register_spam_learner(spamassassin_learn);
591                 procmsg_spam_set_folder(config.save_folder, spamassassin_get_spam_folder);
592         }
593
594         return 0;
595         
596 }
597
598 gboolean plugin_done(void)
599 {
600         if (hook_id != -1) {
601                 spamassassin_unregister_hook();
602         }
603         g_free(config.hostname);
604         g_free(config.save_folder);
605         spamassassin_gtk_done();
606         procmsg_unregister_spam_learner(spamassassin_learn);
607         procmsg_spam_set_folder(NULL, NULL);
608         debug_print("SpamAssassin plugin unloaded\n");
609         return TRUE;
610 }
611
612 const gchar *plugin_name(void)
613 {
614         return PLUGIN_NAME;
615 }
616
617 const gchar *plugin_desc(void)
618 {
619         return _("This plugin can check all messages that are received from an "
620                  "IMAP, LOCAL or POP account for spam using a SpamAssassin "
621                  "server. You will need a SpamAssassin Server (spamd) running "
622                  "somewhere.\n"
623                  "\n"
624                  "It can also be used for marking messages as Ham or Spam.\n"
625                  "\n"
626                  "When a message is identified as spam it can be deleted or "
627                  "saved in a specially designated folder.\n"
628                  "\n"
629                  "Options can be found in /Configuration/Preferences/Plugins/SpamAssassin");
630 }
631
632 const gchar *plugin_type(void)
633 {
634         return "GTK2";
635 }
636
637 const gchar *plugin_licence(void)
638 {
639         return "GPL3+";
640 }
641
642 const gchar *plugin_version(void)
643 {
644         return VERSION;
645 }
646
647 struct PluginFeature *plugin_provides(void)
648 {
649         static struct PluginFeature features[] = 
650                 { {PLUGIN_FILTERING, N_("Spam detection")},
651                   {PLUGIN_FILTERING, N_("Spam learning")},
652                   {PLUGIN_NOTHING, NULL}};
653         return features;
654 }
655
656 void spamassassin_register_hook(void)
657 {
658         if (hook_id == -1)
659                 hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
660         if (hook_id == -1) {
661                 g_warning("Failed to register mail filtering hook");
662                 config.process_emails = FALSE;
663         }
664 }
665
666 void spamassassin_unregister_hook(void)
667 {
668         if (hook_id != -1) {
669                 hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
670         }
671         hook_id = -1;
672 }
673
674 FolderItem *spamassassin_get_spam_folder(MsgInfo *msginfo)
675 {
676         FolderItem *item = folder_find_item_from_identifier(config.save_folder);
677
678         if (item || msginfo == NULL || msginfo->folder == NULL)
679                 return item;
680
681         if (msginfo->folder->folder &&
682             msginfo->folder->folder->account && 
683             msginfo->folder->folder->account->set_trash_folder) {
684                 item = folder_find_item_from_identifier(
685                         msginfo->folder->folder->account->trash_folder);
686         }
687
688         if (item == NULL && 
689             msginfo->folder->folder &&
690             msginfo->folder->folder->trash)
691                 item = msginfo->folder->folder->trash;
692                 
693         if (item == NULL)
694                 item = folder_get_default_trash();
695                 
696         debug_print("SA spam dir: %s\n", folder_item_get_path(item));
697         return item;
698 }
699