deliver.c revision d5ab27fd20523145658a5594b47c9cf2da689266
/* Copyright (c) 2005-2008 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 "var-expand.h"
#include "rfc822-parser.h"
#include "message-address.h"
#include "mail-namespace.h"
#include "raw-storage.h"
#include "dict-client.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_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. */
static const char *wanted_headers[] = {
"From", "Message-ID", "Subject", "Return-Path",
};
struct deliver_settings *deliver_set;
/* 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 bool tried_default_save = FALSE;
static bool no_mailbox_autocreate = FALSE;
static char *explicit_envelope_sender = NULL;
static pool_t plugin_pool;
static ARRAY_DEFINE(plugin_envs, const char *);
{
/* warn about being killed because of some signal, except SIGINT (^C)
which is too common at least while testing :) */
}
{
struct message_address *addr;
const char *str;
if (explicit_envelope_sender != NULL)
return explicit_envelope_sender;
return NULL;
(const unsigned char *)str,
}
static const struct var_expand_table *
{
static struct var_expand_table static_tab[] = {
{ '$', NULL },
{ 'm', NULL },
{ 's', NULL },
{ 'f', NULL },
{ '\0', NULL }
};
struct var_expand_table *tab;
unsigned int i;
return tab;
}
static void
{
const char *msg;
}
static struct mailbox *
{
struct mail_namespace *ns;
enum mail_error error;
return NULL;
}
return box;
if (error != MAIL_ERROR_NOTFOUND)
return NULL;
/* try creating it. */
return NULL;
/* and try opening again */
return NULL;
mailbox_close(&box);
return NULL;
}
return box;
}
const char *const *keywords)
{
struct mailbox_transaction_context *t;
struct mail_keywords *kw;
enum mail_error error;
const char *mailbox_name;
int ret = 0;
"save failed to %s: Unknown namespace",
return -1;
}
return -1;
}
ret = -1;
if (ret < 0)
else
ret = mailbox_transaction_commit(&t);
if (ret == 0) {
saved_mail = TRUE;
} else {
}
mailbox_close(&box);
return ret;
}
{
}
const char *deliver_get_new_message_id(void)
{
static int count = 0;
return t_strdup_printf("<dovecot-%s-%s-%d@%s>",
}
#include "settings.h"
#include "../master/master-settings.h"
#include "../master/master-settings-defs.c"
static bool setting_is_bool(const char *name)
{
const struct setting_def *def;
}
}
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)
{
bool ns_subscriptions = FALSE;
unsigned int i, ns_idx = 0;
if (fd < 0)
/* @UNSAFE: line is modified */
/* skip whitespace */
line++;
/* ignore comments or empty lines */
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--;
lda_section = TRUE;
lda_section = TRUE;
pop3_section = TRUE;
ns_section = TRUE;
ns_idx++;
line += 10;
"NAMESPACE_%u_TYPE=%s", ns_idx,
}
sections++;
}
if (*line == '}') {
sections--;
lda_section = FALSE;
ns_section = FALSE;
if (ns_location)
ns_location = FALSE;
else {
"NAMESPACE_%u=", ns_idx));
}
if (ns_list)
else {
"NAMESPACE_%u_LIST=1", ns_idx));
}
if (ns_subscriptions)
else {
"NAMESPACE_%u_SUBSCRIPTIONS=1",
ns_idx));
}
}
continue;
}
if (sections > 0 && !lda_section) {
if (pop3_section) {
continue;
} else if (ns_section) {
"NAMESPACE_%u_SEP", ns_idx);
ns_location = TRUE;
ns_idx);
} else {
}
}
}
do {
value++;
if (len > 0 &&
}
for (i = 0; i < N_ELEMENTS(default_yes_settings); i++) {
key) == 0)
}
continue;
}
if (!plugin_section) {
} else {
/* %variables need to be expanded.
store these for later. */
}
}
for (i = 0; i < N_ELEMENTS(default_yes_settings); i++) {
if (default_yes_settings[i].set) {
}
}
}
static const struct var_expand_table *
{
static struct var_expand_table static_tab[] = {
{ 'u', NULL },
{ 'n', NULL },
{ 'd', NULL },
{ 's', NULL },
{ 'h', NULL },
{ 'l', NULL },
{ 'r', NULL },
{ 'p', NULL },
{ 'i', NULL },
{ '\0', NULL }
};
struct var_expand_table *tab;
"/HOME_DIRECTORY_USED_BUT_NOT_GIVEN_BY_USERDB";
return tab;
}
static const char *
{
const char *p;
/* it's either type:data or just data */
if (p != NULL) {
while (env != p) {
env++;
}
}
/* expand home */
}
/* expand %vars */
}
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\"",
}
}
return local_part;
}
static const char *address_sanitize(const char *address)
{
struct message_address *addr;
else {
else
}
pool_unref(&pool);
return ret;
}
{
const unsigned char *data;
/* If input begins with a From-line, drop it */
/* skip until the first LF */
for (i = 0; i < size; i++) {
if (data[i] == '\n')
break;
}
if (i != size) {
&sender);
break;
}
}
}
/* use the envelope sender from From_-line, but only if it
hasn't been specified with -f already. */
}
} else {
}
"/tmp/dovecot.deliver.");
return input;
}
static void failure_exit_callback(int *status)
{
/* we want all our exit codes to be sysexits.h compatible */
switch (*status) {
case FATAL_LOGOPEN:
case FATAL_LOGWRITE:
case FATAL_LOGERROR:
case FATAL_OUTOFMEM:
case FATAL_EXEC:
case FATAL_DEFAULT:
*status = EX_TEMPFAIL;
break;
}
}
static void open_logfile(const char *username)
{
int facility;
} else {
/* log to file or stderr */
}
}
static void print_help(void)
{
"Usage: deliver [-c <config file>] [-a <address>] [-d <username>] [-p <path>]\n"
" [-f <envelope sender>] [-m <mailbox>] [-n] [-e] [-k]\n");
}
void deliver_env_clean(void)
{
/* 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();
}
static void expand_envs(const char *user)
{
const struct var_expand_table *table;
unsigned int i, count;
for (i = 0; i < count; i++) {
str_truncate(str, 0);
}
/* get the table again in case plugin envs provided the home
directory (yea, kludgy) */
}
}
{
char **fields;
const char *key, *p;
unsigned int i, count;
for (i = 0; i < count; i++) {
if (p == NULL)
else {
}
}
}
{
const char *config_path = DEFAULT_CONFIG_FILE;
const char *mailbox = "INBOX";
const char *auth_socket;
struct mail_storage *storage;
struct raw_mailbox *raw_box;
struct mailbox_transaction_context *t;
struct mailbox_header_lookup_ctx *headers_ctx;
bool stderr_rejection = FALSE;
bool keep_environment = FALSE;
int i, ret;
lib_init();
ioloop = io_loop_create();
#ifdef SIGXFSZ
#endif
for (i = 1; i < argc; i++) {
/* destination address */
i++;
if (i == argc)
/* destination user */
i++;
if (i == argc)
/* input path */
i++;
if (i == argc)
/* config file path */
i++;
if (i == argc) {
"Missing config file path argument");
}
config_path = argv[i];
/* destination mailbox */
i++;
if (i == argc)
/* Ignore -m "". This allows doing -m ${extension}
in Postfix to handle user+mailbox */
if (*argv[i] != '\0')
/* destination mailbox */
/* envelope sender address */
i++;
if (i == argc)
} else if (argv[i][0] != '\0') {
print_help();
"Unknown argument: %s", argv[i]);
}
}
if (!keep_environment)
process_euid = geteuid();
if (user_auth)
;
else if (process_euid != 0) {
/* we're non-root. get our username and possibly our home. */
/* no need for a pw lookup */
"Couldn't lookup our username (uid=%s)",
}
} else {
"destination user parameter (-d user) not given");
}
T_BEGIN {
} T_END;
env_put("DEBUG=1");
else {
const char *version;
if (plugin_dir == NULL)
}
if (user_auth) {
if (auth_socket == NULL) {
NULL);
}
&extra_fields);
if (ret != 0)
return ret;
}
/* Fix namespaces with empty locations */
for (i = 1;; i++) {
break;
if (*value == '\0') {
getenv("MAIL")));
}
}
/* If possible chdir to home directory, so that core file
could be written in case we crash. */
}
}
i = 0077;
(void)umask(i);
"postmaster_address setting not given");
}
}
i_fatal("Namespace initialization failed");
FILE_LOCK_METHOD_FCNTL, &errstr) < 0)
} else {
}
i_fatal("Can't open delivery mail as raw");
enum mail_error error;
i_fatal("Can't sync delivery mail: %s",
}
t = mailbox_transaction_begin(box, 0);
if (deliver_mail == NULL)
ret = -1;
else {
/* if message was saved, don't bounce it even though
the script failed later. */
} 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. */
}
/* still didn't work. try once more to save it
to INBOX. */
}
if (ret < 0 ) {
const char *error_string;
enum mail_error error;
/* This shouldn't happen */
i_error("BUG: Saving failed for unknown storage");
return EX_TEMPFAIL;
}
if (stderr_rejection) {
/* write to stderr also for tempfails so that MTA
can log the reason if it wants to. */
}
if (error != MAIL_ERROR_NOSPACE ||
/* 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 */
if (stderr_rejection)
return EX_NOPERM;
if (ret != 0)
/* ok, rejection sent */
}
mailbox_close(&box);
lib_deinit();
return EX_OK;
}