diff options
Diffstat (limited to 'src/lmtp/lmtp-commands.c')
-rw-r--r-- | src/lmtp/lmtp-commands.c | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/src/lmtp/lmtp-commands.c b/src/lmtp/lmtp-commands.c new file mode 100644 index 0000000..dd8b791 --- /dev/null +++ b/src/lmtp/lmtp-commands.c @@ -0,0 +1,340 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lmtp-common.h" +#include "str.h" +#include "istream.h" +#include "istream-concat.h" +#include "ostream.h" +#include "iostream-temp.h" +#include "master-service.h" +#include "settings-parser.h" +#include "lda-settings.h" +#include "mail-user.h" +#include "smtp-address.h" +#include "mail-deliver.h" +#include "mail-error.h" +#include "lmtp-recipient.h" +#include "lmtp-proxy.h" +#include "lmtp-local.h" +#include "lmtp-commands.h" + +/* + * MAIL command + */ + +int cmd_mail(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data) +{ + struct client *client = (struct client *)conn_ctx; + + return client->v.cmd_mail(client, cmd, data); +} + +int client_default_cmd_mail(struct client *client, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_mail *data ATTR_UNUSED) +{ + if (client->lmtp_set->lmtp_user_concurrency_limit > 0) { + /* Connect to anvil before dropping privileges */ + lmtp_anvil_init(); + } + return 1; +} + +/* + * RCPT command + */ + +static int +cmd_rcpt_handle_forward_fields(struct smtp_server_cmd_ctx *cmd, + struct lmtp_recipient *lrcpt) +{ + struct smtp_server_recipient *rcpt = lrcpt->rcpt; + string_t *xforward; + const char *error; + int ret; + + ret = smtp_params_rcpt_decode_extra(&rcpt->params, + LMTP_RCPT_FORWARD_PARAMETER, + &xforward, FALSE, &error); + if (ret < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid "LMTP_RCPT_FORWARD_PARAMETER"= " + "parameter: %s", error); + return -1; + } + if (ret == 0) + return 0; + + /* Drop the parameter */ + (void)smtp_params_rcpt_drop_extra(&rcpt->params, + LMTP_RCPT_FORWARD_PARAMETER, NULL); + + /* Check the real IP rather than the proxied client IP, since XCLIENT + command will update that, thereby making it untrusted. Unlike the + XCLIENT command, the RCPT forward parameter needs to be used after + the XCLIENT is first issued. */ + if (!smtp_server_connection_is_trusted(rcpt->conn)) { + smtp_server_reply(cmd, 550, "5.7.14", + "Unacceptable "LMTP_RCPT_FORWARD_PARAMETER"= " + "parameter: You are not from trusted IP"); + return -1; + } + + lrcpt->forward_fields = p_strdup(rcpt->pool, str_c(xforward)); + return 0; +} + +int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_recipient *rcpt) +{ + struct client *client = (struct client *)conn_ctx; + struct smtp_server_transaction *trans; + struct lmtp_recipient *lrcpt; + + trans = smtp_server_connection_get_transaction(rcpt->conn); + i_assert(trans != NULL); /* MAIL command is synchronous */ + + lrcpt = lmtp_recipient_create(client, trans, rcpt); + + if (cmd_rcpt_handle_forward_fields(cmd, lrcpt) < 0) + return -1; + + return client->v.cmd_rcpt(client, cmd, lrcpt); +} + +int client_default_cmd_rcpt(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct lmtp_recipient *lrcpt) +{ + struct smtp_server_recipient *rcpt = lrcpt->rcpt; + const char *username, *detail; + char delim = '\0'; + int ret; + + i_assert(!smtp_address_isnull(rcpt->path)); + if (*rcpt->path->localpart == '\0' && rcpt->path->domain == NULL) { + smtp_server_recipient_reply( + rcpt, 550, "5.1.1", + "Unacceptable TO: Empty path not allowed"); + return -1; + } + + smtp_address_detail_parse_temp( + client->unexpanded_lda_set->recipient_delimiter, + rcpt->path, &username, &delim, &detail); + i_assert(*username != '\0'); + + /* Make user name and detail available in the recipient event. The + mail_user event (for local delivery) also adds the user field, but + adding it here makes it available to the recipient event in general. + Additionally, the auth lookups performed for local and proxy delivery + can further override the "user" recipient event when the auth service + returns a different user name. In any case, we provide the initial + value here. + */ + event_add_str(rcpt->event, "user", username); + if (detail[0] != '\0') + event_add_str(rcpt->event, "detail", detail); + + if (client->lmtp_set->lmtp_proxy) { + /* proxied? */ + if ((ret=lmtp_proxy_rcpt(client, cmd, lrcpt, + username, detail, delim)) != 0) + return (ret < 0 ? -1 : 0); + /* no */ + } + + /* local delivery */ + return lmtp_local_rcpt(client, cmd, lrcpt, username, detail); +} + +/* + * DATA command + */ + +static void +cmd_data_create_added_headers(struct client *client, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans) +{ + size_t proxy_offset = 0; + string_t *str; + + str = t_str_new(512); + + /* Headers for local deliveries only */ + if (client->local != NULL) + lmtp_local_add_headers(client->local, trans, str); + + /* Headers for local and proxied messages */ + proxy_offset = str_len(str); + if (client->lmtp_set->lmtp_add_received_header) { + const struct lmtp_settings *lmtp_set = client->lmtp_set; + enum smtp_server_trace_rcpt_to_address rcpt_to_address = + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL; + + switch (lmtp_set->parsed_lmtp_hdr_delivery_address) { + case LMTP_HDR_DELIVERY_ADDRESS_NONE: + rcpt_to_address = + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_NONE; + break; + case LMTP_HDR_DELIVERY_ADDRESS_FINAL: + rcpt_to_address = + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL; + break; + case LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL: + rcpt_to_address = + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_ORIGINAL; + break; + } + + smtp_server_transaction_write_trace_record( + str, trans, rcpt_to_address); + } + + 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 +cmd_data_finish(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + struct client_state *state = &client->state; + struct istream *input_msg; + int ret; + + i_assert(HAS_ALL_BITS(trans->flags, + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)); + + client->state.data_end_timeval = ioloop_timeval; + + /* finish the message */ + input_msg = iostream_temp_finish(&state->mail_data_output, + IO_BLOCK_SIZE); + + ret = client->v.cmd_data(client, cmd, trans, + input_msg, client->state.data_size); + i_stream_unref(&input_msg); + + return ret; +} + +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 client_state *state = &client->state; + struct istream *data_input = state->data_input; + const unsigned char *data; + size_t size; + ssize_t ret; + + i_assert(state->mail_data_output != NULL); + + while ((ret = i_stream_read(data_input)) > 0 || ret == -2) { + data = i_stream_get_data(data_input, &size); + if (o_stream_send(state->mail_data_output, + data, size) != (ssize_t)size) { + e_error(client->event, "write(%s) failed: %s", + o_stream_get_name(state->mail_data_output), + o_stream_get_error(state->mail_data_output)); + smtp_server_reply(cmd, 451, "4.3.0", + "Temporary internal failure"); + return -1; + } + + i_stream_skip(data_input, size); + + if (!smtp_server_cmd_data_check_size(cmd)) + return -1; + } + + if (ret == 0) + return 0; + if (ret < 0 && data_input->stream_errno != 0) { + /* Client probably disconnected */ + return -1; + } + + /* Current data stream position is the data size */ + client->state.data_size = data_input->v_offset; + + /* The ending "." line was seen. finish delivery. */ + return cmd_data_finish(client, cmd, trans); +} + +int cmd_data_begin(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input) +{ + struct client *client = (struct client *)conn_ctx; + string_t *path; + + i_assert(client->state.mail_data_output == NULL); + + path = t_str_new(256); + mail_user_set_get_temp_prefix(path, client->raw_mail_user->set); + client->state.mail_data_output = + iostream_temp_create_named(str_c(path), 0, "(lmtp data)"); + + client->state.data_input = data_input; + return 0; +} + +int client_default_cmd_data(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *data_input, + uoff_t data_size ATTR_UNUSED) +{ + struct client_state *state = &client->state; + struct istream *input_local, *input_proxy; + struct istream *inputs[3]; + + /* 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] = data_input; + inputs[2] = NULL; + + input_local = i_stream_create_concat(inputs); + i_stream_set_name(input_local, "<lmtp DATA 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] = data_input; + inputs[2] = NULL; + + input_proxy = i_stream_create_concat(inputs); + i_stream_set_name(input_proxy, "<lmtp DATA proxy>"); + i_stream_unref(&inputs[0]); + } + + /* 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; +} |