diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 53 | ||||
-rw-r--r-- | debug.c | 25 | ||||
-rw-r--r-- | defconfig | 25 | ||||
-rw-r--r-- | dspam-exec.c | 426 | ||||
-rw-r--r-- | plugin.c | 421 | ||||
-rw-r--r-- | plugin.h | 36 |
7 files changed, 678 insertions, 310 deletions
@@ -1,2 +1,4 @@ *.o *.so +.config +*~ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..717c58d --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +# include config file +-include .config + +# includes/flags we need for building a dovecot plugin +CFLAGS += -DHAVE_CONFIG_H +CFLAGS += -I$(DOVECOT)/ +CFLAGS += -I$(DOVECOT)/src/ +CFLAGS += -I$(DOVECOT)/src/lib/ +CFLAGS += -I$(DOVECOT)/src/lib-storage/ +CFLAGS += -I$(DOVECOT)/src/lib-mail/ +CFLAGS += -I$(DOVECOT)/src/lib-imap/ +CFLAGS += -I$(DOVECOT)/src/imap/ + +# per-backend configuration +ifeq ("$(BACKEND)", "dspam-exec") +CFLAGS += -DCONFIG_PLUGIN_WANT_SIGNATURE=1 +# can take a while, check more often +CFLAGS += -DCOPY_CHECK_INTERVAL=10 +endif + + +# debug rules +ifeq ("$(DEBUG)", "stderr") +CFLAGS += -DCONFIG_DEBUG -DDEBUG_STDERR +objs += debug.o +else ifeq ("$(DEBUG)", "syslog") +CFLAGS += -DCONFIG_DEBUG -DDEBUG_SYSLOG +objs += debug.o +endif + + +# main make rules +CFLAGS += -fPIC -shared -Wall +CC ?= "gcc" + +objs += plugin.o $(BACKEND).o +ALL = antispam + +all: verify_config $(ALL) + +antispam: $(objs) + $(CC) $(CFLAGS) $(INCLUDES) $(objs) -o $@.so $(LDFLAGS) + +clean: + rm -f *.so *.o *~ + +verify_config: + @if [ ! -r .config ]; then \ + echo -e "\nBuilding the plugin requires a configuration file"; \ + echo -e '(.config). Copy defconfig ("cp defconfig .config")' ; \ + echo -e "to create an example configuration.\n"; \ + exit 1; \ + fi @@ -0,0 +1,25 @@ +#define _BSD_SOURCE +#include <syslog.h> +#include <stdarg.h> +#include <stdio.h> +#include "plugin.h" + +static void _debug(const char *format, va_list ap) +{ +#if defined(DEBUG_SYSLOG) + vsyslog(LOG_DEBUG, format, ap); +#elif defined(DEBUG_STDERR) + vfprintf(stderr, format, ap); +#else +#error no logging method +#endif +} + +void debug(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _debug(fmt, args); + va_end(args); +} diff --git a/defconfig b/defconfig new file mode 100644 index 0000000..c9ac677 --- /dev/null +++ b/defconfig @@ -0,0 +1,25 @@ +# Example plugin build time configuration +# +# This file lists the configuration options that are used when building the +# antispam plugin. All lines starting with # are ignored. Configuration option +# lines must be commented out completely, if they are not to be included, +# i.e. just setting VARIABLE=n is not disabling that variable. +# +# This file is included in Makefile, so variables like CFLAGS and LIBS can also +# be modified from here. In most cases, these lines should use += in order not +# to override previous values of the variables. +# +# IMPORTANT NOTE: If you change the config, you need to run 'make clean'! + +# Dovecot build directory +# (building the plugin requires configured dovecot sources) +DOVECOT=.. + +# backend +# - dspam-exec: direct dspam training by calling dspam executable +BACKEND=dspam-exec + + +# enable debugging to syslog or stderr +DEBUG=stderr +#DEBUG=syslog diff --git a/dspam-exec.c b/dspam-exec.c new file mode 100644 index 0000000..71560f9 --- /dev/null +++ b/dspam-exec.c @@ -0,0 +1,426 @@ +/* + * dspam plugin for dovecot + * + * Copyright (C) 2004-2007 Johannes Berg <johannes@sipsolutions.net> + * 2006 Frank Cusack + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 <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 + +static int call_dspam(const char *signature, int is_spam) +{ + pid_t pid; + int s; + char class_arg[16 + 2]; + char sign_arg[MAXSIGLEN + 2]; + 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"); + + pipe(pipes); /* for dspam stderr */ + + pid = fork(); + if (pid < 0) + return -1; + + if (pid) { + int status; + /* well. dspam doesn't report an error if it has an error, + but instead only prints stuff to stderr. Usually, it + won't print anything, so we treat it having output as + an error condition */ + + char buf[1024]; + int readsize; + close(pipes[1]); + + do { + readsize = read(pipes[0], buf, 1024); + if (readsize < 0) { + readsize = -1; + if (errno == EINTR) + readsize = -2; + } + } while (readsize == -2); + + if (readsize != 0) { + close(pipes[0]); + return -1; + } + + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) { + close(pipes[0]); + return -1; + } + + readsize = read(pipes[0], buf, 1024); + if (readsize != 0) { + close(pipes[0]); + return -1; + } + + close(pipes[0]); + return WEXITSTATUS(status); + } else { + int fd = open("/dev/null", O_RDONLY); + close(0); + close(1); + close(2); + /* see above */ + close(pipes[0]); + + if (dup2(pipes[1], 2) != 2) { + exit(1); + } + if (dup2(pipes[1], 1) != 1) { + exit(1); + } + close(pipes[1]); + + if (dup2(fd, 0) != 0) { + exit(1); + } + 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, + 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) +{ + 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); + } + + return cmd_append(cmd); +} + +static bool cmd_copy_spam_plugin(struct client_command_context *cmd) +{ + 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; + + 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; + } + } + + 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) +{ +} @@ -1,218 +1,97 @@ /* - dspam plugin for dovecot - - Copyright (C) 2004-2006 Johannes Berg <johannes@sipsolutions.net> - 2006 Frank Cusack - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License Version 2 as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - 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. - - To compile: - make "plugins" directory right beside "src" in the dovecot source tree, - copy this into there and run - - cc -fPIC -shared -Wall \ - -I../src/ \ - -I../src/lib \ - -I.. \ - -I../src/lib-storage \ - -I../src/lib-mail \ - -I../src/lib-imap \ - -I../src/imap/ \ - -DHAVE_CONFIG_H \ - -DDSPAM=\"/path/to/dspam\" \ - dspam.c -o lib_dspam.so - - (if you leave out -DDSPAM=... then /usr/bin/dspam is taken as default) - - Install the plugin in the usual dovecot module location. -*/ - -/* - * If you need to ignore a trash folder, define a trash folder - * name as follows, or alternatively give -DIGNORE_TRASH_NAME=\"Trash\" on - * the cc command line. + * antispam plugin for dovecot + * + * Copyright (C) 2004-2007 Johannes Berg <johannes@sipsolutions.net> + * 2006 Frank Cusack + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. */ -/*#define IGNORE_TRASH_NAME "Trash"*/ -#include "common.h" -#include "str.h" -#include "strfuncs.h" -#include "commands.h" -#include "imap-search.h" -#include "lib-storage/mail-storage.h" -#include "lib/mempool.h" -#include "mail-storage.h" #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 -#define SIGHEADERLINE "X-DSPAM-Signature" -#define MAXSIGLEN 100 +/* 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" -#ifndef DSPAM -#define DSPAM "/usr/bin/dspam" -#endif /* DSPAM */ +/* internal stuff we need */ +#include "plugin.h" -static int call_dspam(const char *signature, int is_spam) +static struct signature *list_add(pool_t pool, struct signature *list) { - pid_t pid; - int s; - char class_arg[16 + 2]; - char sign_arg[MAXSIGLEN + 2]; - 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"); - - pipe(pipes); /* for dspam stderr */ - - pid = fork(); - if (pid < 0) - return -1; - - if (pid) { - int status; - /* well. dspam doesn't report an error if it has an error, - but instead only prints stuff to stderr. Usually, it - won't print anything, so we treat it having output as - an error condition */ - - char buf[1024]; - int readsize; - close(pipes[1]); - - do { - readsize = read(pipes[0], buf, 1024); - if (readsize < 0) { - readsize = -1; - if (errno == EINTR) - readsize = -2; - } - } while (readsize == -2); - - if (readsize != 0) { - close(pipes[0]); - return -1; - } - - waitpid(pid, &status, 0); - if (!WIFEXITED(status)) { - close(pipes[0]); - return -1; - } + struct signature *n; - readsize = read(pipes[0], buf, 1024); - if (readsize != 0) { - close(pipes[0]); - return -1; - } - - close(pipes[0]); - return WEXITSTATUS(status); - } else { - int fd = open("/dev/null", O_RDONLY); - close(0); - close(1); - close(2); - /* see above */ - close(pipes[0]); - - if (dup2(pipes[1], 2) != 2) { - exit(1); - } - if (dup2(pipes[1], 1) != 1) { - exit(1); - } - close(pipes[1]); - - if (dup2(fd, 0) != 0) { - exit(1); - } - close(fd); + n = p_malloc(pool, sizeof(struct signature)); + n->next = list; + n->sig = NULL; -#ifdef DEBUG - syslog(LOG_INFO, DSPAM " --source=error --stdout %s %s", - class_arg, sign_arg); -#endif - execl(DSPAM, DSPAM, "--source=error", "--stdout", class_arg, - sign_arg, NULL); - exit(127); /* fall through if dspam can't be found */ - return -1; /* never executed */ - } + return n; } -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) +static void client_send_sendalive_if_needed(struct client *client) { - siglist_t l = *list; - siglist_t p = NULL; - siglist_t n; + time_t now, last_io; - 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; + if (o_stream_get_buffer_used_size(client->output) != 0) + return; + + now = time(NULL); + last_io = I_MAX(client->last_input, client->last_output); + if (now - last_io > MAIL_STORAGE_STAYALIVE_SECS) { + o_stream_send_str(client->output, "* OK Hang in there..\r\n"); + o_stream_flush(client->output); + client->last_output = now; } - 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) +static int fetch_and_copy(struct client *client, + struct mailbox_transaction_context *t, + struct mail_search_arg *search_args) { struct mail_search_context *search_ctx; - struct mailbox_transaction_context *src_trans; + struct mailbox_transaction_context *src_trans; struct mail_keywords *keywords; const char *const *keywords_list; struct mail *mail; + unsigned int copy_count = 0; 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); + src_trans = mailbox_transaction_begin(client->mailbox, 0); search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL); mail = mail_alloc(src_trans, MAIL_FETCH_STREAM_HEADER | @@ -224,21 +103,15 @@ fetch_and_copy_reclassified(struct mailbox_transaction_context *t, 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); + if ((++copy_count % COPY_CHECK_INTERVAL) == 0) + client_send_sendalive_if_needed(client); keywords_list = mail_get_keywords(mail); keywords = strarray_length(keywords_list) == 0 ? NULL : - mailbox_keywords_create(t, keywords_list); + mailbox_keywords_create(t, keywords_list); if (mailbox_copy(t, mail, mail_get_flags(mail), keywords, NULL) < 0) - ret = -1; + ret = mail->expunged ? 0 : -1; mailbox_keywords_free(t, &keywords); } mail_free(&mail); @@ -246,75 +119,23 @@ fetch_and_copy_reclassified(struct mailbox_transaction_context *t, 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; - } + 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); - } - - return cmd_append(cmd); -} -static bool cmd_copy_spam_plugin(struct client_command_context *cmd) +static bool cmd_copy_antispam(struct client_command_context *cmd) { struct client *client = cmd->client; struct mail_storage *storage; struct mailbox *destbox; struct mailbox_transaction_context *t; - struct mail_search_arg *search_arg; + struct mail_search_arg *search_arg; const char *messageset, *mailbox; - enum mailbox_sync_flags sync_flags = 0; + 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)) @@ -323,42 +144,6 @@ static bool cmd_copy_spam_plugin(struct client_command_context *cmd) 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; @@ -375,6 +160,7 @@ static bool cmd_copy_spam_plugin(struct client_command_context *cmd) destbox = client->mailbox; else { destbox = mailbox_open(storage, mailbox, NULL, + MAILBOX_OPEN_SAVEONLY | MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT); if (destbox == NULL) { @@ -385,9 +171,7 @@ static bool cmd_copy_spam_plugin(struct client_command_context *cmd) t = mailbox_transaction_begin(destbox, MAILBOX_TRANSACTION_FLAG_EXTERNAL); - ret = - fetch_and_copy_reclassified(t, client->mailbox, search_arg, is_spam, - &enh_error); + ret = fetch_and_copy(client, t, search_arg); if (ret <= 0) mailbox_transaction_rollback(&t); @@ -406,41 +190,58 @@ static bool cmd_copy_spam_plugin(struct client_command_context *cmd) 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."); + "NO Some of the requested messages no longer exist."); } else { - switch (enh_error) { - case -2: + client_send_storage_error(cmd, storage); + return TRUE; + } +} + + +static bool cmd_append_antispam(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; + 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 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; + "NO Cannot APPEND to SPAM box, sorry."); } + + mailbox_close(&box); } - return TRUE; + return cmd_append(cmd); } + 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() + /* + * 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); + * would otherwise point to nonexisting memory. + */ + command_register(i_strdup("COPY"), cmd_copy_antispam); + command_register(i_strdup("UID COPY"), cmd_copy_antispam); + command_register(i_strdup("APPEND"), cmd_append_antispam); } void dspam_deinit(void) diff --git a/plugin.h b/plugin.h new file mode 100644 index 0000000..e55bd05 --- /dev/null +++ b/plugin.h @@ -0,0 +1,36 @@ +#ifndef _ANTISPAM_PLUGIN_H +#define _ANTISPAM_PLUGIN_H + +#include <unistd.h> +#include "mempool.h" + +struct signature { + struct signature *next; + char *sig; +}; + +#ifdef CONFIG_PLUGIN_WANT_SIGNATURE +/* + * Call backend giving + * - pool: dovecot memory pool, will be freed afterwards + * - spam: whether mail comes from spam folder or not + * - sigs: signatures, next == NULL terminates list + * - + */ +int backend(pool_t pool, int spam, struct signature *sigs); +#elif CONFIG_PLUGIN_WANT_MAIL +#error TODO: no support for pristine training yet +#else +#error BUILD SYSTEM ERROR +#endif + +#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))) +{ +} +#endif + +#endif /* _ANTISPAM_PLUGIN_H */ |