]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
submission: relay backend: Do not close the client connection for failure in a non...
authorStephan Bosch <stephan.bosch@dovecot.fi>
Fri, 12 Oct 2018 07:23:27 +0000 (09:23 +0200)
committerStephan Bosch <stephan.bosch@dovecot.fi>
Mon, 15 Oct 2018 23:33:43 +0000 (01:33 +0200)
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.

src/submission/submission-backend-relay.c
src/submission/submission-backend.c
src/submission/submission-backend.h

index 328d4d6fe10c4e0fb8bccd22ff62ffa35909994a..56620b6241cbbce11db552bc29c1c4d6939cc57b 100644 (file)
@@ -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;
        }
index c8f1beb4905b2ed270cf56827df45bfb5927a57c..d334bb62246ba949303ed851cdb457cf3b0381fb 100644 (file)
@@ -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;
index e3a97bbc010538911d661a9ab402eedcb7ce3400..42ea5242a2a6fcc0f8337ade172c10dac962d676 100644 (file)
@@ -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);