aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugin.c449
1 files changed, 449 insertions, 0 deletions
diff --git a/plugin.c b/plugin.c
new file mode 100644
index 0000000..ddff171
--- /dev/null
+++ b/plugin.c
@@ -0,0 +1,449 @@
+#format CPLUSPLUS
+/*
+ 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.
+ */
+/*#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>
+#ifdef DEBUG
+#include <syslog.h>
+#endif
+
+#define SIGHEADERLINE "X-DSPAM-Signature"
+#define MAXSIGLEN 100
+
+#ifndef DSPAM
+#define DSPAM "/usr/bin/dspam"
+#endif /* DSPAM */
+
+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)
+{
+}