diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/submission/submission-backend-relay.c | |
parent | Initial commit. (diff) | |
download | dovecot-upstream.tar.xz dovecot-upstream.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/submission/submission-backend-relay.c | 1260 |
1 files changed, 1260 insertions, 0 deletions
diff --git a/src/submission/submission-backend-relay.c b/src/submission/submission-backend-relay.c new file mode 100644 index 0000000..eededa0 --- /dev/null +++ b/src/submission/submission-backend-relay.c @@ -0,0 +1,1260 @@ +/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ + +#include "submission-common.h" +#include "str.h" +#include "str-sanitize.h" +#include "mail-user.h" +#include "iostream-ssl.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" +#include "smtp-client-command.h" + +#include "submission-recipient.h" +#include "submission-backend-relay.h" + +struct submission_backend_relay { + struct submission_backend backend; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + + bool trans_started:1; + bool trusted:1; + bool quit_confirmed:1; +}; + +static struct submission_backend_vfuncs backend_relay_vfuncs; + +/* + * Common + */ + +/* The command handling of the submission relay service aims to follow the + following rules: + + - Attempt to keep pipelined commands pipelined when relaying them to the + actual relay service. + - Don't forward commands if they're known to fail at the relay server. Errors + can still occur if pipelined commands fail. Abort subsequent pending + commands if such failures affect those commands. + - Keep predictable errors consistent as much as possible; send our own reply + if the error condition is clear (e.g. missing MAIL, RCPT). +*/ + +static bool +backend_relay_handle_relay_reply(struct submission_backend_relay *backend, + struct smtp_server_cmd_ctx *cmd, + const struct smtp_reply *reply, + struct smtp_reply *reply_r) ATTR_NULL(2) +{ + struct client *client = backend->backend.client; + struct mail_user *user = client->user; + const char *enh_code, *msg, *log_msg = NULL; + const char *const *reply_lines; + bool result = TRUE; + + *reply_r = *reply; + + switch (reply->status) { + case SMTP_CLIENT_COMMAND_ERROR_ABORTED: + return FALSE; + case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: + case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: + case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: + enh_code = "4.4.0"; + msg = "Failed to connect to relay server"; + result = FALSE; + break; + case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED: + enh_code = smtp_reply_get_enh_code(reply); + log_msg = "Lost connection to relay server"; + reply_lines = smtp_reply_get_text_lines_omit_prefix(reply); + msg = t_strconcat("Lost connection to relay server:\n", + t_strarray_join(reply_lines, "\n"), NULL); + result = FALSE; + break; + case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: + case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: + case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: + enh_code = "4.4.0"; + log_msg = msg = "Lost connection to relay server"; + result = FALSE; + break; + /* RFC 4954, Section 6: 530 5.7.0 Authentication required + + This response SHOULD be returned by any command other than AUTH, + EHLO, HELO, NOOP, RSET, or QUIT when server policy requires + authentication in order to perform the requested action and + authentication is not currently in force. */ + case 530: + log_msg = "Relay server requires authentication"; + enh_code = "4.3.5", + msg = "Internal error occurred. " + "Refer to server log for more information."; + result = FALSE; + break; + default: + break; + } + + if (!result) { + const char *detail = "", *reason; + + i_assert(msg != NULL); + + switch (reply->status) { + case SMTP_CLIENT_COMMAND_ERROR_ABORTED: + i_unreached(); + case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: + detail = " (DNS lookup)"; + break; + case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: + case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: + detail = " (connect)"; + break; + case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: + case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED: + if (backend->quit_confirmed) + return FALSE; + detail = " (connection lost)"; + break; + case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: + detail = " (bad reply)"; + break; + case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: + detail = " (timed out)"; + break; + default: + break; + } + + reason = t_strdup_printf("%s%s", msg, detail); + smtp_client_transaction_destroy(&backend->trans); + if (log_msg != NULL) { + if (smtp_reply_is_remote(reply)) { + i_error("%s: %s", + log_msg, smtp_reply_log(reply)); + } else if (user->mail_debug) { + i_debug("%s: %s", + log_msg, smtp_reply_log(reply)); + } + } + submission_backend_fail(&backend->backend, cmd, + enh_code, reason); + return FALSE; + } + + if (!smtp_reply_has_enhanced_code(reply)) { + reply_r->enhanced_code = + SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0); + } + return TRUE; +} + +/* + * Mail transaction + */ + +static void +backend_relay_trans_finished(struct submission_backend_relay *backend) +{ + backend->trans = NULL; +} + +static void +backend_relay_trans_start_callback( + const struct smtp_reply *relay_reply ATTR_UNUSED, + struct submission_backend_relay *backend ATTR_UNUSED) +{ + /* nothing to do */ +} + +static void +backend_relay_trans_start(struct submission_backend *_backend, + struct smtp_server_transaction *trans ATTR_UNUSED, + const struct smtp_address *path, + const struct smtp_params_mail *params) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + if (backend->trans == NULL) { + backend->trans_started = TRUE; + backend->trans = smtp_client_transaction_create( + backend->conn, path, params, 0, + backend_relay_trans_finished, backend); + smtp_client_transaction_set_immediate(backend->trans, TRUE); + smtp_client_transaction_start( + backend->trans, backend_relay_trans_start_callback, + backend); + } else if (!backend->trans_started) { + backend->trans_started = TRUE; + smtp_client_transaction_start_empty( + backend->trans, path, params, + backend_relay_trans_start_callback, backend); + } +} + +static void +backend_relay_trans_free(struct submission_backend *_backend, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + backend->trans_started = FALSE; + + if (backend->trans == NULL) + return; + + smtp_client_transaction_destroy(&backend->trans); +} + +struct smtp_client_transaction * +submission_backend_relay_init_transaction( + struct submission_backend_relay *backend, + enum smtp_client_transaction_flags flags) +{ + i_assert(backend->trans == NULL); + + backend->trans = smtp_client_transaction_create_empty( + backend->conn, flags, + backend_relay_trans_finished, backend); + smtp_client_transaction_set_immediate(backend->trans, TRUE); + + return backend->trans; +} + +/* + * EHLO, HELO commands + */ + +struct relay_cmd_helo_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + struct smtp_server_cmd_helo *data; + + struct smtp_client_command *cmd_relayed; +}; + +static void +relay_cmd_helo_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_helo_context *helo_cmd) +{ + i_assert(helo_cmd != NULL); + if (helo_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&helo_cmd->cmd_relayed); +} + +static void +relay_cmd_helo_update_xclient(struct submission_backend_relay *backend, + struct smtp_server_cmd_helo *data) +{ + struct smtp_proxy_data proxy_data; + + if (!backend->trusted) + return; + + i_zero(&proxy_data); + proxy_data.helo = data->helo.domain; + smtp_client_connection_update_proxy_data(backend->conn, &proxy_data); +} + +static void +relay_cmd_helo_reply(struct smtp_server_cmd_ctx *cmd, + struct relay_cmd_helo_context *helo_cmd) +{ + struct submission_backend_relay *backend = helo_cmd->backend; + + if (helo_cmd->data->changed) + relay_cmd_helo_update_xclient(backend, helo_cmd->data); + + T_BEGIN { + submission_backend_helo_reply_submit(&backend->backend, cmd, + helo_cmd->data); + } T_END; +} + +static void +relay_cmd_helo_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_helo_context *helo_cmd) +{ + i_assert(helo_cmd != NULL); + + struct smtp_server_cmd_ctx *cmd = helo_cmd->cmd; + struct submission_backend_relay *backend = helo_cmd->backend; + struct smtp_reply reply; + + /* finished relaying EHLO command to relay server */ + helo_cmd->cmd_relayed = NULL; + + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + if (smtp_reply_is_success(&reply)) { + relay_cmd_helo_reply(cmd, helo_cmd); + } else { + /* RFC 2034, Section 4: + + These codes must appear in all 2xx, 4xx, and 5xx response + lines other than initial greeting and any response to HELO + or EHLO. + */ + reply.enhanced_code = SMTP_REPLY_ENH_CODE_NONE; + smtp_server_reply_forward(cmd, &reply); + } +} + +static void +relay_cmd_helo_start(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_helo_context *helo_cmd) +{ + struct submission_backend_relay *backend = helo_cmd->backend; + + if (helo_cmd->data->changed) + relay_cmd_helo_update_xclient(backend, helo_cmd->data); +} + +static int +backend_relay_cmd_helo(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct relay_cmd_helo_context *helo_cmd; + + helo_cmd = p_new(cmd->pool, struct relay_cmd_helo_context, 1); + helo_cmd->backend = backend; + helo_cmd->cmd = cmd; + helo_cmd->data = data; + + /* This is not the first HELO/EHLO; just relay a RSET command */ + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, + relay_cmd_helo_start, helo_cmd); + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, + relay_cmd_helo_destroy, helo_cmd); + helo_cmd->cmd_relayed = smtp_client_command_rset_submit( + backend->conn, 0, relay_cmd_helo_callback, helo_cmd); + return 0; +} + +/* + * MAIL command + */ + +struct relay_cmd_mail_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + struct smtp_server_cmd_mail *data; + + struct smtp_client_transaction_mail *relay_mail; +}; + +static void +relay_cmd_mail_replied(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_mail_context *mail_cmd) +{ + if (mail_cmd->relay_mail != NULL) + smtp_client_transaction_mail_abort(&mail_cmd->relay_mail); +} + +static void +relay_cmd_mail_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_mail_context *mail_cmd) +{ + i_assert(mail_cmd != NULL); + + struct smtp_server_cmd_ctx *cmd = mail_cmd->cmd; + struct submission_backend_relay *backend = mail_cmd->backend; + struct smtp_reply reply; + + /* finished relaying MAIL command to relay server */ + mail_cmd->relay_mail = NULL; + + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + if (smtp_reply_is_success(relay_reply)) { + /* if relay accepts it, we accept it too */ + + /* the default 2.0.0 code won't do */ + if (!smtp_reply_has_enhanced_code(relay_reply)) + reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 0); + } + + /* forward reply */ + smtp_server_reply_forward(cmd, &reply); +} + +static int +relay_cmd_mail_parameter_auth(struct submission_backend_relay *backend, + struct smtp_server_cmd_ctx *cmd, + enum smtp_capability relay_caps, + struct smtp_server_cmd_mail *data) +{ + struct client *client = backend->backend.client; + struct smtp_params_mail *params = &data->params; + struct smtp_address *auth_addr; + const char *error; + + if ((relay_caps & SMTP_CAPABILITY_AUTH) == 0) + return 0; + + auth_addr = NULL; + if (smtp_address_parse_username(cmd->pool, client->user->username, + &auth_addr, &error) < 0) { + i_warning("Username `%s' is not a valid SMTP address: %s", + client->user->username, error); + } + + params->auth = auth_addr; + return 0; +} + +static int +relay_cmd_mail_parameter_size(struct submission_backend_relay *backend, + struct smtp_server_cmd_ctx *cmd, + enum smtp_capability relay_caps, + struct smtp_server_cmd_mail *data) +{ + struct client *client = backend->backend.client; + uoff_t max_size; + + /* SIZE=<size-value>: RFC 1870 */ + + if (data->params.size == 0 || (relay_caps & SMTP_CAPABILITY_SIZE) == 0) + return 0; + + /* determine actual size limit (account for our additions) */ + max_size = client_get_max_mail_size(client); + if (max_size > 0 && data->params.size > max_size) { + smtp_server_reply( + cmd, 552, "5.3.4", + "Message size exceeds fixed maximum message size"); + return -1; + } + + /* relay the SIZE parameter (account for additional size) */ + data->params.size += SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE; + return 0; +} + +static int +backend_relay_cmd_mail(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + enum smtp_capability relay_caps = + smtp_client_connection_get_capabilities(backend->conn); + struct relay_cmd_mail_context *mail_cmd; + + /* check and adjust parameters where necessary */ + if (relay_cmd_mail_parameter_auth(backend, cmd, relay_caps, data) < 0) + return -1; + if (relay_cmd_mail_parameter_size(backend, cmd, relay_caps, data) < 0) + return -1; + + /* queue command (pipeline) */ + mail_cmd = p_new(cmd->pool, struct relay_cmd_mail_context, 1); + mail_cmd->backend = backend; + mail_cmd->cmd = cmd; + mail_cmd->data = data; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, + relay_cmd_mail_replied, mail_cmd); + + if (backend->trans == NULL) { + /* start client transaction */ + backend->trans_started = TRUE; + backend->trans = smtp_client_transaction_create( + backend->conn, data->path, &data->params, 0, + backend_relay_trans_finished, backend); + smtp_client_transaction_set_immediate(backend->trans, TRUE); + smtp_client_transaction_start( + backend->trans, relay_cmd_mail_callback, mail_cmd); + } else { + /* forward pipelined MAIL command */ + i_assert(backend->trans_started); + mail_cmd->relay_mail = smtp_client_transaction_add_mail( + backend->trans, data->path, &data->params, + relay_cmd_mail_callback, mail_cmd); + } + return 0; +} + +/* + * RCPT command + */ + +struct relay_cmd_rcpt_context { + struct submission_backend_relay *backend; + struct submission_recipient *rcpt; + + struct smtp_server_cmd_ctx *cmd; + + struct smtp_client_transaction_rcpt *relay_rcpt; +}; + +static void +relay_cmd_rcpt_replied(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_rcpt_context *rcpt_cmd) +{ + if (rcpt_cmd->relay_rcpt != NULL) + smtp_client_transaction_rcpt_abort(&rcpt_cmd->relay_rcpt); +} + +static void +relay_cmd_rcpt_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_rcpt_context *rcpt_cmd) +{ + i_assert(rcpt_cmd != NULL); + + struct smtp_server_cmd_ctx *cmd = rcpt_cmd->cmd; + struct submission_backend_relay *backend = rcpt_cmd->backend; + struct submission_recipient *srcpt = rcpt_cmd->rcpt; + struct smtp_server_recipient *rcpt = srcpt->rcpt; + struct smtp_client_transaction_rcpt *relay_rcpt = rcpt_cmd->relay_rcpt; + struct smtp_reply reply; + + /* finished relaying RCPT command to relay server */ + rcpt_cmd->relay_rcpt = NULL; + + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + if (smtp_reply_is_success(&reply)) { + /* the default 2.0.0 code won't do */ + if (!smtp_reply_has_enhanced_code(&reply)) + reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 5); + + i_assert(relay_rcpt != NULL); + srcpt->backend_context = relay_rcpt; + } + + /* forward reply */ + smtp_server_recipient_reply_forward(rcpt, &reply); +} + +static int +backend_relay_cmd_rcpt(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd, + struct submission_recipient *srcpt) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct smtp_server_recipient *rcpt = srcpt->rcpt; + struct relay_cmd_rcpt_context *rcpt_cmd; + + /* queue command (pipeline) */ + rcpt_cmd = p_new(cmd->pool, struct relay_cmd_rcpt_context, 1); + rcpt_cmd->backend = backend; + rcpt_cmd->cmd = cmd; + rcpt_cmd->rcpt = srcpt; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, + relay_cmd_rcpt_replied, rcpt_cmd); + + if (backend->trans == NULL) + (void)submission_backend_relay_init_transaction(backend, 0); + rcpt_cmd->relay_rcpt = smtp_client_transaction_add_pool_rcpt( + backend->trans, rcpt->pool, rcpt->path, &rcpt->params, + relay_cmd_rcpt_callback, rcpt_cmd); + return 0; +} + +/* + * RSET command + */ + +struct relay_cmd_rset_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + + struct smtp_client_command *cmd_relayed; +}; + +static void +relay_cmd_rset_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_rset_context *rset_cmd) +{ + i_assert(rset_cmd != NULL); + if (rset_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&rset_cmd->cmd_relayed); +} + +static void +relay_cmd_rset_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_rset_context *rset_cmd) +{ + i_assert(rset_cmd != NULL); + + struct smtp_server_cmd_ctx *cmd = rset_cmd->cmd; + struct submission_backend_relay *backend = rset_cmd->backend; + struct smtp_reply reply; + + /* finished relaying MAIL command to relay server */ + rset_cmd->cmd_relayed = NULL; + + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + /* forward reply */ + smtp_server_reply_forward(cmd, &reply); +} + +static int +backend_relay_cmd_rset(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct relay_cmd_rset_context *rset_cmd; + + rset_cmd = p_new(cmd->pool, struct relay_cmd_rset_context, 1); + rset_cmd->backend = backend; + rset_cmd->cmd = cmd; + + if (backend->trans != NULL) { + /* RSET pipelined after MAIL */ + smtp_client_transaction_reset(backend->trans, + relay_cmd_rset_callback, + rset_cmd); + } else { + /* RSET alone */ + smtp_server_command_add_hook(cmd->cmd, + SMTP_SERVER_COMMAND_HOOK_DESTROY, + relay_cmd_rset_destroy, rset_cmd); + rset_cmd->cmd_relayed = smtp_client_command_rset_submit( + backend->conn, 0, relay_cmd_rset_callback, rset_cmd); + } + return 0; +} + +/* + * DATA/BDAT commands + */ + +struct relay_cmd_data_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + struct smtp_server_transaction *trans; +}; + +static void +relay_cmd_data_rcpt_callback(const struct smtp_reply *relay_reply, + struct submission_recipient *srcpt) +{ + struct smtp_server_recipient *rcpt = srcpt->rcpt; + struct smtp_server_cmd_ctx *cmd = rcpt->cmd; + struct submission_backend_relay *backend = + (struct submission_backend_relay *)srcpt->backend; + struct client *client = srcpt->backend->client; + struct smtp_server_transaction *trans = + smtp_server_connection_get_transaction(client->conn); + struct smtp_reply reply; + + i_assert(HAS_ALL_BITS(trans->flags, + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)); + + /* check for fatal problems */ + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + if (smtp_reply_is_success(&reply)) { + i_info("Successfully relayed message: " + "from=<%s>, to=<%s>, size=%"PRIuUOFF_T", " + "id=%s, rcpt=%u/%u, reply=`%s'", + smtp_address_encode(trans->mail_from), + smtp_address_encode(rcpt->path), + client->state.data_size, trans->id, + rcpt->index, array_count(&trans->rcpt_to), + str_sanitize(smtp_reply_log(&reply), 128)); + + } else { + i_info("Failed to relay message: " + "from=<%s>, to=<%s>, size=%"PRIuUOFF_T", " + "rcpt=%u/%u, reply=`%s'", + smtp_address_encode(trans->mail_from), + smtp_address_encode(rcpt->path), + client->state.data_size, rcpt->index, + array_count(&trans->rcpt_to), + str_sanitize(smtp_reply_log(&reply), 128)); + } + + smtp_server_recipient_reply_forward(rcpt, &reply); +} + +static void +relay_cmd_data_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_data_context *data_ctx) +{ + i_assert(data_ctx != NULL); + + struct smtp_server_cmd_ctx *cmd = data_ctx->cmd; + struct smtp_server_transaction *trans = data_ctx->trans; + struct submission_backend_relay *backend = data_ctx->backend; + struct client *client = backend->backend.client; + struct smtp_reply reply; + + /* finished relaying message to relay server */ + + if (HAS_ALL_BITS(trans->flags, + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)) { + /* handled recipient replies individually */ + return; + } + + /* check for fatal problems */ + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + if (smtp_reply_is_success(&reply)) { + i_info("Successfully relayed message: " + "from=<%s>, size=%"PRIuUOFF_T", " + "id=%s, nrcpt=%u, reply=`%s'", + smtp_address_encode(trans->mail_from), + client->state.data_size, trans->id, + array_count(&trans->rcpt_to), + str_sanitize(smtp_reply_log(&reply), 128)); + + } else { + i_info("Failed to relay message: " + "from=<%s>, size=%"PRIuUOFF_T", nrcpt=%u, reply=`%s'", + smtp_address_encode(trans->mail_from), + client->state.data_size, array_count(&trans->rcpt_to), + str_sanitize(smtp_reply_log(&reply), 128)); + } + + smtp_server_reply_forward(cmd, &reply); +} + +static void +backend_relay_cmd_data_init_callbacks(struct submission_backend_relay *backend, + struct smtp_server_transaction *trans) +{ + struct client *client = backend->backend.client; + struct submission_recipient *rcpt; + + if (!HAS_ALL_BITS(trans->flags, + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)) + return; + + array_foreach_elem(&client->rcpt_to, rcpt) { + struct smtp_client_transaction_rcpt *relay_rcpt = + rcpt->backend_context; + + smtp_client_transaction_rcpt_set_data_callback( + relay_rcpt, relay_cmd_data_rcpt_callback, rcpt); + } +} + +static int +backend_relay_cmd_data(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *data_input, uoff_t data_size ATTR_UNUSED) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct relay_cmd_data_context *data_ctx; + + /* start relaying to relay server */ + data_ctx = p_new(trans->pool, struct relay_cmd_data_context, 1); + data_ctx->backend = backend; + data_ctx->cmd = cmd; + data_ctx->trans = trans; + trans->context = (void*)data_ctx; + + i_assert(backend->trans != NULL); + + backend_relay_cmd_data_init_callbacks(backend, trans); + + smtp_client_transaction_send(backend->trans, data_input, + relay_cmd_data_callback, data_ctx); + return 0; +} + +/* + * VRFY command + */ + +struct relay_cmd_vrfy_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + + struct smtp_client_command *cmd_relayed; +}; + +static void +relay_cmd_vrfy_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_vrfy_context *vrfy_cmd) +{ + i_assert(vrfy_cmd != NULL); + if (vrfy_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&vrfy_cmd->cmd_relayed); +} + +static void +relay_cmd_vrfy_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_vrfy_context *vrfy_cmd) +{ + i_assert(vrfy_cmd != NULL); + + struct smtp_server_cmd_ctx *cmd = vrfy_cmd->cmd; + struct submission_backend_relay *backend = vrfy_cmd->backend; + struct smtp_reply reply; + + /* finished relaying VRFY command to relay server */ + vrfy_cmd->cmd_relayed = NULL; + + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + /* RFC 5321, Section 3.5.3: + + A server MUST NOT return a 250 code in response to a VRFY or EXPN + command unless it has actually verified the address. In particular, + a server MUST NOT return 250 if all it has done is to verify that the + syntax given is valid. In that case, 502 (Command not implemented) + or 500 (Syntax error, command unrecognized) SHOULD be returned. As + stated elsewhere, implementation (in the sense of actually validating + addresses and returning information) of VRFY and EXPN are strongly + recommended. Hence, implementations that return 500 or 502 for VRFY + are not in full compliance with this specification. + */ + if (reply.status == 500 || reply.status == 502) { + smtp_server_cmd_vrfy_reply_default(cmd); + return; + } + + if (!smtp_reply_has_enhanced_code(&reply)) { + switch (relay_reply->status) { + case 250: + case 251: + case 252: + reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 5, 0); + break; + default: + break; + } + } + + smtp_server_reply_forward(cmd, &reply); +} + +static int +backend_relay_cmd_vrfy(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd, const char *param) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct relay_cmd_vrfy_context *vrfy_cmd; + + vrfy_cmd = p_new(cmd->pool, struct relay_cmd_vrfy_context, 1); + vrfy_cmd->backend = backend; + vrfy_cmd->cmd = cmd; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, + relay_cmd_vrfy_destroy, vrfy_cmd); + vrfy_cmd->cmd_relayed = smtp_client_command_vrfy_submit( + backend->conn, 0, param, relay_cmd_vrfy_callback, vrfy_cmd); + return 0; +} + +/* + * NOOP command + */ + +struct relay_cmd_noop_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + + struct smtp_client_command *cmd_relayed; +}; + +static void +relay_cmd_noop_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_noop_context *noop_cmd) +{ + i_assert(noop_cmd != NULL); + if (noop_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&noop_cmd->cmd_relayed); +} + +static void +relay_cmd_noop_callback(const struct smtp_reply *relay_reply, + struct relay_cmd_noop_context *noop_cmd) +{ + i_assert(noop_cmd != NULL); + + struct smtp_server_cmd_ctx *cmd = noop_cmd->cmd; + struct submission_backend_relay *backend = noop_cmd->backend; + struct smtp_reply reply; + + /* finished relaying NOOP command to relay server */ + noop_cmd->cmd_relayed = NULL; + + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) + return; + + if (smtp_reply_is_success(&reply)) + smtp_server_cmd_noop_reply_success(cmd); + else + smtp_server_reply_forward(cmd, &reply); +} + +static int +backend_relay_cmd_noop(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct relay_cmd_noop_context *noop_cmd; + + noop_cmd = p_new(cmd->pool, struct relay_cmd_noop_context, 1); + noop_cmd->backend = backend; + noop_cmd->cmd = cmd; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, + relay_cmd_noop_destroy, noop_cmd); + noop_cmd->cmd_relayed = smtp_client_command_noop_submit( + backend->conn, 0, relay_cmd_noop_callback, noop_cmd); + return 0; +} + +/* + * QUIT command + */ + +struct relay_cmd_quit_context { + struct submission_backend_relay *backend; + + struct smtp_server_cmd_ctx *cmd; + + struct smtp_client_command *cmd_relayed; +}; + +static void +relay_cmd_quit_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_quit_context *quit_cmd) +{ + i_assert(quit_cmd != NULL); + if (quit_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&quit_cmd->cmd_relayed); +} + +static void relay_cmd_quit_relayed_destroy(void *context) +{ + struct relay_cmd_quit_context *quit_cmd = context; + + i_assert(quit_cmd != NULL); + quit_cmd->cmd_relayed = NULL; +} + +static void +relay_cmd_quit_replied(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_quit_context *quit_cmd) +{ + if (quit_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&quit_cmd->cmd_relayed); +} + +static void relay_cmd_quit_finish(struct relay_cmd_quit_context *quit_cmd) +{ + struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd; + + quit_cmd->backend->quit_confirmed = TRUE; + if (quit_cmd->cmd_relayed != NULL) + smtp_client_command_abort(&quit_cmd->cmd_relayed); + smtp_server_reply_quit(cmd); +} + +static void +relay_cmd_quit_callback(const struct smtp_reply *relay_reply ATTR_UNUSED, + struct relay_cmd_quit_context *quit_cmd) +{ + i_assert(quit_cmd != NULL); + quit_cmd->cmd_relayed = NULL; + relay_cmd_quit_finish(quit_cmd); +} + +static void relay_cmd_quit_relay(struct relay_cmd_quit_context *quit_cmd) +{ + struct submission_backend_relay *backend = quit_cmd->backend; + struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd; + + if (quit_cmd->cmd_relayed != NULL) + return; + + if (smtp_client_connection_get_state(backend->conn) + < SMTP_CLIENT_CONNECTION_STATE_READY) { + /* Don't bother relaying QUIT command when relay is not + fully initialized. */ + quit_cmd->backend->quit_confirmed = TRUE; + smtp_server_reply_quit(cmd); + return; + } + + /* RFC 5321, Section 4.1.1.10: + + The sender MUST NOT intentionally close the transmission channel + until it sends a QUIT command, and it SHOULD wait until it receives + the reply (even if there was an error response to a previous + command). */ + quit_cmd->cmd_relayed = + smtp_client_command_new(backend->conn, 0, + relay_cmd_quit_callback, quit_cmd); + smtp_client_command_write(quit_cmd->cmd_relayed, "QUIT"); + smtp_client_command_set_abort_callback( + quit_cmd->cmd_relayed, + relay_cmd_quit_relayed_destroy, quit_cmd); + smtp_client_command_submit(quit_cmd->cmd_relayed); +} + +static void +relay_cmd_quit_next(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct relay_cmd_quit_context *quit_cmd) +{ + /* QUIT command is next to reply */ + relay_cmd_quit_relay(quit_cmd); +} + +static int +backend_relay_cmd_quit(struct submission_backend *_backend, + struct smtp_server_cmd_ctx *cmd) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + struct relay_cmd_quit_context *quit_cmd; + + quit_cmd = p_new(cmd->pool, struct relay_cmd_quit_context, 1); + quit_cmd->backend = backend; + quit_cmd->cmd = cmd; + + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, + relay_cmd_quit_next, quit_cmd); + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, + relay_cmd_quit_replied, quit_cmd); + smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, + relay_cmd_quit_destroy, quit_cmd); + + if (smtp_client_connection_get_state(backend->conn) + >= SMTP_CLIENT_CONNECTION_STATE_READY) + relay_cmd_quit_relay(quit_cmd); + return 0; +} + +/* + * Relay backend + */ + +struct submission_backend_relay * +submission_backend_relay_create( + struct client *client, + const struct submision_backend_relay_settings *set) +{ + struct submission_backend_relay *backend; + struct mail_user *user = client->user; + struct ssl_iostream_settings ssl_set; + struct smtp_client_settings smtp_set; + pool_t pool; + + pool = pool_alloconly_create("submission relay backend", 1024); + backend = p_new(pool, struct submission_backend_relay, 1); + submission_backend_init(&backend->backend, pool, client, + &backend_relay_vfuncs); + + mail_user_init_ssl_client_settings(user, &ssl_set); + if (set->ssl_verify) + ssl_set.verbose_invalid_cert = TRUE; + else + ssl_set.allow_invalid_cert = TRUE; + + /* make relay connection */ + i_zero(&smtp_set); + smtp_set.my_hostname = set->my_hostname; + smtp_set.extra_capabilities = set->extra_capabilities; + smtp_set.ssl = &ssl_set; + smtp_set.debug = user->mail_debug; + + if (set->rawlog_dir != NULL) { + smtp_set.rawlog_dir = + mail_user_home_expand(user, set->rawlog_dir); + } + + if (set->trusted) { + backend->trusted = TRUE; + smtp_set.peer_trusted = TRUE; + + smtp_server_connection_get_proxy_data(client->conn, + &smtp_set.proxy_data); + + if (user->conn.remote_ip != NULL) { + smtp_set.proxy_data.source_ip = + *user->conn.remote_ip; + smtp_set.proxy_data.source_port = + user->conn.remote_port; + } + smtp_set.proxy_data.login = user->username; + smtp_set.xclient_defer = TRUE; + } + + smtp_set.username = set->user; + smtp_set.master_user = set->master_user; + smtp_set.password = set->password; + smtp_set.sasl_mech = set->sasl_mech; + smtp_set.connect_timeout_msecs = set->connect_timeout_msecs; + smtp_set.command_timeout_msecs = set->command_timeout_msecs; + + if (set->path != NULL) { + backend->conn = smtp_client_connection_create_unix( + smtp_client, set->protocol, set->path, &smtp_set); + } else if (set->ip.family == 0) { + backend->conn = smtp_client_connection_create( + smtp_client, set->protocol, set->host, set->port, + set->ssl_mode, &smtp_set); + } else { + backend->conn = smtp_client_connection_create_ip( + smtp_client, set->protocol, &set->ip, set->port, + set->host, set->ssl_mode, &smtp_set); + } + + return backend; +} + +struct submission_backend * +submission_backend_relay_get(struct submission_backend_relay *backend) +{ + return &backend->backend; +} + +struct smtp_client_connection * +submission_backend_relay_get_connection( + struct submission_backend_relay *backend) +{ + return backend->conn; +} + +struct smtp_client_transaction * +submission_backend_relay_get_transaction( + struct submission_backend_relay *backend) +{ + return backend->trans; +} + +static void backend_relay_destroy(struct submission_backend *_backend) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + if (backend->trans != NULL) + smtp_client_transaction_destroy(&backend->trans); + if (backend->conn != NULL) + smtp_client_connection_close(&backend->conn); +} + +static void backend_relay_ready_cb(const struct smtp_reply *reply, + void *context) +{ + struct submission_backend_relay *backend = context; + struct smtp_reply dummy; + + /* check relay status */ + if (!backend_relay_handle_relay_reply(backend, NULL, reply, &dummy)) + return; + if (!smtp_reply_is_success(reply)) { + i_error("Failed to establish relay connection: %s", + smtp_reply_log(reply)); + submission_backend_fail( + &backend->backend, NULL, "4.4.0", + "Failed to establish relay connection"); + return; + } + + /* notify the backend API about the fact that we're ready and propagate + our capabilities */ + submission_backend_started(&backend->backend, + smtp_client_connection_get_capabilities(backend->conn)); +} + +static void backend_relay_start(struct submission_backend *_backend) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + smtp_client_connection_connect(backend->conn, + backend_relay_ready_cb, backend); +} + +/* try to proxy pipelined commands in a similarly pipelined fashion */ +static void +backend_relay_client_input_pre(struct submission_backend *_backend) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + if (backend->conn != NULL) + smtp_client_connection_cork(backend->conn); +} +static void +backend_relay_client_input_post(struct submission_backend *_backend) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + if (backend->conn != NULL) + smtp_client_connection_uncork(backend->conn); +} + +static uoff_t +backend_relay_get_max_mail_size(struct submission_backend *_backend) +{ + struct submission_backend_relay *backend = + (struct submission_backend_relay *)_backend; + + return smtp_client_connection_get_size_capability(backend->conn); +} + +static struct submission_backend_vfuncs backend_relay_vfuncs = { + .destroy = backend_relay_destroy, + + .start = backend_relay_start, + + .client_input_pre = backend_relay_client_input_pre, + .client_input_post = backend_relay_client_input_post, + + .get_max_mail_size = backend_relay_get_max_mail_size, + + .trans_start = backend_relay_trans_start, + .trans_free = backend_relay_trans_free, + + .cmd_helo = backend_relay_cmd_helo, + + .cmd_mail = backend_relay_cmd_mail, + .cmd_rcpt = backend_relay_cmd_rcpt, + .cmd_rset = backend_relay_cmd_rset, + .cmd_data = backend_relay_cmd_data, + + .cmd_vrfy = backend_relay_cmd_vrfy, + .cmd_noop = backend_relay_cmd_noop, + + .cmd_quit = backend_relay_cmd_quit, +}; |