diff --git a/NEWS b/NEWS
index 272ff18ead..f5b19e41d1 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,21 @@
+v2.3.21.1 2024-08-14  Aki Tuomi< aki.tuomi@open-xchange.com>
+
+	- CVE-2024-23184: A large number of address headers in email resulted
+	  in excessive CPU usage.
+	- CVE-2024-23185: Abnormally large email headers are now truncated or
+	  discarded, with a limit of 10MB on a single header and 50MB for all
+	  the headers of all the parts of an email.
+	- oauth2: Dovecot would send client_id and client_secret as POST parameters
+	  to introspection server. These need to be optionally in Basic auth
+	  instead as required by OIDC specification.
+	- oauth2: JWT key type check was too strict.
+	- oauth2: JWT token audience was not validated against client_id as
+	  required by OIDC specification.
+	- oauth2: XOAUTH2 and OAUTHBEARER mechanisms were not giving out
+	  protocol specific error message on all errors. This broke OIDC discovery.
+	- oauth2: JWT aud validation was not performed if aud was missing
+	  from token, but was configured on Dovecot.
+
 v2.3.21 2023-09-15  Aki Tuomi <aki.tuomi@open-xchange.com>
 
 	* lib-oauth2: Allow JWT tokens to be validated with missing typ field.
diff --git a/configure.ac b/configure.ac
index a92b99bfbb..40ae311d24 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ AC_PREREQ([2.59])
 
 # Be sure to update ABI version also if anything changes that might require
 # recompiling plugins. Most importantly that means if any structs are changed.
-AC_INIT([Dovecot],[2.3.21],[dovecot@dovecot.org])
+AC_INIT([Dovecot],[2.3.21.1],[dovecot@dovecot.org])
 AC_DEFINE_UNQUOTED([DOVECOT_ABI_VERSION], "2.3.ABIv21($PACKAGE_VERSION)", [Dovecot ABI version])
 
 AC_CONFIG_SRCDIR([src])
diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c
index b36a4ced3f..d5ef604188 100644
--- a/src/auth/db-oauth2.c
+++ b/src/auth/db-oauth2.c
@@ -3,6 +3,7 @@
 #include "auth-common.h"
 #include "array.h"
 #include "str.h"
+#include "strescape.h"
 #include "var-expand.h"
 #include "env-util.h"
 #include "var-expand.h"
@@ -650,7 +651,8 @@ db_oauth2_token_in_scope(struct db_oauth2_request *req,
 	if (*req->db->set.scope != '\0') {
 		bool found = FALSE;
 		const char *value = auth_fields_find(req->fields, "scope");
-		if (value == NULL)
+		bool has_scope = value != NULL;
+		if (!has_scope)
 			value = auth_fields_find(req->fields, "aud");
 		e_debug(authdb_event(req->auth_request),
 			"Token scope(s): %s",
@@ -658,9 +660,11 @@ db_oauth2_token_in_scope(struct db_oauth2_request *req,
 		if (value != NULL) {
 			const char **wanted_scopes =
 				t_strsplit_spaces(req->db->set.scope, " ");
-			const char **scopes = t_strsplit_spaces(value, " ");
+			const char *const *entries = has_scope ?
+				t_strsplit_spaces(value, " ") :
+				t_strsplit_tabescaped(value);
 			for (; !found && *wanted_scopes != NULL; wanted_scopes++)
-				found = str_array_find(scopes, *wanted_scopes);
+				found = str_array_find(entries, *wanted_scopes);
 		}
 		if (!found) {
 			*error_r = t_strdup_printf("Token is not valid for scope '%s'",
diff --git a/src/auth/mech-oauth2.c b/src/auth/mech-oauth2.c
index dae563291d..fc6224824e 100644
--- a/src/auth/mech-oauth2.c
+++ b/src/auth/mech-oauth2.c
@@ -26,7 +26,7 @@ static bool oauth2_find_oidc_url(struct auth_request *req, const char **url_r)
 	for (; db != NULL; db = db->next) {
 		if (strcmp(db->passdb->iface.name, "oauth2") == 0) {
 			const char *url =
-				passdb_oauth2_get_oidc_url(req->passdb->passdb);
+				passdb_oauth2_get_oidc_url(db->passdb);
 			if (url == NULL || *url == '\0')
 				continue;
 			*url_r = url;
diff --git a/src/lib-imap/imap-envelope.c b/src/lib-imap/imap-envelope.c
index 87297f4f69..da3177025a 100644
--- a/src/lib-imap/imap-envelope.c
+++ b/src/lib-imap/imap-envelope.c
@@ -1,6 +1,7 @@
 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "llist.h"
 #include "istream.h"
 #include "str.h"
 #include "message-address.h"
@@ -66,17 +67,17 @@ void imap_envelope_write(struct message_part_envelope *data,
 	}
 
 	str_append_c(str, ' ');
-	imap_write_address(str, data->from);
+	imap_write_address(str, data->from.head);
 	str_append_c(str, ' ');
-	imap_write_address(str, NVL(data->sender, data->from));
+	imap_write_address(str, NVL(data->sender.head, data->from.head));
 	str_append_c(str, ' ');
-	imap_write_address(str, NVL(data->reply_to, data->from));
+	imap_write_address(str, NVL(data->reply_to.head, data->from.head));
 	str_append_c(str, ' ');
-	imap_write_address(str, data->to);
+	imap_write_address(str, data->to.head);
 	str_append_c(str, ' ');
-	imap_write_address(str, data->cc);
+	imap_write_address(str, data->cc.head);
 	str_append_c(str, ' ');
-	imap_write_address(str, data->bcc);
+	imap_write_address(str, data->bcc.head);
 
 	str_append_c(str, ' ');
 	imap_append_nstring_nolf(str, data->in_reply_to);
@@ -125,32 +126,25 @@ imap_envelope_parse_address(const struct imap_arg *arg,
 
 static bool
 imap_envelope_parse_addresses(const struct imap_arg *arg,
-	pool_t pool, struct message_address **addrs_r)
+	pool_t pool, struct message_address_list *addrs_r)
 {
-	struct message_address *first, *addr, *prev;
+	struct message_address *addr;
 	const struct imap_arg *list_args;
 
-	if (arg->type == IMAP_ARG_NIL) {
-		*addrs_r = NULL;
+	i_zero(addrs_r);
+	if (arg->type == IMAP_ARG_NIL)
 		return TRUE;
-	}
 
 	if (!imap_arg_get_list(arg, &list_args))
 		return FALSE;
 
-	first = addr = prev = NULL;
+	addr = NULL;
 	for (; !IMAP_ARG_IS_EOL(list_args); list_args++) {
 		if (!imap_envelope_parse_address
 			(list_args, pool, &addr))
 			return FALSE;
-		if (first == NULL)
-			first = addr;
-		if (prev != NULL)
-			prev->next = addr;
-		prev = addr;
+		DLLIST2_APPEND(&addrs_r->head, &addrs_r->tail, addr);
 	}
-
-	*addrs_r = first;
 	return TRUE;
 }
 
diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c
index 1f295e58ba..c9b92b4be2 100644
--- a/src/lib-imap/test-imap-envelope.c
+++ b/src/lib-imap/test-imap-envelope.c
@@ -157,7 +157,7 @@ static void test_imap_envelope_write(void)
 		envlp = msg_parse(pool, test->message);
 
 		imap_envelope_write(envlp, str);
-		test_assert(strcmp(str_c(str), test->envelope) == 0);
+		test_assert_idx(strcmp(str_c(str), test->envelope) == 0, i);
 
 		pool_unref(&pool);
 		test_end();
@@ -179,12 +179,12 @@ static void test_imap_envelope_parse(void)
 		test_begin(t_strdup_printf("imap envelope parser [%u]", i));
 
 		ret = imap_envelope_parse(test->envelope, pool, &envlp, &error);
-		test_assert(ret);
+		test_assert_idx(ret, i);
 
 		if (ret) {
 			str_truncate(str, 0);
 			imap_envelope_write(envlp, str);
-			test_assert(strcmp(str_c(str), test->envelope) == 0);
+			test_assert_idx(strcmp(str_c(str), test->envelope) == 0, i);
 		} else {
 			i_error("Invalid envelope: %s", error);
 		}
diff --git a/src/lib-mail/message-address.c b/src/lib-mail/message-address.c
index fb06afae7b..ae37014079 100644
--- a/src/lib-mail/message-address.c
+++ b/src/lib-mail/message-address.c
@@ -1,6 +1,7 @@
 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "llist.h"
 #include "str.h"
 #include "strescape.h"
 #include "smtp-address.h"
@@ -12,7 +13,8 @@ struct message_address_parser_context {
 	pool_t pool;
 	struct rfc822_parser_context parser;
 
-	struct message_address *first_addr, *last_addr, addr;
+	struct message_address addr;
+	struct message_address_list addr_list;
 	string_t *str;
 
 	bool fill_missing, non_strict_dots;
@@ -27,11 +29,7 @@ static void add_address(struct message_address_parser_context *ctx)
 	memcpy(addr, &ctx->addr, sizeof(ctx->addr));
 	i_zero(&ctx->addr);
 
-	if (ctx->first_addr == NULL)
-		ctx->first_addr = addr;
-	else
-		ctx->last_addr->next = addr;
-	ctx->last_addr = addr;
+	DLLIST2_APPEND(&ctx->addr_list.head, &ctx->addr_list.tail, addr);
 }
 
 /* quote with "" and escape all '\', '"' and "'" characters if need */
@@ -442,10 +440,11 @@ static int parse_path(struct message_address_parser_context *ctx)
 	return ret;
 }
 
-static struct message_address *
+static void
 message_address_parse_real(pool_t pool, const unsigned char *data, size_t size,
 			   unsigned int max_addresses,
-			   enum message_address_parse_flags flags)
+			   enum message_address_parse_flags flags,
+			   struct message_address_list *list_r)
 {
 	struct message_address_parser_context ctx;
 
@@ -464,7 +463,7 @@ message_address_parse_real(pool_t pool, const unsigned char *data, size_t size,
 		(void)parse_address_list(&ctx, max_addresses);
 	}
 	rfc822_parser_deinit(&ctx.parser);
-	return ctx.first_addr;
+	*list_r = ctx.addr_list;
 }
 
 static int
@@ -484,7 +483,7 @@ message_address_parse_path_real(pool_t pool, const unsigned char *data,
 	ret = parse_path(&ctx);
 
 	rfc822_parser_deinit(&ctx.parser);
-	*addr_r = ctx.first_addr;
+	*addr_r = ctx.addr_list.head;
 	return (ret < 0 ? -1 : 0);
 }
 
@@ -493,17 +492,24 @@ message_address_parse(pool_t pool, const unsigned char *data, size_t size,
 		      unsigned int max_addresses,
 		      enum message_address_parse_flags flags)
 {
-	struct message_address *addr;
+	struct message_address_list list;
+	message_address_parse_full(pool, data, size, max_addresses, flags,
+				   &list);
+	return list.head;
+}
 
+void message_address_parse_full(pool_t pool, const unsigned char *data,
+				size_t size, unsigned int max_addresses,
+				enum message_address_parse_flags flags,
+				struct message_address_list *list_r)
+{
 	if (pool->datastack_pool) {
-		return message_address_parse_real(pool, data, size,
-						  max_addresses, flags);
-	}
-	T_BEGIN {
-		addr = message_address_parse_real(pool, data, size,
-						  max_addresses, flags);
+		message_address_parse_real(pool, data, size,
+					   max_addresses, flags, list_r);
+	} else T_BEGIN {
+		message_address_parse_real(pool, data, size,
+					   max_addresses, flags, list_r);
 	} T_END;
-	return addr;
 }
 
 int message_address_parse_path(pool_t pool, const unsigned char *data,
@@ -631,6 +637,7 @@ const char *message_address_first_to_string(const struct message_address *addr)
 	struct message_address first_addr;
 
 	first_addr = *addr;
+	first_addr.prev = NULL;
 	first_addr.next = NULL;
 	first_addr.route = NULL;
 	return message_address_to_string(&first_addr);
diff --git a/src/lib-mail/message-address.h b/src/lib-mail/message-address.h
index 8370397741..224f7a7560 100644
--- a/src/lib-mail/message-address.h
+++ b/src/lib-mail/message-address.h
@@ -18,7 +18,7 @@ enum message_address_parse_flags {
    {name = NULL, NULL, "group", NULL}, ..., {NULL, NULL, NULL, NULL}
 */
 struct message_address {
-	struct message_address *next;
+	struct message_address *prev, *next;
 
 	/* display-name */
 	const char *name;
@@ -31,12 +31,22 @@ struct message_address {
 	bool invalid_syntax;
 };
 
+struct message_address_list {
+	struct message_address *head, *tail;
+};
+
 /* Parse message addresses from given data. Note that giving an empty string
    will return NULL since there are no addresses. */
 struct message_address *
 message_address_parse(pool_t pool, const unsigned char *data, size_t size,
 		      unsigned int max_addresses,
 		      enum message_address_parse_flags flags);
+/* Same as message_address_parse(), but return message_address_list containing
+   both the first and the last address in the linked list. */
+void message_address_parse_full(pool_t pool, const unsigned char *data,
+				size_t size, unsigned int max_addresses,
+				enum message_address_parse_flags flags,
+				struct message_address_list *list_r);
 
 /* Parse RFC 5322 "path" (Return-Path header) from given data. Returns -1 if
    the path is invalid and 0 otherwise.
diff --git a/src/lib-mail/message-header-parser.c b/src/lib-mail/message-header-parser.c
index c5026f1bb7..5e020bbeb3 100644
--- a/src/lib-mail/message-header-parser.c
+++ b/src/lib-mail/message-header-parser.c
@@ -21,6 +21,9 @@ struct message_header_parser_ctx {
 	string_t *name;
 	buffer_t *value_buf;
 
+	size_t header_block_max_size;
+	size_t header_block_total_size;
+
 	enum message_header_parser_flags flags;
 	bool skip_line:1;
 	bool has_nuls:1;
@@ -38,6 +41,7 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size,
 	ctx->name = str_new(default_pool, 128);
 	ctx->flags = flags;
 	ctx->value_buf = buffer_create_dynamic(default_pool, 4096);
+	ctx->header_block_max_size = MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE;
 	i_stream_ref(input);
 
 	if (hdr_size != NULL)
@@ -45,6 +49,21 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size,
 	return ctx;
 }
 
+void
+message_parse_header_set_limit(struct message_header_parser_ctx *parser,
+			       size_t header_block_max_size)
+{
+	parser->header_block_max_size = header_block_max_size;
+}
+
+void
+message_parse_header_lower_limit(struct message_header_parser_ctx *parser,
+				 size_t header_block_max_size)
+{
+	if (header_block_max_size < parser->header_block_max_size)
+		message_parse_header_set_limit(parser, header_block_max_size);
+}
+
 void message_parse_header_deinit(struct message_header_parser_ctx **_ctx)
 {
 	struct message_header_parser_ctx *ctx = *_ctx;
@@ -77,6 +96,7 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
 		/* new header line */
 		line->name_offset = ctx->input->v_offset;
 		colon_pos = UINT_MAX;
+		ctx->header_block_total_size += ctx->value_buf->used;
 		buffer_set_used_size(ctx->value_buf, 0);
 	}
 
@@ -342,33 +362,39 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
 		}
 	}
 
+	line->value_len = I_MIN(line->value_len, ctx->header_block_max_size);
+	size_t line_value_size = line->value_len;
+	size_t header_total_used = ctx->header_block_total_size + ctx->value_buf->used;
+	size_t line_available = ctx->header_block_max_size <= header_total_used ? 0 :
+				ctx->header_block_max_size - header_total_used;
+	line_value_size = I_MIN(line_value_size, line_available);
+
 	if (!line->continued) {
 		/* first header line. make a copy of the line since we can't
 		   really trust input stream not to lose it. */
-		buffer_append(ctx->value_buf, line->value, line->value_len);
+		buffer_append(ctx->value_buf, line->value, line_value_size);
 		line->value = line->full_value = ctx->value_buf->data;
-		line->full_value_len = line->value_len;
+		line->full_value_len = line->value_len = line_value_size;
 	} else if (line->use_full_value) {
 		/* continue saving the full value. */
 		if (last_no_newline) {
 			/* line is longer than fit into our buffer, so we
 			   were forced to break it into multiple
 			   message_header_lines */
-		} else {
-			if (last_crlf)
+		} else if (line_value_size > 1) {
+			if (last_crlf && line_value_size > 2)
 				buffer_append_c(ctx->value_buf, '\r');
 			buffer_append_c(ctx->value_buf, '\n');
 		}
 		if ((ctx->flags & MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE) != 0 &&
 		    line->value_len > 0 && line->value[0] != ' ' &&
-		    IS_LWSP(line->value[0])) {
+		    IS_LWSP(line->value[0]) &&
+		    line_value_size > 0) {
 			buffer_append_c(ctx->value_buf, ' ');
-			buffer_append(ctx->value_buf,
-				      line->value + 1, line->value_len - 1);
-		} else {
-			buffer_append(ctx->value_buf,
-				      line->value, line->value_len);
-		}
+			buffer_append(ctx->value_buf, line->value + 1, line_value_size - 1);
+		} else
+			buffer_append(ctx->value_buf, line->value, line_value_size);
+
 		line->full_value = ctx->value_buf->data;
 		line->full_value_len = ctx->value_buf->used;
 	} else {
diff --git a/src/lib-mail/message-header-parser.h b/src/lib-mail/message-header-parser.h
index ce0825c8e5..43cf95e56a 100644
--- a/src/lib-mail/message-header-parser.h
+++ b/src/lib-mail/message-header-parser.h
@@ -1,6 +1,9 @@
 #ifndef MESSAGE_HEADER_PARSER_H
 #define MESSAGE_HEADER_PARSER_H
 
+/* This can be overridden by message_parse_header_set_limit() */
+#define MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE ((size_t) 10 * 1024*1024)
+
 #define IS_LWSP(c) \
 	((c) == ' ' || (c) == '\t')
 
@@ -48,6 +51,13 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size,
 			  enum message_header_parser_flags flags) ATTR_NULL(2);
 void message_parse_header_deinit(struct message_header_parser_ctx **ctx);
 
+void
+message_parse_header_set_limit(struct message_header_parser_ctx *parser,
+			       size_t header_block_max_size);
+void
+message_parse_header_lower_limit(struct message_header_parser_ctx *parser,
+				 size_t header_block_max_size);
+
 /* Read and return next header line. Returns 1 if header is returned, 0 if
    input stream is non-blocking and more data needs to be read, -1 when all is
    done or error occurred (see stream's error status). */
diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index 41c32daf3a..8b362a9e71 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -30,6 +30,8 @@ struct message_parser_ctx {
 	enum message_parser_flags flags;
 	unsigned int max_nested_mime_parts;
 	unsigned int max_total_mime_parts;
+	size_t all_headers_max_size;
+	size_t all_headers_total_size;
 
 	char *last_boundary;
 	struct message_boundary *boundaries;
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 9a9c9a3515..c7e3b1e96a 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -617,7 +617,18 @@ static int parse_next_header(struct message_parser_ctx *ctx,
 	}
 	if (ret < 0) {
 		/* no boundary */
+		size_t headers_available =
+			ctx->all_headers_max_size > ctx->all_headers_total_size ?
+			ctx->all_headers_max_size - ctx->all_headers_total_size : 0;
+		message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available);
 		ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr);
+		if (ret > 0) {
+			if (!hdr->continues) {
+				ctx->all_headers_total_size += hdr->name_len;
+				ctx->all_headers_total_size += hdr->middle_len;
+			}
+			ctx->all_headers_total_size += hdr->value_len;
+		}
 		if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) {
 			ctx->want_count = i_stream_get_data_size(ctx->input) + 1;
 			return ret;
@@ -762,6 +773,9 @@ message_parser_init_int(struct istream *input,
 	ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ?
 		set->max_total_mime_parts :
 		MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS;
+	ctx->all_headers_max_size = set->all_headers_max_size != 0 ?
+		set->all_headers_max_size :
+		MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE;
 	ctx->input = input;
 	i_stream_ref(input);
 	return ctx;
@@ -779,6 +793,7 @@ message_parser_init(pool_t part_pool, struct istream *input,
 	ctx->next_part = &ctx->part->children;
 	ctx->parse_next_block = parse_next_header_init;
 	ctx->total_parts_count = 1;
+	ctx->all_headers_total_size = 0;
 	i_array_init(&ctx->next_part_stack, 4);
 	return ctx;
 }
diff --git a/src/lib-mail/message-parser.h b/src/lib-mail/message-parser.h
index f19e526284..8d70d73f05 100644
--- a/src/lib-mail/message-parser.h
+++ b/src/lib-mail/message-parser.h
@@ -19,6 +19,7 @@ enum message_parser_flags {
 
 #define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100
 #define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000
+#define MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE ((size_t) 50 * 1024*1024)
 
 struct message_parser_settings {
 	enum message_header_parser_flags hdr_flags;
@@ -30,6 +31,11 @@ struct message_parser_settings {
 	/* Maximum MIME parts in total.
 	   0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */
 	unsigned int max_total_mime_parts;
+
+	/* Maximum bytes fore headers in top header plus all
+	   MIME sections headers
+	   0 = MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE */
+	size_t all_headers_max_size;
 };
 
 struct message_parser_ctx;
diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c
index a5771f87e2..25019ab432 100644
--- a/src/lib-mail/message-part-data.c
+++ b/src/lib-mail/message-part-data.c
@@ -4,6 +4,7 @@
 #include "str.h"
 #include "wildcard-match.h"
 #include "array.h"
+#include "llist.h"
 #include "rfc822-parser.h"
 #include "rfc2231-parser.h"
 #include "message-address.h"
@@ -176,7 +177,7 @@ void message_part_envelope_parse_from_header(pool_t pool,
 {
 	struct message_part_envelope *d;
 	enum envelope_field field;
-	struct message_address **addr_p, *addr;
+	struct message_address_list *addr_p, new_addr;
 	const char **str_p;
 
 	if (*data == NULL) {
@@ -234,18 +235,18 @@ void message_part_envelope_parse_from_header(pool_t pool,
 	}
 
 	if (addr_p != NULL) {
-		addr = message_address_parse(pool, hdr->full_value,
-					     hdr->full_value_len,
-					     UINT_MAX,
-					     MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING);
+		message_address_parse_full(pool, hdr->full_value,
+					   hdr->full_value_len,
+					   UINT_MAX,
+					   MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING,
+					   &new_addr);
 		/* Merge multiple headers the same as if they were comma
 		   separated in a single line. This is better from security
 		   point of view, because attacker could intentionally write
 		   addresses in a way that e.g. the first From header is
 		   validated while MUA only shows the second From header. */
-		while (*addr_p != NULL)
-			addr_p = &(*addr_p)->next;
-		*addr_p = addr;
+		DLLIST2_JOIN(&addr_p->head, &addr_p->tail,
+			     &new_addr.head, &new_addr.tail);
 	} else if (str_p != NULL) {
 		*str_p = message_header_strdup(pool, hdr->full_value,
 					       hdr->full_value_len);
diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h
index 5ff9ffe1bc..7ec878de68 100644
--- a/src/lib-mail/message-part-data.h
+++ b/src/lib-mail/message-part-data.h
@@ -2,6 +2,7 @@
 #define MESSAGE_PART_DATA_H
 
 #include "message-part.h"
+#include "message-address.h"
 
 #define MESSAGE_PART_DEFAULT_CHARSET "us-ascii"
 
@@ -14,8 +15,9 @@ struct message_part_param {
 
 struct message_part_envelope {
 	const char *date, *subject;
-	struct message_address *from, *sender, *reply_to;
-	struct message_address *to, *cc, *bcc;
+
+	struct message_address_list from, sender, reply_to;
+	struct message_address_list to, cc, bcc;
 
 	const char *in_reply_to, *message_id;
 };
diff --git a/src/lib-mail/test-message-address.c b/src/lib-mail/test-message-address.c
index e6204bb058..54aa9a8310 100644
--- a/src/lib-mail/test-message-address.c
+++ b/src/lib-mail/test-message-address.c
@@ -19,8 +19,9 @@ static bool cmp_addr(const struct message_address *a1,
 		a1->invalid_syntax == a2->invalid_syntax;
 }
 
-static const struct message_address *
-test_parse_address(const char *input, bool fill_missing)
+static void
+test_parse_address_full(const char *input, bool fill_missing,
+			struct message_address_list *list_r)
 {
 	const enum message_address_parse_flags flags =
 		fill_missing ? MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING : 0;
@@ -28,11 +29,18 @@ test_parse_address(const char *input, bool fill_missing)
 	   if there's any out-of-bounds access */
 	size_t input_len = strlen(input);
 	unsigned char *input_dup = i_memdup(input, input_len);
-	const struct message_address *addr =
-		message_address_parse(pool_datastack_create(),
-				      input_dup, input_len, UINT_MAX, flags);
+	message_address_parse_full(pool_datastack_create(),
+				   input_dup, input_len, UINT_MAX, flags,
+				   list_r);
 	i_free(input_dup);
-	return addr;
+}
+
+static const struct message_address *
+test_parse_address(const char *input, bool fill_missing)
+{
+	struct message_address_list list;
+	test_parse_address_full(input, fill_missing, &list);
+	return list.head;
 }
 
 static void test_message_address(void)
@@ -47,174 +55,174 @@ static void test_message_address(void)
 	} tests[] = {
 		/* user@domain -> <user@domain> */
 		{ "user@domain", "<user@domain>", NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE },
-		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
 		{ "\"user\"@domain", "<user@domain>", NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE },
-		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
 		{ "\"user name\"@domain", "<\"user name\"@domain>", NULL,
-		  { NULL, NULL, NULL, "user name", "domain", FALSE },
-		  { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user name", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, 0 },
 		{ "\"user@na\\\\me\"@domain", "<\"user@na\\\\me\"@domain>", NULL,
-		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE },
-		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 },
 		{ "\"user\\\"name\"@domain", "<\"user\\\"name\"@domain>", NULL,
-		  { NULL, NULL, NULL, "user\"name", "domain", FALSE },
-		  { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 },
 		{ "\"\"@domain", "<\"\"@domain>", NULL,
-		  { NULL, NULL, NULL, "", "domain", FALSE },
-		  { NULL, NULL, NULL, "", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "", "domain", FALSE }, 0 },
 		{ "user", "<user>", "<user@MISSING_DOMAIN>",
-		  { NULL, NULL, NULL, "user", "", TRUE },
-		  { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "", TRUE },
+		  { NULL, NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "@domain", "<\"\"@domain>", "<MISSING_MAILBOX@domain>",
-		  { NULL, NULL, NULL, "", "domain", TRUE },
-		  { NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 },
+		  { NULL, NULL, NULL, NULL, "", "domain", TRUE },
+		  { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 },
 
 		/* Display Name -> Display Name */
 		{ "Display Name", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, "Display Name", NULL, "", "", TRUE },
-		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "", "", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "\"Display Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, "Display Name", NULL, "", "", TRUE },
-		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "", "", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "Display \"Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, "Display Name", NULL, "", "", TRUE },
-		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "", "", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "\"Display\" \"Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, "Display Name", NULL, "", "", TRUE },
-		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "", "", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "\"\"", "", "<MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, "", NULL, "", "", TRUE },
-		  { NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "", NULL, "", "", TRUE },
+		  { NULL, NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 
 		/* <user@domain> -> <user@domain> */
 		{ "<user@domain>", NULL, NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE },
-		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
 		{ "<\"user\"@domain>", "<user@domain>", NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE },
-		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
 		{ "<\"user name\"@domain>", NULL, NULL,
-		  { NULL, NULL, NULL, "user name", "domain", FALSE },
-		  { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user name", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, 0 },
 		{ "<\"user@na\\\\me\"@domain>", NULL, NULL,
-		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE },
-		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 },
 		{ "<\"user\\\"name\"@domain>", NULL, NULL,
-		  { NULL, NULL, NULL, "user\"name", "domain", FALSE },
-		  { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 },
 		{ "<\"\"@domain>", NULL, NULL,
-		  { NULL, NULL, NULL, "", "domain", FALSE },
-		  { NULL, NULL, NULL, "", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "", "domain", FALSE }, 0 },
 		{ "<user>", NULL, "<user@MISSING_DOMAIN>",
-		  { NULL, NULL, NULL, "user", "", TRUE },
-		  { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "", TRUE },
+		  { NULL, NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "<@route>", "<@route:\"\">", "<INVALID_ROUTE:MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, NULL, "@route", "", "", TRUE },
-		  { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, "@route", "", "", TRUE },
+		  { NULL, NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 
 		/* user@domain (Display Name) -> "Display Name" <user@domain> */
 		{ "user@domain (DisplayName)", "DisplayName <user@domain>", NULL,
-		  { NULL, "DisplayName", NULL, "user", "domain", FALSE },
-		  { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 },
 		{ "user@domain (Display Name)", "\"Display Name\" <user@domain>", NULL,
-		  { NULL, "Display Name", NULL, "user", "domain", FALSE },
-		  { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
 		{ "user@domain (Display\"Name)", "\"Display\\\"Name\" <user@domain>", NULL,
-		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE },
-		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 },
 		{ "user (Display Name)", "\"Display Name\" <user>", "\"Display Name\" <user@MISSING_DOMAIN>",
-		  { NULL, "Display Name", NULL, "user", "", TRUE },
-		  { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "user", "", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "@domain (Display Name)", "\"Display Name\" <\"\"@domain>", "\"Display Name\" <MISSING_MAILBOX@domain>",
-		  { NULL, "Display Name", NULL, "", "domain", TRUE },
-		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "", "domain", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 },
 		{ "user@domain ()", "<user@domain>", NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE },
-		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
 
 		/* Display Name <user@domain> -> "Display Name" <user@domain> */
 		{ "DisplayName <user@domain>", NULL, NULL,
-		  { NULL, "DisplayName", NULL, "user", "domain", FALSE },
-		  { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 },
 		{ "Display Name <user@domain>", "\"Display Name\" <user@domain>", NULL,
-		  { NULL, "Display Name", NULL, "user", "domain", FALSE },
-		  { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
 		{ "\"Display Name\" <user@domain>", NULL, NULL,
-		  { NULL, "Display Name", NULL, "user", "domain", FALSE },
-		  { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
 		{ "\"Display\\\"Name\" <user@domain>", NULL, NULL,
-		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE },
-		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 },
 		{ "Display Name <user>", "\"Display Name\" <user>", "\"Display Name\" <user@MISSING_DOMAIN>",
-		  { NULL, "Display Name", NULL, "user", "", TRUE },
-		  { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", NULL, "user", "", TRUE },
+		  { NULL, NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "\"\" <user@domain>", "<user@domain>", NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE },
-		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
 
 		/* <@route:user@domain> -> <@route:user@domain> */
 		{ "<@route:user@domain>", NULL, NULL,
-		  { NULL, NULL, "@route", "user", "domain", FALSE },
-		  { NULL, NULL, "@route", "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, "@route", "user", "domain", FALSE },
+		  { NULL, NULL, NULL, "@route", "user", "domain", FALSE }, 0 },
 		{ "<@route,@route2:user@domain>", NULL, NULL,
-		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE },
-		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE },
+		  { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 },
 		{ "<@route@route2:user@domain>", "<@route,@route2:user@domain>", NULL,
-		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE },
-		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE },
+		  { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 },
 		{ "<@route@route2:user>", "<@route,@route2:user>", "<@route,@route2:user@MISSING_DOMAIN>",
-		  { NULL, NULL, "@route,@route2", "user", "", TRUE },
-		  { NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, "@route,@route2", "user", "", TRUE },
+		  { NULL, NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "<@route@route2:\"\"@domain>", "<@route,@route2:\"\"@domain>", NULL,
-		  { NULL, NULL, "@route,@route2", "", "domain", FALSE },
-		  { NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 },
+		  { NULL, NULL, NULL, "@route,@route2", "", "domain", FALSE },
+		  { NULL, NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 },
 
 		/* Display Name <@route:user@domain> ->
 		   "Display Name" <@route:user@domain> */
 		{ "Display Name <@route:user@domain>", "\"Display Name\" <@route:user@domain>", NULL,
-		  { NULL, "Display Name", "@route", "user", "domain", FALSE },
-		  { NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", "@route", "user", "domain", FALSE },
+		  { NULL, NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 },
 		{ "Display Name <@route,@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL,
-		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE },
-		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE },
+		  { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 },
 		{ "Display Name <@route@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL,
-		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE },
-		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE },
+		  { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 },
 		{ "Display Name <@route@route2:user>", "\"Display Name\" <@route,@route2:user>", "\"Display Name\" <@route,@route2:user@MISSING_DOMAIN>",
-		  { NULL, "Display Name", "@route,@route2", "user", "", TRUE },
-		  { NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, "Display Name", "@route,@route2", "user", "", TRUE },
+		  { NULL, NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "Display Name <@route@route2:\"\"@domain>", "\"Display Name\" <@route,@route2:\"\"@domain>", NULL,
-		  { NULL, "Display Name", "@route,@route2", "", "domain", FALSE },
-		  { NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 },
+		  { NULL, NULL, "Display Name", "@route,@route2", "", "domain", FALSE },
+		  { NULL, NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 },
 
 		/* other tests: */
 		{ "\"foo: <a@b>;,\" <user@domain>", NULL, NULL,
-		  { NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE },
-		  { NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, 0 },
+		  { NULL, NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE },
+		  { NULL, NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, 0 },
 		{ "<>", "", "<MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, NULL, NULL, "", "", TRUE },
-		  { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, NULL, "", "", TRUE },
+		  { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "<@>", "", "<INVALID_ROUTE:MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, NULL, NULL, "", "", TRUE },
-		  { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, NULL, "", "", TRUE },
+		  { NULL, NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
 
 		/* Test against a out-of-bounds read bug - keep these two tests
 		   together in this same order: */
 		{ "aaaa@", "<aaaa>", "<aaaa@MISSING_DOMAIN>",
-		  { NULL, NULL, NULL, "aaaa", "", TRUE },
-		  { NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 },
+		  { NULL, NULL, NULL, NULL, "aaaa", "", TRUE },
+		  { NULL, NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 },
 		{ "a(aa", "", "<MISSING_MAILBOX@MISSING_DOMAIN>",
-		  { NULL, NULL, NULL, "", "", TRUE },
-		  { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE },
+		  { NULL, NULL, NULL, NULL, "", "", TRUE },
+		  { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE },
 		  TEST_MESSAGE_ADDRESS_FLAG_SKIP_LIST },
 	};
 	static struct message_address group_prefix = {
-		NULL, NULL, NULL, "group", NULL, FALSE
+		NULL, NULL, NULL, NULL, "group", NULL, FALSE
 	};
 	static struct message_address group_suffix = {
-		NULL, NULL, NULL, NULL, NULL, FALSE
+		NULL, NULL, NULL, NULL, NULL, NULL, FALSE
 	};
 	const struct message_address *addr;
 	string_t *str, *group;
@@ -322,12 +330,39 @@ static void test_message_address(void)
 	test_end();
 }
 
+static void test_message_address_list(void)
+{
+	test_begin("message address list");
+
+	const char *test_input =
+		"user1@example1.com, user2@example2.com, user3@example3.com";
+	const struct message_address wanted_addrs[] = {
+		{ NULL, NULL, NULL, NULL, "user1", "example1.com", FALSE },
+		{ NULL, NULL, NULL, NULL, "user2", "example2.com", FALSE },
+		{ NULL, NULL, NULL, NULL, "user3", "example3.com", FALSE },
+	};
+
+	struct message_address_list list;
+	struct message_address *addr, *scanned_last_addr;
+	test_parse_address_full(test_input, FALSE, &list);
+	addr = list.head;
+	for (unsigned int i = 0; i < N_ELEMENTS(wanted_addrs); i++) {
+		test_assert_idx(cmp_addr(addr, &wanted_addrs[i]), i);
+		scanned_last_addr = addr;
+		addr = addr->next;
+	}
+	test_assert(list.tail == scanned_last_addr);
+	test_assert(addr == NULL);
+
+	test_end();
+}
+
 static void test_message_address_nuls(void)
 {
 	const unsigned char input[] =
 		"\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc] (comment\0nuls\\\0-esc)";
 	const struct message_address output = {
-		NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL,
+		NULL, NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL,
 		"user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc",
 		"[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE
 	};
@@ -345,7 +380,7 @@ static void test_message_address_nuls_display_name(void)
 	const unsigned char input[] =
 		"\"displayname\0nuls\\\0-esc\" <\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc]>";
 	const struct message_address output = {
-		NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL,
+		NULL, NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL,
 		"user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc",
 		"[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE
 	};
@@ -369,7 +404,7 @@ static void test_message_address_non_strict_dots(void)
 	};
 	const struct message_address *addr;
 	struct message_address output = {
-		NULL, NULL, NULL, "local-part",
+		NULL, NULL, NULL, NULL, "local-part",
 		"example.com", FALSE
 	};
 
@@ -421,29 +456,29 @@ static void test_message_address_path(void)
 		struct message_address addr;
 	} tests[] = {
 		{ "<>", NULL,
-		  { NULL, NULL, NULL, NULL, NULL, FALSE } },
+		  { NULL, NULL, NULL, NULL, NULL, NULL, FALSE } },
 		{ " < > ", "<>",
-		  { NULL, NULL, NULL, NULL, NULL, FALSE } },
+		  { NULL, NULL, NULL, NULL, NULL, NULL, FALSE } },
 		{ "<user@domain>", NULL,
-		  { NULL, NULL, NULL, "user", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE } },
 		{ "  <user@domain>  ", "<user@domain>",
-		  { NULL, NULL, NULL, "user", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE } },
 		{ "user@domain", "<user@domain>",
-		  { NULL, NULL, NULL, "user", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE } },
 		{ "  user@domain  ", "<user@domain>",
-		  { NULL, NULL, NULL, "user", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE } },
 		{ "<\"user\"@domain>", "<user@domain>",
-		  { NULL, NULL, NULL, "user", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user", "domain", FALSE } },
 		{ "<\"user name\"@domain>", NULL,
-		  { NULL, NULL, NULL, "user name", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user name", "domain", FALSE } },
 		{ "<\"user@na\\\\me\"@domain>", NULL,
-		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE } },
 		{ "<\"user\\\"name\"@domain>", NULL,
-		  { NULL, NULL, NULL, "user\"name", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE } },
 		{ "<\"\"@domain>", NULL,
-		  { NULL, NULL, NULL, "", "domain", FALSE } },
+		  { NULL, NULL, NULL, NULL, "", "domain", FALSE } },
 		{ "<@source", "<>",
-		  { NULL, NULL, NULL, NULL, NULL, TRUE } },
+		  { NULL, NULL, NULL, NULL, NULL, NULL, TRUE } },
 	};
 	const struct message_address *addr;
 	string_t *str;
@@ -521,6 +556,7 @@ int main(void)
 {
 	static void (*const test_functions[])(void) = {
 		test_message_address,
+		test_message_address_list,
 		test_message_address_nuls,
 		test_message_address_nuls_display_name,
 		test_message_address_non_strict_dots,
diff --git a/src/lib-mail/test-message-header-parser.c b/src/lib-mail/test-message-header-parser.c
index 700d3413f1..93d8842002 100644
--- a/src/lib-mail/test-message-header-parser.c
+++ b/src/lib-mail/test-message-header-parser.c
@@ -463,6 +463,71 @@ static void test_message_header_parser_extra_crlf_in_name(void)
 	test_end();
 }
 
+#define assert_parsed_field(line, expected, actual, len) STMT_START {		\
+	test_assert_idx(memcmp(expected, actual, strlen(expected)) == 0, line); \
+	test_assert_cmp_idx(strlen(expected), ==, len, line);			\
+} STMT_END
+
+/* NOTE: implicit variables: (parser, hdr) */
+#define assert_parse_line(line, exp_name, exp_value, exp_full) STMT_START {		\
+	test_assert_idx(message_parse_header_next(parser, &hdr) > 0, line); 		\
+	assert_parsed_field(line, exp_name,   hdr->name,       hdr->name_len);		\
+	assert_parsed_field(line, exp_value,  hdr->value,      hdr->value_len);		\
+	assert_parsed_field(line, exp_full,   hdr->full_value, hdr->full_value_len);	\
+	if (hdr->continues) hdr->use_full_value = TRUE;					\
+} STMT_END
+
+static const unsigned char test_message_header_truncation_input[] =
+	/*01*/	"header1: this is short\n"
+	/*02*/	"header2: this is multiline\n"
+	/*03*/	" and long 343638404244464850525456586062\n"
+	/*04*/	" 64666870727476788082848688909294969800\n"
+	/*05*/	" 02040608101214161820222426283032343638\n"
+	/*06*/	"header3: I should not appear at all\n"
+	/*07*/	"\n";
+
+static void test_message_header_truncation_clean_oneline(void)
+{
+	test_begin("message header parser truncate + CLEAN_ONELINE flag");
+	struct message_header_line *hdr = NULL;
+	struct istream *input = test_istream_create_data(test_message_header_truncation_input, sizeof(test_message_header_truncation_input));
+	struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE);
+	message_parse_header_set_limit(parser, 96);
+
+	assert_parse_line( 1, "header1", "this is short",	                     "this is short");
+	assert_parse_line( 2, "header2", "this is multiline", 	                     "this is multiline");
+	assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline and long 343638404244464850525456586062");
+	assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800",  "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
+	assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638",  "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
+	assert_parse_line( 6, "header3", "", "");
+	test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh);
+
+	message_parse_header_deinit(&parser);
+	i_stream_unref(&input);
+	test_end();
+}
+
+static void test_message_header_truncation_flag0(void)
+{
+	test_begin("message header parser truncate + NO flags");
+	struct message_header_line *hdr = NULL;
+	struct istream *input = test_istream_create_data(test_message_header_truncation_input, sizeof(test_message_header_truncation_input));
+	struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, 0);
+	message_parse_header_set_limit(parser, 96);
+
+	assert_parse_line( 1, "header1", "this is short",	                     "this is short");
+	assert_parse_line( 2, "header2", "this is multiline", 	                     "this is multiline");
+	assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline\n and long 343638404244464850525456586062");
+	assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800",  "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486");
+	assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638",  "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486");
+	assert_parse_line( 6, "header3", "", "");
+	test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh);
+
+	message_parse_header_deinit(&parser);
+	i_stream_unref(&input);
+	test_end();
+}
+
 int main(void)
 {
 	static void (*const test_functions[])(void) = {
@@ -473,6 +538,8 @@ int main(void)
 		test_message_header_parser_no_eoh,
 		test_message_header_parser_nul,
 		test_message_header_parser_extra_crlf_in_name,
+		test_message_header_truncation_flag0,
+		test_message_header_truncation_clean_oneline,
 		NULL
 	};
 	return test_run(test_functions);
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index 663bfe8c5a..b6bada2303 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -1369,6 +1369,158 @@ static const char input_msg[] =
 	test_end();
 }
 
+#define test_assert_virtual_size(part) \
+	test_assert((part).virtual_size == (part).lines + (part).physical_size)
+
+#define test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) \
+STMT_START { 								\
+	test_assert((part)->flags == (flags_));				\
+	test_assert((part)->children_count == children);		\
+	test_assert((part)->header_size.lines == h_lines);		\
+	test_assert((part)->header_size.physical_size == h_size);	\
+	test_assert((part)->body_size.lines == b_lines);		\
+	test_assert((part)->body_size.physical_size == b_size);		\
+	test_assert_virtual_size((part)->header_size);			\
+	test_assert_virtual_size((part)->body_size);			\
+} STMT_END
+
+static const enum message_part_flags FLAGS_MULTIPART =
+	MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MULTIPART;
+static const enum message_part_flags FLAGS_RFC822 =
+	MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MESSAGE_RFC822;
+static const enum message_part_flags FLAGS_TEXT =
+	MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_TEXT;
+
+static const char too_many_header_bytes_input_msg[] =
+	"Content-Type: multipart/mixed; boundary=\"1\"\n\n"
+		"--1\n"
+		"Content-Type: multipart/mixed; boundary=\"2\"\n\n"
+			"--2\n"
+			"Content-Type: message/rfc822\n\n"
+				"Content-Type: text/plain\n\n1-rfc822\n"
+			"--2\n"
+			"Content-Type: message/rfc822\n\n"
+				"Content-Type: text/plain\n\n2-rfc822\n"
+		"--1\n"
+			"Content-Type: message/rfc822\n\n"
+				"Content-Type: text/plain\n\n3-rfc822\n";
+
+static void test_message_parser_too_many_header_bytes_run(
+	const struct message_parser_settings *parser_set,
+	pool_t *pool_r, struct istream **input_r,
+	struct message_part **parts_r)
+{
+	*pool_r = pool_alloconly_create("message parser", 10240);
+	*input_r = test_istream_create(too_many_header_bytes_input_msg);
+	struct message_parser_ctx *parser = message_parser_init(*pool_r, *input_r, parser_set);
+
+	int ret;
+	struct message_block block ATTR_UNUSED;
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0);
+	test_assert(ret < 0);
+
+	message_parser_deinit(&parser, parts_r);
+}
+
+static void test_message_parser_too_many_header_bytes_default(void)
+{
+	test_begin("message parser too many header bytes default");
+
+	pool_t pool;
+	struct istream *input;
+	struct message_part *part_root;
+	const struct message_parser_settings parser_set = { .all_headers_max_size = 0 };
+
+	test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root);
+
+	// test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size )
+
+	test_assert_part(part_root, FLAGS_MULTIPART, 7, 2, 45, 21, 256);
+	test_assert(part_root->parent == NULL);
+
+		struct message_part *part_1 = part_root->children;
+		test_assert_part(part_1, FLAGS_MULTIPART, 4, 2, 45, 11, 137);
+
+			struct message_part *part_1_1 = part_1->children;
+			test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34);
+
+				struct message_part *part_1_1_1 = part_1_1->children;
+				test_assert_part(part_1_1_1, FLAGS_TEXT, 0, 2, 26, 0, 8);
+
+				test_assert(part_1_1_1->next == NULL);
+
+			struct message_part *part_1_2 = part_1_1->next;
+			test_assert_part(part_1_2, FLAGS_RFC822, 1, 2, 30, 2, 34);
+
+				struct message_part *part_1_2_1 = part_1_2->children;
+				test_assert_part(part_1_2_1, FLAGS_TEXT, 0, 2, 26, 0, 8);
+
+				test_assert(part_1_2_1->next == NULL);
+
+			test_assert(part_1_2->next == NULL);
+
+		struct message_part *part_2 = part_1->next;
+		test_assert_part(part_2, FLAGS_RFC822, 1, 2, 30, 3, 35);
+
+			struct message_part *part_2_1 = part_2->children;
+			test_assert_part(part_2_1, FLAGS_TEXT, 0, 2, 26, 1, 9);
+			test_assert(part_2_1->next == NULL);
+
+		test_assert(part_2->next == NULL);
+
+	test_assert(part_root->next == NULL);
+
+	test_parsed_parts(input, part_root);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
+static void test_message_parser_too_many_header_bytes_100(void)
+{
+	test_begin("message parser too many header bytes 100");
+
+	pool_t pool;
+	struct istream *input;
+	struct message_part *part_root;
+	const struct message_parser_settings parser_set = { .all_headers_max_size = 100 };
+
+	test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root);
+
+	// test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size )
+
+	test_assert_part(part_root, FLAGS_MULTIPART, 5, 2, 45, 21, 256);
+	test_assert(part_root->parent == NULL);
+
+		struct message_part *part_1 = part_root->children;
+		test_assert_part(part_1, FLAGS_MULTIPART, 3, 2, 45, 11, 137);
+
+			struct message_part *part_1_1 = part_1->children;
+			test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34);
+
+				struct message_part *part_1_1_1 = part_1_1->children;
+				test_assert_part(part_1_1_1, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 26, 0, 8);
+
+				test_assert(part_1_1_1->next == NULL);
+
+			struct message_part *part_1_2 = part_1_1->next;
+			test_assert_part(part_1_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 2, 34);
+
+			test_assert(part_1_2->next == NULL);
+
+		struct message_part *part_2 = part_1->next;
+		test_assert_part(part_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 3, 35);
+
+		test_assert(part_2->next == NULL);
+
+	test_assert(part_root->next == NULL);
+
+	test_parsed_parts(input, part_root);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 int main(void)
 {
 	static void (*const test_functions[])(void) = {
@@ -1392,6 +1544,8 @@ int main(void)
 		test_message_parser_mime_part_limit_rfc822,
 		test_message_parser_mime_version,
 		test_message_parser_mime_version_missing,
+		test_message_parser_too_many_header_bytes_default,
+		test_message_parser_too_many_header_bytes_100,
 		NULL
 	};
 	return test_run(test_functions);
diff --git a/src/lib-oauth2/oauth2-jwt.c b/src/lib-oauth2/oauth2-jwt.c
index bc7779fe1d..04db602e9d 100644
--- a/src/lib-oauth2/oauth2-jwt.c
+++ b/src/lib-oauth2/oauth2-jwt.c
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "buffer.h"
 #include "str.h"
+#include "strescape.h"
 #include "hmac.h"
 #include "array.h"
 #include "hash-method.h"
@@ -30,6 +31,29 @@ static const char *get_field(const struct json_tree *tree, const char *key)
 	return json_tree_get_value_str(value_node);
 }
 
+static const char *get_field_multiple(const struct json_tree *tree, const char *key)
+{
+	const struct json_tree_node *root = json_tree_root(tree);
+	const struct json_tree_node *value_node = json_tree_find_key(root, key);
+	if (value_node == NULL || value_node->value_type == JSON_TYPE_OBJECT)
+		return NULL;
+	if (value_node->value_type != JSON_TYPE_ARRAY)
+		return json_tree_get_value_str(value_node);
+
+	const struct json_tree_node *entry_node = json_tree_get_child(value_node);
+	string_t *values = t_str_new(64);
+	for (; entry_node != NULL; entry_node = entry_node->next) {
+		if (entry_node->value_type == JSON_TYPE_OBJECT ||
+		    entry_node->value_type == JSON_TYPE_ARRAY)
+		    	continue;
+		const char *value_str = json_tree_get_value_str(entry_node);
+		if (str_len(values) > 0)
+			str_append_c(values, '\t');
+		str_append_tabescaped(values, value_str);
+	}
+	return str_c(values);
+}
+
 static int get_time_field(const struct json_tree *tree, const char *key,
 			  int64_t *value_r)
 {
@@ -418,7 +442,7 @@ oauth2_jwt_body_process(const struct oauth2_settings *set, const char *alg,
 		}
 	}
 
-	const char *aud = get_field(tree, "aud");
+	const char *aud = get_field_multiple(tree, "aud");
 	/* if there is client_id configured, then aud should be present */
 	if (set->client_id != NULL && *set->client_id != '\0') {
 		if (aud == NULL) {
@@ -426,7 +450,7 @@ oauth2_jwt_body_process(const struct oauth2_settings *set, const char *alg,
 			return -1;
 
 		}
-		const char *const *auds = t_strsplit_spaces(aud, " ");
+		const char *const *auds = t_strsplit_tabescaped(aud);
 		if (!str_array_find(auds, set->client_id)) {
 			*error_r = "client_id not found in aud field";
 			return -1;
diff --git a/src/lib-storage/index/index-search-mime.c b/src/lib-storage/index/index-search-mime.c
index da7e5e1709..3328ce98af 100644
--- a/src/lib-storage/index/index-search-mime.c
+++ b/src/lib-storage/index/index-search-mime.c
@@ -205,7 +205,7 @@ seach_arg_mime_envelope_address_match(
 	enum mail_search_mime_arg_type type, const char *key,
 	const struct message_part_envelope *envelope)
 {
-	const struct message_address *addrs;
+	struct message_address_list addrs;
 	string_t *addrs_enc;
 
 	if (envelope == NULL)
@@ -239,7 +239,7 @@ seach_arg_mime_envelope_address_match(
 	   probably be normalized directly in the struct message_address. */
 
 	addrs_enc = t_str_new(128);
-	message_address_write(addrs_enc, addrs);
+	message_address_write(addrs_enc, addrs.head);
 	return (strstr(str_c(addrs_enc), key) != NULL ? 1 : 0);
 }
 
diff --git a/src/lib/llist.h b/src/lib/llist.h
index 8a52e87335..5ad5d75c0d 100644
--- a/src/lib/llist.h
+++ b/src/lib/llist.h
@@ -78,4 +78,18 @@
 #define DLLIST2_REMOVE(head, tail, item) \
 	DLLIST2_REMOVE_FULL(head, tail, item, prev, next)
 
+#define DLLIST2_JOIN_FULL(head1, tail1, head2, tail2, prev, next) STMT_START { \
+	if (*(head1) == NULL) { \
+		*(head1) = *(head2); \
+		*(tail1) = *(tail2); \
+	} else if (*(head2) != NULL) { \
+		(*(tail1))->next = *(head2); \
+		(*(head2))->prev = *(tail1); \
+		(*tail1) = (*tail2); \
+	} \
+	} STMT_END
+
+#define DLLIST2_JOIN(head1, tail1, head2, tail2) \
+	DLLIST2_JOIN_FULL(head1, tail1, head2, tail2, prev, next)
+
 #endif
diff --git a/src/lib/sha2.c b/src/lib/sha2.c
index 93dddfbb39..b6bef47684 100644
--- a/src/lib/sha2.c
+++ b/src/lib/sha2.c
@@ -287,7 +287,7 @@ void sha256_result(struct sha256_ctx *ctx,
 {
 	size_t block_nb;
 	size_t pm_len;
-	size_t len_b;
+	uint64_t len_b;
 	int i;
 
 	block_nb = (1 + ((SHA256_BLOCK_SIZE - 9)
@@ -298,7 +298,7 @@ void sha256_result(struct sha256_ctx *ctx,
 
 	memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
 	ctx->block[ctx->len] = 0x80;
-	UNPACK32(len_b, ctx->block + pm_len - 4);
+	UNPACK64(len_b, ctx->block + pm_len - 8);
 
 	sha256_transf(ctx, ctx->block, block_nb);
 
@@ -414,7 +414,7 @@ void sha384_result(struct sha384_ctx *ctx,
 {
 	unsigned int block_nb;
 	unsigned int pm_len;
-	size_t len_b;
+	uint64_t len_b;
 	int i;
 
 	block_nb = 1 + ((SHA384_BLOCK_SIZE - 17)
@@ -425,7 +425,7 @@ void sha384_result(struct sha384_ctx *ctx,
 
 	memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
 	ctx->block[ctx->len] = 0x80;
-	UNPACK32(len_b, ctx->block + pm_len - 4);
+	UNPACK64(len_b, ctx->block + pm_len - 8);
 
 	sha384_transf(ctx, ctx->block, block_nb);
 
@@ -541,7 +541,7 @@ void sha512_result(struct sha512_ctx *ctx,
 {
 	unsigned int block_nb;
 	unsigned int pm_len;
-	size_t len_b;
+	uint64_t len_b;
 	int i;
 
 	block_nb = 1 + ((SHA512_BLOCK_SIZE - 17)
@@ -552,7 +552,7 @@ void sha512_result(struct sha512_ctx *ctx,
 
 	memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
 	ctx->block[ctx->len] = 0x80;
-	UNPACK32(len_b, ctx->block + pm_len - 4);
+	UNPACK64(len_b, ctx->block + pm_len - 8);
 
 	sha512_transf(ctx, ctx->block, block_nb);
 
diff --git a/src/lib/sha2.h b/src/lib/sha2.h
index 8c893eb3ad..92bd2c74c6 100644
--- a/src/lib/sha2.h
+++ b/src/lib/sha2.h
@@ -38,21 +38,21 @@
 #include "sha-common.h"
 
 struct sha256_ctx {
-	size_t tot_len;
+	uint64_t tot_len;
 	size_t len;
 	unsigned char block[2 * SHA256_BLOCK_SIZE];
 	uint32_t h[8];
 };
 
 struct sha384_ctx {
-	size_t tot_len;
+	uint64_t tot_len;
 	size_t len;
 	unsigned char block[2 * SHA384_BLOCK_SIZE];
 	uint64_t h[8];
 };
 
 struct sha512_ctx {
-	size_t tot_len;
+	uint64_t tot_len;
 	size_t len;
 	unsigned char block[2 * SHA512_BLOCK_SIZE];
 	uint64_t h[8];
diff --git a/src/lib/test-hash-method.c b/src/lib/test-hash-method.c
index 0fd41e0bb4..97db7c8bb6 100644
--- a/src/lib/test-hash-method.c
+++ b/src/lib/test-hash-method.c
@@ -1,6 +1,7 @@
 /* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
 
 #include "test-lib.h"
+#include "hex-binary.h"
 #include "mmap-util.h"
 #include "hash-method.h"
 
@@ -453,8 +454,42 @@ static void test_hash_methods_fips() {
 	test_end();
 }
 
+static void test_hash_methods_large(void)
+{
+	struct {
+		const char *method;
+		const char *hash;
+	} tests[] = {
+		{ "sha256", "1ad0598b790b3acb38876105cc8938c3365f3215fbee3412ac3cd5e96a7dad01" },
+		{ "sha384", "c187c084ffe516fea74b313340a540bc0bab306b1bdc564da21ecdc639e51f194460a0279c04aa40d65cec58698b10c0" },
+		{ "sha512", "556247cfeab056903a3f42cf8496019d9ad90911ded9aa1ede3046b803623e5e2cd2adbd0620e666a927436d125984de9199d643ff21ad1c76e29b116c13ffb2" },
+	};
+	unsigned char data[1024];
+	unsigned int i;
+
+	test_begin("hash method (large inputs)");
+	for (i = 0; i < sizeof(data); i++)
+		data[i] = i & 0xFF;
+
+	for (i = 0; i < N_ELEMENTS(tests); i++) {
+		const struct hash_method *method =
+			hash_method_lookup(tests[i].method);
+		unsigned char context[method->context_size];
+		unsigned char result[method->digest_size];
+
+		method->init(context);
+		for (unsigned int j = 0; j < 600000; j++)
+			method->loop(context, data, sizeof(data));
+		method->result(context, result);
+		test_assert_strcmp_idx(binary_to_hex(result, method->digest_size),
+				       tests[i].hash, i);
+	}
+	test_end();
+}
+
 void test_hash_method(void)
 {
 	test_hash_method_boundary();
 	test_hash_methods_fips();
+	test_hash_methods_large();
 }
diff --git a/src/lib/test-llist.c b/src/lib/test-llist.c
index d57006ce2a..e293eb6a60 100644
--- a/src/lib/test-llist.c
+++ b/src/lib/test-llist.c
@@ -71,7 +71,7 @@ static void test_dllist2(void)
 	l2 = t_new(struct dllist, 1);
 	l1 = t_new(struct dllist, 1);
 
-	test_begin("dllist");
+	test_begin("dllist2");
 	/* prepend to empty */
 	DLLIST2_PREPEND(&head, &tail, l3);
 	test_assert(head == l3 && tail == l3);
@@ -131,8 +131,47 @@ static void test_dllist2(void)
 	test_end();
 }
 
+static void test_dllist2_join(void)
+{
+	struct dllist *head, *tail, *elem[4];
+	struct dllist *head2, *tail2, *elem2[N_ELEMENTS(elem)];
+
+	test_begin("dllist2 join");
+	for (unsigned int i = 0; i < N_ELEMENTS(elem); i++) {
+		elem[i] = t_new(struct dllist, 1);
+		elem2[i] = t_new(struct dllist, 1);
+	}
+	for (unsigned int i = 0; i < N_ELEMENTS(elem); i++) {
+		for (unsigned int j = 0; j < N_ELEMENTS(elem2); j++) {
+			head = tail = head2 = tail2 = NULL;
+			for (unsigned int n = 0; n < i; n++)
+				DLLIST2_APPEND(&head, &tail, elem[n]);
+			for (unsigned int n = 0; n < j; n++)
+				DLLIST2_APPEND(&head2, &tail2, elem2[n]);
+			DLLIST2_JOIN(&head, &tail, &head2, &tail2);
+
+			/* verify */
+			struct dllist *tmp = head, *last = NULL;
+			for (unsigned int n = 0; n < i; n++) {
+				test_assert(tmp == elem[n]);
+				last = tmp;
+				tmp = tmp->next;
+			}
+			for (unsigned int n = 0; n < j; n++) {
+				test_assert(tmp == elem2[n]);
+				last = tmp;
+				tmp = tmp->next;
+			}
+			test_assert(tmp == NULL);
+			test_assert(tail == last);
+		}
+	}
+	test_end();
+}
+
 void test_llist(void)
 {
 	test_dllist();
 	test_dllist2();
+	test_dllist2_join();
 }
diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c
index fc44d2bc29..17765fbfcb 100644
--- a/src/login-common/client-common.c
+++ b/src/login-common/client-common.c
@@ -566,9 +566,14 @@ int client_init_ssl(struct client *client)
 			"Failed to initialize SSL server context: %s", error);
 		return -1;
 	}
-	if (io_stream_create_ssl_server(ssl_ctx, &ssl_set,
-					&client->input, &client->output,
-					&client->ssl_iostream, &error) < 0) {
+	if (client->v.iostream_change_pre != NULL)
+		client->v.iostream_change_pre(client);
+	int ret = io_stream_create_ssl_server(ssl_ctx, &ssl_set,
+					      &client->input, &client->output,
+					      &client->ssl_iostream, &error);
+	if (client->v.iostream_change_post != NULL)
+		client->v.iostream_change_post(client);
+	if (ret < 0) {
 		e_error(client->event,
 			"Failed to initialize SSL connection: %s", error);
 		ssl_iostream_context_unref(&ssl_ctx);
diff --git a/src/login-common/client-common.h b/src/login-common/client-common.h
index a21dea12f0..1bc57f08af 100644
--- a/src/login-common/client-common.h
+++ b/src/login-common/client-common.h
@@ -129,6 +129,14 @@ struct client_vfuncs {
 	void (*notify_starttls)(struct client *client,
 				bool success, const char *text);
 	void (*starttls)(struct client *client);
+
+	/* Called just before client iostreams are changed (e.g. STARTTLS).
+	   iostream_change_post() is guaranteed to be called. */
+	void (*iostream_change_pre)(struct client *client);
+	/* Called just after client iostreams may have changed. Nothing may
+	   have happened in case of unexpected errors. */
+	void (*iostream_change_post)(struct client *client);
+
 	void (*input)(struct client *client);
 	bool (*sasl_filter_mech)(struct client *client,
 				 struct auth_mech_desc *mech);
