]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-smtp: smtp-command-parser - Make parser suitable for input stream with small...
authorStephan Bosch <stephan.bosch@open-xchange.com>
Fri, 29 Oct 2021 18:41:42 +0000 (20:41 +0200)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Wed, 3 Nov 2021 21:52:55 +0000 (22:52 +0100)
src/lib-smtp/smtp-command-parser.c
src/lib-smtp/test-smtp-server-errors.c

index 575420a9a26dc7b549a223d995dc20685e77f33c..f50abc9e94da80f4b740a066b6cc5c2c0e069851 100644 (file)
@@ -1,6 +1,7 @@
 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "buffer.h"
 #include "unichar.h"
 #include "istream.h"
 #include "istream-failure-at.h"
@@ -40,6 +41,7 @@ struct smtp_command_parser {
        struct smtp_command_limits limits;
 
        const unsigned char *cur, *end;
+       buffer_t *line_buffer;
        struct istream *data;
 
        struct smtp_command_parser_state_data state;
@@ -100,6 +102,7 @@ void smtp_command_parser_deinit(struct smtp_command_parser **_parser)
        struct smtp_command_parser *parser = *_parser;
 
        i_stream_unref(&parser->data);
+       buffer_free(&parser->line_buffer);
        i_free(parser->state.cmd_name);
        i_free(parser->state.cmd_params);
        i_free(parser->error);
@@ -110,6 +113,7 @@ void smtp_command_parser_deinit(struct smtp_command_parser **_parser)
 
 static void smtp_command_parser_restart(struct smtp_command_parser *parser)
 {
+       buffer_free(&parser->line_buffer);
        i_free(parser->state.cmd_name);
        i_free(parser->state.cmd_params);
 
@@ -168,8 +172,19 @@ static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
        size_t max_size = (parser->auth_response ?
                           parser->limits.max_auth_size :
                           parser->limits.max_parameters_size);
+       size_t buf_size = (parser->line_buffer == NULL ?
+                          0 : parser->line_buffer->used);
        int nch = 1;
 
+       i_assert(max_size == 0 || buf_size <= max_size);
+       if (max_size > 0 && buf_size == max_size) {
+               smtp_command_parser_error(
+                       parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
+                       "%s line is too long",
+                       (parser->auth_response ? "AUTH response" : "Command"));
+               return -1;
+       }
+
        /* We assume parameters to match textstr (HT, SP, Printable US-ASCII).
           For command parameters, we also accept valid UTF-8 characters.
         */
@@ -195,7 +210,7 @@ static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
                        break;
                p += nch;
        }
-       if (max_size > 0 && (size_t)(p - parser->cur) > max_size) {
+       if (max_size > 0 && (size_t)(p - parser->cur) > (max_size - buf_size)) {
                smtp_command_parser_error(
                        parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
                        "%s line is too long",
@@ -203,8 +218,34 @@ static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
                return -1;
        }
        parser->state.poff = p - parser->cur;
-       if (p == parser->end || nch == 0)
+       if (p == parser->end || nch == 0) {
+               /* Parsed up to end of what is currently buffered in the input
+                  stream. */
+               unsigned int ch_size = (p == parser->end ?
+                                       0 : uni_utf8_char_bytes(*p));
+               size_t max_input = i_stream_get_max_buffer_size(parser->input);
+
+               /* Move parsed data to parser's line buffer if the input stream
+                  buffer is full. This can happen when the parser's limits
+                  exceed the input stream max buffer size. */
+               if ((parser->state.poff + ch_size) >= max_input) {
+                       if (parser->line_buffer == NULL) {
+                               buf_size = (max_input < SIZE_MAX / 2 ?
+                                           max_input * 2 : SIZE_MAX);
+                               buf_size = I_MAX(buf_size, 2048);
+                               buf_size = I_MIN(buf_size, max_size);
+
+                               parser->line_buffer = buffer_create_dynamic(
+                                       default_pool, buf_size);
+                       }
+                       buffer_append(parser->line_buffer, parser->cur,
+                                     (p - parser->cur));
+
+                       parser->cur = p;
+                       parser->state.poff = 0;
+               }
                return 0;
+       }
 
        /* In the interest of improved interoperability, SMTP receivers SHOULD
           tolerate trailing white space before the terminating <CRLF>.
@@ -226,7 +267,16 @@ static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
                return -1;
        }
 
-       parser->state.cmd_params = i_strdup_until(parser->cur, mp);
+       if (parser->line_buffer == NULL) {
+               /* Buffered only in input stream */
+               parser->state.cmd_params = i_strdup_until(parser->cur, mp);
+       } else {
+               /* Buffered also in the parser */
+               buffer_append(parser->line_buffer, parser->cur,
+                             (mp - parser->cur));
+               parser->state.cmd_params =
+                       buffer_free_without_data(&parser->line_buffer);
+       }
        parser->cur = p;
        parser->state.poff = 0;
        return 1;
@@ -366,15 +416,8 @@ static int smtp_command_parse(struct smtp_command_parser *parser)
                        return ret;
                old_bytes = i_stream_get_data_size(parser->input);
        }
+       i_assert(ret != -2);
 
-       if (ret == -2) {
-               /* Should not really happen */
-               smtp_command_parser_error(
-                       parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
-                       "%s line is too long",
-                       (parser->auth_response ? "AUTH response" : "Command"));
-               return -1;
-       }
        if (ret < 0) {
                i_assert(parser->input->eof);
                if (parser->input->stream_errno == 0) {
index 314bde2643141a07c91e8ddc5c693cc1d2ed3be1..d3e528c063583495748302ca5ea8540801062395 100644 (file)
@@ -56,6 +56,7 @@ static bool debug = FALSE;
 static struct smtp_server *smtp_server = NULL;
 static struct io *io_listen;
 static int fd_listen = -1;
+static size_t server_io_buffer_size = 0;
 static struct smtp_server_callbacks server_callbacks;
 static unsigned int server_pending;
 
@@ -1111,6 +1112,30 @@ test_server_long_auth_line(const struct smtp_server_settings *server_set)
        test_server_run(server_set);
 }
 
+static void
+test_server_long_auth_line_small_buf(
+       const struct smtp_server_settings *server_set)
+{
+       server_io_buffer_size = 1024;
+
+       server_callbacks.conn_disconnect =
+               test_server_long_auth_line_disconnect;
+
+       server_callbacks.conn_cmd_helo =
+               test_server_long_auth_line_helo;
+       server_callbacks.conn_cmd_auth =
+               test_server_long_auth_line_auth;
+       server_callbacks.conn_cmd_auth_continue =
+               test_server_long_auth_line_auth_continue;
+       server_callbacks.conn_cmd_rcpt =
+               test_server_long_auth_line_rcpt;
+       server_callbacks.conn_cmd_data_begin =
+               test_server_long_auth_line_data_begin;
+       server_callbacks.conn_cmd_data_continue =
+               test_server_long_auth_line_data_continue;
+       test_server_run(server_set);
+}
+
 /* test */
 
 static void test_long_auth_line(void)
@@ -1128,6 +1153,22 @@ static void test_long_auth_line(void)
        test_end();
 }
 
+static void test_long_auth_line_small_buf(void)
+{
+       struct smtp_server_settings smtp_server_set;
+
+       test_server_defaults(&smtp_server_set);
+       smtp_server_set.capabilities = SMTP_CAPABILITY_AUTH;
+       smtp_server_set.max_client_idle_time_msecs = 1000;
+
+       test_begin("long auth line (small i/o buffers)");
+       test_run_client_server(&smtp_server_set,
+                              test_server_long_auth_line_small_buf,
+                              test_client_long_auth_line, 3);
+       test_end();
+}
+
+
 /*
  * Big data
  */
@@ -3482,6 +3523,7 @@ static void (*const test_functions[])(void) = {
        test_many_bad_commands,
        test_long_command,
        test_long_auth_line,
+       test_long_auth_line_small_buf,
        test_big_data,
        test_bad_helo,
        test_bad_mail,
@@ -3638,13 +3680,31 @@ static void server_connection_accept(void *context ATTR_UNUSED)
        if (debug)
                i_debug("Accepted connection");
 
+       net_set_nonblock(fd, TRUE);
+
        sconn = i_new(struct server_connection, 1);
 
        server_callbacks.conn_free = server_connection_free;
 
-       conn = smtp_server_connection_create(smtp_server, fd, fd,
-                                            NULL, 0, FALSE, NULL,
-                                            &server_callbacks, sconn);
+       if (server_io_buffer_size == 0) {
+               conn = smtp_server_connection_create(smtp_server, fd, fd,
+                                                    NULL, 0, FALSE, NULL,
+                                                    &server_callbacks, sconn);
+       } else {
+               struct istream *input;
+               struct ostream *output;
+
+               input = i_stream_create_fd(fd, server_io_buffer_size);
+               output = o_stream_create_fd(fd, server_io_buffer_size);
+               o_stream_set_no_error_handling(output, TRUE);
+
+               conn = smtp_server_connection_create_from_streams(
+                       smtp_server, input, output, NULL, 0, NULL,
+                       &server_callbacks, sconn);
+
+               i_stream_unref(&input);
+               o_stream_unref(&output);
+       }
        smtp_server_connection_start(conn);
 }
 
@@ -3750,6 +3810,8 @@ test_run_client_server(const struct smtp_server_settings *server_set,
 {
        unsigned int i;
 
+       server_io_buffer_size = 0;
+
        fd_listen = test_open_server_fd();
 
        for (i = 0; i < client_tests_count; i++) {