From 796e7b7651654783bf9c8f20d28eebc53c3d6e3c Mon Sep 17 00:00:00 2001 From: webbuilder_pel7ppc64lebuilder0 Date: Mon, 14 Dec 2020 18:01:02 +0100 Subject: [PATCH] dovecot package update Signed-off-by: webbuilder_pel7ppc64lebuilder0 --- ...dovecot-1.0.beta2-mkcert-permissions.patch | 4 +- SOURCES/dovecot-1.0.rc7-mkcert-paths.patch | 4 +- SOURCES/dovecot-2.0-defaultconfig.patch | 8 +- SOURCES/dovecot-2.1.10-waitonline.patch | 2 +- SOURCES/dovecot-2.2-gidcheck.patch | 41 +- SOURCES/dovecot-2.2.36-aclfix.patch | 12 +- SOURCES/dovecot-2.2.36-bigkey.patch | 10 + ...ovecot-2.2.36-cve2019_11500_part1of4.patch | 37 + ...ovecot-2.2.36-cve2019_11500_part2of4.patch | 33 + ...ovecot-2.2.36-cve2019_11500_part3of4.patch | 36 + ...ovecot-2.2.36-cve2019_11500_part4of4.patch | 33 + ...dovecot-2.2.36-cve_2019_3814part1of3.patch | 69 + ...dovecot-2.2.36-cve_2019_3814part2of3.patch | 29 + ...dovecot-2.2.36-cve_2019_3814part3of3.patch | 22 + ...dovecot-2.2.36-cve_2019_7524part1of2.patch | 32 + ...dovecot-2.2.36-cve_2019_7524part2of2.patch | 22 + SOURCES/dovecot-2.2.36-portreserve.patch | 11 + SOURCES/dovecot-2.2.9-nodevrand.patch | 24 +- SOURCES/dovecot-2.3.8-CVE_2020_12100.patch | 2432 +++++++++++++++++ SOURCES/dovecot-2.3.8-CVE_2020_12100ph.patch | 56 + .../dovecot-2.3.8-CVE_2020_12100prereq.patch | 121 + SOURCES/dovecot-2.3.8-CVE_2020_12673.patch | 34 + SOURCES/dovecot-2.3.8-CVE_2020_12674.patch | 237 ++ .../dovecot-2.3.8-CVE_2020_12674prereq.patch | 818 ++++++ SOURCES/dovecot.init | 3 +- SOURCES/dovecot.portreserve | 5 + SOURCES/dovecot.tmpfilesd | 1 + SOURCES/prestartscript | 3 + SPECS/dovecot.spec | 134 +- 29 files changed, 4191 insertions(+), 82 deletions(-) create mode 100644 SOURCES/dovecot-2.2.36-bigkey.patch create mode 100644 SOURCES/dovecot-2.2.36-cve2019_11500_part1of4.patch create mode 100644 SOURCES/dovecot-2.2.36-cve2019_11500_part2of4.patch create mode 100644 SOURCES/dovecot-2.2.36-cve2019_11500_part3of4.patch create mode 100644 SOURCES/dovecot-2.2.36-cve2019_11500_part4of4.patch create mode 100644 SOURCES/dovecot-2.2.36-cve_2019_3814part1of3.patch create mode 100644 SOURCES/dovecot-2.2.36-cve_2019_3814part2of3.patch create mode 100644 SOURCES/dovecot-2.2.36-cve_2019_3814part3of3.patch create mode 100644 SOURCES/dovecot-2.2.36-cve_2019_7524part1of2.patch create mode 100644 SOURCES/dovecot-2.2.36-cve_2019_7524part2of2.patch create mode 100644 SOURCES/dovecot-2.2.36-portreserve.patch create mode 100644 SOURCES/dovecot-2.3.8-CVE_2020_12100.patch create mode 100644 SOURCES/dovecot-2.3.8-CVE_2020_12100ph.patch create mode 100644 SOURCES/dovecot-2.3.8-CVE_2020_12100prereq.patch create mode 100644 SOURCES/dovecot-2.3.8-CVE_2020_12673.patch create mode 100644 SOURCES/dovecot-2.3.8-CVE_2020_12674.patch create mode 100644 SOURCES/dovecot-2.3.8-CVE_2020_12674prereq.patch create mode 100644 SOURCES/dovecot.portreserve create mode 100644 SOURCES/prestartscript diff --git a/SOURCES/dovecot-1.0.beta2-mkcert-permissions.patch b/SOURCES/dovecot-1.0.beta2-mkcert-permissions.patch index 163331a..68ac01b 100644 --- a/SOURCES/dovecot-1.0.beta2-mkcert-permissions.patch +++ b/SOURCES/dovecot-1.0.beta2-mkcert-permissions.patch @@ -2,10 +2,10 @@ +++ dovecot-1.0.beta2/doc/mkcert.sh 2006-01-26 14:28:38.000000000 +0100 @@ -29,6 +29,7 @@ fi - + $OPENSSL req -new -x509 -nodes -config $OPENSSLCONFIG -out $CERTFILE -keyout $KEYFILE -days 365 || exit 2 -chmod 0600 $KEYFILE +chown root:root $CERTFILE $KEYFILE +chmod 0600 $CERTFILE $KEYFILE - echo + echo $OPENSSL x509 -subject -fingerprint -noout -in $CERTFILE || exit 2 diff --git a/SOURCES/dovecot-1.0.rc7-mkcert-paths.patch b/SOURCES/dovecot-1.0.rc7-mkcert-paths.patch index ce331a3..e8354e5 100644 --- a/SOURCES/dovecot-1.0.rc7-mkcert-paths.patch +++ b/SOURCES/dovecot-1.0.rc7-mkcert-paths.patch @@ -2,13 +2,13 @@ diff -up dovecot-2.2.27/doc/mkcert.sh.mkcert-paths dovecot-2.2.27/doc/mkcert.sh --- dovecot-2.2.27/doc/mkcert.sh.mkcert-paths 2016-12-05 10:26:07.913515286 +0100 +++ dovecot-2.2.27/doc/mkcert.sh 2016-12-05 10:28:25.439634417 +0100 @@ -5,8 +5,8 @@ - + umask 077 OPENSSL=${OPENSSL-openssl} -SSLDIR=${SSLDIR-/etc/ssl} -OPENSSLCONFIG=${OPENSSLCONFIG-dovecot-openssl.cnf} +SSLDIR=${SSLDIR-/etc/pki/dovecot} +OPENSSLCONFIG=${OPENSSLCONFIG-/etc/pki/dovecot/dovecot-openssl.cnf} - + CERTDIR=$SSLDIR/certs KEYDIR=$SSLDIR/private diff --git a/SOURCES/dovecot-2.0-defaultconfig.patch b/SOURCES/dovecot-2.0-defaultconfig.patch index 5599f93..6a82efc 100644 --- a/SOURCES/dovecot-2.0-defaultconfig.patch +++ b/SOURCES/dovecot-2.0-defaultconfig.patch @@ -8,14 +8,14 @@ diff -up dovecot-2.2.10/doc/example-config/conf.d/10-mail.conf.default-settings -#first_valid_uid = 500 +first_valid_uid = 1000 #last_valid_uid = 0 - + # Valid GID range for users, defaults to non-root/wheel. Users having @@ -286,6 +286,7 @@ namespace inbox { # them simultaneously. #mbox_read_locks = fcntl #mbox_write_locks = dotlock fcntl +mbox_write_locks = fcntl - + # Maximum time to wait for lock (all of them) before aborting. #mbox_lock_timeout = 5 mins diff -up dovecot-2.2.10/doc/example-config/conf.d/10-ssl.conf.default-settings dovecot-2.2.10/doc/example-config/conf.d/10-ssl.conf @@ -23,12 +23,12 @@ diff -up dovecot-2.2.10/doc/example-config/conf.d/10-ssl.conf.default-settings d +++ dovecot-2.2.10/doc/example-config/conf.d/10-ssl.conf 2016-06-17 17:54:18.749626750 +0200 @@ -3,7 +3,9 @@ ## - + # SSL/TLS support: yes, no, required. -#ssl = yes +# disable plain pop3 and imap, allowed are only pop3+TLS, pop3s, imap+TLS and imaps +# plain imap and pop3 are still allowed for local connections +ssl = required - + # PEM encoded X.509 SSL/TLS certificate and private key. They're opened before # dropping root privileges, so keep the key file unreadable by anyone but diff --git a/SOURCES/dovecot-2.1.10-waitonline.patch b/SOURCES/dovecot-2.1.10-waitonline.patch index e73bd65..ba4edeb 100644 --- a/SOURCES/dovecot-2.1.10-waitonline.patch +++ b/SOURCES/dovecot-2.1.10-waitonline.patch @@ -7,7 +7,7 @@ diff -up dovecot-2.2.36/dovecot.service.in.waitonline dovecot-2.2.36/dovecot.ser Documentation=http://wiki2.dovecot.org/ -After=local-fs.target network.target +After=local-fs.target network.target network-online.target - + [Service] Type=forking +ExecStartPre=/usr/libexec/dovecot/prestartscript diff --git a/SOURCES/dovecot-2.2-gidcheck.patch b/SOURCES/dovecot-2.2-gidcheck.patch index 12e3b86..78b4fc3 100644 --- a/SOURCES/dovecot-2.2-gidcheck.patch +++ b/SOURCES/dovecot-2.2-gidcheck.patch @@ -15,45 +15,46 @@ diff -up dovecot-2.2.36/src/auth/auth-settings.c.gidcheck dovecot-2.2.36/src/aut --- dovecot-2.2.36/src/auth/auth-settings.c.gidcheck 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/auth/auth-settings.c 2018-09-17 12:17:13.132032699 +0200 @@ -272,6 +272,8 @@ static const struct setting_define auth_ - DEF_NOPREFIX(SET_BOOL, verbose_proctitle), - DEF_NOPREFIX(SET_UINT, first_valid_uid), - DEF_NOPREFIX(SET_UINT, last_valid_uid), + DEF_NOPREFIX(SET_BOOL, verbose_proctitle), + DEF_NOPREFIX(SET_UINT, first_valid_uid), + DEF_NOPREFIX(SET_UINT, last_valid_uid), + DEF_NOPREFIX(SET_UINT, first_valid_gid), + DEF_NOPREFIX(SET_UINT, last_valid_gid), - - DEF_NOPREFIX(SET_STR, ssl_client_ca_dir), - DEF_NOPREFIX(SET_STR, ssl_client_ca_file), + + DEF_NOPREFIX(SET_STR, ssl_client_ca_dir), + DEF_NOPREFIX(SET_STR, ssl_client_ca_file), @@ -331,6 +333,8 @@ static const struct auth_settings auth_d - .verbose_proctitle = FALSE, - .first_valid_uid = 500, - .last_valid_uid = 0, + .verbose_proctitle = FALSE, + .first_valid_uid = 500, + .last_valid_uid = 0, + .first_valid_gid = 1, + .last_valid_gid = 0, }; - + const struct setting_parser_info auth_setting_parser_info = { diff -up dovecot-2.2.36/src/auth/auth-settings.h.gidcheck dovecot-2.2.36/src/auth/auth-settings.h --- dovecot-2.2.36/src/auth/auth-settings.h.gidcheck 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/auth/auth-settings.h 2018-09-17 12:13:30.540159133 +0200 @@ -88,6 +88,8 @@ struct auth_settings { - bool verbose_proctitle; - unsigned int first_valid_uid; - unsigned int last_valid_uid; + bool verbose_proctitle; + unsigned int first_valid_uid; + unsigned int last_valid_uid; + unsigned int first_valid_gid; + unsigned int last_valid_gid; - - /* generated: */ - char username_chars_map[256]; + + /* generated: */ + char username_chars_map[256]; diff -up dovecot-2.2.36/src/auth/userdb-passwd.c.gidcheck dovecot-2.2.36/src/auth/userdb-passwd.c --- dovecot-2.2.36/src/auth/userdb-passwd.c.gidcheck 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/auth/userdb-passwd.c 2018-09-17 12:13:30.540159133 +0200 @@ -145,6 +145,10 @@ passwd_iterate_want_pw(struct passwd *pw - return FALSE; - if (pw->pw_uid > (uid_t)set->last_valid_uid && set->last_valid_uid != 0) - return FALSE; + return FALSE; + if (pw->pw_uid > (uid_t)set->last_valid_uid && set->last_valid_uid != 0) + return FALSE; + if (pw->pw_gid < (gid_t)set->first_valid_gid) + return FALSE; + if (pw->pw_gid > (gid_t)set->last_valid_gid && set->last_valid_gid != 0) + return FALSE; - return TRUE; + return TRUE; } + diff --git a/SOURCES/dovecot-2.2.36-aclfix.patch b/SOURCES/dovecot-2.2.36-aclfix.patch index 202655b..1288b7b 100644 --- a/SOURCES/dovecot-2.2.36-aclfix.patch +++ b/SOURCES/dovecot-2.2.36-aclfix.patch @@ -2,12 +2,12 @@ diff -up dovecot-2.2.36/src/plugins/acl/acl-backend-vfile.c.aclfix dovecot-2.2.3 --- dovecot-2.2.36/src/plugins/acl/acl-backend-vfile.c.aclfix 2018-09-18 15:00:08.778823903 +0200 +++ dovecot-2.2.36/src/plugins/acl/acl-backend-vfile.c 2018-09-18 15:00:08.814823737 +0200 @@ -161,8 +161,7 @@ acl_backend_vfile_object_init(struct acl - T_BEGIN { - if (*name == '\0' || - mailbox_list_is_valid_name(_backend->list, name, &error)) { + T_BEGIN { + if (*name == '\0' || + mailbox_list_is_valid_name(_backend->list, name, &error)) { - vname = *name == '\0' ? "" : - mailbox_list_get_vname(_backend->list, name); + vname = mailbox_list_get_vname(_backend->list, name); - - dir = acl_backend_vfile_get_local_dir(_backend, name, vname); - aclobj->local_path = dir == NULL ? NULL : + + dir = acl_backend_vfile_get_local_dir(_backend, name, vname); + aclobj->local_path = dir == NULL ? NULL : diff --git a/SOURCES/dovecot-2.2.36-bigkey.patch b/SOURCES/dovecot-2.2.36-bigkey.patch new file mode 100644 index 0000000..c5b23d9 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-bigkey.patch @@ -0,0 +1,10 @@ +diff -up dovecot-2.2.36/doc/dovecot-openssl.cnf.bigkey dovecot-2.2.36/doc/dovecot-openssl.cnf +--- dovecot-2.2.36/doc/dovecot-openssl.cnf.bigkey 2017-06-23 13:18:28.000000000 +0200 ++++ dovecot-2.2.36/doc/dovecot-openssl.cnf 2018-10-16 17:15:35.836205498 +0200 +@@ -1,5 +1,5 @@ + [ req ] +-default_bits = 1024 ++default_bits = 3072 + encrypt_key = yes + distinguished_name = req_dn + x509_extensions = cert_type diff --git a/SOURCES/dovecot-2.2.36-cve2019_11500_part1of4.patch b/SOURCES/dovecot-2.2.36-cve2019_11500_part1of4.patch new file mode 100644 index 0000000..7655fc6 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve2019_11500_part1of4.patch @@ -0,0 +1,37 @@ +From 58ffd3e8a02e54fc98b6be78e02b0511ee9263eb Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 10 May 2019 19:24:51 +0300 +Subject: [PATCH 1/2] lib-imap: Don't accept strings with NULs + +IMAP doesn't allow NULs except in binary literals. We'll still allow them +in regular literals as well, but just not in strings. + +This fixes a bug with unescaping a string with NULs: str_unescape() could +have been called for memory that points outside the allocated string, +causing heap corruption. This could cause crashes or theoretically even +result in remote code execution exploit. + +Found by Nick Roessler and Rafi Rubin +--- + src/lib-imap/imap-parser.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c +index dddf55189..f41668d7a 100644 +--- a/src/lib-imap/imap-parser.c ++++ b/src/lib-imap/imap-parser.c +@@ -363,6 +363,11 @@ static bool imap_parser_read_string(struct imap_parser *parser, + break; + } + ++ if (data[i] == '\0') { ++ parser->error = "NULs not allowed in strings"; ++ return FALSE; ++ } ++ + if (data[i] == '\\') { + if (i+1 == data_size) { + /* known data ends with '\' - leave it to +-- +2.11.0 + diff --git a/SOURCES/dovecot-2.2.36-cve2019_11500_part2of4.patch b/SOURCES/dovecot-2.2.36-cve2019_11500_part2of4.patch new file mode 100644 index 0000000..65746d6 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve2019_11500_part2of4.patch @@ -0,0 +1,33 @@ +From a56b0636b1bf9c7677c6fca9681f48752af700a1 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 17 May 2019 10:33:53 +0300 +Subject: [PATCH 2/2] lib-imap: Make sure str_unescape() won't be writing past + allocated memory + +The previous commit should already prevent this, but this makes sure it +can't become broken in the future either. It makes the performance a tiny +bit worse, but that's not practically noticeable. +--- + src/lib-imap/imap-parser.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c +index f41668d7a..7f58d99e2 100644 +--- a/src/lib-imap/imap-parser.c ++++ b/src/lib-imap/imap-parser.c +@@ -267,10 +267,8 @@ static void imap_parser_save_arg(struct imap_parser *parser, + + /* remove the escapes */ + if (parser->str_first_escape >= 0 && +- (parser->flags & IMAP_PARSE_FLAG_NO_UNESCAPE) == 0) { +- /* -1 because we skipped the '"' prefix */ +- (void)str_unescape(str + parser->str_first_escape-1); +- } ++ (parser->flags & IMAP_PARSE_FLAG_NO_UNESCAPE) == 0) ++ (void)str_unescape(str); + arg->_data.str = str; + arg->str_len = strlen(str); + break; +-- +2.11.0 + diff --git a/SOURCES/dovecot-2.2.36-cve2019_11500_part3of4.patch b/SOURCES/dovecot-2.2.36-cve2019_11500_part3of4.patch new file mode 100644 index 0000000..f04c7aa --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve2019_11500_part3of4.patch @@ -0,0 +1,36 @@ +From 7ce9990a5e6ba59e89b7fe1c07f574279aed922c Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 10 May 2019 19:43:55 +0300 +Subject: [PATCH 1/2] lib-managesieve: Don't accept strings with NULs + +ManageSieve doesn't allow NULs in strings. + +This fixes a bug with unescaping a string with NULs: str_unescape() could +have been called for memory that points outside the allocated string, +causing heap corruption. This could cause crashes or theoretically even +result in remote code execution exploit. + +Found by Nick Roessler and Rafi Rubin +--- + src/lib-managesieve/managesieve-parser.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/lib-managesieve/managesieve-parser.c b/src/lib-managesieve/managesieve-parser.c +index d3eb2101..f5f9d323 100644 +--- a/src/lib-managesieve/managesieve-parser.c ++++ b/src/lib-managesieve/managesieve-parser.c +@@ -258,6 +258,11 @@ managesieve_parser_read_string(struct managesieve_parser *parser, + break; + } + ++ if (data[i] == '\0') { ++ parser->error = "NULs not allowed in strings"; ++ return FALSE; ++ } ++ + if (data[i] == '\\') { + if (i+1 == data_size) { + /* known data ends with '\' - leave it to +-- +2.11.0 + diff --git a/SOURCES/dovecot-2.2.36-cve2019_11500_part4of4.patch b/SOURCES/dovecot-2.2.36-cve2019_11500_part4of4.patch new file mode 100644 index 0000000..6b0e101 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve2019_11500_part4of4.patch @@ -0,0 +1,33 @@ +From 4a299840cdb51f61f8d1ebc0210b19c40dfbc1cc Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 17 May 2019 10:39:25 +0300 +Subject: [PATCH 2/2] lib-managesieve: Make sure str_unescape() won't be + writing past allocated memory + +The previous commit should already prevent this, but this makes sure it +can't become broken in the future either. It makes the performance a tiny +bit worse, but that's not practically noticeable. +--- + src/lib-managesieve/managesieve-parser.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/src/lib-managesieve/managesieve-parser.c b/src/lib-managesieve/managesieve-parser.c +index f5f9d323..dc7d1fa9 100644 +--- a/src/lib-managesieve/managesieve-parser.c ++++ b/src/lib-managesieve/managesieve-parser.c +@@ -169,10 +169,8 @@ static void managesieve_parser_save_arg(struct managesieve_parser *parser, + + /* remove the escapes */ + if (parser->str_first_escape >= 0 && +- (parser->flags & MANAGESIEVE_PARSE_FLAG_NO_UNESCAPE) == 0) { +- /* -1 because we skipped the '"' prefix */ +- str_unescape(str + parser->str_first_escape-1); +- } ++ (parser->flags & MANAGESIEVE_PARSE_FLAG_NO_UNESCAPE) == 0) ++ (void)str_unescape(str); + + arg->_data.str = str; + arg->str_len = strlen(str); +-- +2.11.0 + diff --git a/SOURCES/dovecot-2.2.36-cve_2019_3814part1of3.patch b/SOURCES/dovecot-2.2.36-cve_2019_3814part1of3.patch new file mode 100644 index 0000000..7701369 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve_2019_3814part1of3.patch @@ -0,0 +1,69 @@ +From eb5ffe2641febe0fa5e9038f2e216c130e1e7519 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +Date: Mon, 21 Jan 2019 11:36:30 +0200 +Subject: [PATCH] login-common: Ensure we get username from certificate + +--- + src/login-common/sasl-server.c | 42 ++++++++++++++++++++++++++++++++-- + 1 file changed, 40 insertions(+), 2 deletions(-) + +diff --git a/src/login-common/sasl-server.c b/src/login-common/sasl-server.c +index a833c9a6d4..9465da9657 100644 +--- a/src/login-common/sasl-server.c ++++ b/src/login-common/sasl-server.c +@@ -321,6 +321,37 @@ authenticate_callback(struct auth_client_request *request, + } + } + ++static bool get_cert_username(struct client *client, const char **username_r, ++ const char **error_r) ++{ ++ /* no SSL */ ++ if (client->ssl_proxy == NULL) { ++ *username_r = NULL; ++ return TRUE; ++ } ++ ++ /* no client certificate */ ++ if (!ssl_proxy_has_valid_client_cert(client->ssl_proxy)) { ++ *username_r = NULL; ++ return TRUE; ++ } ++ ++ /* get peer name */ ++ const char *username = ssl_proxy_get_peer_name(client->ssl_proxy); ++ ++ /* if we wanted peer name, but it was not there, fail */ ++ if (client->set->auth_ssl_username_from_cert && ++ (username == NULL || *username == '\0')) { ++ if (client->set->auth_ssl_require_client_cert) { ++ *error_r = "Missing username in certificate"; ++ return FALSE; ++ } ++ } ++ ++ *username_r = username; ++ return TRUE; ++} ++ + void sasl_server_auth_begin(struct client *client, + const char *service, const char *mech_name, + const char *initial_resp_base64, +@@ -359,8 +390,15 @@ void sasl_server_auth_begin(struct client *client, + info.mech = mech->name; + info.service = service; + info.session_id = client_get_session_id(client); +- info.cert_username = client->ssl_proxy == NULL ? NULL : +- ssl_proxy_get_peer_name(client->ssl_proxy); ++ if (client->set->auth_ssl_username_from_cert) { ++ const char *error; ++ if (!get_cert_username(client, &info.cert_username, &error)) { ++ client_log_err(client, t_strdup_printf("Cannot get username " ++ "from certificate: %s", error)); ++ sasl_server_auth_failed(client, "Unable to validate certificate"); ++ return; ++ } ++ } + info.flags = client_get_auth_flags(client); + info.local_ip = client->local_ip; + info.remote_ip = client->ip; diff --git a/SOURCES/dovecot-2.2.36-cve_2019_3814part2of3.patch b/SOURCES/dovecot-2.2.36-cve_2019_3814part2of3.patch new file mode 100644 index 0000000..ea4487e --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve_2019_3814part2of3.patch @@ -0,0 +1,29 @@ +From 7525fece60f01b52deb13df3620976ee1d616837 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +Date: Mon, 21 Jan 2019 10:54:06 +0200 +Subject: [PATCH] auth: Fail authentication if certificate username was + unexpectedly missing + +--- + src/auth/auth-request-handler.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/auth/auth-request-handler.c b/src/auth/auth-request-handler.c +index 617dc1883d..3044e94f91 100644 +--- a/src/auth/auth-request-handler.c ++++ b/src/auth/auth-request-handler.c +@@ -560,6 +560,14 @@ bool auth_request_handler_auth_begin(struct auth_request_handler *handler, + return TRUE; + } + ++ if (request->set->ssl_require_client_cert && ++ request->set->ssl_username_from_cert && ++ !request->cert_username) { ++ auth_request_handler_auth_fail(handler, request, ++ "SSL certificate didn't contain username"); ++ return TRUE; ++ } ++ + /* Empty initial response is a "=" base64 string. Completely empty + string shouldn't really be sent, but at least Exim does it, + so just allow it for backwards compatibility.. */ diff --git a/SOURCES/dovecot-2.2.36-cve_2019_3814part3of3.patch b/SOURCES/dovecot-2.2.36-cve_2019_3814part3of3.patch new file mode 100644 index 0000000..7e1a13c --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve_2019_3814part3of3.patch @@ -0,0 +1,22 @@ +From e5d428297d70e3ac8b6dfce7e0de182b86825082 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +Date: Wed, 16 Jan 2019 18:28:57 +0200 +Subject: [PATCH] auth: Do not import empty certificate username + +--- + src/auth/auth-request.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/auth/auth-request.c b/src/auth/auth-request.c +index dd288b6d23..1cb665ec8c 100644 +--- a/src/auth/auth-request.c ++++ b/src/auth/auth-request.c +@@ -445,7 +445,7 @@ bool auth_request_import_auth(struct auth_request *request, + else if (strcmp(key, "valid-client-cert") == 0) + request->valid_client_cert = TRUE; + else if (strcmp(key, "cert_username") == 0) { +- if (request->set->ssl_username_from_cert) { ++ if (request->set->ssl_username_from_cert && *value != '\0') { + /* get username from SSL certificate. it overrides + the username given by the auth mechanism. */ + request->user = p_strdup(request->pool, value); diff --git a/SOURCES/dovecot-2.2.36-cve_2019_7524part1of2.patch b/SOURCES/dovecot-2.2.36-cve_2019_7524part1of2.patch new file mode 100644 index 0000000..8176880 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve_2019_7524part1of2.patch @@ -0,0 +1,32 @@ +From fcd786753b2ba6b4fb82cc2affea8e0d61889c95 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 4 Feb 2019 19:23:02 -0800 +Subject: [PATCH] lib-storage: Fix buffer overflow when reading oversized + hdr-pop3-uidl header + +--- + src/lib-storage/index/index-pop3-uidl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/lib-storage/index/index-pop3-uidl.c b/src/lib-storage/index/index-pop3-uidl.c +index 13b7363ef6..e537e9ff51 100644 +--- a/src/lib-storage/index/index-pop3-uidl.c ++++ b/src/lib-storage/index/index-pop3-uidl.c +@@ -37,7 +37,7 @@ bool index_pop3_uidl_can_exist(struct mail *mail) + /* this header isn't set yet */ + return TRUE; + } +- memcpy(&uidl, data, size); ++ memcpy(&uidl, data, sizeof(uidl)); + return mail->uid <= uidl.max_uid_with_pop3_uidl; + } + +@@ -95,7 +95,7 @@ void index_pop3_uidl_update_exists_finish(struct mailbox_transaction_context *tr + + /* check if we have already the same header */ + if (size >= sizeof(uidl)) { +- memcpy(&uidl, data, size); ++ memcpy(&uidl, data, sizeof(uidl)); + if (trans->highest_pop3_uidl_uid == uidl.max_uid_with_pop3_uidl) + return; + } diff --git a/SOURCES/dovecot-2.2.36-cve_2019_7524part2of2.patch b/SOURCES/dovecot-2.2.36-cve_2019_7524part2of2.patch new file mode 100644 index 0000000..878a4aa --- /dev/null +++ b/SOURCES/dovecot-2.2.36-cve_2019_7524part2of2.patch @@ -0,0 +1,22 @@ +From df17cee615377f2474c86eb6a5b3fe5caa8b70fe Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 4 Feb 2019 19:25:13 -0800 +Subject: [PATCH] fts: Fix buffer overflow when reading oversized fts header + +--- + src/plugins/fts/fts-api.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/fts/fts-api.c b/src/plugins/fts/fts-api.c +index 5a5b2a919e..4f8a1c125d 100644 +--- a/src/plugins/fts/fts-api.c ++++ b/src/plugins/fts/fts-api.c +@@ -425,7 +425,7 @@ bool fts_index_get_header(struct mailbox *box, struct fts_index_header *hdr_r) + i_zero(hdr_r); + ret = FALSE; + } else { +- memcpy(hdr_r, data, data_size); ++ memcpy(hdr_r, data, sizeof(*hdr_r)); + ret = TRUE; + } + mail_index_view_close(&view); diff --git a/SOURCES/dovecot-2.2.36-portreserve.patch b/SOURCES/dovecot-2.2.36-portreserve.patch new file mode 100644 index 0000000..f235bc8 --- /dev/null +++ b/SOURCES/dovecot-2.2.36-portreserve.patch @@ -0,0 +1,11 @@ +diff -up dovecot-2.2.36/dovecot.service.in.portreserve dovecot-2.2.36/dovecot.service.in +--- dovecot-2.2.36/dovecot.service.in.portreserve 2019-08-13 13:16:17.660982741 +0200 ++++ dovecot-2.2.36/dovecot.service.in 2019-08-13 13:16:17.664982728 +0200 +@@ -13,6 +13,7 @@ After=local-fs.target network.target net + [Service] + Type=forking + ExecStartPre=/usr/libexec/dovecot/prestartscript ++ExecStartPre=-/usr/sbin/portrelease dovecot + ExecStart=@sbindir@/dovecot + PIDFile=@rundir@/master.pid + ExecReload=@bindir@/doveadm reload diff --git a/SOURCES/dovecot-2.2.9-nodevrand.patch b/SOURCES/dovecot-2.2.9-nodevrand.patch index 53f628c..4f91c00 100644 --- a/SOURCES/dovecot-2.2.9-nodevrand.patch +++ b/SOURCES/dovecot-2.2.9-nodevrand.patch @@ -2,16 +2,16 @@ diff -up dovecot-2.2.9/src/lib-master/master-service.c.fixit dovecot-2.2.9/src/l --- dovecot-2.2.9/src/lib-master/master-service.c.fixit 2013-11-24 14:37:39.000000000 +0100 +++ dovecot-2.2.9/src/lib-master/master-service.c 2013-11-27 17:52:48.802843395 +0100 @@ -559,6 +559,11 @@ const char *master_service_get_name(stru - return service->name; + return service->name; } - + +const enum master_service_flags master_service_get_flags(struct master_service *service) +{ + return service->flags; +} + void master_service_run(struct master_service *service, - master_service_connection_callback_t *callback) + master_service_connection_callback_t *callback) { diff -up dovecot-2.2.9/src/lib-master/master-service.h.fixit dovecot-2.2.9/src/lib-master/master-service.h --- dovecot-2.2.9/src/lib-master/master-service.h.fixit 2013-11-24 14:37:39.000000000 +0100 @@ -19,23 +19,23 @@ diff -up dovecot-2.2.9/src/lib-master/master-service.h.fixit dovecot-2.2.9/src/l @@ -134,6 +134,8 @@ const char *master_service_get_version_s /* Returns name of the service, as given in name parameter to _init(). */ const char *master_service_get_name(struct master_service *service); - + +const enum master_service_flags master_service_get_flags(struct master_service *service); + /* Start the service. Blocks until finished */ void master_service_run(struct master_service *service, - master_service_connection_callback_t *callback) + master_service_connection_callback_t *callback) diff -up dovecot-2.2.9/src/ssl-params/main.c.fixit dovecot-2.2.9/src/ssl-params/main.c --- dovecot-2.2.9/src/ssl-params/main.c.fixit 2013-11-24 14:37:39.000000000 +0100 +++ dovecot-2.2.9/src/ssl-params/main.c 2013-11-27 17:51:06.664694558 +0100 @@ -103,7 +103,10 @@ static void sig_chld(const siginfo_t *si - if (waitpid(-1, &status, WNOHANG) < 0) - i_error("waitpid() failed: %m"); - else if (status != 0) + if (waitpid(-1, &status, WNOHANG) < 0) + i_error("waitpid() failed: %m"); + else if (status != 0) + { - i_error("child process failed with status %d", status); + i_error("child process failed with status %d", status); + if(master_service_get_flags(master_service) & MASTER_SERVICE_FLAG_STANDALONE) exit(1); + } - else { - /* params should have been created now. try refreshing. */ - ssl_params_refresh(param); + else { + /* params should have been created now. try refreshing. */ + ssl_params_refresh(param); 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..a45bcdc --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12100.patch @@ -0,0 +1,2432 @@ +diff -up dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c.CVE_2020_12100 dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c +--- dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c 2020-08-08 13:57:49.295662113 +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.2.36/src/lib-imap/test-imap-bodystructure.c.CVE_2020_12100 dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c +--- dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c 2020-08-08 13:57:49.295662113 +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.2.36/src/lib-imap/test-imap-envelope.c.CVE_2020_12100 dovecot-2.2.36/src/lib-imap/test-imap-envelope.c +--- dovecot-2.2.36/src/lib-imap/test-imap-envelope.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-imap/test-imap-envelope.c 2020-08-08 13:57:49.295662113 +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.2.36/src/lib-mail/istream-attachment-extractor.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c +--- dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c 2020-08-08 13:59:10.665535546 +0200 +@@ -691,6 +691,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); +@@ -717,9 +721,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)); + } +diff -up dovecot-2.2.36/src/lib-mail/istream-binary-converter.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/istream-binary-converter.c +--- dovecot-2.2.36/src/lib-mail/istream-binary-converter.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/istream-binary-converter.c 2020-08-08 14:00:06.560761661 +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)); + } +diff -up dovecot-2.2.36/src/lib-mail/Makefile.am.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/Makefile.am +--- dovecot-2.2.36/src/lib-mail/Makefile.am.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/Makefile.am 2020-08-08 13:57:49.296662100 +0200 +@@ -27,6 +27,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 \ +@@ -41,7 +42,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.2.36/src/lib-mail/message-parser.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser.c +--- dovecot-2.2.36/src/lib-mail/message-parser.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/message-parser.c 2020-08-08 14:06:41.696290855 +0200 +@@ -1,53 +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; +- +- unsigned int 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); +- +- unsigned int part_seen_content_type:1; +- unsigned int multipart:1; +- unsigned int eof:1; +-}; ++#include "message-parser-private.h" + + message_part_header_callback_t *null_message_part_header_callback = NULL; + +@@ -57,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; + +@@ -76,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; + } +@@ -121,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; + +@@ -167,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 = +@@ -187,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); + } + +@@ -238,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; + +@@ -263,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); +@@ -312,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. */ +@@ -348,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); +@@ -509,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; + } + } +@@ -532,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) + +@@ -556,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. +@@ -633,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) { +@@ -661,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) ++ } 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; + +@@ -691,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; +@@ -1050,31 +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->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; + } + +@@ -1099,8 +790,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; + } +@@ -1132,13 +830,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.2.36/src/lib-mail/message-parser-from-parts.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c +--- dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c.CVE_2020_12100 2020-08-08 13:57:49.296662100 +0200 ++++ dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c 2020-08-08 13:57:49.296662100 +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.2.36/src/lib-mail/message-parser.h.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser.h +--- dovecot-2.2.36/src/lib-mail/message-parser.h.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/message-parser.h 2020-08-08 14:07:36.351534219 +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,14 +60,12 @@ 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); + /* Use preparsed parts to speed up parsing. */ + 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); + /* Returns 0 if parts were returned, -1 we used preparsed parts and they + didn't match the current message */ + int message_parser_deinit(struct message_parser_ctx **ctx, +diff -up dovecot-2.2.36/src/lib-mail/message-parser-private.h.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser-private.h +--- dovecot-2.2.36/src/lib-mail/message-parser-private.h.CVE_2020_12100 2020-08-08 13:57:49.297662085 +0200 ++++ dovecot-2.2.36/src/lib-mail/message-parser-private.h 2020-08-08 13:57:49.297662085 +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.2.36/src/lib-mail/message-search.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-search.c +--- dovecot-2.2.36/src/lib-mail/message-search.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/message-search.c 2020-08-08 13:57:49.297662085 +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.2.36/src/lib-mail/message-snippet.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-snippet.c +--- dovecot-2.2.36/src/lib-mail/message-snippet.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/message-snippet.c 2020-08-08 13:57:49.297662085 +0200 +@@ -95,6 +95,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; +@@ -108,7 +109,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.2.36/src/lib-mail/test-message-decoder.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/test-message-decoder.c +--- dovecot-2.2.36/src/lib-mail/test-message-decoder.c.CVE_2020_12100 2020-08-08 13:57:49.294662127 +0200 ++++ dovecot-2.2.36/src/lib-mail/test-message-decoder.c 2020-08-08 13:57:49.297662085 +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.2.36/src/lib-mail/test-message-parser.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/test-message-parser.c +--- dovecot-2.2.36/src/lib-mail/test-message-parser.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/test-message-parser.c 2020-08-08 14:17:25.070385831 +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) +@@ -111,9 +116,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); +@@ -129,7 +136,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) +@@ -147,8 +154,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) +@@ -166,6 +176,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[] = +@@ -190,12 +230,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + + 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); +@@ -219,6 +260,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); + } +@@ -253,12 +295,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + + 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); +@@ -266,6 +309,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); +@@ -275,6 +319,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); +@@ -306,11 +351,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + ++ 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); +@@ -343,11 +389,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + ++ 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); +@@ -387,11 +434,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + ++ 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); +@@ -399,6 +447,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); +@@ -407,6 +456,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); +@@ -445,11 +495,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + ++ 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); +@@ -457,6 +508,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); +@@ -465,6 +517,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); +@@ -480,6 +533,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[] = +@@ -503,11 +601,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); + test_assert(message_parser_deinit(&parser, &parts) == 0); + ++ 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); +@@ -515,6 +614,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); +@@ -523,6 +623,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); +@@ -562,12 +663,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); +@@ -577,6 +679,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); +@@ -590,6 +693,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); +@@ -600,6 +704,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); +@@ -614,6 +719,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"; +@@ -627,7 +806,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'); +@@ -642,19 +821,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 (*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.2.36/src/lib-mail/test-message-part.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/test-message-part.c +--- dovecot-2.2.36/src/lib-mail/test-message-part.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/test-message-part.c 2020-08-08 13:57:49.297662085 +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.2.36/src/lib-storage/index/index-mail-headers.c.CVE_2020_12100 dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c +--- dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c 2020-08-08 13:57:49.298662071 +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) +@@ -399,7 +399,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; + } +@@ -422,14 +422,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); + } + } + +@@ -462,7 +460,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) +@@ -517,7 +515,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.2.36/src/plugins/fts/fts-build-mail.c.CVE_2020_12100 dovecot-2.2.36/src/plugins/fts/fts-build-mail.c +--- dovecot-2.2.36/src/plugins/fts/fts-build-mail.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/plugins/fts/fts-build-mail.c 2020-08-08 14:18:31.289469311 +0200 +@@ -458,6 +458,9 @@ static int + fts_build_mail_real(struct fts_backend_update_context *update_ctx, + struct mail *mail) + { ++ 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; +@@ -485,9 +488,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..9898429 --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12100prereq.patch @@ -0,0 +1,121 @@ +diff -up dovecot-2.2.36/src/lib/array.h.CVE_2020_12100prereq dovecot-2.2.36/src/lib/array.h +--- dovecot-2.2.36/src/lib/array.h.CVE_2020_12100prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib/array.h 2020-08-09 20:22:36.326347911 +0200 +@@ -219,6 +219,15 @@ array_idx_i(const struct array *array, u + i_assert(idx * array->element_size < array->buffer->used); + return CONST_PTR_OFFSET(array->buffer->data, idx * array->element_size); + } ++ ++#define array_front(array) array_idx(array, 0) ++#define array_front_modifiable(array) array_idx_modifiable(array, 0) ++#define array_back(array) array_idx(array, array_count(array)-1) ++#define array_back_modifiable(array) array_idx_modifiable(array, array_count(array)-1) ++#define array_pop_back(array) array_delete(array, array_count(array)-1, 1); ++#define array_push_back(array, item) array_append(array, (item), 1) ++#define array_pop_front(array) array_delete(array, 0, 1) ++#define array_push_front(array, item) array_insert(array, 0, (item), 1) + #define array_idx(array, idx) \ + ARRAY_TYPE_CAST_CONST(array)array_idx_i(&(array)->arr, idx) + +diff -up dovecot-2.2.36/src/lib/buffer.c.CVE_2020_12100prereq dovecot-2.2.36/src/lib/buffer.c +--- dovecot-2.2.36/src/lib/buffer.c.CVE_2020_12100prereq 2020-08-09 20:22:57.566465762 +0200 ++++ dovecot-2.2.36/src/lib/buffer.c 2020-08-09 20:22:57.580465840 +0200 +@@ -148,6 +148,10 @@ void buffer_free(buffer_t **_buf) + { + struct real_buffer *buf = (struct real_buffer *)*_buf; + ++ ++ if (buf == NULL) ++ return; ++ + *_buf = NULL; + if (buf->alloced) + p_free(buf->pool, buf->w_buffer); +diff -up dovecot-2.2.36/src/lib-mail/test-message-decoder.c.CVE_2020_12100prereq dovecot-2.2.36/src/lib-mail/test-message-decoder.c +--- dovecot-2.2.36/src/lib-mail/test-message-decoder.c.CVE_2020_12100prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib-mail/test-message-decoder.c 2020-08-09 20:22:36.326347911 +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 (*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..63e5924 --- /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 1; + ++ if (length > data_size) { ++ *error = "buffer length out of bounds"; ++ return 0; ++ } ++ + if (offset >= data_size) { + *error = "buffer offset out of bounds"; + return 0; +-- +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..492c58c --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12674.patch @@ -0,0 +1,237 @@ +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,199 @@ ++/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ ++ ++#include "lib.h" ++#include "auth.h" ++#include "array.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) ++{ ++ 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 ++ }; ++ lib_init(); ++ test_mechs_init(); ++ int ret = test_run(test_functions); ++ mech_register_deinit(&mech_reg); ++ return ret; ++} 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..433e84a --- /dev/null +++ b/SOURCES/dovecot-2.3.8-CVE_2020_12674prereq.patch @@ -0,0 +1,818 @@ +diff -up dovecot-2.2.36/src/auth/auth-request.c.CVE_2020_12674prereq dovecot-2.2.36/src/auth/auth-request.c +--- dovecot-2.2.36/src/auth/auth-request.c.CVE_2020_12674prereq 2020-08-09 14:37:53.468591087 +0200 ++++ dovecot-2.2.36/src/auth/auth-request.c 2020-08-09 14:37:53.482591165 +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" +@@ -68,9 +69,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 +@@ -211,10 +209,12 @@ void auth_request_success_continue(struc + 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); +@@ -228,8 +228,10 @@ void auth_request_fail(struct auth_reque + + 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); +@@ -989,7 +991,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); +@@ -1036,7 +1038,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; +@@ -1046,9 +1049,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; +diff -up dovecot-2.2.36/src/auth/auth-request-handler.c.CVE_2020_12674prereq dovecot-2.2.36/src/auth/auth-request-handler.c +--- dovecot-2.2.36/src/auth/auth-request-handler.c.CVE_2020_12674prereq 2020-08-09 14:37:53.467591082 +0200 ++++ dovecot-2.2.36/src/auth/auth-request-handler.c 2020-08-09 14:37:53.482591165 +0200 +@@ -16,32 +16,29 @@ + #include "auth-token.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; +- +- unsigned int destroyed:1; +- unsigned int 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, +@@ -60,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; + } + +@@ -342,6 +345,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; +@@ -394,6 +407,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); + } +@@ -657,6 +678,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.2.36/src/auth/auth-request-handler.h.CVE_2020_12674prereq dovecot-2.2.36/src/auth/auth-request-handler.h +--- dovecot-2.2.36/src/auth/auth-request-handler.h.CVE_2020_12674prereq 2017-06-23 13:18:28.000000000 +0200 ++++ dovecot-2.2.36/src/auth/auth-request-handler.h 2020-08-09 14:37:53.482591165 +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.2.36/src/auth/auth-request-handler-private.h.CVE_2020_12674prereq dovecot-2.2.36/src/auth/auth-request-handler-private.h +--- dovecot-2.2.36/src/auth/auth-request-handler-private.h.CVE_2020_12674prereq 2020-08-09 14:37:53.482591165 +0200 ++++ dovecot-2.2.36/src/auth/auth-request-handler-private.h 2020-08-09 14:37:53.482591165 +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.2.36/src/auth/auth-request.h.CVE_2020_12674prereq dovecot-2.2.36/src/auth/auth-request.h +--- dovecot-2.2.36/src/auth/auth-request.h.CVE_2020_12674prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/auth/auth-request.h 2020-08-09 14:37:53.482591165 +0200 +@@ -269,6 +269,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 -up dovecot-2.2.36/src/auth/Makefile.am.CVE_2020_12674prereq dovecot-2.2.36/src/auth/Makefile.am +--- dovecot-2.2.36/src/auth/Makefile.am.CVE_2020_12674prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/auth/Makefile.am 2020-08-09 14:37:53.482591165 +0200 +@@ -227,7 +227,8 @@ test_programs = \ + test-auth-cache \ + test-auth-request-var-expand \ + test-username-filter \ +- test-db-dict ++ test-db-dict \ ++ test-mech + + noinst_PROGRAMS = $(test_programs) + +@@ -253,6 +254,13 @@ test_db_dict_SOURCES = test-db-dict.c + test_db_dict_LDADD = $(test_libs) libauth.la + test_db_dict_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: check-am check-test + check-test: all-am + for bin in $(test_programs); do \ +diff -up dovecot-2.2.36/src/auth/passdb.h.CVE_2020_12674prereq dovecot-2.2.36/src/auth/passdb.h +--- dovecot-2.2.36/src/auth/passdb.h.CVE_2020_12674prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/auth/passdb.h 2020-08-09 14:37:53.482591165 +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.2.36/src/auth/test-auth.h.CVE_2020_12674prereq dovecot-2.2.36/src/auth/test-auth.h +--- dovecot-2.2.36/src/auth/test-auth.h.CVE_2020_12674prereq 2020-08-09 14:38:24.950765769 +0200 ++++ dovecot-2.2.36/src/auth/test-auth.h 2020-08-09 14:38:06.304662311 +0200 +@@ -0,0 +1,21 @@ ++/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ ++ ++#ifndef TEST_AUTH_H ++#define TEST_AUTH_H 1 ++ ++#include "lib.h" ++#include "test-common.h" ++ ++struct auth_passdb; ++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); ++void test_db_lua(void); ++struct auth_passdb *passdb_mock(void); ++void passdb_mock_mod_init(void); ++void passdb_mock_mod_deinit(void); ++ ++#endif ++ +diff -up dovecot-2.2.36/src/auth/test-mock.c.CVE_2020_12674prereq dovecot-2.2.36/src/auth/test-mock.c +--- dovecot-2.2.36/src/auth/test-mock.c.CVE_2020_12674prereq 2020-08-09 14:37:53.483591170 +0200 ++++ dovecot-2.2.36/src/auth/test-mock.c 2020-08-09 14:37:53.483591170 +0200 +@@ -0,0 +1,109 @@ ++/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ ++ ++#include "test-auth.h" ++#include "auth-common.h" ++#include "passdb.h" ++ ++struct auth_penalty *auth_penalty; ++time_t process_start_time; ++bool worker, worker_restart_request; ++static struct passdb_module *mock_passdb_mod = NULL; ++static pool_t mock_pool; ++ ++void auth_module_load(const char *names ATTR_UNUSED) ++{ ++} ++void auth_refresh_proctitle(void) { ++} ++ ++static void passdb_mock_init(struct passdb_module *module ATTR_UNUSED) ++{ ++} ++static void passdb_mock_deinit(struct passdb_module *module ATTR_UNUSED) ++{ ++} ++static void passdb_mock_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED, ++ verify_plain_callback_t *callback) ++{ ++ 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, ++}; ++ ++struct auth_passdb_settings mock_passdb_set = { ++ .name = "mock", ++ .driver = "mock", ++ .args = "", ++ .default_fields = "", ++ .override_fields = "", ++ .mechanisms = "", ++ .username_filter = "", ++ .skip = "never", ++ .result_success = "return-ok", ++ .result_failure = "continue", ++ .result_internalfail = "continue", ++ .deny = FALSE, ++ .pass = FALSE, ++ .master = FALSE, ++ .auth_verbose = "default" ++}; ++ ++void passdb_mock_mod_init(void) ++{ ++ if (mock_passdb_mod != NULL) ++ return; ++ ++ mock_pool = pool_allocfree_create("auth mock"); ++ ++ passdb_register_module(&mock_interface); ++ ++ struct auth_passdb_settings set = { ++ .name = "mock", ++ .driver = "mock", ++ .args = "", ++ .default_fields = "", ++ .override_fields = "", ++ .mechanisms = "", ++ .username_filter = "", ++ ++ .skip = "never", ++ .result_success = "return-ok", ++ .result_failure = "continue", ++ .result_internalfail = "continue", ++ ++ .deny = FALSE, ++ .pass = FALSE, ++ .master = FALSE, ++ .auth_verbose = "default" ++ }; ++ mock_passdb_mod = passdb_preinit(mock_pool, &set); ++ passdb_init(mock_passdb_mod); ++} ++ ++void passdb_mock_mod_deinit(void) ++{ ++ passdb_deinit(mock_passdb_mod); ++ passdb_unregister_module(&mock_interface); ++ pool_unref(&mock_pool); ++} ++ ++struct auth_passdb *passdb_mock(void) ++{ ++ struct auth_passdb *ret = i_new(struct auth_passdb, 1); ++ ret->set = &mock_passdb_set; ++ ret->passdb = mock_passdb_mod; ++ return ret; ++} +diff -up dovecot-2.2.36/src/lib/lib.c.CVE_2020_12674prereq dovecot-2.2.36/src/lib/lib.c +--- dovecot-2.2.36/src/lib/lib.c.CVE_2020_12674prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib/lib.c 2020-08-09 14:37:53.483591170 +0200 +@@ -175,7 +175,7 @@ void lib_init(void) + hostpid_init(); + lib_open_non_stdio_dev_null(); + var_expand_extensions_init(); +- ++ random_init(); + lib_initialized = TRUE; + } + +diff -up dovecot-2.2.36/src/lib/lib.h.CVE_2020_12674prereq dovecot-2.2.36/src/lib/lib.h +--- dovecot-2.2.36/src/lib/lib.h.CVE_2020_12674prereq 2020-08-24 14:42:44.084069002 +0200 ++++ dovecot-2.2.36/src/lib/lib.h 2020-08-24 14:42:44.108068770 +0200 +@@ -33,6 +33,7 @@ + #include "imem.h" + #include "byteorder.h" + #include "rand.h" ++#include "randgen.h" + + typedef struct buffer buffer_t; + typedef struct buffer string_t; +diff -up dovecot-2.2.36/src/lib/Makefile.am.CVE_2020_12674prereq dovecot-2.2.36/src/lib/Makefile.am +--- dovecot-2.2.36/src/lib/Makefile.am.CVE_2020_12674prereq 2020-08-09 14:37:53.464591065 +0200 ++++ dovecot-2.2.36/src/lib/Makefile.am 2020-08-09 14:37:53.483591170 +0200 +@@ -98,6 +98,7 @@ liblib_la_SOURCES = \ + md4.c \ + md5.c \ + mempool.c \ ++ mempool-allocfree.c \ + mempool-alloconly.c \ + mempool-datastack.c \ + mempool-system.c \ +diff -up dovecot-2.2.36/src/lib/mempool-allocfree.c.CVE_2020_12674prereq dovecot-2.2.36/src/lib/mempool-allocfree.c +--- dovecot-2.2.36/src/lib/mempool-allocfree.c.CVE_2020_12674prereq 2020-08-09 14:37:53.483591170 +0200 ++++ dovecot-2.2.36/src/lib/mempool-allocfree.c 2020-08-09 14:37:53.483591170 +0200 +@@ -0,0 +1,328 @@ ++/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ ++ ++/* @UNSAFE: whole file */ ++#include "lib.h" ++#include "safe-memset.h" ++#include "mempool.h" ++#include "llist.h" ++ ++/* ++ * As the name implies, allocfree pools support both allocating and freeing ++ * memory. ++ * ++ * Implementation ++ * ============== ++ * ++ * Each allocfree pool contains a pool structure (struct allocfree_pool) to ++ * keep track of allocfree-specific pool information and zero or more blocks ++ * (struct pool_block) that keep track of ranges of memory used to back the ++ * allocations. The blocks are kept in a doubly-linked list used to keep ++ * track of all allocations that belong to the pool. ++ * ++ * +-----------+ ++ * | allocfree | ++ * | pool | ++ * +-----+-----+ ++ * | ++ * | blocks +------------+ next +------------+ next ++ * \------->| pool block |<=====>| pool block |<=====>...<====> NULL ++ * +------------+ prev +------------+ prev ++ * | | | | ++ * . . ++ * . . ++ * . | | ++ * . +------------+ ++ * | | ++ * +------------+ ++ * ++ * Creation ++ * -------- ++ * ++ * When an allocfree pool is created the linked list of allocated blocks is ++ * initialized to be empty. ++ * ++ * Allocation & Freeing ++ * -------------------- ++ * ++ * Since each allocation (via p_malloc()) corresponds to one block, ++ * allocations are simply a matter of: ++ * ++ * - allocating enough memory from the system heap (via calloc()) to hold ++ * the block header and the requested number of bytes, ++ * - making a note of the user-requested size in the block header, ++ * - adding the new block to the pool's linked list of blocks, and ++ * - returning a pointer to the payload area of the block to the caller. ++ * ++ * Freeing memory is simpler. The passed in pointer is converted to a ++ * struct pool_block pointer. Then the block is removed from the pool's ++ * linked list and free()d. ++ * ++ * If the pool was created via pool_allocfree_create_clean(), all blocks are ++ * safe_memset() to zero just before being free()d. ++ * ++ * Reallocation ++ * ------------ ++ * ++ * Reallocation is done by calling realloc() with a new size that is large ++ * enough to cover the requested number of bytes plus the block header ++ * overhead. ++ * ++ * Clearing ++ * -------- ++ * ++ * Clearing the pool is supposed to return the pool to the same state it was ++ * in when it was first created. To that end, the allocfree pool frees all ++ * the blocks allocated since the pool's creation. In other words, clearing ++ * is equivalent to (but faster than) calling p_free() for each allocation ++ * in the pool. ++ * ++ * Finally, if the pool was created via pool_allocfree_create_clean(), all ++ * blocks are safe_memset() to zero before being free()d. ++ * ++ * Destruction ++ * ----------- ++ * ++ * Destroying a pool first clears it (see above) and then the pool structure ++ * itself is safe_memset() to zero (if pool_allocfree_create_clean() was ++ * used) and free()d. (The clearing leaves the pool in a minimal state ++ * with no blocks allocated.) ++ */ ++ ++struct allocfree_pool { ++ struct pool pool; ++ int refcount; ++ size_t total_alloc_count; ++ size_t total_alloc_used; ++ ++ struct pool_block *blocks; ++#ifdef DEBUG ++ char *name; ++#endif ++ bool clean_frees; ++}; ++ ++struct pool_block { ++ struct pool_block *prev,*next; ++ ++ size_t size; ++ unsigned char *block; ++}; ++ ++#define SIZEOF_ALLOCFREE_POOL MEM_ALIGN(sizeof(struct allocfree_pool)) ++#define SIZEOF_POOLBLOCK (MEM_ALIGN(sizeof(struct pool_block))) ++ ++static const char *pool_allocfree_get_name(pool_t pool); ++static void pool_allocfree_ref(pool_t pool); ++static void pool_allocfree_unref(pool_t *pool); ++static void *pool_allocfree_malloc(pool_t pool, size_t size); ++static void pool_allocfree_free(pool_t pool, void *mem); ++static void *pool_allocfree_realloc(pool_t pool, void *mem, ++ size_t old_size, size_t new_size); ++static void pool_allocfree_clear(pool_t pool); ++static size_t pool_allocfree_get_max_easy_alloc_size(pool_t pool); ++ ++static const struct pool_vfuncs static_allocfree_pool_vfuncs = { ++ pool_allocfree_get_name, ++ ++ pool_allocfree_ref, ++ pool_allocfree_unref, ++ ++ pool_allocfree_malloc, ++ pool_allocfree_free, ++ ++ pool_allocfree_realloc, ++ ++ pool_allocfree_clear, ++ pool_allocfree_get_max_easy_alloc_size ++}; ++ ++static const struct pool static_allocfree_pool = { ++ .v = &static_allocfree_pool_vfuncs, ++ ++ .alloconly_pool = FALSE, ++ .datastack_pool = FALSE ++}; ++ ++pool_t pool_allocfree_create(const char *name ATTR_UNUSED) ++{ ++ struct allocfree_pool *pool; ++ ++ if (SIZEOF_POOLBLOCK > (SSIZE_T_MAX - POOL_MAX_ALLOC_SIZE)) ++ i_panic("POOL_MAX_ALLOC_SIZE is too large"); ++ ++ pool = calloc(1, SIZEOF_ALLOCFREE_POOL); ++ if (pool == NULL) ++ i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %"PRIuSIZE_T"): Out of memory", ++ SIZEOF_ALLOCFREE_POOL); ++#ifdef DEBUG ++ pool->name = strdup(name); ++#endif ++ pool->pool = static_allocfree_pool; ++ pool->refcount = 1; ++ return &pool->pool; ++} ++ ++pool_t pool_allocfree_create_clean(const char *name) ++{ ++ struct allocfree_pool *apool; ++ pool_t pool; ++ ++ pool = pool_allocfree_create(name); ++ apool = (struct allocfree_pool *)pool; ++ apool->clean_frees = TRUE; ++ return pool; ++} ++ ++static void pool_allocfree_destroy(struct allocfree_pool *apool) ++{ ++ pool_allocfree_clear(&apool->pool); ++ if (apool->clean_frees) ++ safe_memset(apool, 0, SIZEOF_ALLOCFREE_POOL); ++#ifdef DEBUG ++ free(apool->name); ++#endif ++ free(apool); ++} ++ ++static const char *pool_allocfree_get_name(pool_t pool ATTR_UNUSED) ++{ ++#ifdef DEBUG ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ return apool->name; ++#else ++ return "alloc"; ++#endif ++} ++ ++static void pool_allocfree_ref(pool_t pool) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ i_assert(apool->refcount > 0); ++ ++ apool->refcount++; ++} ++ ++static void pool_allocfree_unref(pool_t *_pool) ++{ ++ pool_t pool = *_pool; ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ i_assert(apool->refcount > 0); ++ ++ /* erase the pointer before freeing anything, as the pointer may ++ exist inside the pool's memory area */ ++ *_pool = NULL; ++ ++ if (--apool->refcount > 0) ++ return; ++ ++ pool_allocfree_destroy(apool); ++} ++ ++static void *pool_block_attach(struct allocfree_pool *apool, struct pool_block *block) ++{ ++ i_assert(block->size > 0); ++ DLLIST_PREPEND(&apool->blocks, block); ++ block->block = PTR_OFFSET(block,SIZEOF_POOLBLOCK); ++ apool->total_alloc_used += block->size; ++ apool->total_alloc_count++; ++ return block->block; ++} ++ ++static struct pool_block * ++pool_block_detach(struct allocfree_pool *apool, unsigned char *mem) ++{ ++ struct pool_block *block = PTR_OFFSET(mem, -SIZEOF_POOLBLOCK); ++ ++ /* make sure the block we are dealing with is correct */ ++ i_assert(block->block == mem); ++ i_assert((block->prev == NULL || block->prev->next == block) && ++ (block->next == NULL || block->next->prev == block)); ++ ++ i_assert(apool->total_alloc_used >= block->size); ++ i_assert(apool->total_alloc_count > 0); ++ DLLIST_REMOVE(&apool->blocks, block); ++ apool->total_alloc_used -= block->size; ++ apool->total_alloc_count--; ++ ++ return block; ++} ++ ++static void *pool_allocfree_malloc(pool_t pool, size_t size) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ ++ struct pool_block *block = calloc(1, SIZEOF_POOLBLOCK + size); ++ if (block == NULL) ++ i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %"PRIuSIZE_T"): Out of memory", ++ SIZEOF_POOLBLOCK + size); ++ block->size = size; ++ return pool_block_attach(apool, block); ++} ++ ++static void pool_allocfree_free(pool_t pool, void *mem) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ struct pool_block *block = pool_block_detach(apool, mem); ++ if (apool->clean_frees) ++ safe_memset(block, 0, SIZEOF_POOLBLOCK+block->size); ++ free(block); ++} ++ ++static void *pool_allocfree_realloc(pool_t pool, void *mem, ++ size_t old_size, size_t new_size) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ unsigned char *new_mem; ++ ++ struct pool_block *block = pool_block_detach(apool, mem); ++ if ((new_mem = realloc(block, SIZEOF_POOLBLOCK+new_size)) == NULL) ++ i_fatal_status(FATAL_OUTOFMEM, "realloc(block, %"PRIuSIZE_T")", ++ SIZEOF_POOLBLOCK+new_size); ++ ++ /* zero out new memory */ ++ if (new_size > old_size) ++ memset(new_mem + SIZEOF_POOLBLOCK + old_size, 0, ++ new_size - old_size); ++ block = (struct pool_block*)new_mem; ++ block->size = new_size; ++ return pool_block_attach(apool, block); ++} ++ ++static void pool_allocfree_clear(pool_t pool) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ struct pool_block *block, *next; ++ ++ for (block = apool->blocks; block != NULL; block = next) { ++ next = block->next; ++ pool_allocfree_free(pool, block->block); ++ } ++ i_assert(apool->total_alloc_used == 0 && apool->total_alloc_count == 0); ++} ++ ++static size_t pool_allocfree_get_max_easy_alloc_size(pool_t pool ATTR_UNUSED) ++{ ++ return 0; ++} ++ ++size_t pool_allocfree_get_total_used_size(pool_t pool) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ return apool->total_alloc_used; ++} ++ ++size_t pool_allocfree_get_total_alloc_size(pool_t pool) ++{ ++ struct allocfree_pool *apool = ++ container_of(pool, struct allocfree_pool, pool); ++ return apool->total_alloc_used + ++ SIZEOF_POOLBLOCK*apool->total_alloc_count + sizeof(*apool); ++} +diff -up dovecot-2.2.36/src/lib/mempool.h.CVE_2020_12674prereq dovecot-2.2.36/src/lib/mempool.h +--- dovecot-2.2.36/src/lib/mempool.h.CVE_2020_12674prereq 2018-04-30 15:52:05.000000000 +0200 ++++ dovecot-2.2.36/src/lib/mempool.h 2020-08-09 14:37:53.483591170 +0200 +@@ -10,6 +10,11 @@ + pools to disable the warning. */ + #define MEMPOOL_GROWING "GROWING-" + ++/* The maximum allocation size that's allowed. Anything larger than that ++ will panic. No pool ever should need more than 4kB of overhead per ++ allocation. */ ++#define POOL_MAX_ALLOC_SIZE (SSIZE_T_MAX - 4096) ++ + /* Memory allocated and reallocated (the new data in it) in pools is always + zeroed, it will cost only a few CPU cycles and may well save some debug + time. */ +@@ -63,6 +68,10 @@ pool_t pool_alloconly_create(const char + needed. */ + pool_t pool_alloconly_create_clean(const char *name, size_t size); + ++/* Create new alloc pool. This is very similar to system pool, but it ++ will deallocate all memory on deinit. */ ++pool_t pool_allocfree_create(const char *name); ++ + /* When allocating memory from returned pool, the data stack frame must be + the same as it was when calling this function. pool_unref() also checks + that the stack frame is the same. This should make it quite safe to use. */ diff --git a/SOURCES/dovecot.init b/SOURCES/dovecot.init index d7880b5..49206f0 100755 --- a/SOURCES/dovecot.init +++ b/SOURCES/dovecot.init @@ -17,7 +17,7 @@ # Required-Stop: $local_fs $network # Should-Start: $remote_fs # Should-Stop: $remote_fs -# Default-Start: +# Default-Start: # Default-Stop: 0 1 2 3 4 5 6 # Short-Description: start and stop Dovecot Imap server # Description: Dovecot is an IMAP server for Linux/UNIX-like systems, @@ -105,3 +105,4 @@ case "$1" in esac exit $RETVAL + diff --git a/SOURCES/dovecot.portreserve b/SOURCES/dovecot.portreserve new file mode 100644 index 0000000..9b44b12 --- /dev/null +++ b/SOURCES/dovecot.portreserve @@ -0,0 +1,5 @@ +imap/tcp +imaps/tcp +pop3/tcp +pop3s/tcp +sieve/tcp diff --git a/SOURCES/dovecot.tmpfilesd b/SOURCES/dovecot.tmpfilesd index cb3e8f5..7178498 100644 --- a/SOURCES/dovecot.tmpfilesd +++ b/SOURCES/dovecot.tmpfilesd @@ -1 +1,2 @@ d /var/run/dovecot 0755 root dovecot - + diff --git a/SOURCES/prestartscript b/SOURCES/prestartscript new file mode 100644 index 0000000..0c5492c --- /dev/null +++ b/SOURCES/prestartscript @@ -0,0 +1,3 @@ +#!/bin/sh +/bin/systemctl -q is-enabled NetworkManager.service >/dev/null 2>&1 && /usr/bin/nm-online -q --timeout 30 ||: + diff --git a/SPECS/dovecot.spec b/SPECS/dovecot.spec index af0dec3..0ab0d65 100644 --- a/SPECS/dovecot.spec +++ b/SPECS/dovecot.spec @@ -5,7 +5,7 @@ Name: dovecot Epoch: 1 Version: 2.2.36 %global prever %{nil} -Release: 3%{?dist} +Release: 8%{?dist} #dovecot itself is MIT, a few sources are PD, pigeonhole is LGPLv2 License: MIT and LGPLv2 Group: System Environment/Daemons @@ -23,14 +23,14 @@ Source10: dovecot.tmpfilesd #our own Source14: dovecot.conf.5 +Source15: prestartscript +Source16: dovecot.portreserve # 3x Fedora/RHEL specific Patch1: dovecot-2.0-defaultconfig.patch Patch2: dovecot-1.0.beta2-mkcert-permissions.patch Patch3: dovecot-1.0.rc7-mkcert-paths.patch -Patch5: dovecot-2.1-privatetmp.patch - #wait for network Patch6: dovecot-2.1.10-waitonline.patch @@ -42,9 +42,26 @@ Patch9: dovecot-2.2.36-aclfix.patch # dovecot < 2.3, rhbz#1280436 Patch10: dovecot-2.2-gidcheck.patch - - -Source15: prestartscript +Patch11: dovecot-2.2.36-portreserve.patch +Patch12: dovecot-2.2.36-cve_2019_3814part1of3.patch +Patch13: dovecot-2.2.36-cve_2019_3814part2of3.patch +Patch14: dovecot-2.2.36-cve_2019_3814part3of3.patch +Patch15: dovecot-2.2.36-cve_2019_7524part1of2.patch +Patch16: dovecot-2.2.36-cve_2019_7524part2of2.patch + +Patch19: dovecot-2.2.36-cve2019_11500_part1of4.patch +Patch20: dovecot-2.2.36-cve2019_11500_part2of4.patch +Patch21: dovecot-2.2.36-cve2019_11500_part3of4.patch +Patch22: dovecot-2.2.36-cve2019_11500_part4of4.patch +Patch23: dovecot-2.2.36-bigkey.patch + +# from upstream, for dovecot <= 2.3.10.1 +Patch24: dovecot-2.3.8-CVE_2020_12100prereq.patch +Patch25: dovecot-2.3.8-CVE_2020_12100.patch +Patch26: dovecot-2.3.8-CVE_2020_12100ph.patch +Patch27: dovecot-2.3.8-CVE_2020_12673.patch +Patch28: dovecot-2.3.8-CVE_2020_12674prereq.patch +Patch29: dovecot-2.3.8-CVE_2020_12674.patch Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: openssl-devel, pam-devel, zlib-devel, bzip2-devel, xz-devel, libcap-devel @@ -77,6 +94,8 @@ Requires(preun): chkconfig initscripts Requires(postun): initscripts %endif +Requires: portreserve + %if %{?fedora}0 > 150 || %{?rhel}0 >60 #clucene in fedora <=15 and rhel<=6 is too old BuildRequires: clucene-core-devel @@ -94,8 +113,8 @@ BuildRequires: libcurl-devel expat-devel %global restart_flag /var/run/%{name}/%{name}-restart-after-rpm-install %description -Dovecot is an IMAP server for Linux/UNIX-like systems, written with security -primarily in mind. It also contains a small POP3 server. It supports mail +Dovecot is an IMAP server for Linux/UNIX-like systems, written with security +primarily in mind. It also contains a small POP3 server. It supports mail in either of maildir or mbox formats. The SQL drivers and authentication plug-ins are in their subpackages. @@ -141,10 +160,30 @@ This package provides the development files for dovecot. %patch7 -p1 -b .nodevrand %patch9 -p1 -b .aclfix %patch10 -p1 -b .gidcheck +%patch11 -p1 -b .portreserve +%patch12 -p1 -b .cve_2019_3814part1of3 +%patch13 -p1 -b .cve_2019_3814part2of3 +%patch14 -p1 -b .cve_2019_3814part3of3 +%patch15 -p1 -b .cve_2019_7524part1of2 +%patch16 -p1 -b .cve_2019_7524part2of2 +%patch19 -p1 -b .cve2019_11500_part1of4 +%patch20 -p1 -b .cve2019_11500_part2of4 +%patch23 -p1 -b .bigkey sed -i '/DEFAULT_INCLUDES *=/s|$| '"$(pkg-config --cflags libclucene-core)|" src/plugins/fts-lucene/Makefile.in #pigeonhole pushd dovecot-2*2-pigeonhole-%{pigeonholever} +%patch21 -p1 -b .cve2019_11500_part3of4 +%patch22 -p1 -b .cve2019_11500_part4of4 +popd +%patch24 -p1 -b .CVE_2020_12100prereq +%patch25 -p1 -b .CVE_2020_12100 +pushd dovecot-2*2-pigeonhole-%{pigeonholever} +%patch26 -p2 -b .CVE_2020_12100ph popd +%patch27 -p1 -b .CVE_2020_12673 +%patch28 -p1 -b .CVE_2020_12674prereq +%patch29 -p1 -b .CVE_2020_12674 + %build #required for fdpass.c line 125,190: dereferencing type-punned pointer will break strict-aliasing rules @@ -209,7 +248,6 @@ make install DESTDIR=$RPM_BUILD_ROOT #move doc dir back to build dir so doc macro in files section can use it mv $RPM_BUILD_ROOT/%{_docdir}/%{name}-%{version} %{_builddir}/%{name}-%{version}%{?prever}/docinstall - pushd dovecot-2*2-pigeonhole-%{pigeonholever} make install DESTDIR=$RPM_BUILD_ROOT @@ -231,6 +269,9 @@ install -p -D -m 644 %{SOURCE14} $RPM_BUILD_ROOT%{_mandir}/man5/dovecot.conf.5 #install waitonline script install -p -D -m 755 %{SOURCE15} $RPM_BUILD_ROOT%{_libexecdir}/dovecot/prestartscript +# install portreserve dovecot config +install -p -D -m 644 %{SOURCE16} $RPM_BUILD_ROOT%{_sysconfdir}/portreserve/dovecot + # generate ghost .pem files mkdir -p $RPM_BUILD_ROOT%{ssldir}/certs mkdir -p $RPM_BUILD_ROOT%{ssldir}/private @@ -276,7 +317,7 @@ rm -rf $RPM_BUILD_ROOT %pre -#dovecot uid and gid are reserved, see /usr/share/doc/setup-*/uidgid +#dovecot uid and gid are reserved, see /usr/share/doc/setup-*/uidgid getent group dovecot >/dev/null || groupadd -r --gid 97 dovecot getent passwd dovecot >/dev/null || \ useradd -r --uid 97 -g dovecot -d /usr/libexec/dovecot -s /sbin/nologin -c "Dovecot IMAP server" dovecot @@ -413,6 +454,7 @@ make check %config(noreplace) %{_sysconfdir}/dovecot/conf.d/auth-system.conf.ext %config(noreplace) %{_sysconfdir}/dovecot/conf.d/auth-vpopmail.conf.ext +%config(noreplace) %{_sysconfdir}/portreserve/dovecot %config(noreplace) %{_sysconfdir}/pam.d/dovecot %config(noreplace) %{ssldir}/dovecot-openssl.cnf @@ -517,6 +559,30 @@ make check %changelog +* Mon Aug 24 2020 Michal Hlavinka - 1:2.2.36-8 +- update release number + +* Mon Aug 10 2020 Michal Hlavinka - 1:2.2.36-7.1 +- fix CVE-2020-12100 resource exhaustion via deeply nested MIME parts (#1866752) +- fix CVE-2020-12673 out of bound reads in dovecot NTLM implementation (#1866757) +- fix CVE-2020-12674 crash due to assert in RPA implementation (#1866764) + +* Mon Mar 02 2020 Michal Hlavinka - 1:2.2.36-7 +- generated key was too small (#1086365) + +* Thu Aug 29 2019 Michal Hlavinka - 1:2.2.36-6 +- fix CVE-2019-11500: IMAP protocol parser does not properly handle NUL byte + when scanning data in quoted strings, leading to out of bounds heap + memory writes (#1741787) + +* Tue Aug 13 2019 Michal Hlavinka - 1:2.2.36-5 +- fix CVE-2019-3814: improper certificate validation (#1674369) +- fix CVE-2019-7524: buffer overflow in indexer-worker process resulting in privilege + escalation (#1700398) + +* Tue Aug 13 2019 Michal Hlavinka - 1:2.2.36-4 +- use portreserve to avoid port conflicts(#1270283) + * Wed Sep 19 2018 Michal Hlavinka - 1:2.2.36-3 - fix global ACL directory configuration search path (#1630380) - update first/last_valid_gid range patch (#1630409) @@ -604,7 +670,7 @@ make check - maildir: Fixed a crash after dovecot-keywords file was re-read. - maildir: If files had reappeared unexpectedly to a Maildir, they were ignored until index files were deleted. -- Maildir: Fixed handling over 26 keywords in a mailbox. +- Maildir: Fixed handling over 26 keywords in a mailbox. - imap/pop3-login proxying: Fixed a crash if TCP connection succeeded, but the remote login timed out. @@ -701,7 +767,7 @@ make check the header wasn't lowercased. - fts-squat: Fixed crash when searching a virtual mailbox. - pop3: Fixed assert crash when doing UIDL on empty mailbox on some - setups. + setups. - auth: GSSAPI RFC compliancy and error handling fixes. - Various fixes related to handling shared namespaces @@ -743,7 +809,7 @@ make check prefix is non-empty, don't assert-crash when rebuilding indexes. - sdbox: Don't use more fds than necessary when copying mails. - auth: Fixed crash with DIGEST-MD5 when attempting to do master user - login without master passdbs. + login without master passdbs. - Several fixes to mail_shared_explicit_inbox=no - imapc: Use imapc_list_prefix also for listing subscriptions. @@ -812,7 +878,7 @@ make check - dovecot updated to 2.1.1 - acl plugin + autocreated mailboxes crashed when listing mailboxes - doveadm force-resync: Don't skip autocreated mailboxes (especially - INBOX). + INBOX). - If process runs out of fds, stop listening for new connections only temporarily, not permanently (avoids hangs with process_limit=1 services) @@ -932,7 +998,7 @@ make check * Mon Mar 07 2011 Michal Hlavinka - 1:2.0.11-1 - IMAP: Fixed hangs with COMPRESS extension -- IMAP: Fixed a hang when trying to COPY to a nonexistent mailbox. +- IMAP: Fixed a hang when trying to COPY to a nonexistent mailbox. - IMAP: Fixed hang/crash with SEARCHRES + pipelining $. - IMAP: Fixed assert-crash if IDLE+DONE is sent in same TCP packet. @@ -971,7 +1037,7 @@ make check * Tue Nov 09 2010 Michal Hlavinka - 1:2.0.7-1 - dovecot updated to 2.0.7 - IMAP: Fixed LIST-STATUS when listing subscriptions with subscriptions=no namespaces. -- IMAP: Fixed SELECT QRESYNC not to crash on mailbox close if a lot of changes were being sent. +- IMAP: Fixed SELECT QRESYNC not to crash on mailbox close if a lot of changes were being sent. - quota: Don't count virtual mailboxes in quota - doveadm expunge didn't always actually do the physical expunging - Fixed some index reading optimizations introduced by v2.0.5. @@ -995,21 +1061,21 @@ make check * Mon Oct 04 2010 Michal Hlavinka - 1:2.0.5-1 - dovecot updated to 2.0.5 - acl: Fixed the logic of merging multiple ACL entries -- sdbox: Fixed memory leak when copying messages with hard links. +- sdbox: Fixed memory leak when copying messages with hard links. - zlib: Fixed several crashes, which mainly showed up with mbox. - quota: Don't crash if user has quota disabled, but plugin loaded. - acl: Fixed crashing when sometimes listing shared mailboxes via dict proxy. * Tue Sep 28 2010 Michal Hlavinka - 1:2.0.4-1 - dovecot updated to 2.0.4 -- multi-dbox: If :INDEX=path is specified, keep storage/dovecot.map.index* +- multi-dbox: If :INDEX=path is specified, keep storage/dovecot.map.index* files also in the index path rather than in the main storage directory. - dsync: POP3 UIDLs weren't copied with Maildir - dict file: Fixed fd leak (showed up easily with LMTP + quota) * Mon Sep 20 2010 Michal Hlavinka - 1:2.0.3-1 - dovecot updated to 2.0.3 -- dovecot-lda: Removed use of non-standard Envelope-To: header as +- dovecot-lda: Removed use of non-standard Envelope-To: header as a default for -a - dsync: Fixed handling \Noselect mailboxes - Fixed an infinite loop introduced by v2.0.2's message parser changes. @@ -1027,7 +1093,7 @@ make check * Wed Aug 25 2010 Michal Hlavinka - 1:2.0.1-1 - dovecot and pigeonhole updated - sieve: sieved renamed to sieve-dump -- when dsync is started as root, remote dsync command is now also executed +- when dsync is started as root, remote dsync command is now also executed as root instead of with dropped privileges. - IMAP: QRESYNC parameters for SELECT weren't handled correctly. - UTF-8 string validity checking wasn't done correctly @@ -1043,14 +1109,14 @@ make check - Using more than 2 plugins could have caused broken behavior - Listescape plugin fixes - mbox: Fixed a couple of assert-crashes -- mdbox: Fixed potential assert-crash when saving multiple messages +- mdbox: Fixed potential assert-crash when saving multiple messages in one transaction * Thu Aug 05 2010 Michal Hlavinka - 1:2.0-0.20.rc4 - dovecot and pigeonhole updated -- doveadm mailbox status: Fixed listing non-ASCII mailbox names. +- doveadm mailbox status: Fixed listing non-ASCII mailbox names. - doveadm fetch: Fixed output when fetching message header or body -- doveadm director map/add/remove: Fixed handling IP address as parameter. +- doveadm director map/add/remove: Fixed handling IP address as parameter. - dsync: A few more fixes * Wed Jul 21 2010 Michal Hlavinka - 1:2.0-0.19.rc3 @@ -1090,7 +1156,7 @@ make check - master: Fixed crash on deinit (maybe also on reload) * Thu Jun 10 2010 Michal Hlavinka - 1:2.0-0.14.beta5.20100610 -- dovecot updated +- dovecot updated - lib-storage: Fixed accessing uncommitted saved mails with dsync - example-config: Moved ACL and quota settings to a separate .conf files - dbox, mdbox: Fixed race conditions when creating mailboxes @@ -1118,7 +1184,7 @@ make check - example-config: auth-checkpassword include wasn't listed in 10-auth.conf - doveadm: Added search command - lib-master: Don't crash after timeouting an auth-master request -- master: If inet listener uses DNS name, which returns multiple IPs, +- master: If inet listener uses DNS name, which returns multiple IPs, listen in all of them * Wed Apr 28 2010 Michal Hlavinka - 1:2.0-0.7.beta4.20100427 @@ -1220,7 +1286,7 @@ make check * Tue Dec 22 2009 Michal Hlavinka - 1:1.2.9-2 - sieve updated to 0.1.14 -- managesieve updated to 0.11.10 +- managesieve updated to 0.11.10 * Fri Dec 18 2009 Michal Hlavinka - 1:1.2.9-1 - updated to 1.2.9 @@ -1273,7 +1339,7 @@ make check - spec cleanup * Wed Oct 21 2009 Michal Hlavinka - 1:1.2.6-4 -- imap-login: If imap_capability is set, show it in the banner +- imap-login: If imap_capability is set, show it in the banner instead of the default (#524485) * Mon Oct 19 2009 Michal Hlavinka - 1:1.2.6-3 @@ -1370,7 +1436,7 @@ make check * Mon Jul 13 2009 Michal Hlavinka - 1:1.2.1-1 - updated to 1.2.1 - GSSAPI authentication is fixed (#506782) -- logins now fail if home directory path is relative, because it was +- logins now fail if home directory path is relative, because it was not working correctly and never was expected to work - sieve and managesieve update @@ -1395,7 +1461,7 @@ make check - IMAP: PERMANENTFLAGS list didn't contain \*, causing some clients not to save keywords. - auth: Using "username" or "domain" passdb fields caused problems - with cache and blocking passdbs in v1.1.8 .. v1.1.10. + with cache and blocking passdbs in v1.1.8 .. v1.1.10. - userdb prefetch + blocking passdbs was broken with non-plaintext auth in v1.1.8 .. v1.1.10. @@ -1414,7 +1480,7 @@ make check * Tue Dec 2 2008 Michal Hlavinka - 1:1.1.7-2 - revert changes from 1:1.1.6-2 and 1:1.1.6-1 -- password can be stored in different file readable only for root +- password can be stored in different file readable only for root via !include_try directive * Tue Dec 2 2008 Michal Hlavinka - 1:1.1.7-1 @@ -1750,7 +1816,7 @@ make check * Wed Sep 8 2004 John Dennis 0.99.11-1.FC3.1 - bring up to latest upstream, - comments from Timo Sirainen on release v0.99.11 2004-09-04 + comments from Timo Sirainen on release v0.99.11 2004-09-04 + 127.* and ::1 IP addresses are treated as secured with disable_plaintext_auth = yes + auth_debug setting for extra authentication debugging @@ -1840,7 +1906,7 @@ make check - update to 0.99.10.4 * Mon Oct 6 2003 Jeremy Katz 0.99.10-7 -- another patch from upstream to fix returning invalid data on partial +- another patch from upstream to fix returning invalid data on partial BODY[part] fetches - patch to avoid confusion of draft/deleted in indexes @@ -1877,9 +1943,9 @@ make check * Thu May 8 2003 Jeremy Katz 0.99.9.1-1 - update to 0.99.9.1 -- add patch from upstream to fix potential bug when fetching with +- add patch from upstream to fix potential bug when fetching with CR+LF linefeeds -- tweak some things in the initscript and config file noticed by the +- tweak some things in the initscript and config file noticed by the fedora folks * Sun Mar 16 2003 Jeremy Katz 0.99.8.1-2