From 2d186fa9ad2d6cad065fabf65048edd3ce82f6df Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 23 Feb 2009 17:42:19 -0800 Subject: fix dest_mail assignment bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit inspired by patch from Stéphane Cottin --- antispam-storage-1.1.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/antispam-storage-1.1.c b/antispam-storage-1.1.c index 348ae9d..80c80e3 100644 --- a/antispam-storage-1.1.c +++ b/antispam-storage-1.1.c @@ -167,7 +167,8 @@ static int antispam_save_init(struct mailbox_transaction_context *t, timezone_offset, from_envelope, input, dest_mail, ctx_r); - (*ctx_r)->dest_mail = dest_mail; + if (ret >= 0) + (*ctx_r)->dest_mail = dest_mail; return ret; } -- cgit v1.2.3 From 3eaeb73dfd1e255342cb7bcf53e9653c0e3c4104 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Tue, 24 Mar 2009 01:29:06 -0400 Subject: Fix Makefile to use DESTDIR when installing for easier packaging. Signed-off-by: Evan Broder --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a16deed..b833a3f 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ clean: rm -f *.so *.o *~ dovecot-version dovecot-version.h antispam-version.h install: all - install -o $(USER) -g $(GROUP) -m 0755 $(LIBRARY_NAME) $(INSTALLDIR)/ + install -o $(USER) -g $(GROUP) -m 0755 $(LIBRARY_NAME) $(DESTDIR)$(INSTALLDIR)/ verify_config: @if [ ! -r $(CONFIG) ]; then \ -- cgit v1.2.3 From 6538e2b6b0007a281be3498bc16b89aaa14337ef Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sat, 11 Apr 2009 16:49:41 +0200 Subject: mailtrain: send correct 5 bytes if message doesn't start with "From " --- mailtrain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mailtrain.c b/mailtrain.c index 7156805..fe2868d 100644 --- a/mailtrain.c +++ b/mailtrain.c @@ -288,7 +288,7 @@ int backend_handle_mail(struct mailbox_transaction_context *t, if (memcmp("From ", beginning, 5) == 0) { i_stream_read_next_line(mailstream); } else { - if (o_stream_send_str(outstream, "From ") < 0) { + if (o_stream_send(outstream, beginning, 5) != 5) { ret = -1; mail_storage_set_error(t->box->storage, ME(NOTPOSSIBLE) -- cgit v1.2.3 From 443004a02c6707e660a82ab85f3161b692b05fe0 Mon Sep 17 00:00:00 2001 From: Jonas Maurus Date: Wed, 29 Apr 2009 13:13:53 +0200 Subject: add dovecot 1.2 support --- antispam-plugin.h | 2 +- antispam-storage-1.2.c | 488 +++++++++++++++++++++++++++++++++++++++++++++++++ dovecot-version.c | 2 +- 3 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 antispam-storage-1.2.c diff --git a/antispam-plugin.h b/antispam-plugin.h index 468d43e..df59e88 100644 --- a/antispam-plugin.h +++ b/antispam-plugin.h @@ -84,7 +84,7 @@ extern bool need_folder_hook; * Dovecot version compat code */ -#if DOVECOT_VERSION_CODE(1, 1) == DOVECOT_VERSION +#if DOVECOT_VERSION_CODE(1, 1) == DOVECOT_VERSION || DOVECOT_VERSION_CODE(1, 2) == DOVECOT_VERSION #define __attr_unused__ ATTR_UNUSED #define ME(err) MAIL_ERROR_ ##err, #define PLUGIN_ID uint32_t PLUGIN_FUNCTION(id) = 0 diff --git a/antispam-storage-1.2.c b/antispam-storage-1.2.c new file mode 100644 index 0000000..54b8f7b --- /dev/null +++ b/antispam-storage-1.2.c @@ -0,0 +1,488 @@ +/* + * Storage implementation for antispam plugin + * Copyright 2007-2008 Johannes Berg + * Copyright 2009 Jonas Maurus + * + * Derived from Quota plugin: + * Copyright (C) 2005 Timo Sirainen + */ + +#include + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "mail-search.h" +#include "mail-index.h" +#include "mailbox-list-private.h" +#include "mail-storage-private.h" + +#include "antispam-plugin.h" + +#define ANTISPAM_CONTEXT(obj) \ + MODULE_CONTEXT(obj, antispam_storage_module) +#define ANTISPAM_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, antispam_mail_module) + +static MODULE_CONTEXT_DEFINE_INIT(antispam_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(antispam_mail_module, + &mail_module_register); + + +enum mailbox_move_type { + MMT_APPEND, + MMT_UNINTERESTING, + MMT_TO_CLEAN, + MMT_TO_SPAM, +}; + +struct antispam_internal_context { + union mailbox_transaction_module_context module_ctx; + struct antispam_transaction_context *backendctx; + struct mail *mail; +}; + +static enum classification move_to_class(enum mailbox_move_type tp) +{ + switch (tp) { + case MMT_TO_CLEAN: + return CLASS_NOTSPAM; + case MMT_TO_SPAM: + return CLASS_SPAM; + default: + i_assert(0); + } +} + +struct antispam_mailbox { + union mailbox_module_context module_ctx; + + enum mailbox_move_type movetype; + + /* used to check if copy was implemented with save */ + unsigned int save_hack:1; +}; + +static uint32_t antispam_storage_module_id = 0; +static bool antispam_storage_module_id_set = FALSE; + +static int +antispam_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct antispam_mailbox *asbox = ANTISPAM_CONTEXT(t->box); + struct antispam_internal_context *ast = ANTISPAM_CONTEXT(t); + int ret; + bool src_trash, dst_trash; + + if (!ctx->dest_mail) { + /* always need mail */ + if (!ast->mail) + ast->mail = mail_alloc(t, MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY, + NULL); + ctx->dest_mail = ast->mail; + } + + i_assert(mail->box); + + asbox->save_hack = FALSE; + asbox->movetype = MMT_UNINTERESTING; + + if (mailbox_is_unsure(t->box)) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Cannot copy to unsure folder"); + return -1; + } + + src_trash = mailbox_is_trash(mail->box); + dst_trash = mailbox_is_trash(t->box); + + debug_verbose("mail copy: from trash: %d, to trash: %d\n", + src_trash, dst_trash); + + if (!src_trash && !dst_trash) { + bool src_spam = mailbox_is_spam(mail->box); + bool dst_spam = mailbox_is_spam(t->box); + bool src_unsu = mailbox_is_unsure(mail->box); + + debug_verbose("mail copy: src spam: %d, dst spam: %d," + " src unsure: %d\n", + src_spam, dst_spam, src_unsu); + + if ((src_spam || src_unsu) && !dst_spam) + asbox->movetype = MMT_TO_CLEAN; + else if ((!src_spam || src_unsu) && dst_spam) + asbox->movetype = MMT_TO_SPAM; + } + + if (asbox->module_ctx.super.copy(ctx, mail) < 0) + return -1; + + /* + * If copying used saving internally, we already have treated the mail + */ + if (asbox->save_hack || asbox->movetype == MMT_UNINTERESTING) + ret = 0; + else + ret = backend_handle_mail(t, ast->backendctx, ctx->dest_mail, + move_to_class(asbox->movetype)); + + /* + * Both save_hack and movetype are only valid within a copy operation, + * i.e. they are now invalid. Because, in theory, another operation + * could be done after mailbox_open(), we need to reset the movetype + * variable here. save_hack doesn't need to be reset because it is + * only ever set within the save function and tested within this copy + * function after being reset at the beginning of the copy, movetype + * however is tested within the save_finish() function and a subsequent + * save to the mailbox should not invoke the backend. + */ + asbox->movetype = MMT_APPEND; + return ret; +} + +static int antispam_save_begin(struct mail_save_context *ctx, + struct istream *input) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct antispam_internal_context *ast = ANTISPAM_CONTEXT(t); + struct antispam_mailbox *asbox = ANTISPAM_CONTEXT(t->box); + int ret; + + if (!ctx->dest_mail) { + if (!ast->mail) + ast->mail = mail_alloc(t, MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY, + NULL); + ctx->dest_mail = ast->mail; + } + ret = asbox->module_ctx.super.save_begin(ctx, input); + + return ret; +} + +static int antispam_save_finish(struct mail_save_context *ctx) +{ + struct antispam_mailbox *asbox = + ANTISPAM_CONTEXT(ctx->transaction->box); + struct antispam_internal_context *ast = + ANTISPAM_CONTEXT(ctx->transaction); + struct mail *dest_mail; + int ret; + + if (asbox->module_ctx.super.save_finish(ctx) < 0) + return -1; + + dest_mail = ctx->dest_mail ? : ast->mail; + + asbox->save_hack = TRUE; + + ret = 0; + + switch (asbox->movetype) { + case MMT_UNINTERESTING: + break; + case MMT_APPEND: + /* Disallow APPENDs to UNSURE folders. */ + if (mailbox_is_unsure(dest_mail->box)) { + ret = -1; + mail_storage_set_error(dest_mail->box->storage, + MAIL_ERROR_NOTPOSSIBLE, + "Cannot APPEND to an UNSURE folder."); + break; + } else if (mailbox_is_spam(dest_mail->box)) { + /* + * The client is APPENDing a message to a SPAM folder + * so we try to train the backend on it. For most of + * the backends, that can only succeed if the message + * contains appropriate information. + * + * This happens especially when offlineimap is used and + * the user moved a message to the SPAM folder while + * offline---offlineimap cannot reproduce the COPY but + * rather APPENDs the moved message on the next sync. + * + * This could be a bad if the spam headers were not + * generated on our server, but since the user can + * always APPEND to another folder and then COPY to a + * SPAM folder backends need to be prepared for cases + * like this anyway. With dspam, for example, the worst + * that can happen is that the APPEND fails with a + * training error from dspam. + * + * Unfortunately, we cannot handle the cases where + * (1) the user moved a message from one folder that + * contains SPAM to another folder containing SPAM + * (2) the user moved a message out of the SPAM folder + * (3) the user recovered a message from trash + * + * Because of these limitations, this behaviour needs + * to be enabled with an option. + */ + if (!antispam_can_append_to_spam) { + ret = -1; + mail_storage_set_error( + dest_mail->box->storage, + MAIL_ERROR_NOTPOSSIBLE, + "Cannot APPEND to a SPAM folder."); + break; + } + asbox->movetype = MMT_TO_SPAM; + /* fall through to default case to invoke backend */ + } else { + /* neither UNSURE nor SPAM, regular folder */ + break; + } + /* fall through */ + default: + ret = backend_handle_mail(ctx->transaction, ast->backendctx, + dest_mail, + move_to_class(asbox->movetype)); + } + + return ret; +} + +static struct antispam_transaction_context * +antispam_transaction_begin(struct mailbox *box) +{ + struct antispam_transaction_context *ast; + + ast = backend_start(box); + i_assert(ast != NULL); + + return ast; +} + +static void +antispam_transaction_rollback(struct antispam_transaction_context **_ast) +{ + struct antispam_transaction_context *ast = *_ast; + + backend_rollback(ast); + *_ast = NULL; +} + +static int +antispam_transaction_commit(struct mailbox_transaction_context *ctx, + struct antispam_transaction_context **_ast) +{ + struct antispam_transaction_context *ast = *_ast; + int ret; + + ret = backend_commit(ctx, ast); + *_ast = NULL; + return ret; +} + +static void +antispam_mail_update_keywords(struct mail *mail, + enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_private *pmail = (struct mail_private *)mail; + union mail_module_context *amail = ANTISPAM_MAIL_CONTEXT(pmail); + unsigned int i, numkwds; + const ARRAY_TYPE(keywords) *idxkwd = mail_index_get_keywords(keywords->index); + const char *const *keyword_names = array_get(idxkwd, &numkwds); + const char *const *orig_keywords; + bool previous_spam_keyword, now_spam_keyword; + + switch (modify_type) { + case MODIFY_ADD: + debug("adding keyword(s)\n"); + break; + case MODIFY_REMOVE: + debug("removing keyword(s)\n"); + break; + case MODIFY_REPLACE: + debug("replacing keyword(s)\n"); + break; + default: + i_assert(0); + } + + orig_keywords = pmail->v.get_keywords(mail); + if (orig_keywords) { + debug("original keyword list:\n"); + while (*orig_keywords) { + debug(" * %s\n", *orig_keywords); + if (keyword_is_spam(*orig_keywords)) + previous_spam_keyword = TRUE; + orig_keywords++; + } + } + + debug("keyword list:\n"); + + for (i = 0; i < keywords->count; i++) { + unsigned int idx = keywords->idx[i]; + + i_assert(idx < numkwds); + + debug(" * %s\n", keyword_names[idx]); + + switch (modify_type) { + case MODIFY_ADD: + case MODIFY_REPLACE: + if (keyword_is_spam(keyword_names[idx])) + now_spam_keyword = TRUE; + break; + case MODIFY_REMOVE: + if (keyword_is_spam(keyword_names[idx])) + now_spam_keyword = FALSE; + break; + default: + i_assert(0); + } + } + + amail->super.update_keywords(mail, modify_type, keywords); + + debug("previous-spam, now-spam: %d, %d\n", + previous_spam_keyword, now_spam_keyword); + + if (previous_spam_keyword != now_spam_keyword) { + /* + * Call backend here. + * + * TODO: It is not clear how to roll back the + * keyword change if the backend fails. + */ + } +} + +static struct mailbox_transaction_context * +antispam_mailbox_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags) +{ + struct antispam_mailbox *asbox = ANTISPAM_CONTEXT(box); + struct mailbox_transaction_context *t; + struct antispam_transaction_context *ast; + struct antispam_internal_context *aic; + + t = asbox->module_ctx.super.transaction_begin(box, flags); + aic = i_new(struct antispam_internal_context, 1); + ast = antispam_transaction_begin(box); + aic->backendctx = ast; + + MODULE_CONTEXT_SET(t, antispam_storage_module, aic); + return t; +} + +static int +antispam_mailbox_transaction_commit(struct mailbox_transaction_context *ctx, + uint32_t *uid_validity_r, + uint32_t *first_saved_uid_r, + uint32_t *last_saved_uid_r) +{ + struct antispam_mailbox *asbox = ANTISPAM_CONTEXT(ctx->box); + struct antispam_internal_context *ast = ANTISPAM_CONTEXT(ctx); + + if (antispam_transaction_commit(ctx, &ast->backendctx) < 0) { + if (ast->mail) + mail_free(&ast->mail); + + asbox->module_ctx.super.transaction_rollback(ctx); + return -1; + } + + if (ast->mail) + mail_free(&ast->mail); + + return asbox->module_ctx.super.transaction_commit(ctx, uid_validity_r, + first_saved_uid_r, + last_saved_uid_r); +} + +static void +antispam_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + struct antispam_mailbox *asbox = ANTISPAM_CONTEXT(ctx->box); + struct antispam_internal_context *ast = ANTISPAM_CONTEXT(ctx); + + if (ast->mail) + mail_free(&ast->mail); + + asbox->module_ctx.super.transaction_rollback(ctx); + + antispam_transaction_rollback(&ast->backendctx); +} + +static struct mail * +antispam_mailbox_mail_alloc(struct mailbox_transaction_context *ctx, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct antispam_mailbox *asbox = ANTISPAM_CONTEXT(ctx->box); + union mail_module_context *amail; + struct mail *_mail; + struct mail_private *mail; + + _mail = asbox->module_ctx.super. + mail_alloc(ctx, wanted_fields, wanted_headers); + mail = (struct mail_private *)_mail; + + amail = p_new(mail->pool, union mail_module_context, 1); + amail->super = mail->v; + + mail->v.update_keywords = antispam_mail_update_keywords; + MODULE_CONTEXT_SET_SELF(mail, antispam_mail_module, amail); + return _mail; +} + +static struct mailbox *antispam_mailbox_open(struct mail_storage *storage, + const char *name, + struct istream *input, + enum mailbox_open_flags flags) +{ + union mail_storage_module_context *as_storage = ANTISPAM_CONTEXT(storage); + struct mailbox *box; + struct antispam_mailbox *asbox; + + box = as_storage->super.mailbox_open(storage, name, input, flags); + if (box == NULL) + return NULL; + + asbox = p_new(box->pool, struct antispam_mailbox, 1); + asbox->module_ctx.super = box->v; + asbox->save_hack = FALSE; + asbox->movetype = MMT_APPEND; + + if (need_folder_hook) { + /* override save_init to override want_mail, we need that */ + box->v.save_begin = antispam_save_begin; + box->v.save_finish = antispam_save_finish; + box->v.transaction_begin = antispam_mailbox_transaction_begin; + box->v.transaction_commit = antispam_mailbox_transaction_commit; + box->v.transaction_rollback = antispam_mailbox_transaction_rollback; + box->v.copy = antispam_copy; + } + + if (need_keyword_hook) + box->v.mail_alloc = antispam_mailbox_mail_alloc; + + MODULE_CONTEXT_SET(box, antispam_storage_module, asbox); + return box; +} + +void antispam_mail_storage_created(struct mail_storage *storage) +{ + union mail_storage_module_context *as_storage; + + if (antispam_next_hook_mail_storage_created != NULL) + antispam_next_hook_mail_storage_created(storage); + + as_storage = p_new(storage->pool, union mail_storage_module_context, 1); + as_storage->super = storage->v; + storage->v.mailbox_open = antispam_mailbox_open; + + if (!antispam_storage_module_id_set) { + antispam_storage_module_id = PLUGIN_FUNCTION(id); + antispam_storage_module_id_set = TRUE; + } + + MODULE_CONTEXT_SET_SELF(storage, antispam_storage_module, as_storage); +} diff --git a/dovecot-version.c b/dovecot-version.c index 82772a9..fbb7a88 100644 --- a/dovecot-version.c +++ b/dovecot-version.c @@ -8,7 +8,7 @@ int main(int argc, char **argv) char *v = PACKAGE_STRING, *e; int maj = 0, min = 0; - if (strncmp(v, "dovecot ", 8)) + if (strncmp(v, "dovecot ", 8) && strncmp(v, "Dovecot ", 8)) return 1; /* skip "dovecot " */ -- cgit v1.2.3 From c6015d222e39020382ec5d0439539684d28faa5c Mon Sep 17 00:00:00 2001 From: Steffen Kaiser Date: Sat, 30 May 2009 15:50:49 +0200 Subject: spool2dir backend --- antispam.7 | 16 ++++ defconfig | 2 + spool2dir.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 spool2dir.c diff --git a/antispam.7 b/antispam.7 index efec9cd..f19e2c7 100644 --- a/antispam.7 +++ b/antispam.7 @@ -94,6 +94,11 @@ into a spam folder and other mail regularly. Has the same drawbacks as the dspam-exec approach. +.SS spool2dir backend (general) + +This backend spools the message into a file. No further processing +is performed. You need to write an extra daemon that picks up the +spooled files and trains the spam filter as appropriate. .SH CONFIGURATION @@ -194,6 +199,17 @@ plugin { # NOTE: you need to set the signature for this backend antispam_signature = X-CRM114-CacheID + + #=================== + # spool2dir plugin + + # spam/not-spam spool2dir drop (default unset which will give errors) + # The first %%lu is replaced by the current time. + # The second %%lu is replaced by a counter to generate unique names. + # These two tokens MUST be present in the template! However + # you can insert any C-style modifier as shown. + # antispam_spool2dir_spam = /tmp/spamspool/%%020lu-%u-%%05lus + # antispam_spool2dir_notspam = /tmp/spamspool/%%020lu-%u-%%05luh } .fi diff --git a/defconfig b/defconfig index ef22e81..3bbda18 100644 --- a/defconfig +++ b/defconfig @@ -14,10 +14,12 @@ # signature-log - signature logging using dovecot's dict API # mailtrain - send mail to special addresses for training # crm114-exec - direct crm114 training by calling mailreaver.crm +# spool2dir - spool trained emails to a directory #BACKEND=dspam-exec #BACKEND=signature-log #BACKEND=mailtrain #BACKEND=crm114-exec +#BACKEND=spool2dir # Dovecot build/header directory # Building the plugin requires configured dovecot sources or having diff --git a/spool2dir.c b/spool2dir.c new file mode 100644 index 0000000..06bd307 --- /dev/null +++ b/spool2dir.c @@ -0,0 +1,268 @@ +/* + * mailing backend for dovecot antispam plugin + * + * Copyright (C) 2008 Steffen Kaiser + * this backend "spool2dir" bases on "mailtrain" backend of + * Copyright (C) 2007 Johannes Berg + * + * 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 + */ + + +/* + * spool2dir antispam backend / plugin + * + * Any modification of SPAM status is recorded into a directory. + * + * Configuration + * + * Via settings similiar to the other antispam backends + * antispam_spool2dir_spam :- filename tempate for SPAM messages + * antispam_spool2dir_notsam :- filename tempate for HAM messages + * + * The templates _must_ provide two arguments: + * 1. %%lu - the current unix time (lowercase L, lowercase U) + * 2. %%lu - a counter to create different temporary files + * Note: The %-sign must be given two times to protect agains + * the expansion by Dovecot itself. You can put any legal + * format modification character of C's printf() function between + * '%%' and 'lu'. + * + * e.g.: + * antispam_spool2dir_spam = /tmp/spamspool/%%020lu-%%05lu-%u-S + * antispam_spool2dir_ham = /tmp/spamspool/%%020lu-%%05lu-%u-H + * + * This example will spool the messages into the directory + * /tmp/spamspool. The individual files start with 20 digits, + * followe by a dash, 5 digits, the current username and S or H, + * indicating Spam or Ham messages. + * The first %%lu placeholder is replace by the current unix time, + * the second %%lu with the counter. That way, if the same user + * trains the same message twice, the filename indicates the order + * in which it was done. So if the message was trained as SPAM first, + * as HAM later, HAM superceeds SPAM. + * + * Operation + * + * When the antispam plugin identifies detects a SPAM status change, + * e.g. moving/copying a message from any antispam_spam folder into + * a folder _not_ listed in antispam_spam or antispam_trash, this + * backend spools the complete message into antispam_mail_basedir. + * If there is an error copying _all_ messages around, old spools + * are kept, but the current one is deleted. For instance, if the + * user is copying 15 messages, but only 10 succeed, the 10 would + * be usually deleted. In this backend there is no rollback of + * successfully spooled message, only the failed message is + * deleted. + * + * Possible usage models + * + * A) + * I use spool2dir for training the Bayes database as follows: + * + * Every 10 seconds a service invokes the training program, unless + * it already runs. + * + * The training progran reads the content of the spool directory, sorts + * the filenames alphanumerically, waits two seconds to allow any current + * spool2dir processes to finish currently open files. + * Then one message at a time is read and identified, if it contains + * local modifications, e.g. user-visible SPAM reports, which are removed. + * Furthermore, reports of untrustworthy people are discarded. + * This process continues until either all messages are processed or + * the next message would have another SPAM report type (HAM or SPAM). The + * file names of the messages processed til now are passed to the Bayes + * trainer to be processed within one run. Then those messages are removed. + * + * B) + * + * An Inotify server watches the spamspool directory and passes the messages + * to spamd. No need for the filenames to indicate the order anymore, unless + * the inotify server is not fast enough. + */ + +#include +#include +#include +#include + +#include "lib.h" +#include "mail-storage-private.h" +#include "ostream.h" +#include "istream.h" + +#include "antispam-plugin.h" + +static const char *spamspool = NULL; +static const char * hamspool = NULL; + +struct antispam_transaction_context { + int count; +}; + + +void backend_rollback(struct antispam_transaction_context *ast) +{ + i_free(ast); +} + +int backend_commit(struct mailbox_transaction_context *ctx __attr_unused__, + struct antispam_transaction_context *ast) +{ + i_free(ast); + + return 0; +} + +int backend_handle_mail(struct mailbox_transaction_context *t, + struct antispam_transaction_context *ast, + struct mail *mail, enum classification wanted) +{ + struct istream *mailstream; + struct ostream *outstream; + int ret; + const char *dest, *buf; + const unsigned char *beginning; + size_t size; + int fd; + + i_assert(ast); + + switch (wanted) { + case CLASS_SPAM: + dest = spamspool; + break; + case CLASS_NOTSPAM: + dest = hamspool; + break; + default: /* cannot handle this */ + return -1; + } + + if(!dest) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "antispam plugin / spool2dir backend not configured"); + return -1; + } + + mailstream = get_mail_stream(mail); + if (!mailstream) { + mail_storage_set_error(t->box->storage, + ME(EXPUNGED) + "Failed to get mail contents"); + return -1; + } + + t_push(); + + /* atomically create a _new_ file */ + while (ast->count <= 9999) { + buf = t_strdup_printf(dest, (long)time(0), (long)++ast->count); + fd = open(buf, O_CREAT | O_EXCL | O_WRONLY, 0600); + if(fd >= 0 || errno != EEXIST) + break; + /* current filename in buf already exists, zap it */ + t_pop(); + t_push(); + /* buf is invalid now ! */ + } + if (fd < 0) { + debug("spool2dir backend: Failed to create spool file %s: %s\n", + dest, strerror(errno)); + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to create spool file"); + goto out; + } + + /* buf still points to allocated memory, because fd >= 0 */ + outstream = o_stream_create_from_fd(fd, t->box->pool); + if (!outstream) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to stream spool file"); + goto out_close; + } + + if (i_stream_read_data(mailstream, &beginning, &size, 5) < 0 || + size < 5) { + ret = -1; + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to read mail beginning"); + goto failed_to_copy; + } + + /* "From "? skip line */ + if (memcmp("From ", beginning, 5) == 0) { + i_stream_read_next_line(mailstream); + } else { + if (o_stream_send(outstream, beginning, 5) != 5) { + ret = -1; + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to write line to temp"); + goto failed_to_copy; + } + } + + if (o_stream_send_istream(outstream, mailstream) < 0) { + ret = -1; + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to copy to spool file"); + goto failed_to_copy; + } + + ret = 0; + + failed_to_copy: + o_stream_destroy(&outstream); + out_close: + close(fd); + out: + if (ret) + unlink(buf); + + t_pop(); + + return ret; +} + +void backend_init(pool_t pool __attr_unused__) +{ + if((spamspool = get_setting("SPOOL2DIR_SPAM"))) + debug("spool2dir spamspool %s\n", spamspool); + + if((hamspool = get_setting("SPOOL2DIR_NOTSPAM"))) + debug("spool2dir hamspool %s\n", hamspool); +} + +struct antispam_transaction_context * + backend_start(struct mailbox *box __attr_unused__) +{ + struct antispam_transaction_context *ast; + + ast = i_new(struct antispam_transaction_context, 1); + + ast->count = 0; + + return ast; +} + + +void backend_exit(void) +{ +} -- cgit v1.2.3 From 07ed21cfec312813f3dca9ccd30e40431909a091 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 3 Jun 2009 09:24:56 +0200 Subject: make reference to incron --- antispam.7 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/antispam.7 b/antispam.7 index f19e2c7..daef414 100644 --- a/antispam.7 +++ b/antispam.7 @@ -98,7 +98,8 @@ Has the same drawbacks as the dspam-exec approach. This backend spools the message into a file. No further processing is performed. You need to write an extra daemon that picks up the -spooled files and trains the spam filter as appropriate. +spooled files and trains the spam filter as appropriate. You can, +for example, use incron to pick up new emails. .SH CONFIGURATION -- cgit v1.2.3