From b62b43dbc2d2fe879f84f144095cc9241c660e73 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Mar 30 2021 13:18:40 +0000 Subject: import dovecot-2.3.8-9.el8 --- diff --git a/SOURCES/dovecot-2.3.0.1-libxcrypt.patch b/SOURCES/dovecot-2.3.0.1-libxcrypt.patch deleted file mode 100644 index a8c33bf..0000000 --- a/SOURCES/dovecot-2.3.0.1-libxcrypt.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff -up dovecot-2.3.0.1/src/auth/mycrypt.c.libxcrypt dovecot-2.3.0.1/src/auth/mycrypt.c ---- dovecot-2.3.0.1/src/auth/mycrypt.c.libxcrypt 2018-02-28 15:28:58.000000000 +0100 -+++ dovecot-2.3.0.1/src/auth/mycrypt.c 2018-03-27 10:57:38.447769201 +0200 -@@ -14,6 +14,7 @@ - # define _XPG6 /* Some Solaris versions require this, some break with this */ - #endif - #include -+#include - - #include "mycrypt.h" - diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_24386-part1.patch b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part1.patch new file mode 100644 index 0000000..5347f43 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part1.patch @@ -0,0 +1,26 @@ +From 68165c8acc6d32a06f8dce2ef515c714c243ce4e Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 18:33:20 +0300 +Subject: [PATCH] imap: Escape tag when sending it to imap-hibernate process + +--- + src/imap/imap-client-hibernate.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/imap/imap-client-hibernate.c b/src/imap/imap-client-hibernate.c +index f639d722cb..4ef323453c 100644 +--- a/src/imap/imap-client-hibernate.c ++++ b/src/imap/imap-client-hibernate.c +@@ -97,8 +97,10 @@ static void imap_hibernate_write_cmd(struct client *client, string_t *cmd, + str_printfa(cmd, "\tuid=%s", dec2str(user->uid)); + if (user->gid != (gid_t)-1) + str_printfa(cmd, "\tgid=%s", dec2str(user->gid)); +- if (tag != NULL) +- str_printfa(cmd, "\ttag=%s", tag); ++ if (tag != NULL) { ++ str_append(cmd, "\ttag="); ++ str_append_tabescaped(cmd, tag); ++ } + str_append(cmd, "\tstats="); + str_append_tabescaped(cmd, client_stats(client)); + if (client->command_queue != NULL && diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_24386-part2.patch b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part2.patch new file mode 100644 index 0000000..e829001 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part2.patch @@ -0,0 +1,25 @@ +From 73937b5fe7eb1dde76f30ef6b181c920bbbc4558 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 24 Aug 2020 16:58:16 +0300 +Subject: [PATCH] imap: Fix crash if imap-hibernate socket can't be connected + to + +The error was supposed to be returned to caller, not logged directly. +--- + src/imap/imap-client-hibernate.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/imap/imap-client-hibernate.c b/src/imap/imap-client-hibernate.c +index 4ef323453c..d3451b1bf6 100644 +--- a/src/imap/imap-client-hibernate.c ++++ b/src/imap/imap-client-hibernate.c +@@ -176,7 +176,8 @@ imap_hibernate_process_send(struct client *client, const buffer_t *state, + "/"IMAP_HIBERNATE_SOCKET_NAME, NULL); + fd = net_connect_unix_with_retries(path, 1000); + if (fd == -1) { +- i_error("net_connect_unix(%s) failed: %m", path); ++ *error_r = t_strdup_printf( ++ "net_connect_unix(%s) failed: %m", path); + return -1; + } + net_set_nonblock(fd, FALSE); diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_24386-part3.patch b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part3.patch new file mode 100644 index 0000000..830b5e4 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part3.patch @@ -0,0 +1,31 @@ +From c7d158681fabdb3044bd213c332e489b46625a3b Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 24 Aug 2020 19:10:10 +0300 +Subject: [PATCH] imap: Delay initializing client IO until + client_create_finish() + +This helps writing unit tests. +--- + src/imap/imap-client.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c +index 95e57dbf53..c8ffeafc43 100644 +--- a/src/imap/imap-client.c ++++ b/src/imap/imap-client.c +@@ -143,7 +143,6 @@ struct client *client_create(int fd_in, int fd_out, + o_stream_set_flush_callback(client->output, client_output, client); + + p_array_init(&client->module_contexts, client->pool, 5); +- client->io = io_add_istream(client->input, client_input, client); + client->last_input = ioloop_time; + client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS, + client_idle_timeout, client); +@@ -228,6 +227,7 @@ int client_create_finish(struct client *client, const char **error_r) + return -1; + mail_namespaces_set_storage_callbacks(client->user->namespaces, + &mail_storage_callbacks, client); ++ client->io = io_add_istream(client->input, client_input, client); + + client->v.init(client); + return 0; diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_24386-part4.patch b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part4.patch new file mode 100644 index 0000000..e6af09e --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_24386-part4.patch @@ -0,0 +1,105 @@ +From 1a27cfa8e337b7e3298ba230059e766cdbc1123d Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 24 Aug 2020 19:10:43 +0300 +Subject: [PATCH] imap: imap_client_hibernate() - Return reason string on + failure + +This helps writing a unit test for it. +--- + src/imap/cmd-idle.c | 3 ++- + src/imap/imap-client-hibernate.c | 10 +++++++++- + src/imap/imap-client.h | 5 +++-- + 3 files changed, 14 insertions(+), 4 deletions(-) + +diff --git a/src/imap/cmd-idle.c b/src/imap/cmd-idle.c +index 8a05582d03..2b31dc714e 100644 +--- a/src/imap/cmd-idle.c ++++ b/src/imap/cmd-idle.c +@@ -175,11 +175,12 @@ static void idle_add_keepalive_timeout(struct cmd_idle_context *ctx) + static void idle_hibernate_timeout(struct cmd_idle_context *ctx) + { + struct client *client = ctx->client; ++ const char *reason; + + i_assert(ctx->sync_ctx == NULL); + i_assert(!ctx->sync_pending); + +- if (imap_client_hibernate(&client)) { ++ if (imap_client_hibernate(&client, &reason)) { + /* client may be destroyed now */ + } else { + /* failed - don't bother retrying */ +diff --git a/src/imap/imap-client-hibernate.c b/src/imap/imap-client-hibernate.c +index d3451b1bf6..0709e4a244 100644 +--- a/src/imap/imap-client-hibernate.c ++++ b/src/imap/imap-client-hibernate.c +@@ -203,19 +203,23 @@ imap_hibernate_process_send(struct client *client, const buffer_t *state, + return 0; + } + +-bool imap_client_hibernate(struct client **_client) ++bool imap_client_hibernate(struct client **_client, const char **reason_r) + { + struct client *client = *_client; + buffer_t *state; + const char *error; + int ret, fd_notify = -1, fd_hibernate = -1; + ++ *reason_r = NULL; ++ + if (client->fd_in != client->fd_out) { + /* we won't try to hibernate stdio clients */ ++ *reason_r = "stdio clients can't be hibernated"; + return FALSE; + } + if (o_stream_get_buffer_used_size(client->output) > 0) { + /* wait until we've sent the pending output to client */ ++ *reason_r = "output pending to client"; + return FALSE; + } + +@@ -233,11 +237,13 @@ bool imap_client_hibernate(struct client **_client) + "Couldn't export state: %s (mailbox=%s)", error, + client->mailbox == NULL ? "" : + mailbox_get_vname(client->mailbox)); ++ *reason_r = error; + } else if (ret == 0) { + e_debug(client->event, "Couldn't hibernate imap client: " + "Couldn't export state: %s (mailbox=%s)", error, + client->mailbox == NULL ? "" : + mailbox_get_vname(client->mailbox)); ++ *reason_r = error; + } + if (ret > 0 && client->mailbox != NULL) { + fd_notify = mailbox_watch_extract_notify_fd(client->mailbox, +@@ -248,6 +254,7 @@ bool imap_client_hibernate(struct client **_client) + e_debug(client->event, "Couldn't hibernate imap client: " + "Couldn't extract notifications fd: %s", + error); ++ *reason_r = error; + ret = -1; + } + } +@@ -257,5 +264,6 @@ bool imap_client_hibernate(struct client **_client) + e_error(client->event, + "Couldn't hibernate imap client: %s", error); ++ *reason_r = error; + ret = -1; + } + } +diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h +index f2ffe0d7c9..4e591d5c7c 100644 +--- a/src/imap/imap-client.h ++++ b/src/imap/imap-client.h +@@ -323,8 +323,9 @@ enum mailbox_feature client_enabled_mailbox_features(struct client *client); + const char *const *client_enabled_features(struct client *client); + + /* Send client processing to imap-idle process. If successful, returns TRUE +- and destroys the client. */ +-bool imap_client_hibernate(struct client **client); ++ and destroys the client. If hibernation failed, the exact reason is ++ returned (mainly for unit tests). */ ++bool imap_client_hibernate(struct client **client, const char **reason_r); + + struct imap_search_update * + client_search_update_lookup(struct client *client, const char *tag, diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_24386-prereq1.patch b/SOURCES/dovecot-2.3.13-CVE_2020_24386-prereq1.patch new file mode 100644 index 0000000..00ee47f --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_24386-prereq1.patch @@ -0,0 +1,129 @@ +diff -up dovecot-2.3.8/src/imap/imap-client-hibernate.c.CVE_2020_24386-prereq1 dovecot-2.3.8/src/imap/imap-client-hibernate.c +--- dovecot-2.3.8/src/imap/imap-client-hibernate.c.CVE_2020_24386-prereq1 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/imap/imap-client-hibernate.c 2021-01-08 17:14:40.051174282 +0100 +@@ -19,24 +19,26 @@ + #define IMAP_HIBERNATE_SEND_TIMEOUT_SECS 10 + #define IMAP_HIBERNATE_HANDSHAKE "VERSION\timap-hibernate\t1\t0\n" + +-static int imap_hibernate_handshake(int fd, const char *path) ++static int ++imap_hibernate_handshake(int fd, const char *path, const char **error_r) + { + char buf[1024]; + ssize_t ret; + + if (write_full(fd, IMAP_HIBERNATE_HANDSHAKE, + strlen(IMAP_HIBERNATE_HANDSHAKE)) < 0) { +- i_error("write(%s) failed: %m", path); ++ *error_r = t_strdup_printf("write(%s) failed: %m", path); + return -1; + } else if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) { +- i_error("read(%s) failed: %m", path); ++ *error_r = t_strdup_printf("read(%s) failed: %m", path); + return -1; + } else if (ret > 0 && buf[ret-1] == '\n') { + buf[ret-1] = '\0'; + if (version_string_verify(buf, "imap-hibernate", 1)) + return 0; + } +- i_error("%s sent invalid VERSION handshake: %s", path, buf); ++ *error_r = t_strdup_printf("%s sent invalid VERSION handshake: %s", ++ path, buf); + return -1; + } + +@@ -105,40 +107,42 @@ static void imap_hibernate_write_cmd(str + + static int + imap_hibernate_process_send_cmd(int fd_socket, const char *path, +- const string_t *cmd, int fd_client) ++ const string_t *cmd, int fd_client, ++ const char **error_r) + { + ssize_t ret; + + i_assert(fd_socket != -1); + i_assert(str_len(cmd) > 1); + +- if (imap_hibernate_handshake(fd_socket, path) < 0) ++ if (imap_hibernate_handshake(fd_socket, path, error_r) < 0) + return -1; + if ((ret = fd_send(fd_socket, fd_client, str_data(cmd), 1)) < 0) { +- i_error("fd_send(%s) failed: %m", path); ++ *error_r = t_strdup_printf("fd_send(%s) failed: %m", path); + return -1; + } + if ((ret = write_full(fd_socket, str_data(cmd)+1, str_len(cmd)-1)) < 0) { +- i_error("write(%s) failed: %m", path); ++ *error_r = t_strdup_printf("write(%s) failed: %m", path); + return -1; + } + return 0; + } + +-static int imap_hibernate_process_read(int fd, const char *path) ++static int ++imap_hibernate_process_read(int fd, const char *path, const char **error_r) + { + char buf[1024]; + ssize_t ret; + + if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) { +- i_error("read(%s) failed: %m", path); ++ *error_r = t_strdup_printf("read(%s) failed: %m", path); + return -1; + } else if (ret == 0) { +- i_error("%s disconnected", path); ++ *error_r = t_strdup_printf("%s disconnected", path); + return -1; + } else if (buf[0] != '+') { + buf[ret] = '\0'; +- i_error("%s returned failure: %s", path, ++ *error_r = t_strdup_printf("%s returned failure: %s", path, + ret > 0 && buf[0] == '-' ? buf+1 : buf); + return -1; + } else { +@@ -147,8 +151,8 @@ static int imap_hibernate_process_read(i + } + + static int +-imap_hibernate_process_send(struct client *client, +- const buffer_t *state, int fd_notify, int *fd_r) ++imap_hibernate_process_send(struct client *client, const buffer_t *state, ++ int fd_notify, int *fd_r, const char **error_r) + { + string_t *cmd = t_str_new(512); + const char *path; +@@ -171,14 +175,14 @@ imap_hibernate_process_send(struct clien + imap_hibernate_write_cmd(client, cmd, state, fd_notify); + + alarm(IMAP_HIBERNATE_SEND_TIMEOUT_SECS); +- if (imap_hibernate_process_send_cmd(fd, path, cmd, client->fd_in) < 0 || +- imap_hibernate_process_read(fd, path) < 0) ++ if (imap_hibernate_process_send_cmd(fd, path, cmd, client->fd_in, error_r) < 0 || ++ imap_hibernate_process_read(fd, path, error_r) < 0) + ret = -1; + else if (fd_notify != -1) { + if ((ret = fd_send(fd, fd_notify, "\n", 1)) < 0) +- i_error("fd_send(%s) failed: %m", path); ++ *error_r = t_strdup_printf("fd_send(%s) failed: %m", path); + else +- ret = imap_hibernate_process_read(fd, path); ++ ret = imap_hibernate_process_read(fd, path, error_r); + } + alarm(0); + if (ret < 0) { +@@ -229,8 +233,12 @@ bool imap_client_hibernate(struct client + } + } + if (ret > 0) { +- if (imap_hibernate_process_send(client, state, fd_notify, &fd_hibernate) < 0) ++ if (imap_hibernate_process_send(client, state, fd_notify, ++ &fd_hibernate, &error) < 0) { ++ e_error(client->event, ++ "Couldn't hibernate imap client: %s", error); + ret = -1; ++ } + } + i_close_fd(&fd_notify); + if (ret > 0) { diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part1.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part1.patch new file mode 100644 index 0000000..b5c630f --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part1.patch @@ -0,0 +1,185 @@ +From b9a2f18466a0d3377bab3e7a57691bdd75d8507c Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 17:32:11 +0300 +Subject: [PATCH] lib-imap: Add imap_parser_read_tag() and _read_command_name() + +--- + src/lib-imap/imap-parser.c | 67 +++++++++++++++++++++++++++++++++ + src/lib-imap/imap-parser.h | 7 ++++ + src/lib-imap/test-imap-parser.c | 67 +++++++++++++++++++++++++++++++++ + 3 files changed, 141 insertions(+) + +diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c +index b6c6e63fb1..52d79282fa 100644 +--- a/src/lib-imap/imap-parser.c ++++ b/src/lib-imap/imap-parser.c +@@ -947,3 +947,70 @@ const char *imap_parser_read_word(struct imap_parser *parser) + return NULL; + } + } ++ ++static int ++imap_parser_read_next_atom(struct imap_parser *parser, bool parsing_tag, ++ const char **atom_r) ++{ ++ const unsigned char *data; ++ size_t i, data_size; ++ ++ data = i_stream_get_data(parser->input, &data_size); ++ ++ /* ++ tag = 1* ++ ASTRING-CHAR = ATOM-CHAR / resp-specials ++ ATOM-CHAR = ++ ++ x-command = "X" atom ++ atom = 1*ATOM-CHAR ++ */ ++ for (i = 0; i < data_size; i++) { ++ /* explicitly check for atom-specials, because ++ IS_ATOM_PARSER_INPUT() allows some atom-specials */ ++ switch (data[i]) { ++ case ' ': ++ case '\r': ++ case '\n': ++ data_size = i + (data[i] == ' ' ? 1 : 0); ++ parser->line_size += data_size; ++ i_stream_skip(parser->input, data_size); ++ *atom_r = p_strndup(parser->pool, data, i); ++ /* don't allow empty string */ ++ return i == 0 ? -1 : 1; ++ /* atom-specials: */ ++ case '(': ++ case ')': ++ case '{': ++ /* list-wildcards: */ ++ case '%': ++ case '*': ++ /* quoted-specials: */ ++ case '"': ++ case '\\': ++ /* resp-specials: */ ++ case ']': ++ return -1; ++ case '+': ++ if (parsing_tag) ++ return -1; ++ break; ++ default: ++ if ((unsigned char)data[i] < ' ' || ++ (unsigned char)data[i] >= 0x80) ++ return -1; ++ } ++ } ++ return 0; ++} ++ ++int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r) ++{ ++ return imap_parser_read_next_atom(parser, TRUE, tag_r); ++} ++ ++int imap_parser_read_command_name(struct imap_parser *parser, ++ const char **name_r) ++{ ++ return imap_parser_read_next_atom(parser, FALSE, name_r); ++} +diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h +index e5d01c17f2..5e09d61d2b 100644 +--- a/src/lib-imap/imap-parser.h ++++ b/src/lib-imap/imap-parser.h +@@ -101,5 +101,12 @@ int imap_parser_finish_line(struct imap_parser *parser, unsigned int count, + /* Read one word - used for reading tag and command name. + Returns NULL if more data is needed. */ + const char *imap_parser_read_word(struct imap_parser *parser); ++/* Read command tag. Returns 1 if tag was returned, 0 if more data is needed, ++ -1 if input isn't a valid tag. */ ++int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r); ++/* Read command name. Returns 1 if command name was returned, 0 if more data is ++ needed, -1 if input isn't a valid command name string. */ ++int imap_parser_read_command_name(struct imap_parser *parser, ++ const char **name_r); + + #endif +diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c +index 93ef8fd59b..3ca4e34858 100644 +--- a/src/lib-imap/test-imap-parser.c ++++ b/src/lib-imap/test-imap-parser.c +@@ -79,10 +79,77 @@ static void test_imap_parser_partial_list(void) + test_end(); + } + ++static void test_imap_parser_read_tag_cmd(void) ++{ ++ enum read_type { ++ BOTH, ++ TAG, ++ COMMAND ++ }; ++ struct { ++ const char *input; ++ const char *tag; ++ int ret; ++ enum read_type type; ++ } tests[] = { ++ { "tag foo", "tag", 1, BOTH }, ++ { "tag\r", "tag", 1, BOTH }, ++ { "tag\rfoo", "tag", 1, BOTH }, ++ { "tag\nfoo", "tag", 1, BOTH }, ++ { "tag\r\nfoo", "tag", 1, BOTH }, ++ { "\n", NULL, -1, BOTH }, ++ { "tag", NULL, 0, BOTH }, ++ { "tag\t", NULL, -1, BOTH }, ++ { "tag\001", NULL, -1, BOTH }, ++ { "tag\x80", NULL, -1, BOTH }, ++ { "tag(", NULL, -1, BOTH }, ++ { "tag)", NULL, -1, BOTH }, ++ { "tag{", NULL, -1, BOTH }, ++ { "tag/ ", "tag/", 1, BOTH }, ++ { "tag%", NULL, -1, BOTH }, ++ { "tag*", NULL, -1, BOTH }, ++ { "tag\"", NULL, -1, BOTH }, ++ { "tag\\", NULL, -1, BOTH }, ++ { "tag+", NULL, -1, TAG }, ++ { "tag+ ", "tag+", 1, COMMAND }, ++ }; ++ struct istream *input; ++ struct imap_parser *parser; ++ const char *atom; ++ int ret; ++ ++ test_begin("imap_parser_read_tag and imap_parser_read_command_name"); ++ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { ++ if (tests[i].type != COMMAND) { ++ input = test_istream_create(tests[i].input); ++ test_assert(i_stream_read(input) > 0); ++ parser = imap_parser_create(input, NULL, 1024); ++ ret = imap_parser_read_tag(parser, &atom); ++ test_assert_idx(ret == tests[i].ret, i); ++ test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i); ++ imap_parser_unref(&parser); ++ i_stream_destroy(&input); ++ } ++ ++ if (tests[i].type != TAG) { ++ input = test_istream_create(tests[i].input); ++ test_assert(i_stream_read(input) > 0); ++ parser = imap_parser_create(input, NULL, 1024); ++ ret = imap_parser_read_command_name(parser, &atom); ++ test_assert_idx(ret == tests[i].ret, i); ++ test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i); ++ imap_parser_unref(&parser); ++ i_stream_destroy(&input); ++ } ++ } ++ test_end(); ++} ++ + int main(void) + { + static void (*const test_functions[])(void) = { + test_imap_parser_crlf, ++ test_imap_parser_read_tag_cmd, + NULL + }; + return test_run(test_functions); diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part2.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part2.patch new file mode 100644 index 0000000..d50ae5b --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part2.patch @@ -0,0 +1,46 @@ +From eea57c8683325f9767b2eb1b44a0b23352541c1e Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 17:59:19 +0300 +Subject: [PATCH] imap: Split off client_command_failed_early() + +--- + src/imap/imap-client.c | 19 ++++++++++++++----- + 1 file changed, 14 insertions(+), 5 deletions(-) + +diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c +index 07b2a8018b..0bf03caa97 100644 +--- a/src/imap/imap-client.c ++++ b/src/imap/imap-client.c +@@ -1176,6 +1176,19 @@ bool client_handle_unfinished_cmd(struct client_command_context *cmd) + return TRUE; + } + ++static void ++client_command_failed_early(struct client_command_context **_cmd, ++ const char *error) ++{ ++ struct client_command_context *cmd = *_cmd; ++ ++ io_loop_time_refresh(); ++ command_stats_start(cmd); ++ client_send_command_error(cmd, error); ++ cmd->param_error = TRUE; ++ client_command_free(_cmd); ++} ++ + static bool client_command_input(struct client_command_context *cmd) + { + struct client *client = cmd->client; +@@ -1239,11 +1252,7 @@ static bool client_command_input(struct client_command_context *cmd) + + if (cmd->func == NULL) { + /* unknown command */ +- io_loop_time_refresh(); +- command_stats_start(cmd); +- client_send_command_error(cmd, "Unknown command."); +- cmd->param_error = TRUE; +- client_command_free(&cmd); ++ client_command_failed_early(&cmd, "Unknown command."); + return TRUE; + } else { + i_assert(!client->disconnected); diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part3.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part3.patch new file mode 100644 index 0000000..5c176c3 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part3.patch @@ -0,0 +1,77 @@ +From 0386140f61f9ba62225e90b419215f72bba6ad8b Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 18:11:36 +0300 +Subject: [PATCH] imap: Use imap_parser_read_tag() and _read_command_name() + +--- + src/imap/imap-client.c | 33 ++++++++++++++++++++++----------- + 1 file changed, 22 insertions(+), 11 deletions(-) + +diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c +index 0bf03caa97..95e57dbf53 100644 +--- a/src/imap/imap-client.c ++++ b/src/imap/imap-client.c +@@ -1182,6 +1182,9 @@ client_command_failed_early(struct client_command_context **_cmd, + { + struct client_command_context *cmd = *_cmd; + ++ /* ignore the rest of this line */ ++ cmd->client->input_skip_line = TRUE; ++ + io_loop_time_refresh(); + command_stats_start(cmd); + client_send_command_error(cmd, error); +@@ -1193,6 +1196,8 @@ static bool client_command_input(struct client_command_context *cmd) + { + struct client *client = cmd->client; + struct command *command; ++ const char *tag, *name; ++ int ret; + + if (cmd->func != NULL) { + /* command is being executed - continue it */ +@@ -1207,27 +1212,33 @@ static bool client_command_input(struct client_command_context *cmd) + } + + if (cmd->tag == NULL) { +- cmd->tag = imap_parser_read_word(cmd->parser); +- if (cmd->tag == NULL) ++ ret = imap_parser_read_tag(cmd->parser, &tag); ++ if (ret == 0) + return FALSE; /* need more data */ +- cmd->tag = p_strdup(cmd->pool, cmd->tag); ++ if (ret < 0) { ++ client_command_failed_early(&cmd, "Invalid tag."); ++ return TRUE; ++ } ++ cmd->tag = p_strdup(cmd->pool, tag); + } + + if (cmd->name == NULL) { +- cmd->name = imap_parser_read_word(cmd->parser); +- if (cmd->name == NULL) ++ ret = imap_parser_read_command_name(cmd->parser, &name); ++ if (ret == 0) + return FALSE; /* need more data */ ++ if (ret < 0) { ++ client_command_failed_early(&cmd, "Invalid command name."); ++ return TRUE; ++ } + + /* UID commands are a special case. better to handle them + here. */ +- if (!cmd->uid && strcasecmp(cmd->name, "UID") == 0) { ++ if (!cmd->uid && strcasecmp(name, "UID") == 0) { + cmd->uid = TRUE; +- cmd->name = imap_parser_read_word(cmd->parser); +- if (cmd->name == NULL) +- return FALSE; /* need more data */ ++ return client_command_input(cmd); + } +- cmd->name = !cmd->uid ? p_strdup(cmd->pool, cmd->name) : +- p_strconcat(cmd->pool, "UID ", cmd->name, NULL); ++ cmd->name = !cmd->uid ? p_strdup(cmd->pool, name) : ++ p_strconcat(cmd->pool, "UID ", name, NULL); + client_command_init_finished(cmd); + imap_refresh_proctitle(); + } diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part4.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part4.patch new file mode 100644 index 0000000..a59ad97 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part4.patch @@ -0,0 +1,55 @@ +From 62061e8cf68f506c0ccaaba21fd4174764ca875f Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 18:15:35 +0300 +Subject: [PATCH] imap-login: Split off client_invalid_command() + +--- + src/imap-login/imap-login-client.c | 27 +++++++++++++++++---------- + 1 file changed, 17 insertions(+), 10 deletions(-) + +diff --git a/src/imap-login/imap-login-client.c b/src/imap-login/imap-login-client.c +index e2af176309..ce5049d567 100644 +--- a/src/imap-login/imap-login-client.c ++++ b/src/imap-login/imap-login-client.c +@@ -194,6 +194,22 @@ static int client_command_execute(struct imap_client *client, const char *cmd, + return login_cmd->func(client, args); + } + ++static bool client_invalid_command(struct imap_client *client) ++{ ++ if (*client->cmd_tag == '\0') ++ client->cmd_tag = "*"; ++ if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) { ++ client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, ++ "Too many invalid IMAP commands."); ++ client_destroy(&client->common, ++ "Disconnected: Too many invalid commands"); ++ return FALSE; ++ } ++ client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, ++ "Error in IMAP command received by server."); ++ return TRUE; ++} ++ + static bool imap_is_valid_tag(const char *tag) + { + for (; *tag != '\0'; tag++) { +@@ -326,17 +342,8 @@ static bool imap_client_input_next_cmd(struct client *_client) + "not the command name. Add that before the command, " + "like: a login user pass"); + } else if (ret < 0) { +- if (*client->cmd_tag == '\0') +- client->cmd_tag = "*"; +- if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) { +- client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, +- "Too many invalid IMAP commands."); +- client_destroy(&client->common, +- "Disconnected: Too many invalid commands"); ++ if (!client_invalid_command(client)) + return FALSE; +- } +- client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, +- "Error in IMAP command received by server."); + } + + return ret != 0 && !client->common.destroyed; diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part5.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part5.patch new file mode 100644 index 0000000..8c3d0eb --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part5.patch @@ -0,0 +1,110 @@ +From 9d3ecff3de5553159334cf644e996a616dc52670 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 18:22:42 +0300 +Subject: [PATCH] imap-login: Use imap_parser_read_tag() and + _read_command_name() + +--- + src/imap-login/imap-login-client.c | 58 ++++++++++++------------------ + 1 file changed, 23 insertions(+), 35 deletions(-) + +diff --git a/src/imap-login/imap-login-client.c b/src/imap-login/imap-login-client.c +index ce5049d567..b2c8af9cbf 100644 +--- a/src/imap-login/imap-login-client.c ++++ b/src/imap-login/imap-login-client.c +@@ -196,7 +196,7 @@ static int client_command_execute(struct imap_client *client, const char *cmd, + + static bool client_invalid_command(struct imap_client *client) + { +- if (*client->cmd_tag == '\0') ++ if (client->cmd_tag == NULL || *client->cmd_tag == '\0') + client->cmd_tag = "*"; + if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) { + client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, +@@ -210,33 +210,6 @@ static bool client_invalid_command(struct imap_client *client) + return TRUE; + } + +-static bool imap_is_valid_tag(const char *tag) +-{ +- for (; *tag != '\0'; tag++) { +- switch (*tag) { +- case '+': +- /* atom-specials: */ +- case '(': +- case ')': +- case '{': +- case '/': +- case ' ': +- /* list-wildcards: */ +- case '%': +- case '*': +- /* quoted-specials: */ +- case '"': +- case '\\': +- return FALSE; +- default: +- if (*tag < ' ') /* CTL */ +- return FALSE; +- break; +- } +- } +- return TRUE; +-} +- + static int client_parse_command(struct imap_client *client, + const struct imap_arg **args_r) + { +@@ -261,6 +234,9 @@ static int client_parse_command(struct imap_client *client, + + static bool client_handle_input(struct imap_client *client) + { ++ const char *tag, *name; ++ int ret; ++ + i_assert(!client->common.authenticating); + + if (client->cmd_finished) { +@@ -282,23 +258,35 @@ static bool client_handle_input(struct imap_client *client) + } + + if (client->cmd_tag == NULL) { +- client->cmd_tag = imap_parser_read_word(client->parser); +- if (client->cmd_tag == NULL) ++ ret = imap_parser_read_tag(client->parser, &tag); ++ if (ret == 0) + return FALSE; /* need more data */ +- if (!imap_is_valid_tag(client->cmd_tag) || +- strlen(client->cmd_tag) > IMAP_TAG_MAX_LEN) { ++ if (ret < 0 || strlen(tag) > IMAP_TAG_MAX_LEN) { + /* the tag is invalid, don't allow it and don't + send it back. this attempts to prevent any + potentially dangerous replies in case someone tries + to access us using HTTP protocol. */ +- client->cmd_tag = ""; ++ client->skip_line = TRUE; ++ client->cmd_finished = TRUE; ++ if (!client_invalid_command(client)) ++ return FALSE; ++ return client_handle_input(client); + } ++ client->cmd_tag = tag; + } + + if (client->cmd_name == NULL) { +- client->cmd_name = imap_parser_read_word(client->parser); +- if (client->cmd_name == NULL) ++ ret = imap_parser_read_command_name(client->parser, &name); ++ if (ret == 0) + return FALSE; /* need more data */ ++ if (ret < 0) { ++ client->skip_line = TRUE; ++ client->cmd_finished = TRUE; ++ if (!client_invalid_command(client)) ++ return FALSE; ++ return client_handle_input(client); ++ } ++ client->cmd_name = name; + } + return client->common.v.input_next_cmd(&client->common); + } diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part6.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part6.patch new file mode 100644 index 0000000..0233807 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part6.patch @@ -0,0 +1,39 @@ +From 7a70f01fe8084431901433a2f74cb9c70fd00568 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 18:26:01 +0300 +Subject: [PATCH] lib-imap: Add imap_parser_client_read_tag() + +--- + src/lib-imap/imap-parser.c | 6 ++++++ + src/lib-imap/imap-parser.h | 5 +++++ + 2 files changed, 11 insertions(+) + +diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c +index 52d79282fa..cc283f5c06 100644 +--- a/src/lib-imap/imap-parser.c ++++ b/src/lib-imap/imap-parser.c +@@ -1014,3 +1014,9 @@ int imap_parser_read_command_name(struct imap_parser *parser, + { + return imap_parser_read_next_atom(parser, FALSE, name_r); + } ++ ++int imap_parser_client_read_tag(struct imap_parser *parser, ++ const char **tag_r) ++{ ++ return imap_parser_read_next_atom(parser, FALSE, tag_r); ++} +diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h +index 5e09d61d2b..cd3748c00f 100644 +--- a/src/lib-imap/imap-parser.h ++++ b/src/lib-imap/imap-parser.h +@@ -108,5 +108,10 @@ int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r); + needed, -1 if input isn't a valid command name string. */ + int imap_parser_read_command_name(struct imap_parser *parser, + const char **name_r); ++/* For IMAP clients: Read the command tag, which could also be "+" or "*". ++ Returns 1 if tag was returned, 0 if more data is needed, -1 if input isn't ++ valid. */ ++int imap_parser_client_read_tag(struct imap_parser *parser, ++ const char **tag_r); + + #endif diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part7.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part7.patch new file mode 100644 index 0000000..87b52ea --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part7.patch @@ -0,0 +1,126 @@ +From fb97a1cddbda4019e327fa736972a1c7433fedaa Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 11 Sep 2020 09:53:03 +0300 +Subject: [PATCH] lib-mail: message-parser - Fix assert-crash when enforcing + MIME part limit + +The limit could have been exceeded with message/rfc822 parts. +--- + src/lib-mail/message-parser.c | 3 +- + src/lib-mail/test-message-parser.c | 82 ++++++++++++++++++++++++++++++ + 2 files changed, 84 insertions(+), 1 deletion(-) + +diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c +index 6ab4c3266f..40a504da0a 100644 +--- a/src/lib-mail/message-parser.c ++++ b/src/lib-mail/message-parser.c +@@ -703,7 +703,8 @@ static int parse_next_header(struct message_parser_ctx *ctx, + ctx->multipart = FALSE; + ctx->parse_next_block = parse_next_body_to_boundary; + } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 && +- !parse_too_many_nested_mime_parts(ctx)) { ++ !parse_too_many_nested_mime_parts(ctx) && ++ ctx->total_parts_count < ctx->max_total_mime_parts) { + ctx->parse_next_block = parse_next_body_message_rfc822_init; + } else { + part->flags &= ~MESSAGE_PART_FLAG_MESSAGE_RFC822; +diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c +index 8c5a3404f1..c4e117afc7 100644 +--- a/src/lib-mail/test-message-parser.c ++++ b/src/lib-mail/test-message-parser.c +@@ -1127,6 +1127,87 @@ static const char input_msg[] = + test_end(); + } + ++static void test_message_parser_mime_part_limit_rfc822(void) ++{ ++static const char input_msg[] = ++"Content-Type: multipart/mixed; boundary=\"1\"\n" ++"\n" ++"--1\n" ++"Content-Type: multipart/mixed; boundary=\"2\"\n" ++"\n" ++"--2\n" ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"1\n" ++"--2\n" ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"22\n" ++"--1\n" ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"333\n"; ++ const struct message_parser_settings parser_set = { ++ .max_total_mime_parts = 3, ++ }; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts, *part; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser mime part limit rfc822"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &parser_set); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ part = parts; ++ test_assert(part->children_count == 2); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 45); ++ test_assert(part->header_size.virtual_size == 45+2); ++ test_assert(part->body_size.lines == 21); ++ test_assert(part->body_size.physical_size == 238); ++ test_assert(part->body_size.virtual_size == 238+21); ++ ++ part = parts->children; ++ test_assert(part->children_count == 1); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 45); ++ test_assert(part->header_size.virtual_size == 45+2); ++ test_assert(part->body_size.lines == 18); ++ test_assert(part->body_size.physical_size == 189); ++ test_assert(part->body_size.virtual_size == 189+18); ++ ++ part = parts->children->children; ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 30); ++ test_assert(part->header_size.virtual_size == 30+2); ++ test_assert(part->body_size.lines == 15); ++ test_assert(part->body_size.physical_size == 155); ++ test_assert(part->body_size.virtual_size == 155+15); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + int main(void) + { + static void (*const test_functions[])(void) = { +@@ -1301,6 +1382,7 @@ int main(void) + test_message_parser_mime_part_nested_limit, + test_message_parser_mime_part_nested_limit_rfc822, + test_message_parser_mime_part_limit, ++ test_message_parser_mime_part_limit_rfc822, + NULL + }; + return test_run(test_functions); diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275-part8.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part8.patch new file mode 100644 index 0000000..1953372 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275-part8.patch @@ -0,0 +1,63 @@ +From 266e54b7b8c34c9a58dd60a2e53c5ca7d1deae19 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 11 Sep 2020 10:57:51 +0300 +Subject: [PATCH] lib-imap: Don't generate invalid BODYSTRUCTURE when reaching + MIME part limit + +If the last MIME part was message/rfc822 and its child was truncated away, +BODYSTRUCTURE was missing the ENVELOPE and BODY[STRUCTURE] parts. Fixed by +writing empty dummy ones. +--- + src/lib-imap/imap-bodystructure.c | 29 +++++++++++++++++++++++++++-- + 1 file changed, 27 insertions(+), 2 deletions(-) + +diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c +index 4e379e56a9..e3da1090b4 100644 +--- a/src/lib-imap/imap-bodystructure.c ++++ b/src/lib-imap/imap-bodystructure.c +@@ -146,11 +146,25 @@ static void part_write_body(const struct message_part *part, + string_t *str, bool extended) + { + const struct message_part_data *data = part->data; +- bool text; ++ bool text, message_rfc822; + + i_assert(part->data != NULL); + +- if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { ++ if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) ++ message_rfc822 = TRUE; ++ else if (data->content_type != NULL && ++ strcasecmp(data->content_type, "message") == 0 && ++ strcasecmp(data->content_subtype, "rfc822") == 0) { ++ /* It's message/rfc822, but without ++ MESSAGE_PART_FLAG_MESSAGE_RFC822. That likely means maximum ++ MIME part count was reached while parsing the mail. Write ++ the missing child mail's ENVELOPE and BODY as empty dummy ++ values. */ ++ message_rfc822 = TRUE; ++ } else ++ message_rfc822 = FALSE; ++ ++ if (message_rfc822) { + str_append(str, "\"message\" \"rfc822\""); + text = FALSE; + } else { +@@ -200,6 +214,17 @@ static void part_write_body(const struct message_part *part, + + part_write_bodystructure_siblings(part->children, str, extended); + str_printfa(str, " %u", part->body_size.lines); ++ } else if (message_rfc822) { ++ /* truncated MIME part - write out dummy values */ ++ i_assert(part->children == NULL); ++ ++ str_append(str, " (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) "); ++ ++ if (!extended) ++ str_append(str, EMPTY_BODY); ++ else ++ str_append(str, EMPTY_BODYSTRUCTURE); ++ str_printfa(str, " %u", part->body_size.lines); + } + + if (!extended) diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part1.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part1.patch new file mode 100644 index 0000000..8781d5f --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part1.patch @@ -0,0 +1,228 @@ +From 530c1e950a1bb46ff4e4a7c8e4b7cd945ff28916 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Wed, 18 Nov 2020 18:55:34 +0200 +Subject: [PATCH] lib-imap: Fix writing BODYSTRUCTURE for truncated + message/rfc822 part + +If the max nesting limit is reached, write the last part out as +application/octet-stream instead of dummy message/rfc822. + +Fixes error while parsing BODYSTRUCTURE: +message_part message/rfc822 flag doesn't match BODYSTRUCTURE +--- + src/lib-imap/imap-bodystructure.c | 54 +++++++++---------- + src/lib-imap/test-imap-bodystructure.c | 73 ++++++++++++++++++++++++-- + 2 files changed, 96 insertions(+), 31 deletions(-) + +diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c +index e3da1090b4..ab422c00d2 100644 +--- a/src/lib-imap/imap-bodystructure.c ++++ b/src/lib-imap/imap-bodystructure.c +@@ -142,31 +142,42 @@ static void part_write_body_multipart(const struct message_part *part, + part_write_bodystructure_common(data, str); + } + ++static bool part_is_truncated(const struct message_part *part) ++{ ++ const struct message_part_data *data = part->data; ++ ++ i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0); ++ ++ if (data->content_type != NULL) { ++ if (strcasecmp(data->content_type, "message") == 0 && ++ strcasecmp(data->content_subtype, "rfc822") == 0) { ++ /* It's message/rfc822, but without ++ MESSAGE_PART_FLAG_MESSAGE_RFC822. */ ++ return TRUE; ++ } ++ } ++ return FALSE; ++} ++ + static void part_write_body(const struct message_part *part, + string_t *str, bool extended) + { + const struct message_part_data *data = part->data; +- bool text, message_rfc822; ++ bool text; + + i_assert(part->data != NULL); + +- if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) +- message_rfc822 = TRUE; +- else if (data->content_type != NULL && +- strcasecmp(data->content_type, "message") == 0 && +- strcasecmp(data->content_subtype, "rfc822") == 0) { +- /* It's message/rfc822, but without +- MESSAGE_PART_FLAG_MESSAGE_RFC822. That likely means maximum +- MIME part count was reached while parsing the mail. Write +- the missing child mail's ENVELOPE and BODY as empty dummy +- values. */ +- message_rfc822 = TRUE; +- } else +- message_rfc822 = FALSE; +- +- if (message_rfc822) { ++ if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { + str_append(str, "\"message\" \"rfc822\""); + text = FALSE; ++ } else if (part_is_truncated(part)) { ++ /* Maximum MIME part count was reached while parsing the mail. ++ Write this part out as application/octet-stream instead. ++ We're not using text/plain, because it would require ++ message-parser to use MESSAGE_PART_FLAG_TEXT for this part ++ to avoid losing line count in message_part serialization. */ ++ str_append(str, "\"application\" \"octet-stream\""); ++ text = FALSE; + } else { + /* "content type" "subtype" */ + if (data->content_type == NULL) { +@@ -214,17 +225,6 @@ static void part_write_body(const struct message_part *part, + + part_write_bodystructure_siblings(part->children, str, extended); + str_printfa(str, " %u", part->body_size.lines); +- } else if (message_rfc822) { +- /* truncated MIME part - write out dummy values */ +- i_assert(part->children == NULL); +- +- str_append(str, " (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) "); +- +- if (!extended) +- str_append(str, EMPTY_BODY); +- else +- str_append(str, EMPTY_BODYSTRUCTURE); +- str_printfa(str, " %u", part->body_size.lines); + } + + if (!extended) +diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c +index dfc9957488..6cb699e126 100644 +--- a/src/lib-imap/test-imap-bodystructure.c ++++ b/src/lib-imap/test-imap-bodystructure.c +@@ -4,6 +4,7 @@ + #include "istream.h" + #include "str.h" + #include "message-part-data.h" ++#include "message-part-serialize.h" + #include "message-parser.h" + #include "imap-bodystructure.h" + #include "test-common.h" +@@ -379,12 +380,14 @@ struct normalize_test normalize_tests[] = { + static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests); + + static struct message_part * +-msg_parse(pool_t pool, const char *message, bool parse_bodystructure) ++msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts, ++ bool parse_bodystructure) + { + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, ++ .max_nested_mime_parts = max_nested_mime_parts, + }; + struct message_parser_ctx *parser; + struct istream *input; +@@ -418,7 +421,7 @@ static void test_imap_bodystructure_write(void) + pool_t pool = pool_alloconly_create("imap bodystructure write", 1024); + + test_begin(t_strdup_printf("imap bodystructure write [%u]", i)); +- parts = msg_parse(pool, test->message, TRUE); ++ parts = msg_parse(pool, test->message, 0, TRUE); + + imap_bodystructure_write(parts, str, TRUE); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); +@@ -445,7 +448,7 @@ static void test_imap_bodystructure_parse(void) + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure parser [%u]", i)); +- parts = msg_parse(pool, test->message, FALSE); ++ parts = msg_parse(pool, test->message, 0, FALSE); + + test_assert(imap_body_parse_from_bodystructure(test->bodystructure, + str, &error) == 0); +@@ -512,7 +515,7 @@ static void test_imap_bodystructure_normalize(void) + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure normalize [%u]", i)); +- parts = msg_parse(pool, test->message, FALSE); ++ parts = msg_parse(pool, test->message, 0, FALSE); + + ret = imap_bodystructure_parse(test->input, + pool, parts, &error); +@@ -531,6 +534,67 @@ static void test_imap_bodystructure_normalize(void) + } T_END; + } + ++static const struct { ++ const char *input; ++ const char *bodystructure; ++ unsigned int max_depth; ++} truncation_tests[] = { ++ { ++ .input = "Content-Type: message/rfc822\n" ++ "\n" ++ "Content-Type: message/rfc822\n" ++ "Header2: value2\n" ++ "\n" ++ "Subject: hello world\n" ++ "Header2: value2\n" ++ "Header3: value3\n" ++ "\n" ++ "body line 1\n" ++ "body line 2\n" ++ "body line 4\n" ++ "body line 3\n", ++ .bodystructure = "\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 159 (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 110 NIL NIL NIL NIL) 11 NIL NIL NIL NIL", ++ .max_depth = 2, ++ }, ++}; ++ ++static void test_imap_bodystructure_truncation(void) ++{ ++ struct message_part *parts; ++ const char *error; ++ string_t *str_body = t_str_new(128); ++ string_t *str_parts = t_str_new(128); ++ pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); ++ ++ test_begin("imap bodystructure truncation"); ++ ++ for (unsigned int i = 0; i < N_ELEMENTS(truncation_tests); i++) { ++ p_clear(pool); ++ str_truncate(str_body, 0); ++ str_truncate(str_parts, 0); ++ ++ parts = msg_parse(pool, truncation_tests[i].input, ++ truncation_tests[i].max_depth, ++ TRUE); ++ ++ /* write out BODYSTRUCTURE and serialize message_parts */ ++ imap_bodystructure_write(parts, str_body, TRUE); ++ message_part_serialize(parts, str_parts); ++ ++ /* now deserialize message_parts and make sure they can be used ++ to parse BODYSTRUCTURE */ ++ parts = message_part_deserialize(pool, str_data(str_parts), ++ str_len(str_parts), &error); ++ test_assert(parts != NULL); ++ test_assert(imap_bodystructure_parse(str_c(str_body), pool, ++ parts, &error) == 0); ++ test_assert_strcmp(str_c(str_body), ++ truncation_tests[i].bodystructure); ++ } ++ pool_unref(&pool); ++ test_end(); ++} ++ + int main(void) + { + static void (*const test_functions[])(void) = { +@@ -538,6 +602,7 @@ int main(void) + test_imap_bodystructure_parse, + test_imap_bodystructure_normalize, + test_imap_bodystructure_parse_full, ++ test_imap_bodystructure_truncation, + NULL + }; + return test_run(test_functions); diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part2.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part2.patch new file mode 100644 index 0000000..db36512 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part2.patch @@ -0,0 +1,64 @@ +From ec2c5ffde7a1ca63219d47831725599e7de76f7f Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Wed, 18 Nov 2020 20:48:11 +0200 +Subject: [PATCH] lib-imap: Fix writing BODYSTRUCTURE for truncated multipart/ + part + +If the max nesting limit is reached, write the last part out as +application/octet-stream. The original content-type could be confusing +IMAP clients when they don't see any child parts. +--- + src/lib-imap/imap-bodystructure.c | 6 ++++++ + src/lib-imap/test-imap-bodystructure.c | 15 +++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c +index ab422c00d2..bfb6e64197 100644 +--- a/src/lib-imap/imap-bodystructure.c ++++ b/src/lib-imap/imap-bodystructure.c +@@ -147,6 +147,7 @@ static bool part_is_truncated(const struct message_part *part) + const struct message_part_data *data = part->data; + + i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0); ++ i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0); + + if (data->content_type != NULL) { + if (strcasecmp(data->content_type, "message") == 0 && +@@ -155,6 +156,11 @@ static bool part_is_truncated(const struct message_part *part) + MESSAGE_PART_FLAG_MESSAGE_RFC822. */ + return TRUE; + } ++ if (strcasecmp(data->content_type, "multipart") == 0) { ++ /* It's multipart/, but without ++ MESSAGE_PART_FLAG_MULTIPART. */ ++ return TRUE; ++ } + } + return FALSE; + } +diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c +index 6cb699e126..2118907e78 100644 +--- a/src/lib-imap/test-imap-bodystructure.c ++++ b/src/lib-imap/test-imap-bodystructure.c +@@ -556,6 +556,21 @@ static const struct { + .bodystructure = "\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 159 (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 110 NIL NIL NIL NIL) 11 NIL NIL NIL NIL", + .max_depth = 2, + }, ++ { ++ .input = "Content-Type: multipart/mixed; boundary=1\n" ++ "\n" ++ "--1\n" ++ "Content-Type: multipart/mixed; boundary=2\n" ++ "\n" ++ "--2\n" ++ "Content-Type: multipart/mixed; boundary=3\n" ++ "\n" ++ "--3\n" ++ "\n" ++ "body\n", ++ .bodystructure = "(\"application\" \"octet-stream\" (\"boundary\" \"2\") NIL NIL \"7bit\" 63 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"1\") NIL NIL NIL", ++ .max_depth = 2, ++ }, + }; + + static void test_imap_bodystructure_truncation(void) diff --git a/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part3.patch b/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part3.patch new file mode 100644 index 0000000..b8eeb62 --- /dev/null +++ b/SOURCES/dovecot-2.3.13-CVE_2020_25275regr-part3.patch @@ -0,0 +1,127 @@ +From a912198bdc38421ad84044089db84fc14c69c228 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Wed, 18 Nov 2020 21:22:45 +0200 +Subject: [PATCH] lib-imap: Fix writing BODYSTRUCTURE for truncated + multipart/digest part + +Fixes error while parsing BODYSTRUCTURE: +message_part message/rfc822 flag doesn't match lines in BODYSTRUCTURE +--- + src/lib-imap/imap-bodystructure.c | 9 +++++++++ + src/lib-imap/test-imap-bodystructure.c | 28 ++++++++++++++++++++++---- + 2 files changed, 33 insertions(+), 4 deletions(-) + +diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c +index bfb6e64197..5d2e5a3a84 100644 +--- a/src/lib-imap/imap-bodystructure.c ++++ b/src/lib-imap/imap-bodystructure.c +@@ -161,6 +161,14 @@ static bool part_is_truncated(const struct message_part *part) + MESSAGE_PART_FLAG_MULTIPART. */ + return TRUE; + } ++ } else { ++ /* No Content-Type */ ++ if (part->parent != NULL && ++ (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) { ++ /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST ++ (so this should have been message/rfc822). */ ++ return TRUE; ++ } + } + return FALSE; + } +@@ -195,6 +203,7 @@ static void part_write_body(const struct message_part *part, + str_append_c(str, ' '); + imap_append_string(str, data->content_subtype); + } ++ i_assert(text == ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)); + } + + /* ("content type param key" "value" ...) */ +diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c +index 2118907e78..0f70cb0035 100644 +--- a/src/lib-imap/test-imap-bodystructure.c ++++ b/src/lib-imap/test-imap-bodystructure.c +@@ -381,13 +381,14 @@ static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests); + + static struct message_part * + msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts, +- bool parse_bodystructure) ++ unsigned int max_total_mime_parts, bool parse_bodystructure) + { + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + .max_nested_mime_parts = max_nested_mime_parts, ++ .max_total_mime_parts = max_total_mime_parts, + }; + struct message_parser_ctx *parser; + struct istream *input; +@@ -421,7 +422,7 @@ static void test_imap_bodystructure_write(void) + pool_t pool = pool_alloconly_create("imap bodystructure write", 1024); + + test_begin(t_strdup_printf("imap bodystructure write [%u]", i)); +- parts = msg_parse(pool, test->message, 0, TRUE); ++ parts = msg_parse(pool, test->message, 0, 0, TRUE); + + imap_bodystructure_write(parts, str, TRUE); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); +@@ -448,7 +449,7 @@ static void test_imap_bodystructure_parse(void) + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure parser [%u]", i)); +- parts = msg_parse(pool, test->message, 0, FALSE); ++ parts = msg_parse(pool, test->message, 0, 0, FALSE); + + test_assert(imap_body_parse_from_bodystructure(test->bodystructure, + str, &error) == 0); +@@ -515,7 +516,7 @@ static void test_imap_bodystructure_normalize(void) + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure normalize [%u]", i)); +- parts = msg_parse(pool, test->message, 0, FALSE); ++ parts = msg_parse(pool, test->message, 0, 0, FALSE); + + ret = imap_bodystructure_parse(test->input, + pool, parts, &error); +@@ -538,6 +539,7 @@ static const struct { + const char *input; + const char *bodystructure; + unsigned int max_depth; ++ unsigned int max_total; + } truncation_tests[] = { + { + .input = "Content-Type: message/rfc822\n" +@@ -571,6 +573,23 @@ static const struct { + .bodystructure = "(\"application\" \"octet-stream\" (\"boundary\" \"2\") NIL NIL \"7bit\" 63 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"1\") NIL NIL NIL", + .max_depth = 2, + }, ++ { ++ .input = "Content-Type: multipart/digest; boundary=1\n" ++ "\n" ++ "--1\n" ++ "\n" ++ "Subject: hdr1\n" ++ "\n" ++ "body1\n" ++ "--1\n" ++ "\n" ++ "Subject: hdr2\n" ++ "\n" ++ "body2\n", ++ .bodystructure = "(\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 55 NIL NIL NIL NIL) \"digest\" (\"boundary\" \"1\") NIL NIL NIL", ++ .max_total = 2, ++ }, ++ + }; + + static void test_imap_bodystructure_truncation(void) +@@ -590,6 +609,7 @@ static void test_imap_bodystructure_truncation(void) + + parts = msg_parse(pool, truncation_tests[i].input, + truncation_tests[i].max_depth, ++ truncation_tests[i].max_total, + TRUE); + + /* write out BODYSTRUCTURE and serialize message_parts */ diff --git a/SOURCES/dovecot-2.3.8-CVE_2020_12100.patch b/SOURCES/dovecot-2.3.8-CVE_2020_12100.patch new file mode 100644 index 0000000..c1ddfdd --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12100.patch @@ -0,0 +1,2437 @@ +diff -up dovecot-2.3.8/src/doveadm/doveadm-mail-fetch.c.CVE_2020_12100 dovecot-2.3.8/src/doveadm/doveadm-mail-fetch.c +--- dovecot-2.3.8/src/doveadm/doveadm-mail-fetch.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/doveadm/doveadm-mail-fetch.c 2020-08-07 12:29:39.975827486 +0200 +@@ -265,6 +265,9 @@ static int fetch_text(struct fetch_cmd_c + + static int fetch_text_utf8(struct fetch_cmd_context *ctx) + { ++ const struct message_parser_settings parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, ++ }; + struct istream *input; + struct message_parser_ctx *parser; + struct message_decoder_context *decoder; +@@ -275,9 +278,7 @@ static int fetch_text_utf8(struct fetch_ + if (mail_get_stream(ctx->mail, NULL, NULL, &input) < 0) + return -1; + +- parser = message_parser_init(pool_datastack_create(), input, +- MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, +- 0); ++ parser = message_parser_init(pool_datastack_create(), input, &parser_set); + decoder = message_decoder_init(NULL, 0); + + while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) { +diff -up dovecot-2.3.8/src/lib-imap/test-imap-bodystructure.c.CVE_2020_12100 dovecot-2.3.8/src/lib-imap/test-imap-bodystructure.c +--- dovecot-2.3.8/src/lib-imap/test-imap-bodystructure.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-imap/test-imap-bodystructure.c 2020-08-07 12:29:39.975827486 +0200 +@@ -381,6 +381,11 @@ static const unsigned int normalize_test + static struct message_part * + msg_parse(pool_t pool, const char *message, bool parse_bodystructure) + { ++ const struct message_parser_settings parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | ++ MESSAGE_HEADER_PARSER_FLAG_DROP_CR, ++ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, ++ }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_block block; +@@ -388,10 +393,7 @@ msg_parse(pool_t pool, const char *messa + int ret; + + input = i_stream_create_from_data(message, strlen(message)); +- parser = message_parser_init(pool, input, +- MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | +- MESSAGE_HEADER_PARSER_FLAG_DROP_CR, +- MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); ++ parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + if (parse_bodystructure) { + message_part_data_parse_from_header(pool, block.part, +diff -up dovecot-2.3.8/src/lib-imap/test-imap-envelope.c.CVE_2020_12100 dovecot-2.3.8/src/lib-imap/test-imap-envelope.c +--- dovecot-2.3.8/src/lib-imap/test-imap-envelope.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-imap/test-imap-envelope.c 2020-08-07 12:29:39.975827486 +0200 +@@ -118,6 +118,11 @@ static const unsigned int parse_tests_co + static struct message_part_envelope * + msg_parse(pool_t pool, const char *message) + { ++ const struct message_parser_settings parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | ++ MESSAGE_HEADER_PARSER_FLAG_DROP_CR, ++ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, ++ }; + struct message_parser_ctx *parser; + struct message_part_envelope *envlp = NULL; + struct istream *input; +@@ -126,10 +131,7 @@ msg_parse(pool_t pool, const char *messa + int ret; + + input = i_stream_create_from_data(message, strlen(message)); +- parser = message_parser_init(pool, input, +- MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | +- MESSAGE_HEADER_PARSER_FLAG_DROP_CR, +- MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); ++ parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + i_assert(block.part->parent == NULL); + message_part_envelope_parse_from_header(pool, &envlp, block.hdr); +diff -up dovecot-2.3.8/src/lib-mail/istream-attachment-extractor.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/istream-attachment-extractor.c +--- dovecot-2.3.8/src/lib-mail/istream-attachment-extractor.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/istream-attachment-extractor.c 2020-08-07 12:29:39.975827486 +0200 +@@ -696,6 +696,10 @@ i_stream_create_attachment_extractor(str + struct istream_attachment_settings *set, + void *context) + { ++ const struct message_parser_settings parser_set = { ++ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | ++ MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES, ++ }; + struct attachment_istream *astream; + + i_assert(set->min_size > 0); +@@ -722,9 +726,7 @@ i_stream_create_attachment_extractor(str + astream->istream.istream.seekable = FALSE; + + astream->pool = pool_alloconly_create("istream attachment", 1024); +- astream->parser = message_parser_init(astream->pool, input, 0, +- MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | +- MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); ++ astream->parser = message_parser_init(astream->pool, input, &parser_set); + return i_stream_create(&astream->istream, input, + i_stream_get_fd(input), 0); + } +diff -up dovecot-2.3.8/src/lib-mail/istream-binary-converter.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/istream-binary-converter.c +--- dovecot-2.3.8/src/lib-mail/istream-binary-converter.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/istream-binary-converter.c 2020-08-07 12:29:39.975827486 +0200 +@@ -286,6 +286,10 @@ static void i_stream_binary_converter_cl + + struct istream *i_stream_create_binary_converter(struct istream *input) + { ++ const struct message_parser_settings parser_set = { ++ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | ++ MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES, ++ }; + struct binary_converter_istream *bstream; + + bstream = i_new(struct binary_converter_istream, 1); +@@ -299,9 +303,7 @@ struct istream *i_stream_create_binary_c + bstream->istream.istream.seekable = FALSE; + + bstream->pool = pool_alloconly_create("istream binary converter", 128); +- bstream->parser = message_parser_init(bstream->pool, input, 0, +- MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | +- MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); ++ bstream->parser = message_parser_init(bstream->pool, input, &parser_set); + return i_stream_create(&bstream->istream, input, + i_stream_get_fd(input), 0); + } +diff -up dovecot-2.3.8/src/lib-mail/Makefile.am.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/Makefile.am +--- dovecot-2.3.8/src/lib-mail/Makefile.am.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/Makefile.am 2020-08-07 12:29:39.972827528 +0200 +@@ -28,6 +28,7 @@ libmail_la_SOURCES = \ + message-header-parser.c \ + message-id.c \ + message-parser.c \ ++ message-parser-from-parts.c \ + message-part.c \ + message-part-data.c \ + message-part-serialize.c \ +@@ -42,7 +43,8 @@ libmail_la_SOURCES = \ + rfc822-parser.c + + noinst_HEADERS = \ +- html-entities.h ++ html-entities.h \ ++ message-parser-private.h + + headers = \ + istream-attachment-connector.h \ +diff -up dovecot-2.3.8/src/lib-mail/message-parser.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/message-parser.c +--- dovecot-2.3.8/src/lib-mail/message-parser.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-parser.c 2020-08-07 12:29:39.978827444 +0200 +@@ -1,54 +1,12 @@ + /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + + #include "lib.h" +-#include "buffer.h" ++#include "array.h" + #include "str.h" + #include "istream.h" + #include "rfc822-parser.h" + #include "rfc2231-parser.h" +-#include "message-parser.h" +- +-/* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix. +- We'll add a bit more just in case. */ +-#define BOUNDARY_END_MAX_LEN (70 + 2 + 2 + 10) +- +-struct message_boundary { +- struct message_boundary *next; +- +- struct message_part *part; +- const char *boundary; +- size_t len; +- +- bool epilogue_found:1; +-}; +- +-struct message_parser_ctx { +- pool_t parser_pool, part_pool; +- struct istream *input; +- struct message_part *parts, *part; +- const char *broken_reason; +- +- enum message_header_parser_flags hdr_flags; +- enum message_parser_flags flags; +- +- const char *last_boundary; +- struct message_boundary *boundaries; +- +- size_t skip; +- char last_chr; +- unsigned int want_count; +- +- struct message_header_parser_ctx *hdr_parser_ctx; +- unsigned int prev_hdr_newline_size; +- +- int (*parse_next_block)(struct message_parser_ctx *ctx, +- struct message_block *block_r); +- +- bool part_seen_content_type:1; +- bool multipart:1; +- bool preparsed:1; +- bool eof:1; +-}; ++#include "message-parser-private.h" + + message_part_header_callback_t *null_message_part_header_callback = NULL; + +@@ -58,14 +16,10 @@ static int parse_next_body_to_boundary(s + struct message_block *block_r); + static int parse_next_body_to_eof(struct message_parser_ctx *ctx, + struct message_block *block_r); +-static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, +- struct message_block *block_r); +-static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, +- struct message_block *block_r); + + static struct message_boundary * + boundary_find(struct message_boundary *boundaries, +- const unsigned char *data, size_t len) ++ const unsigned char *data, size_t len, bool trailing_dashes) + { + struct message_boundary *best = NULL; + +@@ -77,8 +31,18 @@ boundary_find(struct message_boundary *b + while (boundaries != NULL) { + if (boundaries->len <= len && + memcmp(boundaries->boundary, data, boundaries->len) == 0 && +- (best == NULL || best->len < boundaries->len)) ++ (best == NULL || best->len < boundaries->len)) { + best = boundaries; ++ /* If we see "foo--", it could either mean that there ++ is a boundary named "foo" that ends now or there's ++ a boundary "foo--" which continues. */ ++ if (best->len == len || ++ (best->len == len-2 && trailing_dashes)) { ++ /* This is exactly the wanted boundary. There ++ can't be a better one. */ ++ break; ++ } ++ } + + boundaries = boundaries->next; + } +@@ -122,8 +86,8 @@ static void parse_body_add_block(struct + ctx->part->body_size.virtual_size += block->size + missing_cr_count; + } + +-static int message_parser_read_more(struct message_parser_ctx *ctx, +- struct message_block *block_r, bool *full_r) ++int message_parser_read_more(struct message_parser_ctx *ctx, ++ struct message_block *block_r, bool *full_r) + { + int ret; + +@@ -168,19 +132,18 @@ static int message_parser_read_more(stru + return 1; + } + +-static struct message_part * +-message_part_append(pool_t pool, struct message_part *parent) ++static void ++message_part_append(struct message_parser_ctx *ctx) + { +- struct message_part *p, *part, **list; ++ struct message_part *parent = ctx->part; ++ struct message_part *part; + + i_assert(parent != NULL); + i_assert((parent->flags & (MESSAGE_PART_FLAG_MULTIPART | + MESSAGE_PART_FLAG_MESSAGE_RFC822)) != 0); + +- part = p_new(pool, struct message_part, 1); ++ part = p_new(ctx->part_pool, struct message_part, 1); + part->parent = parent; +- for (p = parent; p != NULL; p = p->parent) +- p->children_count++; + + /* set child position */ + part->physical_pos = +@@ -188,33 +151,78 @@ message_part_append(pool_t pool, struct + parent->body_size.physical_size + + parent->header_size.physical_size; + +- list = &part->parent->children; +- while (*list != NULL) +- list = &(*list)->next; ++ /* add to parent's linked list */ ++ *ctx->next_part = part; ++ /* update the parent's end-of-linked-list pointer */ ++ struct message_part **next_part = &part->next; ++ array_push_back(&ctx->next_part_stack, &next_part); ++ /* This part is now the new parent for the next message_part_append() ++ call. Its linked list begins with the children pointer. */ ++ ctx->next_part = &part->children; ++ ++ ctx->part = part; ++ ctx->nested_parts_count++; ++ ctx->total_parts_count++; ++ i_assert(ctx->nested_parts_count < ctx->max_nested_mime_parts); ++ i_assert(ctx->total_parts_count <= ctx->max_total_mime_parts); ++} ++ ++static void message_part_finish(struct message_parser_ctx *ctx) ++{ ++ struct message_part **const *parent_next_partp; ++ ++ i_assert(ctx->nested_parts_count > 0); ++ ctx->nested_parts_count--; ++ ++ parent_next_partp = array_back(&ctx->next_part_stack); ++ array_pop_back(&ctx->next_part_stack); ++ ctx->next_part = *parent_next_partp; ++ ++ message_size_add(&ctx->part->parent->body_size, &ctx->part->body_size); ++ message_size_add(&ctx->part->parent->body_size, &ctx->part->header_size); ++ ctx->part->parent->children_count += 1 + ctx->part->children_count; ++ ctx->part = ctx->part->parent; ++} ++ ++static void message_boundary_free(struct message_boundary *b) ++{ ++ i_free(b->boundary); ++ i_free(b); ++} ++ ++static void ++boundary_remove_until(struct message_parser_ctx *ctx, ++ struct message_boundary *boundary) ++{ ++ while (ctx->boundaries != boundary) { ++ struct message_boundary *cur = ctx->boundaries; + +- *list = part; +- return part; ++ i_assert(cur != NULL); ++ ctx->boundaries = cur->next; ++ message_boundary_free(cur); ++ ++ } ++ ctx->boundaries = boundary; + } + + static void parse_next_body_multipart_init(struct message_parser_ctx *ctx) + { + struct message_boundary *b; + +- b = p_new(ctx->parser_pool, struct message_boundary, 1); ++ b = i_new(struct message_boundary, 1); + b->part = ctx->part; + b->boundary = ctx->last_boundary; ++ ctx->last_boundary = NULL; + b->len = strlen(b->boundary); + + b->next = ctx->boundaries; + ctx->boundaries = b; +- +- ctx->last_boundary = NULL; + } + + static int parse_next_body_message_rfc822_init(struct message_parser_ctx *ctx, + struct message_block *block_r) + { +- ctx->part = message_part_append(ctx->part_pool, ctx->part); ++ message_part_append(ctx); + return parse_next_header_init(ctx, block_r); + } + +@@ -239,19 +247,38 @@ boundary_line_find(struct message_parser + return -1; + } + ++ if (ctx->total_parts_count >= ctx->max_total_mime_parts) { ++ /* can't add any more MIME parts. just stop trying to find ++ more boundaries. */ ++ return -1; ++ } ++ + /* need to find the end of line */ +- if (memchr(data + 2, '\n', size - 2) == NULL && +- size < BOUNDARY_END_MAX_LEN && ++ data += 2; ++ size -= 2; ++ const unsigned char *lf_pos = memchr(data, '\n', size); ++ if (lf_pos == NULL && ++ size+2 < BOUNDARY_END_MAX_LEN && + !ctx->input->eof && !full) { + /* no LF found */ + ctx->want_count = BOUNDARY_END_MAX_LEN; + return 0; + } ++ size_t find_size = size; ++ bool trailing_dashes = FALSE; + +- data += 2; +- size -= 2; ++ if (lf_pos != NULL) { ++ find_size = lf_pos - data; ++ if (find_size > 0 && data[find_size-1] == '\r') ++ find_size--; ++ if (find_size > 2 && data[find_size-1] == '-' && ++ data[find_size-2] == '-') ++ trailing_dashes = TRUE; ++ } else if (find_size > BOUNDARY_END_MAX_LEN) ++ find_size = BOUNDARY_END_MAX_LEN; + +- *boundary_r = boundary_find(ctx->boundaries, data, size); ++ *boundary_r = boundary_find(ctx->boundaries, data, find_size, ++ trailing_dashes); + if (*boundary_r == NULL) + return -1; + +@@ -264,7 +291,7 @@ boundary_line_find(struct message_parser + static int parse_next_mime_header_init(struct message_parser_ctx *ctx, + struct message_block *block_r) + { +- ctx->part = message_part_append(ctx->part_pool, ctx->part); ++ message_part_append(ctx); + ctx->part->flags |= MESSAGE_PART_FLAG_IS_MIME; + + return parse_next_header_init(ctx, block_r); +@@ -313,26 +340,25 @@ static int parse_part_finish(struct mess + struct message_boundary *boundary, + struct message_block *block_r, bool first_line) + { +- struct message_part *part; + size_t line_size; ++ size_t boundary_len = boundary->len; ++ bool boundary_epilogue_found = boundary->epilogue_found; + + i_assert(ctx->last_boundary == NULL); + + /* get back to parent MIME part, summing the child MIME part sizes + into parent's body sizes */ +- for (part = ctx->part; part != boundary->part; part = part->parent) { +- message_size_add(&part->parent->body_size, &part->body_size); +- message_size_add(&part->parent->body_size, &part->header_size); ++ while (ctx->part != boundary->part) { ++ message_part_finish(ctx); ++ i_assert(ctx->part != NULL); + } +- i_assert(part != NULL); +- ctx->part = part; + + if (boundary->epilogue_found) { + /* this boundary isn't needed anymore */ +- ctx->boundaries = boundary->next; ++ boundary_remove_until(ctx, boundary->next); + } else { + /* forget about the boundaries we possibly skipped */ +- ctx->boundaries = boundary; ++ boundary_remove_until(ctx, boundary); + } + + /* the boundary itself should already be in buffer. add that. */ +@@ -349,7 +375,7 @@ static int parse_part_finish(struct mess + i_assert(block_r->data[0] == '\n'); + line_size = 1; + } +- line_size += 2 + boundary->len + (boundary->epilogue_found ? 2 : 0); ++ line_size += 2 + boundary_len + (boundary_epilogue_found ? 2 : 0); + i_assert(block_r->size >= ctx->skip + line_size); + block_r->size = line_size; + parse_body_add_block(ctx, block_r); +@@ -510,8 +536,10 @@ static void parse_content_type(struct me + rfc2231_parse(&parser, &results); + for (; *results != NULL; results += 2) { + if (strcasecmp(results[0], "boundary") == 0) { ++ /* truncate excessively long boundaries */ ++ i_free(ctx->last_boundary); + ctx->last_boundary = +- p_strdup(ctx->parser_pool, results[1]); ++ i_strndup(results[1], BOUNDARY_STRING_MAX_LEN); + break; + } + } +@@ -533,6 +561,11 @@ static bool block_is_at_eoh(const struct + return FALSE; + } + ++static bool parse_too_many_nested_mime_parts(struct message_parser_ctx *ctx) ++{ ++ return ctx->nested_parts_count+1 >= ctx->max_nested_mime_parts; ++} ++ + #define MUTEX_FLAGS \ + (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_MULTIPART) + +@@ -557,8 +590,12 @@ static int parse_next_header(struct mess + "\n--boundary" belongs to us or to a previous boundary. + this is a problem if the boundary prefixes are identical, + because MIME requires only the prefix to match. */ +- parse_next_body_multipart_init(ctx); +- ctx->multipart = TRUE; ++ if (!parse_too_many_nested_mime_parts(ctx)) { ++ parse_next_body_multipart_init(ctx); ++ ctx->multipart = TRUE; ++ } else { ++ part->flags &= ~MESSAGE_PART_FLAG_MULTIPART; ++ } + } + + /* before parsing the header see if we can find a --boundary from here. +@@ -634,7 +671,7 @@ static int parse_next_header(struct mess + i_assert(!ctx->multipart); + part->flags = 0; + } +- ctx->last_boundary = NULL; ++ i_free(ctx->last_boundary); + + if (!ctx->part_seen_content_type || + (part->flags & MESSAGE_PART_FLAG_IS_MIME) == 0) { +@@ -662,12 +699,16 @@ static int parse_next_header(struct mess + i_assert(ctx->last_boundary == NULL); + ctx->multipart = FALSE; + ctx->parse_next_block = parse_next_body_to_boundary; +- } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) ++ } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 && ++ !parse_too_many_nested_mime_parts(ctx)) { + ctx->parse_next_block = parse_next_body_message_rfc822_init; +- else if (ctx->boundaries != NULL) +- ctx->parse_next_block = parse_next_body_to_boundary; +- else +- ctx->parse_next_block = parse_next_body_to_eof; ++ } else { ++ part->flags &= ~MESSAGE_PART_FLAG_MESSAGE_RFC822; ++ if (ctx->boundaries != NULL) ++ ctx->parse_next_block = parse_next_body_to_boundary; ++ else ++ ctx->parse_next_block = parse_next_body_to_eof; ++ } + + ctx->want_count = 1; + +@@ -692,358 +733,21 @@ static int parse_next_header_init(struct + return parse_next_header(ctx, block_r); + } + +-static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED, +- struct message_block *block_r ATTR_UNUSED) +-{ +- return -1; +-} +- +-static void preparsed_skip_to_next(struct message_parser_ctx *ctx) +-{ +- ctx->parse_next_block = preparsed_parse_next_header_init; +- while (ctx->part != NULL) { +- if (ctx->part->next != NULL) { +- ctx->part = ctx->part->next; +- break; +- } +- +- /* parse epilogue of multipart parent if requested */ +- if (ctx->part->parent != NULL && +- (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && +- (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) { +- /* check for presence of epilogue */ +- uoff_t part_end = ctx->part->physical_pos + +- ctx->part->header_size.physical_size + +- ctx->part->body_size.physical_size; +- uoff_t parent_end = ctx->part->parent->physical_pos + +- ctx->part->parent->header_size.physical_size + +- ctx->part->parent->body_size.physical_size; +- +- if (parent_end > part_end) { +- ctx->parse_next_block = preparsed_parse_epilogue_init; +- break; +- } +- } +- ctx->part = ctx->part->parent; +- } +- if (ctx->part == NULL) +- ctx->parse_next_block = preparsed_parse_eof; +-} +- +-static int preparsed_parse_body_finish(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- i_stream_skip(ctx->input, ctx->skip); +- ctx->skip = 0; +- +- preparsed_skip_to_next(ctx); +- return ctx->parse_next_block(ctx, block_r); +-} +- +-static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- i_stream_skip(ctx->input, ctx->skip); +- ctx->skip = 0; +- +- ctx->parse_next_block = preparsed_parse_next_header_init; +- ctx->part = ctx->part->children; +- return ctx->parse_next_block(ctx, block_r); +-} +- +-static int preparsed_parse_body_more(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- uoff_t end_offset = ctx->part->physical_pos + +- ctx->part->header_size.physical_size + +- ctx->part->body_size.physical_size; +- bool full; +- int ret; +- +- if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) +- return ret; +- +- if (ctx->input->v_offset + block_r->size >= end_offset) { +- block_r->size = end_offset - ctx->input->v_offset; +- ctx->parse_next_block = preparsed_parse_body_finish; +- } +- ctx->skip = block_r->size; +- return 1; +-} +- +-static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- uoff_t boundary_min_start, end_offset; +- const unsigned char *cur; +- bool full; +- int ret; +- +- i_assert(ctx->part->children != NULL); +- end_offset = ctx->part->children->physical_pos; +- +- if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) +- return ret; +- +- if (ctx->input->v_offset + block_r->size >= end_offset) { +- /* we've got the full prologue: clip off the initial boundary */ +- block_r->size = end_offset - ctx->input->v_offset; +- cur = block_r->data + block_r->size - 1; +- +- /* [\r]\n--boundary[\r]\n */ +- if (block_r->size < 5 || *cur != '\n') { +- ctx->broken_reason = "Prologue boundary end not at expected position"; +- return -1; +- } +- +- cur--; +- if (*cur == '\r') cur--; +- +- /* find newline just before boundary */ +- for (; cur >= block_r->data; cur--) { +- if (*cur == '\n') break; +- } +- +- if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') { +- ctx->broken_reason = "Prologue boundary beginning not at expected position"; +- return -1; +- } +- +- if (cur != block_r->data && cur[-1] == '\r') cur--; +- +- /* clip boundary */ +- block_r->size = cur - block_r->data; +- +- ctx->parse_next_block = preparsed_parse_prologue_finish; +- ctx->skip = block_r->size; +- return 1; +- } +- +- /* retain enough data in the stream buffer to contain initial boundary */ +- if (end_offset > BOUNDARY_END_MAX_LEN) +- boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN; +- else +- boundary_min_start = 0; +- +- if (ctx->input->v_offset + block_r->size >= boundary_min_start) { +- if (boundary_min_start <= ctx->input->v_offset) +- return 0; +- block_r->size = boundary_min_start - ctx->input->v_offset; +- } +- ctx->skip = block_r->size; +- return 1; +-} +- +-static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- uoff_t end_offset = ctx->part->physical_pos + +- ctx->part->header_size.physical_size + +- ctx->part->body_size.physical_size; +- bool full; +- int ret; +- +- if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) +- return ret; +- +- if (ctx->input->v_offset + block_r->size >= end_offset) { +- block_r->size = end_offset - ctx->input->v_offset; +- ctx->parse_next_block = preparsed_parse_body_finish; +- } +- ctx->skip = block_r->size; +- return 1; +-} +- +-static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- uoff_t end_offset = ctx->part->physical_pos + +- ctx->part->header_size.physical_size + +- ctx->part->body_size.physical_size; +- const unsigned char *data, *cur; +- size_t size; +- bool full; +- int ret; +- +- if (end_offset - ctx->input->v_offset < 7) { +- ctx->broken_reason = "Epilogue position is wrong"; +- return -1; +- } +- +- if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) +- return ret; +- +- /* [\r]\n--boundary--[\r]\n */ +- if (block_r->size < 7) { +- ctx->want_count = 7; +- return 0; +- } +- +- data = block_r->data; +- size = block_r->size; +- cur = data; +- +- if (*cur == '\r') cur++; +- +- if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') { +- ctx->broken_reason = "Epilogue boundary start not at expected position"; +- return -1; +- } +- +- /* find the end of the line */ +- cur += 3; +- if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) { +- if (end_offset < ctx->input->v_offset + size) { +- ctx->broken_reason = "Epilogue boundary end not at expected position"; +- return -1; +- } else if (ctx->input->v_offset + size < end_offset && +- size < BOUNDARY_END_MAX_LEN && +- !ctx->input->eof && !full) { +- ctx->want_count = BOUNDARY_END_MAX_LEN; +- return 0; +- } +- } +- +- block_r->size = 0; +- ctx->parse_next_block = preparsed_parse_epilogue_more; +- ctx->skip = cur - data + 1; +- return 0; +-} +- +-static int preparsed_parse_body_init(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- uoff_t offset = ctx->part->physical_pos + +- ctx->part->header_size.physical_size; +- +- if (offset < ctx->input->v_offset) { +- /* header was actually larger than the cached size suggested */ +- ctx->broken_reason = "Header larger than its cached size"; +- return -1; +- } +- i_stream_skip(ctx->input, offset - ctx->input->v_offset); +- +- /* multipart messages may begin with --boundary--, which makes them +- not have any children. */ +- if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || +- ctx->part->children == NULL) +- ctx->parse_next_block = preparsed_parse_body_more; +- else +- ctx->parse_next_block = preparsed_parse_prologue_more; +- return ctx->parse_next_block(ctx, block_r); +-} +- +-static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- uoff_t offset = ctx->part->physical_pos + +- ctx->part->header_size.physical_size + +- ctx->part->body_size.physical_size; +- +- ctx->part = ctx->part->parent; +- +- if (offset < ctx->input->v_offset) { +- /* last child was actually larger than the cached size +- suggested */ +- ctx->broken_reason = "Part larger than its cached size"; +- return -1; +- } +- i_stream_skip(ctx->input, offset - ctx->input->v_offset); +- +- ctx->parse_next_block = preparsed_parse_epilogue_boundary; +- return ctx->parse_next_block(ctx, block_r); +-} +- +-static int preparsed_parse_finish_header(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- if (ctx->part->children != NULL) { +- if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && +- (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) +- ctx->parse_next_block = preparsed_parse_body_init; +- else { +- ctx->parse_next_block = preparsed_parse_next_header_init; +- ctx->part = ctx->part->children; +- } +- } else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) { +- ctx->parse_next_block = preparsed_parse_body_init; +- } else { +- preparsed_skip_to_next(ctx); +- } +- return ctx->parse_next_block(ctx, block_r); +-} +- +-static int preparsed_parse_next_header(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- struct message_header_line *hdr; +- int ret; +- +- ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr); +- if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) { +- ctx->want_count = i_stream_get_data_size(ctx->input) + 1; +- return ret; +- } +- +- if (hdr != NULL) { +- block_r->hdr = hdr; +- block_r->size = 0; +- return 1; +- } +- message_parse_header_deinit(&ctx->hdr_parser_ctx); +- +- ctx->parse_next_block = preparsed_parse_finish_header; +- +- /* return empty block as end of headers */ +- block_r->hdr = NULL; +- block_r->size = 0; +- +- i_assert(ctx->skip == 0); +- if (ctx->input->v_offset != ctx->part->physical_pos + +- ctx->part->header_size.physical_size) { +- ctx->broken_reason = "Cached header size mismatch"; +- return -1; +- } +- return 1; +-} +- +-static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, +- struct message_block *block_r) +-{ +- struct istream *hdr_input; +- +- i_assert(ctx->hdr_parser_ctx == NULL); +- +- i_assert(ctx->part->physical_pos >= ctx->input->v_offset); +- i_stream_skip(ctx->input, ctx->part->physical_pos - +- ctx->input->v_offset); +- +- /* the header may become truncated by --boundaries. limit the header +- stream's size to what it's supposed to be to avoid duplicating (and +- keeping in sync!) all the same complicated logic as in +- parse_next_header(). */ +- hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size); +- ctx->hdr_parser_ctx = +- message_parse_header_init(hdr_input, NULL, ctx->hdr_flags); +- i_stream_unref(&hdr_input); +- +- ctx->parse_next_block = preparsed_parse_next_header; +- return preparsed_parse_next_header(ctx, block_r); +-} +- +-static struct message_parser_ctx * ++struct message_parser_ctx * + message_parser_init_int(struct istream *input, +- enum message_header_parser_flags hdr_flags, +- enum message_parser_flags flags) ++ const struct message_parser_settings *set) + { + struct message_parser_ctx *ctx; +- pool_t pool; + +- pool = pool_alloconly_create("Message Parser", 1024); +- ctx = p_new(pool, struct message_parser_ctx, 1); +- ctx->parser_pool = pool; +- ctx->hdr_flags = hdr_flags; +- ctx->flags = flags; ++ ctx = i_new(struct message_parser_ctx, 1); ++ ctx->hdr_flags = set->hdr_flags; ++ ctx->flags = set->flags; ++ ctx->max_nested_mime_parts = set->max_nested_mime_parts != 0 ? ++ set->max_nested_mime_parts : ++ MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS; ++ ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ? ++ set->max_total_mime_parts : ++ MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS; + ctx->input = input; + i_stream_ref(input); + return ctx; +@@ -1051,32 +755,17 @@ message_parser_init_int(struct istream * + + struct message_parser_ctx * + message_parser_init(pool_t part_pool, struct istream *input, +- enum message_header_parser_flags hdr_flags, +- enum message_parser_flags flags) ++ const struct message_parser_settings *set) + { + struct message_parser_ctx *ctx; + +- ctx = message_parser_init_int(input, hdr_flags, flags); ++ ctx = message_parser_init_int(input, set); + ctx->part_pool = part_pool; + ctx->parts = ctx->part = p_new(part_pool, struct message_part, 1); ++ ctx->next_part = &ctx->part->children; + ctx->parse_next_block = parse_next_header_init; +- return ctx; +-} +- +-struct message_parser_ctx * +-message_parser_init_from_parts(struct message_part *parts, +- struct istream *input, +- enum message_header_parser_flags hdr_flags, +- enum message_parser_flags flags) +-{ +- struct message_parser_ctx *ctx; +- +- i_assert(parts != NULL); +- +- ctx = message_parser_init_int(input, hdr_flags, flags); +- ctx->preparsed = TRUE; +- ctx->parts = ctx->part = parts; +- ctx->parse_next_block = preparsed_parse_next_header_init; ++ ctx->total_parts_count = 1; ++ i_array_init(&ctx->next_part_stack, 4); + return ctx; + } + +@@ -1103,8 +792,15 @@ int message_parser_deinit_from_parts(str + + if (ctx->hdr_parser_ctx != NULL) + message_parse_header_deinit(&ctx->hdr_parser_ctx); ++ boundary_remove_until(ctx, NULL); ++ /* caller might have stopped the parsing early */ ++ i_assert(ctx->nested_parts_count == 0 || ++ i_stream_have_bytes_left(ctx->input)); ++ + i_stream_unref(&ctx->input); +- pool_unref(&ctx->parser_pool); ++ array_free(&ctx->next_part_stack); ++ i_free(ctx->last_boundary); ++ i_free(ctx); + i_assert(ret < 0 || *parts_r != NULL); + return ret; + } +@@ -1136,13 +832,8 @@ int message_parser_parse_next_block(stru + i_assert(ctx->input->eof || ctx->input->closed || + ctx->input->stream_errno != 0 || + ctx->broken_reason != NULL); +- while (ctx->part->parent != NULL) { +- message_size_add(&ctx->part->parent->body_size, +- &ctx->part->body_size); +- message_size_add(&ctx->part->parent->body_size, +- &ctx->part->header_size); +- ctx->part = ctx->part->parent; +- } ++ while (ctx->part->parent != NULL) ++ message_part_finish(ctx); + } + + if (block_r->size == 0) { +diff -up dovecot-2.3.8/src/lib-mail/message-parser-from-parts.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/message-parser-from-parts.c +--- dovecot-2.3.8/src/lib-mail/message-parser-from-parts.c.CVE_2020_12100 2020-08-07 12:29:39.972827528 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-parser-from-parts.c 2020-08-07 12:29:39.975827486 +0200 +@@ -0,0 +1,365 @@ ++/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ ++ ++#include "lib.h" ++#include "istream.h" ++#include "message-parser-private.h" ++ ++static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, ++ struct message_block *block_r); ++static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, ++ struct message_block *block_r); ++ ++static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED, ++ struct message_block *block_r ATTR_UNUSED) ++{ ++ return -1; ++} ++ ++static void preparsed_skip_to_next(struct message_parser_ctx *ctx) ++{ ++ ctx->parse_next_block = preparsed_parse_next_header_init; ++ while (ctx->part != NULL) { ++ if (ctx->part->next != NULL) { ++ ctx->part = ctx->part->next; ++ break; ++ } ++ ++ /* parse epilogue of multipart parent if requested */ ++ if (ctx->part->parent != NULL && ++ (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && ++ (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) { ++ /* check for presence of epilogue */ ++ uoff_t part_end = ctx->part->physical_pos + ++ ctx->part->header_size.physical_size + ++ ctx->part->body_size.physical_size; ++ uoff_t parent_end = ctx->part->parent->physical_pos + ++ ctx->part->parent->header_size.physical_size + ++ ctx->part->parent->body_size.physical_size; ++ ++ if (parent_end > part_end) { ++ ctx->parse_next_block = preparsed_parse_epilogue_init; ++ break; ++ } ++ } ++ ctx->part = ctx->part->parent; ++ } ++ if (ctx->part == NULL) ++ ctx->parse_next_block = preparsed_parse_eof; ++} ++ ++static int preparsed_parse_body_finish(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ i_stream_skip(ctx->input, ctx->skip); ++ ctx->skip = 0; ++ ++ preparsed_skip_to_next(ctx); ++ return ctx->parse_next_block(ctx, block_r); ++} ++ ++static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ i_stream_skip(ctx->input, ctx->skip); ++ ctx->skip = 0; ++ ++ ctx->parse_next_block = preparsed_parse_next_header_init; ++ ctx->part = ctx->part->children; ++ return ctx->parse_next_block(ctx, block_r); ++} ++ ++static int preparsed_parse_body_more(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ uoff_t end_offset = ctx->part->physical_pos + ++ ctx->part->header_size.physical_size + ++ ctx->part->body_size.physical_size; ++ bool full; ++ int ret; ++ ++ if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) ++ return ret; ++ ++ if (ctx->input->v_offset + block_r->size >= end_offset) { ++ block_r->size = end_offset - ctx->input->v_offset; ++ ctx->parse_next_block = preparsed_parse_body_finish; ++ } ++ ctx->skip = block_r->size; ++ return 1; ++} ++ ++static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ uoff_t boundary_min_start, end_offset; ++ const unsigned char *cur; ++ bool full; ++ int ret; ++ ++ i_assert(ctx->part->children != NULL); ++ end_offset = ctx->part->children->physical_pos; ++ ++ if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) ++ return ret; ++ ++ if (ctx->input->v_offset + block_r->size >= end_offset) { ++ /* we've got the full prologue: clip off the initial boundary */ ++ block_r->size = end_offset - ctx->input->v_offset; ++ cur = block_r->data + block_r->size - 1; ++ ++ /* [\r]\n--boundary[\r]\n */ ++ if (block_r->size < 5 || *cur != '\n') { ++ ctx->broken_reason = "Prologue boundary end not at expected position"; ++ return -1; ++ } ++ ++ cur--; ++ if (*cur == '\r') cur--; ++ ++ /* find newline just before boundary */ ++ for (; cur >= block_r->data; cur--) { ++ if (*cur == '\n') break; ++ } ++ ++ if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') { ++ ctx->broken_reason = "Prologue boundary beginning not at expected position"; ++ return -1; ++ } ++ ++ if (cur != block_r->data && cur[-1] == '\r') cur--; ++ ++ /* clip boundary */ ++ block_r->size = cur - block_r->data; ++ ++ ctx->parse_next_block = preparsed_parse_prologue_finish; ++ ctx->skip = block_r->size; ++ return 1; ++ } ++ ++ /* retain enough data in the stream buffer to contain initial boundary */ ++ if (end_offset > BOUNDARY_END_MAX_LEN) ++ boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN; ++ else ++ boundary_min_start = 0; ++ ++ if (ctx->input->v_offset + block_r->size >= boundary_min_start) { ++ if (boundary_min_start <= ctx->input->v_offset) ++ return 0; ++ block_r->size = boundary_min_start - ctx->input->v_offset; ++ } ++ ctx->skip = block_r->size; ++ return 1; ++} ++ ++static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ uoff_t end_offset = ctx->part->physical_pos + ++ ctx->part->header_size.physical_size + ++ ctx->part->body_size.physical_size; ++ bool full; ++ int ret; ++ ++ if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) ++ return ret; ++ ++ if (ctx->input->v_offset + block_r->size >= end_offset) { ++ block_r->size = end_offset - ctx->input->v_offset; ++ ctx->parse_next_block = preparsed_parse_body_finish; ++ } ++ ctx->skip = block_r->size; ++ return 1; ++} ++ ++static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ uoff_t end_offset = ctx->part->physical_pos + ++ ctx->part->header_size.physical_size + ++ ctx->part->body_size.physical_size; ++ const unsigned char *data, *cur; ++ size_t size; ++ bool full; ++ int ret; ++ ++ if (end_offset - ctx->input->v_offset < 7) { ++ ctx->broken_reason = "Epilogue position is wrong"; ++ return -1; ++ } ++ ++ if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) ++ return ret; ++ ++ /* [\r]\n--boundary--[\r]\n */ ++ if (block_r->size < 7) { ++ ctx->want_count = 7; ++ return 0; ++ } ++ ++ data = block_r->data; ++ size = block_r->size; ++ cur = data; ++ ++ if (*cur == '\r') cur++; ++ ++ if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') { ++ ctx->broken_reason = "Epilogue boundary start not at expected position"; ++ return -1; ++ } ++ ++ /* find the end of the line */ ++ cur += 3; ++ if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) { ++ if (end_offset < ctx->input->v_offset + size) { ++ ctx->broken_reason = "Epilogue boundary end not at expected position"; ++ return -1; ++ } else if (ctx->input->v_offset + size < end_offset && ++ size < BOUNDARY_END_MAX_LEN && ++ !ctx->input->eof && !full) { ++ ctx->want_count = BOUNDARY_END_MAX_LEN; ++ return 0; ++ } ++ } ++ ++ block_r->size = 0; ++ ctx->parse_next_block = preparsed_parse_epilogue_more; ++ ctx->skip = cur - data + 1; ++ return 0; ++} ++ ++static int preparsed_parse_body_init(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ uoff_t offset = ctx->part->physical_pos + ++ ctx->part->header_size.physical_size; ++ ++ if (offset < ctx->input->v_offset) { ++ /* header was actually larger than the cached size suggested */ ++ ctx->broken_reason = "Header larger than its cached size"; ++ return -1; ++ } ++ i_stream_skip(ctx->input, offset - ctx->input->v_offset); ++ ++ /* multipart messages may begin with --boundary--, which makes them ++ not have any children. */ ++ if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || ++ ctx->part->children == NULL) ++ ctx->parse_next_block = preparsed_parse_body_more; ++ else ++ ctx->parse_next_block = preparsed_parse_prologue_more; ++ return ctx->parse_next_block(ctx, block_r); ++} ++ ++static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ uoff_t offset = ctx->part->physical_pos + ++ ctx->part->header_size.physical_size + ++ ctx->part->body_size.physical_size; ++ ++ ctx->part = ctx->part->parent; ++ ++ if (offset < ctx->input->v_offset) { ++ /* last child was actually larger than the cached size ++ suggested */ ++ ctx->broken_reason = "Part larger than its cached size"; ++ return -1; ++ } ++ i_stream_skip(ctx->input, offset - ctx->input->v_offset); ++ ++ ctx->parse_next_block = preparsed_parse_epilogue_boundary; ++ return ctx->parse_next_block(ctx, block_r); ++} ++ ++static int preparsed_parse_finish_header(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ if (ctx->part->children != NULL) { ++ if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && ++ (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) ++ ctx->parse_next_block = preparsed_parse_body_init; ++ else { ++ ctx->parse_next_block = preparsed_parse_next_header_init; ++ ctx->part = ctx->part->children; ++ } ++ } else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) { ++ ctx->parse_next_block = preparsed_parse_body_init; ++ } else { ++ preparsed_skip_to_next(ctx); ++ } ++ return ctx->parse_next_block(ctx, block_r); ++} ++ ++static int preparsed_parse_next_header(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ struct message_header_line *hdr; ++ int ret; ++ ++ ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr); ++ if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) { ++ ctx->want_count = i_stream_get_data_size(ctx->input) + 1; ++ return ret; ++ } ++ ++ if (hdr != NULL) { ++ block_r->hdr = hdr; ++ block_r->size = 0; ++ return 1; ++ } ++ message_parse_header_deinit(&ctx->hdr_parser_ctx); ++ ++ ctx->parse_next_block = preparsed_parse_finish_header; ++ ++ /* return empty block as end of headers */ ++ block_r->hdr = NULL; ++ block_r->size = 0; ++ ++ i_assert(ctx->skip == 0); ++ if (ctx->input->v_offset != ctx->part->physical_pos + ++ ctx->part->header_size.physical_size) { ++ ctx->broken_reason = "Cached header size mismatch"; ++ return -1; ++ } ++ return 1; ++} ++ ++static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, ++ struct message_block *block_r) ++{ ++ struct istream *hdr_input; ++ ++ i_assert(ctx->hdr_parser_ctx == NULL); ++ ++ i_assert(ctx->part->physical_pos >= ctx->input->v_offset); ++ i_stream_skip(ctx->input, ctx->part->physical_pos - ++ ctx->input->v_offset); ++ ++ /* the header may become truncated by --boundaries. limit the header ++ stream's size to what it's supposed to be to avoid duplicating (and ++ keeping in sync!) all the same complicated logic as in ++ parse_next_header(). */ ++ hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size); ++ ctx->hdr_parser_ctx = ++ message_parse_header_init(hdr_input, NULL, ctx->hdr_flags); ++ i_stream_unref(&hdr_input); ++ ++ ctx->parse_next_block = preparsed_parse_next_header; ++ return preparsed_parse_next_header(ctx, block_r); ++} ++ ++struct message_parser_ctx * ++message_parser_init_from_parts(struct message_part *parts, ++ struct istream *input, ++ const struct message_parser_settings *set) ++{ ++ struct message_parser_ctx *ctx; ++ ++ i_assert(parts != NULL); ++ ++ ctx = message_parser_init_int(input, set); ++ ctx->preparsed = TRUE; ++ ctx->parts = ctx->part = parts; ++ ctx->parse_next_block = preparsed_parse_next_header_init; ++ return ctx; ++} +diff -up dovecot-2.3.8/src/lib-mail/message-parser.h.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/message-parser.h +--- dovecot-2.3.8/src/lib-mail/message-parser.h.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-parser.h 2020-08-07 12:29:39.978827444 +0200 +@@ -17,6 +17,21 @@ enum message_parser_flags { + MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES = 0x08 + }; + ++#define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100 ++#define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000 ++ ++struct message_parser_settings { ++ enum message_header_parser_flags hdr_flags; ++ enum message_parser_flags flags; ++ ++ /* Maximum nested MIME parts. ++ 0 = MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS. */ ++ unsigned int max_nested_mime_parts; ++ /* Maximum MIME parts in total. ++ 0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */ ++ unsigned int max_total_mime_parts; ++}; ++ + struct message_parser_ctx; + + struct message_block { +@@ -45,8 +60,7 @@ extern message_part_header_callback_t *n + are allocated from. */ + struct message_parser_ctx * + message_parser_init(pool_t part_pool, struct istream *input, +- enum message_header_parser_flags hdr_flags, +- enum message_parser_flags flags); ++ const struct message_parser_settings *set); + /* Deinitialize message parser. The ctx must NOT have been created by + message_parser_init_from_parts(). */ + void message_parser_deinit(struct message_parser_ctx **ctx, +@@ -55,8 +69,7 @@ void message_parser_deinit(struct messag + struct message_parser_ctx * + message_parser_init_from_parts(struct message_part *parts, + struct istream *input, +- enum message_header_parser_flags hdr_flags, +- enum message_parser_flags flags); ++ const struct message_parser_settings *set); + /* Same as message_parser_deinit(), but return an error message describing + why the preparsed parts didn't match the message. This can also safely be + called even when preparsed parts weren't used - it'll always just return +diff -up dovecot-2.3.8/src/lib-mail/message-parser-private.h.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/message-parser-private.h +--- dovecot-2.3.8/src/lib-mail/message-parser-private.h.CVE_2020_12100 2020-08-07 12:29:39.972827528 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-parser-private.h 2020-08-07 12:29:39.978827444 +0200 +@@ -0,0 +1,62 @@ ++#ifndef MESSAGE_PARSER_PRIVATE_H ++#define MESSAGE_PARSER_PRIVATE_H ++ ++#include "message-parser.h" ++ ++/* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix. ++ We'll add a bit more just in case. */ ++#define BOUNDARY_STRING_MAX_LEN (70 + 10) ++#define BOUNDARY_END_MAX_LEN (BOUNDARY_STRING_MAX_LEN + 2 + 2) ++ ++struct message_boundary { ++ struct message_boundary *next; ++ ++ struct message_part *part; ++ char *boundary; ++ size_t len; ++ ++ bool epilogue_found:1; ++}; ++ ++struct message_parser_ctx { ++ pool_t part_pool; ++ struct istream *input; ++ struct message_part *parts, *part; ++ const char *broken_reason; ++ unsigned int nested_parts_count; ++ unsigned int total_parts_count; ++ ++ enum message_header_parser_flags hdr_flags; ++ enum message_parser_flags flags; ++ unsigned int max_nested_mime_parts; ++ unsigned int max_total_mime_parts; ++ ++ char *last_boundary; ++ struct message_boundary *boundaries; ++ ++ struct message_part **next_part; ++ ARRAY(struct message_part **) next_part_stack; ++ ++ size_t skip; ++ char last_chr; ++ unsigned int want_count; ++ ++ struct message_header_parser_ctx *hdr_parser_ctx; ++ unsigned int prev_hdr_newline_size; ++ ++ int (*parse_next_block)(struct message_parser_ctx *ctx, ++ struct message_block *block_r); ++ ++ bool part_seen_content_type:1; ++ bool multipart:1; ++ bool preparsed:1; ++ bool eof:1; ++}; ++ ++struct message_parser_ctx * ++message_parser_init_int(struct istream *input, ++ const struct message_parser_settings *set); ++int message_parser_read_more(struct message_parser_ctx *ctx, ++ struct message_block *block_r, bool *full_r); ++ ++#endif +diff -up dovecot-2.3.8/src/lib-mail/message-search.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/message-search.c +--- dovecot-2.3.8/src/lib-mail/message-search.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-search.c 2020-08-07 12:29:39.976827472 +0200 +@@ -196,8 +196,9 @@ message_search_msg_real(struct message_s + struct istream *input, struct message_part *parts, + const char **error_r) + { +- const enum message_header_parser_flags hdr_parser_flags = +- MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE; ++ const struct message_parser_settings parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, ++ }; + struct message_parser_ctx *parser_ctx; + struct message_block raw_block; + struct message_part *new_parts; +@@ -207,10 +208,10 @@ message_search_msg_real(struct message_s + + if (parts != NULL) { + parser_ctx = message_parser_init_from_parts(parts, +- input, hdr_parser_flags, 0); ++ input, &parser_set); + } else { + parser_ctx = message_parser_init(pool_datastack_create(), +- input, hdr_parser_flags, 0); ++ input, &parser_set); + } + + while ((ret = message_parser_parse_next_block(parser_ctx, +diff -up dovecot-2.3.8/src/lib-mail/message-snippet.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/message-snippet.c +--- dovecot-2.3.8/src/lib-mail/message-snippet.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-snippet.c 2020-08-07 12:30:01.710525318 +0200 +@@ -99,6 +99,7 @@ int message_snippet_generate(struct istr + unsigned int max_snippet_chars, + string_t *snippet) + { ++ const struct message_parser_settings parser_set = { .flags = 0 }; + struct message_parser_ctx *parser; + struct message_part *parts; + struct message_decoder_context *decoder; +@@ -112,7 +113,7 @@ int message_snippet_generate(struct istr + ctx.snippet = snippet; + ctx.chars_left = max_snippet_chars; + +- parser = message_parser_init(pool_datastack_create(), input, 0, 0); ++ parser = message_parser_init(pool_datastack_create(), input, &parser_set); + decoder = message_decoder_init(NULL, 0); + while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) { + if (!message_decoder_decode_next_block(decoder, &raw_block, &block)) +diff -up dovecot-2.3.8/src/lib-mail/test-message-decoder.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/test-message-decoder.c +--- dovecot-2.3.8/src/lib-mail/test-message-decoder.c.CVE_2020_12100 2020-08-07 12:29:39.970827555 +0200 ++++ dovecot-2.3.8/src/lib-mail/test-message-decoder.c 2020-08-07 12:29:39.976827472 +0200 +@@ -105,6 +105,7 @@ static void test_message_decoder_multipa + "\n" + "?garbage\n" + "--foo--\n"; ++ const struct message_parser_settings parser_set = { .flags = 0, }; + struct message_parser_ctx *parser; + struct message_decoder_context *decoder; + struct message_part *parts; +@@ -116,7 +117,8 @@ static void test_message_decoder_multipa + test_begin("message decoder multipart"); + + istream = test_istream_create(test_message_input); +- parser = message_parser_init(pool_datastack_create(), istream, 0, 0); ++ parser = message_parser_init(pool_datastack_create(), istream, ++ &parser_set); + decoder = message_decoder_init(NULL, 0); + + test_istream_set_allow_eof(istream, FALSE); +diff -up dovecot-2.3.8/src/lib-mail/test-message-parser.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/test-message-parser.c +--- dovecot-2.3.8/src/lib-mail/test-message-parser.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/test-message-parser.c 2020-08-07 12:29:39.979827431 +0200 +@@ -39,6 +39,8 @@ static const char test_msg[] = + "\n"; + #define TEST_MSG_LEN (sizeof(test_msg)-1) + ++static const struct message_parser_settings set_empty = { .flags = 0 }; ++ + static bool msg_parts_cmp(struct message_part *p1, struct message_part *p2) + { + while (p1 != NULL || p2 != NULL) { +@@ -59,6 +61,7 @@ static bool msg_parts_cmp(struct message + p1->body_size.physical_size != p2->body_size.physical_size || + p1->body_size.virtual_size != p2->body_size.virtual_size || + p1->body_size.lines != p2->body_size.lines || ++ p1->children_count != p2->children_count || + p1->flags != p2->flags) + return FALSE; + +@@ -70,6 +73,9 @@ static bool msg_parts_cmp(struct message + + static void test_parsed_parts(struct istream *input, struct message_part *parts) + { ++ const struct message_parser_settings parser_set = { ++ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, ++ }; + struct message_parser_ctx *parser; + struct message_block block; + struct message_part *parts2; +@@ -81,8 +87,7 @@ static void test_parsed_parts(struct ist + if (i_stream_get_size(input, TRUE, &input_size) < 0) + i_unreached(); + +- parser = message_parser_init_from_parts(parts, input, 0, +- MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); ++ parser = message_parser_init_from_parts(parts, input, &parser_set); + for (i = 1; i <= input_size*2+1; i++) { + test_istream_set_size(input, i/2); + if (i > TEST_MSG_LEN*2) +@@ -112,9 +117,11 @@ static void test_message_parser_small_bl + output = t_str_new(128); + + /* full parsing */ +- parser = message_parser_init(pool, input, 0, +- MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | +- MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); ++ const struct message_parser_settings full_parser_set = { ++ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | ++ MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES, ++ }; ++ parser = message_parser_init(pool, input, &full_parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + if (block.hdr != NULL) + message_header_line_write(output, block.hdr); +@@ -130,7 +137,7 @@ static void test_message_parser_small_bl + i_stream_seek(input, 0); + test_istream_set_allow_eof(input, FALSE); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + for (i = 1; i <= TEST_MSG_LEN*2+1; i++) { + test_istream_set_size(input, i/2); + if (i > TEST_MSG_LEN*2) +@@ -148,8 +155,11 @@ static void test_message_parser_small_bl + test_istream_set_allow_eof(input, FALSE); + + end_of_headers_idx = (strstr(test_msg, "\n-----") - test_msg); +- parser = message_parser_init_from_parts(parts, input, 0, +- MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); ++ const struct message_parser_settings preparsed_parser_set = { ++ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, ++ }; ++ parser = message_parser_init_from_parts(parts, input, ++ &preparsed_parser_set); + for (i = 1; i <= TEST_MSG_LEN*2+1; i++) { + test_istream_set_size(input, i/2); + if (i > TEST_MSG_LEN*2) +@@ -167,6 +177,36 @@ static void test_message_parser_small_bl + test_end(); + } + ++static void test_message_parser_stop_early(void) ++{ ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts; ++ struct message_block block; ++ unsigned int i; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser in stop early"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(test_msg); ++ ++ test_istream_set_allow_eof(input, FALSE); ++ for (i = 1; i <= TEST_MSG_LEN+1; i++) { ++ i_stream_seek(input, 0); ++ test_istream_set_size(input, i); ++ parser = message_parser_init(pool, input, &set_empty); ++ while ((ret = message_parser_parse_next_block(parser, ++ &block)) > 0) ; ++ test_assert(ret == 0); ++ message_parser_deinit(&parser, &parts); ++ } ++ ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + static void test_message_parser_truncated_mime_headers(void) + { + static const char input_msg[] = +@@ -191,12 +231,13 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + test_assert((parts->flags & MESSAGE_PART_FLAG_MULTIPART) != 0); ++ test_assert(parts->children_count == 4); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 48); + test_assert(parts->header_size.virtual_size == 48+2); +@@ -220,6 +261,7 @@ static const char input_msg[] = + test_assert(parts->children->next->next->next->header_size.virtual_size == 23); + test_assert(parts->children->next->next->next->header_size.lines == 0); + for (part = parts->children; part != NULL; part = part->next) { ++ test_assert(part->children_count == 0); + test_assert(part->body_size.physical_size == 0); + test_assert(part->body_size.virtual_size == 0); + } +@@ -254,12 +296,13 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(parts->children_count == 2); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 46); + test_assert(parts->header_size.virtual_size == 46+2); +@@ -267,6 +310,7 @@ static const char input_msg[] = + test_assert(parts->body_size.physical_size == 86); + test_assert(parts->body_size.virtual_size == 86+8); + ++ test_assert(parts->children->children_count == 0); + test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->physical_pos == 51); + test_assert(parts->children->header_size.lines == 1); +@@ -276,6 +320,7 @@ static const char input_msg[] = + test_assert(parts->children->body_size.physical_size == 0); + test_assert(parts->children->children == NULL); + ++ test_assert(parts->children->next->children_count == 0); + test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->next->physical_pos == 101); + test_assert(parts->children->next->header_size.lines == 2); +@@ -307,11 +352,12 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + ++ test_assert(parts->children_count == 0); + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->header_size.lines == 1); + test_assert(parts->header_size.physical_size == 45); +@@ -344,11 +390,12 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + ++ test_assert(parts->children_count == 0); + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 46); +@@ -388,11 +435,12 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + ++ test_assert(parts->children_count == 2); + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 45); +@@ -400,6 +448,7 @@ static const char input_msg[] = + test_assert(parts->body_size.lines == 7); + test_assert(parts->body_size.physical_size == 84); + test_assert(parts->body_size.virtual_size == 84+7); ++ test_assert(parts->children->children_count == 1); + test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->physical_pos == 49); + test_assert(parts->children->header_size.lines == 2); +@@ -408,6 +457,7 @@ static const char input_msg[] = + test_assert(parts->children->body_size.lines == 4); + test_assert(parts->children->body_size.physical_size == 35); + test_assert(parts->children->body_size.virtual_size == 35+4); ++ test_assert(parts->children->children->children_count == 0); + test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->children->physical_pos == 98); + test_assert(parts->children->children->header_size.lines == 2); +@@ -446,11 +496,12 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + ++ test_assert(parts->children_count == 2); + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 45); +@@ -458,6 +509,7 @@ static const char input_msg[] = + test_assert(parts->body_size.lines == 7); + test_assert(parts->body_size.physical_size == 86); + test_assert(parts->body_size.virtual_size == 86+7); ++ test_assert(parts->children->children_count == 1); + test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->physical_pos == 50); + test_assert(parts->children->header_size.lines == 2); +@@ -466,6 +518,7 @@ static const char input_msg[] = + test_assert(parts->children->body_size.lines == 4); + test_assert(parts->children->body_size.physical_size == 36); + test_assert(parts->children->body_size.virtual_size == 36+4); ++ test_assert(parts->children->children->children_count == 0); + test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->children->physical_pos == 100); + test_assert(parts->children->children->header_size.lines == 2); +@@ -481,6 +534,51 @@ static const char input_msg[] = + test_end(); + } + ++static void test_message_parser_trailing_dashes(void) ++{ ++static const char input_msg[] = ++"Content-Type: multipart/mixed; boundary=\"a--\"\n" ++"\n" ++"--a--\n" ++"Content-Type: multipart/mixed; boundary=\"a----\"\n" ++"\n" ++"--a----\n" ++"Content-Type: text/plain\n" ++"\n" ++"body\n" ++"--a------\n" ++"Content-Type: text/html\n" ++"\n" ++"body2\n" ++"--a----"; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser trailing dashes"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &set_empty); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ test_assert(parts->children_count == 2); ++ test_assert(parts->children->next == NULL); ++ test_assert(parts->children->children_count == 1); ++ test_assert(parts->children->children->next == NULL); ++ test_assert(parts->children->children->children_count == 0); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + static void test_message_parser_continuing_mime_boundary(void) + { + static const char input_msg[] = +@@ -504,11 +602,12 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + ++ test_assert(parts->children_count == 2); + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 45); +@@ -516,6 +615,7 @@ static const char input_msg[] = + test_assert(parts->body_size.lines == 7); + test_assert(parts->body_size.physical_size == 86); + test_assert(parts->body_size.virtual_size == 86+7); ++ test_assert(parts->children->children_count == 1); + test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->physical_pos == 49); + test_assert(parts->children->header_size.lines == 2); +@@ -524,6 +624,7 @@ static const char input_msg[] = + test_assert(parts->children->body_size.lines == 4); + test_assert(parts->children->body_size.physical_size == 36); + test_assert(parts->children->body_size.virtual_size == 36+4); ++ test_assert(parts->children->children->children_count == 0); + test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->children->physical_pos == 100); + test_assert(parts->children->children->header_size.lines == 2); +@@ -563,12 +664,13 @@ static const char input_msg[] = + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; ++ test_assert(part->children_count == 3); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); +@@ -578,6 +680,7 @@ static const char input_msg[] = + test_assert(part->body_size.virtual_size == 112+9); + + part = parts->children; ++ test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->physical_pos == 49); + test_assert(part->header_size.lines == 1); +@@ -591,6 +694,7 @@ static const char input_msg[] = + we could make it, but it would complicate the message-parser even + more. */ + part = parts->children->next; ++ test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->physical_pos == 117); + test_assert(part->header_size.lines == 1); +@@ -601,6 +705,7 @@ static const char input_msg[] = + test_assert(part->children == NULL); + + part = parts->children->next->next; ++ test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 0); + test_assert(part->header_size.physical_size == 0); +@@ -615,6 +720,80 @@ static const char input_msg[] = + test_end(); + } + ++static void test_message_parser_continuing_mime_boundary_reverse(void) ++{ ++static const char input_msg[] = ++"Content-Type: multipart/mixed; boundary=\"ab\"\n" ++"\n" ++"--ab\n" ++"Content-Type: multipart/mixed; boundary=\"a\"\n" ++"\n" ++"--a\n" ++"Content-Type: text/plain\n" ++"\n" ++"body\n" ++"--ab\n" ++"Content-Type: text/html\n" ++"\n" ++"body2\n"; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser continuing mime boundary reverse"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &set_empty); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ test_assert(parts->children_count == 3); ++ test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(parts->header_size.lines == 2); ++ test_assert(parts->header_size.physical_size == 46); ++ test_assert(parts->header_size.virtual_size == 46+2); ++ test_assert(parts->body_size.lines == 11); ++ test_assert(parts->body_size.physical_size == 121); ++ test_assert(parts->body_size.virtual_size == 121+11); ++ test_assert(parts->children->children_count == 1); ++ test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(parts->children->physical_pos == 51); ++ test_assert(parts->children->header_size.lines == 2); ++ test_assert(parts->children->header_size.physical_size == 45); ++ test_assert(parts->children->header_size.virtual_size == 45+2); ++ test_assert(parts->children->body_size.lines == 3); ++ test_assert(parts->children->body_size.physical_size == 34); ++ test_assert(parts->children->body_size.virtual_size == 34+3); ++ test_assert(parts->children->children->children_count == 0); ++ test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(parts->children->children->physical_pos == 100); ++ test_assert(parts->children->children->header_size.lines == 2); ++ test_assert(parts->children->children->header_size.physical_size == 26); ++ test_assert(parts->children->children->header_size.virtual_size == 26+2); ++ test_assert(parts->children->children->body_size.lines == 0); ++ test_assert(parts->children->children->body_size.physical_size == 4); ++ test_assert(parts->children->children->body_size.virtual_size == 4); ++ test_assert(parts->children->next->children_count == 0); ++ test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(parts->children->next->physical_pos == 136); ++ test_assert(parts->children->next->header_size.lines == 2); ++ test_assert(parts->children->next->header_size.physical_size == 25); ++ test_assert(parts->children->next->header_size.virtual_size == 25+2); ++ test_assert(parts->children->next->body_size.lines == 1); ++ test_assert(parts->children->next->body_size.physical_size == 6); ++ test_assert(parts->children->next->body_size.virtual_size == 6+1); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + static void test_message_parser_no_eoh(void) + { + static const char input_msg[] = "a:b\n"; +@@ -628,7 +807,7 @@ static void test_message_parser_no_eoh(v + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set_empty); + test_assert(message_parser_parse_next_block(parser, &block) > 0 && + block.hdr != NULL && strcmp(block.hdr->name, "a") == 0 && + block.hdr->value_len == 1 && block.hdr->value[0] == 'b'); +@@ -643,19 +822,335 @@ static void test_message_parser_no_eoh(v + test_end(); + } + ++static void test_message_parser_long_mime_boundary(void) ++{ ++ /* Close the boundaries in wrong reverse order. But because all ++ boundaries are actually truncated to the same size (..890) it ++ works the same as if all of them were duplicate boundaries. */ ++static const char input_msg[] = ++"Content-Type: multipart/mixed; boundary=\"1234567890123456789012345678901234567890123456789012345678901234567890123456789012\"\n" ++"\n" ++"--1234567890123456789012345678901234567890123456789012345678901234567890123456789012\n" ++"Content-Type: multipart/mixed; boundary=\"123456789012345678901234567890123456789012345678901234567890123456789012345678901\"\n" ++"\n" ++"--123456789012345678901234567890123456789012345678901234567890123456789012345678901\n" ++"Content-Type: multipart/mixed; boundary=\"12345678901234567890123456789012345678901234567890123456789012345678901234567890\"\n" ++"\n" ++"--12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" ++"Content-Type: text/plain\n" ++"\n" ++"1\n" ++"--1234567890123456789012345678901234567890123456789012345678901234567890123456789012\n" ++"Content-Type: text/plain\n" ++"\n" ++"22\n" ++"--123456789012345678901234567890123456789012345678901234567890123456789012345678901\n" ++"Content-Type: text/plain\n" ++"\n" ++"333\n" ++"--12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" ++"Content-Type: text/plain\n" ++"\n" ++"4444\n"; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts, *part; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser long mime boundary"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &set_empty); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ part = parts; ++ test_assert(part->children_count == 6); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 126); ++ test_assert(part->header_size.virtual_size == 126+2); ++ test_assert(part->body_size.lines == 22); ++ test_assert(part->body_size.physical_size == 871); ++ test_assert(part->body_size.virtual_size == 871+22); ++ ++ part = parts->children; ++ test_assert(part->children_count == 5); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 125); ++ test_assert(part->header_size.virtual_size == 125+2); ++ test_assert(part->body_size.lines == 19); ++ test_assert(part->body_size.physical_size == 661); ++ test_assert(part->body_size.virtual_size == 661+19); ++ ++ part = parts->children->children; ++ test_assert(part->children_count == 4); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 124); ++ test_assert(part->header_size.virtual_size == 124+2); ++ test_assert(part->body_size.lines == 16); ++ test_assert(part->body_size.physical_size == 453); ++ test_assert(part->body_size.virtual_size == 453+16); ++ ++ part = parts->children->children->children; ++ for (unsigned int i = 1; i <= 3; i++, part = part->next) { ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 26); ++ test_assert(part->header_size.virtual_size == 26+2); ++ test_assert(part->body_size.lines == 0); ++ test_assert(part->body_size.physical_size == i); ++ test_assert(part->body_size.virtual_size == i); ++ } ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ ++static void test_message_parser_mime_part_nested_limit(void) ++{ ++static const char input_msg[] = ++"Content-Type: multipart/mixed; boundary=\"1\"\n" ++"\n" ++"--1\n" ++"Content-Type: multipart/mixed; boundary=\"2\"\n" ++"\n" ++"--2\n" ++"Content-Type: text/plain\n" ++"\n" ++"1\n" ++"--2\n" ++"Content-Type: text/plain\n" ++"\n" ++"22\n" ++"--1\n" ++"Content-Type: text/plain\n" ++"\n" ++"333\n"; ++ const struct message_parser_settings parser_set = { ++ .max_nested_mime_parts = 2, ++ }; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts, *part; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser mime part nested limit"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &parser_set); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ part = parts; ++ test_assert(part->children_count == 2); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 45); ++ test_assert(part->header_size.virtual_size == 45+2); ++ test_assert(part->body_size.lines == 15); ++ test_assert(part->body_size.physical_size == 148); ++ test_assert(part->body_size.virtual_size == 148+15); ++ ++ part = parts->children; ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 45); ++ test_assert(part->header_size.virtual_size == 45+2); ++ test_assert(part->body_size.lines == 7); ++ test_assert(part->body_size.physical_size == 64); ++ test_assert(part->body_size.virtual_size == 64+7); ++ ++ part = parts->children->next; ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 26); ++ test_assert(part->header_size.virtual_size == 26+2); ++ test_assert(part->body_size.lines == 1); ++ test_assert(part->body_size.physical_size == 4); ++ test_assert(part->body_size.virtual_size == 4+1); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ ++static void test_message_parser_mime_part_nested_limit_rfc822(void) ++{ ++static const char input_msg[] = ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"1\n"; ++ const struct message_parser_settings parser_set = { ++ .max_nested_mime_parts = 2, ++ }; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts, *part; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser mime part nested limit rfc822"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &parser_set); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ part = parts; ++ test_assert(part->children_count == 1); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 30); ++ test_assert(part->header_size.virtual_size == 30+2); ++ test_assert(part->body_size.lines == 5); ++ test_assert(part->body_size.physical_size == 58); ++ test_assert(part->body_size.virtual_size == 58+5); ++ ++ part = parts->children; ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 30); ++ test_assert(part->header_size.virtual_size == 30+2); ++ test_assert(part->body_size.lines == 3); ++ test_assert(part->body_size.physical_size == 28); ++ test_assert(part->body_size.virtual_size == 28+3); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ ++static void test_message_parser_mime_part_limit(void) ++{ ++static const char input_msg[] = ++"Content-Type: multipart/mixed; boundary=\"1\"\n" ++"\n" ++"--1\n" ++"Content-Type: multipart/mixed; boundary=\"2\"\n" ++"\n" ++"--2\n" ++"Content-Type: text/plain\n" ++"\n" ++"1\n" ++"--2\n" ++"Content-Type: text/plain\n" ++"\n" ++"22\n" ++"--1\n" ++"Content-Type: text/plain\n" ++"\n" ++"333\n"; ++ const struct message_parser_settings parser_set = { ++ .max_total_mime_parts = 4, ++ }; ++ struct message_parser_ctx *parser; ++ struct istream *input; ++ struct message_part *parts, *part; ++ struct message_block block; ++ pool_t pool; ++ int ret; ++ ++ test_begin("message parser mime part limit"); ++ pool = pool_alloconly_create("message parser", 10240); ++ input = test_istream_create(input_msg); ++ ++ parser = message_parser_init(pool, input, &parser_set); ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; ++ test_assert(ret < 0); ++ message_parser_deinit(&parser, &parts); ++ ++ part = parts; ++ test_assert(part->children_count == 3); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 45); ++ test_assert(part->header_size.virtual_size == 45+2); ++ test_assert(part->body_size.lines == 15); ++ test_assert(part->body_size.physical_size == 148); ++ test_assert(part->body_size.virtual_size == 148+15); ++ ++ part = parts->children; ++ test_assert(part->children_count == 2); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 45); ++ test_assert(part->header_size.virtual_size == 45+2); ++ test_assert(part->body_size.lines == 12); ++ test_assert(part->body_size.physical_size == 99); ++ test_assert(part->body_size.virtual_size == 99+12); ++ ++ part = parts->children->children; ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 26); ++ test_assert(part->header_size.virtual_size == 26+2); ++ test_assert(part->body_size.lines == 0); ++ test_assert(part->body_size.physical_size == 1); ++ test_assert(part->body_size.virtual_size == 1); ++ ++ part = parts->children->children->next; ++ test_assert(part->children_count == 0); ++ test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); ++ test_assert(part->header_size.lines == 2); ++ test_assert(part->header_size.physical_size == 26); ++ test_assert(part->header_size.virtual_size == 26+2); ++ test_assert(part->body_size.lines == 5); ++ test_assert(part->body_size.physical_size == 37); ++ test_assert(part->body_size.virtual_size == 37+5); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + int main(void) + { + static void (*const test_functions[])(void) = { + test_message_parser_small_blocks, ++ test_message_parser_stop_early, + test_message_parser_truncated_mime_headers, + test_message_parser_truncated_mime_headers2, + test_message_parser_truncated_mime_headers3, + test_message_parser_empty_multipart, + test_message_parser_duplicate_mime_boundary, + test_message_parser_garbage_suffix_mime_boundary, ++ test_message_parser_trailing_dashes, + test_message_parser_continuing_mime_boundary, + test_message_parser_continuing_truncated_mime_boundary, ++ test_message_parser_continuing_mime_boundary_reverse, ++ test_message_parser_long_mime_boundary, + test_message_parser_no_eoh, ++ test_message_parser_mime_part_nested_limit, ++ test_message_parser_mime_part_nested_limit_rfc822, ++ test_message_parser_mime_part_limit, + NULL + }; + return test_run(test_functions); +diff -up dovecot-2.3.8/src/lib-mail/test-message-part.c.CVE_2020_12100 dovecot-2.3.8/src/lib-mail/test-message-part.c +--- dovecot-2.3.8/src/lib-mail/test-message-part.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/test-message-part.c 2020-08-07 12:29:39.976827472 +0200 +@@ -65,6 +65,7 @@ static const char test_msg[] = + + static void test_message_part_idx(void) + { ++ const struct message_parser_settings set = { .flags = 0 }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part, *prev_part; +@@ -77,7 +78,7 @@ static void test_message_part_idx(void) + pool = pool_alloconly_create("message parser", 10240); + input = i_stream_create_from_data(test_msg, TEST_MSG_LEN); + +- parser = message_parser_init(pool, input, 0, 0); ++ parser = message_parser_init(pool, input, &set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + part_idx = message_part_to_idx(block.part); + test_assert(part_idx >= prev_idx); +diff -up dovecot-2.3.8/src/lib-storage/index/index-mail-headers.c.CVE_2020_12100 dovecot-2.3.8/src/lib-storage/index/index-mail-headers.c +--- dovecot-2.3.8/src/lib-storage/index/index-mail-headers.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-storage/index/index-mail-headers.c 2020-08-07 12:31:09.948576696 +0200 +@@ -16,11 +16,11 @@ + #include "index-storage.h" + #include "index-mail.h" + +-static const enum message_header_parser_flags hdr_parser_flags = +- MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | +- MESSAGE_HEADER_PARSER_FLAG_DROP_CR; +-static const enum message_parser_flags msg_parser_flags = +- MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK; ++static const struct message_parser_settings msg_parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | ++ MESSAGE_HEADER_PARSER_FLAG_DROP_CR, ++ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, ++}; + + static int header_line_cmp(const struct index_mail_line *l1, + const struct index_mail_line *l2) +@@ -397,7 +397,7 @@ index_mail_cache_parse_init(struct mail + mail->data.parser_input = input; + mail->data.parser_ctx = + message_parser_init(mail->mail.data_pool, input, +- hdr_parser_flags, msg_parser_flags); ++ &msg_parser_set); + i_stream_unref(&input); + return input2; + } +@@ -426,14 +426,12 @@ static void index_mail_init_parser(struc + data->parser_input = data->stream; + data->parser_ctx = message_parser_init(mail->mail.data_pool, + data->stream, +- hdr_parser_flags, +- msg_parser_flags); ++ &msg_parser_set); + } else { + data->parser_ctx = + message_parser_init_from_parts(data->parts, + data->stream, +- hdr_parser_flags, +- msg_parser_flags); ++ &msg_parser_set); + } + } + +@@ -466,7 +464,7 @@ int index_mail_parse_headers(struct inde + i_assert(!data->save_bodystructure_body || + data->parser_ctx != NULL); + message_parse_header(data->stream, &data->hdr_size, +- hdr_parser_flags, ++ msg_parser_set.hdr_flags, + index_mail_parse_header_cb, mail); + } + if (index_mail_stream_check_failure(mail) < 0) +@@ -521,7 +519,7 @@ int index_mail_headers_get_envelope(stru + if (mail->data.envelope == NULL && stream != NULL) { + /* we got the headers from cache - parse them to get the + envelope */ +- message_parse_header(stream, NULL, hdr_parser_flags, ++ message_parse_header(stream, NULL, msg_parser_set.hdr_flags, + imap_envelope_parse_callback, mail); + if (stream->stream_errno != 0) { + index_mail_stream_log_failure_for(mail, stream); +diff -up dovecot-2.3.8/src/plugins/fts/fts-build-mail.c.CVE_2020_12100 dovecot-2.3.8/src/plugins/fts/fts-build-mail.c +--- dovecot-2.3.8/src/plugins/fts/fts-build-mail.c.CVE_2020_12100 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/plugins/fts/fts-build-mail.c 2020-08-07 12:29:39.977827458 +0200 +@@ -475,6 +475,9 @@ fts_build_mail_real(struct fts_backend_u + const char **retriable_err_msg_r, + bool *may_need_retry_r) + { ++ const struct message_parser_settings parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, ++ }; + struct fts_mail_build_context ctx; + struct istream *input; + struct message_parser_ctx *parser; +@@ -503,9 +506,7 @@ fts_build_mail_real(struct fts_backend_u + ctx.pending_input = buffer_create_dynamic(default_pool, 128); + + prev_part = NULL; +- parser = message_parser_init(pool_datastack_create(), input, +- MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, +- 0); ++ parser = message_parser_init(pool_datastack_create(), input, &parser_set); + + decoder = message_decoder_init(update_ctx->normalizer, 0); + for (;;) { diff --git a/SOURCES/dovecot-2.3.8-CVE_2020_12100ph.patch b/SOURCES/dovecot-2.3.8-CVE_2020_12100ph.patch new file mode 100644 index 0000000..3a8045d --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12100ph.patch @@ -0,0 +1,56 @@ +diff -up dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/plugins/notify/ext-notify-common.c.CVE_2020_12100ph dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/plugins/notify/ext-notify-common.c +--- dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/plugins/notify/ext-notify-common.c.CVE_2020_12100ph 2019-10-08 10:48:14.000000000 +0200 ++++ dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/plugins/notify/ext-notify-common.c 2020-08-07 16:42:56.515389867 +0200 +@@ -148,6 +148,7 @@ static int cmd_notify_extract_body_text + const char **body_text_r, size_t *body_size_r) + { + const struct sieve_extension *this_ext = renv->oprtn->ext; ++ const struct message_parser_settings parser_set = { .flags = 0 }; + struct ext_notify_message_context *mctx; + struct mail *mail = renv->msgdata->mail; + struct message_parser_ctx *parser; +@@ -181,7 +182,7 @@ static int cmd_notify_extract_body_text + /* Initialize body decoder */ + decoder = message_decoder_init(NULL, 0); + +- parser = message_parser_init(mctx->pool, input, 0, 0); ++ parser = message_parser_init(mctx->pool, input, &parser_set); + is_text = TRUE; + save_body = FALSE; + while ( (ret=message_parser_parse_next_block(parser, &block)) > 0 ) { +diff -up dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/sieve-message.c.CVE_2020_12100ph dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/sieve-message.c +--- dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/sieve-message.c.CVE_2020_12100ph 2019-10-08 10:48:14.000000000 +0200 ++++ dovecot-2.3.8/dovecot-2.3-pigeonhole-0.5.8/src/lib-sieve/sieve-message.c 2020-08-07 16:42:56.516389854 +0200 +@@ -1077,10 +1077,10 @@ static int sieve_message_parts_add_missi + struct sieve_message_context *msgctx = renv->msgctx; + pool_t pool = msgctx->context_pool; + struct mail *mail = sieve_message_get_mail(renv->msgctx); +- enum message_parser_flags mparser_flags = +- MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS; +- enum message_header_parser_flags hparser_flags = +- MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP; ++ struct message_parser_settings parser_set = { ++ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP, ++ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS, ++ }; + ARRAY(struct sieve_message_header) headers; + struct sieve_message_part *body_part, *header_part, *last_part; + struct message_parser_ctx *parser; +@@ -1117,7 +1117,7 @@ static int sieve_message_parts_add_missi + if (iter_all) { + t_array_init(&headers, 64); + hdr_content = t_str_new(512); +- hparser_flags |= MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE; ++ parser_set.hdr_flags |= MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE; + } else { + i_zero(&headers); + } +@@ -1129,7 +1129,7 @@ static int sieve_message_parts_add_missi + //parser = message_parser_init_from_parts(parts, input, + // hparser_flags, mparser_flags); + parser = message_parser_init(pool_datastack_create(), +- input, hparser_flags, mparser_flags); ++ input, &parser_set); + while ( (ret=message_parser_parse_next_block + (parser, &block)) > 0 ) { + struct sieve_message_part **body_part_idx; diff --git a/SOURCES/dovecot-2.3.8-CVE_2020_12100prereq.patch b/SOURCES/dovecot-2.3.8-CVE_2020_12100prereq.patch new file mode 100644 index 0000000..1af8c55 --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12100prereq.patch @@ -0,0 +1,224 @@ +diff -up dovecot-2.3.8/src/lib-mail/message-decoder.c.CVE_2020_12100prereq dovecot-2.3.8/src/lib-mail/message-decoder.c +--- dovecot-2.3.8/src/lib-mail/message-decoder.c.CVE_2020_12100prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/message-decoder.c 2020-08-07 17:48:58.320126698 +0200 +@@ -13,9 +13,6 @@ + #include "message-header-decode.h" + #include "message-decoder.h" + +-/* base64 takes max 4 bytes per character, q-p takes max 3. */ +-#define MAX_ENCODING_BUF_SIZE 3 +- + struct message_decoder_context { + enum message_decoder_flags flags; + normalizer_func_t *normalizer; +@@ -30,7 +27,7 @@ struct message_decoder_context { + size_t translation_size; + + struct qp_decoder *qp; +- buffer_t *encoding_buf; ++ struct base64_decoder base64_decoder; + + char *content_type, *content_charset; + enum message_cte message_cte; +@@ -53,7 +50,7 @@ message_decoder_init(normalizer_func_t * + ctx->normalizer = normalizer; + ctx->buf = buffer_create_dynamic(default_pool, 8192); + ctx->buf2 = buffer_create_dynamic(default_pool, 8192); +- ctx->encoding_buf = buffer_create_dynamic(default_pool, 128); ++ base64_decode_init(&ctx->base64_decoder, &base64_scheme, 0); + return ctx; + } + +@@ -68,7 +65,6 @@ void message_decoder_deinit(struct messa + if (ctx->qp != NULL) + qp_decoder_deinit(&ctx->qp); + +- buffer_free(&ctx->encoding_buf); + buffer_free(&ctx->buf); + buffer_free(&ctx->buf2); + i_free(ctx->charset_trans_charset); +@@ -273,14 +269,9 @@ static bool message_decode_body(struct m + struct message_block *input, + struct message_block *output) + { +- struct base64_decoder b64dec; + const unsigned char *data = NULL; +- size_t pos = 0, size = 0; ++ size_t pos, size = 0; + const char *error; +- int ret; +- +- if (ctx->encoding_buf->used != 0) +- buffer_append(ctx->encoding_buf, input->data, input->size); + + switch (ctx->message_cte) { + case MESSAGE_CTE_UNKNOWN: +@@ -289,12 +280,10 @@ static bool message_decode_body(struct m + + case MESSAGE_CTE_78BIT: + case MESSAGE_CTE_BINARY: +- i_assert(ctx->encoding_buf->used == 0); + data = input->data; +- size = pos = input->size; ++ size = input->size; + break; + case MESSAGE_CTE_QP: { +- i_assert(ctx->encoding_buf->used == 0); + buffer_set_used_size(ctx->buf, 0); + if (ctx->qp == NULL) + ctx->qp = qp_decoder_init(ctx->buf); +@@ -302,45 +291,24 @@ static bool message_decode_body(struct m + &pos, &error); + data = ctx->buf->data; + size = ctx->buf->used; +- /* eat away all input. qp-decoder buffers it internally. */ +- pos = input->size; + break; + } + case MESSAGE_CTE_BASE64: + buffer_set_used_size(ctx->buf, 0); +- base64_decode_init(&b64dec, &base64_scheme, 0); +- if (ctx->encoding_buf->used != 0) { +- ret = base64_decode_more(&b64dec, +- ctx->encoding_buf->data, +- ctx->encoding_buf->used, +- &pos, ctx->buf); +- } else { +- ret = base64_decode_more(&b64dec, +- input->data, input->size, +- &pos, ctx->buf); +- } +- if (ret < 0 || base64_decode_finish(&b64dec) < 0) { +- /* corrupted base64 data, don't bother with +- the rest of it */ +- return FALSE; +- } +- if (ret == 0) { +- /* end of base64 input */ +- pos = input->size; +- buffer_set_used_size(ctx->encoding_buf, 0); ++ if (!base64_decode_is_finished(&ctx->base64_decoder)) { ++ if (base64_decode_more(&ctx->base64_decoder, ++ input->data, input->size, ++ &pos, ctx->buf) <= 0) { ++ /* ignore the rest of the input in this ++ MIME part */ ++ (void)base64_decode_finish(&ctx->base64_decoder); ++ } + } + data = ctx->buf->data; + size = ctx->buf->used; + break; + } + +- if (ctx->encoding_buf->used != 0) +- buffer_delete(ctx->encoding_buf, 0, pos); +- else if (pos != input->size) { +- buffer_append(ctx->encoding_buf, +- input->data + pos, input->size - pos); +- } +- + if (ctx->binary_input) { + output->data = data; + output->size = size; +@@ -402,10 +370,11 @@ void message_decoder_decode_reset(struct + { + const char *error; + ++ base64_decode_reset(&ctx->base64_decoder); ++ + if (ctx->qp != NULL) + (void)qp_decoder_finish(ctx->qp, &error); + i_free_and_null(ctx->content_type); + i_free_and_null(ctx->content_charset); + ctx->message_cte = MESSAGE_CTE_78BIT; +- buffer_set_used_size(ctx->encoding_buf, 0); + } +diff -up dovecot-2.3.8/src/lib-mail/test-message-decoder.c.CVE_2020_12100prereq dovecot-2.3.8/src/lib-mail/test-message-decoder.c +--- dovecot-2.3.8/src/lib-mail/test-message-decoder.c.CVE_2020_12100prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/lib-mail/test-message-decoder.c 2020-08-07 17:50:04.612248484 +0200 +@@ -1,7 +1,8 @@ + /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + + #include "lib.h" +-#include "buffer.h" ++#include "str.h" ++#include "istream.h" + #include "charset-utf8.h" + #include "message-parser.h" + #include "message-header-decode.h" +@@ -82,6 +83,66 @@ static void test_message_decoder(void) + test_end(); + } + ++static void test_message_decoder_multipart(void) ++{ ++ static const char test_message_input[] = ++ "Content-Type: multipart/mixed; boundary=foo\n" ++ "\n" ++ "--foo\n" ++ "Content-Transfer-Encoding: quoted-printable\n" ++ "Content-Type: text/plain; charset=utf-8\n" ++ "\n" ++ "p=C3=A4iv=C3=A4=C3=A4\n" ++ "\n" ++ "--foo\n" ++ "Content-Transfer-Encoding: base64\n" ++ "Content-Type: text/plain; charset=utf-8\n" ++ "\n" ++ "ecO2dMOkIHZhYW4uCg== ignored\n" ++ "--foo\n" ++ "Content-Transfer-Encoding: base64\n" ++ "Content-Type: text/plain; charset=utf-8\n" ++ "\n" ++ "?garbage\n" ++ "--foo--\n"; ++ struct message_parser_ctx *parser; ++ struct message_decoder_context *decoder; ++ struct message_part *parts; ++ struct message_block input, output; ++ struct istream *istream; ++ string_t *str_out = t_str_new(20); ++ int ret; ++ ++ test_begin("message decoder multipart"); ++ ++ istream = test_istream_create(test_message_input); ++ parser = message_parser_init(pool_datastack_create(), istream, 0, 0); ++ decoder = message_decoder_init(NULL, 0); ++ ++ test_istream_set_allow_eof(istream, FALSE); ++ for (size_t i = 0; i < sizeof(test_message_input); i++) { ++ if (i == sizeof(test_message_input)-1) ++ test_istream_set_allow_eof(istream, TRUE); ++ test_istream_set_size(istream, i); ++ while ((ret = message_parser_parse_next_block(parser, &input)) > 0) { ++ if (message_decoder_decode_next_block(decoder, &input, &output) && ++ output.hdr == NULL && output.size > 0) ++ str_append_data(str_out, output.data, output.size); ++ } ++ if (i == sizeof(test_message_input)-1) ++ test_assert(ret == -1); ++ else ++ test_assert(ret == 0); ++ } ++ /* NOTE: qp-decoder decoder changes \n into \r\n */ ++ test_assert_strcmp(str_c(str_out), "p\xC3\xA4iv\xC3\xA4\xC3\xA4\r\ny\xC3\xB6t\xC3\xA4 vaan.\n"); ++ ++ message_decoder_deinit(&decoder); ++ message_parser_deinit(&parser, &parts); ++ i_stream_unref(&istream); ++ test_end(); ++} ++ + static void test_message_decoder_current_content_type(void) + { + struct message_decoder_context *ctx; +@@ -149,6 +210,7 @@ int main(void) + { + static void (*const test_functions[])(void) = { + test_message_decoder, ++ test_message_decoder_multipart, + test_message_decoder_current_content_type, + NULL + }; diff --git a/SOURCES/dovecot-2.3.8-CVE_2020_12673.patch b/SOURCES/dovecot-2.3.8-CVE_2020_12673.patch new file mode 100644 index 0000000..a48760b --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12673.patch @@ -0,0 +1,34 @@ +From 1c6405d3026e5ceae3d214d63945bba85251af4c Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +Date: Mon, 18 May 2020 12:33:39 +0300 +Subject: [PATCH 2/3] lib-ntlm: Check buffer length on responses + +Add missing check for buffer length. + +If this is not checked, it is possible to send message which +causes read past buffer bug. + +Broken in c7480644202e5451fbed448508ea29a25cffc99c +--- + src/lib-ntlm/ntlm-message.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/lib-ntlm/ntlm-message.c b/src/lib-ntlm/ntlm-message.c +index 160b9f918c..a29413b47e 100644 +--- a/src/lib-ntlm/ntlm-message.c ++++ b/src/lib-ntlm/ntlm-message.c +@@ -184,6 +184,11 @@ static bool ntlmssp_check_buffer(const struct ntlmssp_buffer *buffer, + if (length == 0 && space == 0) + return TRUE; + ++ if (length > data_size) { ++ *error = "buffer length out of bounds"; ++ return FALSE; ++ } ++ + if (offset >= data_size) { + *error = "buffer offset out of bounds"; + return FALSE; +-- +2.11.0 + diff --git a/SOURCES/dovecot-2.3.8-CVE_2020_12674.patch b/SOURCES/dovecot-2.3.8-CVE_2020_12674.patch new file mode 100644 index 0000000..c786011 --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12674.patch @@ -0,0 +1,234 @@ +From bd9d2fe7da833f0e4705a8280efc56930371806b Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +Date: Wed, 6 May 2020 13:40:36 +0300 +Subject: [PATCH 1/3] auth: mech-rpa - Fail on zero len buffer + +--- + src/auth/mech-rpa.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/auth/mech-rpa.c b/src/auth/mech-rpa.c +index 08298ebdd6..2de8705b4f 100644 +--- a/src/auth/mech-rpa.c ++++ b/src/auth/mech-rpa.c +@@ -224,7 +224,7 @@ rpa_read_buffer(pool_t pool, const unsigned char **data, + return 0; + + len = *p++; +- if (p + len > end) ++ if (p + len > end || len == 0) + return 0; + + *buffer = p_malloc(pool, len); +-- +2.11.0 + +From 98c39fd633adf9b1d11a7bad58ef0784a25042e6 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +Date: Mon, 18 May 2020 13:08:45 +0300 +Subject: [PATCH 3/3] auth: test-mech - Add tests for RPA and NTLM bug + +--- + src/auth/test-mech.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 66 insertions(+) + +diff -up dovecot-2.3.8/src/auth/test-mech.c.CVE_2020_12674prereq dovecot-2.3.8/src/auth/test-mech.c +--- dovecot-2.3.8/src/auth/test-mech.c.CVE_2020_12674prereq 2020-08-07 20:46:56.095295825 +0200 ++++ dovecot-2.3.8/src/auth/test-mech.c 2020-08-07 20:47:08.742124304 +0200 +@@ -0,0 +1,196 @@ ++/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ ++ ++#include "lib.h" ++#include "auth.h" ++#include "str.h" ++#include "auth-common.h" ++#include "auth-request.h" ++#include "auth-request-handler-private.h" ++#include "auth-settings.h" ++#include "otp.h" ++#include "mech-otp-skey-common.h" ++#include "settings-parser.h" ++#include "password-scheme.h" ++#include "test-common.h" ++#include "test-auth.h" ++#include "auth-token.h" ++ ++#include ++#include ++ ++#define UCHAR_LEN(str) (const unsigned char *)(str), sizeof(str)-1 ++ ++extern const struct mech_module mech_oauthbearer; ++extern const struct mech_module mech_otp; ++extern const struct mech_module mech_ntlm; ++extern const struct mech_module mech_rpa; ++ ++static struct auth_settings set; ++static struct mechanisms_register *mech_reg; ++ ++struct test_case { ++ const struct mech_module *mech; ++ const unsigned char *in; ++ size_t len; ++ const char *username; ++ const char *expect_error; ++ bool success; ++ bool set_username_before_test; ++ bool set_cert_username; ++}; ++ ++static void ++verify_plain_continue_mock_callback(struct auth_request *request, ++ verify_plain_callback_t *callback) ++{ ++ request->passdb_success = TRUE; ++ callback(PASSDB_RESULT_OK, request); ++} ++ ++static void ++request_handler_reply_mock_callback(struct auth_request *request, ++ enum auth_client_result result, ++ const void *auth_reply ATTR_UNUSED, ++ size_t reply_size ATTR_UNUSED) ++{ ++ request->failed = result != AUTH_CLIENT_RESULT_SUCCESS; ++ ++ if (request->passdb_result == PASSDB_RESULT_OK) ++ request->failed = FALSE; ++ else if (request->mech == &mech_otp) { ++ if (null_strcmp(request->user, "otp_phase_2") == 0) ++ request->failed = FALSE; ++ } else if (request->mech == &mech_oauthbearer) { ++ } ++}; ++ ++static void ++request_handler_reply_continue_mock_callback(struct auth_request *request, ++ const void *reply, ++ size_t reply_size) ++{ ++ request->context = p_strndup(request->pool, reply, reply_size); ++} ++ ++static void ++auth_client_request_mock_callback(const char *reply ATTR_UNUSED, ++ struct auth_client_connection *conn ATTR_UNUSED) ++{ ++} ++ ++static void test_mechs_init(void) ++{ ++ const char *const services[] = {NULL}; ++ process_start_time = time(NULL); ++ ++ /* Copy default settings */ ++ set = *(struct auth_settings *) auth_setting_parser_info.defaults; ++ global_auth_settings = &set; ++ global_auth_settings->base_dir = "."; ++ memset((&set)->username_chars_map, 1, sizeof((&set)->username_chars_map)); ++ set.username_format = ""; ++ ++ t_array_init(&set.passdbs, 2); ++ struct auth_passdb_settings *mock_set = t_new(struct auth_passdb_settings, 1); ++ *mock_set = mock_passdb_set; ++ array_push_back(&set.passdbs, &mock_set); ++ mock_set = t_new(struct auth_passdb_settings, 1); ++ *mock_set = mock_passdb_set; ++ mock_set->master = TRUE; ++ array_push_back(&set.passdbs, &mock_set); ++ t_array_init(&set.userdbs, 1); ++ ++ /* Disable stats */ ++ set.stats = FALSE; ++ ++ /* For tests of digest-md5. */ ++ set.realms_arr = t_strsplit_spaces("example.com ", " "); ++ /* For tests of mech-anonymous. */ ++ set.anonymous_username = "anonuser"; ++ ++ mech_init(global_auth_settings); ++ mech_reg = mech_register_init(global_auth_settings); ++ passdbs_init(); ++ userdbs_init(); ++ passdb_mock_mod_init(); ++ password_schemes_init(); ++ ++ auths_preinit(&set, pool_datastack_create(), mech_reg, services); ++ auths_init(); ++ auth_token_init(); ++} ++ ++ ++static void test_rpa(void) ++{ ++ test_mechs_init(); ++ static struct auth_request_handler handler = { ++ .callback = auth_client_request_mock_callback, ++ .reply_callback = request_handler_reply_mock_callback, ++ .reply_continue_callback = request_handler_reply_continue_mock_callback, ++ .verify_plain_continue_callback = verify_plain_continue_mock_callback, ++ }; ++ ++ const struct mech_module *mech = &mech_rpa; ++ test_begin("test rpa"); ++ struct auth_request *req = mech->auth_new(); ++ global_auth_settings->realms_arr = t_strsplit("example.com", " "); ++ req->set = global_auth_settings; ++ req->service = "login"; ++ req->handler = &handler; ++ req->mech_event = event_create(NULL); ++ req->event = event_create(NULL); ++ req->mech = mech; ++ req->state = AUTH_REQUEST_STATE_MECH_CONTINUE; ++ auth_request_state_count[AUTH_REQUEST_STATE_MECH_CONTINUE] = 1; ++ mech->auth_initial(req, UCHAR_LEN("\x60\x11\x06\x09\x60\x86\x48\x01\x86\xf8\x73\x01\x01\x01\x00\x04\x00\x00\x01")); ++ mech->auth_continue(req, UCHAR_LEN("\x60\x11\x06\x09\x60\x86\x48\x01\x86\xf8\x73\x01\x01\x00\x03A@A\x00")); ++ test_assert(req->failed == TRUE); ++ test_assert(req->passdb_success == FALSE); ++ event_unref(&req->mech_event); ++ event_unref(&req->event); ++ mech->auth_free(req); ++ test_end(); ++} ++ ++static void test_ntlm(void) ++{ ++ static struct auth_request_handler handler = { ++ .callback = auth_client_request_mock_callback, ++ .reply_callback = request_handler_reply_mock_callback, ++ .reply_continue_callback = request_handler_reply_continue_mock_callback, ++ .verify_plain_continue_callback = verify_plain_continue_mock_callback, ++ }; ++ ++ const struct mech_module *mech = &mech_ntlm; ++ test_begin("test ntlm"); ++ struct auth_request *req = mech->auth_new(); ++ global_auth_settings->realms_arr = t_strsplit("example.com", " "); ++ req->set = global_auth_settings; ++ req->service = "login"; ++ req->handler = &handler; ++ req->mech_event = event_create(NULL); ++ req->event = event_create(NULL); ++ req->mech = mech; ++ req->state = AUTH_REQUEST_STATE_MECH_CONTINUE; ++ auth_request_state_count[AUTH_REQUEST_STATE_MECH_CONTINUE] = 1; ++ mech->auth_initial(req, UCHAR_LEN("NTLMSSP\x00\x01\x00\x00\x00\x00\x02\x00\x00""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); ++ mech->auth_continue(req, UCHAR_LEN("NTLMSSP\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00""AA\x00\x00\x41\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00""orange""\x00")); ++ test_assert(req->failed == TRUE); ++ test_assert(req->passdb_success == FALSE); ++ event_unref(&req->mech_event); ++ event_unref(&req->event); ++ mech->auth_free(req); ++ test_end(); ++} ++ ++int main(void) ++{ ++ static void (*const test_functions[])(void) = { ++ test_rpa, ++ test_ntlm, ++ NULL ++ }; ++ ++ return test_run(test_functions); ++} diff --git a/SOURCES/dovecot-2.3.8-CVE_2020_12674prereq.patch b/SOURCES/dovecot-2.3.8-CVE_2020_12674prereq.patch new file mode 100644 index 0000000..931ce9a --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12674prereq.patch @@ -0,0 +1,345 @@ +diff -up dovecot-2.3.8/src/auth/Makefile.am.CVE_2020_12674prereq dovecot-2.3.8/src/auth/Makefile.am +--- dovecot-2.3.8/src/auth/Makefile.am.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/Makefile.am 2020-08-07 20:46:56.095295825 +0200 +@@ -38,6 +38,7 @@ AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib-oauth2 \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-lua \ ++ -I$(top_srcdir)/src/lib-dcrypt \ + -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \ + -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ + -DPKG_RUNDIR=\""$(rundir)"\" \ +@@ -248,7 +249,8 @@ libstats_auth_la_SOURCES = auth-stats.c + test_programs = \ + test-libpassword \ + test-auth-cache \ +- test-auth ++ test-auth \ ++ test-mech + + noinst_PROGRAMS = $(test_programs) + +@@ -288,6 +290,13 @@ test_auth_SOURCES = \ + test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) + test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) + ++test_mech_SOURCES = \ ++ test-mock.c \ ++ test-mech.c ++ ++test_mech_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) ++test_mech_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) ++ + check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ +diff -up dovecot-2.3.8/src/auth/passdb.h.CVE_2020_12674prereq dovecot-2.3.8/src/auth/passdb.h +--- dovecot-2.3.8/src/auth/passdb.h.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/passdb.h 2020-08-07 20:35:16.295684287 +0200 +@@ -24,6 +24,8 @@ enum passdb_result { + + typedef void verify_plain_callback_t(enum passdb_result result, + struct auth_request *request); ++typedef void verify_plain_continue_callback_t(struct auth_request *request, ++ verify_plain_callback_t *callback); + typedef void lookup_credentials_callback_t(enum passdb_result result, + const unsigned char *credentials, + size_t size, +diff -up dovecot-2.3.8/src/auth/auth-request-handler-private.h.CVE_2020_12674prereq dovecot-2.3.8/src/auth/auth-request-handler-private.h +--- dovecot-2.3.8/src/auth/auth-request-handler-private.h.CVE_2020_12674prereq 2020-08-07 20:35:16.295684287 +0200 ++++ dovecot-2.3.8/src/auth/auth-request-handler-private.h 2020-08-07 20:35:16.295684287 +0200 +@@ -0,0 +1,27 @@ ++#ifndef AUTH_REQUEST_HANDLER_PRIVATE_H ++#define AUTH_REQUEST_HANDLER_PRIVATE_H ++ ++struct auth_request; ++struct auth_client_connection; ++ ++struct auth_request_handler { ++ int refcount; ++ pool_t pool; ++ HASH_TABLE(void *, struct auth_request *) requests; ++ ++ unsigned int connect_uid, client_pid; ++ ++ auth_client_request_callback_t *callback; ++ struct auth_client_connection *conn; ++ ++ auth_master_request_callback_t *master_callback; ++ auth_request_handler_reply_callback_t *reply_callback; ++ auth_request_handler_reply_continue_callback_t *reply_continue_callback; ++ verify_plain_continue_callback_t *verify_plain_continue_callback; ++ ++ bool destroyed:1; ++ bool token_auth:1; ++}; ++ ++ ++#endif +diff -up dovecot-2.3.8/src/auth/auth-request-handler.h.CVE_2020_12674prereq dovecot-2.3.8/src/auth/auth-request-handler.h +--- dovecot-2.3.8/src/auth/auth-request-handler.h.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/auth-request-handler.h 2020-08-07 20:35:16.295684287 +0200 +@@ -17,6 +17,17 @@ auth_client_request_callback_t(const cha + typedef void + auth_master_request_callback_t(const char *reply, struct auth_master_connection *conn); + ++typedef void ++auth_request_handler_reply_callback_t(struct auth_request *request, ++ enum auth_client_result result, ++ const void *auth_reply, ++ size_t reply_size); ++typedef void ++auth_request_handler_reply_continue_callback_t(struct auth_request *request, ++ const void *reply, ++ size_t reply_size); ++ ++ + struct auth_request_handler * + auth_request_handler_create(bool token_auth, auth_client_request_callback_t *callback, + struct auth_client_connection *conn, +diff -up dovecot-2.3.8/src/auth/test-mock.c.CVE_2020_12674prereq dovecot-2.3.8/src/auth/test-mock.c +--- dovecot-2.3.8/src/auth/test-mock.c.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/test-mock.c 2020-08-07 20:35:16.296684273 +0200 +@@ -28,14 +28,22 @@ static void passdb_mock_verify_plain(str + callback(PASSDB_RESULT_OK, request); + } + ++static void passdb_mock_lookup_credentials(struct auth_request *request, ++ lookup_credentials_callback_t *callback) ++{ ++ passdb_handle_credentials(PASSDB_RESULT_OK, "password", "PLAIN", ++ callback, request); ++} ++ + static struct passdb_module_interface mock_interface = { + .name = "mock", + .init = passdb_mock_init, + .deinit = passdb_mock_deinit, + .verify_plain = passdb_mock_verify_plain, ++ .lookup_credentials = passdb_mock_lookup_credentials, + }; + +-static struct auth_passdb_settings set = { ++struct auth_passdb_settings mock_passdb_set = { + .name = "mock", + .driver = "mock", + .args = "", +@@ -95,7 +103,7 @@ void passdb_mock_mod_deinit(void) + struct auth_passdb *passdb_mock(void) + { + struct auth_passdb *ret = i_new(struct auth_passdb, 1); +- ret->set = &set; ++ ret->set = &mock_passdb_set; + ret->passdb = mock_passdb_mod; + return ret; + } +diff -up dovecot-2.3.8/src/auth/test-auth.h.CVE_2020_12674prereq dovecot-2.3.8/src/auth/test-auth.h +--- dovecot-2.3.8/src/auth/test-auth.h.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/test-auth.h 2020-08-07 20:35:16.296684273 +0200 +@@ -8,6 +8,8 @@ + + struct auth_passdb; + ++extern struct auth_passdb_settings mock_passdb_set; ++ + void test_auth_request_var_expand(void); + void test_db_dict_parse_cache_key(void); + void test_username_filter(void); +diff -up dovecot-2.3.8/src/auth/auth-request.c.CVE_2020_12674prereq dovecot-2.3.8/src/auth/auth-request.c +--- dovecot-2.3.8/src/auth/auth-request.c.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/auth-request.c 2020-08-07 20:35:16.295684287 +0200 +@@ -16,6 +16,7 @@ + #include "auth-cache.h" + #include "auth-request.h" + #include "auth-request-handler.h" ++#include "auth-request-handler-private.h" + #include "auth-request-stats.h" + #include "auth-client-connection.h" + #include "auth-master-connection.h" +@@ -67,9 +68,6 @@ static void + auth_request_userdb_import(struct auth_request *request, const char *args); + + static +-void auth_request_verify_plain_continue(struct auth_request *request, +- verify_plain_callback_t *callback); +-static + void auth_request_lookup_credentials_policy_continue(struct auth_request *request, + lookup_credentials_callback_t *callback); + static +@@ -307,10 +307,12 @@ void auth_request_success_continue(struct auth_policy_check_ctx *ctx) + return; + } + +- stats = auth_request_stats_get(request); +- stats->auth_success_count++; +- if (request->master_user != NULL) +- stats->auth_master_success_count++; ++ if (request->set->stats) { ++ stats = auth_request_stats_get(request); ++ stats->auth_success_count++; ++ if (request->master_user != NULL) ++ stats->auth_master_success_count++; ++ } + + auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED); + auth_request_refresh_last_access(request); +@@ -324,8 +326,10 @@ void auth_request_fail(struct auth_request *request) + + i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); + +- stats = auth_request_stats_get(request); +- stats->auth_failure_count++; ++ if (request->set->stats) { ++ stats = auth_request_stats_get(request); ++ stats->auth_failure_count++; ++ } + + auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED); + auth_request_refresh_last_access(request); +@@ -1233,7 +1231,7 @@ void auth_request_policy_penalty_finish( + + switch(ctx->type) { + case AUTH_POLICY_CHECK_TYPE_PLAIN: +- auth_request_verify_plain_continue(ctx->request, ctx->callback_plain); ++ ctx->request->handler->verify_plain_continue_callback(ctx->request, ctx->callback_plain); + return; + case AUTH_POLICY_CHECK_TYPE_LOOKUP: + auth_request_lookup_credentials_policy_continue(ctx->request, ctx->callback_lookup); +@@ -1284,7 +1282,8 @@ void auth_request_verify_plain(struct au + request->user_changed_by_lookup = FALSE; + + if (request->policy_processed || !request->set->policy_check_before_auth) { +- auth_request_verify_plain_continue(request, callback); ++ request->handler->verify_plain_continue_callback(request, ++ callback); + } else { + ctx = p_new(request->pool, struct auth_policy_check_ctx, 1); + ctx->request = request; +@@ -1294,10 +1293,9 @@ void auth_request_verify_plain(struct au + } + } + +-static +-void auth_request_verify_plain_continue(struct auth_request *request, +- verify_plain_callback_t *callback) { +- ++void auth_request_default_verify_plain_continue(struct auth_request *request, ++ verify_plain_callback_t *callback) ++{ + struct auth_passdb *passdb; + enum passdb_result result; + const char *cache_key, *error; +diff -up dovecot-2.3.8/src/auth/auth-request-handler.c.CVE_2020_12674prereq dovecot-2.3.8/src/auth/auth-request-handler.c +--- dovecot-2.3.8/src/auth/auth-request-handler.c.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/auth-request-handler.c 2020-08-07 20:35:16.295684287 +0200 +@@ -17,32 +17,28 @@ + #include "auth-client-connection.h" + #include "auth-master-connection.h" + #include "auth-request-handler.h" ++#include "auth-request-handler-private.h" + #include "auth-policy.h" + + #define AUTH_FAILURE_DELAY_CHECK_MSECS 500 +- +-struct auth_request_handler { +- int refcount; +- pool_t pool; +- HASH_TABLE(void *, struct auth_request *) requests; +- +- unsigned int connect_uid, client_pid; +- +- auth_client_request_callback_t *callback; +- struct auth_client_connection *conn; +- +- auth_master_request_callback_t *master_callback; +- +- bool destroyed:1; +- bool token_auth:1; +-}; +- + static ARRAY(struct auth_request *) auth_failures_arr; + static struct aqueue *auth_failures; + static struct timeout *to_auth_failures; + + static void auth_failure_timeout(void *context) ATTR_NULL(1); + ++ ++static void ++auth_request_handler_default_reply_callback(struct auth_request *request, ++ enum auth_client_result result, ++ const void *auth_reply, ++ size_t reply_size); ++ ++static void ++auth_request_handler_default_reply_continue(struct auth_request *request, ++ const void *reply, ++ size_t reply_size); ++ + struct auth_request_handler * + auth_request_handler_create(bool token_auth, auth_client_request_callback_t *callback, + struct auth_client_connection *conn, +@@ -61,6 +57,12 @@ auth_request_handler_create(bool token_a + handler->conn = conn; + handler->master_callback = master_callback; + handler->token_auth = token_auth; ++ handler->reply_callback = ++ auth_request_handler_default_reply_callback; ++ handler->reply_continue_callback = ++ auth_request_handler_default_reply_continue; ++ handler->verify_plain_continue_callback = ++ auth_request_default_verify_plain_continue; + return handler; + } + +@@ -355,6 +363,16 @@ void auth_request_handler_reply(struct a + enum auth_client_result result, + const void *auth_reply, size_t reply_size) + { ++ struct auth_request_handler *handler = request->handler; ++ handler->reply_callback(request, result, auth_reply, reply_size); ++} ++ ++static void ++auth_request_handler_default_reply_callback(struct auth_request *request, ++ enum auth_client_result result, ++ const void *auth_reply, ++ size_t reply_size) ++{ + struct auth_request_handler *handler = request->handler; + string_t *str; + int ret; +@@ -407,6 +425,14 @@ void auth_request_handler_reply(struct a + void auth_request_handler_reply_continue(struct auth_request *request, + const void *reply, size_t reply_size) + { ++ request->handler->reply_continue_callback(request, reply, reply_size); ++} ++ ++static void ++auth_request_handler_default_reply_continue(struct auth_request *request, ++ const void *reply, ++ size_t reply_size) ++{ + auth_request_handler_reply(request, AUTH_CLIENT_RESULT_CONTINUE, + reply, reply_size); + } +@@ -703,6 +729,7 @@ static void auth_str_append_userdb_extra + auth_str_add_keyvalue(dest, "master_user", + request->master_user); + } ++ auth_str_add_keyvalue(dest, "auth_mech", request->mech->mech_name); + if (*request->set->anonymous_username != '\0' && + strcmp(request->user, request->set->anonymous_username) == 0) { + /* this is an anonymous login, either via ANONYMOUS +diff -up dovecot-2.3.8/src/auth/auth-request.h.CVE_2020_12674prereq dovecot-2.3.8/src/auth/auth-request.h +--- dovecot-2.3.8/src/auth/auth-request.h.CVE_2020_12674prereq 2019-10-08 10:46:18.000000000 +0200 ++++ dovecot-2.3.8/src/auth/auth-request.h 2020-08-07 20:35:16.295684287 +0200 +@@ -295,6 +295,8 @@ void auth_request_set_credentials(struct + set_credentials_callback_t *callback); + void auth_request_userdb_callback(enum userdb_result result, + struct auth_request *request); ++void auth_request_default_verify_plain_continue(struct auth_request *request, ++ verify_plain_callback_t *callback); + + void auth_request_refresh_last_access(struct auth_request *request); + void auth_str_append(string_t *dest, const char *key, const char *value); diff --git a/SOURCES/dovecot-2.3.8-a668d767.patch b/SOURCES/dovecot-2.3.8-a668d767.patch new file mode 100644 index 0000000..71aba2a --- /dev/null +++ b/SOURCES/dovecot-2.3.8-a668d767.patch @@ -0,0 +1,73 @@ +diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c +index 011dea9050..8baf622e59 100644 +--- a/src/lib-mail/message-parser.c ++++ b/src/lib-mail/message-parser.c +@@ -138,6 +138,7 @@ message_part_append(struct message_parser_ctx *ctx) + struct message_part *parent = ctx->part; + struct message_part *part; + ++ i_assert(!ctx->preparsed); + i_assert(parent != NULL); + i_assert((parent->flags & (MESSAGE_PART_FLAG_MULTIPART | + MESSAGE_PART_FLAG_MESSAGE_RFC822)) != 0); +@@ -171,12 +172,14 @@ static void message_part_finish(struct message_parser_ctx *ctx) + { + struct message_part **const *parent_next_partp; + +- i_assert(ctx->nested_parts_count > 0); +- ctx->nested_parts_count--; ++ if (!ctx->preparsed) { ++ i_assert(ctx->nested_parts_count > 0); ++ ctx->nested_parts_count--; + +- parent_next_partp = array_back(&ctx->next_part_stack); +- array_pop_back(&ctx->next_part_stack); +- ctx->next_part = *parent_next_partp; ++ parent_next_partp = array_back(&ctx->next_part_stack); ++ array_pop_back(&ctx->next_part_stack); ++ ctx->next_part = *parent_next_partp; ++ } + + message_size_add(&ctx->part->parent->body_size, &ctx->part->body_size); + message_size_add(&ctx->part->parent->body_size, &ctx->part->header_size); +diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c +index 13984f939e..a00f0d6200 100644 +--- a/src/lib-mail/test-message-parser.c ++++ b/src/lib-mail/test-message-parser.c +@@ -178,9 +178,10 @@ static void test_message_parser_small_blocks(void) + static void test_message_parser_stop_early(void) + { + struct message_parser_ctx *parser; +- struct istream *input; ++ struct istream *input, *input2; + struct message_part *parts; + struct message_block block; ++ const char *error; + unsigned int i; + pool_t pool; + int ret; +@@ -198,6 +199,24 @@ static void test_message_parser_stop_early(void) + &block)) > 0) ; + test_assert(ret == 0); + message_parser_deinit(&parser, &parts); ++ ++ /* test preparsed - first re-parse everything with a stream ++ that sees EOF at this position */ ++ input2 = i_stream_create_from_data(test_msg, i); ++ parser = message_parser_init(pool, input2, &set_empty); ++ while ((ret = message_parser_parse_next_block(parser, ++ &block)) > 0) ; ++ test_assert(ret == -1); ++ message_parser_deinit(&parser, &parts); ++ ++ /* now parse from the parts */ ++ i_stream_seek(input2, 0); ++ parser = message_parser_init_from_parts(parts, input2, &set_empty); ++ while ((ret = message_parser_parse_next_block(parser, ++ &block)) > 0) ; ++ test_assert(ret == -1); ++ test_assert(message_parser_deinit_from_parts(&parser, &parts, &error) == 0); ++ i_stream_unref(&input2); + } + + i_stream_unref(&input); diff --git a/SOURCES/dovecot-2.3.8-blockcount.patch b/SOURCES/dovecot-2.3.8-blockcount.patch new file mode 100644 index 0000000..fb0a9b0 --- /dev/null +++ b/SOURCES/dovecot-2.3.8-blockcount.patch @@ -0,0 +1,20 @@ +diff -up dovecot-2.3.8/src/lib-storage/index/index-mail-binary.c.blockcount dovecot-2.3.8/src/lib-storage/index/index-mail-binary.c +--- dovecot-2.3.8/src/lib-storage/index/index-mail-binary.c.blockcount 2020-12-02 11:34:10.229027593 +0100 ++++ dovecot-2.3.8/src/lib-storage/index/index-mail-binary.c 2020-12-02 11:36:47.328933276 +0100 +@@ -339,13 +339,14 @@ blocks_count_lines(struct binary_ctx *ct + i_stream_skip(full_input, skip); + cur_block_offset += skip; + +- if (cur_block->input->eof) { ++ if (i_stream_read_eof(cur_block->input)) { + /* go to the next block */ +- if (++block_idx == block_count) { ++ if (block_idx+1 == block_count) { + i_assert(i_stream_read_eof(full_input)); + ret = -1; + break; + } ++ block_idx++; + cur_block++; + cur_block_offset = 0; + } diff --git a/SOURCES/dovecot.tmpfilesd b/SOURCES/dovecot.tmpfilesd index 7178498..d96639a 100644 --- a/SOURCES/dovecot.tmpfilesd +++ b/SOURCES/dovecot.tmpfilesd @@ -1,2 +1,2 @@ -d /var/run/dovecot 0755 root dovecot - +d /run/dovecot 0755 root dovecot - diff --git a/SPECS/dovecot.spec b/SPECS/dovecot.spec index 62c60a1..c79e55d 100644 --- a/SPECS/dovecot.spec +++ b/SPECS/dovecot.spec @@ -5,7 +5,7 @@ Name: dovecot Epoch: 1 Version: 2.3.8 %global prever %{nil} -Release: 3%{?dist} +Release: 9%{?dist} #dovecot itself is MIT, a few sources are PD, pigeonhole is LGPLv2 License: MIT and LGPLv2 Group: System Environment/Daemons @@ -32,7 +32,6 @@ Patch6: dovecot-2.1.10-waitonline.patch Patch8: dovecot-2.2.20-initbysystemd.patch Patch9: dovecot-2.2.22-systemd_w_protectsystem.patch -Patch10: dovecot-2.3.0.1-libxcrypt.patch # sent upstream, rhbz#1630380 Patch11: dovecot-2.2.36-aclfix.patch @@ -48,6 +47,36 @@ Patch14: dovecot-2.3.6-opensslhmac.patch Patch15: dovecot-2.3.10-smtppre.patch Patch16: dovecot-2.3.10-CVE_2020_10957,10958,10967.patch +# from upstream, for dovecot <= 2.3.10.1 +Patch17: dovecot-2.3.8-CVE_2020_12100prereq.patch +Patch18: dovecot-2.3.8-CVE_2020_12100.patch +Patch19: dovecot-2.3.8-CVE_2020_12100ph.patch +Patch20: dovecot-2.3.8-CVE_2020_12673.patch +Patch21: dovecot-2.3.8-CVE_2020_12674prereq.patch +Patch22: dovecot-2.3.8-CVE_2020_12674.patch + +# from upstream, for dovecot <= 2.3.11.3, rhbz#1894418 +Patch23: dovecot-2.3.8-blockcount.patch + +# from upstream, for dovecot < 2.3.11.3, rhbz#1888111 +Patch24: dovecot-2.3.8-a668d767.patch +Patch25: dovecot-2.3.13-CVE_2020_25275-part1.patch +Patch26: dovecot-2.3.13-CVE_2020_25275-part2.patch +Patch27: dovecot-2.3.13-CVE_2020_25275-part3.patch +Patch28: dovecot-2.3.13-CVE_2020_25275-part4.patch +Patch29: dovecot-2.3.13-CVE_2020_25275-part5.patch +Patch30: dovecot-2.3.13-CVE_2020_25275-part6.patch +Patch31: dovecot-2.3.13-CVE_2020_25275-part7.patch +Patch32: dovecot-2.3.13-CVE_2020_25275-part8.patch +Patch33: dovecot-2.3.13-CVE_2020_25275regr-part1.patch +Patch34: dovecot-2.3.13-CVE_2020_25275regr-part2.patch +Patch35: dovecot-2.3.13-CVE_2020_25275regr-part3.patch +Patch36: dovecot-2.3.13-CVE_2020_24386-prereq1.patch +Patch37: dovecot-2.3.13-CVE_2020_24386-part1.patch +Patch38: dovecot-2.3.13-CVE_2020_24386-part2.patch +Patch39: dovecot-2.3.13-CVE_2020_24386-part3.patch +Patch40: dovecot-2.3.13-CVE_2020_24386-part4.patch + Source15: prestartscript BuildRequires: openssl-devel, pam-devel, zlib-devel, bzip2-devel, libcap-devel @@ -62,6 +91,7 @@ BuildRequires: krb5-devel BuildRequires: quota-devel BuildRequires: xz-devel BuildRequires: lz4-devel +BuildRequires: multilib-rpm-config #BuildRequires: libsodium-devel #BuildRequires: libexttextcat-devel #BuildRequires: libstemmer-devel @@ -148,12 +178,35 @@ This package provides the development files for dovecot. %patch6 -p1 -b .waitonline %patch8 -p1 -b .initbysystemd %patch9 -p1 -b .systemd_w_protectsystem -#%patch10 -p1 -b .libxcrypt %patch11 -p1 -b .aclfix %patch13 -p1 -b .bigkey %patch14 -p1 -b .opensslhmac %patch15 -p1 -b .smtppre %patch16 -p1 -b .CVE_2020_10957,10958,10967 +%patch17 -p1 -b .CVE_2020_12100prereq +%patch18 -p1 -b .CVE_2020_12100 +%patch19 -p1 -b .CVE_2020_12100ph +%patch20 -p1 -b .CVE_2020_12673 +%patch21 -p1 -b .CVE_2020_12674prereq +%patch22 -p1 -b .CVE_2020_12674 +%patch23 -p1 -b .blockcount +%patch24 -p1 -b .a668d767 +%patch25 -p1 -b .CVE_2020_25275-part1 +%patch26 -p1 -b .CVE_2020_25275-part2 +%patch27 -p1 -b .CVE_2020_25275-part3 +%patch28 -p1 -b .CVE_2020_25275-part4 +%patch29 -p1 -b .CVE_2020_25275-part5 +%patch30 -p1 -b .CVE_2020_25275-part6 +%patch31 -p1 -b .CVE_2020_25275-part7 +%patch32 -p1 -b .CVE_2020_25275-part8 +%patch33 -p1 -b .CVE_2020_25275regr-part1 +%patch34 -p1 -b .CVE_2020_25275regr-part2 +%patch35 -p1 -b .CVE_2020_25275regr-part3 +%patch36 -p1 -b .CVE_2020_24386-prereq1 +%patch37 -p1 -b .CVE_2020_24386-part1 +%patch38 -p1 -b .CVE_2020_24386-part2 +%patch39 -p1 -b .CVE_2020_24386-part3 +%patch40 -p1 -b .CVE_2020_24386-part4 pushd dovecot-2*3-pigeonhole-%{pigeonholever} popd @@ -172,6 +225,7 @@ autoreconf -I . -fiv #required for aarch64 support %endif %configure \ INSTALL_DATA="install -c -p -m644" \ + --with-rundir=%{_rundir}/%{name} \ --docdir=%{_docdir}/%{name} \ --disable-static \ --disable-rpath \ @@ -218,9 +272,11 @@ rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT -#move doc dir back to build dir so doc macro in files section can use it +# move doc dir back to build dir so doc macro in files section can use it mv $RPM_BUILD_ROOT/%{_docdir}/%{name} %{_builddir}/%{name}-%{version}%{?prever}/docinstall +# fix multilib issues +%multilib_fix_c_header --file %{_includedir}/dovecot/config.h pushd dovecot-2*3-pigeonhole-%{pigeonholever} make install DESTDIR=$RPM_BUILD_ROOT @@ -513,6 +569,27 @@ make check %{_libdir}/%{name}/dict/libdriver_pgsql.so %changelog +* Wed Feb 03 2021 Michal Hlavinka - 1:2.3.8-9 +- fix CVE-2020-24386 IMAP hibernation function allows mail access (#1913534) + +* Tue Jan 12 2021 Michal Hlavinka - 1:2.3.8-8 +- fix CVE-2020-25275 denial of service via mail MIME parsing (#1914019) + +* Thu Jan 07 2021 Michal Hlavinka - 1:2.3.8-7 +- change run directory from /var/run to /run (#1805947) + +* Wed Dec 02 2020 Michal Hlavinka - 1:2.3.8-6 +- fix mail storage block count parsing (#1894418) +- MIME parser crashed when boundaries were wrong (#1888111) + +* Mon Nov 02 2020 Michal Hlavinka - 1:2.3.8-5 +- multilib compatibility (#1853137) + +* Fri Aug 07 2020 Michal Hlavinka - 1:2.3.8-4 +- fix CVE-2020-12100 resource exhaustion via deeply nested MIME parts (#1866756) +- fix CVE-2020-12673 out of bound reads in dovecot NTLM implementation (#1866761) +- fix CVE-2020-12674 crash due to assert in RPA implementation (#1866768) + * Mon Jun 01 2020 Michal Hlavinka - 1:2.3.8-3 - fix CVE-2020-10957 dovecot: malformed NOOP commands leads to DoS (#1840354) - fix CVE-2020-10958 dovecot: command followed by sufficient number of newlines