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