Browse code

bb12380: Added limits to the mailbox parser.

Implemented several maximums in parsing MIME messages to avoid Denial of
Service attempts, as well as improving parsing logic to avoid repeatedly
calling the realloc function. These are in response to excessively long scan
times for specially crafted files.
This is in response to CVE-2019-15961.
The limits added are
1. Limit on number of MIME parts per message.
2. Limit on number of header bytes.
3. Limit on number of email headers.
4. Limit on number of line folds.
5. Limit on numbef of MIME arguments.

Andy Ragusa authored on 2019/11/08 02:10:26
Showing 1 changed files
... ...
@@ -23,7 +23,6 @@
23 23
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 24
  *  MA 02110-1301, USA.
25 25
  */
26
-
27 26
 #if HAVE_CONFIG_H
28 27
 #include "clamav-config.h"
29 28
 #endif
... ...
@@ -201,9 +200,9 @@ typedef struct mbox_ctx {
201 201
 #endif
202 202
 
203 203
 static int cli_parse_mbox(const char *dir, cli_ctx *ctx);
204
-static message *parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821Table, const char *firstLine, const char *dir);
205
-static message *parseEmailHeaders(message *m, const table_t *rfc821Table);
206
-static int parseEmailHeader(message *m, const char *line, const table_t *rfc821Table);
204
+static message *parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821Table, const char *firstLine, const char *dir, cli_ctx *ctx, bool *heuristicFound);
205
+static message *parseEmailHeaders(message *m, const table_t *rfc821Table, bool *heuristicFound);
206
+static int parseEmailHeader(message *m, const char *line, const table_t *rfc821, cli_ctx *ctx, bool *heuristicFound);
207 207
 static int parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata);
208 208
 static mbox_status parseRootMHTML(mbox_ctx *mctx, message *m, text *t);
209 209
 static mbox_status parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int recursion_level);
... ...
@@ -212,7 +211,7 @@ static int boundaryEnd(const char *line, const char *boundary);
212 212
 static int initialiseTables(table_t **rfc821Table, table_t **subtypeTable);
213 213
 static int getTextPart(message *const messages[], size_t size);
214 214
 static size_t strip(char *buf, int len);
215
-static int parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg);
215
+static int parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg, cli_ctx *ctx, bool *heuristicFound);
216 216
 static int saveTextPart(mbox_ctx *mctx, message *m, int destroy_text);
217 217
 static char *rfc2047(const char *in);
218 218
 static char *rfc822comments(const char *in, char *out);
... ...
@@ -233,6 +232,12 @@ static blob *getHrefs(message *m, tag_arguments_t *hrefs);
233 233
 static void hrefs_done(blob *b, tag_arguments_t *hrefs);
234 234
 static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html);
235 235
 
236
+static bool haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx);
237
+static bool hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx);
238
+static bool haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx);
239
+static bool haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx);
240
+static bool haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx);
241
+
236 242
 /* Maximum line length according to RFC2821 */
237 243
 #define RFC2821LENGTH 1000
238 244
 
... ...
@@ -281,6 +286,12 @@ static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html);
281 281
                          */
282 282
 #define KNOWBOT 14      /* Unknown and undocumented format? */
283 283
 
284
+#define HEURISTIC_EMAIL_MAX_LINE_FOLDS_PER_HEADER (256 * 1024)
285
+#define HEURISTIC_EMAIL_MAX_HEADER_BYTES (1024 * 256)
286
+#define HEURISTIC_EMAIL_MAX_HEADERS 1024
287
+#define HEURISTIC_EMAIL_MAX_MIME_PARTS_PER_MESSAGE 1024
288
+#define HEURISTIC_EMAIL_MAX_ARGUMENTS_PER_HEADER 256
289
+
284 290
 static const struct tableinit {
285 291
     const char *key;
286 292
     int value;
... ...
@@ -423,7 +434,7 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx)
423 423
 		 */
424 424
         bool lastLineWasEmpty;
425 425
         int messagenumber;
426
-        message *m = messageCreate();
426
+        message *m = messageCreate(); /*Create an empty email */
427 427
 
428 428
         if (m == NULL)
429 429
             return CL_EMEM;
... ...
@@ -434,15 +445,20 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx)
434 434
 
435 435
         do {
436 436
             cli_chomp(buffer);
437
-            /*if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) {*/
437
+            /*if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) */
438 438
             if (lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0)) {
439 439
                 cli_dbgmsg("Deal with message number %d\n", messagenumber++);
440 440
                 /*
441 441
 				 * End of a message in the mail box
442 442
 				 */
443
-                body = parseEmailHeaders(m, rfc821);
443
+                bool heuristicFound = FALSE;
444
+                body                = parseEmailHeaders(m, rfc821, &heuristicFound);
444 445
                 if (body == NULL) {
445 446
                     messageReset(m);
447
+                    if (heuristicFound) {
448
+                        retcode = CL_VIRUS;
449
+                        break;
450
+                    }
446 451
                     continue;
447 452
                 }
448 453
                 messageSetCTX(body, ctx);
... ...
@@ -493,7 +509,11 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx)
493 493
 
494 494
         if (retcode == CL_SUCCESS) {
495 495
             cli_dbgmsg("Extract attachments from email %d\n", messagenumber);
496
-            body = parseEmailHeaders(m, rfc821);
496
+            bool heuristicFound = FALSE;
497
+            body                = parseEmailHeaders(m, rfc821, &heuristicFound);
498
+            if (heuristicFound) {
499
+                retcode = CL_VIRUS;
500
+            }
497 501
         }
498 502
         if (m)
499 503
             messageDestroy(m);
... ...
@@ -520,7 +540,11 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx)
520 520
 
521 521
         buffer[sizeof(buffer) - 1] = '\0';
522 522
 
523
-        body = parseEmailFile(map, &at, rfc821, buffer, dir);
523
+        bool heuristicFound = FALSE;
524
+        body                = parseEmailFile(map, &at, rfc821, buffer, dir, ctx, &heuristicFound);
525
+        if (heuristicFound) {
526
+            retcode = CL_VIRUS;
527
+        }
524 528
     }
525 529
 
526 530
     if (body) {
... ...
@@ -576,6 +600,253 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx)
576 576
     return retcode;
577 577
 }
578 578
 
579
+/*TODO: move these to a header.*/
580
+#define DO_STRDUP(buf, var) \
581
+    do {                    \
582
+        var = strdup(buf);  \
583
+        if (NULL == var) {  \
584
+            goto done;      \
585
+        }                   \
586
+    } while (0)
587
+
588
+#define DO_FREE(var)       \
589
+    do {                   \
590
+        if (NULL != var) { \
591
+            free(var);     \
592
+            var = NULL;    \
593
+        }                  \
594
+    } while (0)
595
+
596
+#define DO_MALLOC(var, size) \
597
+    do {                     \
598
+        var = malloc(size);  \
599
+        if (NULL == var) {   \
600
+            goto done;       \
601
+        }                    \
602
+    } while (0)
603
+
604
+#define DO_CALLOC(var, size)                   \
605
+    do {                                       \
606
+        (var) = calloc((size), sizeof *(var)); \
607
+        if (NULL == var) {                     \
608
+            goto done;                         \
609
+        }                                      \
610
+    } while (0)
611
+
612
+#define DO_VERIFY_POINTER(ptr) \
613
+    do {                       \
614
+        if (NULL == ptr) {     \
615
+            goto done;         \
616
+        }                      \
617
+    } while (0)
618
+
619
+#define READ_STRUCT_BUFFER_LEN 1024
620
+typedef struct _ReadStruct {
621
+    char buffer[READ_STRUCT_BUFFER_LEN + 1];
622
+
623
+    size_t bufferLen;
624
+
625
+    struct _ReadStruct *next;
626
+
627
+} ReadStruct;
628
+
629
+static ReadStruct *
630
+appendReadStruct(ReadStruct *rs, const char *const buffer)
631
+{
632
+    if (NULL == rs) {
633
+        assert(rs && "Invalid argument");
634
+        goto done;
635
+    }
636
+
637
+    size_t spaceLeft = (READ_STRUCT_BUFFER_LEN - rs->bufferLen);
638
+
639
+    if (strlen(buffer) > spaceLeft) {
640
+        ReadStruct *next = NULL;
641
+        int part = spaceLeft;
642
+        strncpy(&(rs->buffer[rs->bufferLen]), buffer, part);
643
+        rs->bufferLen += part;
644
+
645
+        DO_CALLOC(next, 1);
646
+
647
+        rs->next = next;
648
+        strcpy(next->buffer, &(buffer[part]));
649
+        next->bufferLen = strlen(&(buffer[part]));
650
+
651
+        rs = next;
652
+    } else {
653
+        strcpy(&(rs->buffer[rs->bufferLen]), buffer);
654
+        rs->bufferLen += strlen(buffer);
655
+    }
656
+
657
+done:
658
+    return rs;
659
+}
660
+
661
+static char *
662
+getMallocedBufferFromList(const ReadStruct *head)
663
+{
664
+
665
+    const ReadStruct *rs = head;
666
+    int bufferLen        = 1;
667
+    char *working        = NULL;
668
+    char *ret            = NULL;
669
+
670
+    while (rs) {
671
+        bufferLen += rs->bufferLen;
672
+        rs = rs->next;
673
+    }
674
+
675
+    DO_MALLOC(working, bufferLen);
676
+
677
+    rs        = head;
678
+    bufferLen = 0;
679
+    while (rs) {
680
+        memcpy(&(working[bufferLen]), rs->buffer, rs->bufferLen);
681
+        bufferLen += rs->bufferLen;
682
+        working[bufferLen] = 0;
683
+        rs                 = rs->next;
684
+    }
685
+
686
+    ret = working;
687
+done:
688
+    if (NULL == ret) {
689
+        DO_FREE(working);
690
+    }
691
+
692
+    return ret;
693
+}
694
+
695
+static void
696
+freeList(ReadStruct *head)
697
+{
698
+    while (head) {
699
+        ReadStruct *rs = head->next;
700
+        DO_FREE(head);
701
+        head = rs;
702
+    }
703
+}
704
+
705
+#ifndef FREELIST_REALLOC
706
+#define FREELIST_REALLOC(head, curr) \
707
+    do {                             \
708
+        if (curr != head) {          \
709
+            freeList(head->next);    \
710
+        }                            \
711
+        head->bufferLen = 0;         \
712
+        head->next      = 0;         \
713
+        curr            = head;      \
714
+    } while (0)
715
+#endif /*FREELIST_REALLOC*/
716
+
717
+/*Check if we have repeated blank lines with only a semicolon at the end.  Semicolon is a delimiter for parameters, 
718
+ * but if there is no data, it isn't a parameter.  Allow the first one because it may be continuation of a previous line 
719
+ * that actually had data in it.*/
720
+static bool
721
+doContinueMultipleEmptyOptions(const char *const line, bool *lastWasOnlySemi)
722
+{
723
+    if (line) {
724
+        size_t i   = 0;
725
+        int doCont = 1;
726
+        for (; i < strlen(line); i++) {
727
+            if (isblank(line[i])) {
728
+            } else if (';' == line[i]) {
729
+            } else {
730
+                doCont = 0;
731
+                break;
732
+            }
733
+        }
734
+
735
+        if (1 == doCont) {
736
+            if (*lastWasOnlySemi) {
737
+                return TRUE;
738
+            }
739
+            *lastWasOnlySemi = TRUE;
740
+        } else {
741
+            *lastWasOnlySemi = FALSE;
742
+        }
743
+    }
744
+    return FALSE;
745
+}
746
+
747
+static bool
748
+hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx)
749
+{
750
+
751
+    if (line) {
752
+        if (isblank(line[0])) {
753
+            (*lineFoldCnt)++;
754
+        } else {
755
+            (*lineFoldCnt) = 0;
756
+        }
757
+
758
+        if ((*lineFoldCnt) >= HEURISTIC_EMAIL_MAX_LINE_FOLDS_PER_HEADER) {
759
+            if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) {
760
+                cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxLineFoldCnt");
761
+            }
762
+
763
+            return TRUE;
764
+        }
765
+    }
766
+    return FALSE;
767
+}
768
+
769
+static bool
770
+haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx)
771
+{
772
+
773
+    if (totalLen > HEURISTIC_EMAIL_MAX_HEADER_BYTES) {
774
+        if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) {
775
+            cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxHeaderBytes");
776
+        }
777
+
778
+        return TRUE;
779
+    }
780
+    return FALSE;
781
+}
782
+
783
+static bool
784
+haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx)
785
+{
786
+
787
+    if (totalHeaderCnt > HEURISTIC_EMAIL_MAX_HEADERS) {
788
+        if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) {
789
+            cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxEmailHeaders");
790
+        }
791
+
792
+        return TRUE;
793
+    }
794
+    return FALSE;
795
+}
796
+
797
+static bool
798
+haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx)
799
+{
800
+
801
+    if (mimePartCnt >= HEURISTIC_EMAIL_MAX_MIME_PARTS_PER_MESSAGE) {
802
+        if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) {
803
+            cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxMIMEPartsPerMessage");
804
+        }
805
+
806
+        return TRUE;
807
+    }
808
+    return FALSE;
809
+}
810
+
811
+static bool
812
+haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx)
813
+{
814
+
815
+    if (argCnt >= HEURISTIC_EMAIL_MAX_ARGUMENTS_PER_HEADER) {
816
+        if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) {
817
+            cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxMIMEArguments");
818
+        }
819
+
820
+        return TRUE;
821
+    }
822
+
823
+    return FALSE;
824
+}
825
+
579 826
 /*
580 827
  * Read in an email message from fin, parse it, and return the message
581 828
  *
... ...
@@ -583,7 +854,7 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx)
583 583
  * handled ungracefully...
584 584
  */
585 585
 static message *
586
-parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *firstLine, const char *dir)
586
+parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *firstLine, const char *dir, cli_ctx *ctx, bool *heuristicFound)
587 587
 {
588 588
     bool inHeader     = TRUE;
589 589
     bool bodyIsEmpty  = TRUE;
... ...
@@ -591,9 +862,21 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
591 591
     message *ret;
592 592
     bool anyHeadersFound = FALSE;
593 593
     int commandNumber    = -1;
594
-    char *fullline = NULL, *boundary = NULL;
595
-    size_t fulllinelength = 0;
594
+    char *boundary       = NULL;
596 595
     char buffer[RFC2821LENGTH + 1];
596
+    bool lastWasOnlySemi    = FALSE;
597
+    int err                 = 1;
598
+    size_t totalHeaderBytes = 0;
599
+    size_t totalHeaderCnt   = 0;
600
+
601
+    size_t lineFoldCnt = 0;
602
+
603
+    *heuristicFound = FALSE;
604
+
605
+    ReadStruct *head = NULL;
606
+    ReadStruct *curr = NULL;
607
+    DO_CALLOC(head, 1);
608
+    curr = head;
597 609
 
598 610
     cli_dbgmsg("parseEmailFile\n");
599 611
 
... ...
@@ -612,6 +895,15 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
612 612
         else
613 613
             line = buffer;
614 614
 
615
+        if (doContinueMultipleEmptyOptions(line, &lastWasOnlySemi)) {
616
+            continue;
617
+        }
618
+
619
+        if (hitLineFoldCnt(line, &lineFoldCnt, ctx)) {
620
+            *heuristicFound = TRUE;
621
+            break;
622
+        }
623
+
615 624
         /*
616 625
 		 * Don't blank lines which are only spaces from headers,
617 626
 		 * otherwise they'll be treated as the end of header marker
... ...
@@ -624,8 +916,8 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
624 624
             }
625 625
         }
626 626
         if (inHeader) {
627
-            cli_dbgmsg("parseEmailFile: check '%s' fullline %p\n",
628
-                       buffer, fullline);
627
+            cli_dbgmsg("parseEmailFile: check '%s'\n", buffer);
628
+
629 629
             /*
630 630
 			 * Ensure wide characters are handled where
631 631
 			 * sizeof(char) > 1
... ...
@@ -649,13 +941,30 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
649 649
 					 * content-type line. So we just have
650 650
 					 * to make a best guess. Sigh.
651 651
 					 */
652
-                    if (fullline) {
653
-                        if (parseEmailHeader(ret, fullline, rfc821) < 0)
654
-                            continue;
652
+                    if (head->bufferLen) {
653
+                        char *header     = getMallocedBufferFromList(head);
654
+                        int needContinue = 0;
655
+                        DO_VERIFY_POINTER(header);
656
+
657
+                        totalHeaderCnt++;
658
+                        if (haveTooManyEmailHeaders(totalHeaderCnt, ctx)) {
659
+                            *heuristicFound = TRUE;
660
+                            break;
661
+                        }
662
+                        needContinue = (parseEmailHeader(ret, header, rfc821, ctx, heuristicFound) < 0);
663
+                        if (*heuristicFound) {
664
+                            DO_FREE(header);
665
+                            break;
666
+                        }
655 667
 
656
-                        free(fullline);
657
-                        fullline = NULL;
668
+                        DO_FREE(header);
669
+                        FREELIST_REALLOC(head, curr);
670
+
671
+                        if (needContinue) {
672
+                            continue;
673
+                        }
658 674
                     }
675
+
659 676
                     if (boundary ||
660 677
                         ((boundary = (char *)messageFindArgument(ret, "boundary")) != NULL)) {
661 678
                         lastWasBlank = TRUE;
... ...
@@ -663,7 +972,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
663 663
                     }
664 664
                 }
665 665
             }
666
-            if ((line == NULL) && (fullline == NULL)) { /* empty line */
666
+            if ((line == NULL) && (0 == head->bufferLen)) { /* empty line */
667 667
                 /*
668 668
 				 * A blank line signifies the end of
669 669
 				 * the header and the start of the text
... ...
@@ -678,8 +987,9 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
678 678
             } else {
679 679
                 char *ptr;
680 680
                 const char *lookahead;
681
+                bool lineAdded = TRUE;
681 682
 
682
-                if (fullline == NULL) {
683
+                if (0 == head->bufferLen) {
683 684
                     char cmd[RFC2821LENGTH + 1], out[RFC2821LENGTH + 1];
684 685
 
685 686
                     /*
... ...
@@ -712,23 +1022,26 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
712 712
                                 anyHeadersFound = usefulHeader(commandNumber, cmd);
713 713
                             continue;
714 714
                     }
715
-                    fullline       = cli_strdup(line);
716
-                    fulllinelength = strlen(line) + 1;
717
-                    if (!fullline) {
718
-                        if (ret)
715
+                    curr = appendReadStruct(curr, line);
716
+                    if (NULL == curr) {
717
+                        if (ret) {
719 718
                             ret->isTruncated = TRUE;
719
+                        }
720 720
                         break;
721 721
                     }
722 722
                 } else if (line != NULL) {
723
-                    fulllinelength += strlen(line) + 1;
724
-                    ptr = cli_realloc(fullline, fulllinelength);
725
-                    if (ptr == NULL)
726
-                        continue;
727
-                    fullline = ptr;
728
-                    cli_strlcat(fullline, line, fulllinelength);
723
+                    curr = appendReadStruct(curr, line);
724
+                } else {
725
+                    lineAdded = FALSE;
729 726
                 }
730 727
 
731
-                assert(fullline != NULL);
728
+                if (lineAdded) {
729
+                    totalHeaderBytes += strlen(line);
730
+                    if (haveTooManyHeaderBytes(totalHeaderBytes, ctx)) {
731
+                        *heuristicFound = TRUE;
732
+                        break;
733
+                    }
734
+                }
732 735
 
733 736
                 if ((lookahead = fmap_need_off_once(map, *at, 1))) {
734 737
                     /*
... ...
@@ -746,24 +1059,34 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
746 746
 				 * Handle broken headers, where the next
747 747
 				 * line isn't indented by whitespace
748 748
 				 */
749
-                if (fullline[strlen(fullline) - 1] == ';')
750
-                    /* Add arguments to this line */
751
-                    continue;
749
+                {
750
+                    char *header     = getMallocedBufferFromList(head); /*This is the issue */
751
+                    int needContinue = 0;
752
+                    needContinue     = (header[strlen(header) - 1] == ';');
753
+                    if (0 == needContinue) {
754
+                        needContinue = (line && (count_quotes(header) & 1));
755
+                    }
752 756
 
753
-                if (line && (count_quotes(fullline) & 1))
754
-                    continue;
757
+                    if (0 == needContinue) {
758
+                        totalHeaderCnt++;
759
+                        if (haveTooManyEmailHeaders(totalHeaderCnt, ctx)) {
760
+                            *heuristicFound = TRUE;
761
+                            break;
762
+                        }
763
+                        needContinue = (parseEmailHeader(ret, header, rfc821, ctx, heuristicFound) < 0);
764
+                        if (*heuristicFound) {
765
+                            DO_FREE(header);
766
+                            break;
767
+                        }
768
+                        /*Check total headers here;*/
769
+                    }
755 770
 
756
-                ptr = rfc822comments(fullline, NULL);
757
-                if (ptr) {
758
-                    free(fullline);
759
-                    fullline = ptr;
771
+                    DO_FREE(header);
772
+                    if (needContinue) {
773
+                        continue;
774
+                    }
775
+                    FREELIST_REALLOC(head, curr);
760 776
                 }
761
-
762
-                if (parseEmailHeader(ret, fullline, rfc821) < 0)
763
-                    continue;
764
-
765
-                free(fullline);
766
-                fullline = NULL;
767 777
             }
768 778
         } else if (line && isuuencodebegin(line)) {
769 779
             /*
... ...
@@ -807,19 +1130,17 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
807 807
         }
808 808
     } while (getline_from_mbox(buffer, sizeof(buffer) - 1, map, at) != NULL);
809 809
 
810
-    if (boundary)
811
-        free(boundary);
812
-
813
-    if (fullline) {
814
-        if (*fullline) switch (commandNumber) {
815
-                case CONTENT_TRANSFER_ENCODING:
816
-                case CONTENT_DISPOSITION:
817
-                case CONTENT_TYPE:
818
-                    cli_dbgmsg("parseEmailFile: Fullline unparsed '%s'\n", fullline);
819
-            }
820
-        free(fullline);
810
+    err = 0;
811
+done:
812
+    if (err) {
813
+        cli_errmsg("parseEmailFile: ERROR parsing file\n");
814
+        ret->isTruncated = TRUE;
821 815
     }
822 816
 
817
+    DO_FREE(boundary);
818
+
819
+    freeList(head);
820
+
823 821
     if (!anyHeadersFound) {
824 822
         /*
825 823
 		 * False positive in believing we have an e-mail when we don't
... ...
@@ -829,6 +1150,12 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
829 829
         return NULL;
830 830
     }
831 831
 
832
+    if (*heuristicFound) {
833
+        messageDestroy(ret);
834
+        cli_dbgmsg("parseEmailFile: found heuristic\n");
835
+        return NULL;
836
+    }
837
+
832 838
     cli_dbgmsg("parseEmailFile: return\n");
833 839
 
834 840
     return ret;
... ...
@@ -843,7 +1170,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first
843 843
  * TODO: remove the duplication with parseEmailFile
844 844
  */
845 845
 static message *
846
-parseEmailHeaders(message *m, const table_t *rfc821)
846
+parseEmailHeaders(message *m, const table_t *rfc821, bool *heuristicFound)
847 847
 {
848 848
     bool inHeader    = TRUE;
849 849
     bool bodyIsEmpty = TRUE;
... ...
@@ -853,9 +1180,14 @@ parseEmailHeaders(message *m, const table_t *rfc821)
853 853
     int commandNumber     = -1;
854 854
     char *fullline        = NULL;
855 855
     size_t fulllinelength = 0;
856
+    bool lastWasOnlySemi  = FALSE;
857
+    size_t lineFoldCnt    = 0;
858
+    size_t totalHeaderCnt = 0;
856 859
 
857 860
     cli_dbgmsg("parseEmailHeaders\n");
858 861
 
862
+    *heuristicFound = FALSE;
863
+
859 864
     if (m == NULL)
860 865
         return NULL;
861 866
 
... ...
@@ -869,6 +1201,15 @@ parseEmailHeaders(message *m, const table_t *rfc821)
869 869
         else
870 870
             line = NULL;
871 871
 
872
+        if (doContinueMultipleEmptyOptions(line, &lastWasOnlySemi)) {
873
+            continue;
874
+        }
875
+
876
+        if (hitLineFoldCnt(line, &lineFoldCnt, m->ctx)) {
877
+            *heuristicFound = TRUE;
878
+            break;
879
+        }
880
+
872 881
         if (inHeader) {
873 882
             cli_dbgmsg("parseEmailHeaders: check '%s'\n",
874 883
                        line ? line : "");
... ...
@@ -886,6 +1227,7 @@ parseEmailHeaders(message *m, const table_t *rfc821)
886 886
                 bodyIsEmpty = TRUE;
887 887
             } else {
888 888
                 char *ptr;
889
+                bool lineAdded = TRUE;
889 890
 
890 891
                 if (fullline == NULL) {
891 892
                     char cmd[RFC2821LENGTH + 1];
... ...
@@ -931,8 +1273,21 @@ parseEmailHeaders(message *m, const table_t *rfc821)
931 931
                         continue;
932 932
                     fullline = ptr;
933 933
                     cli_strlcat(fullline, line, fulllinelength);
934
+                } else {
935
+                    lineAdded = FALSE;
934 936
                 }
935 937
                 assert(fullline != NULL);
938
+                /*continue doesn't seem right here, but that is what is done everywhere else when a malloc fails.*/
939
+                if (NULL == fullline) {
940
+                    continue;
941
+                }
942
+
943
+                if (lineAdded) {
944
+                    if (haveTooManyHeaderBytes(fulllinelength, m->ctx)) {
945
+                        *heuristicFound = TRUE;
946
+                        break;
947
+                    }
948
+                }
936 949
 
937 950
                 if (next_is_folded_header(t))
938 951
                     /* Add arguments to this line */
... ...
@@ -950,8 +1305,17 @@ parseEmailHeaders(message *m, const table_t *rfc821)
950 950
                     fullline = ptr;
951 951
                 }
952 952
 
953
-                if (parseEmailHeader(ret, fullline, rfc821) < 0)
953
+                totalHeaderCnt++;
954
+                if (haveTooManyEmailHeaders(totalHeaderCnt, m->ctx)) {
955
+                    *heuristicFound = TRUE;
956
+                    break;
957
+                }
958
+                if (parseEmailHeader(ret, fullline, rfc821, m->ctx, heuristicFound) < 0) {
954 959
                     continue;
960
+                }
961
+                if (*heuristicFound) {
962
+                    break;
963
+                }
955 964
 
956 965
                 free(fullline);
957 966
                 fullline = NULL;
... ...
@@ -997,6 +1361,11 @@ parseEmailHeaders(message *m, const table_t *rfc821)
997 997
         cli_dbgmsg("parseEmailHeaders: no headers found, assuming it isn't an email\n");
998 998
         return NULL;
999 999
     }
1000
+    if (*heuristicFound) {
1001
+        messageDestroy(ret);
1002
+        cli_dbgmsg("parseEmailHeaders: found a heuristic, delete message and stop parsing.\n");
1003
+        return NULL;
1004
+    }
1000 1005
 
1001 1006
     cli_dbgmsg("parseEmailHeaders: return\n");
1002 1007
 
... ...
@@ -1007,9 +1376,9 @@ parseEmailHeaders(message *m, const table_t *rfc821)
1007 1007
  * Handle a header line of an email message
1008 1008
  */
1009 1009
 static int
1010
-parseEmailHeader(message *m, const char *line, const table_t *rfc821)
1010
+parseEmailHeader(message *m, const char *line, const table_t *rfc821, cli_ctx *ctx, bool *heuristicFound)
1011 1011
 {
1012
-    int ret;
1012
+    int ret = -1;
1013 1013
 #ifdef CL_THREAD_SAFE
1014 1014
     char *strptr;
1015 1015
 #endif
... ...
@@ -1032,15 +1401,17 @@ parseEmailHeader(message *m, const char *line, const table_t *rfc821)
1032 1032
         return -1;
1033 1033
 
1034 1034
     copy = rfc2047(line);
1035
-    if (copy == NULL)
1035
+    if (copy == NULL) {
1036 1036
         /* an RFC checker would return -1 here */
1037 1037
         copy = cli_strdup(line);
1038
+        if (NULL == copy) {
1039
+            goto done;
1040
+        }
1041
+    }
1038 1042
 
1039 1043
     tokenseparator[0] = *separator;
1040 1044
     tokenseparator[1] = '\0';
1041 1045
 
1042
-    ret = -1;
1043
-
1044 1046
 #ifdef CL_THREAD_SAFE
1045 1047
     cmd = strtok_r(copy, tokenseparator, &strptr);
1046 1048
 #else
... ...
@@ -1054,7 +1425,7 @@ parseEmailHeader(message *m, const char *line, const table_t *rfc821)
1054 1054
         char *arg = strtok(NULL, "");
1055 1055
 #endif
1056 1056
 
1057
-        if (arg)
1057
+        if (arg) {
1058 1058
             /*
1059 1059
 			 * Found a header such as
1060 1060
 			 * Content-Type: multipart/mixed;
... ...
@@ -1062,9 +1433,12 @@ parseEmailHeader(message *m, const char *line, const table_t *rfc821)
1062 1062
 			 * "multipart/mixed" and cmd to
1063 1063
 			 * be "Content-Type"
1064 1064
 			 */
1065
-            ret = parseMimeHeader(m, cmd, rfc821, arg);
1065
+            ret = parseMimeHeader(m, cmd, rfc821, arg, ctx, heuristicFound);
1066
+        }
1066 1067
     }
1067
-    free(copy);
1068
+done:
1069
+    DO_FREE(copy);
1070
+
1068 1071
     return ret;
1069 1072
 }
1070 1073
 
... ...
@@ -1115,9 +1489,6 @@ parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata
1115 1115
 #if HAVE_LIBXML2
1116 1116
     const char *xmlsrt, *xmlend;
1117 1117
     xmlTextReaderPtr reader;
1118
-#if HAVE_JSON
1119
-    json_object *thisjobj = (json_object *)wrkjobj;
1120
-#endif
1121 1118
     int ret = CL_SUCCESS;
1122 1119
 
1123 1120
     UNUSEDPARAM(cbdata);
... ...
@@ -1310,6 +1681,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1310 1310
 #if HAVE_JSON
1311 1311
     json_object *saveobj = mctx->wrkobj;
1312 1312
 #endif
1313
+    bool heuristicFound = FALSE;
1313 1314
 
1314 1315
     cli_dbgmsg("in parseEmailBody, %u files saved so far\n",
1315 1316
                mctx->files);
... ...
@@ -1391,12 +1763,14 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1391 1391
                 cli_dbgmsg("Not a mime encoded message\n");
1392 1392
                 aText = textAddMessage(aText, mainMessage);
1393 1393
 
1394
-                if (!doPhishingScan)
1394
+                if (!doPhishingScan) {
1395 1395
                     break;
1396
+                }
1396 1397
                 /*
1397 1398
 			 * Fall through: some phishing mails claim they are
1398 1399
 			 * text/plain, when they are in fact html
1399 1400
 			 */
1401
+                /* fall through */
1400 1402
             case TEXT:
1401 1403
                 /* text/plain has been preprocessed as no encoding */
1402 1404
                 if (doPhishingScan) {
... ...
@@ -1605,7 +1979,11 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1605 1605
 						 * Content-Type: application/octet-stream;
1606 1606
 						 * Content-Transfer-Encoding: base64
1607 1607
 						 */
1608
-                            parseEmailHeader(aMessage, line, mctx->rfc821Table);
1608
+                            parseEmailHeader(aMessage, line, mctx->rfc821Table, mctx->ctx, &heuristicFound);
1609
+                            if (heuristicFound) {
1610
+                                rc = VIRUS;
1611
+                                break;
1612
+                            }
1609 1613
 
1610 1614
                             while (isspace((int)*line))
1611 1615
                                 line++;
... ...
@@ -1750,8 +2128,11 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1750 1750
                             cli_dbgmsg("Multipart %d: About to parse folded header '%s'\n",
1751 1751
                                        multiparts, fullline);
1752 1752
 
1753
-                            parseEmailHeader(aMessage, fullline, mctx->rfc821Table);
1753
+                            parseEmailHeader(aMessage, fullline, mctx->rfc821Table, mctx->ctx, &heuristicFound);
1754 1754
                             free(fullline);
1755
+                            if (heuristicFound) {
1756
+                                rc = VIRUS;
1757
+                            }
1755 1758
                         } else if (boundaryEnd(line, boundary)) {
1756 1759
                             /*
1757 1760
 						 * Some viruses put information
... ...
@@ -1828,6 +2209,12 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1828 1828
 
1829 1829
                 free((char *)boundary);
1830 1830
 
1831
+                if (haveTooManyMIMEPartsPerMessage(multiparts, mctx->ctx)) {
1832
+                    DO_FREE(messages);
1833
+                    rc = VIRUS;
1834
+                    break;
1835
+                }
1836
+
1831 1837
                 /*
1832 1838
 			 * Preprocess. Anything special to be done before
1833 1839
 			 * we handle the multiparts?
... ...
@@ -1906,25 +2293,28 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1906 1906
                         htmltextPart = getTextPart(messages, multiparts);
1907 1907
 
1908 1908
                         if (htmltextPart >= 0 && messages) {
1909
-                            if (messageGetBody(messages[htmltextPart]))
1909
+                            if (messageGetBody(messages[htmltextPart])) {
1910 1910
 
1911 1911
                                 aText = textAddMessage(aText, messages[htmltextPart]);
1912
-                        } else
1912
+                            }
1913
+                        } else {
1913 1914
                             /*
1914 1915
 					 * There isn't an HTML bit. If there's a
1915 1916
 					 * multipart bit, it'll may be in there
1916 1917
 					 * somewhere
1917 1918
 					 */
1918
-                            for (i = 0; i < multiparts; i++)
1919
+                            for (i = 0; i < multiparts; i++) {
1919 1920
                                 if (messageGetMimeType(messages[i]) == MULTIPART) {
1920 1921
                                     aMessage     = messages[i];
1921 1922
                                     htmltextPart = i;
1922 1923
                                     break;
1923 1924
                                 }
1925
+                            }
1926
+                        }
1924 1927
 
1925
-                        if (htmltextPart == -1)
1928
+                        if (htmltextPart == -1) {
1926 1929
                             cli_dbgmsg("No HTML code found to be scanned\n");
1927
-                        else {
1930
+                        } else {
1928 1931
 #if HAVE_JSON
1929 1932
                             /* Send root HTML file for preclassification */
1930 1933
                             if (mctx->ctx->wrkproperty)
... ...
@@ -1950,6 +2340,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1950 1950
 				 * Content-Type: multipart/related;
1951 1951
 				 *	type="multipart/alternative"
1952 1952
 				 */
1953
+                        /* fall through */
1953 1954
                     case DIGEST:
1954 1955
                         /*
1955 1956
 				 * According to section 5.1.5 RFC2046, the
... ...
@@ -1971,6 +2362,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
1971 1971
 				 * virus is broken that way, and anyway we
1972 1972
 				 * wish to scan all of the alternatives
1973 1973
 				 */
1974
+                        /* fall through */
1974 1975
                     case REPORT:
1975 1976
                         /*
1976 1977
 				 * According to section 1 of RFC1892, the
... ...
@@ -2081,7 +2473,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
2081 2081
                 rc = FAIL;
2082 2082
                 if ((strcasecmp(mimeSubtype, "rfc822") == 0) ||
2083 2083
                     (strcasecmp(mimeSubtype, "delivery-status") == 0)) {
2084
-                    message *m = parseEmailHeaders(mainMessage, mctx->rfc821Table);
2084
+                    message *m = parseEmailHeaders(mainMessage, mctx->rfc821Table, &heuristicFound);
2085 2085
                     if (m) {
2086 2086
                         cli_dbgmsg("Decode rfc822\n");
2087 2087
 
... ...
@@ -2096,6 +2488,8 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
2096 2096
                             rc = parseEmailBody(m, NULL, mctx, recursion_level + 1);
2097 2097
 
2098 2098
                         messageDestroy(m);
2099
+                    } else if (heuristicFound) {
2100
+                        rc = VIRUS;
2099 2101
                     }
2100 2102
                     break;
2101 2103
                 } else if (strcasecmp(mimeSubtype, "disposition-notification") == 0) {
... ...
@@ -2134,6 +2528,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
2134 2134
 			 * Content-Type: application/unknown;
2135 2135
 			 * so let's try our best to salvage something
2136 2136
 			 */
2137
+                /* fall through */
2137 2138
             case APPLICATION:
2138 2139
                 /*cptr = messageGetMimeSubtype(mainMessage);
2139 2140
 
... ...
@@ -2734,11 +3129,14 @@ strstrip(char *s)
2734 2734
  * Returns 0 for OK, -1 for error
2735 2735
  */
2736 2736
 static int
2737
-parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg)
2737
+parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg, cli_ctx *ctx, bool *heuristicFound)
2738 2738
 {
2739 2739
     char *copy, *p, *buf;
2740 2740
     const char *ptr;
2741 2741
     int commandNumber;
2742
+    size_t argCnt = 0;
2743
+
2744
+    *heuristicFound = FALSE;
2742 2745
 
2743 2746
     cli_dbgmsg("parseMimeHeader: cmd='%s', arg='%s'\n", cmd, arg);
2744 2747
 
... ...
@@ -2746,15 +3144,17 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c
2746 2746
     if (copy) {
2747 2747
         commandNumber = tableFind(rfc821Table, copy);
2748 2748
         free(copy);
2749
-    } else
2749
+    } else {
2750 2750
         commandNumber = tableFind(rfc821Table, cmd);
2751
+    }
2751 2752
 
2752 2753
     copy = rfc822comments(arg, NULL);
2753 2754
 
2754
-    if (copy)
2755
+    if (copy) {
2755 2756
         ptr = copy;
2756
-    else
2757
+    } else {
2757 2758
         ptr = arg;
2759
+    }
2758 2760
 
2759 2761
     buf = NULL;
2760 2762
 
... ...
@@ -2889,6 +3289,11 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c
2889 2889
                 while (cli_strtokbuf(ptr, i++, ";", buf) != NULL) {
2890 2890
                     cli_dbgmsg("mimeArgs = '%s'\n", buf);
2891 2891
 
2892
+                    argCnt++;
2893
+                    if (haveTooManyMIMEArguments(argCnt, ctx)) {
2894
+                        *heuristicFound = TRUE;
2895
+                        break;
2896
+                    }
2892 2897
                     messageAddArguments(m, buf);
2893 2898
                 }
2894 2899
             }
... ...
@@ -3277,7 +3682,7 @@ rfc1341(message *m, const char *dir)
3277 3277
                 while ((dent = readdir(dd))) {
3278 3278
 #endif
3279 3279
                     FILE *fin;
3280
-                    char buffer[BUFSIZ], fullname[NAME_MAX + 1];
3280
+                    char buffer[BUFSIZ], fullname[PATH_MAX + 1];
3281 3281
                     int nblanks;
3282 3282
                     STATBUF statb;
3283 3283
                     const char *dentry_idpart;
... ...
@@ -3802,8 +4207,9 @@ static const char *getMimeTypeStr(mime_type mimetype)
3802 3802
     const struct tableinit *entry = mimeTypeStr;
3803 3803
 
3804 3804
     while (entry->key) {
3805
-        if (mimetype == entry->value)
3805
+        if (mimetype == ((mime_type)entry->value)) {
3806 3806
             return entry->key;
3807
+        }
3807 3808
         entry++;
3808 3809
     }
3809 3810
     return "UNKNOWN";
... ...
@@ -3817,8 +4223,9 @@ static const char *getEncTypeStr(encoding_type enctype)
3817 3817
     const struct tableinit *entry = encTypeStr;
3818 3818
 
3819 3819
     while (entry->key) {
3820
-        if (enctype == entry->value)
3820
+        if (enctype == ((encoding_type)entry->value)) {
3821 3821
             return entry->key;
3822
+        }
3822 3823
         entry++;
3823 3824
     }
3824 3825
     return "UNKNOWN";
... ...
@@ -3838,7 +4245,6 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m
3838 3838
     message *aMessage        = messages[i];
3839 3839
     const int doPhishingScan = mctx->ctx->engine->dboptions & CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE);
3840 3840
 #if HAVE_JSON
3841
-    const char *mtype    = NULL;
3842 3841
     json_object *thisobj = NULL, *saveobj = mctx->wrkobj;
3843 3842
 
3844 3843
     if (mctx->wrkobj != NULL) {
... ...
@@ -4058,8 +4464,9 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m
4058 4058
                 messages[i] = NULL;
4059 4059
             } else {
4060 4060
                 *rc = parseEmailBody(NULL, NULL, mctx, recursion_level + 1);
4061
-                if (mainMessage && (mainMessage != messageIn))
4061
+                if (mainMessage && (mainMessage != messageIn)) {
4062 4062
                     messageDestroy(mainMessage);
4063
+                }
4063 4064
                 mainMessage = NULL;
4064 4065
             }
4065 4066
 #if HAVE_JSON
... ...
@@ -4080,18 +4487,21 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m
4080 4080
 
4081 4081
         if (thisobj != NULL) {
4082 4082
             /* attempt to determine container size - prevents incorrect type reporting */
4083
-            if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj))
4083
+            if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) {
4084 4084
                 arrlen = json_object_array_length(arrobj);
4085
+            }
4085 4086
         }
4086 4087
 
4087 4088
 #endif
4088 4089
         if (fb) {
4089 4090
             /* aMessage doesn't always have a ctx set */
4090 4091
             fileblobSetCTX(fb, mctx->ctx);
4091
-            if (fileblobScanAndDestroy(fb) == CL_VIRUS)
4092
+            if (fileblobScanAndDestroy(fb) == CL_VIRUS) {
4092 4093
                 *rc = VIRUS;
4093
-            if (!addToText)
4094
+            }
4095
+            if (!addToText) {
4094 4096
                 mctx->files++;
4097
+            }
4095 4098
         }
4096 4099
 #if HAVE_JSON
4097 4100
         if (thisobj != NULL) {
... ...
@@ -4099,20 +4509,24 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m
4099 4099
             const char *dtype  = NULL;
4100 4100
 
4101 4101
             /* attempt to acquire container type */
4102
-            if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj))
4103
-                if (json_object_array_length(arrobj) > arrlen)
4102
+            if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) {
4103
+                if (json_object_array_length(arrobj) > ((int)arrlen)) {
4104 4104
                     entry = json_object_array_get_idx(arrobj, arrlen);
4105
+                }
4106
+            }
4105 4107
             if (entry) {
4106 4108
                 json_object_object_get_ex(entry, "FileType", &entry);
4107
-                if (entry)
4109
+                if (entry) {
4108 4110
                     dtype = json_object_get_string(entry);
4111
+                }
4109 4112
             }
4110 4113
             cli_jsonint(thisobj, "ContainedObjectsIndex", arrlen);
4111 4114
             cli_jsonstr(thisobj, "ClamAVFileType", dtype ? dtype : "UNKNOWN");
4112 4115
         }
4113 4116
 #endif
4114
-        if (messageContainsVirus(aMessage))
4117
+        if (messageContainsVirus(aMessage)) {
4115 4118
             *rc = VIRUS;
4119
+        }
4116 4120
     }
4117 4121
     messageDestroy(aMessage);
4118 4122
     messages[i] = NULL;
... ...
@@ -4216,5 +4630,7 @@ newline_in_header(const char *line)
4216 4216
     if (strncmp(line, "Date: ", 6) == 0)
4217 4217
         return TRUE;
4218 4218
 
4219
+    cli_dbgmsg("newline_in_header, returning \"%s\"\n", line);
4220
+
4219 4221
     return FALSE;
4220 4222
 }