summaryrefslogtreecommitdiffstats
path: root/src/submission/submission-commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/submission/submission-commands.c')
-rw-r--r--src/submission/submission-commands.c615
1 files changed, 615 insertions, 0 deletions
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, "<submission DATA>");
+ 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);
+}
+
+