Mercurial > dovecot > original-hg > dovecot-1.2
changeset 4347:a73d2867f6e1 HEAD
Moved all the non-Sieve code from dovecot-lda in here and rewrote parts of
it to be cleaner. The Sieve code can now be run as a plugin from
dovecot-sieve CVS module.
Includes also all fixes to this code from branch_1_0.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Fri, 16 Jun 2006 12:30:07 +0300 |
parents | 5fe316cdd8a6 |
children | 2dc5f48ff4ee |
files | src/deliver/Makefile.am src/deliver/auth-client.c src/deliver/auth-client.h src/deliver/deliver.c src/deliver/deliver.h src/deliver/duplicate.c src/deliver/duplicate.h src/deliver/mail-send.c src/deliver/mail-send.h src/deliver/smtp-client.c src/deliver/smtp-client.h |
diffstat | 11 files changed, 1117 insertions(+), 227 deletions(-) [+] |
line wrap: on
line diff
--- a/src/deliver/Makefile.am Fri Jun 16 12:27:21 2006 +0300 +++ b/src/deliver/Makefile.am Fri Jun 16 12:30:07 2006 +0300 @@ -4,9 +4,19 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-storage \ - -DSYSCONFDIR=\""$(sysconfdir)"\" + -I$(top_srcdir)/src/lib-storage/index/mbox \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DPKG_RUNDIR=\""$(rundir)"\" \ + -DMODULEDIR=\""$(moduledir)"\" + +# get some functions included which only plugins use. liblib should probably +# be a shared library so this wouldn't be needed.. +unused_objects = \ + ../lib/mountpoint.o \ + ../lib/str-sanitize.o libs = \ ../lib-storage/register/libstorage-register.a \ @@ -15,8 +25,10 @@ ../lib-storage/subscription-file/libstorage_subscription_file.a \ ../lib-imap/libimap.a \ ../lib-mail/libmail.a \ + ../lib-dict/libdict.a \ ../lib-charset/libcharset.a \ - ../lib/liblib.a + ../lib/liblib.a \ + $(unused_objects) deliver_LDADD = \ $(libs) \ @@ -27,4 +39,15 @@ deliver_DEPENDENCIES = $(libs) deliver_SOURCES = \ - deliver.c + auth-client.c \ + deliver.c \ + duplicate.c \ + mail-send.c \ + smtp-client.c + +noinst_HEADERS = \ + auth-client.h \ + deliver.h \ + duplicate.h \ + mail-send.h \ + smtp-client.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/auth-client.c Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,181 @@ +/* Copyright (C) 2005-2006 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "network.h" +#include "istream.h" +#include "ostream.h" +#include "env-util.h" +#include "restrict-access.h" +#include "auth-client.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sysexits.h> + +#define MAX_INBUF_SIZE 8192 +#define MAX_OUTBUF_SIZE 512 + +static int return_value; + +struct auth_connection { + int fd; + struct io *io; + struct istream *input; + struct ostream *output; + + struct ioloop *ioloop; + uid_t euid; + + unsigned int handshaked:1; +}; + +static void auth_connection_destroy(struct auth_connection *conn) +{ + io_loop_stop(conn->ioloop); + + io_remove(&conn->io); + i_stream_unref(&conn->input); + o_stream_unref(&conn->output); + if (close(conn->fd) < 0) + i_error("close() failed: %m"); + i_free(conn); +} + +static void auth_parse_input(struct auth_connection *conn, const char *args) +{ + const char *const *tmp, *key, *value; + int home_found = FALSE; + + for (tmp = t_strsplit(args, "\t"); *tmp != NULL; tmp++) { + if (strncmp(*tmp, "uid=", 4) == 0) { + if (conn->euid != strtoul(*tmp + 3, NULL, 10)) { + env_put(t_strconcat("RESTRICT_SETUID=", + *tmp + 4, NULL)); + } + } else if (strncmp(*tmp, "gid=", 4) == 0) { + gid_t gid = strtoul(*tmp + 4, NULL, 10); + + if (conn->euid == 0 || getegid() != gid) { + env_put(t_strconcat("RESTRICT_SETGID=", + *tmp + 4, NULL)); + } + } else if (strncmp(*tmp, "chroot=", 7) == 0) { + env_put(t_strconcat("RESTRICT_CHROOT=", + *tmp + 7, NULL)); + } else if (strncmp(*tmp, "home=", 5) == 0) { + home_found = TRUE; + env_put(t_strconcat("HOME=", *tmp + 5, NULL)); + } else { + key = t_str_ucase(t_strcut(*tmp, '=')); + value = strchr(*tmp, '='); + if (value != NULL) + env_put(t_strconcat(key, "=", value+1, NULL)); + } + } + + if (!home_found) { + /* we must have a home directory */ + i_error("userdb didn't return a home directory"); + return_value = EX_TEMPFAIL; + return; + } + + restrict_access_by_env(TRUE); + return_value = EX_OK; +} + +static void auth_input(void *context) +{ + struct auth_connection *conn = context; + const char *line; + + switch (i_stream_read(conn->input)) { + case 0: + return; + case -1: + /* disconnected */ + auth_connection_destroy(conn); + return; + case -2: + /* buffer full */ + i_error("BUG: Auth master sent us more than %d bytes", + MAX_INBUF_SIZE); + auth_connection_destroy(conn); + return; + } + + if (!conn->handshaked) { + while ((line = i_stream_next_line(conn->input)) != NULL) { + if (strncmp(line, "VERSION\t", 8) == 0) { + if (strncmp(line + 8, "1\t", 2) != 0) { + i_error("Auth master version mismatch"); + auth_connection_destroy(conn); + return; + } + } else if (strncmp(line, "SPID\t", 5) == 0) { + conn->handshaked = TRUE; + break; + } + } + } + + line = i_stream_next_line(conn->input); + if (line != NULL) { + if (strncmp(line, "USER\t1\t", 7) == 0) { + auth_parse_input(conn, line + 7); + } else if (strcmp(line, "NOTFOUND\t1") == 0) + return_value = EX_NOUSER; + else if (strncmp(line, "FAIL\t1\t", 7) == 0) + return_value = EX_TEMPFAIL; + else { + i_error("BUG: Unexpected input from auth master: %s", + line); + } + auth_connection_destroy(conn); + } +} + +static struct auth_connection *auth_connection_new(const char *auth_socket) +{ + struct auth_connection *conn; + int fd; + + fd = net_connect_unix(auth_socket); + if (fd < 0) { + i_error("net_connect(%s) failed: %m", auth_socket); + return NULL; + } + + conn = i_new(struct auth_connection, 1); + conn->fd = fd; + conn->input = + i_stream_create_file(fd, default_pool, MAX_INBUF_SIZE, FALSE); + conn->output = + o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE, FALSE); + conn->io = io_add(fd, IO_READ, auth_input, conn); + return conn; +} + +int auth_client_put_user_env(struct ioloop *ioloop, const char *auth_socket, + const char *user, uid_t euid) +{ + struct auth_connection *conn; + + conn = auth_connection_new(auth_socket); + if (conn == NULL) + return EX_TEMPFAIL; + + conn->ioloop = ioloop; + conn->euid = euid; + + o_stream_send_str(conn->output, + t_strconcat("VERSION\t1\t0\n" + "USER\t1\t", user, "\t" + "service=deliver\n", NULL)); + + return_value = EX_TEMPFAIL; + io_loop_run(ioloop); + return return_value; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/auth-client.h Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,7 @@ +#ifndef __AUTH_CLIENT_H +#define __AUTH_CLIENT_H + +int auth_client_put_user_env(struct ioloop *ioloop, const char *auth_socket, + const char *user, uid_t euid); + +#endif
--- a/src/deliver/deliver.c Fri Jun 16 12:27:21 2006 +0300 +++ b/src/deliver/deliver.c Fri Jun 16 12:30:07 2006 +0300 @@ -1,43 +1,47 @@ -/* Copyright (C) 2005 Timo Sirainen */ - -/* FIXME: pretty ugly thing. */ -#error This program is somewhat out of date, use dovecot-lda instead +/* Copyright (C) 2005-2006 Timo Sirainen */ #include "lib.h" #include "lib-signals.h" #include "ioloop.h" +#include "hostpid.h" +#include "home-expand.h" #include "env-util.h" -#include "network.h" -#include "restrict-access.h" +#include "fd-set-nonblock.h" #include "istream.h" -#include "ostream.h" +#include "istream-seekable.h" +#include "module-dir.h" #include "str.h" #include "var-expand.h" -#include "mail-storage.h" +#include "message-address.h" +#include "dict-client.h" +#include "mbox-from.h" +#include "auth-client.h" +#include "mail-send.h" +#include "duplicate.h" +#include "deliver.h" +#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <pwd.h> -#include <sysexits.h> +#include <syslog.h> -#define DEFAULT_CONFIG_FILE SYSCONFDIR"/dovecot-deliver.conf" -#define DEFAULT_AUTH_SOCKET_PATH "/var/run/dovecot-auth-master" - -#define MAX_INBUF_SIZE 8192 -#define MAX_OUTBUF_SIZE 512 +#define DEFAULT_CONFIG_FILE SYSCONFDIR"/dovecot.conf" +#define DEFAULT_AUTH_SOCKET_PATH PKG_RUNDIR"/auth-master" +#define DEFAULT_SENDMAIL_PATH "/usr/lib/sendmail" -struct auth_connection { - int fd; - struct io *io; - struct istream *input; - struct ostream *output; +/* After buffer grows larger than this, create a temporary file to /tmp + where to read the mail. */ +#define MAIL_MAX_MEMORY_BUFFER (1024*128) - unsigned int handshaked:1; -}; +struct deliver_settings *deliver_set; +deliver_mail_func_t *deliver_mail = NULL; +void (*hook_mail_storage_created)(struct mail_storage *storage) = NULL; + +static struct module *modules; static struct ioloop *ioloop; -static int return_value = EX_SOFTWARE; static void sig_die(int signo, void *context __attr_unused__) { @@ -57,202 +61,104 @@ ctx = mailbox_sync_init(box, 0); while (mailbox_sync_next(ctx, &sync_rec) > 0) ; - return mailbox_sync_deinit(ctx, &status); + return mailbox_sync_deinit(&ctx, &status); } -struct save_mail_context { - struct mail_save_context *save_ctx; - struct istream *input; - int ret; -}; - -static void save_mail_input(void *context) +static struct mailbox * +mailbox_open_or_create_synced(struct mail_storage *storage, const char *name) { - struct save_mail_context *ctx = context; + struct mailbox *box; + bool syntax, temp; + + box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST | + MAILBOX_OPEN_KEEP_RECENT); + if (box != NULL) + return box; + + (void)mail_storage_get_last_error(storage, &syntax, &temp); + if (syntax || temp) + return NULL; - if (ctx->input->closed || - mailbox_save_continue(ctx->save_ctx) < 0) - io_loop_stop(ioloop); - else if (ctx->input->eof) { - ctx->ret = 0; - io_loop_stop(ioloop); + /* probably the mailbox just doesn't exist. try creating it. */ + if (mail_storage_mailbox_create(storage, name, FALSE) < 0) + return NULL; + + /* and try opening again */ + box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST | + MAILBOX_OPEN_KEEP_RECENT); + if (box == NULL) + return NULL; + + if (sync_quick(box) < 0) { + mailbox_close(&box); + return NULL; } + return box; } -static int save_mail(struct mail_storage *storage, const char *mailbox, - struct istream *input) +int deliver_save(struct mail_storage *storage, const char *mailbox, + struct mail *mail, enum mail_flags flags, + const char *const *keywords) { struct mailbox *box; struct mailbox_transaction_context *t; - struct save_mail_context ctx; - struct io *io; + struct mail_keywords *kw; int ret = 0; - box = mailbox_open(storage, mailbox, NULL, MAILBOX_OPEN_FAST | - MAILBOX_OPEN_KEEP_RECENT); + box = mailbox_open_or_create_synced(storage, mailbox); if (box == NULL) - return FALSE; - - if (sync_quick(box) < 0) { - mailbox_close(box); - return FALSE; - } + return -1; t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL); - memset(&ctx, 0, sizeof(ctx)); - ctx.ret = -1; - ctx.input = input; - ctx.save_ctx = mailbox_save_init(t, 0, NULL, (time_t)-1, 0, NULL, - input, FALSE); - - io = io_add(i_stream_get_fd(input), IO_READ, save_mail_input, &ctx); - io_loop_run(ioloop); - io_remove(io); - - ret = ctx.ret; - if (ret < 0) - mailbox_save_cancel(ctx.save_ctx); - else - ret = mailbox_save_finish(ctx.save_ctx, NULL); + kw = strarray_length(keywords) == 0 ? NULL : + mailbox_keywords_create(t, keywords); + if (mailbox_copy(t, mail, flags, kw, NULL) < 0) + ret = -1; + mailbox_keywords_free(t, &kw); if (ret < 0) - mailbox_transaction_rollback(t); + mailbox_transaction_rollback(&t); else - ret = mailbox_transaction_commit(t, 0); + ret = mailbox_transaction_commit(&t, 0); - mailbox_close(box); + mailbox_close(&box); return ret; } -static void auth_connection_destroy(struct auth_connection *conn) +const char *deliver_get_return_address(struct mail *mail) { - io_loop_stop(ioloop); - - io_remove(conn->io); - i_stream_destroy(conn->input); - o_stream_destroy(conn->output); - if (close(conn->fd) < 0) - i_error("close() failed: %m"); - i_free(conn); -} - -static void auth_parse_input(const char *args) -{ - const char *const *tmp; + struct message_address *addr; + const char *str; - for (tmp = t_strsplit(args, "\t"); *tmp != NULL; tmp++) { - if (strncmp(*tmp, "uid=", 4) == 0) { - env_put(t_strconcat("RESTRICT_SETUID=", - *tmp + 4, NULL)); - } else if (strncmp(*tmp, "gid=", 4) == 0) { - env_put(t_strconcat("RESTRICT_SETGID=", - *tmp + 4, NULL)); - } else if (strncmp(*tmp, "chroot=", 7) == 0) { - env_put(t_strconcat("RESTRICT_CHROOT=", - *tmp + 7, NULL)); - } else if (strncmp(*tmp, "home=", 5) == 0) - env_put(t_strconcat("HOME=", *tmp + 5, NULL)); - } - - restrict_access_by_env(TRUE); - return_value = EX_OK; + str = mail_get_first_header(mail, "Return-Path"); + addr = str == NULL ? NULL : + message_address_parse(pool_datastack_create(), + (const unsigned char *)str, + strlen(str), 1); + return addr == NULL || addr->mailbox == NULL || addr->domain == NULL ? + NULL : t_strconcat(addr->mailbox, "@", addr->domain, NULL); } -static void auth_input(void *context) +const char *deliver_get_new_message_id(void) { - struct auth_connection *conn = context; - const char *line; - - switch (i_stream_read(conn->input)) { - case 0: - return; - case -1: - /* disconnected */ - auth_connection_destroy(conn); - return; - case -2: - /* buffer full */ - i_error("BUG: Auth master sent us more than %d bytes", - MAX_INBUF_SIZE); - auth_connection_destroy(conn); - return; - } + static int count = 0; - if (!conn->handshaked) { - while ((line = i_stream_next_line(conn->input)) != NULL) { - if (strncmp(line, "VERSION\t", 8) == 0) { - if (strncmp(line + 8, "1\t", 2) != 0) { - i_error("Auth master version mismatch"); - auth_connection_destroy(conn); - return; - } - } else if (strncmp(line, "SPID\t", 5) == 0) { - conn->handshaked = TRUE; - break; - } - } - } - - line = i_stream_next_line(conn->input); - if (line != NULL) { - if (strncmp(line, "USER\t1\t", 7) == 0) { - auth_parse_input(line + 7); - } else if (strcmp(line, "NOTFOUND\t1") == 0) - return_value = EX_NOUSER; - else if (strncmp(line, "FAIL\t1\t", 7) == 0) - return_value = EX_TEMPFAIL; - else { - i_error("BUG: Unexpected input from auth master: %s", - line); - } - auth_connection_destroy(conn); - } + return t_strdup_printf("<dovecot-%s-%s-%d@%s>", + dec2str(ioloop_timeval.tv_sec), + dec2str(ioloop_timeval.tv_usec), + count++, deliver_set->hostname); } -static struct auth_connection *auth_connection_new(const char *auth_socket) -{ - struct auth_connection *conn; - int fd; - - fd = net_connect_unix(auth_socket); - if (fd < 0) { - i_error("net_connect(%s) failed: %m", auth_socket); - return NULL; - } - - conn = i_new(struct auth_connection, 1); - conn->fd = fd; - conn->input = - i_stream_create_file(fd, default_pool, MAX_INBUF_SIZE, FALSE); - conn->output = - o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE, FALSE); - conn->io = io_add(fd, IO_READ, auth_input, conn); - return conn; -} - -static int user_init(const char *auth_socket, const char *destination) -{ - struct auth_connection *conn; - - conn = auth_connection_new(auth_socket); - if (conn == NULL) - return EX_TEMPFAIL; - - o_stream_send_str(conn->output, - t_strconcat("VERSION\t1\t0\n" - "USER\t1\t", destination, "\t" - "service=deliver\n", NULL)); - - io_loop_run(ioloop); - return return_value; -} +#define IS_WHITE(c) ((c) == ' ' || (c) == '\t') static void config_file_init(const char *path) { struct istream *input; - const char *line, *p, *key, *value; - int fd; + const char *key, *value; + char *line, *p, quote; + int fd, sections = 0, lda_section = FALSE; + size_t len; fd = open(path, O_RDONLY); if (fd < 0) @@ -261,12 +167,54 @@ t_push(); input = i_stream_create_file(fd, default_pool, 1024, TRUE); while ((line = i_stream_read_next_line(input)) != NULL) { - while (*line == ' ') line++; - if (*line == '#') + /* @UNSAFE: line is modified */ + + /* skip whitespace */ + while (IS_WHITE(*line)) + line++; + + /* ignore comments or empty lines */ + if (*line == '#' || *line == '\0') continue; + /* strip away comments. pretty kludgy way really.. */ + for (p = line; *p != '\0'; p++) { + if (*p == '\'' || *p == '"') { + quote = *p; + for (p++; *p != quote && *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') + p++; + } + if (*p == '\0') + break; + } else if (*p == '#') { + *p = '\0'; + break; + } + } + + /* remove whitespace from end of line */ + len = strlen(line); + while (IS_WHITE(line[len-1])) + len--; + line[len] = '\0'; + value = p = strchr(line, '='); - if (value == NULL) + if (value == NULL) { + if (strchr(line, '{') != NULL) { + if (strcmp(line, "protocol lda {") == 0 || + strcmp(line, "plugin {") == 0) + lda_section = TRUE; + sections++; + } + if (*line == '}') { + sections--; + lda_section = FALSE; + } + continue; + } + + if (sections > 0 && !lda_section) continue; while (p > line && p[-1] == ' ') p--; @@ -278,7 +226,7 @@ env_put(t_strconcat(t_str_ucase(key), "=", value, NULL)); } - i_stream_unref(input); + i_stream_unref(&input); t_pop(); } @@ -309,7 +257,7 @@ tab[4].value = home; tab[5].value = NULL; tab[6].value = NULL; - tab[7].value = dec2str(getpid()); + tab[7].value = my_pid; return tab; } @@ -343,18 +291,71 @@ return str_c(str); } +static struct istream *create_mbox_stream(int fd) +{ + const char *mbox_hdr; + struct istream *input_list[4], *input; + + fd_set_nonblock(fd, FALSE); + + mbox_hdr = mbox_from_create("dovecot.deliver", ioloop_time); + + input_list[0] = i_stream_create_from_data(default_pool, mbox_hdr, + strlen(mbox_hdr)); + input_list[1] = i_stream_create_file(fd, default_pool, 4096, FALSE); + input_list[2] = i_stream_create_from_data(default_pool, "\n", 1); + input_list[3] = NULL; + + input = i_stream_create_seekable(input_list, default_pool, + MAIL_MAX_MEMORY_BUFFER, + "/tmp/dovecot.deliver."); + i_stream_unref(&input_list[0]); + i_stream_unref(&input_list[1]); + i_stream_unref(&input_list[2]); + return input; +} + +static void open_logfile(const char *username) +{ + const char *prefix; + + prefix = t_strdup_printf("deliver(%s)", username); + if (getenv("LOG_PATH") == NULL) { + const char *env = getenv("SYSLOG_FACILITY"); + i_set_failure_syslog(prefix, LOG_NDELAY, + env == NULL ? LOG_MAIL : atoi(env)); + } else { + /* log to file or stderr */ + i_set_failure_file(getenv("LOG_PATH"), prefix); + } + + if (getenv("INFO_LOG_PATH") != NULL) + i_set_info_file(getenv("INFO_LOG_PATH")); + + i_set_failure_timestamp_format(getenv("LOG_TIMESTAMP")); +} + +static void print_help(void) +{ + printf("Usage: deliver [-c <config file>] [-d <destination user>] [-m <mailbox>]\n"); +} + int main(int argc, char *argv[]) { - const char *auth_socket = DEFAULT_AUTH_SOCKET_PATH; + const char *config_path = DEFAULT_CONFIG_FILE; const char *mailbox = "INBOX"; - const char *destination, *mail; + const char *auth_socket, *env_tz; + const char *home, *destination, *user, *mail_env, *str; const struct var_expand_table *table; enum mail_storage_flags flags; enum mail_storage_lock_method lock_method; - struct mail_storage *storage; + struct mail_storage *storage, *mbox_storage; + struct mailbox *box; struct istream *input; + struct mailbox_transaction_context *t; + struct mail *mail; + uid_t process_euid; int i, ret; - const char *str; lib_init(); ioloop = io_loop_create(default_pool); @@ -364,6 +365,18 @@ lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL); lib_signals_ignore(SIGPIPE); lib_signals_set_handler(SIGALRM, FALSE, NULL, NULL); +#ifdef SIGXFSZ + lib_signals_set_handler(SIGXFSZ, FALSE, NULL, NULL); +#endif + + /* Clean up environment. */ + env_tz = getenv("TZ"); + home = getenv("HOME"); + env_clean(); + if (env_tz != NULL) + env_put(t_strconcat("TZ=", env_tz, NULL)); + if (home != NULL) + env_put(t_strconcat("HOME=", home, NULL)); destination = NULL; for (i = 1; i < argc; i++) { @@ -375,14 +388,14 @@ "Missing destination argument"); } destination = argv[i]; - } else if (strcmp(argv[i], "-a") == 0) { - /* auth master socket path */ + } else if (strcmp(argv[i], "-c") == 0) { + /* config file path */ i++; if (i == argc) { i_fatal_status(EX_USAGE, - "Missing auth socket path argument"); + "Missing config file path argument"); } - auth_socket = argv[i]; + config_path = argv[i]; } else if (strcmp(argv[i], "-m") == 0) { /* destination mailbox */ i++; @@ -392,67 +405,165 @@ } mailbox = argv[i]; } else { + print_help(); i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[1]); } } - config_file_init(DEFAULT_CONFIG_FILE); - - if (destination != NULL) { - ret = user_init(auth_socket, destination); - if (ret != 0) - return ret; - } else if (geteuid() != 0) { + process_euid = geteuid(); + if (destination != NULL) + user = destination; + else if (process_euid != 0) { /* we're non-root. get our username. */ struct passwd *pw; - pw = getpwuid(geteuid()); + pw = getpwuid(process_euid); if (pw != NULL) - destination = t_strdup(pw->pw_name); - } - - if (destination == NULL) { + user = t_strdup(pw->pw_name); + else { + i_fatal("Couldn't lookup our username (uid=%s)", + dec2str(process_euid)); + } + } else { i_fatal_status(EX_USAGE, "destination user parameter (-d user) not given"); } + config_file_init(config_path); + open_logfile(user); + + if (destination != NULL) { + auth_socket = getenv("AUTH_SOCKET_PATH"); + if (auth_socket == NULL) + auth_socket = DEFAULT_AUTH_SOCKET_PATH; + + ret = auth_client_put_user_env(ioloop, auth_socket, + destination, process_euid); + if (ret != 0) + return ret; + + home = getenv("HOME"); + if (home != NULL) { + /* If possible chdir to home directory so core file + could be written. If it fails, don't worry. */ + (void)chdir(home); + } + } else { + destination = user; + } + + deliver_set = i_new(struct deliver_settings, 1); + deliver_set->hostname = getenv("HOSTNAME"); + if (deliver_set->hostname == NULL) + deliver_set->hostname = my_hostname; + deliver_set->postmaster_address = getenv("POSTMASTER_ADDRESS"); + if (deliver_set->postmaster_address == NULL) { + i_fatal_status(EX_CONFIG, + "postmaster_address setting not given"); + } + deliver_set->sendmail_path = getenv("SENDMAIL_PATH"); + if (deliver_set->sendmail_path == NULL) + deliver_set->sendmail_path = DEFAULT_SENDMAIL_PATH; + + dict_client_register(); + duplicate_init(); mail_storage_init(); mail_storage_register_all(); - mail = getenv("MAIL"); - if (mail == NULL) - i_fatal_status(EX_CONFIG, "mail setting not given"); - - table = get_var_expand_table(destination, getenv("HOME")); - mail = expand_mail_env(mail, table); + /* MAIL comes from userdb, DEFAULT_MAIL_ENV from dovecot.conf */ + mail_env = getenv("MAIL"); + if (mail_env == NULL) + mail_env = getenv("DEFAULT_MAIL_ENV"); + if (mail_env != NULL) { + table = get_var_expand_table(destination, getenv("HOME")); + mail_env = expand_mail_env(mail_env, table); + } str = getenv("POP3_UIDL_FORMAT"); if (str != NULL && (str = strchr(str, '%')) != NULL && str != NULL && var_get_key(str + 1) == 'm') flags |= MAIL_STORAGE_FLAG_KEEP_HEADER_MD5; + if (getenv("MAIL_PLUGINS") == NULL) + modules = NULL; + else { + const char *plugin_dir = getenv("MAIL_PLUGIN_DIR"); + + if (plugin_dir == NULL) + plugin_dir = MODULEDIR"/lda"; + modules = module_dir_load(plugin_dir, getenv("MAIL_PLUGINS"), + TRUE); + } + /* FIXME: how should we handle namespaces? */ mail_storage_parse_env(&flags, &lock_method); - storage = mail_storage_create_with_data(mail, destination, + storage = mail_storage_create_with_data(mail_env, destination, flags, lock_method); if (storage == NULL) { i_fatal_status(EX_CONFIG, "Failed to create storage for '%s' with mail '%s'", - destination, mail == NULL ? "(null)" : mail); + destination, mail_env == NULL ? "(null)" : mail_env); } - net_set_nonblock(0, TRUE); - input = i_stream_create_file(0, default_pool, 8192, FALSE); - if (save_mail(storage, mailbox, input) < 0) - return EX_TEMPFAIL; - i_stream_unref(input); + if (hook_mail_storage_created != NULL) + hook_mail_storage_created(storage); + + mbox_storage = mail_storage_create("mbox", "/tmp", destination, 0, + MAIL_STORAGE_LOCK_FCNTL); + input = create_mbox_stream(0); + box = mailbox_open(mbox_storage, "Dovecot Delivery Mail", input, + MAILBOX_OPEN_NO_INDEX_FILES); + if (box == NULL) + i_fatal("Can't open delivery mail as mbox"); + if (sync_quick(box) < 0) + i_fatal("Can't sync delivery mail"); + + t = mailbox_transaction_begin(box, 0); + mail = mail_alloc(t, 0, NULL); + if (mail_set_seq(mail, 1) < 0) + i_fatal("mail_set_seq() failed"); + + ret = deliver_mail == NULL ? 0 : + deliver_mail(storage, mail, destination, mailbox); - mail_storage_destroy(storage); - mail_storage_deinit(); + if (ret <= 0) { + /* plugins didn't handle this. save into INBOX. */ + i_stream_seek(input, 0); + if (deliver_save(storage, mailbox, mail, 0, NULL) < 0) { + const char *error; + bool syntax, temporary_error; + int ret; + + error = mail_storage_get_last_error(storage, &syntax, + &temporary_error); + if (temporary_error) + return EX_TEMPFAIL; + + /* we'll have to reply with permanent failure */ + ret = mail_send_rejection(mail, destination, error); + if (ret != 0) + return ret < 0 ? EX_TEMPFAIL : ret; + /* ok, rejection sent */ + } + } + i_stream_unref(&input); + + mail_free(&mail); + mailbox_transaction_rollback(&t); + mailbox_close(&box); + + mail_storage_destroy(&mbox_storage); + mail_storage_destroy(&storage); + + module_dir_unload(&modules); + mail_storage_deinit(); + + duplicate_deinit(); + dict_client_unregister(); lib_signals_deinit(); - io_loop_destroy(ioloop); + io_loop_destroy(&ioloop); lib_deinit(); return EX_OK;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/deliver.h Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,33 @@ +#ifndef __DELIVER_H +#define __DELIVER_H + +#include <sysexits.h> + +#include "lib.h" +#include "mail-storage.h" + +struct deliver_settings { + const char *hostname; + const char *postmaster_address; + const char *sendmail_path; +}; + +extern struct deliver_settings *deliver_set; + +typedef int deliver_mail_func_t(struct mail_storage *storage, struct mail *mail, + const char *username, const char *mailbox); + +extern deliver_mail_func_t *deliver_mail; + +/* Save a mail into given mailbox with given flags and keywords. */ +int deliver_save(struct mail_storage *storage, const char *mailbox, + struct mail *mail, enum mail_flags flags, + const char *const *keywords); + +/* Extracts user@domain from Return-Path header. Returns NULL if not found. */ +const char *deliver_get_return_address(struct mail *mail); + +/* Returns a new unique Message-ID */ +const char *deliver_get_new_message_id(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/duplicate.c Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,250 @@ +/* Copyright (C) 2005-2006 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "home-expand.h" +#include "file-dotlock.h" +#include "hash.h" +#include "duplicate.h" + +#include <fcntl.h> +#include <unistd.h> + +#define DUPLICATE_PATH "~/.dovecot.lda-dupes" +#define COMPRESS_PERCENTAGE 10 + +struct duplicate { + const void *id; + unsigned int id_size; + + const char *user; + time_t time; +}; + +struct duplicate_file { + pool_t pool; + struct hash_table *hash; + const char *path; + + int new_fd; + struct dotlock *dotlock; + unsigned int changed:1; +}; + +static struct dotlock_settings duplicate_dotlock_set = { + MEMBER(temp_prefix) NULL, + MEMBER(lock_suffix) NULL, + + MEMBER(timeout) 10, + MEMBER(stale_timeout) 60, + + MEMBER(callback) NULL, + MEMBER(context) NULL, + + MEMBER(use_excl_lock) FALSE +}; +static struct duplicate_file *duplicate_file = NULL; + +static int duplicate_cmp(const void *p1, const void *p2) +{ + const struct duplicate *d1 = p1, *d2 = p2; + + return (d1->id_size == d2->id_size && + memcmp(d1->id, d2->id, d1->id_size) == 0 && + strcasecmp(d1->user, d2->user) == 0) ? 0 : 1; +} + +static unsigned int duplicate_hash(const void *p) +{ + /* a char* hash function from ASU -- from glib */ + const struct duplicate *d = p; + const unsigned char *s = d->id, *end = s + d->id_size; + unsigned int g, h = 0; + + while (s != end) { + h = (h << 4) + *s; + if ((g = h & 0xf0000000UL)) { + h = h ^ (g >> 24); + h = h ^ g; + } + s++; + } + + return h ^ strcase_hash(d->user); +} + +static int duplicate_read(struct duplicate_file *file) +{ + int fd; + struct istream *input; + const unsigned char *data; + size_t size; + time_t stamp; + unsigned int offset, id_size, user_size, change_count; + + fd = open(file->path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) + return 0; + i_error("open(%s) failed: %m", file->path); + return -1; + } + + /* <timestamp> <id_size> <user_size> <id> <user> */ + input = i_stream_create_file(fd, default_pool, 4096, FALSE); + + change_count = 0; + while (i_stream_read_data(input, &data, &size, sizeof(stamp) + + sizeof(id_size) + sizeof(user_size)) > 0) { + offset = 0; + memcpy(&stamp, data, sizeof(stamp)); + offset += sizeof(stamp); + memcpy(&id_size, data + offset, sizeof(id_size)); + offset += sizeof(id_size); + memcpy(&user_size, data + offset, sizeof(user_size)); + offset += sizeof(user_size); + + i_stream_skip(input, offset); + + if (i_stream_read_data(input, &data, &size, + id_size + user_size - 1) <= 0) { + i_error("unexpected end of file in %s", file->path); + break; + } + + if (stamp >= ioloop_time) { + /* still valid, save it */ + struct duplicate *d; + void *new_id; + + new_id = p_malloc(file->pool, id_size); + memcpy(new_id, data, id_size); + + d = p_new(file->pool, struct duplicate, 1); + d->id = new_id; + d->id_size = id_size; + d->user = p_strndup(file->pool, + data + id_size, user_size); + hash_insert(file->hash, d, d); + } else { + change_count++; + } + i_stream_skip(input, id_size + user_size); + } + + if (hash_size(file->hash) * COMPRESS_PERCENTAGE / 100 > change_count) + file->changed = TRUE; + + i_stream_unref(&input); + if (close(fd) < 0) + i_error("close(%s) failed: %m", file->path); + return 0; +} + +static struct duplicate_file *duplicate_new(const char *path) +{ + struct duplicate_file *file; + pool_t pool; + + pool = pool_alloconly_create("duplicates", 10240); + + file = p_new(pool, struct duplicate_file, 1); + file->pool = pool; + file->path = p_strdup(pool, path); + file->new_fd = file_dotlock_open(&duplicate_dotlock_set, path, 0, + &file->dotlock); + file->hash = hash_create(default_pool, pool, 0, + duplicate_hash, duplicate_cmp); + (void)duplicate_read(file); + return file; +} + +static void duplicate_free(struct duplicate_file *file) +{ + if (file->dotlock != NULL) + file_dotlock_delete(&file->dotlock); + + hash_destroy(file->hash); + pool_unref(file->pool); +} + +int duplicate_check(const void *id, size_t id_size, const char *user) +{ + struct duplicate d; + + if (duplicate_file == NULL) + duplicate_file = duplicate_new(home_expand(DUPLICATE_PATH)); + + d.id = id; + d.id_size = id_size; + d.user = user; + + return hash_lookup(duplicate_file->hash, &d) != NULL; +} + +void duplicate_mark(const void *id, size_t id_size, + const char *user, time_t time) +{ + struct duplicate *d; + void *new_id; + + if (duplicate_file == NULL) + duplicate_file = duplicate_new(home_expand(DUPLICATE_PATH)); + + new_id = p_malloc(duplicate_file->pool, id_size); + memcpy(new_id, id, id_size); + + d = p_new(duplicate_file->pool, struct duplicate, 1); + d->id = new_id; + d->id_size = id_size; + d->user = p_strdup(duplicate_file->pool, user); + d->time = time; + + duplicate_file->changed = TRUE; + hash_insert(duplicate_file->hash, d, d); +} + +void duplicate_flush(void) +{ + struct duplicate_file *file = duplicate_file; + struct ostream *output; + struct hash_iterate_context *iter; + void *key, *value; + + if (duplicate_file == NULL || !file->changed || file->new_fd == -1) + return; + + output = o_stream_create_file(file->new_fd, default_pool, 4096, FALSE); + iter = hash_iterate_init(file->hash); + while (hash_iterate(iter, &key, &value)) { + struct duplicate *d = value; + unsigned int user_size = strlen(d->user); + + o_stream_send(output, &d->time, sizeof(d->time)); + o_stream_send(output, &d->id_size, sizeof(d->id_size)); + o_stream_send(output, &user_size, sizeof(user_size)); + o_stream_send(output, d->id, d->id_size); + o_stream_send(output, d->user, user_size); + } + hash_iterate_deinit(iter); + o_stream_unref(&output); + + file->changed = FALSE; + if (file_dotlock_replace(&file->dotlock, 0) < 0) + i_error("file_dotlock_replace(%s) failed: %m", file->path); + file->new_fd = -1; +} + +void duplicate_init(void) +{ +} + +void duplicate_deinit(void) +{ + if (duplicate_file != NULL) { + duplicate_flush(); + duplicate_free(duplicate_file); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/duplicate.h Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,15 @@ +#ifndef __DUPLICATE_H +#define __DUPLICATE_H + +#define DUPLICATE_DEFAULT_KEEP (3600 * 24) + +int duplicate_check(const void *id, size_t id_size, const char *user); +void duplicate_mark(const void *id, size_t id_size, + const char *user, time_t time); + +void duplicate_flush(void); + +void duplicate_init(void); +void duplicate_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/mail-send.c Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,142 @@ +/* Copyright (C) 2005-2006 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "hostpid.h" +#include "istream.h" +#include "message-date.h" +#include "message-size.h" +#include "duplicate.h" +#include "istream-header-filter.h" +#include "smtp-client.h" +#include "deliver.h" +#include "mail-send.h" + +#include <sys/wait.h> + +#define MAIL_REJECTION_HUMAN_REASON \ +"Your message was automatically rejected by Dovecot Mail Delivery Agent.\r\n" \ +"\r\n" \ +"The following reason was given:\r\n" \ +"%s\r\n" + +int global_outgoing_count = 0; + +int mail_send_rejection(struct mail *mail, const char *recipient, + const char *reason) +{ + struct istream *input; + struct smtp_client *smtp_client; + FILE *f; + struct message_size hdr_size; + const char *return_addr, *str; + const unsigned char *data; + const char *msgid, *boundary; + size_t size; + int ret; + + return_addr = deliver_get_return_address(mail); + if (return_addr == NULL) { + i_info("Return-Path missing, rejection reason: %s", reason); + return -1; + } + + smtp_client = smtp_client_open(return_addr, NULL, &f); + + msgid = deliver_get_new_message_id(); + duplicate_mark(msgid, strlen(msgid), recipient, + ioloop_time + DUPLICATE_DEFAULT_KEEP); + boundary = t_strdup_printf("%s/%s", my_pid, deliver_set->hostname); + + fprintf(f, "Message-ID: %s\r\n", msgid); + fprintf(f, "Date: %s\r\n", message_date_create(ioloop_time)); + fprintf(f, "From: Mail Delivery Subsystem <%s>\r\n", + deliver_set->postmaster_address); + fprintf(f, "To: <%s>\r\n", return_addr); + fprintf(f, "MIME-Version: 1.0\r\n"); + fprintf(f, "Content-Type: " + "multipart/report; report-type=disposition-notification;\r\n" + "\tboundary=\"%s\"\r\n", boundary); + fprintf(f, "Subject: Automatically rejected mail\r\n"); + fprintf(f, "Auto-Submitted: auto-replied (rejected)\r\n"); + fprintf(f, "Precedence: bulk\r\n"); + fprintf(f, "\r\nThis is a MIME-encapsulated message\r\n\r\n"); + + /* human readable status report */ + fprintf(f, "--%s\r\n", boundary); + fprintf(f, "Content-Type: text/plain; charset=utf-8\r\n"); + fprintf(f, "Content-Disposition: inline\r\n"); + fprintf(f, "Content-Transfer-Encoding: 8bit\r\n\r\n"); + fprintf(f, MAIL_REJECTION_HUMAN_REASON"\r\n", reason); + + /* MDN status report */ + fprintf(f, "--%s\r\n" + "Content-Type: message/disposition-notification\r\n\r\n", + boundary); + fprintf(f, "Reporting-UA: %s; Dovecot Mail Delivery Agent\r\n", + deliver_set->hostname); + str = mail_get_first_header(mail, "Original-Recipient"); + if (str != NULL) + fprintf(f, "Original-Recipient: rfc822; %s\r\n", str); + fprintf(f, "Final-Recipient: rfc822; %s\r\n", recipient); + + str = mail_get_first_header(mail, "Message-ID"); + if (str != NULL) + fprintf(f, "Original-Message-ID: %s\r\n", str); + fprintf(f, "Disposition: " + "automatic-action/MDN-sent-automatically; deleted\r\n"); + fprintf(f, "\r\n"); + + /* original message's headers */ + fprintf(f, "--%s\r\nContent-Type: message/rfc822\r\n\r\n", boundary); + + input = mail_get_stream(mail, &hdr_size, NULL); + if (input != NULL) { + input = i_stream_create_limit(default_pool, input, + 0, hdr_size.physical_size); + while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) { + fwrite(data, size, 1, f); + i_stream_skip(input, size); + } + i_stream_unref(&input); + + i_assert(ret != 0); + } + + fprintf(f, "\r\n\r\n--%s--\r\n", boundary); + return smtp_client_close(smtp_client); +} + +int mail_send_forward(struct mail *mail, const char *forwardto) +{ + static const char *hide_headers[] = { + "Return-Path" + }; + struct istream *input; + struct smtp_client *smtp_client; + FILE *f; + const unsigned char *data; + size_t size; + int ret; + + input = mail_get_stream(mail, NULL, NULL); + if (input == NULL) + return -1; + + smtp_client = smtp_client_open(forwardto, + mail_get_first_header(mail, "Return-Path"), + &f); + + input = i_stream_create_header_filter(input, HEADER_FILTER_EXCLUDE | + HEADER_FILTER_NO_CR, hide_headers, + sizeof(hide_headers) / + sizeof(hide_headers[0]), NULL, NULL); + + while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) { + fwrite(data, size, 1, f); + i_stream_skip(input, size); + } + + return smtp_client_close(smtp_client); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/mail-send.h Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,10 @@ +#ifndef __MAIL_SEND_H +#define __MAIL_SEND_H + +struct mail; + +int mail_send_rejection(struct mail *mail, const char *recipient, + const char *reason); +int mail_send_forward(struct mail *mail, const char *forwardto); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/smtp-client.c Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,107 @@ +/* Copyright (C) 2006 Timo Sirainen */ + +#include "lib.h" +#include "deliver.h" +#include "smtp-client.h" + +#include <unistd.h> +#include <sys/wait.h> + +struct smtp_client { + FILE *f; + pid_t pid; +}; + +static struct smtp_client *smtp_client_devnull(FILE **file_r) +{ + struct smtp_client *client; + + client = i_new(struct smtp_client, 1); + client->f = *file_r = fopen("/dev/null", "w"); + if (client->f == NULL) + i_fatal("fopen() failed: %m"); + client->pid = (pid_t)-1; + return client; +} + +static void smtp_client_run_sendmail(const char *destination, + const char *return_path, int fd) +{ + const char *argv[7]; + + argv[0] = deliver_set->sendmail_path; + argv[1] = "-i"; /* ignore dots */ + argv[2] = "-f"; + argv[3] = return_path != NULL && *return_path != '\0' ? + return_path : "<>"; + argv[4] = "--"; + argv[5] = destination; + argv[6] = NULL; + + if (dup2(fd, STDIN_FILENO) < 0) + i_fatal("dup2() failed: %m"); + + (void)execv(deliver_set->sendmail_path, (char **)argv); + i_fatal("execv(%s) failed: %m", deliver_set->sendmail_path); +} + +struct smtp_client *smtp_client_open(const char *destination, + const char *return_path, FILE **file_r) +{ + struct smtp_client *client; + int fd[2]; + pid_t pid; + + if (pipe(fd) < 0) { + i_error("pipe() failed: %m"); + return smtp_client_devnull(file_r); + } + + if ((pid = fork()) == (pid_t)-1) { + i_error("fork() failed: %m"); + (void)close(fd[0]); (void)close(fd[1]); + return smtp_client_devnull(file_r); + } + if (pid == 0) { + /* child */ + (void)close(fd[1]); + smtp_client_run_sendmail(destination, return_path, fd[0]); + } + (void)close(fd[0]); + + client = i_new(struct smtp_client, 1); + client->f = *file_r = fdopen(fd[1], "w"); + if (client->f == NULL) + i_fatal("fdopen() failed: %m"); + return client; +} + +int smtp_client_close(struct smtp_client *client) +{ + int ret = EX_TEMPFAIL, status; + + fclose(client->f); + if (client->pid == (pid_t)-1) { + /* smtp_client_open() failed already */ + } else if (waitpid(client->pid, &status, 0) < 0) + i_error("waitpid() failed: %m"); + else if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + if (ret != 0) { + i_error("Sendmail process terminated abnormally, " + "exit status %d", ret); + } + } else if (WIFSIGNALED(status)) { + i_error("Sendmail process terminated abnormally, " + "signal %d", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + i_error("Sendmail process stopped, signal %d", + WSTOPSIG(status)); + } else { + i_error("Sendmail process terminated abnormally, " + "return status %d", status); + } + + i_free(client); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/deliver/smtp-client.h Fri Jun 16 12:30:07 2006 +0300 @@ -0,0 +1,11 @@ +#ifndef __SMTP_CLIENT_H +#define __SMTP_CLIENT_H + +#include <stdio.h> + +struct smtp_client *smtp_client_open(const char *destination, + const char *return_path, FILE **file_r); +/* Returns sysexits-compatible return value */ +int smtp_client_close(struct smtp_client *client); + +#endif