From f7548d6d28c313cf80e6f3ef89aed16a19815df1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:51:24 +0200 Subject: Adding upstream version 1:2.3.19.1+dfsg1. Signed-off-by: Daniel Baumann --- src/submission/submission-commands.c | 615 +++++++++++++++++++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100644 src/submission/submission-commands.c (limited to 'src/submission/submission-commands.c') diff --git a/src/submission/submission-commands.c b/src/submission/submission-commands.c new file mode 100644 index 0000000..8f6fbfb --- /dev/null +++ b/src/submission/submission-commands.c @@ -0,0 +1,615 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "submission-common.h" +#include "str.h" +#include "istream.h" +#include "istream-concat.h" +#include "istream-seekable.h" +#include "mail-storage.h" +#include "imap-url.h" +#include "imap-msgpart.h" +#include "imap-msgpart-url.h" +#include "imap-urlauth.h" +#include "imap-urlauth-fetch.h" + +#include "submission-recipient.h" +#include "submission-commands.h" +#include "submission-backend-relay.h" + +/* + * EHLO, HELO commands + */ + +static void +submission_helo_reply_add_extra(struct client *client, + struct smtp_server_reply *reply) +{ + const struct client_extra_capability *cap; + + if (!array_is_created(&client->extra_capabilities)) + return; + + array_foreach(&client->extra_capabilities, cap) { + if (cap->params == NULL) { + smtp_server_reply_ehlo_add(reply, cap->capability); + } else { + smtp_server_reply_ehlo_add_param(reply, cap->capability, + "%s", cap->params); + } + } +} + +void submission_helo_reply_submit(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data) +{ + struct client *client = smtp_server_connection_get_context(cmd->conn); + enum smtp_capability backend_caps = client->backend_capabilities; + struct smtp_server_reply *reply; + uoff_t cap_size; + + reply = smtp_server_reply_create_ehlo(cmd->cmd); + if (!data->helo.old_smtp) { + string_t *burl_params = t_str_new(256); + + str_append(burl_params, "imap"); + if (*client->set->imap_urlauth_host == '\0' || + strcmp(client->set->imap_urlauth_host, + URL_HOST_ALLOW_ANY) == 0) { + str_printfa(burl_params, " imap://%s", + client->set->hostname); + } else { + str_printfa(burl_params, " imap://%s", + client->set->imap_urlauth_host); + } + if (client->set->imap_urlauth_port != 143) { + str_printfa(burl_params, ":%u", + client->set->imap_urlauth_port); + } + + if ((backend_caps & SMTP_CAPABILITY_8BITMIME) != 0) + smtp_server_reply_ehlo_add(reply, "8BITMIME"); + smtp_server_reply_ehlo_add(reply, "AUTH"); + if ((backend_caps & SMTP_CAPABILITY_BINARYMIME) != 0 && + (backend_caps & SMTP_CAPABILITY_CHUNKING) != 0) + smtp_server_reply_ehlo_add(reply, "BINARYMIME"); + smtp_server_reply_ehlo_add_param(reply, + "BURL", "%s", str_c(burl_params)); + smtp_server_reply_ehlo_add(reply, "CHUNKING"); + if ((backend_caps & SMTP_CAPABILITY_DSN) != 0) + smtp_server_reply_ehlo_add(reply, "DSN"); + smtp_server_reply_ehlo_add(reply, + "ENHANCEDSTATUSCODES"); + smtp_server_reply_ehlo_add(reply, + "PIPELINING"); + + cap_size = client_get_max_mail_size(client); + if (cap_size > 0) { + smtp_server_reply_ehlo_add_param(reply, + "SIZE", "%"PRIuUOFF_T, cap_size); + } else { + smtp_server_reply_ehlo_add(reply, "SIZE"); + } + if ((backend_caps & SMTP_CAPABILITY_VRFY) != 0) + smtp_server_reply_ehlo_add(reply, "VRFY"); + + submission_helo_reply_add_extra(client, reply); + } + smtp_server_reply_submit(reply); +} + +int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data) +{ + struct client *client = conn_ctx; + + if (!data->first || + smtp_server_connection_get_state(client->conn, NULL) + >= SMTP_SERVER_STATE_READY) + return client->v.cmd_helo(client, cmd, data); + + /* respond right away */ + submission_helo_reply_submit(cmd, data); + return 1; +} + +int client_default_cmd_helo(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data) +{ + return submission_backend_cmd_helo(client->backend_default, cmd, data); +} + + +/* + * MAIL command + */ + +int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data) +{ + struct client *client = conn_ctx; + + client->state.backend = client->backend_default; + + return client->v.cmd_mail(client, cmd, data); +} + +int client_default_cmd_mail(struct client *client, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data) +{ + if (client->user->anonymous && !client->state.anonymous_allowed) { + /* NOTE: may need to allow anonymous BURL access in the future, + but while that is not supported, deny all anonymous access + explicitly. */ + smtp_server_reply(cmd, 554, "5.7.1", + "Access denied (anonymous user)"); + return -1; + } + + return submission_backend_cmd_mail(client->state.backend, cmd, data); +} + +/* + * RCPT command + */ + +int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_recipient *rcpt) +{ + struct client *client = conn_ctx; + struct submission_recipient *srcpt; + + srcpt = submission_recipient_create(client, rcpt); + + return client->v.cmd_rcpt(client, cmd, srcpt); +} + +int client_default_cmd_rcpt(struct client *client ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct submission_recipient *srcpt) +{ + if (client->user->anonymous && !srcpt->anonymous_allowed) { + /* NOTE: may need to allow anonymous BURL access in the future, + but while that is not supported, deny all anonymous access + explicitly. */ + smtp_server_recipient_reply( + srcpt->rcpt, 554, "5.7.1", + "Access denied (anonymous user)"); + return -1; + } + + return submission_backend_cmd_rcpt(srcpt->backend, cmd, srcpt); +} + +/* + * RSET command + */ + +int cmd_rset(void *conn_ctx, struct smtp_server_cmd_ctx *cmd) +{ + struct client *client = conn_ctx; + + return client->v.cmd_rset(client, cmd); +} + +int client_default_cmd_rset(struct client *client, + struct smtp_server_cmd_ctx *cmd) +{ + struct submission_backend *backend = client->state.backend; + + if (backend == NULL) + backend = client->backend_default; + + /* all backends will also be notified through trans_free(), but that + doesn't allow changing the RSET command response. */ + return submission_backend_cmd_rset(backend, cmd); +} + +/* + * DATA/BDAT commands + */ + +int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + struct client *client = conn_ctx; + struct istream *data_input = client->state.data_input; + uoff_t data_size; + struct istream *inputs[3]; + string_t *added_headers; + const unsigned char *data; + size_t size; + int ret; + + while ((ret = i_stream_read_more(data_input, &data, &size)) > 0) { + 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) + return -1; + + /* Done reading DATA stream; remove it from state and continue with + local variable. */ + client->state.data_input = NULL; + + /* Current data stream position is the data size */ + client->state.data_size = data_input->v_offset; + + /* prepend our own headers */ + added_headers = t_str_new(200); + smtp_server_transaction_write_trace_record( + added_headers, trans, SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL); + + i_stream_seek(data_input, 0); + inputs[0] = i_stream_create_copy_from_data( + str_data(added_headers), str_len(added_headers)); + inputs[1] = data_input; + inputs[2] = NULL; + + data_input = i_stream_create_concat(inputs); + i_stream_set_name(data_input, ""); + data_size = client->state.data_size + str_len(added_headers); + + i_stream_unref(&inputs[0]); + i_stream_unref(&inputs[1]); + + ret = client->v.cmd_data(client, cmd, trans, data_input, data_size); + + i_stream_unref(&data_input); + return ret; +} + +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 = conn_ctx; + struct istream *inputs[2]; + string_t *path; + + if (client->user->anonymous && !client->state.anonymous_allowed) { + smtp_server_reply(cmd, 554, "5.7.1", + "Access denied (anonymous user)"); + return -1; + } + + inputs[0] = data_input; + inputs[1] = NULL; + + path = t_str_new(256); + mail_user_set_get_temp_prefix(path, client->user->set); + client->state.data_input = i_stream_create_seekable_path(inputs, + SUBMISSION_MAIL_DATA_MAX_INMEMORY_SIZE, str_c(path)); + 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) +{ + return submission_backends_cmd_data(client, cmd, trans, + data_input, data_size); +} + +/* + * BURL command + */ + +/* FIXME: RFC 4468 + If the URL argument to BURL refers to binary data, then the submit server + MAY refuse the command or down convert as described in Binary SMTP. + */ + +struct cmd_burl_context { + struct client *client; + struct smtp_server_cmd_ctx *cmd; + + struct imap_urlauth_fetch *urlauth_fetch; + struct imap_msgpart_url *url_fetch; + + bool chunk_last:1; +}; + +static void +cmd_burl_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct cmd_burl_context *burl_cmd) +{ + if (burl_cmd->urlauth_fetch != NULL) + imap_urlauth_fetch_deinit(&burl_cmd->urlauth_fetch); + if (burl_cmd->url_fetch != NULL) + imap_msgpart_url_free(&burl_cmd->url_fetch); +} + +static int +cmd_burl_fetch_cb(struct imap_urlauth_fetch_reply *reply, + bool last, void *context) +{ + struct cmd_burl_context *burl_cmd = context; + struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd; + int ret; + + i_assert(last); + + if (reply == NULL) { + /* fatal failure */ + // FIXME: make this an internal error + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URLAUTH resolution failed"); + return -1; + } + if (!reply->succeeded) { + /* URL fetch failed */ + if (reply->error != NULL) { + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URLAUTH resolution failed: %s", + reply->error); + } else { + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URLAUTH resolution failed"); + } + return 1; + } + + /* URL fetch succeeded */ + ret = smtp_server_connection_data_chunk_add(cmd, + reply->input, reply->size, burl_cmd->chunk_last, FALSE); + if (ret < 0) + return -1; + + /* Command is likely not yet complete at this point, so return 0 */ + return 0; +} + +static int +cmd_burl_fetch_trusted(struct cmd_burl_context *burl_cmd, + struct imap_url *imap_url) +{ + struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd; + struct client *client = burl_cmd->client; + const char *host_name = client->set->imap_urlauth_host; + in_port_t host_port = client->set->imap_urlauth_port; + struct imap_msgpart_open_result result; + const char *error; + + /* validate host */ + if (imap_url->host.name == NULL || + (strcmp(host_name, URL_HOST_ALLOW_ANY) != 0 && + strcmp(imap_url->host.name, host_name) != 0)) { + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URL resolution failed: " + "Inappropriate or missing host name"); + return -1; + } + + /* validate port */ + if ((imap_url->port == 0 && host_port != 143) || + (imap_url->port != 0 && host_port != imap_url->port)) { + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URL resolution failed: " + "Inappropriate server port"); + return -1; + } + + /* retrieve URL */ + if (imap_msgpart_url_create + (client->user, imap_url, &burl_cmd->url_fetch, &error) < 0) { + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URL resolution failed: %s", error); + return -1; + } + if (imap_msgpart_url_read_part(burl_cmd->url_fetch, + &result, &error) <= 0) { + smtp_server_reply(cmd, 554, "5.6.6", + "IMAP URL resolution failed: %s", error); + return -1; + } + + return smtp_server_connection_data_chunk_add(cmd, + result.input, result.size, burl_cmd->chunk_last, FALSE); +} + +static int +cmd_burl_fetch(struct cmd_burl_context *burl_cmd, const char *url, + struct imap_url *imap_url) +{ + struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd; + struct client *client = burl_cmd->client; + + if (client->urlauth_ctx == NULL) { + /* RFC5248, Section 2.4: + + 554 5.7.14 Trust relationship required + + The submission server requires a configured trust + relationship with a third-party server in order to access + the message content. This value replaces the prior use of + X.7.8 for this error condition, thereby updating [RFC4468]. + */ + smtp_server_reply(cmd, 554, "5.7.14", + "No IMAP URLAUTH access available"); + return -1; + } + + /* urlauth */ + burl_cmd->urlauth_fetch = + imap_urlauth_fetch_init(client->urlauth_ctx, + cmd_burl_fetch_cb, burl_cmd); + if (imap_urlauth_fetch_url_parsed(burl_cmd->urlauth_fetch, + url, imap_url, IMAP_URLAUTH_FETCH_FLAG_BODY) == 0) { + /* wait for URL fetch */ + return 0; + } + return 1; +} + +void cmd_burl(struct smtp_server_cmd_ctx *cmd, const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct client *client = smtp_server_connection_get_context(conn); + struct cmd_burl_context *burl_cmd; + const char *const *argv; + enum imap_url_parse_flags url_parse_flags = + IMAP_URL_PARSE_ALLOW_URLAUTH; + struct imap_url *imap_url; + const char *url, *error; + bool chunk_last = FALSE; + int ret = 1; + + smtp_server_connection_data_chunk_init(cmd); + + /* burl-cmd = "BURL" SP absolute-URI [SP end-marker] CRLF + end-marker = "LAST" + */ + argv = t_strsplit(params, " "); + url = argv[0]; + if (url == NULL) { + smtp_server_reply(cmd, 501, "5.5.4", + "Missing chunk URL parameter"); + ret = -1; + } else if (imap_url_parse(url, NULL, url_parse_flags, + &imap_url, &error) < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid chunk URL: %s", error); + ret = -1; + } else if (argv[1] != NULL) { + if (strcasecmp(argv[1], "LAST") != 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid end marker parameter"); + ret = -1; + } else if (argv[2] != NULL) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid parameters"); + ret = -1; + } else { + chunk_last = TRUE; + } + } + + if (ret < 0 || !smtp_server_connection_data_check_state(cmd)) + return; + + if (client->user->anonymous) { + smtp_server_reply(cmd, 554, "5.7.1", + "Access denied (anonymous user)"); + return; + } + + burl_cmd = p_new(cmd->pool, struct cmd_burl_context, 1); + burl_cmd->client = client; + burl_cmd->cmd = cmd; + burl_cmd->chunk_last = chunk_last; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, + cmd_burl_destroy, burl_cmd); + + if (imap_url->uauth_rumpurl == NULL) { + /* direct local url */ + ret = cmd_burl_fetch_trusted(burl_cmd, imap_url); + } else { + ret = cmd_burl_fetch(burl_cmd, url, imap_url); + } + + if (ret == 0 && chunk_last) + smtp_server_command_input_lock(cmd); +} + +/* + * VRFY command + */ + +int cmd_vrfy(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + const char *param) +{ + struct client *client = conn_ctx; + + if (client->user->anonymous) { + smtp_server_reply(cmd, 550, "5.7.1", + "Access denied (anonymous user)"); + return -1; + } + + return client->v.cmd_vrfy(client, cmd, param); +} + +int client_default_cmd_vrfy(struct client *client, + struct smtp_server_cmd_ctx *cmd, const char *param) +{ + return submission_backend_cmd_vrfy(client->backend_default, cmd, param); +} + +/* + * NOOP command + */ + +int cmd_noop(void *conn_ctx, struct smtp_server_cmd_ctx *cmd) +{ + struct client *client = conn_ctx; + + return client->v.cmd_noop(client, cmd); +} + +int client_default_cmd_noop(struct client *client, + struct smtp_server_cmd_ctx *cmd) +{ + return submission_backend_cmd_noop(client->backend_default, cmd); +} + +/* + * QUIT command + */ + +struct cmd_quit_context { + struct client *client; + + struct smtp_server_cmd_ctx *cmd; +}; + +static void cmd_quit_finish(struct cmd_quit_context *quit_cmd) +{ + struct client *client = quit_cmd->client; + struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd; + + timeout_remove(&client->to_quit); + smtp_server_reply_quit(cmd); +} + +static void +cmd_quit_next(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct cmd_quit_context *quit_cmd) +{ + struct client *client = quit_cmd->client; + + /* give backend a brief interval to generate a quit reply */ + client->to_quit = timeout_add(SUBMISSION_MAX_WAIT_QUIT_REPLY_MSECS, + cmd_quit_finish, quit_cmd); +} + +int cmd_quit(void *conn_ctx, struct smtp_server_cmd_ctx *cmd) +{ + struct client *client = conn_ctx; + struct cmd_quit_context *quit_cmd; + + quit_cmd = p_new(cmd->pool, struct cmd_quit_context, 1); + quit_cmd->client = client; + quit_cmd->cmd = cmd; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_quit_next, quit_cmd); + + return client->v.cmd_quit(client, cmd); +} + +int client_default_cmd_quit(struct client *client, + struct smtp_server_cmd_ctx *cmd) +{ + return submission_backend_cmd_quit(client->backend_default, cmd); +} + + -- cgit v1.2.3