Mercurial > dovecot > original-hg > dovecot-1.2
view src/deliver/deliver.c @ 9423:c50acaf1f09f HEAD
deliver: Don't escape addresses with "." in local-part.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 12 Oct 2009 12:36:38 -0400 |
parents | 72a8063cfde0 |
children | f5ce7d271b56 |
line wrap: on
line source
/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ /* This is getting pretty horrible. Especially the config file parsing. Dovecot v2.0 should have a config file handling process which should help with this.. */ #include "lib.h" #include "lib-signals.h" #include "file-lock.h" #include "array.h" #include "ioloop.h" #include "hostpid.h" #include "home-expand.h" #include "env-util.h" #include "fd-set-nonblock.h" #include "istream.h" #include "istream-seekable.h" #include "module-dir.h" #include "str.h" #include "str-sanitize.h" #include "strescape.h" #include "safe-mkstemp.h" #include "mkdir-parents.h" #include "eacces-error.h" #include "close-keep-errno.h" #include "var-expand.h" #include "rfc822-parser.h" #include "message-address.h" #include "mail-namespace.h" #include "raw-storage.h" #include "imap-utf7.h" #include "dict.h" #include "auth-client.h" #include "mail-send.h" #include "duplicate.h" #include "mbox-from.h" #include "../master/syslog-util.h" #include "../master/syslog-util.c" /* ugly, ugly.. */ #include "deliver.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <pwd.h> #include <syslog.h> #define DEFAULT_CONFIG_FILE SYSCONFDIR"/dovecot.conf" #define DEFAULT_SENDMAIL_PATH "/usr/lib/sendmail" #define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON" /* After buffer grows larger than this, create a temporary file to /tmp where to read the mail. */ #define MAIL_MAX_MEMORY_BUFFER (1024*128) static const char *wanted_headers[] = { "From", "Message-ID", "Subject", "Return-Path", NULL }; struct deliver_settings *deliver_set; deliver_mail_func_t *deliver_mail = NULL; bool tried_default_save = FALSE; /* FIXME: these two should be in some context struct instead of as globals.. */ static const char *default_mailbox_name = NULL; static bool saved_mail = FALSE; static char *explicit_envelope_sender = NULL; static struct module *modules; static struct ioloop *ioloop; static pool_t plugin_pool; static ARRAY_DEFINE(lda_envs, const char *); static ARRAY_DEFINE(plugin_envs, const char *); static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) { /* warn about being killed because of some signal, except SIGINT (^C) which is too common at least while testing :) */ if (si->si_signo != SIGINT) { i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)", si->si_signo, dec2str(si->si_pid), dec2str(si->si_uid), lib_signal_code_to_str(si->si_signo, si->si_code)); } io_loop_stop(current_ioloop); } static const char *deliver_get_address(struct mail *mail, const char *header) { struct message_address *addr; const char *str; if (mail_get_first_header(mail, header, &str) <= 0) return NULL; addr = message_address_parse(pool_datastack_create(), (const unsigned char *)str, strlen(str), 1, FALSE); return addr == NULL || addr->mailbox == NULL || addr->domain == NULL || *addr->mailbox == '\0' || *addr->domain == '\0' ? NULL : t_strconcat(addr->mailbox, "@", addr->domain, NULL); } static const struct var_expand_table * get_log_var_expand_table(struct mail *mail, const char *message) { static struct var_expand_table static_tab[] = { { '$', NULL, NULL }, { 'm', NULL, "msgid" }, { 's', NULL, "subject" }, { 'f', NULL, "from" }, { '\0', NULL, NULL } }; struct var_expand_table *tab; unsigned int i; tab = t_malloc(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = message; (void)mail_get_first_header(mail, "Message-ID", &tab[1].value); (void)mail_get_first_header_utf8(mail, "Subject", &tab[2].value); tab[3].value = deliver_get_address(mail, "From"); for (i = 1; tab[i].key != '\0'; i++) tab[i].value = str_sanitize(tab[i].value, 80); return tab; } static void deliver_log(struct mail *mail, const char *fmt, ...) ATTR_FORMAT(2, 3); static void deliver_log(struct mail *mail, const char *fmt, ...) { va_list args; string_t *str; const char *msg; va_start(args, fmt); msg = t_strdup_vprintf(fmt, args); str = t_str_new(256); var_expand(str, deliver_set->log_format, get_log_var_expand_table(mail, msg)); i_info("%s", str_c(str)); va_end(args); } static struct mailbox * mailbox_open_or_create_synced(struct mail_namespace *namespaces, struct mail_storage **storage_r, const char *name) { struct mail_namespace *ns; struct mailbox *box; enum mail_error error; enum mailbox_open_flags open_flags = MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT | MAILBOX_OPEN_SAVEONLY | MAILBOX_OPEN_POST_SESSION; if (strcasecmp(name, "INBOX") == 0) { /* deliveries to INBOX must always succeed, regardless of ACLs */ open_flags |= MAILBOX_OPEN_IGNORE_ACLS; } ns = mail_namespace_find(namespaces, &name); if (ns == NULL) { *storage_r = NULL; return NULL; } *storage_r = ns->storage; if (*name == '\0') { /* delivering to a namespace prefix means we actually want to deliver to the INBOX instead */ return NULL; } box = mailbox_open(storage_r, name, NULL, open_flags); if (box != NULL || !deliver_set->mailbox_autocreate) return box; (void)mail_storage_get_last_error(*storage_r, &error); if (error != MAIL_ERROR_NOTFOUND) return NULL; /* try creating it. */ if (mail_storage_mailbox_create(*storage_r, name, FALSE) < 0) return NULL; if (deliver_set->mailbox_autosubscribe) { /* (try to) subscribe to it */ (void)mailbox_list_set_subscribed(ns->list, name, TRUE); } /* and try opening again */ box = mailbox_open(storage_r, name, NULL, open_flags); if (box == NULL) return NULL; if (mailbox_sync(box, 0, 0, NULL) < 0) { mailbox_close(&box); return NULL; } return box; } int deliver_save(struct mail_namespace *namespaces, struct mail_storage **storage_r, const char *mailbox, struct mail *mail, enum mail_flags flags, const char *const *keywords) { struct mailbox *box; struct mailbox_transaction_context *t; struct mail_save_context *save_ctx; struct mail_keywords *kw; enum mail_error error; const char *mailbox_name; bool default_save; int ret = 0; default_save = strcmp(mailbox, default_mailbox_name) == 0; if (default_save) tried_default_save = TRUE; mailbox_name = str_sanitize(mailbox, 80); box = mailbox_open_or_create_synced(namespaces, storage_r, mailbox); if (box == NULL) { if (*storage_r == NULL) { deliver_log(mail, "save failed to %s: Unknown namespace", mailbox_name); return -1; } if (default_save && strcmp((*storage_r)->ns->prefix, mailbox) == 0) { /* silently store to the INBOX instead */ return -1; } deliver_log(mail, "save failed to %s: %s", mailbox_name, mail_storage_get_last_error(*storage_r, &error)); return -1; } t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL); kw = str_array_length(keywords) == 0 ? NULL : mailbox_keywords_create_valid(box, keywords); save_ctx = mailbox_save_alloc(t); mailbox_save_set_flags(save_ctx, flags, kw); if (mailbox_copy(&save_ctx, mail) < 0) ret = -1; mailbox_keywords_free(box, &kw); if (ret < 0) mailbox_transaction_rollback(&t); else ret = mailbox_transaction_commit(&t); if (ret == 0) { saved_mail = TRUE; deliver_log(mail, "saved mail to %s", mailbox_name); } else { deliver_log(mail, "save failed to %s: %s", mailbox_name, mail_storage_get_last_error(*storage_r, &error)); } mailbox_close(&box); return ret; } const char *deliver_get_return_address(struct mail *mail) { if (explicit_envelope_sender != NULL) return explicit_envelope_sender; return deliver_get_address(mail, "Return-Path"); } const char *deliver_get_new_message_id(void) { static int count = 0; return t_strdup_printf("<dovecot-%s-%s-%d@%s>", dec2str(ioloop_timeval.tv_sec), dec2str(ioloop_timeval.tv_usec), count++, deliver_set->hostname); } #include "settings.h" #include "../master/master-settings.h" #include "../master/master-settings-defs.c" #define IS_WHITE(c) ((c) == ' ' || (c) == '\t') static bool setting_is_bool(const char *name) { const struct setting_def *def; for (def = setting_defs; def->name != NULL; def++) { if (strcmp(def->name, name) == 0) return def->type == SET_BOOL; } if (strncmp(name, "NAMESPACE_", 10) == 0) { return strstr(name, "_list") != NULL || strstr(name, "_inbox") != NULL || strstr(name, "_hidden") != NULL || strstr(name, "_subscriptions") != NULL; } if (strcmp(name, "quota_full_tempfail") == 0) return TRUE; return FALSE; } /* more ugly kludging because we have our own config parsing code. hopefully this goes away in v1.2. */ static struct { const char *name; bool set; } default_yes_settings[] = { { "dotlock_use_excl", TRUE }, { "maildir_copy_with_hardlinks", TRUE }, { "mbox_dirty_syncs", TRUE }, { "mbox_lazy_writes", TRUE } }; static void config_file_init(const char *path) { struct istream *input; const char *key, *value, *str, *ukey; char *line, *p, quote; int fd, sections = 0; bool lda_section = FALSE, pop3_section = FALSE, plugin_section = FALSE; bool ns_section = FALSE, ns_location = FALSE, ns_list = FALSE; bool ns_subscriptions = FALSE; unsigned int i, ns_idx = 0; size_t len; plugin_pool = pool_alloconly_create("Plugin strings", 512); i_array_init(&lda_envs, 16); i_array_init(&plugin_envs, 16); fd = open(path, O_RDONLY); if (fd < 0) i_fatal_status(EX_TEMPFAIL, "open(%s) failed: %m", path); input = i_stream_create_fd(fd, (size_t)-1, TRUE); i_stream_set_return_partial_line(input, TRUE); while ((line = i_stream_read_next_line(input)) != NULL) { /* @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'; if (strncmp(line, "!include_try ", 13) == 0) continue; if (strncmp(line, "!include ", 9) == 0) { i_fatal_status(EX_TEMPFAIL, "Error in config file %s: " "deliver doesn't support !include directive", path); } value = p = strchr(line, '='); if (value == NULL) { if (strchr(line, '{') != NULL) { if (strcmp(line, "protocol lda {") == 0) lda_section = TRUE; else if (strcmp(line, "plugin {") == 0) plugin_section = TRUE; else if (strcmp(line, "protocol pop3 {") == 0) pop3_section = TRUE; else if (strncmp(line, "namespace ", 10) == 0) { ns_section = TRUE; ns_idx++; line += 10; env_put(t_strdup_printf( "NAMESPACE_%u_TYPE=%s", ns_idx, t_strcut(line, ' '))); } sections++; } if (*line == '}') { sections--; lda_section = FALSE; plugin_section = FALSE; pop3_section = FALSE; if (ns_section) { ns_section = FALSE; if (ns_location) ns_location = FALSE; else { env_put(t_strdup_printf( "NAMESPACE_%u=", ns_idx)); } if (ns_list) ns_list = FALSE; else { env_put(t_strdup_printf( "NAMESPACE_%u_LIST=1", ns_idx)); } if (ns_subscriptions) ns_subscriptions = FALSE; else { env_put(t_strdup_printf( "NAMESPACE_%u_SUBSCRIPTIONS=1", ns_idx)); } } } continue; } while (p > line && IS_WHITE(p[-1])) p--; key = t_strdup_until(line, p); if (sections > 0 && !lda_section && !plugin_section) { if (pop3_section) { if (strcmp(key, "pop3_uidl_format") != 0) continue; } else if (ns_section) { if (strcmp(key, "separator") == 0) { key = t_strdup_printf( "NAMESPACE_%u_SEP", ns_idx); } else if (strcmp(key, "location") == 0) { ns_location = TRUE; key = t_strdup_printf("NAMESPACE_%u", ns_idx); } else { if (strcmp(key, "list") == 0) ns_list = TRUE; if (strcmp(key, "subscriptions") == 0) ns_subscriptions = TRUE; key = t_strdup_printf("NAMESPACE_%u_%s", ns_idx, key); } } else { /* unwanted section */ continue; } } do { value++; } while (IS_WHITE(*value)); len = strlen(value); if (len > 0 && ((*value == '"' && value[len-1] == '"') || (*value == '\'' && value[len-1] == '\''))) { value = str_unescape(p_strndup(unsafe_data_stack_pool, value+1, len - 2)); } ukey = t_str_ucase(key); if (setting_is_bool(key) && strcasecmp(value, "yes") != 0) { for (i = 0; i < N_ELEMENTS(default_yes_settings); i++) { if (strcmp(default_yes_settings[i].name, key) == 0) default_yes_settings[i].set = FALSE; } env_remove(ukey); continue; } if (lda_section) { str = p_strconcat(plugin_pool, ukey, "=", value, NULL); array_append(&lda_envs, &str, 1); } if (!plugin_section) { env_put(t_strconcat(ukey, "=", value, NULL)); } else { /* %variables need to be expanded. store these for later. */ value = p_strconcat(plugin_pool, ukey, "=", value, NULL); array_append(&plugin_envs, &value, 1); } } i_stream_unref(&input); for (i = 0; i < N_ELEMENTS(default_yes_settings); i++) { if (default_yes_settings[i].set) { key = default_yes_settings[i].name; env_put(t_strconcat(t_str_ucase(key), "=1", NULL)); } } } static const struct var_expand_table * get_var_expand_table(const char *user, const char *home) { static struct var_expand_table static_tab[] = { { 'u', NULL, "user" }, { 'n', NULL, "username" }, { 'd', NULL, "domain" }, { 's', NULL, "service" }, { 'h', NULL, "home" }, { 'l', NULL, "lip" }, { 'r', NULL, "rip" }, { 'p', NULL, "pid" }, { 'i', NULL, "uid" }, { '\0', NULL, NULL } }; struct var_expand_table *tab; tab = t_malloc(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = user; tab[1].value = t_strcut(user, '@'); tab[2].value = strchr(user, '@'); if (tab[2].value != NULL) tab[2].value++; tab[3].value = "DELIVER"; tab[4].value = home != NULL ? home : "/HOME_DIRECTORY_USED_BUT_NOT_GIVEN_BY_USERDB"; tab[5].value = NULL; tab[6].value = NULL; tab[7].value = my_pid; tab[8].value = dec2str(geteuid()); return tab; } static const char * expand_mail_env(const char *env, const struct var_expand_table *table) { string_t *str; const char *p; str = t_str_new(256); /* it's either type:data or just data */ p = strchr(env, ':'); if (p != NULL) { while (env != p) { str_append_c(str, *env); env++; } str_append_c(str, *env++); } if (env[0] == '~' && env[1] == '/') { /* expand home */ env = t_strconcat("%h", env+1, NULL); } /* expand %vars */ var_expand(str, env, table); return str_c(str); } static const char *escape_local_part(const char *local_part) { const char *p; /* if local_part isn't dot-atom-text, we need to return quoted-string */ for (p = local_part; *p != '\0'; p++) { if (!IS_ATEXT(*p) && *p != '.') { return t_strdup_printf("\"%s\"", str_escape(local_part)); } } return local_part; } static const char *address_sanitize(const char *address) { struct message_address *addr; const char *ret, *mailbox; pool_t pool; pool = pool_alloconly_create("address sanitizer", 256); addr = message_address_parse(pool, (const unsigned char *)address, strlen(address), 1, FALSE); if (addr == NULL || addr->mailbox == NULL || addr->domain == NULL || *addr->mailbox == '\0') ret = DEFAULT_ENVELOPE_SENDER; else { mailbox = escape_local_part(addr->mailbox); if (*addr->domain == '\0') ret = t_strdup(mailbox); else ret = t_strdup_printf("%s@%s", mailbox, addr->domain); } pool_unref(&pool); return ret; } static int deliver_create_dir(struct mail_user *user, const char *dir) { struct mail_namespace *ns; const char *origin; mode_t mode; gid_t gid; ns = mail_namespace_find_inbox(user->namespaces); if (ns == NULL) ns = user->namespaces; mailbox_list_get_dir_permissions(ns->list, NULL, &mode, &gid, &origin); if (mkdir_parents_chgrp(dir, mode, gid, origin) == 0) { return 0; } else if (errno == EACCES) { i_error("%s", eacces_error_get_creating("mkdir_parents_chown", dir)); return -1; } else { i_error("mkdir_parents_chown(%s, gid=%s) failed: %m", dir, dec2str(gid)); return -1; } } static int seekable_fd_callback(const char **path_r, void *context) { struct mail_user *user = context; const char *dir, *p; string_t *path; int fd; path = t_str_new(128); str_append(path, mail_user_get_temp_prefix(user)); fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); if (fd == -1 && errno == ENOENT) { dir = str_c(path); p = strrchr(dir, '/'); if (p != NULL) { dir = t_strdup_until(dir, p); if (deliver_create_dir(user, dir) < 0) return -1; fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); } } if (fd == -1) { i_error("safe_mkstemp(%s) failed: %m", str_c(path)); return -1; } /* we just want the fd, unlink it */ if (unlink(str_c(path)) < 0) { /* shouldn't happen.. */ i_error("unlink(%s) failed: %m", str_c(path)); close_keep_errno(fd); return -1; } *path_r = str_c(path); return fd; } static struct istream * create_raw_stream(struct mail_user *user, int fd, time_t *mtime_r) { struct istream *input, *input2, *input_list[2]; const unsigned char *data; char *sender = NULL; size_t i, size; int ret, tz; *mtime_r = (time_t)-1; fd_set_nonblock(fd, FALSE); input = i_stream_create_fd(fd, 4096, FALSE); input->blocking = TRUE; /* If input begins with a From-line, drop it */ ret = i_stream_read_data(input, &data, &size, 5); if (ret > 0 && size >= 5 && memcmp(data, "From ", 5) == 0) { /* skip until the first LF */ i_stream_skip(input, 5); while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) { for (i = 0; i < size; i++) { if (data[i] == '\n') break; } if (i != size) { (void)mbox_from_parse(data, i, mtime_r, &tz, &sender); i_stream_skip(input, i + 1); break; } i_stream_skip(input, size); } } if (sender != NULL && explicit_envelope_sender == NULL) { /* use the envelope sender from From_-line, but only if it hasn't been specified with -f already. */ explicit_envelope_sender = i_strdup(sender); } i_free(sender); if (input->v_offset == 0) { input2 = input; i_stream_ref(input2); } else { input2 = i_stream_create_limit(input, (uoff_t)-1); } i_stream_unref(&input); input_list[0] = input2; input_list[1] = NULL; input = i_stream_create_seekable(input_list, MAIL_MAX_MEMORY_BUFFER, seekable_fd_callback, user); i_stream_unref(&input2); return input; } static void failure_exit_callback(int *status) { /* we want all our exit codes to be sysexits.h compatible. if we failed because of a logging related error, we most likely aren't writing to stderr, so try writing there to give some kind of a clue what's wrong. FATAL_LOGOPEN failure already wrote to stderr, so don't duplicate it. */ switch (*status) { case FATAL_LOGWRITE: fputs("Failed to write to log file", stderr); break; case FATAL_LOGERROR: fputs("Internal logging error", stderr); break; case FATAL_LOGOPEN: case FATAL_OUTOFMEM: case FATAL_EXEC: case FATAL_DEFAULT: break; default: return; } *status = EX_TEMPFAIL; } static void open_logfile(const char *username) { const char *prefix, *log_path, *stamp; prefix = t_strdup_printf("deliver(%s): ", username); log_path = home_expand(getenv("LOG_PATH")); if (log_path == NULL || *log_path == '\0') { const char *env = getenv("SYSLOG_FACILITY"); int facility; if (env == NULL || !syslog_facility_find(env, &facility)) facility = LOG_MAIL; i_set_failure_prefix(prefix); i_set_failure_syslog("dovecot", LOG_NDELAY, facility); } else { /* log to file or stderr */ i_set_failure_file(log_path, prefix); } log_path = home_expand(getenv("INFO_LOG_PATH")); if (log_path != NULL && *log_path != '\0') i_set_info_file(log_path); stamp = getenv("LOG_TIMESTAMP"); if (stamp == NULL) stamp = DEFAULT_FAILURE_STAMP_FORMAT; i_set_failure_timestamp_format(stamp); } static void print_help(void) { printf( "Usage: deliver [-c <config file>] [-a <address>] [-d <username>] [-p <path>]\n" " [-f <envelope sender>] [-m <mailbox>] [-n] [-s] [-e] [-k]\n"); } void deliver_env_clean(bool preserve_home) { const char *tz, *home; tz = getenv("TZ"); if (tz != NULL) tz = t_strconcat("TZ=", tz, NULL); home = preserve_home ? getenv("HOME") : NULL; if (home != NULL) home = t_strconcat("HOME=", home, NULL); /* Note that if the original environment was set with env_put(), the environment strings will be invalid after env_clean(). That's why we t_strconcat() them above. */ env_clean(); if (tz != NULL) env_put(tz); if (home != NULL) env_put(home); } static void expand_envs(const char *user) { const struct var_expand_table *table; const char *value, *const *envs, *home, *env_name; unsigned int i, count; string_t *str; home = getenv("HOME"); str = t_str_new(256); table = get_var_expand_table(user, home); envs = array_get(&plugin_envs, &count); for (i = 0; i < count; i++) { str_truncate(str, 0); var_expand(str, envs[i], table); env_put(str_c(str)); } /* add LDA envs again to make sure they override plugin settings */ envs = array_get(&lda_envs, &count); for (i = 0; i < count; i++) env_put(envs[i]); /* get the table again in case plugin envs provided the home directory (yea, kludgy) */ if (home == NULL) home = getenv("HOME"); table = get_var_expand_table(user, home); value = getenv("MAIL_LOCATION"); if (value != NULL) value = expand_mail_env(value, table); env_put(t_strconcat("MAIL=", value, NULL)); for (i = 1;; i++) { env_name = t_strdup_printf("NAMESPACE_%u", i); value = getenv(env_name); if (value == NULL) break; value = expand_mail_env(value, table); env_put(t_strconcat(env_name, "=", value, NULL)); env_name = t_strdup_printf("NAMESPACE_%u_PREFIX", i); value = getenv(env_name); if (value != NULL) { str_truncate(str, 0); var_expand(str, value, table); env_put(t_strconcat(env_name, "=", str_c(str), NULL)); } } } static void putenv_extra_fields(const ARRAY_TYPE(const_string) *extra_fields) { const char *const *fields; const char *key, *p; unsigned int i, count; fields = array_get(extra_fields, &count); for (i = 0; i < count; i++) { p = strchr(fields[i], '='); if (p == NULL) env_put(t_strconcat(fields[i], "=1", NULL)); else { key = t_str_ucase(t_strdup_until(fields[i], p)); env_put(t_strconcat(key, p, NULL)); } } } int main(int argc, char *argv[]) { const char *config_path = DEFAULT_CONFIG_FILE; const char *mailbox = "INBOX"; const char *auth_socket; const char *home, *destaddr, *user, *value, *errstr, *path, *orig_user; ARRAY_TYPE(const_string) extra_fields = ARRAY_INIT; struct mail_user *mail_user, *raw_mail_user; struct mail_namespace *raw_ns; struct mail_storage *storage; struct mailbox *box; struct raw_mailbox *raw_box; struct istream *input; struct mailbox_transaction_context *t; struct mailbox_header_lookup_ctx *headers_ctx; struct mail *mail; char cwd[PATH_MAX]; uid_t process_euid; bool stderr_rejection = FALSE; bool keep_environment = FALSE; bool user_auth = FALSE; time_t mtime; int i, ret; pool_t userdb_pool = NULL; string_t *str; enum mail_error error; if (getuid() != geteuid() && geteuid() == 0) { /* running setuid - don't allow this if deliver is executable by anyone */ struct stat st; if (stat(argv[0], &st) < 0) { fprintf(stderr, "stat(%s) failed: %s\n", argv[0], strerror(errno)); return EX_TEMPFAIL; } else if ((st.st_mode & 1) != 0) { fprintf(stderr, "%s must not be both world-executable " "and setuid-root. This allows root exploits. " "See http://wiki.dovecot.org/LDA#multipleuids\n", argv[0]); return EX_TEMPFAIL; } } i_set_failure_exit_callback(failure_exit_callback); lib_init(); ioloop = io_loop_create(); lib_signals_init(); lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL); lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL); lib_signals_ignore(SIGPIPE, TRUE); lib_signals_ignore(SIGALRM, FALSE); #ifdef SIGXFSZ lib_signals_ignore(SIGXFSZ, TRUE); #endif deliver_set = i_new(struct deliver_settings, 1); deliver_set->mailbox_autocreate = TRUE; destaddr = user = path = NULL; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-a") == 0) { /* destination address */ i++; if (i == argc) i_fatal_status(EX_USAGE, "Missing -a argument"); destaddr = argv[i]; } else if (strcmp(argv[i], "-d") == 0) { /* destination user */ i++; if (i == argc) i_fatal_status(EX_USAGE, "Missing -d argument"); user = argv[i]; user_auth = TRUE; } else if (strcmp(argv[i], "-p") == 0) { /* input path */ i++; if (i == argc) i_fatal_status(EX_USAGE, "Missing -p argument"); path = argv[i]; if (*path != '/') { /* expand relative paths before we chdir */ if (getcwd(cwd, sizeof(cwd)) == NULL) i_fatal("getcwd() failed: %m"); path = t_strconcat(cwd, "/", path, NULL); } } else if (strcmp(argv[i], "-e") == 0) { stderr_rejection = TRUE; } else if (strcmp(argv[i], "-c") == 0) { /* config file path */ i++; if (i == argc) { i_fatal_status(EX_USAGE, "Missing config file path argument"); } config_path = argv[i]; } else if (strcmp(argv[i], "-k") == 0) { keep_environment = TRUE; } else if (strcmp(argv[i], "-m") == 0) { /* destination mailbox */ i++; if (i == argc) i_fatal_status(EX_USAGE, "Missing -m argument"); /* Ignore -m "". This allows doing -m ${extension} in Postfix to handle user+mailbox */ if (*argv[i] != '\0') { str = t_str_new(256); if (imap_utf8_to_utf7(argv[i], str) < 0) { i_fatal("Mailbox name not UTF-8: %s", mailbox); } mailbox = str_c(str); } } else if (strcmp(argv[i], "-n") == 0) { deliver_set->mailbox_autocreate = FALSE; } else if (strcmp(argv[i], "-s") == 0) { deliver_set->mailbox_autosubscribe = TRUE; } else if (strcmp(argv[i], "-f") == 0) { /* envelope sender address */ i++; if (i == argc) i_fatal_status(EX_USAGE, "Missing -f argument"); explicit_envelope_sender = i_strdup(address_sanitize(argv[i])); } else if (argv[i][0] != '\0') { print_help(); i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[i]); } } if (user == NULL) user = getenv("USER"); if (!keep_environment) deliver_env_clean(!user_auth); process_euid = geteuid(); if (user_auth) ; else if (process_euid != 0) { /* we're non-root. get our username and possibly our home. */ struct passwd *pw; home = getenv("HOME"); if (user != NULL && home != NULL) { /* no need for a pw lookup */ } else if ((pw = getpwuid(process_euid)) != NULL) { user = t_strdup(pw->pw_name); if (home == NULL) env_put(t_strconcat("HOME=", pw->pw_dir, NULL)); } else if (user == NULL) { i_fatal_status(EX_USAGE, "Couldn't lookup our username (uid=%s)", dec2str(process_euid)); } } else { i_fatal_status(EX_USAGE, "destination user parameter (-d user) not given"); } T_BEGIN { config_file_init(config_path); } T_END; open_logfile(user); if (getenv("MAIL_DEBUG") != NULL) env_put("DEBUG=1"); if (getenv("MAIL_PLUGINS") == NULL) modules = NULL; else { const char *plugin_dir = getenv("MAIL_PLUGIN_DIR"); const char *version; if (plugin_dir == NULL) plugin_dir = MODULEDIR"/lda"; version = getenv("VERSION_IGNORE") != NULL ? NULL : PACKAGE_VERSION; modules = module_dir_load(plugin_dir, getenv("MAIL_PLUGINS"), TRUE, version); } if (user_auth) { auth_socket = getenv("AUTH_SOCKET_PATH"); if (auth_socket == NULL) { const char *base_dir = getenv("BASE_DIR"); if (base_dir == NULL) base_dir = PKG_RUNDIR; auth_socket = t_strconcat(base_dir, "/auth-master", NULL); } userdb_pool = pool_alloconly_create("userdb lookup replys", 512); orig_user = user; ret = auth_client_lookup_and_restrict(auth_socket, &user, process_euid, userdb_pool, &extra_fields); if (ret != 0) return ret; if (strcmp(user, orig_user) != 0) { /* auth lookup changed the user. */ if (getenv("DEBUG") != NULL) i_info("userdb changed username to %s", user); i_set_failure_prefix(t_strdup_printf("deliver(%s): ", user)); } /* if user was changed, it was allocated from userdb_pool which we'll free soon. */ user = t_strdup(user); } expand_envs(user); if (userdb_pool != NULL) { putenv_extra_fields(&extra_fields); pool_unref(&userdb_pool); } /* Fix namespaces with empty locations */ for (i = 1;; i++) { value = getenv(t_strdup_printf("NAMESPACE_%u", i)); if (value == NULL) break; if (*value == '\0') { env_put(t_strdup_printf("NAMESPACE_%u=%s", i, getenv("MAIL"))); } } /* If possible chdir to home directory, so that core file could be written in case we crash. */ home = getenv("HOME"); if (home != NULL) { if (chdir(home) < 0) { if (errno != ENOENT) i_error("chdir(%s) failed: %m", home); else if (getenv("DEBUG") != NULL) i_info("Home dir not found: %s", home); } } env_put(t_strconcat("USER=", user, NULL)); (void)umask(0077); 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_TEMPFAIL, "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; deliver_set->rejection_subject = getenv("REJECTION_SUBJECT"); if (deliver_set->rejection_subject == NULL) deliver_set->rejection_subject = DEFAULT_MAIL_REJECTION_SUBJECT; deliver_set->rejection_reason = getenv("REJECTION_REASON"); if (deliver_set->rejection_reason == NULL) { deliver_set->rejection_reason = DEFAULT_MAIL_REJECTION_HUMAN_REASON; } deliver_set->log_format = getenv("DELIVER_LOG_FORMAT"); if (deliver_set->log_format == NULL) deliver_set->log_format = DEFAULT_LOG_FORMAT; dict_drivers_register_builtin(); duplicate_init(); mail_users_init(getenv("AUTH_SOCKET_PATH"), getenv("DEBUG") != NULL); mail_storage_init(); mail_storage_register_all(); mailbox_list_register_all(); module_dir_init(modules); mail_user = mail_user_init(user); mail_user_set_home(mail_user, home); if (mail_namespaces_init(mail_user) < 0) i_fatal("Namespace initialization failed"); /* create a separate mail user for the internal namespace */ raw_mail_user = mail_user_init(user); mail_user_set_home(raw_mail_user, NULL); raw_ns = mail_namespaces_init_empty(raw_mail_user); raw_ns->flags |= NAMESPACE_FLAG_NOQUOTA | NAMESPACE_FLAG_NOACL; if (mail_storage_create(raw_ns, "raw", "/tmp", MAIL_STORAGE_FLAG_FULL_FS_ACCESS, FILE_LOCK_METHOD_FCNTL, &errstr) < 0) i_fatal("Couldn't create internal raw storage: %s", errstr); if (path == NULL) { input = create_raw_stream(mail_user, 0, &mtime); box = mailbox_open(&raw_ns->storage, "Dovecot Delivery Mail", input, MAILBOX_OPEN_NO_INDEX_FILES); i_stream_unref(&input); } else { mtime = (time_t)-1; box = mailbox_open(&raw_ns->storage, path, NULL, MAILBOX_OPEN_NO_INDEX_FILES); } if (box == NULL) { i_fatal("Can't open delivery mail as raw: %s", mail_storage_get_last_error(raw_ns->storage, &error)); } if (mailbox_sync(box, 0, 0, NULL) < 0) { i_fatal("Can't sync delivery mail: %s", mail_storage_get_last_error(raw_ns->storage, &error)); } raw_box = (struct raw_mailbox *)box; raw_box->envelope_sender = explicit_envelope_sender != NULL ? explicit_envelope_sender : DEFAULT_ENVELOPE_SENDER; raw_box->mtime = mtime; t = mailbox_transaction_begin(box, 0); headers_ctx = mailbox_header_lookup_init(box, wanted_headers); mail = mail_alloc(t, 0, headers_ctx); mail_set_seq(mail, 1); if (destaddr == NULL) { destaddr = deliver_get_address(mail, "Envelope-To"); if (destaddr == NULL) { destaddr = strchr(user, '@') != NULL ? user : t_strconcat(user, "@", deliver_set->hostname, NULL); } } storage = NULL; default_mailbox_name = mailbox; if (deliver_mail == NULL) ret = -1; else { if (deliver_mail(mail_user->namespaces, &storage, mail, destaddr, mailbox) <= 0) { /* if message was saved, don't bounce it even though the script failed later. */ ret = saved_mail ? 0 : -1; } else { /* success. message may or may not have been saved. */ ret = 0; } } if (ret < 0 && !tried_default_save) { /* plugins didn't handle this. save into the default mailbox. */ ret = deliver_save(mail_user->namespaces, &storage, mailbox, mail, 0, NULL); } if (ret < 0 && strcasecmp(mailbox, "INBOX") != 0) { /* still didn't work. try once more to save it to INBOX. */ ret = deliver_save(mail_user->namespaces, &storage, "INBOX", mail, 0, NULL); } if (ret < 0 ) { const char *error_string; enum mail_error error; if (storage == NULL) { /* This shouldn't happen */ i_error("BUG: Saving failed for unknown storage"); return EX_TEMPFAIL; } error_string = mail_storage_get_last_error(storage, &error); if (stderr_rejection) { /* write to stderr also for tempfails so that MTA can log the reason if it wants to. */ fprintf(stderr, "%s\n", error_string); } if (error != MAIL_ERROR_NOSPACE || getenv("QUOTA_FULL_TEMPFAIL") != NULL) { /* Saving to INBOX should always work unless we're over quota. If it didn't, it's probably a configuration problem. */ return EX_TEMPFAIL; } /* we'll have to reply with permanent failure */ deliver_log(mail, "rejected: %s", str_sanitize(error_string, 512)); if (stderr_rejection) return EX_NOPERM; ret = mail_send_rejection(mail, user, error_string); if (ret != 0) return ret < 0 ? EX_TEMPFAIL : ret; /* ok, rejection sent */ } i_free(explicit_envelope_sender); mail_free(&mail); mailbox_header_lookup_unref(&headers_ctx); mailbox_transaction_rollback(&t); mailbox_close(&box); mail_user_unref(&mail_user); mail_user_unref(&raw_mail_user); module_dir_unload(&modules); mail_storage_deinit(); mail_users_deinit(); duplicate_deinit(); dict_drivers_unregister_builtin(); lib_signals_deinit(); io_loop_destroy(&ioloop); lib_deinit(); return EX_OK; }