From: Stephan Bosch Date: Fri, 29 Oct 2021 18:41:42 +0000 (+0200) Subject: lib-smtp: smtp-command-parser - Make parser suitable for input stream with small... X-Git-Tag: 2.3.18~167 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=056aeb07487b9ba07292e1fbdf6ab004fed0916c;p=thirdparty%2Fdovecot%2Fcore.git lib-smtp: smtp-command-parser - Make parser suitable for input stream with small buffer. --- diff --git a/src/lib-smtp/smtp-command-parser.c b/src/lib-smtp/smtp-command-parser.c index 575420a9a2..f50abc9e94 100644 --- a/src/lib-smtp/smtp-command-parser.c +++ b/src/lib-smtp/smtp-command-parser.c @@ -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 . @@ -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) { diff --git a/src/lib-smtp/test-smtp-server-errors.c b/src/lib-smtp/test-smtp-server-errors.c index 314bde2643..d3e528c063 100644 --- a/src/lib-smtp/test-smtp-server-errors.c +++ b/src/lib-smtp/test-smtp-server-errors.c @@ -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++) {