diff options
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | defconfig | 4 | ||||
-rw-r--r-- | dspam-exec.c | 340 | ||||
-rw-r--r-- | plugin.c | 185 | ||||
-rw-r--r-- | plugin.h | 18 |
5 files changed, 201 insertions, 353 deletions
@@ -13,7 +13,7 @@ CFLAGS += -I$(DOVECOT)/src/imap/ # per-backend configuration ifeq ("$(BACKEND)", "dspam-exec") -CFLAGS += -DCONFIG_PLUGIN_WANT_SIGNATURE=1 +CFLAGS += -DBACKEND_WANT_SIGNATURE=1 # can take a while, check more often CFLAGS += -DCOPY_CHECK_INTERVAL=10 endif @@ -38,8 +38,11 @@ ALL = antispam all: verify_config $(ALL) +%.o: %.c .config + $(CC) -c $(CFLAGS) -o $@ $< + antispam: $(objs) - $(CC) $(CFLAGS) $(INCLUDES) $(objs) -o $@.so $(LDFLAGS) + $(CC) $(CFLAGS) $(objs) -o $@.so $(LDFLAGS) clean: rm -f *.so *.o *~ @@ -21,5 +21,5 @@ BACKEND=dspam-exec # enable debugging to syslog or stderr -DEBUG=stderr -#DEBUG=syslog +#DEBUG=stderr +DEBUG=syslog diff --git a/dspam-exec.c b/dspam-exec.c index 71560f9..b413209 100644 --- a/dspam-exec.c +++ b/dspam-exec.c @@ -1,5 +1,5 @@ /* - * dspam plugin for dovecot + * dspam backend for dovecot antispam plugin * * Copyright (C) 2004-2007 Johannes Berg <johannes@sipsolutions.net> * 2006 Frank Cusack @@ -16,52 +16,30 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * - * based on the original framework http://www.dovecot.org/patches/1.0/copy_plugin.c - * - * Please see http://johannes.sipsolutions.net/wiki/Projects/dovecot-dspam-integration - * for more information on this code. - * - * Install the plugin in the usual dovecot module location. */ -#include <unistd.h> -#include <sys/wait.h> #include <stdlib.h> -#include <stdio.h> -#include <errno.h> +#include <sys/wait.h> #include <fcntl.h> -#ifdef DEBUG -#include <syslog.h> -#endif #include "lib.h" -#include "mempool.h" -#include "str.h" -#include "strfuncs.h" -#include "commands.h" -#include "imap-search.h" -#include "lib-storage/mail-storage.h" -#include "mail-storage.h" -#include "client.h" -#define DEFAULT_SIGHDR "X-DSPAM-Signature" -#define DEFAULT_DSPAM "/usr/bin/dspam" -#define MAXSIGLEN 100 +#include "plugin.h" -static int call_dspam(const char *signature, int is_spam) +static const char *dspam_binary = "/usr/bin/dspam"; + +static bool call_dspam(pool_t pool, const char *signature, bool is_spam) { pid_t pid; - int s; - char class_arg[16 + 2]; - char sign_arg[MAXSIGLEN + 2]; + const char *class_arg; + const char *sign_arg; int pipes[2]; - s = snprintf(sign_arg, 101, "--signature=%s", signature); - if (s > MAXSIGLEN || s <= 0) - return -1; - - snprintf(class_arg, 17, "--class=%s", is_spam ? "spam" : "innocent"); + sign_arg = t_strconcat("--signature=", signature, NULL); + if (is_spam) + class_arg = t_strconcat("--class=", "spam", NULL); + else + class_arg = t_strconcat("--class=", "innocent", NULL); pipe(pipes); /* for dspam stderr */ @@ -129,298 +107,42 @@ static int call_dspam(const char *signature, int is_spam) } close(fd); -#ifdef DEBUG - syslog(LOG_INFO, DSPAM " --source=error --stdout %s %s", - class_arg, sign_arg); -#endif - execl(DSPAM, DSPAM, "--source=error", "--stdout", class_arg, + debug("antispam: %s --source=error --stdout %s %s", + dspam_binary, class_arg, sign_arg); + execl(dspam_binary, dspam_binary, + "--source=error", "--stdout", class_arg, sign_arg, NULL); exit(127); /* fall through if dspam can't be found */ return -1; /* never executed */ } } -struct dspam_signature_list { - struct dspam_signature_list *next; - char *sig; -}; -typedef struct dspam_signature_list *siglist_t; - -static siglist_t list_append(pool_t pool, siglist_t * list) +bool backend(pool_t pool, bool spam, struct strlist *sigs) { - siglist_t l = *list; - siglist_t p = NULL; - siglist_t n; - - while (l != NULL) { - p = l; - l = l->next; - } - n = p_malloc(pool, sizeof(struct dspam_signature_list)); - n->next = NULL; - n->sig = NULL; - if (p == NULL) { - *list = n; - } else { - p->next = n; - } - return n; -} - -static int -fetch_and_copy_reclassified(struct mailbox_transaction_context *t, - struct mailbox *srcbox, - struct mail_search_arg *search_args, - int is_spam, int *enh_error) -{ - struct mail_search_context *search_ctx; - struct mailbox_transaction_context *src_trans; - struct mail_keywords *keywords; - const char *const *keywords_list; - struct mail *mail; int ret; - const char *signature; - struct dspam_signature_list *siglist = NULL; - pool_t listpool = pool_alloconly_create("dspam-siglist-pool", 1024); - - *enh_error = 0; - - src_trans = mailbox_transaction_begin(srcbox, 0); - search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL); - - mail = mail_alloc(src_trans, MAIL_FETCH_STREAM_HEADER | - MAIL_FETCH_STREAM_BODY, NULL); - ret = 1; - while (mailbox_search_next(search_ctx, mail) > 0 && ret > 0) { - if (mail->expunged) { - ret = 0; - break; - } - - signature = mail_get_first_header(mail, SIGHEADERLINE); - if (is_empty_str(signature)) { - ret = -1; - *enh_error = -2; - break; - } - list_append(listpool, &siglist)->sig = - p_strdup(listpool, signature); - - keywords_list = mail_get_keywords(mail); - keywords = strarray_length(keywords_list) == 0 ? NULL : - mailbox_keywords_create(t, keywords_list); - if (mailbox_copy(t, mail, mail_get_flags(mail), - keywords, NULL) < 0) - ret = -1; - mailbox_keywords_free(t, &keywords); - } - mail_free(&mail); - - if (mailbox_search_deinit(&search_ctx) < 0) - ret = -1; - /* got all signatures now, walk them passing to dspam */ - while (siglist) { - if ((*enh_error = call_dspam(siglist->sig, is_spam))) { - ret = -1; - break; - } - siglist = siglist->next; - } - - pool_unref(listpool); - - if (*enh_error) { - mailbox_transaction_rollback(&src_trans); - } else { - if (mailbox_transaction_commit(&src_trans, 0) < 0) - ret = -1; - } - - return ret; -} - -static bool cmd_append_spam_plugin(struct client_command_context *cmd) -{ - const char *mailbox; - struct mail_storage *storage; - struct mailbox *box; - - /* <mailbox> */ - if (!client_read_string_args(cmd, 1, &mailbox)) - return FALSE; - - storage = client_find_storage(cmd, &mailbox); - if (storage == NULL) - return FALSE; - /* TODO: is this really the best way to handle this? maybe more logic could be provided */ - box = - mailbox_open(storage, mailbox, NULL, - MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT); - if (box != NULL) { - - if (mailbox_equals(box, storage, "SPAM")) { - mailbox_close(&box); - return cmd_sync(cmd, 0, 0, - "NO Cannot APPEND to SPAM box, sorry."); - } - - mailbox_close(&box); + while (sigs) { + ret = call_dspam(pool, sigs->str, spam); + if (ret) + return FALSE; + sigs = sigs->next; } - return cmd_append(cmd); + return TRUE; } -static bool cmd_copy_spam_plugin(struct client_command_context *cmd) +void backend_init(pool_t pool) { - struct client *client = cmd->client; - struct mail_storage *storage; - struct mailbox *destbox; - struct mailbox_transaction_context *t; - struct mail_search_arg *search_arg; - const char *messageset, *mailbox; - enum mailbox_sync_flags sync_flags = 0; - int ret; - int spam_folder = 0; - int enh_error = 0, is_spam; -#ifdef IGNORE_TRASH_NAME - int is_trash; - int trash_folder = 0; -#endif - struct mailbox *box; - - /* <message set> <mailbox> */ - if (!client_read_string_args(cmd, 2, &messageset, &mailbox)) - return FALSE; - - if (!client_verify_open_mailbox(cmd)) - return TRUE; - - storage = client_find_storage(cmd, &mailbox); - if (storage == NULL) - return FALSE; - box = - mailbox_open(storage, mailbox, NULL, - MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT); - if (!box) { - client_send_storage_error(cmd, storage); - return TRUE; - } - - is_spam = mailbox_equals(box, storage, "SPAM"); - spam_folder = is_spam - || mailbox_equals(cmd->client->mailbox, storage, "SPAM"); -#ifdef IGNORE_TRASH_NAME - is_trash = mailbox_equals(box, storage, IGNORE_TRASH_NAME); - trash_folder = is_trash - || mailbox_equals(cmd->client->mailbox, storage, IGNORE_TRASH_NAME); -#endif - - mailbox_close(&box); - - /* only act on spam */ - if (!spam_folder) - return cmd_copy(cmd); -#ifdef IGNORE_TRASH_NAME - /* - * Ignore any mail going into or out of trash; - * this means users can circumvent re-classification - * by moving into trash and then out again... - * All in all, it may be a better idea to not use - * a Trash folder at all :) - */ - if (trash_folder) - return cmd_copy(cmd); -#endif - - /* otherwise, do (almost) everything the copy would have done */ - /* open the destination mailbox */ - if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE)) - return TRUE; + char *tmp; - search_arg = imap_search_get_arg(cmd, messageset, cmd->uid); - if (search_arg == NULL) - return TRUE; - - storage = client_find_storage(cmd, &mailbox); - if (storage == NULL) - return TRUE; - - if (mailbox_equals(client->mailbox, storage, mailbox)) - destbox = client->mailbox; - else { - destbox = mailbox_open(storage, mailbox, NULL, - MAILBOX_OPEN_FAST | - MAILBOX_OPEN_KEEP_RECENT); - if (destbox == NULL) { - client_send_storage_error(cmd, storage); - return TRUE; - } + tmp = getenv("ANTISPAM_DSPAM_BINARY"); + if (tmp) { + dspam_binary = tmp; + debug("dspam binary set to %s\n", tmp); } - - t = mailbox_transaction_begin(destbox, - MAILBOX_TRANSACTION_FLAG_EXTERNAL); - ret = - fetch_and_copy_reclassified(t, client->mailbox, search_arg, is_spam, - &enh_error); - - if (ret <= 0) - mailbox_transaction_rollback(&t); - else { - if (mailbox_transaction_commit(&t, 0) < 0) - ret = -1; - } - - if (destbox != client->mailbox) { - sync_flags |= MAILBOX_SYNC_FLAG_FAST; - mailbox_close(&destbox); - } - - if (ret > 0) - return cmd_sync(cmd, sync_flags, 0, "OK Copy completed."); - else if (ret == 0) { - /* some messages were expunged, sync them */ - return cmd_sync(cmd, 0, 0, - "NO Some of the requested messages no longer exist."); - } else { - switch (enh_error) { - case -2: - return cmd_sync(cmd, 0, 0, - "NO Some messages did not have " - SIGHEADERLINE " header line"); - break; - case -3: - return cmd_sync(cmd, 0, 0, "NO Failed to call dspam"); - break; - case 0: - client_send_storage_error(cmd, storage); - return TRUE; - break; - default: - return cmd_sync(cmd, 0, 0, "NO dspam failed"); - break; - } - } - - return TRUE; -} - -void dspam_init(void) -{ - command_unregister("COPY"); - command_unregister("APPEND"); - command_unregister("UID COPY"); - /* - * i_strdup() here is a kludge to avoid crashing in commands_deinit() - * since modules are unloaded before it's called, this "COPY" string - * would otherwise point to nonexisting memory. - */ - command_register(i_strdup("COPY"), cmd_copy_spam_plugin); - command_register(i_strdup("UID COPY"), cmd_copy_spam_plugin); - command_register(i_strdup("APPEND"), cmd_append_spam_plugin); } -void dspam_deinit(void) +void backend_exit(void) { } @@ -25,43 +25,53 @@ * Install the plugin in the usual dovecot module location. */ -#include <unistd.h> -#include <sys/wait.h> #include <stdlib.h> -#include <stdio.h> -#include <errno.h> -#include <fcntl.h> -#include <time.h> -#ifdef DEBUG -#include <syslog.h> -#endif /* dovecot headers we need */ #include "lib.h" -#include "mempool.h" -#include "str.h" -#include "strfuncs.h" -#include "commands.h" -#include "imap-search.h" -#include "lib-storage/mail-storage.h" -#include "mail-storage.h" #include "client.h" #include "ostream.h" +#include "imap-search.h" /* internal stuff we need */ #include "plugin.h" -static struct signature *list_add(pool_t pool, struct signature *list) +static pool_t global_pool; +static char **trash_folders = NULL; +static char *default_spam_folders[] = { + "SPAM", + NULL +}; +static char **spam_folders = default_spam_folders; +#ifdef BACKEND_WANT_SIGNATURE +static char *signature_hdr = "X-DSPAM-Signature"; +#endif + +static struct strlist *list_add(pool_t pool, struct strlist *list) { - struct signature *n; + struct strlist *n; - n = p_malloc(pool, sizeof(struct signature)); + n = p_malloc(pool, sizeof(struct strlist)); n->next = list; - n->sig = NULL; + n->str = NULL; return n; } +static bool mailbox_in_list(struct mail_storage *storage, struct mailbox *box, + char **list) +{ + if (!list) + return FALSE; + + while (*list) { + if (mailbox_equals(box, storage, *list)) + return TRUE; + list++; + } + + return FALSE; +} static void client_send_sendalive_if_needed(struct client *client) { @@ -79,9 +89,16 @@ static void client_send_sendalive_if_needed(struct client *client) } } +#define GENERIC_ERROR -1 +#define SIGNATURE_MISSING -2 +#define BACKEND_FAILURE -3 + +/* mostly copied from cmd-copy.c (notes added where changed) */ +/* MODIFIED: prototype to include "src_spam" */ static int fetch_and_copy(struct client *client, struct mailbox_transaction_context *t, - struct mail_search_arg *search_args) + struct mail_search_arg *search_args, + bool src_spam) { struct mail_search_context *search_ctx; struct mailbox_transaction_context *src_trans; @@ -90,6 +107,14 @@ static int fetch_and_copy(struct client *client, struct mail *mail; unsigned int copy_count = 0; int ret; + /* MODIFIED: new variables */ + pool_t pool = pool_alloconly_create("antispam-copy-pool", 1024); +#ifdef BACKEND_WANT_SIGNATURE + const char *signature; + struct strlist *siglist = NULL; +#else +#error Not implemented +#endif src_trans = mailbox_transaction_begin(client->mailbox, 0); search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL); @@ -106,6 +131,19 @@ static int fetch_and_copy(struct client *client, if ((++copy_count % COPY_CHECK_INTERVAL) == 0) client_send_sendalive_if_needed(client); + /* MODIFIED: keep track of mail as we copy */ +#ifdef BACKEND_WANT_SIGNATURE + signature = mail_get_first_header(mail, signature_hdr); + if (is_empty_str(signature)) { + ret = SIGNATURE_MISSING; + break; + } + siglist = list_add(pool, siglist); + siglist->str = p_strdup(pool, signature); +#else +#error Not implemented +#endif + keywords_list = mail_get_keywords(mail); keywords = strarray_length(keywords_list) == 0 ? NULL : mailbox_keywords_create(t, keywords_list); @@ -122,10 +160,25 @@ static int fetch_and_copy(struct client *client, if (mailbox_transaction_commit(&src_trans, 0) < 0) ret = -1; + /* MODIFIED: pass to backend */ +#ifdef BACKEND_WANT_SIGNATURE + /* got all signatures now, pass them to backend if no errors */ + if (ret == 0) { + ret = backend(pool, src_spam, siglist); + if (ret) + ret = BACKEND_FAILURE; + } +#else +#error Not implemented +#endif + /* MODIFIED: kill pool */ + pool_unref(pool); + return ret; } +/* mostly copied from cmd-copy.c (notes added where changed) */ static bool cmd_copy_antispam(struct client_command_context *cmd) { struct client *client = cmd->client; @@ -136,6 +189,8 @@ static bool cmd_copy_antispam(struct client_command_context *cmd) const char *messageset, *mailbox; enum mailbox_sync_flags sync_flags = 0; int ret; + /* MODIFIED: added variables */ + bool dst_spam, src_spam; /* <message set> <mailbox> */ if (!client_read_string_args(cmd, 2, &messageset, &mailbox)) @@ -156,9 +211,11 @@ static bool cmd_copy_antispam(struct client_command_context *cmd) if (storage == NULL) return TRUE; - if (mailbox_equals(client->mailbox, storage, mailbox)) + if (mailbox_equals(client->mailbox, storage, mailbox)) { destbox = client->mailbox; - else { + /* MODIFIED: don't try to reclassify on copy within folder */ + return cmd_copy(cmd); + } else { destbox = mailbox_open(storage, mailbox, NULL, MAILBOX_OPEN_SAVEONLY | MAILBOX_OPEN_FAST | @@ -169,9 +226,29 @@ static bool cmd_copy_antispam(struct client_command_context *cmd) } } + /* MODIFIED: Trash detection */ + if (mailbox_in_list(storage, client->mailbox, trash_folders) || + mailbox_in_list(storage, destbox, trash_folders)) { + mailbox_close(&destbox); + return cmd_copy(cmd); + } + + /* MODIFIED: from/to-SPAM detection */ + src_spam = mailbox_in_list(storage, client->mailbox, spam_folders); + dst_spam = mailbox_in_list(storage, destbox, spam_folders); + /* + * "both spam" can happen with multiple spam folders, + * "none spam" is the common case where spam folders are not involved + */ + if ((src_spam && dst_spam) || + (!src_spam && !dst_spam)) { + mailbox_close(&destbox); + return cmd_copy(cmd); + } + t = mailbox_transaction_begin(destbox, MAILBOX_TRANSACTION_FLAG_EXTERNAL); - ret = fetch_and_copy(client, t, search_arg); + ret = fetch_and_copy(client, t, search_arg, src_spam); if (ret <= 0) mailbox_transaction_rollback(&t); @@ -209,17 +286,16 @@ static bool cmd_append_antispam(struct client_command_context *cmd) return FALSE; storage = client_find_storage(cmd, &mailbox); - if (storage == NULL) + if (!storage) return FALSE; - box = - mailbox_open(storage, mailbox, NULL, - MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT); - if (box != NULL) { - if (mailbox_equals(box, storage, "SPAM")) { + box = mailbox_open(storage, mailbox, NULL, + MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT); + if (box) { + if (mailbox_in_list(storage, box, spam_folders)) { mailbox_close(&box); return cmd_sync(cmd, 0, 0, - "NO Cannot APPEND to SPAM box, sorry."); + "NO Cannot APPEND to spam folder."); } mailbox_close(&box); @@ -229,8 +305,49 @@ static bool cmd_append_antispam(struct client_command_context *cmd) } -void dspam_init(void) +void antispam_init(void) { + char *tmp, **iter; + + debug("antispam plugin intialising\n"); + + global_pool = pool_alloconly_create("antispam-pool", 1024); + + tmp = getenv("ANTISPAM_TRASH"); + if (tmp) + trash_folders = p_strsplit(global_pool, tmp, ";"); + + if (trash_folders) { + iter = trash_folders; + while (*iter) { + debug("antispam: \"%s\" is trash folder\n", *iter); + iter++; + } + } else + debug("antispam: no trash folders\n"); + + tmp = getenv("ANTISPAM_SPAM"); + if (tmp) + spam_folders = p_strsplit(global_pool, tmp, ";"); + + if (spam_folders) { + iter = spam_folders; + while (*iter) { + debug("antispam: \"%s\" is spam folder\n", *iter); + iter++; + } + } else + debug("antispam: no spam folders\n"); + +#ifdef BACKEND_WANT_SIGNATURE + tmp = getenv("ANTISPAM_SIGNATURE"); + if (tmp) + signature_hdr = tmp; + debug("antispam: signature header line is \"%s\"\n", signature_hdr); +#endif + + backend_init(global_pool); + command_unregister("COPY"); command_unregister("APPEND"); command_unregister("UID COPY"); @@ -244,6 +361,8 @@ void dspam_init(void) command_register(i_strdup("APPEND"), cmd_append_antispam); } -void dspam_deinit(void) +void antispam_deinit(void) { + backend_exit(); + pool_unref(global_pool); } @@ -2,14 +2,15 @@ #define _ANTISPAM_PLUGIN_H #include <unistd.h> +#include "lib.h" #include "mempool.h" -struct signature { - struct signature *next; - char *sig; +struct strlist { + struct strlist *next; + const char *str; }; -#ifdef CONFIG_PLUGIN_WANT_SIGNATURE +#ifdef BACKEND_WANT_SIGNATURE /* * Call backend giving * - pool: dovecot memory pool, will be freed afterwards @@ -17,18 +18,21 @@ struct signature { * - sigs: signatures, next == NULL terminates list * - */ -int backend(pool_t pool, int spam, struct signature *sigs); +bool backend(pool_t pool, bool spam, struct strlist *sigs); #elif CONFIG_PLUGIN_WANT_MAIL #error TODO: no support for pristine training yet #else #error BUILD SYSTEM ERROR #endif +void backend_init(pool_t pool); +void backend_exit(void); + #ifdef CONFIG_DEBUG void debug(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); #else -static inline void -debug(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))) +static void debug(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); +static inline void debug(const char *fmt, ...) { } #endif |