Mercurial > dovecot > core-2.2
diff src/lda/main.c @ 9092:e4832f128738 HEAD
Renamed deliver to dovecot-lda and moved most of its code to lib-lda.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 13 Apr 2009 15:45:17 -0400 |
parents | |
children | 5c12eac2c3ca |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lda/main.c Mon Apr 13 15:45:17 2009 -0400 @@ -0,0 +1,425 @@ +/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "lib-signals.h" +#include "env-util.h" +#include "fd-set-nonblock.h" +#include "istream.h" +#include "istream-seekable.h" +#include "str.h" +#include "str-sanitize.h" +#include "strescape.h" +#include "rfc822-parser.h" +#include "message-address.h" +#include "imap-utf7.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "mail-storage-service.h" +#include "mail-namespace.h" +#include "raw-storage.h" +#include "mail-deliver.h" +#include "mail-send.h" +#include "duplicate.h" +#include "mbox-from.h" +#include "lda-settings.h" + +#include <stdio.h> +#include <stdlib.h> +#include <pwd.h> +#include <sysexits.h> + +#ifndef EX_CONFIG +# define EX_CONFIG 78 /* for HP-UX */ +#endif + +#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 +}; + +static struct master_service *service; + +static const char *escape_local_part(const char *local_part) +{ + const char *p; + + /* if there are non-atext chars, we need to return quoted-string */ + for (p = local_part; *p != '\0'; p++) { + if (!IS_ATEXT(*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 struct istream * +create_raw_stream(struct mail_deliver_context *ctx, + const char *temp_path_prefix, 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 && ctx->src_envelope_sender == NULL) { + /* use the envelope sender from From_-line, but only if it + hasn't been specified with -f already. */ + ctx->src_envelope_sender = p_strdup(ctx->pool, 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, + temp_path_prefix); + 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 print_help(void) +{ + printf( +"Usage: deliver [-c <config file>] [-a <address>] [-d <username>] [-p <path>]\n" +" [-f <envelope sender>] [-m <mailbox>] [-e] [-k]\n"); +} + +int main(int argc, char *argv[]) +{ + struct mail_deliver_context ctx; + enum mail_storage_service_flags service_flags = 0; + const char *user, *errstr, *path, *getopt_str; + struct mail_user *raw_mail_user; + struct mail_namespace *raw_ns; + struct mail_namespace_settings raw_ns_set; + 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; + char cwd[PATH_MAX]; + void **sets; + uid_t process_euid; + bool stderr_rejection = FALSE; + time_t mtime; + int ret, c; + 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_CONFIG; + } 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_CONFIG; + } + } + + i_set_failure_exit_callback(failure_exit_callback); + + service = master_service_init("lda", MASTER_SERVICE_FLAG_STANDALONE, + argc, argv); +#ifdef SIGXFSZ + lib_signals_ignore(SIGXFSZ, TRUE); +#endif + + memset(&ctx, 0, sizeof(ctx)); + ctx.pool = pool_alloconly_create("mail deliver context", 256); + ctx.dest_mailbox_name = "INBOX"; + path = NULL; + + user = getenv("USER"); + getopt_str = t_strconcat("a:d:p:ekm:nsf:", + master_service_getopt_string(), NULL); + while ((c = getopt(argc, argv, getopt_str)) > 0) { + switch (c) { + case 'a': + /* destination address */ + ctx.dest_addr = optarg; + break; + case 'd': + /* destination user */ + user = optarg; + service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; + break; + case 'p': + /* input path */ + path = optarg; + 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); + } + break; + case 'e': + stderr_rejection = TRUE; + break; + case 'm': + /* destination mailbox. + Ignore -m "". This allows doing -m ${extension} + in Postfix to handle user+mailbox */ + if (*optarg != '\0') T_BEGIN { + str = t_str_new(256); + if (imap_utf8_to_utf7(optarg, str) < 0) { + i_fatal("Mailbox name not UTF-8: %s", + optarg); + } + ctx.dest_mailbox_name = + p_strdup(ctx.pool, str_c(str)); + } T_END; + break; + case 'f': + /* envelope sender address */ + ctx.src_envelope_sender = + p_strdup(ctx.pool, address_sanitize(optarg)); + break; + default: + if (!master_service_parse_option(service, c, optarg)) { + print_help(); + i_fatal_status(EX_USAGE, + "Unknown argument: %c", c); + } + break; + } + } + if (optind != argc) { + print_help(); + i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]); + } + + process_euid = geteuid(); + if ((service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) + ; + else if (process_euid != 0) { + /* we're non-root. get our username and possibly our home. */ + struct passwd *pw; + const char *home; + + 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"); + } + + service_flags |= MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT; + ctx.dest_user = mail_storage_service_init_user(service, user, + &lda_setting_parser_info, service_flags); + ctx.set = mail_storage_service_get_settings(service); + duplicate_init(mail_user_set_get_storage_set(ctx.dest_user->set)); + + /* create a separate mail user for the internal namespace */ + if (master_service_set(service, "mail_full_filesystem_access=yes") < 0) + i_unreached(); + sets = master_service_settings_get_others(service); + raw_mail_user = mail_user_alloc(user, sets[0]); + mail_user_set_home(raw_mail_user, "/"); + if (mail_user_init(raw_mail_user, &errstr) < 0) + i_fatal("Raw user initialization failed: %s", errstr); + + memset(&raw_ns_set, 0, sizeof(raw_ns_set)); + raw_ns_set.location = "/tmp"; + + raw_ns = mail_namespaces_init_empty(raw_mail_user); + raw_ns->flags |= NAMESPACE_FLAG_INTERNAL; + raw_ns->set = &raw_ns_set; + if (mail_storage_create(raw_ns, "raw", 0, &errstr) < 0) + i_fatal("Couldn't create internal raw storage: %s", errstr); + if (path == NULL) { + const char *prefix = mail_user_get_temp_prefix(ctx.dest_user); + input = create_raw_stream(&ctx, prefix, 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 = ctx.src_envelope_sender != NULL ? + ctx.src_envelope_sender : DEFAULT_ENVELOPE_SENDER; + raw_box->mtime = mtime; + + t = mailbox_transaction_begin(box, 0); + headers_ctx = mailbox_header_lookup_init(box, wanted_headers); + ctx.src_mail = mail_alloc(t, 0, headers_ctx); + mail_set_seq(ctx.src_mail, 1); + + if (ctx.dest_addr == NULL) { + ctx.dest_addr = mail_deliver_get_address(&ctx, "Envelope-To"); + if (ctx.dest_addr == NULL) { + ctx.dest_addr = strchr(user, '@') != NULL ? user : + t_strconcat(user, "@", ctx.set->hostname, NULL); + } + } + + ret = mail_deliver(&ctx, &storage); + if (ret < 0) { + if (storage == NULL) { + /* This shouldn't happen */ + i_error("BUG: Saving failed for unknown storage"); + return EX_TEMPFAIL; + } + + errstr = 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", errstr); + } + + if (error != MAIL_ERROR_NOSPACE || + ctx.set->quota_full_tempfail) { + /* 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 */ + mail_deliver_log(&ctx, "rejected: %s", + str_sanitize(errstr, 512)); + + if (stderr_rejection) + return EX_NOPERM; + ret = mail_send_rejection(&ctx, user, errstr); + if (ret != 0) + return ret < 0 ? EX_TEMPFAIL : ret; + /* ok, rejection sent */ + } + + mail_free(&ctx.src_mail); + mailbox_header_lookup_unref(&headers_ctx); + mailbox_transaction_rollback(&t); + mailbox_close(&box); + + mail_user_unref(&ctx.dest_user); + mail_user_unref(&raw_mail_user); + duplicate_deinit(); + pool_unref(&ctx.pool); + + mail_storage_service_deinit_user(); + master_service_deinit(&service); + return EX_OK; +}