/* * mailing backend for dovecot antispam plugin * * Copyright (C) 2009 Alexander Sulfrian * * 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 #include #include #include #include #include #include #include #include #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); 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) { }