From: Timo Sirainen Date: Thu, 23 Apr 2020 13:59:40 +0000 (+0300) Subject: lib-mail: message-parser - Support limiting max number of nested MIME parts X-Git-Tag: 2.3.11.2~46 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e2eb98d00b215e96dbac0723303d8451ada13b69;p=thirdparty%2Fdovecot%2Fcore.git lib-mail: message-parser - Support limiting max number of nested MIME parts The default is to allow 100 nested MIME parts. When the limit is reached, the innermost MIME part's body contains all the rest of the inner bodies until a parent MIME part is reached. --- diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h index dbf8464cfb..4bb0c3dbfd 100644 --- a/src/lib-mail/message-parser-private.h +++ b/src/lib-mail/message-parser-private.h @@ -23,9 +23,11 @@ struct message_parser_ctx { struct istream *input; struct message_part *parts, *part; const char *broken_reason; + unsigned int nested_parts_count; enum message_header_parser_flags hdr_flags; enum message_parser_flags flags; + unsigned int max_nested_mime_parts; char *last_boundary; struct message_boundary *boundaries; diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c index 41b9ed133a..44b313f2c7 100644 --- a/src/lib-mail/message-parser.c +++ b/src/lib-mail/message-parser.c @@ -157,12 +157,17 @@ message_part_append(struct message_parser_ctx *ctx) ctx->next_part = &part->children; ctx->part = part; + ctx->nested_parts_count++; + i_assert(ctx->nested_parts_count < ctx->max_nested_mime_parts); } static void message_part_finish(struct message_parser_ctx *ctx) { struct message_part **const *parent_next_partp; + i_assert(ctx->nested_parts_count > 0); + ctx->nested_parts_count--; + parent_next_partp = array_back(&ctx->next_part_stack); array_pop_back(&ctx->next_part_stack); ctx->next_part = *parent_next_partp; @@ -542,6 +547,11 @@ static bool block_is_at_eoh(const struct message_block *block) return FALSE; } +static bool parse_too_many_nested_mime_parts(struct message_parser_ctx *ctx) +{ + return ctx->nested_parts_count > ctx->max_nested_mime_parts; +} + #define MUTEX_FLAGS \ (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_MULTIPART) @@ -566,8 +576,12 @@ static int parse_next_header(struct message_parser_ctx *ctx, "\n--boundary" belongs to us or to a previous boundary. this is a problem if the boundary prefixes are identical, because MIME requires only the prefix to match. */ - parse_next_body_multipart_init(ctx); - ctx->multipart = TRUE; + if (!parse_too_many_nested_mime_parts(ctx)) { + parse_next_body_multipart_init(ctx); + ctx->multipart = TRUE; + } else { + part->flags &= ~MESSAGE_PART_FLAG_MULTIPART; + } } /* before parsing the header see if we can find a --boundary from here. @@ -671,12 +685,16 @@ static int parse_next_header(struct message_parser_ctx *ctx, i_assert(ctx->last_boundary == NULL); ctx->multipart = FALSE; ctx->parse_next_block = parse_next_body_to_boundary; - } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) + } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 && + !parse_too_many_nested_mime_parts(ctx)) { ctx->parse_next_block = parse_next_body_message_rfc822_init; - else if (ctx->boundaries != NULL) - ctx->parse_next_block = parse_next_body_to_boundary; - else - ctx->parse_next_block = parse_next_body_to_eof; + } else { + part->flags &= ~MESSAGE_PART_FLAG_MESSAGE_RFC822; + if (ctx->boundaries != NULL) + ctx->parse_next_block = parse_next_body_to_boundary; + else + ctx->parse_next_block = parse_next_body_to_eof; + } ctx->want_count = 1; @@ -710,6 +728,9 @@ message_parser_init_int(struct istream *input, ctx = i_new(struct message_parser_ctx, 1); ctx->hdr_flags = set->hdr_flags; ctx->flags = set->flags; + ctx->max_nested_mime_parts = set->max_nested_mime_parts != 0 ? + set->max_nested_mime_parts : + MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS; ctx->input = input; i_stream_ref(input); return ctx; @@ -754,6 +775,8 @@ int message_parser_deinit_from_parts(struct message_parser_ctx **_ctx, if (ctx->hdr_parser_ctx != NULL) message_parse_header_deinit(&ctx->hdr_parser_ctx); boundary_remove_until(ctx, NULL); + i_assert(ctx->nested_parts_count == 0); + i_stream_unref(&ctx->input); array_free(&ctx->next_part_stack); i_free(ctx->last_boundary); diff --git a/src/lib-mail/message-parser.h b/src/lib-mail/message-parser.h index d159b2607d..7f6ea04936 100644 --- a/src/lib-mail/message-parser.h +++ b/src/lib-mail/message-parser.h @@ -17,9 +17,15 @@ enum message_parser_flags { MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES = 0x08 }; +#define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100 + struct message_parser_settings { enum message_header_parser_flags hdr_flags; enum message_parser_flags flags; + + /* Maximum nested MIME parts. + 0 = MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS. */ + unsigned int max_nested_mime_parts; }; struct message_parser_ctx; diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c index 5e496275fe..c7236cbe50 100644 --- a/src/lib-mail/test-message-parser.c +++ b/src/lib-mail/test-message-parser.c @@ -839,6 +839,136 @@ static const char input_msg[] = test_end(); } +static void test_message_parser_mime_part_nested_limit(void) +{ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"1\"\n" +"\n" +"--1\n" +"Content-Type: multipart/mixed; boundary=\"2\"\n" +"\n" +"--2\n" +"Content-Type: text/plain\n" +"\n" +"1\n" +"--2\n" +"Content-Type: text/plain\n" +"\n" +"22\n" +"--1\n" +"Content-Type: text/plain\n" +"\n" +"333\n"; + const struct message_parser_settings parser_set = { + .max_nested_mime_parts = 2, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser mime part nested limit"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; + test_assert(part->children_count == 2); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); + test_assert(part->header_size.virtual_size == 45+2); + test_assert(part->body_size.lines == 15); + test_assert(part->body_size.physical_size == 148); + test_assert(part->body_size.virtual_size == 148+15); + + part = parts->children; + test_assert(part->children_count == 0); + test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); + test_assert(part->header_size.virtual_size == 45+2); + test_assert(part->body_size.lines == 7); + test_assert(part->body_size.physical_size == 64); + test_assert(part->body_size.virtual_size == 64+7); + + part = parts->children->next; + test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 26); + test_assert(part->header_size.virtual_size == 26+2); + test_assert(part->body_size.lines == 1); + test_assert(part->body_size.physical_size == 4); + test_assert(part->body_size.virtual_size == 4+1); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + +static void test_message_parser_mime_part_nested_limit_rfc822(void) +{ +static const char input_msg[] = +"Content-Type: message/rfc822\n" +"\n" +"Content-Type: message/rfc822\n" +"\n" +"Content-Type: text/plain\n" +"\n" +"1\n"; + const struct message_parser_settings parser_set = { + .max_nested_mime_parts = 2, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser mime part nested limit rfc822"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; + test_assert(part->children_count == 1); + test_assert(part->flags == (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 30); + test_assert(part->header_size.virtual_size == 30+2); + test_assert(part->body_size.lines == 5); + test_assert(part->body_size.physical_size == 58); + test_assert(part->body_size.virtual_size == 58+5); + + part = parts->children; + test_assert(part->children_count == 0); + test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 30); + test_assert(part->header_size.virtual_size == 30+2); + test_assert(part->body_size.lines == 3); + test_assert(part->body_size.physical_size == 28); + test_assert(part->body_size.virtual_size == 28+3); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + int main(void) { static void (*const test_functions[])(void) = { @@ -854,6 +984,8 @@ int main(void) test_message_parser_continuing_mime_boundary_reverse, test_message_parser_long_mime_boundary, test_message_parser_no_eoh, + test_message_parser_mime_part_nested_limit, + test_message_parser_mime_part_nested_limit_rfc822, NULL }; return test_run(test_functions);