From b73539ef2de3db3abc0ad5f729406e695e4cb76b Mon Sep 17 00:00:00 2001 From: Stephan Bosch Date: Wed, 23 Nov 2016 10:43:53 +0100 Subject: [PATCH] lmtp: Ported to use lib-smtp/server. Implicitly fixes handling of multi-line replies from proxy backend. Implicitly adds support for mixing local and proxy recipients. Implicitly adds support for SMTP CHUNKING. RCPT failures are reported back to the client immediately, rather than waiting for the DATA command. --- TODO | 13 +- src/lmtp/client.c | 337 ++++++++++----------------- src/lmtp/client.h | 56 ++--- src/lmtp/commands.c | 526 ++++++++++-------------------------------- src/lmtp/commands.h | 21 +- src/lmtp/lmtp-local.c | 292 ++++++++++++++--------- src/lmtp/lmtp-local.h | 22 +- src/lmtp/lmtp-proxy.c | 359 ++++++++++++++-------------- src/lmtp/lmtp-proxy.h | 27 +-- src/lmtp/main.c | 15 +- src/lmtp/main.h | 2 + 11 files changed, 694 insertions(+), 976 deletions(-) diff --git a/TODO b/TODO index f47a639373..d3cf6745b8 100644 --- a/TODO +++ b/TODO @@ -12,8 +12,6 @@ - quota: maybe check quota once more at commit time to make sure the whole transaction fits. avoids multiple parallel slow COPY commands from being able to go over quota - - lmtp: Calculate incoming mail's hash, forward it via proxying, have the - final delivery code verify that it's correct - METADATA: quota, NOTIFY interaction, METADATA-SERVER capability - fts: if SEARCH X-MAILBOX is used on virtual/all folder, it doesn't update any indexes. (and it should skip those physical mailboxes that don't @@ -95,8 +93,6 @@ - after reading whole message text, update has_nul-state to cache - FIFOs maybe should be counted as connections, but unlisten should unlink+reopen it in master? - - lmtp client/proxy: Handle multiline replies better - - lmtp: support DSN extension (especially ORCPT) - recreate mailbox -> existing sessions log "indexid changed" error - add message/mime limits - imapc: @@ -341,3 +337,12 @@ multiple services can coexist independently on the same HTTP server. - Implement support for `Range:' requests. - Review compliance with RFC 7230 and RFC 7231 + + - lmtp: + - Implement parallel pipelined RCPT TO: verification (requires auth API + changes). + - Improve efficiency and security by splitting lmtp up into a protocol + handler and a one-user local delivery service. + - Fully support DSN extension (especially ORCPT) + - Calculate incoming mail's hash, forward it via proxying, have the + final delivery code verify that it's correct diff --git a/src/lmtp/client.c b/src/lmtp/client.c index 200b1cba0b..a24eb4e91f 100644 --- a/src/lmtp/client.c +++ b/src/lmtp/client.c @@ -12,6 +12,7 @@ #include "process-title.h" #include "var-expand.h" #include "settings-parser.h" +#include "smtp-server.h" #include "master-service.h" #include "master-service-ssl.h" #include "master-service-settings.h" @@ -31,13 +32,14 @@ #include #define CLIENT_IDLE_TIMEOUT_MSECS (1000*60*5) -#define CLIENT_MAX_INPUT_SIZE 4096 static struct client *clients = NULL; -unsigned int clients_count = 0; +static unsigned int clients_count = 0; static bool verbose_proctitle = FALSE; +static const struct smtp_server_callbacks lmtp_callbacks; + const char *client_remote_id(struct client *client) { const char *addr; @@ -48,12 +50,11 @@ const char *client_remote_id(struct client *client) return addr; } -void client_state_set(struct client *client, const char *name, const char *args) +static void refresh_proctitle(void) { + struct client *client; string_t *title; - client->state.name = name; - if (!verbose_proctitle) return; @@ -64,11 +65,10 @@ void client_state_set(struct client *client, const char *name, const char *args) str_append(title, "idling"); break; case 1: + client = clients; str_append(title, client_remote_id(client)); str_append_c(title, ' '); - str_append(title, client->state.name); - if (args[0] != '\0') - str_printfa(title, " %s", args); + str_append(title, client_state_get_name(client)); break; default: str_printfa(title, "%u connections", clients_count); @@ -78,13 +78,6 @@ void client_state_set(struct client *client, const char *name, const char *args) process_title_set(str_c(title)); } -static void client_idle_timeout(struct client *client) -{ - client_destroy(client, - t_strdup_printf("421 4.4.2 %s", client->my_domain), - "Disconnected client for inactivity"); -} - static void client_raw_user_create(struct client *client) { void **sets; @@ -125,71 +118,58 @@ static void client_read_settings(struct client *client) client->unexpanded_lda_set = lda_set; } -unsigned int client_get_rcpt_count(struct client *client) -{ - return lmtp_local_rcpt_count(client) + lmtp_proxy_rcpt_count(client); -} - -static void client_generate_session_id(struct client *client) -{ - guid_128_t guid; - string_t *id = t_str_new(30); - - guid_128_generate(guid); - base64_encode(guid, sizeof(guid), id); - i_assert(str_c(id)[str_len(id)-2] == '='); - str_truncate(id, str_len(id)-2); /* drop trailing "==" */ - client->state.session_id = p_strdup(client->state_pool, str_c(id)); -} - -struct client *client_create(int fd_in, int fd_out, +struct client *client_create(int fd_in, int fd_out, bool ssl_start, const struct master_service_connection *conn) { + struct smtp_server_settings lmtp_set; struct client *client; pool_t pool; - /* always use nonblocking I/O */ - net_set_nonblock(fd_in, TRUE); - net_set_nonblock(fd_out, TRUE); - pool = pool_alloconly_create("lmtp client", 2048); client = p_new(pool, struct client, 1); client->pool = pool; - client->fd_in = fd_in; - client->fd_out = fd_out; client->remote_ip = conn->remote_ip; client->remote_port = conn->remote_port; client->local_ip = conn->local_ip; client->local_port = conn->local_port; - client->input = i_stream_create_fd(fd_in, CLIENT_MAX_INPUT_SIZE); - client->output = o_stream_create_fd(fd_out, (size_t)-1); - o_stream_set_no_error_handling(client->output, TRUE); - - client_io_reset(client); client->state_pool = pool_alloconly_create("client state", 4096); client->state.mail_data_fd = -1; + client_read_settings(client); client_raw_user_create(client); - client_generate_session_id(client); client->my_domain = client->unexpanded_lda_set->hostname; - client->lhlo = i_strdup("missing"); - client->proxy_ttl = LMTP_PROXY_DEFAULT_TTL; if (client->service_set->verbose_proctitle) verbose_proctitle = TRUE; + i_zero(&lmtp_set); + lmtp_set.capabilities = + SMTP_CAPABILITY_PIPELINING | + SMTP_CAPABILITY_ENHANCEDSTATUSCODES | + SMTP_CAPABILITY_8BITMIME | + SMTP_CAPABILITY_CHUNKING; + if (!ssl_start && master_service_ssl_is_enabled(master_service)) + lmtp_set.capabilities |= SMTP_CAPABILITY_STARTTLS; + lmtp_set.hostname = client->unexpanded_lda_set->hostname; + lmtp_set.rcpt_domain_optional = TRUE; + lmtp_set.max_client_idle_time_msecs = CLIENT_IDLE_TIMEOUT_MSECS; + + client->conn = smtp_server_connection_create + (lmtp_server, fd_in, fd_out, + &conn->remote_ip, conn->remote_port, + &lmtp_set, &lmtp_callbacks, client); + DLLIST_PREPEND(&clients, client); clients_count++; - client_state_set(client, "banner", ""); - client_send_line(client, "220 %s %s", client->my_domain, - client->lmtp_set->login_greeting); + smtp_server_connection_start(client->conn, ssl_start); i_info("Connect from %s", client_remote_id(client)); + refresh_proctitle(); return client; } -void client_state_reset(struct client *client, const char *state_name) +void client_state_reset(struct client *client) { if (client->local != NULL) lmtp_local_deinit(&client->local); @@ -203,77 +183,112 @@ void client_state_reset(struct client *client, const char *state_name) i_zero(&client->state); p_clear(client->state_pool); client->state.mail_data_fd = -1; - - client_generate_session_id(client); - client_state_set(client, state_name, ""); } -void client_destroy(struct client *client, const char *prefix, +void client_destroy(struct client *client, const char *enh_code, const char *reason) { - client_disconnect(client, prefix, reason); - o_stream_uncork(client->output); + if (client->destroyed) + return; + client->destroyed = TRUE; + + client_disconnect(client, enh_code, reason); clients_count--; DLLIST_REMOVE(&clients, client); - client_state_set(client, "destroyed", ""); - if (client->raw_mail_user != NULL) mail_user_unref(&client->raw_mail_user); - if (client->local != NULL) - lmtp_local_deinit(&client->local); - if (client->proxy != NULL) - lmtp_proxy_deinit(&client->proxy); - io_remove(&client->io); - timeout_remove(&client->to_idle); - if (client->ssl_iostream != NULL) - ssl_iostream_destroy(&client->ssl_iostream); - i_stream_destroy(&client->input); - o_stream_destroy(&client->output); - - fd_close_maybe_stdio(&client->fd_in, &client->fd_out); - client_state_reset(client, "destroyed"); - i_free(client->lhlo); + + client_state_reset(client); pool_unref(&client->state_pool); pool_unref(&client->pool); master_service_client_connection_destroyed(master_service); } -static const char *client_get_disconnect_reason(struct client *client) +const char *client_state_get_name(struct client *client) { - const char *err; - - if (client->ssl_iostream != NULL && - !ssl_iostream_is_handshaked(client->ssl_iostream)) { - err = ssl_iostream_get_last_error(client->ssl_iostream); - if (err != NULL) { - return t_strdup_printf("TLS handshaking failed: %s", - err); - } - } - return io_stream_get_disconnect_reason(client->input, client->output); + enum smtp_server_state state; + + if (client->conn == NULL) + state = client->last_state; + else + state = smtp_server_connection_get_state(client->conn); + return smtp_server_state_names[state]; } -void client_disconnect(struct client *client, const char *prefix, +void client_disconnect(struct client *client, const char *enh_code, const char *reason) { + struct smtp_server_connection *conn = client->conn; + if (client->disconnected) return; + client->disconnected = TRUE; - if (reason != NULL) - client_send_line(client, "%s %s", prefix, reason); - else - reason = client_get_disconnect_reason(client); - i_info("Disconnect from %s: %s (in %s)", client_remote_id(client), - reason, client->state.name); + if (reason == NULL) + reason = "Connection closed"; + i_info("Disconnect from %s: %s (state = %s)", client_remote_id(client), + reason, client_state_get_name(client)); - client->disconnected = TRUE; + if (conn != NULL) { + client->last_state = smtp_server_connection_get_state(conn); + smtp_server_connection_terminate(&conn, + (enh_code == NULL ? "4.0.0" : enh_code), reason); + } +} + +static void +client_connection_trans_free(void *context, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + struct client *client = (struct client *)context; + + client_state_reset(client); +} + +static void +client_connection_state_changed(void *context ATTR_UNUSED, + enum smtp_server_state newstate ATTR_UNUSED) +{ + if (clients_count == 1) + refresh_proctitle(); +} + +static void +client_connection_proxy_data_updated(void *context, + const struct smtp_proxy_data *data) +{ + struct client *client = (struct client *)context; + + client->remote_ip = data->source_ip; + client->remote_port = data->source_port; + + if (clients_count == 1) + refresh_proctitle(); +} + +static void client_connection_disconnect(void *context, const char *reason) +{ + struct client *client = (struct client *)context; + struct smtp_server_connection *conn = client->conn; + + if (conn != NULL) + client->last_state = smtp_server_connection_get_state(conn); + client_disconnect(client, NULL, reason); } -bool client_is_trusted(struct client *client) +static void client_connection_destroy(void *context) { + struct client *client = (struct client *)context; + + client_destroy(client, NULL, NULL); +} + +static bool client_connection_is_trusted(void *context) +{ + struct client *client = (struct client *)context; const char *const *net; struct ip_addr net_ip; unsigned int bits; @@ -298,133 +313,33 @@ bool client_is_trusted(struct client *client) void clients_destroy(void) { while (clients != NULL) { - client_destroy(clients, - t_strdup_printf("421 4.3.2 %s", clients->my_domain), - "Shutting down"); + client_destroy(clients, "4.3.2", "Shutting down"); } } -/* - * Input handling - */ +static const struct smtp_server_callbacks lmtp_callbacks = { + .conn_cmd_mail = cmd_mail, + .conn_cmd_rcpt = cmd_rcpt, + .conn_cmd_data_begin = cmd_data_begin, + .conn_cmd_data_continue = cmd_data_continue, -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, "STARTTLS") == 0 && - master_service_ssl_is_enabled(master_service)) - return cmd_starttls(client); - 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); - if (strcmp(cmd, "XCLIENT") == 0) - return cmd_xclient(client, args); - - client_send_line(client, "502 5.5.2 Unknown command"); - return 0; -} + .conn_trans_free = client_connection_trans_free, -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; - } -} + .conn_state_changed = client_connection_state_changed, -void client_input_handle(struct client *client) -{ - struct ostream *output; - const char *line; - int ret; - - output = client->output; - o_stream_ref(output); - while ((line = i_stream_next_line(client->input)) != NULL) { - T_BEGIN { - o_stream_cork(output); - ret = client_input_line(client, line); - o_stream_uncork(output); - } T_END; - if (ret < 0) - break; - } - o_stream_unref(&output); -} + .conn_proxy_data_updated = client_connection_proxy_data_updated, -static void client_input(struct client *client) -{ - if (client_input_read(client) < 0) - return; - client_input_handle(client); -} + .conn_disconnect = client_connection_disconnect, + .conn_destroy = client_connection_destroy, -void client_io_reset(struct client *client) -{ - io_remove(&client->io); - timeout_remove(&client->to_idle); - client->io = io_add(client->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); -} + .conn_is_trusted = client_connection_is_trusted +}; + +/* + * Input handling + */ /* * Output handling */ -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_nsend(client->output, str_data(str), str_len(str)); - } T_END; - va_end(args); -} diff --git a/src/lmtp/client.h b/src/lmtp/client.h index 57edf3c4df..c640266a23 100644 --- a/src/lmtp/client.h +++ b/src/lmtp/client.h @@ -2,35 +2,33 @@ #define CLIENT_H #include "net.h" -#include "smtp-params.h" +#include "smtp-server.h" #define CLIENT_MAIL_DATA_MAX_INMEMORY_SIZE (1024*128) struct lmtp_recipient { struct client *client; - const char *session_id; - struct smtp_address *address; - const char *detail; /* +detail part is also in address */ - struct smtp_params_rcpt params; + struct smtp_address *path; + struct smtp_server_cmd_ctx *rcpt_cmd; + struct smtp_server_recipient *rcpt; + unsigned int index; }; struct client_state { const char *name; - const char *session_id; - struct smtp_address *mail_from; - struct smtp_params_mail mail_params; + unsigned int session_id_seq; - unsigned int data_end_idx; + struct timeval data_end_timeval; /* 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; - const char *added_headers; - struct timeval mail_from_timeval, data_end_timeval; + const char *added_headers_local; + const char *added_headers_proxy; }; struct client { @@ -41,55 +39,37 @@ struct client { const struct lda_settings *unexpanded_lda_set; const struct lmtp_settings *lmtp_set; const struct master_service_settings *service_set; - int fd_in, fd_out; - struct io *io; - struct istream *input; - struct ostream *output; - struct ssl_iostream *ssl_iostream; - struct timeout *to_idle; - time_t last_input; + struct smtp_server_connection *conn; + enum smtp_server_state last_state; struct ip_addr remote_ip, local_ip; in_port_t remote_port, local_port; struct mail_user *raw_mail_user; const char *my_domain; - char *lhlo; pool_t state_pool; struct client_state state; struct istream *dot_input; struct lmtp_local *local; struct lmtp_proxy *proxy; - unsigned int proxy_ttl; - unsigned int proxy_timeout_secs; bool disconnected:1; + bool destroyed:1; }; -extern unsigned int clients_count; - -struct client *client_create(int fd_in, int fd_out, +struct client *client_create(int fd_in, int fd_out, bool ssl_start, const struct master_service_connection *conn); -void client_destroy(struct client *client, const char *prefix, +void client_destroy(struct client *client, const char *enh_code, const char *reason) ATTR_NULL(2, 3); -void client_disconnect(struct client *client, const char *prefix, - const char *reason); -unsigned int client_get_rcpt_count(struct client *client); -void client_state_reset(struct client *client, const char *state_name); -void client_state_set(struct client *client, const char *name, const char *args); +void client_disconnect(struct client *client, const char *enh_code, + const char *reason) ATTR_NULL(2, 3); const char *client_remote_id(struct client *client); -bool client_is_trusted(struct client *client); +const char *client_state_get_name(struct client *client); +void client_state_reset(struct client *client); void clients_destroy(void); -void client_input_handle(struct client *client); -int client_input_read(struct client *client); -void client_io_reset(struct client *client); - -void client_send_line(struct client *client, const char *fmt, ...) - ATTR_FORMAT(2, 3); - #endif diff --git a/src/lmtp/commands.c b/src/lmtp/commands.c index 7064fcfa81..61245709b4 100644 --- a/src/lmtp/commands.c +++ b/src/lmtp/commands.c @@ -1,297 +1,95 @@ /* Copyright (c) 2009-2017 Dovecot authors, see the included COPYING file */ #include "lib.h" -#include "ioloop.h" #include "array.h" #include "str.h" #include "istream.h" #include "istream-concat.h" #include "ostream.h" -#include "istream-dot.h" #include "safe-mkstemp.h" -#include "anvil-client.h" -#include "master-service.h" -#include "master-service-ssl.h" -#include "iostream-ssl.h" -#include "rfc822-parser.h" -#include "message-date.h" -#include "mail-storage-service.h" #include "index/raw/raw-storage.h" +#include "master-service.h" +#include "settings-parser.h" #include "lda-settings.h" #include "lmtp-settings.h" +#include "smtp-address.h" +#include "smtp-server.h" +#include "lmtp-proxy.h" #include "lmtp-local.h" #include "mail-deliver.h" -#include "message-address.h" +#include "mail-error.h" #include "main.h" #include "client.h" #include "commands.h" -#include "lmtp-proxy.h" - -#define ERRSTR_TEMP_MAILBOX_FAIL "451 4.3.0 <%s> Temporary internal error" /* * EHLO command */ -int cmd_lhlo(struct client *client, const char *args) -{ - struct rfc822_parser_context parser; - string_t *domain = t_str_new(128); - const char *p; - int ret = 0; - - if (*args == '\0') { - client_send_line(client, "501 Missing hostname"); - return 0; - } - - /* domain / address-literal */ - rfc822_parser_init(&parser, (const unsigned char *)args, strlen(args), - NULL); - if (*args != '[') - ret = rfc822_parse_dot_atom(&parser, domain); - else { - for (p = args+1; *p != ']'; p++) { - if (*p == '\\' || *p == '[') - break; - } - if (strcmp(p, "]") != 0) - ret = -1; - } - if (ret < 0) { - str_truncate(domain, 0); - str_append(domain, "invalid"); - } - - client_state_reset(client, "LHLO"); - client_send_line(client, "250-%s", client->my_domain); - if (master_service_ssl_is_enabled(master_service) && - client->ssl_iostream == NULL) - client_send_line(client, "250-STARTTLS"); - if (client_is_trusted(client)) - client_send_line(client, "250-XCLIENT ADDR PORT TTL TIMEOUT"); - client_send_line(client, "250-8BITMIME"); - client_send_line(client, "250-ENHANCEDSTATUSCODES"); - client_send_line(client, "250 PIPELINING"); - - i_free(client->lhlo); - client->lhlo = i_strdup(str_c(domain)); - client_state_set(client, "LHLO", ""); - return 0; -} - /* * STARTTLS command */ -int cmd_starttls(struct client *client) -{ - struct ostream *plain_output = client->output; - const char *error; - - if (client->ssl_iostream != NULL) { - o_stream_nsend_str(client->output, - "443 5.5.1 TLS is already active.\r\n"); - return 0; - } - - if (master_service_ssl_init(master_service, - &client->input, &client->output, - &client->ssl_iostream, &error) < 0) { - i_error("TLS initialization failed: %s", error); - o_stream_nsend_str(client->output, - "454 4.7.0 Internal error, TLS not available.\r\n"); - return 0; - } - o_stream_nsend_str(plain_output, - "220 2.0.0 Begin TLS negotiation now.\r\n"); - if (ssl_iostream_handshake(client->ssl_iostream) < 0) { - client_destroy(client, NULL, NULL); - return -1; - } - return 0; -} - /* * MAIL command */ -int cmd_mail(struct client *client, const char *args) +int cmd_mail(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_mail *data ATTR_UNUSED) { - struct smtp_address *address; - enum smtp_param_parse_error pperror; - const char *error; - - if (client->state.mail_from != NULL) { - client_send_line(client, "503 5.5.1 MAIL already given"); - return 0; - } - - if (strncasecmp(args, "FROM:", 5) != 0) { - client_send_line(client, "501 5.5.4 Invalid parameters"); - return 0; - } - if (smtp_address_parse_path_full(pool_datastack_create(), args + 5, - SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY, - &address, &error, &args) < 0) { - client_send_line(client, "501 5.5.4 Invalid FROM: %s", error); - return 0; - } - if (*args == ' ') - args++; - else if (*args != '\0') { - client_send_line(client, "501 5.5.4 Invalid FROM: " - "Invalid character in path"); - return 0; - } - - /* [SP Mail-parameters] */ - if (smtp_params_mail_parse(client->state_pool, args, - SMTP_CAPABILITY_8BITMIME, FALSE, - &client->state.mail_params, &pperror, &error) < 0) { - switch (pperror) { - case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX: - client_send_line(client, "501 5.5.4 %s", error); - break; - case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED: - client_send_line(client, "555 5.5.4 %s", error); - break; - default: - i_unreached(); - } - return 0; - } - - client->state.mail_from = - smtp_address_clone(client->state_pool, address); - client_send_line(client, "250 2.1.0 OK"); - client_state_set(client, "MAIL FROM", - smtp_address_encode(address)); + struct client *client = (struct client *)conn_ctx; if (client->lmtp_set->lmtp_user_concurrency_limit > 0) { /* connect to anvil before dropping privileges */ lmtp_anvil_init(); } - - client->state.mail_from_timeval = ioloop_timeval; - return 0; + return 1; } /* * RCPT command */ -int cmd_rcpt(struct client *client, const char *args) +int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data) { - struct smtp_address *address; + struct client *client = (struct client *)conn_ctx; const char *username, *detail; - struct smtp_params_rcpt params; - enum smtp_param_parse_error pperror; - const char *error = NULL; char delim = '\0'; - - if (client->state.mail_from == NULL) { - client_send_line(client, "503 5.5.1 MAIL needed first"); - return 0; - } - - if (strncasecmp(args, "TO:", 3) != 0) { - client_send_line(client, "501 5.5.4 Invalid parameters"); - return 0; - } - if (smtp_address_parse_path_full(pool_datastack_create(), args + 3, - SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART, - &address, &error, &args) < 0) { - client_send_line(client, "501 5.5.4 Invalid TO: %s", error); - return 0; - } - if (*args == ' ') - args++; - else if (*args != '\0') { - client_send_line(client, "501 5.5.4 Invalid TO: " - "Invalid character in path"); - return 0; - } - - /* [SP Rcpt-parameters] */ - if (smtp_params_rcpt_parse(client->state_pool, args, - SMTP_CAPABILITY_DSN, FALSE, - ¶ms, &pperror, &error) < 0) { - switch (pperror) { - case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX: - client_send_line(client, "501 5.5.4 %s", error); - break; - case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED: - client_send_line(client, "555 5.5.4 %s", error); - break; - default: - i_unreached(); - } - return 0; - } + int ret; smtp_address_detail_parse_temp( client->unexpanded_lda_set->recipient_delimiter, - address, &username, &delim, &detail); - - client_state_set(client, "RCPT TO", - smtp_address_encode(address)); - + data->path, &username, &delim, &detail); if (client->lmtp_set->lmtp_proxy) { - if (lmtp_proxy_rcpt(client, address, username, detail, delim, - ¶ms) != 0) - return 0; + /* proxied? */ + if ((ret=lmtp_proxy_rcpt(client, cmd, data, + username, detail, delim)) != 0) + return (ret < 0 ? -1 : 0); + /* no */ } - return lmtp_local_rcpt(client, address, username, detail, - ¶ms); + /* local delivery */ + return lmtp_local_rcpt(client, cmd, data, username, detail); } /* * QUIT command */ -int cmd_quit(struct client *client, const char *args ATTR_UNUSED) -{ - client_send_line(client, "221 2.0.0 OK"); - /* don't log the (state name) for successful QUITs */ - i_info("Disconnect from %s: Successful quit", client_remote_id(client)); - client->disconnected = TRUE; - client_destroy(client, NULL, NULL); - return -1; -} - /* * VRFY command */ -int cmd_vrfy(struct client *client, const char *args ATTR_UNUSED) -{ - client_send_line(client, "252 2.3.3 Try RCPT instead"); - return 0; -} - /* * RSET command */ -int cmd_rset(struct client *client, const char *args ATTR_UNUSED) -{ - client_state_reset(client, "RSET"); - client_send_line(client, "250 2.0.0 OK"); - return 0; -} - /* * NOOP command */ -int cmd_noop(struct client *client, const char *args ATTR_UNUSED) -{ - client_send_line(client, "250 2.0.0 OK"); - return 0; -} - /* * DATA command */ @@ -299,96 +97,43 @@ int cmd_noop(struct client *client, const char *args ATTR_UNUSED) static struct istream *cmd_data_get_input(struct client *client) { struct client_state *state = &client->state; - struct istream *cinput, *inputs[3]; - - inputs[0] = i_stream_create_from_data(state->added_headers, - strlen(state->added_headers)); + struct istream *input; if (state->mail_data_output != NULL) { o_stream_unref(&state->mail_data_output); - inputs[1] = i_stream_create_fd(state->mail_data_fd, - MAIL_READ_FULL_BLOCK_SIZE); - i_stream_set_init_buffer_size(inputs[1], + input = i_stream_create_fd(state->mail_data_fd, + MAIL_READ_FULL_BLOCK_SIZE); + i_stream_set_init_buffer_size(input, MAIL_READ_FULL_BLOCK_SIZE); } else { - inputs[1] = i_stream_create_from_data(state->mail_data->data, - state->mail_data->used); + input = i_stream_create_from_data(state->mail_data->data, + state->mail_data->used); } - inputs[2] = NULL; - - cinput = i_stream_create_concat(inputs); - i_stream_set_name(cinput, ""); - i_stream_unref(&inputs[0]); - i_stream_unref(&inputs[1]); - return cinput; + return input; } -static void client_input_data_finish(struct client *client) +static void +cmd_data_create_added_headers(struct client *client, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans) { - client_io_reset(client); - client_state_reset(client, "DATA finished"); - if (i_stream_have_bytes_left(client->input)) - client_input_handle(client); -} + size_t proxy_offset = 0; + string_t *str; -static void client_proxy_finish(void *context) -{ - struct client *client = context; - - lmtp_proxy_deinit(&client->proxy); - client_input_data_finish(client); -} - -static const char *client_get_added_headers(struct client *client) -{ - string_t *str = t_str_new(200); - const char *host, *rcpt_to = NULL; + str = t_str_new(512); /* headers for local deliveries only */ if (client->local != NULL) - lmtp_local_add_headers(client->local, str); - - str_printfa(str, "Received: from %s", client->lhlo); - host = net_ip2addr(&client->remote_ip); - if (host[0] != '\0') - str_printfa(str, " ([%s])", host); - str_append(str, "\r\n"); - if (client->ssl_iostream != NULL) { - str_printfa(str, "\t(using %s)\r\n", - ssl_iostream_get_security_string(client->ssl_iostream)); - } - str_printfa(str, "\tby %s with LMTP id %s", - client->my_domain, client->state.session_id); - - str_append(str, "\r\n\t"); - if (rcpt_to != NULL) - str_printfa(str, "for <%s>", rcpt_to); - str_printfa(str, "; %s\r\n", message_date_create(ioloop_time)); - return str_c(str); -} + lmtp_local_add_headers(client->local, trans, str); -static void client_input_data_write(struct client *client) -{ - struct istream *input; + /* headers for local and proxied messages */ + proxy_offset = str_len(str); + smtp_server_transaction_write_trace_record(str, trans); - /* stop handling client input until saving/proxying is finished */ - timeout_remove(&client->to_idle); - io_remove(&client->io); - i_stream_destroy(&client->dot_input); - - client->state.data_end_timeval = ioloop_timeval; - - input = cmd_data_get_input(client); - if (lmtp_local_rcpt_count(client) != 0) - lmtp_local_data(client, input); - if (client->proxy != NULL) { - client_state_set(client, "DATA", "proxying"); - lmtp_proxy_start(client->proxy, input, - client_proxy_finish, client); - } else { - client_input_data_finish(client); - } - i_stream_unref(&input); + client->state.added_headers_local = + p_strdup(client->state_pool, str_c(str)); + client->state.added_headers_proxy = + client->state.added_headers_local + proxy_offset; } static int @@ -453,129 +198,112 @@ cmd_data_input_add(struct client *client, } } -static void client_input_data_handle(struct client *client) +static int +cmd_data_finish(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) { - struct istream *data_input = client->dot_input; + struct client_state *state = &client->state; + struct istream *input_msg, *input_local, *input_proxy; + struct istream *inputs[3]; + + client->state.data_end_timeval = ioloop_timeval; + + /* finish the message */ + input_msg = cmd_data_get_input(client); + + /* formulate prepended headers for both local and proxy delivery */ + cmd_data_create_added_headers(client, cmd, trans); + + /* construct message streams for local and proxy delivery */ + input_local = input_proxy = NULL; + if (client->local != NULL) { + inputs[0] = i_stream_create_from_data( + state->added_headers_local, + strlen(state->added_headers_local)); + inputs[1] = input_msg; + inputs[2] = NULL; + + input_local = i_stream_create_concat(inputs); + i_stream_set_name(input_local, ""); + i_stream_unref(&inputs[0]); + } + if (client->proxy != NULL) { + inputs[0] = i_stream_create_from_data( + state->added_headers_proxy, + strlen(state->added_headers_proxy)); + inputs[1] = input_msg; + inputs[2] = NULL; + + input_proxy = i_stream_create_concat(inputs); + i_stream_set_name(input_proxy, ""); + i_stream_unref(&inputs[0]); + } + + i_stream_unref(&input_msg); + + /* local delivery */ + if (client->local != NULL) { + lmtp_local_data(client, cmd, trans, input_local); + i_stream_unref(&input_local); + } + /* proxy delivery */ + if (client->proxy != NULL) { + lmtp_proxy_data(client, cmd, trans, input_proxy); + i_stream_unref(&input_proxy); + } + return 0; +} + +int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + struct client *client = (struct client *)conn_ctx; + struct istream *data_input = (struct istream *)trans->context; const unsigned char *data; size_t size; ssize_t ret; + i_assert(client->state.mail_data_output != NULL); + while ((ret = i_stream_read(data_input)) > 0 || ret == -2) { data = i_stream_get_data(data_input, &size); if (cmd_data_input_add(client, data, size) < 0) { - client_destroy(client, "451 4.3.0", - "Temporary internal failure"); - return; + smtp_server_reply(cmd, 451, "4.3.0", + "Temporary internal failure"); + return -1; } + i_stream_skip(data_input, size); } - if (ret == 0) - return; - if (data_input->stream_errno != 0) { + if (ret == 0) + return 0; + if (ret < 0 && data_input->stream_errno != 0) { /* client probably disconnected */ - client_destroy(client, NULL, NULL); - return; + return -1; } - /* the ending "." line was seen. begin saving the mail. */ - client_input_data_write(client); -} - -static void client_input_data(struct client *client) -{ - if (client_input_read(client) < 0) - return; - - client_input_data_handle(client); + /* the ending "." line was seen. finish delivery. */ + return cmd_data_finish(client, cmd, trans); } -int cmd_data(struct client *client, const char *args ATTR_UNUSED) +int cmd_data_begin(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *data_input) { - if (client->state.mail_from == NULL) { - client_send_line(client, "503 5.5.1 MAIL needed first"); - return 0; - } - if ((lmtp_local_rcpt_count(client) + - lmtp_proxy_rcpt_count(client)) == 0) { - client_send_line(client, "554 5.5.1 No valid recipients"); - return 0; - } - - client->state.added_headers = - p_strdup(client->state_pool, client_get_added_headers(client)); + struct client *client = (struct client *)conn_ctx; i_assert(client->state.mail_data == NULL); client->state.mail_data = buffer_create_dynamic(default_pool, 1024*64); - i_assert(client->dot_input == NULL); - client->dot_input = i_stream_create_dot(client->input, TRUE); - client_send_line(client, "354 OK"); - /* send the DATA reply immediately before we start handling any data */ - o_stream_uncork(client->output); - - io_remove(&client->io); - client_state_set(client, "DATA", ""); - client->io = io_add(client->fd_in, IO_READ, client_input_data, client); - client_input_data_handle(client); - return -1; + cmd->context = (void*)client; + + trans->context = (void*)data_input; + return 0; } /* * XCLIENT command */ - -int cmd_xclient(struct client *client, const char *args) -{ - const char *const *tmp; - struct ip_addr remote_ip; - in_port_t remote_port = 0; - unsigned int ttl = UINT_MAX, timeout_secs = 0; - bool args_ok = TRUE; - - if (!client_is_trusted(client)) { - client_send_line(client, "550 You are not from trusted IP"); - return 0; - } - remote_ip.family = 0; - for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) { - if (strncasecmp(*tmp, "ADDR=", 5) == 0) { - const char *addr = *tmp + 5; - bool ipv6 = FALSE; - - if (strncasecmp(addr, "IPV6:", 5) == 0) { - addr += 5; - ipv6 = TRUE; - } - if (net_addr2ip(addr, &remote_ip) < 0 || - (ipv6 && remote_ip.family != AF_INET6)) - args_ok = FALSE; - } else if (strncasecmp(*tmp, "PORT=", 5) == 0) { - if (net_str2port(*tmp + 5, &remote_port) < 0) - args_ok = FALSE; - } else if (strncasecmp(*tmp, "TTL=", 4) == 0) { - if (str_to_uint(*tmp + 4, &ttl) < 0) - args_ok = FALSE; - } else if (strncasecmp(*tmp, "TIMEOUT=", 8) == 0) { - if (str_to_uint(*tmp + 8, &timeout_secs) < 0) - args_ok = FALSE; - } - } - if (!args_ok) { - client_send_line(client, "501 Invalid parameters"); - return 0; - } - - /* args ok, set them and reset the state */ - client_state_reset(client, "XCLIENT"); - if (remote_ip.family != 0) - client->remote_ip = remote_ip; - if (remote_port != 0) - client->remote_port = remote_port; - if (ttl != UINT_MAX) - client->proxy_ttl = ttl; - client->proxy_timeout_secs = timeout_secs; - client_send_line(client, "220 %s %s", client->my_domain, - client->lmtp_set->login_greeting); - return 0; -} diff --git a/src/lmtp/commands.h b/src/lmtp/commands.h index 56310e39fe..f93c4953dc 100644 --- a/src/lmtp/commands.h +++ b/src/lmtp/commands.h @@ -2,16 +2,17 @@ #define COMMANDS_H struct client; +struct smtp_server_cmd_ctx; +struct smtp_server_cmd_helo; -int cmd_lhlo(struct client *client, const char *args); -int cmd_starttls(struct client *client); -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); -int cmd_xclient(struct client *client, const char *args); +int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data); +int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data); +int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans); +int cmd_data_begin(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans, struct istream *data_input); #endif diff --git a/src/lmtp/lmtp-local.c b/src/lmtp/lmtp-local.c index 3414509b9c..7b969e15ff 100644 --- a/src/lmtp/lmtp-local.c +++ b/src/lmtp/lmtp-local.c @@ -2,6 +2,7 @@ #include "lib.h" #include "str.h" +#include "istream.h" #include "strescape.h" #include "array.h" #include "time-util.h" @@ -18,17 +19,18 @@ #include "mail-autoexpunge.h" #include "index/raw/raw-storage.h" #include "master-service.h" +#include "smtp-common.h" +#include "smtp-params.h" #include "smtp-address.h" #include "smtp-submit-settings.h" +#include "smtp-server.h" #include "lda-settings.h" #include "lmtp-settings.h" #include "client.h" #include "main.h" +#include "lmtp-settings.h" #include "lmtp-local.h" -#define ERRSTR_TEMP_MAILBOX_FAIL "451 4.3.0 <%s> Temporary internal error" -#define ERRSTR_TEMP_USERDB_FAIL_PREFIX "451 4.3.0 <%s> " - struct lmtp_local_recipient { struct lmtp_recipient rcpt; char *session_id; @@ -99,13 +101,6 @@ void lmtp_local_deinit(struct lmtp_local **_local) * Recipient */ -unsigned int lmtp_local_rcpt_count(struct client *client) -{ - if (client->local == NULL) - return 0; - return array_count(&client->local->rcpt_to); -} - static void lmtp_local_rcpt_anvil_disconnect(struct lmtp_local_recipient *rcpt) { @@ -113,6 +108,7 @@ lmtp_local_rcpt_anvil_disconnect(struct lmtp_local_recipient *rcpt) if (!rcpt->anvil_connect_sent) return; + rcpt->anvil_connect_sent = FALSE; input = mail_storage_service_user_get_input(rcpt->service_user); master_service_anvil_send(master_service, t_strconcat( @@ -120,7 +116,8 @@ lmtp_local_rcpt_anvil_disconnect(struct lmtp_local_recipient *rcpt) "/", input->username, "\n", NULL)); } -void lmtp_local_rcpt_deinit(struct lmtp_local_recipient *rcpt) +static void +lmtp_local_rcpt_deinit(struct lmtp_local_recipient *rcpt) { if (rcpt->anvil_query != NULL) anvil_client_query_abort(anvil, &rcpt->anvil_query); @@ -136,23 +133,41 @@ static void lmtp_local_rcpt_reply_overquota(struct lmtp_local_recipient *rcpt, const char *error) { - struct client *client = rcpt->rcpt.client; + struct smtp_address *address = rcpt->rcpt.rcpt->path; struct lda_settings *lda_set = mail_storage_service_user_get_set(rcpt->service_user)[2]; - client_send_line(client, "%s <%s> %s", - lda_set->quota_full_tempfail ? "452 4.2.2" : "552 5.2.2", - smtp_address_encode(rcpt->rcpt.address), error); + if (lda_set->quota_full_tempfail) { + smtp_server_reply(rcpt->rcpt.rcpt_cmd, + 452, "4.2.2", "<%s> %s", + smtp_address_encode(address), error); + } else { + smtp_server_reply(rcpt->rcpt.rcpt_cmd, + 552, "5.2.2", "<%s> %s", + smtp_address_encode(address), error); + } } -static void -lmtp_local_rcpt_fail_all(struct lmtp_local *local) +static void ATTR_FORMAT(5,6) +lmtp_local_rcpt_fail_all(struct lmtp_local *local, + struct smtp_server_cmd_ctx *cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) { - struct lmtp_local_recipient *const *rcptp; + struct lmtp_local_recipient *const *rcpts; + const char *msg; + unsigned int count, i; + va_list args; - array_foreach(&local->rcpt_to, rcptp) { - client_send_line(local->client, ERRSTR_TEMP_MAILBOX_FAIL, - smtp_address_encode((*rcptp)->rcpt.address)); + va_start(args, fmt); + msg = t_strdup_vprintf(fmt, args); + va_end(args); + + rcpts = array_get(&local->rcpt_to, &count); + for (i = 0; i < count; i++) { + smtp_server_reply_index(cmd, rcpts[i]->rcpt.index, + status, enh_code, "<%s> %s", + smtp_address_encode(rcpts[i]->rcpt.rcpt->path), msg); } } @@ -164,7 +179,7 @@ static int lmtp_local_rcpt_check_quota(struct lmtp_local_recipient *rcpt) { struct client *client = rcpt->rcpt.client; - struct smtp_address *address = rcpt->rcpt.address; + struct smtp_address *address = rcpt->rcpt.path; struct mail_user *user; struct mail_namespace *ns; struct mailbox *box; @@ -207,6 +222,7 @@ lmtp_local_rcpt_check_quota(struct lmtp_local_recipient *rcpt) "failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); + ret = -1; } } mailbox_free(&box); @@ -214,32 +230,62 @@ lmtp_local_rcpt_check_quota(struct lmtp_local_recipient *rcpt) } if (ret < 0) { - client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, - smtp_address_encode(address)); + smtp_server_reply(rcpt->rcpt.rcpt_cmd, + 451, "4.3.0", "<%s> Temporary internal error", + smtp_address_encode(address)); } return ret; } +static void lmtp_local_rcpt_finished( + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct smtp_server_recipient *trcpt, + unsigned int index) +{ + struct lmtp_local_recipient *rcpt = + (struct lmtp_local_recipient *)cmd->context; + struct client *client = rcpt->rcpt.client; + + if (!smtp_server_command_replied_success(cmd->cmd)) { + /* failed in RCPT command; clean up early */ + lmtp_local_rcpt_deinit(rcpt); + return; + } + + trcpt->context = (void *)rcpt; + + /* add to local recipients */ + array_append(&client->local->rcpt_to, &rcpt, 1); + + rcpt->rcpt.rcpt = trcpt; + rcpt->rcpt.index = index; + rcpt->rcpt.rcpt_cmd = NULL; +} + static bool lmtp_local_rcpt_anvil_finish(struct lmtp_local_recipient *rcpt) { - struct client *client = rcpt->rcpt.client; + struct smtp_server_cmd_ctx *cmd = rcpt->rcpt.rcpt_cmd; int ret; if ((ret = lmtp_local_rcpt_check_quota(rcpt)) < 0) { - mail_storage_service_user_unref(&rcpt->service_user); + lmtp_local_rcpt_deinit(rcpt); return FALSE; } - array_append(&client->local->rcpt_to, &rcpt, 1); - client_send_line(client, "250 2.1.5 OK"); + + smtp_server_reply(cmd, 250, "2.1.5", "OK"); return TRUE; } static void lmtp_local_rcpt_anvil_cb(const char *reply, void *context) { - struct lmtp_local_recipient *rcpt = context; + struct lmtp_local_recipient *rcpt = + (struct lmtp_local_recipient *)context; + struct smtp_server_cmd_ctx *cmd = rcpt->rcpt.rcpt_cmd; struct client *client = rcpt->rcpt.client; + struct smtp_address *address = rcpt->rcpt.path; const struct mail_storage_service_input *input; unsigned int parallel_count = 0; @@ -251,10 +297,9 @@ lmtp_local_rcpt_anvil_cb(const char *reply, void *context) } if (parallel_count >= client->lmtp_set->lmtp_user_concurrency_limit) { - client_send_line(client, ERRSTR_TEMP_USERDB_FAIL_PREFIX - "Too many concurrent deliveries for user", - smtp_address_encode(rcpt->rcpt.address)); - mail_storage_service_user_unref(&rcpt->service_user); + smtp_server_reply(cmd, 451, "4.3.0", + "<%s> Too many concurrent deliveries for user", + smtp_address_encode(address)); } else if (lmtp_local_rcpt_anvil_finish(rcpt)) { rcpt->anvil_connect_sent = TRUE; input = mail_storage_service_user_get_input(rcpt->service_user); @@ -262,30 +307,33 @@ lmtp_local_rcpt_anvil_cb(const char *reply, void *context) "CONNECT\t", my_pid, "\t", master_service_get_name(master_service), "/", input->username, "\n", NULL)); } - - client_io_reset(client); - client_input_handle(client); } int lmtp_local_rcpt(struct client *client, - struct smtp_address *address, - const char *username, const char *detail, - const struct smtp_params_rcpt *params) + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data, + const char *username, const char *detail) { + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_address *address = data->path; + struct smtp_server_transaction *trans; struct lmtp_local_recipient *rcpt; struct mail_storage_service_input input; struct mail_storage_service_user *service_user; const char *session_id, *error = NULL; - int ret; + int ret = 0; + + trans = smtp_server_connection_get_transaction(conn); + i_assert(trans != NULL); /* MAIL command is synchronous */ /* Use a unique session_id for each mail delivery. This is especially important for stats process to not see duplicate sessions. */ - if (client_get_rcpt_count(client) == 0) - session_id = client->state.session_id; + client->state.session_id_seq++; + if (client->state.session_id_seq == 1) + session_id = trans->id; else { - session_id = - t_strdup_printf("%s:%u", client->state.session_id, - client_get_rcpt_count(client)+1); + session_id = t_strdup_printf("%s:%u", + trans->id, client->state.session_id_seq); } i_zero(&input); @@ -299,28 +347,18 @@ int lmtp_local_rcpt(struct client *client, ret = mail_storage_service_lookup(storage_service, &input, &service_user, &error); - if (ret < 0) { i_error("Failed to lookup user %s: %s", username, error); - client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, + smtp_server_reply(cmd, 451, "4.3.0", + "<%s> Temporary internal error", smtp_address_encode(address)); - return 0; + return -1; } if (ret == 0) { - client_send_line(client, - "550 5.1.1 <%s> User doesn't exist: %s", - smtp_address_encode(address), username); - return 0; - } - if (client->proxy != NULL) { - /* NOTE: if this restriction is ever removed, we'll also need - to send different message bodies to local and proxy - (with and without Return-Path: header) */ - client_send_line(client, "451 4.3.0 <%s> " - "Can't handle mixed proxy/non-proxy destinations", - smtp_address_encode(address)); - mail_storage_service_user_unref(&service_user); - return 0; + smtp_server_reply(cmd, 550, "5.1.1", + "<%s> User doesn't exist: %s", + smtp_address_encode(address), username); + return -1; } if (client->local == NULL) @@ -328,15 +366,17 @@ int lmtp_local_rcpt(struct client *client, rcpt = i_new(struct lmtp_local_recipient, 1); rcpt->rcpt.client = client; - rcpt->rcpt.address = smtp_address_clone(client->state_pool, address); - smtp_params_rcpt_copy(client->state_pool, &rcpt->rcpt.params, params); + rcpt->rcpt.path = data->path; + rcpt->rcpt.rcpt_cmd = cmd; rcpt->detail = i_strdup(detail); rcpt->service_user = service_user; rcpt->session_id = i_strdup(session_id); + cmd->context = (void*)rcpt; + data->hook_finished = lmtp_local_rcpt_finished; + if (client->lmtp_set->lmtp_user_concurrency_limit == 0) { (void)lmtp_local_rcpt_anvil_finish(rcpt); - return 0; } else { /* NOTE: username may change as the result of the userdb lookup. Look up the new one via service_user. */ @@ -345,13 +385,12 @@ int lmtp_local_rcpt(struct client *client, const char *query = t_strconcat("LOOKUP\t", master_service_get_name(master_service), "/", str_tabescape(input->username), NULL); - io_remove(&client->io); rcpt->anvil_query = anvil_client_query(anvil, query, lmtp_local_rcpt_anvil_cb, rcpt); - /* stop processing further commands while anvil query is - pending */ - return rcpt->anvil_query == NULL ? 0 : -1; + return 0; } + + return 1; } /* @@ -359,9 +398,9 @@ int lmtp_local_rcpt(struct client *client, */ void lmtp_local_add_headers(struct lmtp_local *local, + struct smtp_server_transaction *trans, string_t *headers) { - struct client *client = local->client; struct lmtp_local_recipient *const *rcpts; const struct lmtp_settings *lmtp_set; const struct smtp_address *rcpt_to = NULL; @@ -369,7 +408,7 @@ void lmtp_local_add_headers(struct lmtp_local *local, void **sets; str_printfa(headers, "Return-Path: <%s>\r\n", - smtp_address_encode(client->state.mail_from)); + smtp_address_encode(trans->mail_from)); rcpts = array_get(&local->rcpt_to, &count); if (count == 1) { @@ -380,10 +419,10 @@ void lmtp_local_add_headers(struct lmtp_local *local, case LMTP_HDR_DELIVERY_ADDRESS_NONE: break; case LMTP_HDR_DELIVERY_ADDRESS_FINAL: - rcpt_to = rcpts[0]->rcpt.address; + rcpt_to = rcpts[0]->rcpt.rcpt->path; break; case LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL: - rcpt_to = rcpts[0]->rcpt.params.orcpt.addr; + rcpt_to = rcpts[0]->rcpt.rcpt->params.orcpt.addr; break; } } @@ -395,11 +434,17 @@ void lmtp_local_add_headers(struct lmtp_local *local, static int lmtp_local_deliver(struct lmtp_local *local, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, struct lmtp_local_recipient *rcpt, struct mail *src_mail, struct mail_deliver_session *session) { struct client *client = local->client; + struct smtp_address *rcpt_to = rcpt->rcpt.rcpt->path; + unsigned int rcpt_idx = rcpt->rcpt.index; + const struct smtp_server_recipient *trcpt = + *array_idx(&trans->rcpt_to, rcpt_idx); struct mail_storage_service_user *service_user = rcpt->service_user; struct mail_deliver_context dctx; struct mail_user *rcpt_user; @@ -407,6 +452,7 @@ lmtp_local_deliver(struct lmtp_local *local, const struct mail_storage_service_input *input; const struct mail_storage_settings *mail_set; struct smtp_submit_settings *smtp_set; + struct smtp_proxy_data proxy_data; struct lda_settings *lda_set; struct mail_namespace *ns; struct setting_parser_context *set_parser; @@ -423,16 +469,19 @@ lmtp_local_deliver(struct lmtp_local *local, mail_set = mail_storage_service_user_get_mail_set(service_user); set_parser = mail_storage_service_user_get_settings_parser(service_user); - if (client->proxy_timeout_secs > 0 && + + smtp_server_connection_get_proxy_data + (client->conn, &proxy_data); + if (proxy_data.timeout_secs > 0 && (mail_set->mail_max_lock_timeout == 0 || - mail_set->mail_max_lock_timeout > client->proxy_timeout_secs)) { + mail_set->mail_max_lock_timeout > proxy_data.timeout_secs)) { /* set lock timeout waits to be less than when proxy has advertised that it's going to timeout the connection. this avoids duplicate deliveries in case the delivery succeeds after the proxy has already disconnected from us. */ line = t_strdup_printf("mail_max_lock_timeout=%us", - client->proxy_timeout_secs <= 1 ? 1 : - client->proxy_timeout_secs-1); + proxy_data.timeout_secs <= 1 ? 1 : + proxy_data.timeout_secs-1); if (settings_parse_line(set_parser, line) < 0) i_unreached(); } @@ -441,13 +490,13 @@ lmtp_local_deliver(struct lmtp_local *local, io_loop_time_refresh(); delivery_time_started = ioloop_timeval; - client_state_set(client, "DATA", username); i_set_failure_prefix("lmtp(%s, %s): ", my_pid, username); if (mail_storage_service_next(storage_service, service_user, &rcpt_user, &error) < 0) { i_error("Failed to initialize user: %s", error); - client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, - smtp_address_encode(rcpt->rcpt.address)); + smtp_server_reply_index(cmd, rcpt_idx, 451, "4.3.0", + "<%s> Temporary internal error", + smtp_address_encode(rcpt_to)); return -1; } local->rcpt_user = rcpt_user; @@ -468,8 +517,9 @@ lmtp_local_deliver(struct lmtp_local *local, } if (ret <= 0) { i_error("Failed to expand settings: %s", error); - client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, - smtp_address_encode(rcpt->rcpt.address)); + smtp_server_reply_index(cmd, rcpt_idx, 451, "4.3.0", + "<%s> Temporary internal error", + smtp_address_encode(rcpt_to)); return -1; } @@ -479,8 +529,9 @@ lmtp_local_deliver(struct lmtp_local *local, rcpt_user, &error) <= 0) { i_error("Failed to expand mail_log_prefix=%s: %s", rcpt_user->set->mail_log_prefix, error); - client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, - smtp_address_encode(rcpt->rcpt.address)); + smtp_server_reply_index(cmd, rcpt_idx, 451, "4.3.0", + "<%s> Temporary internal error", + smtp_address_encode(rcpt_to)); return -1; } i_set_failure_prefix("%s", str_c(str)); @@ -494,21 +545,23 @@ lmtp_local_deliver(struct lmtp_local *local, dctx.src_mail = src_mail; /* MAIL FROM */ - dctx.mail_from = client->state.mail_from; - dctx.mail_params = client->state.mail_params; + dctx.mail_from = trans->mail_from; + smtp_params_mail_copy(dctx.pool, + &dctx.mail_params, &trans->params); /* RCPT TO */ dctx.rcpt_user = rcpt_user; - dctx.rcpt_params = rcpt->rcpt.params; - if (dctx.rcpt_params.orcpt.addr != NULL) { - /* used ORCPT */ - } else if (*dctx.set->lda_original_recipient_header != '\0') { - dctx.rcpt_params.orcpt.addr = mail_deliver_get_address(src_mail, + smtp_params_rcpt_copy(dctx.pool, + &dctx.rcpt_params, &trcpt->params); + if (dctx.rcpt_params.orcpt.addr == NULL && + *dctx.set->lda_original_recipient_header != '\0') { + dctx.rcpt_params.orcpt.addr = + mail_deliver_get_address(src_mail, dctx.set->lda_original_recipient_header); } if (dctx.rcpt_params.orcpt.addr == NULL) - dctx.rcpt_params.orcpt.addr = rcpt->rcpt.address; - dctx.rcpt_to = rcpt->rcpt.address; + dctx.rcpt_params.orcpt.addr = rcpt_to; + dctx.rcpt_to = rcpt_to; if (*rcpt->detail == '\0' || !client->lmtp_set->lmtp_save_to_detail_mailbox) dctx.rcpt_default_mailbox = "INBOX"; @@ -518,12 +571,12 @@ lmtp_local_deliver(struct lmtp_local *local, t_strconcat(ns->prefix, rcpt->detail, NULL); } - dctx.save_dest_mail = array_count(&local->rcpt_to) > 1 && + dctx.save_dest_mail = array_count(&trans->rcpt_to) > 1 && local->first_saved_mail == NULL; dctx.session_time_msecs = timeval_diff_msecs(&client->state.data_end_timeval, - &client->state.mail_from_timeval); + &trans->timestamp); dctx.delivery_time_started = delivery_time_started; if (mail_deliver(&dctx, &storage) == 0) { @@ -531,39 +584,44 @@ lmtp_local_deliver(struct lmtp_local *local, i_assert(local->first_saved_mail == NULL); local->first_saved_mail = dctx.dest_mail; } - client_send_line(client, "250 2.0.0 <%s> %s Saved", - smtp_address_encode(rcpt->rcpt.address), - rcpt->session_id); + smtp_server_reply_index(cmd, rcpt_idx, + 250, "2.0.0", "<%s> %s Saved", + smtp_address_encode(rcpt_to), rcpt->session_id); ret = 0; } else if (dctx.tempfail_error != NULL) { - client_send_line(client, "451 4.2.0 <%s> %s", - smtp_address_encode(rcpt->rcpt.address), - dctx.tempfail_error); + smtp_server_reply_index(cmd, rcpt_idx, + 451, "4.2.0", "<%s> %s", + smtp_address_encode(rcpt_to), + dctx.tempfail_error); ret = -1; } else if (storage != NULL) { error = mail_storage_get_last_error(storage, &mail_error); if (mail_error == MAIL_ERROR_NOQUOTA) { lmtp_local_rcpt_reply_overquota(rcpt, error); } else { - client_send_line(client, "451 4.2.0 <%s> %s", - smtp_address_encode(rcpt->rcpt.address), error); + smtp_server_reply_index(cmd, rcpt_idx, + 451, "4.2.0", "<%s> %s", + smtp_address_encode(rcpt_to), error); } ret = -1; } else { /* This shouldn't happen */ i_error("BUG: Saving failed to unknown storage"); - client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, - smtp_address_encode(rcpt->rcpt.address)); + smtp_server_reply_index(cmd, rcpt_idx, 451, "4.3.0", + "<%s> Temporary internal error", + smtp_address_encode(rcpt_to)); ret = -1; } + lmtp_local_rcpt_anvil_disconnect(rcpt); return ret; } static uid_t lmtp_local_deliver_to_rcpts(struct lmtp_local *local, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, struct mail_deliver_session *session) { - struct client *client = local->client; uid_t first_uid = (uid_t)-1; struct mail *src_mail; struct lmtp_local_recipient *const *rcpts; @@ -571,14 +629,12 @@ lmtp_local_deliver_to_rcpts(struct lmtp_local *local, int ret; src_mail = local->raw_mail; - rcpts = array_get(&local->rcpt_to, &count); for (i = 0; i < count; i++) { struct lmtp_local_recipient *rcpt = rcpts[i]; - ret = lmtp_local_deliver(local, rcpt, - src_mail, session); - client_state_set(client, "DATA", ""); + ret = lmtp_local_deliver(local, cmd, + trans, rcpt, src_mail, session); i_set_failure_prefix("lmtp(%s): ", my_pid); /* succeeded and mail_user is not saved in first_saved_mail */ @@ -606,6 +662,8 @@ lmtp_local_deliver_to_rcpts(struct lmtp_local *local, static int lmtp_local_open_raw_mail(struct lmtp_local *local, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, struct istream *input) { static const char *wanted_headers[] = { @@ -619,12 +677,13 @@ lmtp_local_open_raw_mail(struct lmtp_local *local, enum mail_error error; if (raw_mailbox_alloc_stream(client->raw_mail_user, input, - (time_t)-1, smtp_address_encode(client->state.mail_from), + (time_t)-1, smtp_address_encode(trans->mail_from), &box) < 0) { i_error("Can't open delivery mail as raw: %s", mailbox_get_last_internal_error(box, &error)); mailbox_free(&box); - lmtp_local_rcpt_fail_all(local); + lmtp_local_rcpt_fail_all(local, cmd, + 451, "4.3.0", "Temporary internal error"); return -1; } @@ -637,18 +696,21 @@ lmtp_local_open_raw_mail(struct lmtp_local *local, return 0; } -void lmtp_local_data(struct client *client, struct istream *input) +void lmtp_local_data(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *input) { struct lmtp_local *local = client->local; struct mail_deliver_session *session; uid_t old_uid, first_uid; - if (lmtp_local_open_raw_mail(local, input) < 0) + if (lmtp_local_open_raw_mail(local, cmd, trans, input) < 0) return; session = mail_deliver_session_init(); old_uid = geteuid(); - first_uid = lmtp_local_deliver_to_rcpts(local, session); + first_uid = lmtp_local_deliver_to_rcpts(local, cmd, trans, session); mail_deliver_session_deinit(&session); if (local->first_saved_mail != NULL) { diff --git a/src/lmtp/lmtp-local.h b/src/lmtp/lmtp-local.h index 66bc7da7b2..edd6156f7f 100644 --- a/src/lmtp/lmtp-local.h +++ b/src/lmtp/lmtp-local.h @@ -1,24 +1,28 @@ #ifndef LMTP_LOCAL_H #define LMTP_LOCAL_H -struct smtp_address; -struct smtp_params_rcpt; -struct lmtp_recipient; +#include "net.h" + +struct mail_deliver_session; +struct smtp_server_cmd_ctx; +struct smtp_server_cmd_rcpt; struct lmtp_local; struct client; -unsigned int lmtp_local_rcpt_count(struct client *client); - void lmtp_local_deinit(struct lmtp_local **_local); int lmtp_local_rcpt(struct client *client, - struct smtp_address *address, - const char *username, const char *detail, - const struct smtp_params_rcpt *params); + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data, + const char *username, const char *detail); void lmtp_local_add_headers(struct lmtp_local *local, + struct smtp_server_transaction *trans, string_t *headers); -void lmtp_local_data(struct client *client, struct istream *input); +void lmtp_local_data(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *input); #endif diff --git a/src/lmtp/lmtp-proxy.c b/src/lmtp/lmtp-proxy.c index 21aad788de..82cf7953b4 100644 --- a/src/lmtp/lmtp-proxy.c +++ b/src/lmtp/lmtp-proxy.c @@ -8,24 +8,26 @@ #include "ostream.h" #include "str.h" #include "time-util.h" -#include "smtp-reply.h" +#include "smtp-common.h" +#include "smtp-params.h" +#include "smtp-address.h" +#include "smtp-server.h" #include "smtp-client.h" #include "smtp-client-connection.h" #include "smtp-client-transaction.h" #include "auth-master.h" +#include "settings-parser.h" #include "master-service.h" #include "mail-storage-service.h" +#include "lda-settings.h" #include "client.h" #include "main.h" +#include "lmtp-settings.h" #include "lmtp-proxy.h" -#define LMTP_MAX_LINE_LEN 1024 +#define LMTP_MAX_REPLY_SIZE 4096 #define LMTP_PROXY_DEFAULT_TIMEOUT_MSECS (1000*125) -#define ERRSTR_TEMP_USERDB_FAIL_PREFIX "451 4.3.0 <%s> " -#define ERRSTR_TEMP_USERDB_FAIL \ - ERRSTR_TEMP_USERDB_FAIL_PREFIX "Temporary user lookup failure" - struct lmtp_proxy_rcpt_settings { enum smtp_protocol protocol; const char *host; @@ -36,10 +38,8 @@ struct lmtp_proxy_rcpt_settings { }; struct lmtp_proxy_recipient { + struct lmtp_recipient rcpt; struct lmtp_proxy_connection *conn; - struct smtp_address *address; - char *reply; - unsigned int idx; bool rcpt_to_failed:1; bool data_reply_received:1; @@ -74,13 +74,11 @@ struct lmtp_proxy { unsigned int max_timeout_msecs; - lmtp_proxy_finish_callback_t *finish_callback; - void *finish_context; + struct smtp_server_cmd_ctx *pending_data_cmd; bool finished:1; }; -static void lmtp_proxy_try_finish(struct lmtp_proxy *proxy); static void lmtp_proxy_data_cb(const struct smtp_reply *reply, struct lmtp_proxy_recipient *rcpt); @@ -90,29 +88,32 @@ lmtp_proxy_data_cb(const struct smtp_reply *reply, */ static struct lmtp_proxy * -lmtp_proxy_init(struct client *client) +lmtp_proxy_init(struct client *client, + struct smtp_server_transaction *trans) { struct smtp_client_settings lmtp_set; struct lmtp_proxy *proxy; - i_assert(client->proxy_ttl > 1); - proxy = i_new(struct lmtp_proxy, 1); proxy->client = client; + proxy->trans = trans; i_array_init(&proxy->rcpt_to, 32); i_array_init(&proxy->connections, 32); i_zero(&lmtp_set); lmtp_set.my_hostname = client->my_domain; lmtp_set.dns_client_socket_path = dns_client_socket_path; + lmtp_set.max_reply_size = LMTP_MAX_REPLY_SIZE; + smtp_server_connection_get_proxy_data(client->conn, + &lmtp_set.proxy_data); lmtp_set.proxy_data.source_ip = client->remote_ip; lmtp_set.proxy_data.source_port = client->remote_port; - lmtp_set.proxy_data.ttl_plus_1 = client->proxy_ttl + 1; if (lmtp_set.proxy_data.ttl_plus_1 == 0) lmtp_set.proxy_data.ttl_plus_1 = LMTP_PROXY_DEFAULT_TTL + 1; else lmtp_set.proxy_data.ttl_plus_1--; + lmtp_set.peer_trusted = TRUE; proxy->lmtp_client = smtp_client_init(&lmtp_set); @@ -122,8 +123,6 @@ lmtp_proxy_init(struct client *client) static void lmtp_proxy_recipient_deinit(struct lmtp_proxy_recipient *rcpt) { - i_free(rcpt->address); - i_free(rcpt->reply); i_free(rcpt); } @@ -170,20 +169,16 @@ lmtp_proxy_mail_cb(const struct smtp_reply *proxy_reply ATTR_UNUSED, static void lmtp_proxy_connection_finish(struct lmtp_proxy_connection *conn) { - struct lmtp_proxy *proxy = conn->proxy; - conn->finished = TRUE; conn->lmtp_trans = NULL; - - lmtp_proxy_try_finish(proxy); } static struct lmtp_proxy_connection * lmtp_proxy_get_connection(struct lmtp_proxy *proxy, const struct lmtp_proxy_rcpt_settings *set) { - struct client *client = proxy->client; struct smtp_client_connection *lmtp_conn; + struct smtp_server_transaction *trans = proxy->trans; struct lmtp_proxy_connection *const *conns, *conn; const char *host = (set->hostip.family == 0 ? set->host : net_ip2addr(&set->hostip)); @@ -215,7 +210,7 @@ lmtp_proxy_get_connection(struct lmtp_proxy *proxy, smtp_client_connection_connect(lmtp_conn, NULL, NULL); conn->lmtp_trans = smtp_client_transaction_create(lmtp_conn, - client->state.mail_from, &client->state.mail_params, + trans->mail_from, &trans->params, lmtp_proxy_connection_finish, conn); smtp_client_connection_unref(&lmtp_conn); @@ -227,97 +222,55 @@ lmtp_proxy_get_connection(struct lmtp_proxy *proxy, return conn; } -static bool lmtp_proxy_send_data_replies(struct lmtp_proxy *proxy) +static bool +lmtp_proxy_handle_reply(struct smtp_server_cmd_ctx *cmd, + const struct smtp_reply *reply, + struct smtp_reply *reply_r) { - struct client *client = proxy->client; - struct lmtp_proxy_recipient *const *rcpt; - unsigned int i, count; - - o_stream_cork(client->output); - rcpt = array_get(&proxy->rcpt_to, &count); - for (i = proxy->next_data_reply_idx; i < count; i++) { - if (!(rcpt[i]->rcpt_to_failed || rcpt[i]->data_reply_received)) - break; - o_stream_nsend_str(client->output, - t_strconcat(rcpt[i]->reply, "\r\n", NULL)); - } - o_stream_uncork(client->output); - proxy->next_data_reply_idx = i; - - return i == count; -} + *reply_r = *reply; -static void lmtp_proxy_finish_timeout(struct lmtp_proxy *proxy) -{ - i_assert(!proxy->finished); + if (!smtp_reply_is_remote(reply)) { + const char *detail = ""; - timeout_remove(&proxy->to_finish); - proxy->finished = TRUE; - proxy->finish_callback(proxy->finish_context); -} - -static void lmtp_proxy_try_finish(struct lmtp_proxy *proxy) -{ - if (proxy->finish_callback == NULL) { - /* DATA command hasn't been sent yet */ - return; - } - if (!lmtp_proxy_send_data_replies(proxy)) { - /* we can't received reply from all clients yet */ - return; - } - /* do the actual finishing in a timeout handler, since the finish - callback causes the proxy to be destroyed and the code leading up - to this function can be called from many different places. it's - easier this way rather than having all the callers check if the - proxy was already destroyed. */ - if (proxy->to_finish == NULL) { - proxy->to_finish = timeout_add(0, lmtp_proxy_finish_timeout, - proxy); - } -} - -static void -lmtp_proxy_write_reply(string_t *reply, const struct smtp_reply *proxy_reply) -{ - if (smtp_reply_is_remote(proxy_reply)) { - smtp_reply_write_one_line(reply, proxy_reply); - } else { - str_append(reply, "451 4.4.0 Remote server not answering"); - switch (proxy_reply->status) { + switch (reply->status) { + case SMTP_CLIENT_COMMAND_ERROR_ABORTED: + break; case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: - str_append(reply, " (DNS lookup)"); + detail = " (DNS lookup)"; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: - str_append(reply, " (connect)"); + detail = " (connect)"; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: - str_append(reply, " (connection lost)"); + detail = " (connection lost)"; break; case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: - str_append(reply, " (bad reply)"); - break; + detail = " (bad reply)"; + break; case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: - str_append(reply, " (timed out)"); + detail = " (timed out)"; break; default: break; } + + smtp_server_command_fail(cmd->cmd, 451, "4.4.0", + "Remote server not answering%s", detail); + return FALSE; } + + if (!smtp_reply_has_enhanced_code(reply)) { + reply_r->enhanced_code = + SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0); + } + return TRUE; } /* * RCPT command */ -unsigned int lmtp_proxy_rcpt_count(struct client *client) -{ - if (client->proxy == NULL) - return 0; - return array_count(&client->proxy->rcpt_to); -} - static bool lmtp_proxy_rcpt_parse_fields(struct lmtp_proxy_rcpt_settings *set, const char *const *args, const char **address) @@ -404,37 +357,89 @@ lmtp_proxy_is_ourself(const struct client *client, return TRUE; } +static void +lmtp_proxy_rcpt_destroy(struct smtp_server_cmd_ctx *cmd) +{ + struct lmtp_proxy_recipient *rcpt = + (struct lmtp_proxy_recipient *)cmd->context; + + lmtp_proxy_recipient_deinit(rcpt); +} + +static void +lmtp_proxy_rcpt_finished(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct smtp_server_recipient *trcpt, + unsigned int index) +{ + struct lmtp_proxy_recipient *rcpt = + (struct lmtp_proxy_recipient *)cmd->context; + struct client *client = rcpt->rcpt.client; + + if (!smtp_server_command_replied_success(cmd->cmd)) { + /* failed in RCPT command; clean up early */ + lmtp_proxy_recipient_deinit(rcpt); + return; + } + + cmd->hook_destroy = NULL; + + /* copy to transaction */ + trcpt->context = (void *)rcpt; + + /* add to local recipients */ + array_append(&client->proxy->rcpt_to, &rcpt, 1); + + rcpt->rcpt.rcpt = trcpt; + rcpt->rcpt.index = index; + rcpt->rcpt.rcpt_cmd = NULL; +} + static void lmtp_proxy_rcpt_cb(const struct smtp_reply *proxy_reply, struct lmtp_proxy_recipient *rcpt) { - string_t *reply; + struct smtp_server_cmd_ctx *cmd = rcpt->rcpt.rcpt_cmd; + struct smtp_reply reply; + + if (!lmtp_proxy_handle_reply(cmd, proxy_reply, &reply)) + return; - i_assert(rcpt->reply == NULL); + if (smtp_reply_is_success(proxy_reply)) { + /* if backend accepts it, we accept it too */ - reply = t_str_new(128); - lmtp_proxy_write_reply(reply, proxy_reply); + /* the default 2.0.0 code won't do */ + if (!smtp_reply_has_enhanced_code(proxy_reply)) + reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 0); + } - rcpt->reply = i_strdup(str_c(reply)); - rcpt->rcpt_to_failed = !smtp_reply_is_success(proxy_reply); + /* forward reply */ + smtp_server_reply_forward(cmd, &reply); } int lmtp_proxy_rcpt(struct client *client, - struct smtp_address *address, - const char *username, const char *detail, char delim, - struct smtp_params_rcpt *params) + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data, + const char *username, const char *detail, + char delim) { struct auth_master_connection *auth_conn; struct lmtp_proxy_rcpt_settings set; struct lmtp_proxy_connection *conn; struct lmtp_proxy_recipient *rcpt; + struct smtp_server_transaction *trans; + struct smtp_address *address = data->path; struct auth_user_info info; struct mail_storage_service_input input; const char *const *fields, *errstr, *orig_username = username; + struct smtp_proxy_data proxy_data; struct smtp_address *user; pool_t auth_pool; int ret; + trans = smtp_server_connection_get_transaction(cmd->conn); + i_assert(trans != NULL); /* MAIL command is synchronous */ + i_zero(&input); input.module = input.service = "lmtp"; mail_storage_service_init_settings(storage_service, &input); @@ -446,20 +451,21 @@ int lmtp_proxy_rcpt(struct client *client, info.local_port = client->local_port; info.remote_port = client->remote_port; + // FIXME: make this async auth_pool = pool_alloconly_create("auth lookup", 1024); auth_conn = mail_storage_service_get_auth_conn(storage_service); ret = auth_master_pass_lookup(auth_conn, username, &info, auth_pool, &fields); if (ret <= 0) { - errstr = ret < 0 && fields[0] != NULL ? t_strdup(fields[0]) : - t_strdup_printf(ERRSTR_TEMP_USERDB_FAIL, - smtp_address_encode(address)); + errstr = ret < 0 && fields[0] != NULL ? + t_strdup(fields[0]) : "Temporary user lookup failure"; pool_unref(&auth_pool); if (ret < 0) { - client_send_line(client, "%s", errstr); + smtp_server_reply(cmd, 451, "4.3.0", "<%s> %s", + smtp_address_encode(address), errstr); return -1; } else { - /* user not found from passdb. try userdb also. */ + /* user not found from passdb. revert to local delivery */ return 0; } } @@ -468,7 +474,6 @@ int lmtp_proxy_rcpt(struct client *client, set.port = client->local_port; set.protocol = SMTP_PROTOCOL_LMTP; set.timeout_msecs = LMTP_PROXY_DEFAULT_TIMEOUT_MSECS; - set.params = *params; if (!lmtp_proxy_rcpt_parse_fields(&set, fields, &username)) { /* not proxying this user */ @@ -480,7 +485,7 @@ int lmtp_proxy_rcpt(struct client *client, username, &user, &errstr) < 0) { i_error("%s: Username `%s' returned by passdb lookup is not a valid SMTP address", orig_username, username); - client_send_line(client, "550 5.3.5 <%s> " + smtp_server_reply(cmd, 550, "5.3.5", "<%s> " "Internal user lookup failure", smtp_address_encode(address)); pool_unref(&auth_pool); @@ -494,54 +499,45 @@ int lmtp_proxy_rcpt(struct client *client, } } else if (lmtp_proxy_is_ourself(client, &set)) { i_error("Proxying to <%s> loops to itself", username); - client_send_line(client, "554 5.4.6 <%s> " - "Proxying loops to itself", - smtp_address_encode(address)); + smtp_server_reply(cmd, 554, "5.4.6", + "<%s> Proxying loops to itself", + smtp_address_encode(address)); pool_unref(&auth_pool); return -1; } - if (client->proxy_ttl <= 1) { + smtp_server_connection_get_proxy_data(cmd->conn, &proxy_data); + if (proxy_data.ttl_plus_1 == 1) { i_error("Proxying to <%s> appears to be looping (TTL=0)", username); - client_send_line(client, "554 5.4.6 <%s> " - "Proxying appears to be looping (TTL=0)", - username); - pool_unref(&auth_pool); - return -1; - } - if (client_get_rcpt_count(client) > - lmtp_proxy_rcpt_count(client)) { - client_send_line(client, "451 4.3.0 <%s> " - "Can't handle mixed proxy/non-proxy destinations", + smtp_server_reply(cmd, 554, "5.4.6", + "<%s> Proxying appears to be looping (TTL=0)", smtp_address_encode(address)); pool_unref(&auth_pool); return -1; } if (client->proxy == NULL) - client->proxy = lmtp_proxy_init(client); + client->proxy = lmtp_proxy_init(client, trans); + + data->path = smtp_address_clone(cmd->pool, address); conn = lmtp_proxy_get_connection(client->proxy, &set); - if (conn->failed) { - client_send_line(client, - "451 4.4.0 Remote server not answering"); - pool_unref(&auth_pool); - return -1; - } + pool_unref(&auth_pool); rcpt = i_new(struct lmtp_proxy_recipient, 1); - rcpt->idx = array_count(&client->proxy->rcpt_to); + rcpt->rcpt.client = client; + rcpt->rcpt.rcpt_cmd = cmd; + rcpt->rcpt.path = data->path; rcpt->conn = conn; - rcpt->address = smtp_address_clone(default_pool, address); - array_append(&client->proxy->rcpt_to, &rcpt, 1); + + cmd->context = (void*)rcpt; + cmd->hook_destroy = lmtp_proxy_rcpt_destroy; + data->hook_finished = lmtp_proxy_rcpt_finished; smtp_client_transaction_add_rcpt(conn->lmtp_trans, - address, &set.params, + address, &data->params, lmtp_proxy_rcpt_cb, lmtp_proxy_data_cb, rcpt); - - client_send_line(client, "250 2.1.5 OK"); - pool_unref(&auth_pool); return 1; } @@ -554,46 +550,60 @@ lmtp_proxy_data_cb(const struct smtp_reply *proxy_reply, struct lmtp_proxy_recipient *rcpt) { struct lmtp_proxy_connection *conn = rcpt->conn; - struct client *client = conn->proxy->client; + struct lmtp_proxy *proxy = conn->proxy; + struct smtp_server_cmd_ctx *cmd = proxy->pending_data_cmd; + struct smtp_server_transaction *trans = proxy->trans; + struct smtp_address *address = rcpt->rcpt.rcpt->path; const struct smtp_client_transaction_times *times = smtp_client_transaction_get_times(conn->lmtp_trans); - string_t *reply; + unsigned int rcpt_index = rcpt->rcpt.index; + struct smtp_reply reply; string_t *msg; - i_assert(!rcpt->rcpt_to_failed); - i_assert(rcpt->reply != NULL); - - /* reset timeout in case there are a lot of RCPT TOs */ - if (conn->to != NULL) - timeout_reset(conn->to); - - reply = t_str_new(128); - lmtp_proxy_write_reply(reply, proxy_reply); - - rcpt->reply = i_strdup(str_c(reply)); - rcpt->data_reply_received = TRUE; - + /* compose log message */ msg = t_str_new(128); - str_printfa(msg, "%s: ", client->state.session_id); + str_printfa(msg, "%s: ", trans->id); if (smtp_reply_is_success(proxy_reply)) str_append(msg, "Sent message to"); else str_append(msg, "Failed to send message to"); str_printfa(msg, " <%s> at %s:%u: %s (%u/%u at %u ms)", - smtp_address_encode(rcpt->address), conn->set.host, - conn->set.port, str_c(reply), - rcpt->idx + 1, array_count(&conn->proxy->rcpt_to), + smtp_address_encode(address), + conn->set.host, conn->set.port, + smtp_reply_log(proxy_reply), + rcpt_index + 1, array_count(&trans->rcpt_to), timeval_diff_msecs(&ioloop_timeval, ×->started)); - if (smtp_reply_is_success(proxy_reply) || - smtp_reply_is_remote(proxy_reply)) { - /* the problem isn't with the proxy, it's with the remote side. - so the remote side will log an error, while for us this is - just an info event */ + + /* handle reply */ + if (smtp_reply_is_success(proxy_reply)) { + /* if backend accepts it, we accept it too */ i_info("%s", str_c(msg)); + + /* substitute our own success message */ + smtp_reply_printf(&reply, 250, "<%s> %s Saved", + smtp_address_encode(address), trans->id); + /* do let the enhanced code through */ + if (!smtp_reply_has_enhanced_code(proxy_reply)) + reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 0, 0); + else + reply.enhanced_code = proxy_reply->enhanced_code; + } else { - i_error("%s", str_c(msg)); + if (smtp_reply_is_remote(proxy_reply)) { + /* The problem isn't with the proxy, it's with the + remote side. so the remote side will log an error, + while for us this is just an info event */ + i_info("%s", str_c(msg)); + } else { + i_error("%s", str_c(msg)); + } + + if (!lmtp_proxy_handle_reply(cmd, proxy_reply, &reply)) + return; } - lmtp_proxy_try_finish(conn->proxy); + + /* forward reply */ + smtp_server_reply_index_forward(cmd, rcpt_index, &reply); } static void @@ -603,17 +613,19 @@ lmtp_proxy_data_dummy_cb(const struct smtp_reply *proxy_reply ATTR_UNUSED, /* nothing */ } -void lmtp_proxy_start(struct lmtp_proxy *proxy, struct istream *data_input, - lmtp_proxy_finish_callback_t *callback, void *context) +void lmtp_proxy_data(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input) { + struct lmtp_proxy *proxy = client->proxy; struct lmtp_proxy_connection *const *conns; uoff_t size; i_assert(data_input->seekable); i_assert(proxy->data_input == NULL); - proxy->finish_callback = callback; - proxy->finish_context = context; + proxy->pending_data_cmd = cmd; proxy->data_input = data_input; i_stream_ref(proxy->data_input); if (i_stream_get_size(proxy->data_input, TRUE, &size) < 0) { @@ -642,14 +654,15 @@ void lmtp_proxy_start(struct lmtp_proxy *proxy, struct istream *data_input, array_foreach(&proxy->connections, conns) { struct lmtp_proxy_connection *conn = *conns; + if (conn->finished) { + /* this connection had already failed */ + continue; + } + smtp_client_transaction_set_timeout(conn->lmtp_trans, proxy->max_timeout_msecs); - if (conn->data_input != NULL) { - smtp_client_transaction_send(conn->lmtp_trans, - conn->data_input, - lmtp_proxy_data_dummy_cb, conn); - } + smtp_client_transaction_send(conn->lmtp_trans, + conn->data_input, + lmtp_proxy_data_dummy_cb, conn); } - /* finish if all of the connections have already failed */ - lmtp_proxy_try_finish(proxy); } diff --git a/src/lmtp/lmtp-proxy.h b/src/lmtp/lmtp-proxy.h index 6e7ff10446..ee6fc2e4a5 100644 --- a/src/lmtp/lmtp-proxy.h +++ b/src/lmtp/lmtp-proxy.h @@ -2,31 +2,28 @@ #define LMTP_PROXY_H #include "net.h" -#include "smtp-address.h" + +#include "smtp-common.h" #include "smtp-params.h" #include "smtp-client.h" #define LMTP_PROXY_DEFAULT_TTL 5 -struct smtp_address; +struct smtp_server_cmd_ctx; +struct smtp_server_cmd_rcpt; struct lmtp_proxy; struct client; -typedef void lmtp_proxy_finish_callback_t(void *context); - void lmtp_proxy_deinit(struct lmtp_proxy **proxy); -unsigned int lmtp_proxy_rcpt_count(struct client *client); - int lmtp_proxy_rcpt(struct client *client, - struct smtp_address *address, - const char *username, const char *detail, char delim, - struct smtp_params_rcpt *params); - -/* Start proxying */ -void lmtp_proxy_start(struct lmtp_proxy *proxy, struct istream *data_input, - lmtp_proxy_finish_callback_t *callback, void *context) - ATTR_NULL(3); - + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data, + const char *username, const char *detail, char delim); + +void lmtp_proxy_data(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input); #endif diff --git a/src/lmtp/main.c b/src/lmtp/main.c index 283ec7a439..a499eed5f6 100644 --- a/src/lmtp/main.c +++ b/src/lmtp/main.c @@ -30,6 +30,8 @@ char *dns_client_socket_path, *base_dir; struct mail_storage_service_ctx *storage_service; struct anvil_client *anvil; +struct smtp_server *lmtp_server; + void lmtp_anvil_init(void) { if (anvil == NULL) { @@ -41,7 +43,7 @@ void lmtp_anvil_init(void) static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); - (void)client_create(conn->fd, conn->fd, conn); + (void)client_create(conn->fd, conn->fd, conn->ssl, conn); } static void drop_privileges(void) @@ -67,10 +69,18 @@ static void drop_privileges(void) static void main_init(void) { struct master_service_connection conn; + struct smtp_server_settings lmtp_set; + + i_zero(&lmtp_set); + lmtp_set.protocol = SMTP_PROTOCOL_LMTP; + lmtp_set.auth_optional = TRUE; + lmtp_set.rcpt_domain_optional = TRUE; + + lmtp_server = smtp_server_init(&lmtp_set); if (IS_STANDALONE()) { i_zero(&conn); - (void)client_create(STDIN_FILENO, STDOUT_FILENO, &conn); + (void)client_create(STDIN_FILENO, STDOUT_FILENO, FALSE, &conn); } const char *error, *tmp_socket_path; @@ -88,6 +98,7 @@ static void main_deinit(void) anvil_client_deinit(&anvil); i_free(dns_client_socket_path); i_free(base_dir); + smtp_server_deinit(&lmtp_server); } int main(int argc, char *argv[]) diff --git a/src/lmtp/main.h b/src/lmtp/main.h index 00683421f1..f15b5b00f8 100644 --- a/src/lmtp/main.h +++ b/src/lmtp/main.h @@ -5,6 +5,8 @@ extern char *dns_client_socket_path, *base_dir; extern struct mail_storage_service_ctx *storage_service; extern struct anvil_client *anvil; +extern struct smtp_server *lmtp_server; + void lmtp_anvil_init(void); void listener_client_destroyed(void); -- 2.47.3