From: Stephan Bosch Date: Fri, 12 Oct 2018 07:23:27 +0000 (+0200) Subject: submission: relay backend: Do not close the client connection for failure in a non... X-Git-Tag: 2.3.9~1190 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2387195f7cd4e06ec18f1340b220396a1f872d74;p=thirdparty%2Fdovecot%2Fcore.git submission: relay backend: Do not close the client connection for failure in a non-default backend. Adjusts the backend API to remember the failure until the present transaction is reset. In the mean time, any commands issued to the backend are failed immediately. In contrast, failure on the default backend will cause the client connection to be closed, like before. --- diff --git a/src/submission/submission-backend-relay.c b/src/submission/submission-backend-relay.c index 328d4d6fe1..56620b6241 100644 --- a/src/submission/submission-backend-relay.c +++ b/src/submission/submission-backend-relay.c @@ -44,10 +44,12 @@ static struct submission_backend_vfuncs backend_relay_vfuncs; 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) { - struct client *client = backend->backend.client; + const char *enh_code, *msg, *detail = ""; + bool result = TRUE; *reply_r = *reply; @@ -57,16 +59,18 @@ backend_relay_handle_relay_reply(struct submission_backend_relay *backend, case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: - client_destroy(client, - "4.4.0", "Failed to connect to relay server"); - return FALSE; + enh_code = "4.4.0"; + msg = "Failed to connect to relay server"; + result = FALSE; + break; case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED: case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: - client_destroy(client, - "4.4.0", "Lost connection to relay server"); - return FALSE; + enh_code = "4.4.0"; + 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, @@ -76,14 +80,47 @@ backend_relay_handle_relay_reply(struct submission_backend_relay *backend, case 530: i_error("Relay server requires authentication: %s", smtp_reply_log(reply)); - client_destroy(client, "4.3.5", - "Internal error occurred. " - "Refer to server log for more information."); - return FALSE; + enh_code = "4.3.5", + msg = "Internal error occurred. " + "Refer to server log for more information."; + result = FALSE; + break; + default: + break; + } + + 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: + 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; } + if (!result) { + const char *reason = t_strdup_printf("%s%s", msg, detail); + + 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); @@ -202,7 +239,8 @@ relay_cmd_helo_callback(const struct smtp_reply *relay_reply, struct submission_backend_relay *backend = helo->backend; struct smtp_reply reply; - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; if (smtp_reply_is_success(&reply)) { @@ -310,7 +348,8 @@ relay_cmd_mail_callback(const struct smtp_reply *relay_reply, i_assert(mail_cmd != NULL); mail_cmd->relay_mail = NULL; - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; if (smtp_reply_is_success(relay_reply)) { @@ -457,7 +496,8 @@ relay_cmd_rcpt_callback(const struct smtp_reply *relay_reply, i_assert(rcpt_cmd != NULL); rcpt_cmd->relay_rcpt = NULL; - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; if (smtp_reply_is_success(&reply)) { @@ -524,7 +564,8 @@ relay_cmd_rset_callback(const struct smtp_reply *relay_reply, i_assert(rset_cmd != NULL); rset_cmd->cmd_relayed = NULL; - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; /* forward reply */ @@ -580,7 +621,8 @@ relay_cmd_data_callback(const struct smtp_reply *relay_reply, /* finished relaying message to relay server */ /* check for fatal problems */ - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; if (smtp_reply_is_success(&reply)) { @@ -646,7 +688,8 @@ relay_cmd_vrfy_callback(const struct smtp_reply *relay_reply, struct submission_backend_relay *backend = vrfy_cmd->backend; struct smtp_reply reply; - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; if (!smtp_reply_has_enhanced_code(&reply)) { @@ -701,7 +744,8 @@ relay_cmd_noop_callback(const struct smtp_reply *relay_reply, struct submission_backend_relay *backend = noop_cmd->backend; struct smtp_reply reply; - if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply)) + if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, + &reply)) return; if (smtp_reply_is_success(&reply)) { @@ -918,13 +962,12 @@ static void backend_relay_ready_cb(const struct smtp_reply *reply, void *context) { struct submission_backend_relay *backend = context; - struct client *client = backend->backend.client; /* check relay status */ if (!smtp_reply_is_success(reply)) { i_error("Failed to establish relay connection: %s", smtp_reply_log(reply)); - client_destroy(client, + submission_backend_fail(&backend->backend, NULL, "4.4.0", "Failed to establish relay connection"); return; } diff --git a/src/submission/submission-backend.c b/src/submission/submission-backend.c index c8f1beb490..d334bb6224 100644 --- a/src/submission/submission-backend.c +++ b/src/submission/submission-backend.c @@ -27,6 +27,9 @@ static void submission_backend_destroy(struct submission_backend *backend) i_stream_unref(&backend->data_input); + i_free(backend->fail_enh_code); + i_free(backend->fail_reason); + DLLIST_REMOVE(&client->backends, backend); backend->v.destroy(backend); } @@ -43,6 +46,10 @@ void submission_backend_start(struct submission_backend *backend) { if (backend->started) return; + if (backend->fail_reason != NULL) { + /* Don't restart until failure is reset at transaction end */ + return; + } backend->v.start(backend); backend->started = TRUE; } @@ -56,6 +63,104 @@ void submission_backend_started(struct submission_backend *backend, client_default_backend_started(client, caps); } +static void +submission_backend_fail_rcpts(struct submission_backend *backend) +{ + struct client *client = backend->client; + struct submission_recipient *const *rcptp; + const char *enh_code = backend->fail_enh_code; + const char *reason = backend->fail_reason; + + i_assert(array_count(&client->rcpt_to) > 0); + + i_assert(reason != NULL); + if (enh_code == NULL) + enh_code = "4.0.0"; + + array_foreach_modifiable(&client->rcpt_to, rcptp) { + struct submission_recipient *rcpt = *rcptp; + struct smtp_server_cmd_ctx *cmd = rcpt->rcpt->cmd; + unsigned int index = 0; + + if (rcpt->backend != backend) + continue; + if (cmd == NULL) + continue; + + if (smtp_server_command_get_reply_count(cmd->cmd) > 1) + index = rcpt->rcpt->index; + + smtp_server_reply_index(cmd, index, 451, + enh_code, "%s", reason); + } +} + +static inline void +submission_backend_reply_failure(struct submission_backend *backend, + struct smtp_server_cmd_ctx *cmd) +{ + const char *enh_code = backend->fail_enh_code; + const char *reason = backend->fail_reason; + + if (enh_code == NULL) + enh_code = "4.0.0"; + + i_assert(smtp_server_command_get_reply_count(cmd->cmd) == 1); + smtp_server_reply(cmd, 451, enh_code, "%s", reason); +} + +static inline bool +submission_backend_handle_failure(struct submission_backend *backend, + struct smtp_server_cmd_ctx *cmd) +{ + if (backend->fail_reason == NULL) + return TRUE; + + /* already failed */ + submission_backend_reply_failure(backend, cmd); + return TRUE; +} + +void submission_backend_fail(struct submission_backend *backend, + struct smtp_server_cmd_ctx *cmd, + const char *enh_code, const char *reason) +{ + struct client *client = backend->client; + bool failed_before = (backend->fail_reason != NULL); + + /* Can be called several times */ + + if (backend == client->backend_default) { + /* default backend: fail the whole client */ + client_destroy(client, enh_code, reason); + return; + } + + /* Non-default backend for this transaction (maybe even for only + some of the approved recipients): fail only the affected + transaction and/or specific recipients. */ + + /* Remember the failure only once */ + if (!failed_before) { + backend->fail_enh_code = i_strdup(enh_code); + backend->fail_reason = i_strdup(reason); + } + if (cmd == NULL) { + /* Called outside command context: just remember the failure */ + } else if (smtp_server_command_get_reply_count(cmd->cmd) > 1) { + /* Fail DATA/BDAT/BURL command expecting several replies */ + submission_backend_fail_rcpts(backend); + } else { + /* Single command */ + submission_backend_reply_failure(backend, cmd); + } + + /* Call the fail vfunc only once */ + if (!failed_before && backend->v.fail != NULL) + backend->v.fail(backend, enh_code, reason); + backend->started = FALSE; +} + void submission_backends_client_input_pre(struct client *client) { struct submission_backend *backend; @@ -110,6 +215,9 @@ submission_backend_trans_free(struct submission_backend *backend, i_stream_unref(&backend->data_input); if (backend->v.trans_free != NULL) backend->v.trans_free(backend, trans); + + i_free(backend->fail_enh_code); + i_free(backend->fail_reason); } void submission_backends_trans_start(struct client *client, @@ -149,6 +257,9 @@ int submission_backend_cmd_helo(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { + /* failure on default backend closes the client connection */ + i_assert(backend->fail_reason == NULL); + if (!backend->started || backend->v.cmd_helo == NULL) { /* default backend is not interested, respond right away */ submission_helo_reply_submit(cmd, data); @@ -169,6 +280,9 @@ int submission_backend_cmd_mail(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data) { + if (!submission_backend_handle_failure(backend, cmd)) + return -1; + submission_backend_start(backend); if (backend->v.cmd_mail == NULL) { @@ -200,6 +314,9 @@ int submission_backend_cmd_rcpt(struct submission_backend *backend, { struct smtp_server_transaction *trans; + if (!submission_backend_handle_failure(backend, cmd)) + return -1; + if (backend->v.cmd_rcpt == NULL) { /* backend is not interested, respond right away */ return 1; @@ -217,6 +334,9 @@ int submission_backend_cmd_rcpt(struct submission_backend *backend, int submission_backend_cmd_rset(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { + if (!submission_backend_handle_failure(backend, cmd)) + return -1; + if (backend->v.cmd_rset == NULL) { /* backend is not interested, respond right away */ return 1; @@ -229,6 +349,11 @@ submission_backend_cmd_data(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans) { + if (backend->fail_reason != NULL) { + submission_backend_fail_rcpts(backend); + return 0; + } + return backend->v.cmd_data(backend, cmd, trans, backend->data_input, backend->data_size); } @@ -270,6 +395,9 @@ int submission_backend_cmd_vrfy(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, const char *param) { + /* failure on default backend closes the client connection */ + i_assert(backend->fail_reason == NULL); + if (backend->v.cmd_vrfy == NULL) { /* backend is not interested, respond right away */ return 1; @@ -280,6 +408,9 @@ int submission_backend_cmd_vrfy(struct submission_backend *backend, int submission_backend_cmd_noop(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { + /* failure on default backend closes the client connection */ + i_assert(backend->fail_reason == NULL); + if (backend->v.cmd_noop == NULL) { /* backend is not interested, respond right away */ return 1; @@ -290,6 +421,9 @@ int submission_backend_cmd_noop(struct submission_backend *backend, int submission_backend_cmd_quit(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { + /* failure on default backend closes the client connection */ + i_assert(backend->fail_reason == NULL); + if (backend->v.cmd_quit == NULL) { /* backend is not interested, respond right away */ return 1; diff --git a/src/submission/submission-backend.h b/src/submission/submission-backend.h index e3a97bbc01..42ea5242a2 100644 --- a/src/submission/submission-backend.h +++ b/src/submission/submission-backend.h @@ -9,6 +9,9 @@ struct submission_backend_vfuncs { void (*start)(struct submission_backend *backend); + void (*fail)(struct submission_backend *backend, const char *enh_code, + const char *reason); + void (*client_input_pre)(struct submission_backend *backend); void (*client_input_post)(struct submission_backend *backend); @@ -56,6 +59,9 @@ struct submission_backend { struct istream *data_input; uoff_t data_size; + char *fail_enh_code; + char *fail_reason; + bool started:1; bool trans_started:1; }; @@ -69,6 +75,11 @@ void submission_backend_start(struct submission_backend *backend); void submission_backend_started(struct submission_backend *backend, enum smtp_capability caps); +void submission_backend_fail(struct submission_backend *backend, + struct smtp_server_cmd_ctx *cmd, + const char *enh_code, const char *reason) + ATTR_NULL(2); + void submission_backends_client_input_pre(struct client *client); void submission_backends_client_input_post(struct client *client);