]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-mail: message-parser - Support limiting max number of nested MIME parts
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 23 Apr 2020 13:59:40 +0000 (16:59 +0300)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 27 May 2020 05:28:17 +0000 (08:28 +0300)
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.

src/lib-mail/message-parser-private.h
src/lib-mail/message-parser.c
src/lib-mail/message-parser.h
src/lib-mail/test-message-parser.c

index dbf8464cfba09a0f1fe84a9db39767ed59f4f2df..4bb0c3dbfd78a517d2cc837ef9b65571c53fbbdc 100644 (file)
@@ -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;
index 41b9ed133a964c130c3382ca4ab23904c31fa111..44b313f2c7225d3d9ca3acb18258f4339376c239 100644 (file)
@@ -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);
index d159b2607db5436017980274554e704a17c1a2a5..7f6ea04936d9504956691c1aa2158b0359a0154f 100644 (file)
@@ -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;
index 5e496275fecee1ee2d10b4882a050ac92778ba3a..c7236cbe5087842b101ac06b199aa27ff1206422 100644 (file)
@@ -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);