]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-smtp: smtp-server - Support accepting broken path parameter in MAIL command.
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sat, 14 Sep 2019 15:59:08 +0000 (17:59 +0200)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Fri, 4 Oct 2019 18:52:24 +0000 (20:52 +0200)
src/lda/main.c
src/lib-smtp/smtp-server-cmd-mail.c
src/lib-smtp/smtp-server-connection.c
src/lib-smtp/smtp-server-transaction.c
src/lib-smtp/smtp-server.c
src/lib-smtp/smtp-server.h
src/lib-smtp/test-smtp-server-errors.c

index 7076460db49b0f67de63bae0e333b35e5ed1c399..4d0755886cb07531cc2edbd4c06bb751ff328f46 100644 (file)
@@ -556,6 +556,11 @@ int main(int argc, char *argv[])
                                "userdb lookup skipped, username taken from %s",
                                user_source);
                }
+               if (mail_from_error != NULL) {
+                       e_debug(event, "Broken -f parameter: %s "
+                               "(proceeding with <> as sender)",
+                               mail_from_error);
+               }
 
                ret = lda_deliver(&dinput, service_user, user, path,
                                  rcpt_to, rcpt_to_source, stderr_rejection);
index 57a7c74e37efb6bf28fa983c09cb49b84776e8ca..7fbcaa9348c1fe8897b43fe694b3c9b2f6c58cab 100644 (file)
@@ -72,7 +72,7 @@ void smtp_server_cmd_mail(struct smtp_server_cmd_ctx *cmd,
        struct smtp_server_cmd_mail *mail_data;
        enum smtp_address_parse_flags path_parse_flags;
        const char *const *param_extensions = NULL;
-       struct smtp_address *path;
+       struct smtp_address *path = NULL;
        enum smtp_param_parse_error pperror;
        const char *error;
        int ret;
@@ -103,23 +103,38 @@ void smtp_server_cmd_mail(struct smtp_server_cmd_ctx *cmd,
                                  "Unexpected whitespace before path");
                return;
        }
-       path_parse_flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY;
+       path_parse_flags =
+               SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
+               SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW;
        if (*params != '\0' &&
-           (set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0)
+           (set->mail_path_allow_broken ||
+            (set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0))
                path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL;
-       if (smtp_address_parse_path_full(pool_datastack_create(), params,
-                                        path_parse_flags, &path, &error,
-                                        &params) < 0) {
-               smtp_server_reply(cmd, 501, "5.5.4", "Invalid FROM: %s", error);
+       if (set->mail_path_allow_broken) {
+               path_parse_flags |=
+                       SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART |
+                       SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN;
+       }
+       ret = smtp_address_parse_path_full(pool_datastack_create(), params,
+                                          path_parse_flags, &path, &error,
+                                          &params);
+       if (ret < 0 && !smtp_address_is_broken(path)) {
+               smtp_server_reply(cmd, 501, "5.5.4",
+                                 "Invalid FROM: %s", error);
                return;
        }
        if (*params == ' ')
                params++;
        else if (*params != '\0') {
-               smtp_server_reply(cmd, 501, "5.5.4",
+               smtp_server_reply(
+                       cmd, 501, "5.5.4",
                        "Invalid FROM: Invalid character in path");
                return;
        }
+       if (ret < 0) {
+               e_debug(conn->event, "Invalid FROM: %s "
+                       "(proceeding with <> as sender)", error);
+       }
 
        mail_data = p_new(cmd->pool, struct smtp_server_cmd_mail, 1);
 
index 8c4f2e36b1cc02d93782e311ac4497389786075e..5e0a14854836d88727eeb62840e16db41e447cd1 100644 (file)
@@ -881,6 +881,9 @@ smtp_server_connection_alloc(struct smtp_server *server,
                        conn->set.tls_required || set->tls_required;
                conn->set.auth_optional =
                        conn->set.auth_optional || set->auth_optional;
+               conn->set.mail_path_allow_broken =
+                       conn->set.mail_path_allow_broken ||
+                               set->mail_path_allow_broken;
                conn->set.rcpt_domain_optional =
                        conn->set.rcpt_domain_optional ||
                                set->rcpt_domain_optional;
index 1fe66484752ba9b607711cae7e2cb40923e56ff6..a8481abbc93f713c2b57108fc3bd66d944d1f78b 100644 (file)
@@ -20,6 +20,8 @@ smtp_server_transaction_update_event(struct smtp_server_transaction *trans)
        event_add_str(event, "transaction_id", trans->id);
        event_add_str(event, "mail_from",
                      smtp_address_encode(trans->mail_from));
+       event_add_str(event, "mail_from_raw",
+                     smtp_address_encode_raw(trans->mail_from));
        smtp_params_mail_add_to_event(&trans->params, event);
        event_set_append_log_prefix(event,
                                    t_strdup_printf("trans %s: ", trans->id));
index 3058a6817fa970ff926cc1b9caed74939805b4c2..cf5ea4374ddd4476f0bf8156ed1230cba707578a 100644 (file)
@@ -82,6 +82,7 @@ struct smtp_server *smtp_server_init(const struct smtp_server_settings *set)
        server->set.tls_required = set->tls_required;
        server->set.auth_optional = set->auth_optional;
        server->set.rcpt_domain_optional = set->rcpt_domain_optional;
+       server->set.mail_path_allow_broken = set->mail_path_allow_broken;
        server->set.debug = set->debug;
 
        /* There is no event log prefix added here, since the server itself does
index 633282761e72533752a004ee756ec4415fbf182f..c25b97a090458c8e4d30468a21ac528764f1168c 100644 (file)
@@ -364,6 +364,15 @@ struct smtp_server_settings {
        bool auth_optional:1;
        /* TLS security is required for this service */
        bool tls_required:1;
+       /* The path provided to the MAIL command does not need to be valid. A
+          completely invalid path will parse as <>. Paths that can still be
+          fixed by splitting it on the last `@' yielding a usable localpart and
+          domain, will be parsed as such. There are limits though; when the
+          path is badly delimited or contains control characters, the MAIL
+          command will still fail. The unparsed broken address will be
+          available in the `raw' field of struct smtp_address for logging etc.
+        */
+       bool mail_path_allow_broken:1;
        /* The path provided to the RCPT command does not need to have the
           domain part. */
        bool rcpt_domain_optional:1;
index d13df451e538861f3bbd6da129dee0687aefc4e7..1a96fa89ba0ab4554650503d106a2cf33d186b91 100644 (file)
@@ -2014,6 +2014,225 @@ static void test_data_binarymime(void)
        test_end();
 }
 
+/*
+ * MAIL broken path
+ */
+
+/* client */
+
+struct _mail_broken_path_client {
+       struct smtp_reply_parser *parser;
+       unsigned int reply;
+
+       bool replied:1;
+};
+
+static void
+test_mail_broken_path_client_input(struct client_connection *conn)
+{
+       struct _mail_broken_path_client *ctx = conn->context;
+       struct smtp_reply *reply;
+       const char *error;
+       int ret;
+
+       while ((ret=smtp_reply_parse_next(ctx->parser, FALSE,
+                                         &reply, &error)) > 0) {
+               if (debug)
+                       i_debug("REPLY: %s", smtp_reply_log(reply));
+
+               switch (ctx->reply++) {
+               case 0: /* greeting */
+                       i_assert(reply->status == 220);
+                       break;
+               case 1: /* bad command reply */
+                       switch (client_index) {
+                       case 0: case 1: case 2: case 4: case 5:
+                       case 6: case 7: case 8:
+                               i_assert(reply->status == 501);
+                               break;
+                       case 3: case 9: case 10: case 11: case 12: case 13:
+                       case 14: case 15: case 16: case 17:
+                               i_assert(reply->status == 250);
+                               break;
+                       default:
+                               i_info("STATUS: %u", reply->status);
+                               i_unreached();
+                       }
+                       ctx->replied = TRUE;
+                       io_loop_stop(ioloop);
+                       connection_disconnect(&conn->conn);
+                       return;
+               default:
+                       i_unreached();
+               }
+       }
+
+       i_assert(ret >= 0);
+}
+
+static void
+test_mail_broken_path_client_connected(struct client_connection *conn)
+{
+       struct _mail_broken_path_client *ctx;
+
+       ctx = p_new(conn->pool, struct _mail_broken_path_client, 1);
+       ctx->parser = smtp_reply_parser_init(conn->conn.input, (size_t)-1);
+       conn->context = ctx;
+
+       switch (client_index) {
+       case 0:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM: <hendrik@example.com>\r\n");
+               break;
+       case 1:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:\t<hendrik@example.com>\r\n");
+               break;
+       case 2:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:\t <hendrik@example.com>\r\n");
+               break;
+       case 3:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:hendrik@example.com\r\n");
+               break;
+       case 4:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM: hendrik@example.com\r\n");
+               break;
+       case 5:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:\r\n");
+               break;
+       case 6:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM: \r\n");
+               break;
+       case 7:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM: BODY=7BIT\r\n");
+               break;
+       case 8:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM: <>\r\n");
+               break;
+       case 9:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:<hendrik@example.com>\r\n");
+               break;
+       case 10:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:<>\r\n");
+               break;
+       case 11:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:bla$die%bla@die&bla\r\n");
+               break;
+       case 12:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:<u\"ser>\r\n");
+               break;
+       case 13:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:<u\"ser@domain.tld>\r\n");
+               break;
+       case 14:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:/@)$@)BLAARGH!@#$$\r\n");
+               break;
+       case 15:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:</@)$@)BLAARGH!@#$$>\r\n");
+               break;
+       case 16:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4\r\n");
+               break;
+       case 17:
+               o_stream_nsend_str(conn->conn.output,
+                       "MAIL FROM:<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>\r\n");
+               break;
+       default:
+               i_unreached();
+       }
+}
+
+static void
+test_mail_broken_path_client_deinit(struct client_connection *conn)
+{
+       struct _mail_broken_path_client *ctx = conn->context;
+
+       i_assert(ctx->replied);
+       smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_mail_broken_path(unsigned int index)
+{
+       test_client_input = test_mail_broken_path_client_input;
+       test_client_connected = test_mail_broken_path_client_connected;
+       test_client_deinit = test_mail_broken_path_client_deinit;
+       test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_mail_broken_path_disconnect(void *context ATTR_UNUSED,
+                                       const char *reason)
+{
+       if (debug)
+               i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_mail_broken_path_rcpt(void *conn_ctx ATTR_UNUSED,
+       struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+       struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+       test_assert(FALSE);
+       return 1;
+}
+
+static int
+test_server_mail_broken_path_data_begin(void *conn_ctx ATTR_UNUSED,
+       struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+       struct smtp_server_transaction *trans ATTR_UNUSED,
+       struct istream *data_input ATTR_UNUSED)
+{
+       test_assert(FALSE);
+       return 1;
+}
+
+static void test_server_mail_broken_path
+(const struct smtp_server_settings *server_set)
+{
+       server_callbacks.conn_disconnect =
+               test_server_mail_broken_path_disconnect;
+
+       server_callbacks.conn_cmd_rcpt =
+               test_server_mail_broken_path_rcpt;
+       server_callbacks.conn_cmd_data_begin =
+               test_server_mail_broken_path_data_begin;
+       test_server_run(server_set);
+}
+
+/* test */
+
+static void test_mail_broken_path(void)
+{
+       struct smtp_server_settings smtp_server_set;
+
+       test_server_defaults(&smtp_server_set);
+       smtp_server_set.mail_path_allow_broken = TRUE;
+       smtp_server_set.max_client_idle_time_msecs = 1000;
+
+       test_begin("MAIL workarounds");
+       test_run_client_server(&smtp_server_set,
+               test_server_mail_broken_path,
+               test_client_mail_broken_path, 16);
+       test_end();
+}
+
 /*
  * All tests
  */
@@ -2034,6 +2253,7 @@ static void (*const test_functions[])(void) = {
        test_data_no_mail,
        test_data_no_rcpt,
        test_data_binarymime,
+       test_mail_broken_path,
        NULL
 };