diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-26 10:32:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-26 10:32:01 +0000 |
commit | 21103af131d3308ef39ad30c66e0fa0ea87b4525 (patch) | |
tree | 1f5669b5b37a04d2d2a6becc2056c5d341d4ab16 /src | |
parent | Adding upstream version 1:2.3.21+dfsg1. (diff) | |
download | dovecot-21103af131d3308ef39ad30c66e0fa0ea87b4525.tar.xz dovecot-21103af131d3308ef39ad30c66e0fa0ea87b4525.zip |
Adding upstream version 1:2.3.21.1+dfsg1.upstream/1%2.3.21.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
25 files changed, 661 insertions, 202 deletions
diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c index b36a4ce..d5ef604 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 dae5632..fc62248 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 87297f4..da31770 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 1f295e5..c9b92b4 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 fb06afa..ae37014 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 8370397..224f7a7 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 c5026f1..5e020bb 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 ce0825c..43cf95e 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 41c32da..8b362a9 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 9a9c9a3..c7e3b1e 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 f19e526..8d70d73 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 a5771f8..25019ab 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 5ff9ffe..7ec878d 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 e6204bb..54aa9a8 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 700d341..93d8842 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 663bfe8..b6bada2 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 bc7779f..04db602 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 da7e5e1..3328ce9 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 8a52e87..5ad5d75 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 93dddfb..b6bef47 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 8c893eb..92bd2c7 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 0fd41e0..97db7c8 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 d57006c..e293eb6 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 fc44d2b..17765fb 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 a21dea1..1bc57f0 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); |