]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Initial implementation of LMTP server. Master process doesn't yet execute it though.
authorTimo Sirainen <tss@iki.fi>
Thu, 16 Apr 2009 22:12:30 +0000 (18:12 -0400)
committerTimo Sirainen <tss@iki.fi>
Thu, 16 Apr 2009 22:12:30 +0000 (18:12 -0400)
--HG--
branch : HEAD

.hgignore
configure.in
src/Makefile.am
src/lmtp/Makefile.am [new file with mode: 0644]
src/lmtp/client.c [new file with mode: 0644]
src/lmtp/client.h [new file with mode: 0644]
src/lmtp/commands.c [new file with mode: 0644]
src/lmtp/commands.h [new file with mode: 0644]
src/lmtp/main.c [new file with mode: 0644]
src/lmtp/main.h [new file with mode: 0644]

index 28f032ac607e9dd2e2099dbd7ed2dc5a604716b1..8229f1424b4b274eec06badba79bf9eb550a970e 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -65,6 +65,7 @@ src/lib-dict/dict-drivers-register.c
 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
index 41ff76ce50010adf4b2afeb314dffb8807b98502..6c60edcae7f219b11a992d1d868ae2a6ebaeafdc 100644 (file)
@@ -2406,6 +2406,7 @@ src/lib-storage/register/Makefile
 src/auth/Makefile
 src/config/Makefile
 src/lda/Makefile
+src/lmtp/Makefile
 src/dict/Makefile
 src/imap/Makefile
 src/imap-login/Makefile
index a556bdfb50e2fda67ef3cf107c05734e56c58479..9ab9fa22074040c19ae49613b3ecd176b7964b75 100644 (file)
@@ -26,6 +26,7 @@ SUBDIRS = \
        pop3-login \
        pop3 \
        lda \
+       lmtp \
        config \
        tests \
        util \
diff --git a/src/lmtp/Makefile.am b/src/lmtp/Makefile.am
new file mode 100644 (file)
index 0000000..1a52f71
--- /dev/null
@@ -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
diff --git a/src/lmtp/client.c b/src/lmtp/client.c
new file mode 100644 (file)
index 0000000..4cb5b6c
--- /dev/null
@@ -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");
+       }
+}
diff --git a/src/lmtp/client.h b/src/lmtp/client.h
new file mode 100644 (file)
index 0000000..f8fcbd0
--- /dev/null
@@ -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
diff --git a/src/lmtp/commands.c b/src/lmtp/commands.c
new file mode 100644 (file)
index 0000000..62c81bb
--- /dev/null
@@ -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;
+}
diff --git a/src/lmtp/commands.h b/src/lmtp/commands.h
new file mode 100644 (file)
index 0000000..c42080a
--- /dev/null
@@ -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
diff --git a/src/lmtp/main.c b/src/lmtp/main.c
new file mode 100644 (file)
index 0000000..afe005e
--- /dev/null
@@ -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;
+}
diff --git a/src/lmtp/main.h b/src/lmtp/main.h
new file mode 100644 (file)
index 0000000..81178a1
--- /dev/null
@@ -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