aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile53
-rw-r--r--debug.c25
-rw-r--r--defconfig25
-rw-r--r--dspam-exec.c426
-rw-r--r--plugin.c421
-rw-r--r--plugin.h36
7 files changed, 678 insertions, 310 deletions
diff --git a/.gitignore b/.gitignore
index 9d22eb4..81b36f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/debug.c b/debug.c
new file mode 100644
index 0000000..6f5de19
--- /dev/null
+++ b/debug.c
@@ -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)
+{
+}
diff --git a/plugin.c b/plugin.c
index bcd546e..3f7eb97 100644
--- a/plugin.c
+++ b/plugin.c
@@ -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 */