diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | trainstore.c | 311 |
2 files changed, 314 insertions, 0 deletions
@@ -54,6 +54,9 @@ endif ifeq ("$(BACKEND)", "crm114-exec") objs += signature.o endif +ifeq ("$(BACKEND)", "trainstore") +CFLAGS += -lssl +endif # main make rules CFLAGS += -fPIC -shared -Wall -Wextra -DPLUGINNAME=$(PLUGINNAME) diff --git a/trainstore.c b/trainstore.c new file mode 100644 index 0000000..bcd40be --- /dev/null +++ b/trainstore.c @@ -0,0 +1,311 @@ +/* + * mailing backend for dovecot antispam plugin + * + * Copyright (C) 2009 Alexander Sulfrian <alexander@sulfrian.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 + */ + +#include <unistd.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <dirent.h> +#include <openssl/sha.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <libgen.h> + +#include "lib.h" +#include "dict.h" +#include "mail-storage-private.h" +#include "ostream.h" +#include "istream.h" + +#include "antispam-plugin.h" + +static const char *spamdir = NULL; +static const char *hamdir = NULL; + +struct antispam_transaction_context { + int count; +}; + +int mkdir_rec(const char *dir, mode_t mask) { + char *parent; + struct stat sb; + int status; + + if (stat(dir, &sb) == 0) { + if (! S_ISDIR(sb.st_mode)) { + return -1; + } + + return 0; + } + + t_push(); + + parent = t_malloc(strlen(dir) + 1); + strcpy(parent, dir); + + if ((status = mkdir_rec(dirname(parent), mask)) == 0) { + mkdir(dir, mask); + + status = 0; + } + + t_pop(); + + return status; +} + +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; + + /* Create spam and hamdir */ + mkdir_rec(spamdir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + mkdir_rec(hamdir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + return ast; +} + +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; +} + +static char *create_unique_mailname(const char *date, const char *mail_id, char *sha1) +{ + char *unique_string; + unsigned char *sha_bytes; + + t_push(); + + unique_string = t_malloc(strlen(date) + strlen(mail_id) + 1); + i_snprintf(unique_string, strlen(date) + strlen(mail_id) + 1, "%s%s", date, mail_id); + + sha_bytes = t_malloc(20); + sha_bytes = SHA1((unsigned char*)unique_string, strlen(date) + strlen(mail_id), sha_bytes); + + int i; + for (i = 0; i < 20; i++) { + i_snprintf(sha1+(i*2), 3, "%02hhx", sha_bytes[i]); + } + sha1[40] = 0; + + t_pop(); + + return sha1; +} + +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; + char *file; + const unsigned char *beginning; + size_t size; + int fd; + char *name = NULL, *check_file = NULL; + const char *date = NULL, *mail_id = NULL, *dest = NULL, *other = NULL; + struct stat sb; + + if (!hamdir || !spamdir) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "antispam plugin 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(); + + /* Generate unique id for mail */ + + /* get date header */ + if (mail_get_first_header(mail, "Date", &date) == 0) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Invalid mail with no date header"); + ret = -1; + goto out; + } + + /* get message id */ + if (mail_get_first_header(mail, "Message-Id", &mail_id) == 0) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Invalid mail with no message id header"); + ret = -1; + goto out; + } + + /* hash name and message id */ + name = t_malloc(41 * sizeof(char)); + name = create_unique_mailname(date, mail_id, name); + + /* switch weather this should be ham or spam */ + switch (wanted) { + case CLASS_SPAM: + dest = spamdir; + other = hamdir; + break; + case CLASS_NOTSPAM: + dest = hamdir; + other = spamdir; + break; + } + + if (dest == NULL) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to determine whether mail is ham or spam"); + ret = -1; + goto out; + } + + /* check if this mail is already marked as the opposit */ + check_file = t_malloc(2 + strlen(other) + strlen(name)); + i_snprintf(check_file, 1 + strlen(other) + strlen(name), "%s/%s", other, name); + + if (stat(check_file, &sb) == 0) { + debug("mail is marked opposit, remove it there"); + unlink(check_file); + } + + /* create file name */ + file = t_malloc(2 + strlen(dest) + strlen(name)); + i_snprintf(file, 1 + strlen(dest) + strlen(name), "%s/%s", dest, name); + + if (stat(file, &sb) == 0) { + debug("mail is already there, nothing to do"); + ret = 0; + goto out; + } + + fd = creat(file, 0600); + if (fd < 0) { + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to create temporary file"); + ret = -1; + goto out; + } + + ast->count++; + + outstream = o_stream_create_from_fd(fd, t->box->pool); + if (!outstream) { + ret = -1; + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to stream temporary file"); + goto out_close; + } + + if (o_stream_send(outstream, &wanted, sizeof(wanted)) + != sizeof(wanted)) { + ret = -1; + mail_storage_set_error(t->box->storage, + ME(NOTPOSSIBLE) + "Failed to write marker to temp file"); + goto failed_to_copy; + } + + 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_str(outstream, "From ") < 0) { + 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 temporary file"); + goto failed_to_copy; + } + + ret = 0; + + failed_to_copy: + o_stream_destroy(&outstream); + out_close: + close(fd); + out: + t_pop(); + + return ret; +} + +void backend_init(pool_t pool __attr_unused__) +{ + const char *tmp; + + tmp = get_setting("DIR_SPAM"); + if (tmp) { + spamdir = tmp; + debug("mail backend spam directory %s\n", tmp); + } + + tmp = get_setting("DIR_NOTSPAM"); + if (tmp) { + hamdir = tmp; + debug("mail backend not-spam directory %s\n", tmp); + } +} + +void backend_exit(void) +{ +} |