changeset 9121:a957a6be4af5 HEAD

Initial implementation of LMTP server. Master process doesn't yet execute it though.
author Timo Sirainen <tss@iki.fi>
date Thu, 16 Apr 2009 18:12:30 -0400
parents 749339f9d1df
children d33915e5da10
files .hgignore configure.in src/Makefile.am src/lmtp/Makefile.am src/lmtp/client.c src/lmtp/client.h src/lmtp/commands.c src/lmtp/commands.h src/lmtp/main.c src/lmtp/main.h
diffstat 10 files changed, 999 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Apr 16 18:11:00 2009 -0400
+++ b/.hgignore	Thu Apr 16 18:12:30 2009 -0400
@@ -65,6 +65,7 @@
 src/lib-sql/sql-drivers-register.c
 src/lib-storage/register/mail-storage-register.c
 src/lib-storage/register/mailbox-list-register.c
+src/lmtp/lmtp
 src/master/dovecot
 src/master/ssl-build-param
 src/plugins/convert/convert-tool
--- a/configure.in	Thu Apr 16 18:11:00 2009 -0400
+++ b/configure.in	Thu Apr 16 18:12:30 2009 -0400
@@ -2406,6 +2406,7 @@
 src/auth/Makefile
 src/config/Makefile
 src/lda/Makefile
+src/lmtp/Makefile
 src/dict/Makefile
 src/imap/Makefile
 src/imap-login/Makefile
--- a/src/Makefile.am	Thu Apr 16 18:11:00 2009 -0400
+++ b/src/Makefile.am	Thu Apr 16 18:12:30 2009 -0400
@@ -26,6 +26,7 @@
 	pop3-login \
 	pop3 \
 	lda \
+	lmtp \
 	config \
 	tests \
 	util \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/Makefile.am	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,36 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = lmtp
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-lda \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-storage/index \
+	-I$(top_srcdir)/src/lib-storage/index/raw \
+	-DPKG_RUNDIR=\""$(rundir)"\"
+
+lmtp_LDFLAGS = -export-dynamic
+
+libs = \
+	../lib-lda/liblda.a \
+	$(LIBDOVECOT_STORAGE) \
+	$(LIBDOVECOT)
+
+lmtp_LDADD = $(libs) $(MODULE_LIBS)
+
+lmtp_DEPENDENCIES = $(libs)
+
+lmtp_SOURCES = \
+	main.c \
+	client.c \
+	commands.c
+
+noinst_HEADERS = \
+	client.h \
+	commands.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/client.c	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,271 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hostpid.h"
+#include "master-service-settings.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "main.h"
+#include "commands.h"
+#include "client.h"
+
+#include <unistd.h>
+
+#define CLIENT_IDLE_TIMEOUT_MSECS (1000*60)
+#define CLIENT_MAX_INPUT_SIZE 4096
+
+static struct client *clients = NULL;
+unsigned int clients_count = 0;
+
+static void client_idle_timeout(struct client *client)
+{
+	client_destroy(client,
+		       t_strdup_printf("421 4.4.2 %s", client->my_domain),
+		       "Disconnected for inactivity");
+}
+
+static int client_input_line(struct client *client, const char *line)
+{
+	const char *cmd, *args;
+
+	args = strchr(line, ' ');
+	if (args == NULL) {
+		cmd = line;
+		args = "";
+	} else {
+		cmd = t_strdup_until(line, args);
+		args++;
+	}
+	cmd = t_str_ucase(cmd);
+
+	if (strcmp(cmd, "LHLO") == 0)
+		return cmd_lhlo(client, args);
+	if (strcmp(cmd, "MAIL") == 0)
+		return cmd_mail(client, args);
+	if (strcmp(cmd, "RCPT") == 0)
+		return cmd_rcpt(client, args);
+	if (strcmp(cmd, "DATA") == 0)
+		return cmd_data(client, args);
+	if (strcmp(cmd, "QUIT") == 0)
+		return cmd_quit(client, args);
+	if (strcmp(cmd, "VRFY") == 0)
+		return cmd_vrfy(client, args);
+	if (strcmp(cmd, "RSET") == 0)
+		return cmd_rset(client, args);
+	if (strcmp(cmd, "NOOP") == 0)
+		return cmd_noop(client, args);
+
+	client_send_line(client, "502 5.5.2 Unknown command");
+	return 0;
+}
+
+int client_input_read(struct client *client)
+{
+	client->last_input = ioloop_time;
+	timeout_reset(client->to_idle);
+
+	switch (i_stream_read(client->input)) {
+	case -2:
+		/* buffer full */
+		client_destroy(client, "502 5.5.2",
+			       "Disconnected: Input buffer full");
+		return -1;
+	case -1:
+		/* disconnected */
+		client_destroy(client, NULL, NULL);
+		return -1;
+	case 0:
+		/* nothing new read */
+		return 0;
+	default:
+		/* something was read */
+		return 0;
+	}
+}
+
+void client_input_handle(struct client *client)
+{
+	struct ostream *output;
+	const char *line;
+	int ret;
+
+	output = client->output;
+	o_stream_ref(output);
+	o_stream_cork(output);
+	while ((line = i_stream_next_line(client->input)) != NULL) {
+		T_BEGIN {
+			ret = client_input_line(client, line);
+		} T_END;
+		if (ret < 0)
+			break;
+	}
+	o_stream_uncork(output);
+	o_stream_unref(&output);
+}
+
+void client_input(struct client *client)
+{
+	if (client_input_read(client) < 0)
+		return;
+	client_input_handle(client);
+}
+
+static void client_raw_user_create(struct client *client)
+{
+	struct mail_namespace *raw_ns;
+	struct mail_namespace_settings raw_ns_set;
+	const char *error;
+	void **sets;
+
+	sets = master_service_settings_get_others(service);
+
+	client->raw_mail_user = mail_user_alloc("raw user", sets[0]);
+	mail_user_set_home(client->raw_mail_user, "/");
+	if (mail_user_init(client->raw_mail_user, &error) < 0)
+		i_fatal("Raw user initialization failed: %s", error);
+
+	memset(&raw_ns_set, 0, sizeof(raw_ns_set));
+	raw_ns_set.location = "/tmp";
+
+	raw_ns = mail_namespaces_init_empty(client->raw_mail_user);
+	raw_ns->flags |= NAMESPACE_FLAG_INTERNAL;
+	raw_ns->set = &raw_ns_set;
+	if (mail_storage_create(raw_ns, "raw", 0, &error) < 0)
+		i_fatal("Couldn't create internal raw storage: %s", error);
+}
+
+struct client *client_create(int fd_in, int fd_out)
+{
+	struct client *client;
+
+	/* always use nonblocking I/O */
+	net_set_nonblock(fd_in, TRUE);
+	net_set_nonblock(fd_out, TRUE);
+
+	client = i_new(struct client, 1);
+	client->fd_in = fd_in;
+	client->fd_out = fd_out;
+	client->input = i_stream_create_fd(fd_in, CLIENT_MAX_INPUT_SIZE, FALSE);
+	client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
+
+	client->io = io_add(fd_in, IO_READ, client_input, client);
+        client->last_input = ioloop_time;
+	client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
+				      client_idle_timeout, client);
+	client->my_domain = my_hostname;
+	client->state_pool = pool_alloconly_create("client state", 4096);
+	client->state.mail_data_fd = -1;
+
+	DLLIST_PREPEND(&clients, client);
+	clients_count++;
+
+	client_send_line(client, "220 %s Dovecot LMTP ready",
+			 client->my_domain);
+	client_raw_user_create(client);
+	return client;
+}
+
+void client_destroy(struct client *client, const char *prefix,
+		    const char *reason)
+{
+	client_disconnect(client, prefix, reason);
+
+	clients_count--;
+	DLLIST_REMOVE(&clients, client);
+
+	mail_user_unref(&client->raw_mail_user);
+	if (client->io != NULL)
+		io_remove(&client->io);
+	timeout_remove(&client->to_idle);
+	i_stream_destroy(&client->input);
+	o_stream_destroy(&client->output);
+
+	if (close(client->fd_in) < 0)
+		i_error("close(client in) failed: %m");
+	if (client->fd_in != client->fd_out) {
+		if (close(client->fd_out) < 0)
+			i_error("close(client out) failed: %m");
+	}
+	client_state_reset(client);
+	pool_unref(&client->state_pool);
+	i_free(client);
+
+	listener_client_destroyed();
+}
+
+static const char *client_get_disconnect_reason(struct client *client)
+{
+	errno = client->input->stream_errno != 0 ?
+		client->input->stream_errno :
+		client->output->stream_errno;
+	return errno == 0 || errno == EPIPE ? "Connection closed" :
+		t_strdup_printf("Connection closed: %m");
+}
+
+void client_disconnect(struct client *client, const char *prefix,
+		       const char *reason)
+{
+	if (client->disconnected)
+		return;
+
+	if (reason != NULL)
+		client_send_line(client, "%s %s", prefix, reason);
+	else
+		reason = client_get_disconnect_reason(client);
+	i_info("%s", reason);
+
+	client->disconnected = TRUE;
+}
+
+void client_state_reset(struct client *client)
+{
+	if (client->state.raw_mail != NULL)
+		mail_free(&client->state.raw_mail);
+	if (client->state.raw_trans != NULL)
+		mailbox_transaction_rollback(&client->state.raw_trans);
+	if (client->state.raw_box != NULL)
+		mailbox_close(&client->state.raw_box);
+
+	if (client->state.mail_data != NULL)
+		buffer_free(&client->state.mail_data);
+	if (client->state.mail_data_output != NULL)
+		o_stream_unref(&client->state.mail_data_output);
+	if (client->state.mail_data_fd != -1) {
+		if (close(client->state.mail_data_fd) < 0)
+			i_error("close(mail data fd) failed: %m");
+	}
+
+	memset(&client->state, 0, sizeof(client->state));
+	p_clear(client->state_pool);
+	client->state.mail_data_fd = -1;
+}
+
+void client_send_line(struct client *client, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	T_BEGIN {
+		string_t *str;
+
+		str = t_str_new(256);
+		str_vprintfa(str, fmt, args);
+		str_append(str, "\r\n");
+		o_stream_send(client->output, str_data(str), str_len(str));
+	} T_END;
+	va_end(args);
+}
+
+void clients_destroy(void)
+{
+	while (clients != NULL) {
+		client_destroy(clients,
+			t_strdup_printf("421 4.3.2 %s", clients->my_domain),
+			"Shutting down");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/client.h	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,75 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "network.h"
+
+#define CLIENT_MAIL_DATA_MAX_INMEMORY_SIZE (1024*128)
+
+struct mail_recipient {
+	const char *name;
+	struct mail_storage_service_multi_user *multi_user;
+};
+
+struct client_state {
+	const char *mail_from;
+	ARRAY_DEFINE(rcpt_to, struct mail_recipient);
+	unsigned int rcpt_idx;
+
+	unsigned int data_end_idx;
+
+	/* Initially we start writing to mail_data. If it grows too large,
+	   start using mail_data_fd. */
+	buffer_t *mail_data;
+	int mail_data_fd;
+	struct ostream *mail_data_output;
+
+	struct mailbox *raw_box;
+	struct mailbox_transaction_context *raw_trans;
+	struct mail *raw_mail;
+
+	struct mail_user *dest_user;
+	struct mail *first_saved_mail;
+};
+
+struct client {
+	struct client *prev, *next;
+
+	int fd_in, fd_out;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+
+	struct timeout *to_idle;
+	time_t last_input;
+
+	struct ip_addr remote_ip, local_ip;
+	unsigned int remote_port, local_port;
+
+	struct mail_user *raw_mail_user;
+	const char *my_domain;
+
+	pool_t state_pool;
+	struct client_state state;
+
+	unsigned int disconnected:1;
+};
+
+extern unsigned int clients_count;
+
+struct client *client_create(int fd_in, int fd_out);
+void client_destroy(struct client *client, const char *prefix,
+		    const char *reason);
+void client_disconnect(struct client *client, const char *prefix,
+		       const char *reason);
+void client_state_reset(struct client *client);
+
+void client_input(struct client *client);
+void client_input_handle(struct client *client);
+int client_input_read(struct client *client);
+
+void client_send_line(struct client *client, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+
+void clients_destroy(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/commands.c	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,408 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "istream.h"
+#include "mail-storage-service.h"
+#include "index/raw/raw-storage.h"
+#include "lda-settings.h"
+#include "mail-deliver.h"
+#include "main.h"
+#include "client.h"
+#include "commands.h"
+
+#define ERRSTR_MAILBOX_TEMP_FAIL "451 4.2.0 <%s> Temporary internal error"
+
+int cmd_lhlo(struct client *client, const char *args ATTR_UNUSED)
+{
+	client_state_reset(client);
+	client_send_line(client, "250-%s", client->my_domain);
+	client_send_line(client, "250-8BITMIME");
+	client_send_line(client, "250-ENHANCEDSTATUSCODES");
+	client_send_line(client, "250 PIPELINING");
+	return 0;
+}
+
+int cmd_mail(struct client *client, const char *args)
+{
+	const char *addr;
+	unsigned int len;
+
+	if (client->state.mail_from != NULL) {
+		client_send_line(client, "503 5.5.1 MAIL already given");
+		return 0;
+	}
+
+	addr = args;
+	args = strchr(args, ' ');
+	if (args == NULL)
+		args = "";
+	else {
+		addr = t_strdup_until(addr, args);
+		args++;
+	}
+	len = strlen(addr);
+	if (strncasecmp(addr, "FROM:<", 6) != 0 || addr[len-1] != '>') {
+		client_send_line(client, "501 5.5.4 Invalid parameters");
+		return 0;
+	}
+
+	if (*args != '\0') {
+		client_send_line(client, "501 5.5.4 Unsupported options");
+		return 0;
+	}
+
+	client->state.mail_from =
+		p_strndup(client->state_pool, addr + 6, len - 7);
+	p_array_init(&client->state.rcpt_to, client->state_pool, 64);
+	client_send_line(client, "250 2.1.0 OK");
+	return 0;
+}
+
+static bool rcpt_is_duplicate(struct client *client, const char *name)
+{
+	const struct mail_recipient *rcpts;
+	unsigned int i, count;
+
+	rcpts = array_get(&client->state.rcpt_to, &count);
+	for (i = 0; i < count; i++) {
+		if (strcmp(rcpts[i].name, name) == 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+int cmd_rcpt(struct client *client, const char *args)
+{
+	struct mail_recipient rcpt;
+	const char *name, *error;
+	unsigned int len;
+	int ret;
+
+	if (client->state.mail_from == NULL) {
+		client_send_line(client, "503 5.5.1 MAIL needed first");
+		return 0;
+	}
+
+	len = strlen(args);
+	if (strncasecmp(args, "TO:<", 4) != 0 || args[len-1] != '>') {
+		client_send_line(client, "501 5.5.4 Invalid parameters");
+		return 0;
+	}
+
+	memset(&rcpt, 0, sizeof(rcpt));
+	name = t_strndup(args + 4, len - 5);
+
+	if (rcpt_is_duplicate(client, name)) {
+		client_send_line(client, "250 2.1.5 OK, ignoring duplicate");
+		return 0;
+	}
+
+	ret = mail_storage_service_multi_lookup(multi_service, name,
+						client->state_pool,
+						&rcpt.multi_user, &error);
+	if (ret < 0) {
+		i_error("User lookup failed: %s", error);
+		client_send_line(client,
+				 "451 4.3.0 Temporary user lookup failure");
+		return 0;
+	}
+	if (ret == 0) {
+		client_send_line(client,
+				 "550 5.1.1 <%s> User doesn't exist", name);
+		return 0;
+	}
+
+	rcpt.name = p_strdup(client->state_pool, name);
+	array_append(&client->state.rcpt_to, &rcpt, 1);
+
+	client_send_line(client, "250 2.1.5 OK");
+	return 0;
+}
+
+int cmd_quit(struct client *client, const char *args ATTR_UNUSED)
+{
+	client_destroy(client, "221 2.0.0", "Logged out");
+	return -1;
+}
+
+int cmd_vrfy(struct client *client, const char *args ATTR_UNUSED)
+{
+	client_send_line(client, "252 2.3.3 Try RCPT instead");
+	return 0;
+}
+
+int cmd_rset(struct client *client, const char *args ATTR_UNUSED)
+{
+	client_state_reset(client);
+	client_send_line(client, "250 2.0.0 OK");
+	return 0;
+}
+
+int cmd_noop(struct client *client, const char *args ATTR_UNUSED)
+{
+	client_send_line(client, "250 2.0.0 OK");
+	return 0;
+}
+
+static int
+client_deliver(struct client *client, const struct mail_recipient *rcpt,
+	       struct mail *src_mail)
+{
+	struct mail_deliver_context dctx;
+	struct mail_storage *storage;
+	void **sets;
+	const char *error;
+	enum mail_error mail_error;
+	int ret;
+
+	i_set_failure_prefix(t_strdup_printf("lmtp(%s): ", rcpt->name));
+	if (mail_storage_service_multi_next(multi_service, rcpt->multi_user,
+					    &client->state.dest_user,
+					    &error) < 0) {
+		i_error("%s", error);
+		client_send_line(client, ERRSTR_MAILBOX_TEMP_FAIL, rcpt->name);
+		return -1;
+	}
+	sets = mail_storage_service_multi_user_get_set(rcpt->multi_user);
+
+	memset(&dctx, 0, sizeof(dctx));
+	dctx.pool = pool_alloconly_create("mail delivery", 1024);
+	dctx.set = sets[1];
+	dctx.src_mail = src_mail;
+	dctx.src_envelope_sender = client->state.mail_from;
+	dctx.dest_user = client->state.dest_user;
+	dctx.dest_addr = rcpt->name;
+	dctx.dest_mailbox_name = "INBOX";
+	dctx.save_dest_mail = array_count(&client->state.rcpt_to) > 1 &&
+		client->state.first_saved_mail == NULL;
+
+	if (mail_deliver(&dctx, &storage) == 0) {
+		if (dctx.dest_mail != NULL) {
+			i_assert(client->state.first_saved_mail == NULL);
+			client->state.first_saved_mail = dctx.dest_mail;
+		}
+		client_send_line(client, "250 2.0.0 <%s> Saved", rcpt->name);
+		ret = 0;
+	} else if (storage == NULL) {
+		/* This shouldn't happen */
+		i_error("BUG: Saving failed to unknown storage");
+		client_send_line(client, ERRSTR_MAILBOX_TEMP_FAIL,
+				 rcpt->name);
+		ret = -1;
+	} else {
+		error = mail_storage_get_last_error(storage, &mail_error);
+		if (mail_error == MAIL_ERROR_NOSPACE) {
+			client_send_line(client, "%s <%s> %s",
+					 dctx.set->quota_full_tempfail ?
+					 "452 4.2.2" : "552 5.2.2",
+					 rcpt->name, error);
+		} else {
+			client_send_line(client, "451 4.2.0 <%s> %s",
+					 rcpt->name, error);
+		}
+		ret = -1;
+	}
+	pool_unref(&dctx.pool);
+	return ret;
+}
+
+static bool client_deliver_next(struct client *client, struct mail *src_mail)
+{
+	const struct mail_recipient *rcpts;
+	unsigned int count;
+	int ret;
+
+	rcpts = array_get(&client->state.rcpt_to, &count);
+	while (client->state.rcpt_idx < count) {
+		ret = client_deliver(client, &rcpts[client->state.rcpt_idx],
+				     src_mail);
+		i_set_failure_prefix("lmtp: ");
+
+		client->state.rcpt_idx++;
+		if (ret == 0)
+			return TRUE;
+		/* failed. try the next one. */
+		if (client->state.dest_user != NULL)
+			mail_user_unref(&client->state.dest_user);
+	}
+	return FALSE;
+}
+
+static void client_rcpt_fail_all(struct client *client)
+{
+	const struct mail_recipient *rcpts;
+	unsigned int i, count;
+
+	rcpts = array_get(&client->state.rcpt_to, &count);
+	for (i = 0; i < count; i++) {
+		client_send_line(client, ERRSTR_MAILBOX_TEMP_FAIL,
+				 rcpts[i].name);
+	}
+}
+
+static int client_open_raw_mail(struct client *client)
+{
+	static const char *wanted_headers[] = {
+		"From", "To", "Message-ID", "Subject", "Return-Path",
+		NULL
+	};
+	struct mail_storage *raw_storage =
+		client->raw_mail_user->namespaces->storage;
+	struct mailbox *box;
+	struct raw_mailbox *raw_box;
+	struct mailbox_header_lookup_ctx *headers_ctx;
+	struct istream *input;
+	enum mail_error error;
+
+	input = i_stream_create_from_data(client->state.mail_data->data,
+					  client->state.mail_data->used);
+	client->state.raw_box = box =
+		mailbox_open(&raw_storage, "Dovecot Delivery Mail", input,
+			     MAILBOX_OPEN_NO_INDEX_FILES);
+	i_stream_unref(&input);
+	if (box == NULL) {
+		i_error("Can't open delivery mail as raw: %s",
+			mail_storage_get_last_error(raw_storage, &error));
+		client_rcpt_fail_all(client);
+		return -1;
+	}
+	if (mailbox_sync(box, 0, 0, NULL) < 0) {
+		i_error("Can't sync delivery mail: %s",
+			mail_storage_get_last_error(raw_storage, &error));
+		client_rcpt_fail_all(client);
+		return -1;
+	}
+	raw_box = (struct raw_mailbox *)box;
+	raw_box->envelope_sender = client->state.mail_from;
+
+	client->state.raw_trans = mailbox_transaction_begin(box, 0);
+
+	headers_ctx = mailbox_header_lookup_init(box, wanted_headers);
+	client->state.raw_mail = mail_alloc(client->state.raw_trans,
+					    0, headers_ctx);
+	mailbox_header_lookup_unref(&headers_ctx);
+	mail_set_seq(client->state.raw_mail, 1);
+	return 0;
+}
+
+static void client_input_data_finish(struct client *client)
+{
+	struct mail *src_mail;
+
+	io_remove(&client->io);
+	client->io = io_add(client->fd_in, IO_READ, client_input, client);
+
+	if (client_open_raw_mail(client) < 0)
+		return;
+
+	/* save the message to the first recipient's mailbox */
+	src_mail = client->state.raw_mail;
+	if (!client_deliver_next(client, src_mail))
+		return;
+
+	if (client->state.first_saved_mail == NULL)
+		mail_user_unref(&client->state.dest_user);
+	else
+		src_mail = client->state.first_saved_mail;
+
+	/* use the first saved message to save it elsewhere too.
+	   this might allow hard linking the files. */
+	while (client_deliver_next(client, src_mail))
+		mail_user_unref(&client->state.dest_user);
+
+	if (client->state.first_saved_mail != NULL) {
+		struct mail *mail = client->state.first_saved_mail;
+		struct mailbox_transaction_context *trans = mail->transaction;
+		struct mailbox *box = trans->box;
+		struct mail_user *user = box->storage->ns->user;
+
+		mail_free(&mail);
+		mailbox_transaction_rollback(&trans);
+		mailbox_close(&box);
+		mail_user_unref(&user);
+	}
+}
+
+static void
+client_input_add(struct client *client, const unsigned char *data, size_t size)
+{
+	buffer_append(client->state.mail_data, data, size);
+}
+
+static void client_input_data_handle(struct client *client)
+{
+#define DATA_DOT_NEXT_POS 3
+#define DATA_END_SIZE 5
+	static const char *data_end = "\r\n.\r\n";
+	const unsigned char *data;
+	size_t i, size, start, skip;
+	unsigned int rewind;
+
+	data = i_stream_get_data(client->input, &size);
+	skip = 0;
+	for (i = start = 0; i < size; i++) {
+		if (data[i] == data_end[client->state.data_end_idx]) {
+			if (++client->state.data_end_idx == DATA_END_SIZE) {
+				/* found the ending. drop the "." line out. */
+				skip = i + 1;
+				i -= DATA_END_SIZE - DATA_DOT_NEXT_POS;
+				client->state.data_end_idx = 0;
+				break;
+			}
+		} else if (client->state.data_end_idx == DATA_DOT_NEXT_POS) {
+			/* saw a dot at the beginning of line. drop it. */
+			client_input_add(client, data, i-1);
+			start = i;
+			client->state.data_end_idx = 0;
+		} else {
+			client->state.data_end_idx = 0;
+		}
+	}
+	if (client->state.data_end_idx >= DATA_DOT_NEXT_POS) {
+		/* we might not want to write the dot, so keep it in buffer
+		   until we're sure what to do about it. */
+		rewind = client->state.data_end_idx - DATA_DOT_NEXT_POS + 1;
+		i -= rewind; size -= rewind;
+	}
+	client_input_add(client, data + start, i-start);
+	i_stream_skip(client->input, skip == 0 ? i : skip);
+
+	if (i < size) {
+		client_input_data_finish(client);
+		client_state_reset(client);
+		if (i_stream_have_bytes_left(client->input))
+			client_input_handle(client);
+	}
+}
+
+static void client_input_data(struct client *client)
+{
+	if (client_input_read(client) < 0)
+		return;
+
+	client_input_data_handle(client);
+}
+
+int cmd_data(struct client *client, const char *args ATTR_UNUSED)
+{
+	if (client->state.mail_from == NULL) {
+		client_send_line(client, "503 5.5.1 MAIL needed first");
+		return 0;
+	}
+	if (array_count(&client->state.rcpt_to) == 0) {
+		client_send_line(client, "554 5.5.1 No valid recipients");
+		return 0;
+	}
+
+	i_assert(client->state.mail_data == NULL);
+	client->state.mail_data = buffer_create_dynamic(default_pool, 1024*64);
+
+	io_remove(&client->io);
+	client->io = io_add(client->fd_in, IO_READ, client_input_data, client);
+	client_send_line(client, "354 OK");
+
+	client_input_data_handle(client);
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/commands.h	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,15 @@
+#ifndef COMMANDS_H
+#define COMMANDS_H
+
+struct client;
+
+int cmd_lhlo(struct client *client, const char *args);
+int cmd_mail(struct client *client, const char *args);
+int cmd_rcpt(struct client *client, const char *args);
+int cmd_quit(struct client *client, const char *args);
+int cmd_vrfy(struct client *client, const char *args);
+int cmd_rset(struct client *client, const char *args);
+int cmd_noop(struct client *client, const char *args);
+int cmd_data(struct client *client, const char *args);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/main.c	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,182 @@
+/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "restrict-access.h"
+#include "fd-close-on-exec.h"
+#include "process-title.h"
+#include "master-service.h"
+#include "mail-storage-service.h"
+#include "lda-settings.h"
+#include "client.h"
+#include "main.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define LMTP_MASTER_FIRST_LISTEN_FD 3
+
+#define IS_STANDALONE() \
+        (getenv("MASTER_SERVICE") == NULL)
+
+struct lmtp_listener {
+	int fd;
+	struct io *io;
+};
+
+struct master_service *service;
+struct mail_storage_service_multi_ctx *multi_service;
+
+static struct io *log_io = NULL;
+static ARRAY_DEFINE(listeners, struct lmtp_listener *);
+
+static void log_error_callback(void *context ATTR_UNUSED)
+{
+	/* the log fd is closed, don't die when trying to log later */
+	i_set_failure_ignore_errors(TRUE);
+
+	master_service_stop(service);
+}
+
+static void listen_connected(struct lmtp_listener *l)
+{
+	struct client *client;
+	struct ip_addr remote_ip;
+	unsigned int remote_port;
+	int fd;
+
+	fd = net_accept(l->fd, &remote_ip, &remote_port);
+	if (fd < 0) {
+		if (fd < -1)
+			i_error("accept() failed: %m");
+		return;
+	}
+	client = client_create(fd, fd);
+	client->remote_ip = remote_ip;
+	client->remote_port = remote_port;
+
+	(void)net_getsockname(fd, &client->local_ip, &client->local_port);
+}
+
+static void listen_start(void)
+{
+	struct lmtp_listener *const *l;
+	unsigned int i, count;
+
+	l = array_get(&listeners, &count);
+	for (i = 0; i < count; i++) {
+		i_assert(l[i]->io == NULL);
+		l[i]->io = io_add(l[i]->fd, IO_READ, listen_connected, l[i]);
+	}
+}
+
+static void listen_stop(void)
+{
+	struct lmtp_listener *const *l;
+	unsigned int i, count;
+
+	l = array_get(&listeners, &count);
+	for (i = 0; i < count; i++) {
+		i_assert(l[i]->io != NULL);
+		io_remove(&l[i]->io);
+	}
+}
+
+static void listen_free(void)
+{
+	struct lmtp_listener **l;
+	unsigned int i, count;
+
+	l = array_get_modifiable(&listeners, &count);
+	for (i = 0; i < count; i++) {
+		if (l[i]->io != NULL)
+			io_remove(&l[i]->io);
+		i_free(l[i]);
+	}
+	array_free(&listeners);
+}
+
+void listener_client_destroyed(void)
+{
+	if (array_count(&listeners) == 0)
+		master_service_stop(service);
+}
+
+static void main_init(void)
+{
+	struct lmtp_listener *l;
+	const char *value;
+	unsigned int i, count;
+
+	/* If master dies, the log fd gets closed and we'll quit */
+	log_io = io_add(STDERR_FILENO, IO_ERROR, log_error_callback, NULL);
+
+	value = getenv("LISTEN_FDS");
+	count = value == NULL ? 0 : atoi(value);
+	i_array_init(&listeners, count + 1);
+	for (i = 0; i < count; i++) {
+		l = i_new(struct lmtp_listener, 1);
+		l->fd = LMTP_MASTER_FIRST_LISTEN_FD + i;
+		array_append(&listeners, &l, 1);
+	}
+
+	if (count == 0)
+		(void)client_create(STDIN_FILENO, STDOUT_FILENO);
+	else
+		listen_start();
+}
+
+static void main_deinit(void)
+{
+	if (log_io != NULL)
+		io_remove(&log_io);
+	clients_destroy();
+	listen_free();
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+	const struct setting_parser_info *set_roots[] = {
+		&lda_setting_parser_info,
+		NULL
+	};
+	enum master_service_flags service_flags = 0;
+	enum mail_storage_service_flags storage_service_flags =
+		MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT |
+		MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+	int c;
+
+#ifdef DEBUG
+	if (!IS_STANDALONE() && getenv("GDB") == NULL) {
+		const char *env;
+
+		env = getenv("LISTEN_FDS");
+		fd_debug_verify_leaks(LMTP_MASTER_FIRST_LISTEN_FD +
+				      (env == NULL ? 0 : atoi(env)), 1024);
+	}
+#endif
+
+	if (IS_STANDALONE())
+		service_flags |= MASTER_SERVICE_FLAG_STANDALONE;
+
+	service = master_service_init("lmtp", service_flags, argc, argv);
+	while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) {
+		if (!master_service_parse_option(service, c, optarg))
+			i_fatal("Unknown argument: %c", c);
+	}
+
+	multi_service = mail_storage_service_multi_init(service, set_roots,
+							storage_service_flags);
+	restrict_access_allow_coredumps(TRUE);
+
+        process_title_init(argv, envp);
+
+	main_init();
+	master_service_run(service);
+
+	main_deinit();
+	mail_storage_service_multi_deinit(&multi_service);
+	master_service_deinit(&service);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lmtp/main.h	Thu Apr 16 18:12:30 2009 -0400
@@ -0,0 +1,9 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+extern struct master_service *service;
+extern struct mail_storage_service_multi_ctx *multi_service;
+
+void listener_client_destroyed(void);
+
+#endif