diff -up dovecot-2.3.8/src/lib-smtp/Makefile.am.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/Makefile.am --- dovecot-2.3.8/src/lib-smtp/Makefile.am.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/Makefile.am 2020-05-29 19:11:53.357162340 +0200 @@ -72,6 +72,7 @@ pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) test_programs = \ + test-smtp-syntax \ test-smtp-address \ test-smtp-params \ test-smtp-reply \ @@ -121,6 +122,10 @@ if BUILD_OPENSSL test_libs_ssl += ../lib-ssl-iostream/libssl_iostream_openssl.la endif +test_smtp_syntax_SOURCES = test-smtp-syntax.c +test_smtp_syntax_LDADD = $(test_libs) +test_smtp_syntax_DEPENDENCIES = $(test_deps) + test_smtp_address_SOURCES = test-smtp-address.c test_smtp_address_LDFLAGS = -export-dynamic test_smtp_address_LDADD = $(test_libs) diff -up dovecot-2.3.8/src/lib-smtp/smtp-address.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-address.c --- dovecot-2.3.8/src/lib-smtp/smtp-address.c.CVE_2020_10957,10958,10967 2020-05-29 19:11:53.356162354 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-address.c 2020-05-29 19:13:33.310813425 +0200 @@ -625,13 +625,13 @@ smtp_address_clone(pool_t pool, const st size_t size, lpsize = 0, dsize = 0; char *data, *localpart = NULL, *domain = NULL; - if (smtp_address_isnull(src)) + if (src == NULL) return NULL; /* @UNSAFE */ size = sizeof(struct smtp_address); - if (src->localpart != NULL && *src->localpart != '\0') { + if (!smtp_address_isnull(src)) { lpsize = strlen(src->localpart) + 1; size = MALLOC_ADD(size, lpsize); } @@ -686,7 +686,7 @@ smtp_address_clone_temp(const struct smt { struct smtp_address *new; - if (smtp_address_isnull(src)) + if (src == NULL) return NULL; new = t_new(struct smtp_address, 1); diff -up dovecot-2.3.8/src/lib-smtp/smtp-address.h.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-address.h --- dovecot-2.3.8/src/lib-smtp/smtp-address.h.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-address.h 2020-05-29 19:11:53.358162327 +0200 @@ -122,8 +122,14 @@ smtp_address_equals(const struct smtp_ad static inline bool ATTR_NULL(1) ATTR_PURE smtp_address_isnull(const struct smtp_address *address) { - return (address == NULL || address->localpart == NULL || - *address->localpart == '\0'); + return (address == NULL || address->localpart == NULL); } +static inline bool ATTR_NULL(1) ATTR_PURE +smtp_address_is_broken(const struct smtp_address *address) +{ + return (address != NULL && + smtp_address_isnull(address) /*&& + (address->raw != NULL && *address->raw != '\0')*/); +} #endif diff -up dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-noop.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-noop.c --- dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-noop.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-noop.c 2020-05-29 19:11:53.358162327 +0200 @@ -13,11 +13,15 @@ void smtp_server_cmd_noop(struct smtp_se struct smtp_server_connection *conn = cmd->conn; struct smtp_server_command *command = cmd->cmd; const struct smtp_server_callbacks *callbacks = conn->callbacks; + const char *param, *error; int ret; /* "NOOP" [ SP String ] CRLF */ - if (*params != '\0' && smtp_string_parse(params, NULL, NULL) < 0) { - smtp_server_reply(cmd, 501, "5.5.4", "Invalid parameters"); + ret = smtp_string_parse(params, ¶m, &error); + if (ret < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid string parameter: %s", + error); return; } diff -up dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-vrfy.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-vrfy.c --- dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-vrfy.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-server-cmd-vrfy.c 2020-05-29 19:11:53.358162327 +0200 @@ -17,15 +17,13 @@ void smtp_server_cmd_vrfy(struct smtp_se int ret; /* vrfy = "VRFY" SP String CRLF */ - if ((ret=smtp_string_parse(params, ¶m, &error)) <= 0) { - if (ret < 0) { - smtp_server_reply(cmd, - 501, "5.5.4", - "Invalid string parameter: %s", error); - } else { - smtp_server_reply(cmd, - 501, "5.5.4", "Invalid parameters"); - } + ret = smtp_string_parse(params, ¶m, &error); + if (ret < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid string parameter: %s", error); + return; + } else if (ret == 0) { + smtp_server_reply(cmd, 501, "5.5.4", "Invalid parameters"); return; } diff -up dovecot-2.3.8/src/lib-smtp/smtp-server-command.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-server-command.c --- dovecot-2.3.8/src/lib-smtp/smtp-server-command.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-server-command.c 2020-05-29 19:11:53.358162327 +0200 @@ -179,14 +179,15 @@ smtp_server_command_new_invalid(struct s struct smtp_server_command * smtp_server_command_new(struct smtp_server_connection *conn, - const char *name, const char *params) + const char *name) { struct smtp_server *server = conn->server; - const struct smtp_server_command_reg *cmd_reg; struct smtp_server_command *cmd; cmd = smtp_server_command_alloc(conn); cmd->context.name = p_strdup(cmd->context.pool, name); + cmd->reg = smtp_server_command_find(server, name); + smtp_server_command_update_event(cmd); struct event_passthrough *e = @@ -194,7 +195,15 @@ smtp_server_command_new(struct smtp_serv set_name("smtp_server_command_started"); e_debug(e->event(), "New command"); - if ((cmd_reg=smtp_server_command_find(server, name)) == NULL) { + return cmd; +} + +void smtp_server_command_execute(struct smtp_server_command *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->context.conn; + + if (cmd->reg == NULL) { /* RFC 5321, Section 4.2.4: Reply Code 502 Questions have been raised as to when reply code 502 (Command @@ -207,7 +216,7 @@ smtp_server_command_new(struct smtp_serv 500, "5.5.1", "Unknown command"); } else if (!conn->ssl_secured && conn->set.tls_required && - (cmd_reg->flags & SMTP_SERVER_CMD_FLAG_PRETLS) == 0) { + (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PRETLS) == 0) { /* RFC 3207, Section 4: A SMTP server that is not publicly referenced may choose to @@ -226,7 +235,7 @@ smtp_server_command_new(struct smtp_serv 530, "5.7.0", "TLS required."); } else if (!conn->authenticated && !conn->set.auth_optional && - (cmd_reg->flags & SMTP_SERVER_CMD_FLAG_PREAUTH) == 0) { + (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PREAUTH) == 0) { /* RFC 4954, Section 6: Status Codes 530 5.7.0 Authentication required @@ -242,16 +251,14 @@ smtp_server_command_new(struct smtp_serv } else { struct smtp_server_command *tmp_cmd = cmd; - i_assert(cmd_reg->func != NULL); + i_assert(cmd->reg->func != NULL); smtp_server_command_ref(tmp_cmd); - tmp_cmd->reg = cmd_reg; - cmd_reg->func(&tmp_cmd->context, params); + cmd->reg->func(&tmp_cmd->context, params); if (tmp_cmd->state == SMTP_SERVER_COMMAND_STATE_NEW) tmp_cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING; if (!smtp_server_command_unref(&tmp_cmd)) cmd = NULL; } - return cmd; } void smtp_server_command_ref(struct smtp_server_command *cmd) @@ -389,8 +396,11 @@ bool smtp_server_command_call_hooks(stru struct smtp_server_command *cmd = *_cmd; struct smtp_server_command_hook *hook; - if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) + if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) { + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) + return FALSE; smtp_server_command_ref(cmd); + } hook = cmd->hooks_head; while (hook != NULL) { diff -up dovecot-2.3.8/src/lib-smtp/smtp-server-connection.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-server-connection.c --- dovecot-2.3.8/src/lib-smtp/smtp-server-connection.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-server-connection.c 2020-05-29 19:11:53.358162327 +0200 @@ -293,20 +293,28 @@ smtp_server_connection_handle_command(st { struct smtp_server_connection *tmp_conn = conn; struct smtp_server_command *cmd; + bool finished; + + cmd = smtp_server_command_new(tmp_conn, cmd_name); + + smtp_server_command_ref(cmd); smtp_server_connection_ref(tmp_conn); - cmd = smtp_server_command_new(tmp_conn, cmd_name, cmd_params); + smtp_server_command_execute(cmd, cmd_params); if (!smtp_server_connection_unref(&tmp_conn)) { /* the command start callback managed to get this connection destroyed */ + smtp_server_command_unref(&cmd); return FALSE; } - if (cmd != NULL && conn->command_queue_head == cmd) + if (conn->command_queue_head == cmd) (void)smtp_server_command_next_to_reply(&cmd); smtp_server_connection_timeout_update(conn); - return (cmd == NULL || !cmd->input_locked); + + finished = !cmd->input_locked; + return (!smtp_server_command_unref(&cmd) || finished); } static int diff -up dovecot-2.3.8/src/lib-smtp/smtp-server-private.h.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-server-private.h --- dovecot-2.3.8/src/lib-smtp/smtp-server-private.h.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-server-private.h 2020-05-29 19:11:53.358162327 +0200 @@ -241,8 +241,11 @@ void smtp_server_command_debug(struct sm struct smtp_server_command * smtp_server_command_new_invalid(struct smtp_server_connection *conn); struct smtp_server_command * -smtp_server_command_new(struct smtp_server_connection *conn, - const char *name, const char *params); +smtp_server_command_new(struct smtp_server_connection *conn, const char *name); + +void smtp_server_command_execute(struct smtp_server_command *cmd, + const char *params); + void smtp_server_command_ref(struct smtp_server_command *cmd); bool smtp_server_command_unref(struct smtp_server_command **_cmd); void smtp_server_command_abort(struct smtp_server_command **_cmd); diff -up dovecot-2.3.8/src/lib-smtp/smtp-syntax.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/smtp-syntax.c --- dovecot-2.3.8/src/lib-smtp/smtp-syntax.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/smtp-syntax.c 2020-05-29 19:11:53.358162327 +0200 @@ -17,18 +17,19 @@ int smtp_string_parse(const char *string const char **value_r, const char **error_r) { struct smtp_parser parser; - int ret; + + *value_r = NULL; + *error_r = NULL; if (string == NULL || *string == '\0') { *value_r = ""; - return 1; + return 0; } smtp_parser_init(&parser, pool_datastack_create(), string); - if ((ret=smtp_parser_parse_string(&parser, value_r)) < 0) { - if (error_r != NULL) - *error_r = parser.error; + if (smtp_parser_parse_string(&parser, value_r) < 0) { + *error_r = parser.error; return -1; } if (parser.cur < parser.end) { @@ -85,20 +86,20 @@ int smtp_xtext_parse(const char *xtext, { struct smtp_parser parser; string_t *value = NULL; - int ret; + + *value_r = NULL; + *error_r = NULL; if (xtext == NULL || *xtext == '\0') { *value_r = ""; return 1; } - if (value_r != NULL) - value = t_str_new(256); + value = t_str_new(256); smtp_parser_init(&parser, pool_datastack_create(), xtext); - if ((ret=smtp_parser_parse_xtext(&parser, value)) < 0) { - if (error_r != NULL) - *error_r = parser.error; + if (smtp_parser_parse_xtext(&parser, value) < 0) { + *error_r = parser.error; return -1; } if (parser.cur < parser.end) { @@ -109,8 +110,7 @@ int smtp_xtext_parse(const char *xtext, if (value_r != NULL) { *value_r = str_c(value); if (strlen(*value_r) != str_len(value)) { - if (*error_r != NULL) - *error_r = "Encountered NUL character in xtext"; + *error_r = "Encountered NUL character in xtext"; return -1; } } @@ -249,12 +249,10 @@ static int smtp_parse_ehlo_line(struct s (i_isalnum(*parser->cur) || *parser->cur == '-')) parser->cur++; - if (key_r != NULL) - *key_r = p_strdup_until(parser->pool, pbegin, parser->cur); + *key_r = p_strdup_until(parser->pool, pbegin, parser->cur); if (parser->cur >= parser->end) { - if (params_r != NULL) - *params_r = p_new(parser->pool, const char *, 1); + *params_r = p_new(parser->pool, const char *, 1); return 1; } if (*parser->cur != ' ') { @@ -264,18 +262,16 @@ static int smtp_parse_ehlo_line(struct s parser->cur++; pbegin = parser->cur; - if (params_r != NULL) - p_array_init(¶ms, parser->pool, 32); + p_array_init(¶ms, parser->pool, 32); while (parser->cur < parser->end) { if (*parser->cur == ' ') { if (parser->cur+1 >= parser->end || *(parser->cur+1) == ' ') { parser->error = "Missing EHLO parameter after ' '"; return -1; } - if (params_r != NULL) { - param = p_strdup_until(parser->pool, pbegin, parser->cur); - array_push_back(¶ms, ¶m); - } + param = p_strdup_until(parser->pool, pbegin, + parser->cur); + array_push_back(¶ms, ¶m); pbegin = parser->cur + 1; } else if (!smtp_char_is_ehlo_param(*parser->cur)) { parser->error = "Unexpected character in EHLO parameter"; @@ -284,12 +280,10 @@ static int smtp_parse_ehlo_line(struct s parser->cur++; } - if (params_r != NULL) { - param = p_strdup_until(parser->pool, pbegin, parser->cur); - array_push_back(¶ms, ¶m); - array_append_zero(¶ms); - *params_r = array_front(¶ms); - } + param = p_strdup_until(parser->pool, pbegin, parser->cur); + array_push_back(¶ms, ¶m); + array_append_zero(¶ms); + *params_r = array_front(¶ms); return 1; } @@ -297,19 +291,20 @@ int smtp_ehlo_line_parse(const char *ehl const char *const **params_r, const char **error_r) { struct smtp_parser parser; - int ret; + + *key_r = NULL; + *params_r = NULL; + *error_r = NULL; if (ehlo_line == NULL || *ehlo_line == '\0') { - if (error_r != NULL) - *error_r = "Parameter is empty"; + *error_r = "Parameter is empty"; return -1; } smtp_parser_init(&parser, pool_datastack_create(), ehlo_line); - if ((ret=smtp_parse_ehlo_line(&parser, key_r, params_r)) <= 0) { - if (error_r != NULL) - *error_r = parser.error; + if (smtp_parse_ehlo_line(&parser, key_r, params_r) <= 0) { + *error_r = parser.error; return -1; } return 1; diff -up dovecot-2.3.8/src/lib-smtp/test-smtp-server-errors.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/test-smtp-server-errors.c --- dovecot-2.3.8/src/lib-smtp/test-smtp-server-errors.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lib-smtp/test-smtp-server-errors.c 2020-05-29 19:11:53.359162313 +0200 @@ -574,6 +574,174 @@ static void test_bad_command(void) } /* + * Many bad commands + */ + +/* client */ + +struct _many_bad_commands_client { + struct smtp_reply_parser *parser; + unsigned int reply; + bool replied:1; +}; + +static void +test_many_bad_commands_client_input(struct client_connection *conn) +{ + struct _many_bad_commands_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++) { + /* greeting */ + case 0: + i_assert(reply->status == 220); + break; + /* bad command reply */ + case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 9: case 10: + i_assert(reply->status == 500); + break; + case 11: + i_assert(reply->status == 421); + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void +test_many_bad_commands_client_connected(struct client_connection *conn) +{ + struct _many_bad_commands_client *ctx; + + ctx = p_new(conn->pool, struct _many_bad_commands_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, + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\n" + "i\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_many_bad_commands_client_deinit(struct client_connection *conn) +{ + struct _many_bad_commands_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_many_bad_commands(unsigned int index) +{ + test_client_input = test_many_bad_commands_client_input; + test_client_connected = test_many_bad_commands_client_connected; + test_client_deinit = test_many_bad_commands_client_deinit; + test_client_run(index); +} + +/* server */ + +struct _many_bad_commands { + struct istream *payload_input; + struct io *io; + + bool serviced:1; +}; + +static void +test_server_many_bad_commands_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); + io_loop_stop(ioloop); +} + +static int +test_server_many_bad_commands_helo( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_many_bad_commands_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_many_bad_commands_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_many_bad_commands +(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_many_bad_commands_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_many_bad_commands_helo; + server_callbacks.conn_cmd_rcpt = + test_server_many_bad_commands_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_many_bad_commands_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_many_bad_commands(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_bad_commands = 10; + + test_begin("many bad commands"); + test_run_client_server(&smtp_server_set, + test_server_many_bad_commands, + test_client_many_bad_commands, 2); + test_end(); +} + +/* * Long command */ @@ -1274,6 +1442,316 @@ static void test_bad_rcpt(void) } /* + * Bad VRFY + */ + +/* client */ + +struct _bad_vrfy_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void +test_bad_vrfy_client_input(struct client_connection *conn) +{ + struct _bad_vrfy_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: + i_assert(reply->status == 501); + break; + case 3: + i_assert(smtp_reply_is_success(reply)); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret == 0); +} + +static void +test_bad_vrfy_client_connected(struct client_connection *conn) +{ + struct _bad_vrfy_client *ctx; + + ctx = p_new(conn->pool, struct _bad_vrfy_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, + "VRFY\r\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "VRFY \"hendrik\r\n"); + break; + case 2: + o_stream_nsend_str(conn->conn.output, + "VRFY hen\"drik\r\n"); + break; + case 3: + o_stream_nsend_str(conn->conn.output, + "VRFY \"hendrik\"\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_bad_vrfy_client_deinit(struct client_connection *conn) +{ + struct _bad_vrfy_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_vrfy(unsigned int index) +{ + test_client_input = test_bad_vrfy_client_input; + test_client_connected = test_bad_vrfy_client_connected; + test_client_deinit = test_bad_vrfy_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_vrfy_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_vrfy_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_bad_vrfy_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_bad_vrfy(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = test_server_bad_vrfy_disconnect; + + server_callbacks.conn_cmd_rcpt = test_server_bad_vrfy_rcpt; + server_callbacks.conn_cmd_data_begin = test_server_bad_vrfy_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_vrfy(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad VRFY"); + test_run_client_server(&smtp_server_set, + test_server_bad_vrfy, + test_client_bad_vrfy, 4); + test_end(); +} + +/* + * Bad NOOP + */ + +/* client */ + +struct _bad_noop_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void +test_bad_noop_client_input(struct client_connection *conn) +{ + struct _bad_noop_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 1: case 2: + i_assert(reply->status == 501); + break; + case 0: case 3: + i_assert(smtp_reply_is_success(reply)); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret == 0); +} + +static void +test_bad_noop_client_connected(struct client_connection *conn) +{ + struct _bad_noop_client *ctx; + + ctx = p_new(conn->pool, struct _bad_noop_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, + "NOOP\r\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "NOOP \"frop\r\n"); + break; + case 2: + o_stream_nsend_str(conn->conn.output, + "NOOP fr\"op\r\n"); + break; + case 3: + o_stream_nsend_str(conn->conn.output, + "NOOP \"frop\"\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_bad_noop_client_deinit(struct client_connection *conn) +{ + struct _bad_noop_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_noop(unsigned int index) +{ + test_client_input = test_bad_noop_client_input; + test_client_connected = test_bad_noop_client_connected; + test_client_deinit = test_bad_noop_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_noop_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_noop_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_bad_noop_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_bad_noop(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = test_server_bad_noop_disconnect; + + server_callbacks.conn_cmd_rcpt = test_server_bad_noop_rcpt; + server_callbacks.conn_cmd_data_begin = test_server_bad_noop_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_noop(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad NOOP"); + test_run_client_server(&smtp_server_set, + test_server_bad_noop, + test_client_bad_noop, 4); + test_end(); +} + +/* * MAIL workarounds */ @@ -2023,11 +2501,14 @@ static void (*const test_functions[])(vo test_slow_client, test_hanging_command_payload, test_bad_command, + test_many_bad_commands, test_long_command, test_big_data, test_bad_ehlo, test_bad_mail, test_bad_rcpt, + test_bad_vrfy, + test_bad_noop, test_mail_workarounds, test_rcpt_workarounds, test_too_many_recipients, diff -up dovecot-2.3.8/src/lib-smtp/test-smtp-syntax.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lib-smtp/test-smtp-syntax.c --- dovecot-2.3.8/src/lib-smtp/test-smtp-syntax.c.CVE_2020_10957,10958,10967 2020-05-29 19:11:53.359162313 +0200 +++ dovecot-2.3.8/src/lib-smtp/test-smtp-syntax.c 2020-05-29 19:11:53.359162313 +0200 @@ -0,0 +1,150 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "test-common.h" +#include "smtp-syntax.h" + +/* + * Valid string parse tests + */ + +struct valid_string_parse_test { + const char *input, *parsed, *output; +}; + +static const struct valid_string_parse_test +valid_string_parse_tests[] = { + { + .input = "", + .parsed = "", + }, + { + .input = "atom", + .parsed = "atom", + }, + { + .input = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789!#$%&'*+-/=?^_`{|}~", + .parsed = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789!#$%&'*+-/=?^_`{|}~", + }, + { + .input = "\"quoted-string\"", + .parsed = "quoted-string", + .output = "quoted-string", + }, + { + .input = "\"quoted \\\"string\\\"\"", + .parsed = "quoted \"string\"", + }, + { + .input = "\"quoted \\\\string\\\\\"", + .parsed = "quoted \\string\\", + }, +}; + +static const unsigned int valid_string_parse_test_count = + N_ELEMENTS(valid_string_parse_tests); + +static void test_smtp_string_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_string_parse_test_count; i++) T_BEGIN { + const struct valid_string_parse_test *test = + &valid_string_parse_tests[i]; + const char *parsed, *error = NULL; + int ret; + + ret = smtp_string_parse(test->input, &parsed, &error); + + test_begin(t_strdup_printf("smtp string valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret >= 0, error); + test_assert(ret != 0 || *test->input == '\0'); + + if (!test_has_failed()) { + string_t *encoded; + const char *output; + + test_out(t_strdup_printf("parsed = \"%s\"", parsed), + null_strcmp(parsed, test->parsed) == 0); + + encoded = t_str_new(255); + smtp_string_write(encoded, parsed); + output = (test->output == NULL ? + test->input : test->output); + test_out(t_strdup_printf("write() = \"%s\"", + str_c(encoded)), + strcmp(str_c(encoded), output) == 0); + } + test_end(); + } T_END; +} + +/* + * Invalid string parse tests + */ + +struct invalid_string_parse_test { + const char *input; +}; + +static const struct invalid_string_parse_test +invalid_string_parse_tests[] = { + { + .input = " ", + }, + { + .input = "\\", + }, + { + .input = "\"", + }, + { + .input = "\"aa", + }, + { + .input = "aa\"", + }, +}; + +static const unsigned int invalid_string_parse_test_count = + N_ELEMENTS(invalid_string_parse_tests); + +static void test_smtp_string_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_string_parse_test_count; i++) T_BEGIN { + const struct invalid_string_parse_test *test = + &invalid_string_parse_tests[i]; + const char *parsed, *error; + int ret; + + ret = smtp_string_parse(test->input, &parsed, &error); + + test_begin(t_strdup_printf("smtp string invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret < 0, error); + test_end(); + } T_END; +} + +/* + * Tests + */ + +int main(void) +{ + static void (*test_functions[])(void) = { + test_smtp_string_parse_valid, + test_smtp_string_parse_invalid, + NULL + }; + return test_run(test_functions); +} diff -up dovecot-2.3.8/src/lmtp/lmtp-commands.c.CVE_2020_10957,10958,10967 dovecot-2.3.8/src/lmtp/lmtp-commands.c --- dovecot-2.3.8/src/lmtp/lmtp-commands.c.CVE_2020_10957,10958,10967 2019-10-08 10:46:18.000000000 +0200 +++ dovecot-2.3.8/src/lmtp/lmtp-commands.c 2020-05-29 19:11:53.359162313 +0200 @@ -66,10 +66,18 @@ int client_default_cmd_rcpt(struct clien char delim = '\0'; int ret; + i_assert(!smtp_address_isnull(rcpt->path)); + if (*rcpt->path->localpart == '\0' && rcpt->path->domain == NULL) { + smtp_server_recipient_reply( + rcpt, 550, "5.1.1", + "Unacceptable TO: Empty path not allowed"); + return -1; + } smtp_address_detail_parse_temp( client->unexpanded_lda_set->recipient_delimiter, rcpt->path, &username, &delim, &detail); + i_assert(*username != '\0'); /* Make user name and detail available in the recipient event. The mail_user event (for local delivery) also adds the user field, but