1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
|
/*
* mailing backend for dovecot antispam plugin
*
* Copyright (C) 2008 Steffen Kaiser <skdovecot@smail.inf.fh-brs.de>
* this backend "spool2dir" bases on "mailtrain" backend of
* Copyright (C) 2007 Johannes Berg <johannes@sipsolutions.net>
*
* 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 <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "lib.h"
#include "mail-storage-private.h"
#include "ostream.h"
#include "istream.h"
#include "antispam-plugin.h"
struct antispam_transaction_context {
int count;
};
static void backend_rollback(const struct antispam_config *cfg ATTR_UNUSED,
struct antispam_transaction_context *ast)
{
i_free(ast);
}
static int backend_commit(const struct antispam_config *cfg ATTR_UNUSED,
struct mailbox_transaction_context *ctx ATTR_UNUSED,
struct antispam_transaction_context *ast)
{
i_free(ast);
return 0;
}
static int backend_handle_mail(const struct antispam_config *cfg,
struct mailbox_transaction_context *t,
struct antispam_transaction_context *ast,
struct mail *mail, enum classification wanted)
{
struct istream *mailstream;
struct ostream *outstream;
int ret = -1;
const char *dest, *buf;
const unsigned char *beginning;
size_t size;
int fd = -1;
i_assert(ast);
switch (wanted) {
case CLASS_SPAM:
dest = cfg->s2d.spamspool;
break;
case CLASS_NOTSPAM:
dest = cfg->s2d.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;
}
if (mail_get_stream(mail, NULL, NULL, &mailstream) < 0) {
mail_storage_set_error(t->box->storage,
ME(EXPUNGED)
"Failed to get mail contents");
return -1;
}
T_BEGIN {
/* 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 */
}
if (fd < 0) {
debug(&cfg->dbgcfg, "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) {
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);
if (o_stream_send_istream(outstream, mailstream) < 0) {
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);
if (ret)
unlink(buf);
out: ;
} T_END;
return ret;
}
static void backend_init(struct antispam_config *cfg,
const char *(getenv)(const char *env, void *data),
void *getenv_data)
{
cfg->s2d.spamspool = getenv("SPOOL2DIR_SPAM", getenv_data);
if (cfg->s2d.spamspool)
debug(&cfg->dbgcfg, "spool2dir spamspool %s\n", cfg->s2d.spamspool);
cfg->s2d.hamspool = getenv("SPOOL2DIR_NOTSPAM", getenv_data);
if (cfg->s2d.hamspool)
debug(&cfg->dbgcfg, "spool2dir hamspool %s\n", cfg->s2d.hamspool);
}
static struct antispam_transaction_context *
backend_start(const struct antispam_config *cfg ATTR_UNUSED,
struct mailbox *box ATTR_UNUSED)
{
struct antispam_transaction_context *ast;
ast = i_new(struct antispam_transaction_context, 1);
ast->count = 0;
return ast;
}
struct backend spool2dir_backend = {
.init = backend_init,
.handle_mail = backend_handle_mail,
.start = backend_start,
.rollback = backend_rollback,
.commit = backend_commit,
};
|