Mercurial > dovecot > original-hg > dovecot-1.2
diff src/deliver/deliver.c @ 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 | 545a747d0347 |
children | 13350093fa1b |
line wrap: on
line diff
--- 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;