# HG changeset patch # User Timo Sirainen # Date 1239919950 14400 # Node ID a957a6be4af59cffcadb5c878ed0df72e42237f4 # Parent 749339f9d1dfdbc6f07b68771fda493dbc50faf3 Initial implementation of LMTP server. Master process doesn't yet execute it though. diff -r 749339f9d1df -r a957a6be4af5 .hgignore --- 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 diff -r 749339f9d1df -r a957a6be4af5 configure.in --- 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 diff -r 749339f9d1df -r a957a6be4af5 src/Makefile.am --- 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 \ diff -r 749339f9d1df -r a957a6be4af5 src/lmtp/Makefile.am --- /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 diff -r 749339f9d1df -r a957a6be4af5 src/lmtp/client.c --- /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 + +#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 -r 749339f9d1df -r a957a6be4af5 src/lmtp/client.h --- /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 diff -r 749339f9d1df -r a957a6be4af5 src/lmtp/commands.c --- /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; +} diff -r 749339f9d1df -r a957a6be4af5 src/lmtp/commands.h --- /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 diff -r 749339f9d1df -r a957a6be4af5 src/lmtp/main.c --- /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 +#include + +#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 -r 749339f9d1df -r a957a6be4af5 src/lmtp/main.h --- /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