diff --git a/CVE-2019-15961-1.patch b/CVE-2019-15961-1.patch new file mode 100644 index 0000000000000000000000000000000000000000..a2ac841c67ce42a04bb71c18fc4736834ddd3cdb --- /dev/null +++ b/CVE-2019-15961-1.patch @@ -0,0 +1,1106 @@ +From bd7b591bbf27fda6fff092f7f707725088ca8769 Mon Sep 17 00:00:00 2001 +From: Andy Ragusa +Date: Thu, 7 Nov 2019 09:10:26 -0800 +Subject: [PATCH] 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. +--- + libclamav/mbox.c | 610 +++++++++++++++++++++++++++++++++++++++-------- + 1 file changed, 513 insertions(+), 97 deletions(-) + +diff --git a/libclamav/mbox.c b/libclamav/mbox.c +index d4afa78cd2..684f0d7e34 100644 +--- a/libclamav/mbox.c ++++ b/libclamav/mbox.c +@@ -23,7 +23,6 @@ + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +- + #if HAVE_CONFIG_H + #include "clamav-config.h" + #endif +@@ -201,9 +200,9 @@ typedef struct mbox_ctx { + #endif + + static int cli_parse_mbox(const char *dir, cli_ctx *ctx); +-static message *parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821Table, const char *firstLine, const char *dir); +-static message *parseEmailHeaders(message *m, const table_t *rfc821Table); +-static int parseEmailHeader(message *m, const char *line, const table_t *rfc821Table); ++static message *parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821Table, const char *firstLine, const char *dir, cli_ctx *ctx, bool *heuristicFound); ++static message *parseEmailHeaders(message *m, const table_t *rfc821Table, bool *heuristicFound); ++static int parseEmailHeader(message *m, const char *line, const table_t *rfc821, cli_ctx *ctx, bool *heuristicFound); + static int parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata); + static mbox_status parseRootMHTML(mbox_ctx *mctx, message *m, text *t); + 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); + static int initialiseTables(table_t **rfc821Table, table_t **subtypeTable); + static int getTextPart(message *const messages[], size_t size); + static size_t strip(char *buf, int len); +-static int parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg); ++static int parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg, cli_ctx *ctx, bool *heuristicFound); + static int saveTextPart(mbox_ctx *mctx, message *m, int destroy_text); + static char *rfc2047(const char *in); + static char *rfc822comments(const char *in, char *out); +@@ -233,6 +232,12 @@ static blob *getHrefs(message *m, tag_arguments_t *hrefs); + static void hrefs_done(blob *b, tag_arguments_t *hrefs); + static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html); + ++static bool haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx); ++static bool hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx); ++static bool haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx); ++static bool haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx); ++static bool haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx); ++ + /* Maximum line length according to RFC2821 */ + #define RFC2821LENGTH 1000 + +@@ -281,6 +286,12 @@ static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html); + */ + #define KNOWBOT 14 /* Unknown and undocumented format? */ + ++#define HEURISTIC_EMAIL_MAX_LINE_FOLDS_PER_HEADER (256 * 1024) ++#define HEURISTIC_EMAIL_MAX_HEADER_BYTES (1024 * 256) ++#define HEURISTIC_EMAIL_MAX_HEADERS 1024 ++#define HEURISTIC_EMAIL_MAX_MIME_PARTS_PER_MESSAGE 1024 ++#define HEURISTIC_EMAIL_MAX_ARGUMENTS_PER_HEADER 256 ++ + static const struct tableinit { + const char *key; + int value; +@@ -423,7 +434,7 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + */ + bool lastLineWasEmpty; + int messagenumber; +- message *m = messageCreate(); ++ message *m = messageCreate(); /*Create an empty email */ + + if (m == NULL) + return CL_EMEM; +@@ -434,15 +445,20 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + + do { + cli_chomp(buffer); +- /*if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) {*/ ++ /*if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) */ + if (lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0)) { + cli_dbgmsg("Deal with message number %d\n", messagenumber++); + /* + * End of a message in the mail box + */ +- body = parseEmailHeaders(m, rfc821); ++ bool heuristicFound = FALSE; ++ body = parseEmailHeaders(m, rfc821, &heuristicFound); + if (body == NULL) { + messageReset(m); ++ if (heuristicFound) { ++ retcode = CL_VIRUS; ++ break; ++ } + continue; + } + messageSetCTX(body, ctx); +@@ -493,7 +509,11 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + + if (retcode == CL_SUCCESS) { + cli_dbgmsg("Extract attachments from email %d\n", messagenumber); +- body = parseEmailHeaders(m, rfc821); ++ bool heuristicFound = FALSE; ++ body = parseEmailHeaders(m, rfc821, &heuristicFound); ++ if (heuristicFound) { ++ retcode = CL_VIRUS; ++ } + } + if (m) + messageDestroy(m); +@@ -520,7 +540,11 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + + buffer[sizeof(buffer) - 1] = '\0'; + +- body = parseEmailFile(map, &at, rfc821, buffer, dir); ++ bool heuristicFound = FALSE; ++ body = parseEmailFile(map, &at, rfc821, buffer, dir, ctx, &heuristicFound); ++ if (heuristicFound) { ++ retcode = CL_VIRUS; ++ } + } + + if (body) { +@@ -576,6 +600,253 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + return retcode; + } + ++/*TODO: move these to a header.*/ ++#define DO_STRDUP(buf, var) \ ++ do { \ ++ var = strdup(buf); \ ++ if (NULL == var) { \ ++ goto done; \ ++ } \ ++ } while (0) ++ ++#define DO_FREE(var) \ ++ do { \ ++ if (NULL != var) { \ ++ free(var); \ ++ var = NULL; \ ++ } \ ++ } while (0) ++ ++#define DO_MALLOC(var, size) \ ++ do { \ ++ var = malloc(size); \ ++ if (NULL == var) { \ ++ goto done; \ ++ } \ ++ } while (0) ++ ++#define DO_CALLOC(var, size) \ ++ do { \ ++ (var) = calloc((size), sizeof *(var)); \ ++ if (NULL == var) { \ ++ goto done; \ ++ } \ ++ } while (0) ++ ++#define DO_VERIFY_POINTER(ptr) \ ++ do { \ ++ if (NULL == ptr) { \ ++ goto done; \ ++ } \ ++ } while (0) ++ ++#define READ_STRUCT_BUFFER_LEN 1024 ++typedef struct _ReadStruct { ++ char buffer[READ_STRUCT_BUFFER_LEN + 1]; ++ ++ size_t bufferLen; ++ ++ struct _ReadStruct *next; ++ ++} ReadStruct; ++ ++static ReadStruct * ++appendReadStruct(ReadStruct *rs, const char *const buffer) ++{ ++ if (NULL == rs) { ++ assert(rs && "Invalid argument"); ++ goto done; ++ } ++ ++ size_t spaceLeft = (READ_STRUCT_BUFFER_LEN - rs->bufferLen); ++ ++ if (strlen(buffer) > spaceLeft) { ++ ReadStruct *next = NULL; ++ int part = spaceLeft; ++ strncpy(&(rs->buffer[rs->bufferLen]), buffer, part); ++ rs->bufferLen += part; ++ ++ DO_CALLOC(next, 1); ++ ++ rs->next = next; ++ strcpy(next->buffer, &(buffer[part])); ++ next->bufferLen = strlen(&(buffer[part])); ++ ++ rs = next; ++ } else { ++ strcpy(&(rs->buffer[rs->bufferLen]), buffer); ++ rs->bufferLen += strlen(buffer); ++ } ++ ++done: ++ return rs; ++} ++ ++static char * ++getMallocedBufferFromList(const ReadStruct *head) ++{ ++ ++ const ReadStruct *rs = head; ++ int bufferLen = 1; ++ char *working = NULL; ++ char *ret = NULL; ++ ++ while (rs) { ++ bufferLen += rs->bufferLen; ++ rs = rs->next; ++ } ++ ++ DO_MALLOC(working, bufferLen); ++ ++ rs = head; ++ bufferLen = 0; ++ while (rs) { ++ memcpy(&(working[bufferLen]), rs->buffer, rs->bufferLen); ++ bufferLen += rs->bufferLen; ++ working[bufferLen] = 0; ++ rs = rs->next; ++ } ++ ++ ret = working; ++done: ++ if (NULL == ret) { ++ DO_FREE(working); ++ } ++ ++ return ret; ++} ++ ++static void ++freeList(ReadStruct *head) ++{ ++ while (head) { ++ ReadStruct *rs = head->next; ++ DO_FREE(head); ++ head = rs; ++ } ++} ++ ++#ifndef FREELIST_REALLOC ++#define FREELIST_REALLOC(head, curr) \ ++ do { \ ++ if (curr != head) { \ ++ freeList(head->next); \ ++ } \ ++ head->bufferLen = 0; \ ++ head->next = 0; \ ++ curr = head; \ ++ } while (0) ++#endif /*FREELIST_REALLOC*/ ++ ++/*Check if we have repeated blank lines with only a semicolon at the end. Semicolon is a delimiter for parameters, ++ * but if there is no data, it isn't a parameter. Allow the first one because it may be continuation of a previous line ++ * that actually had data in it.*/ ++static bool ++doContinueMultipleEmptyOptions(const char *const line, bool *lastWasOnlySemi) ++{ ++ if (line) { ++ size_t i = 0; ++ int doCont = 1; ++ for (; i < strlen(line); i++) { ++ if (isblank(line[i])) { ++ } else if (';' == line[i]) { ++ } else { ++ doCont = 0; ++ break; ++ } ++ } ++ ++ if (1 == doCont) { ++ if (*lastWasOnlySemi) { ++ return TRUE; ++ } ++ *lastWasOnlySemi = TRUE; ++ } else { ++ *lastWasOnlySemi = FALSE; ++ } ++ } ++ return FALSE; ++} ++ ++static bool ++hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx) ++{ ++ ++ if (line) { ++ if (isblank(line[0])) { ++ (*lineFoldCnt)++; ++ } else { ++ (*lineFoldCnt) = 0; ++ } ++ ++ if ((*lineFoldCnt) >= HEURISTIC_EMAIL_MAX_LINE_FOLDS_PER_HEADER) { ++ if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { ++ cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxLineFoldCnt"); ++ } ++ ++ return TRUE; ++ } ++ } ++ return FALSE; ++} ++ ++static bool ++haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx) ++{ ++ ++ if (totalLen > HEURISTIC_EMAIL_MAX_HEADER_BYTES) { ++ if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { ++ cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxHeaderBytes"); ++ } ++ ++ return TRUE; ++ } ++ return FALSE; ++} ++ ++static bool ++haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx) ++{ ++ ++ if (totalHeaderCnt > HEURISTIC_EMAIL_MAX_HEADERS) { ++ if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { ++ cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxEmailHeaders"); ++ } ++ ++ return TRUE; ++ } ++ return FALSE; ++} ++ ++static bool ++haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx) ++{ ++ ++ if (mimePartCnt >= HEURISTIC_EMAIL_MAX_MIME_PARTS_PER_MESSAGE) { ++ if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { ++ cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxMIMEPartsPerMessage"); ++ } ++ ++ return TRUE; ++ } ++ return FALSE; ++} ++ ++static bool ++haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx) ++{ ++ ++ if (argCnt >= HEURISTIC_EMAIL_MAX_ARGUMENTS_PER_HEADER) { ++ if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { ++ cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxMIMEArguments"); ++ } ++ ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ + /* + * Read in an email message from fin, parse it, and return the message + * +@@ -583,7 +854,7 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + * handled ungracefully... + */ + static message * +-parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *firstLine, const char *dir) ++parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *firstLine, const char *dir, cli_ctx *ctx, bool *heuristicFound) + { + bool inHeader = TRUE; + bool bodyIsEmpty = TRUE; +@@ -591,9 +862,21 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + message *ret; + bool anyHeadersFound = FALSE; + int commandNumber = -1; +- char *fullline = NULL, *boundary = NULL; +- size_t fulllinelength = 0; ++ char *boundary = NULL; + char buffer[RFC2821LENGTH + 1]; ++ bool lastWasOnlySemi = FALSE; ++ int err = 1; ++ size_t totalHeaderBytes = 0; ++ size_t totalHeaderCnt = 0; ++ ++ size_t lineFoldCnt = 0; ++ ++ *heuristicFound = FALSE; ++ ++ ReadStruct *head = NULL; ++ ReadStruct *curr = NULL; ++ DO_CALLOC(head, 1); ++ curr = head; + + cli_dbgmsg("parseEmailFile\n"); + +@@ -612,6 +895,15 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + else + line = buffer; + ++ if (doContinueMultipleEmptyOptions(line, &lastWasOnlySemi)) { ++ continue; ++ } ++ ++ if (hitLineFoldCnt(line, &lineFoldCnt, ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ + /* + * Don't blank lines which are only spaces from headers, + * 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 + } + } + if (inHeader) { +- cli_dbgmsg("parseEmailFile: check '%s' fullline %p\n", +- buffer, fullline); ++ cli_dbgmsg("parseEmailFile: check '%s'\n", buffer); ++ + /* + * Ensure wide characters are handled where + * sizeof(char) > 1 +@@ -649,13 +941,30 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + * content-type line. So we just have + * to make a best guess. Sigh. + */ +- if (fullline) { +- if (parseEmailHeader(ret, fullline, rfc821) < 0) +- continue; ++ if (head->bufferLen) { ++ char *header = getMallocedBufferFromList(head); ++ int needContinue = 0; ++ DO_VERIFY_POINTER(header); ++ ++ totalHeaderCnt++; ++ if (haveTooManyEmailHeaders(totalHeaderCnt, ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ needContinue = (parseEmailHeader(ret, header, rfc821, ctx, heuristicFound) < 0); ++ if (*heuristicFound) { ++ DO_FREE(header); ++ break; ++ } + +- free(fullline); +- fullline = NULL; ++ DO_FREE(header); ++ FREELIST_REALLOC(head, curr); ++ ++ if (needContinue) { ++ continue; ++ } + } ++ + if (boundary || + ((boundary = (char *)messageFindArgument(ret, "boundary")) != NULL)) { + lastWasBlank = TRUE; +@@ -663,7 +972,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + } + } + } +- if ((line == NULL) && (fullline == NULL)) { /* empty line */ ++ if ((line == NULL) && (0 == head->bufferLen)) { /* empty line */ + /* + * A blank line signifies the end of + * 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 + } else { + char *ptr; + const char *lookahead; ++ bool lineAdded = TRUE; + +- if (fullline == NULL) { ++ if (0 == head->bufferLen) { + char cmd[RFC2821LENGTH + 1], out[RFC2821LENGTH + 1]; + + /* +@@ -712,23 +1022,26 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + anyHeadersFound = usefulHeader(commandNumber, cmd); + continue; + } +- fullline = cli_strdup(line); +- fulllinelength = strlen(line) + 1; +- if (!fullline) { +- if (ret) ++ curr = appendReadStruct(curr, line); ++ if (NULL == curr) { ++ if (ret) { + ret->isTruncated = TRUE; ++ } + break; + } + } else if (line != NULL) { +- fulllinelength += strlen(line) + 1; +- ptr = cli_realloc(fullline, fulllinelength); +- if (ptr == NULL) +- continue; +- fullline = ptr; +- cli_strlcat(fullline, line, fulllinelength); ++ curr = appendReadStruct(curr, line); ++ } else { ++ lineAdded = FALSE; + } + +- assert(fullline != NULL); ++ if (lineAdded) { ++ totalHeaderBytes += strlen(line); ++ if (haveTooManyHeaderBytes(totalHeaderBytes, ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ } + + if ((lookahead = fmap_need_off_once(map, *at, 1))) { + /* +@@ -746,24 +1059,34 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + * Handle broken headers, where the next + * line isn't indented by whitespace + */ +- if (fullline[strlen(fullline) - 1] == ';') +- /* Add arguments to this line */ +- continue; ++ { ++ char *header = getMallocedBufferFromList(head); /*This is the issue */ ++ int needContinue = 0; ++ needContinue = (header[strlen(header) - 1] == ';'); ++ if (0 == needContinue) { ++ needContinue = (line && (count_quotes(header) & 1)); ++ } + +- if (line && (count_quotes(fullline) & 1)) +- continue; ++ if (0 == needContinue) { ++ totalHeaderCnt++; ++ if (haveTooManyEmailHeaders(totalHeaderCnt, ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ needContinue = (parseEmailHeader(ret, header, rfc821, ctx, heuristicFound) < 0); ++ if (*heuristicFound) { ++ DO_FREE(header); ++ break; ++ } ++ /*Check total headers here;*/ ++ } + +- ptr = rfc822comments(fullline, NULL); +- if (ptr) { +- free(fullline); +- fullline = ptr; ++ DO_FREE(header); ++ if (needContinue) { ++ continue; ++ } ++ FREELIST_REALLOC(head, curr); + } +- +- if (parseEmailHeader(ret, fullline, rfc821) < 0) +- continue; +- +- free(fullline); +- fullline = NULL; + } + } else if (line && isuuencodebegin(line)) { + /* +@@ -807,19 +1130,17 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + } + } while (getline_from_mbox(buffer, sizeof(buffer) - 1, map, at) != NULL); + +- if (boundary) +- free(boundary); +- +- if (fullline) { +- if (*fullline) switch (commandNumber) { +- case CONTENT_TRANSFER_ENCODING: +- case CONTENT_DISPOSITION: +- case CONTENT_TYPE: +- cli_dbgmsg("parseEmailFile: Fullline unparsed '%s'\n", fullline); +- } +- free(fullline); ++ err = 0; ++done: ++ if (err) { ++ cli_errmsg("parseEmailFile: ERROR parsing file\n"); ++ ret->isTruncated = TRUE; + } + ++ DO_FREE(boundary); ++ ++ freeList(head); ++ + if (!anyHeadersFound) { + /* + * 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 + return NULL; + } + ++ if (*heuristicFound) { ++ messageDestroy(ret); ++ cli_dbgmsg("parseEmailFile: found heuristic\n"); ++ return NULL; ++ } ++ + cli_dbgmsg("parseEmailFile: return\n"); + + return ret; +@@ -843,7 +1170,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + * TODO: remove the duplication with parseEmailFile + */ + static message * +-parseEmailHeaders(message *m, const table_t *rfc821) ++parseEmailHeaders(message *m, const table_t *rfc821, bool *heuristicFound) + { + bool inHeader = TRUE; + bool bodyIsEmpty = TRUE; +@@ -853,9 +1180,14 @@ parseEmailHeaders(message *m, const table_t *rfc821) + int commandNumber = -1; + char *fullline = NULL; + size_t fulllinelength = 0; ++ bool lastWasOnlySemi = FALSE; ++ size_t lineFoldCnt = 0; ++ size_t totalHeaderCnt = 0; + + cli_dbgmsg("parseEmailHeaders\n"); + ++ *heuristicFound = FALSE; ++ + if (m == NULL) + return NULL; + +@@ -869,6 +1201,15 @@ parseEmailHeaders(message *m, const table_t *rfc821) + else + line = NULL; + ++ if (doContinueMultipleEmptyOptions(line, &lastWasOnlySemi)) { ++ continue; ++ } ++ ++ if (hitLineFoldCnt(line, &lineFoldCnt, m->ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ + if (inHeader) { + cli_dbgmsg("parseEmailHeaders: check '%s'\n", + line ? line : ""); +@@ -886,6 +1227,7 @@ parseEmailHeaders(message *m, const table_t *rfc821) + bodyIsEmpty = TRUE; + } else { + char *ptr; ++ bool lineAdded = TRUE; + + if (fullline == NULL) { + char cmd[RFC2821LENGTH + 1]; +@@ -931,8 +1273,21 @@ parseEmailHeaders(message *m, const table_t *rfc821) + continue; + fullline = ptr; + cli_strlcat(fullline, line, fulllinelength); ++ } else { ++ lineAdded = FALSE; + } + assert(fullline != NULL); ++ /*continue doesn't seem right here, but that is what is done everywhere else when a malloc fails.*/ ++ if (NULL == fullline) { ++ continue; ++ } ++ ++ if (lineAdded) { ++ if (haveTooManyHeaderBytes(fulllinelength, m->ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ } + + if (next_is_folded_header(t)) + /* Add arguments to this line */ +@@ -950,8 +1305,17 @@ parseEmailHeaders(message *m, const table_t *rfc821) + fullline = ptr; + } + +- if (parseEmailHeader(ret, fullline, rfc821) < 0) ++ totalHeaderCnt++; ++ if (haveTooManyEmailHeaders(totalHeaderCnt, m->ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } ++ if (parseEmailHeader(ret, fullline, rfc821, m->ctx, heuristicFound) < 0) { + continue; ++ } ++ if (*heuristicFound) { ++ break; ++ } + + free(fullline); + fullline = NULL; +@@ -997,6 +1361,11 @@ parseEmailHeaders(message *m, const table_t *rfc821) + cli_dbgmsg("parseEmailHeaders: no headers found, assuming it isn't an email\n"); + return NULL; + } ++ if (*heuristicFound) { ++ messageDestroy(ret); ++ cli_dbgmsg("parseEmailHeaders: found a heuristic, delete message and stop parsing.\n"); ++ return NULL; ++ } + + cli_dbgmsg("parseEmailHeaders: return\n"); + +@@ -1007,9 +1376,9 @@ parseEmailHeaders(message *m, const table_t *rfc821) + * Handle a header line of an email message + */ + static int +-parseEmailHeader(message *m, const char *line, const table_t *rfc821) ++parseEmailHeader(message *m, const char *line, const table_t *rfc821, cli_ctx *ctx, bool *heuristicFound) + { +- int ret; ++ int ret = -1; + #ifdef CL_THREAD_SAFE + char *strptr; + #endif +@@ -1032,15 +1401,17 @@ parseEmailHeader(message *m, const char *line, const table_t *rfc821) + return -1; + + copy = rfc2047(line); +- if (copy == NULL) ++ if (copy == NULL) { + /* an RFC checker would return -1 here */ + copy = cli_strdup(line); ++ if (NULL == copy) { ++ goto done; ++ } ++ } + + tokenseparator[0] = *separator; + tokenseparator[1] = '\0'; + +- ret = -1; +- + #ifdef CL_THREAD_SAFE + cmd = strtok_r(copy, tokenseparator, &strptr); + #else +@@ -1054,7 +1425,7 @@ parseEmailHeader(message *m, const char *line, const table_t *rfc821) + char *arg = strtok(NULL, ""); + #endif + +- if (arg) ++ if (arg) { + /* + * Found a header such as + * Content-Type: multipart/mixed; +@@ -1062,9 +1433,12 @@ parseEmailHeader(message *m, const char *line, const table_t *rfc821) + * "multipart/mixed" and cmd to + * be "Content-Type" + */ +- ret = parseMimeHeader(m, cmd, rfc821, arg); ++ ret = parseMimeHeader(m, cmd, rfc821, arg, ctx, heuristicFound); ++ } + } +- free(copy); ++done: ++ DO_FREE(copy); ++ + return ret; + } + +@@ -1115,9 +1489,6 @@ parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata + #if HAVE_LIBXML2 + const char *xmlsrt, *xmlend; + xmlTextReaderPtr reader; +-#if HAVE_JSON +- json_object *thisjobj = (json_object *)wrkjobj; +-#endif + int ret = CL_SUCCESS; + + UNUSEDPARAM(cbdata); +@@ -1310,6 +1681,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + #if HAVE_JSON + json_object *saveobj = mctx->wrkobj; + #endif ++ bool heuristicFound = FALSE; + + cli_dbgmsg("in parseEmailBody, %u files saved so far\n", + mctx->files); +@@ -1391,12 +1763,14 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + cli_dbgmsg("Not a mime encoded message\n"); + aText = textAddMessage(aText, mainMessage); + +- if (!doPhishingScan) ++ if (!doPhishingScan) { + break; ++ } + /* + * Fall through: some phishing mails claim they are + * text/plain, when they are in fact html + */ ++ /* fall through */ + case TEXT: + /* text/plain has been preprocessed as no encoding */ + if (doPhishingScan) { +@@ -1605,7 +1979,11 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * Content-Type: application/octet-stream; + * Content-Transfer-Encoding: base64 + */ +- parseEmailHeader(aMessage, line, mctx->rfc821Table); ++ parseEmailHeader(aMessage, line, mctx->rfc821Table, mctx->ctx, &heuristicFound); ++ if (heuristicFound) { ++ rc = VIRUS; ++ break; ++ } + + while (isspace((int)*line)) + line++; +@@ -1750,8 +2128,11 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + cli_dbgmsg("Multipart %d: About to parse folded header '%s'\n", + multiparts, fullline); + +- parseEmailHeader(aMessage, fullline, mctx->rfc821Table); ++ parseEmailHeader(aMessage, fullline, mctx->rfc821Table, mctx->ctx, &heuristicFound); + free(fullline); ++ if (heuristicFound) { ++ rc = VIRUS; ++ } + } else if (boundaryEnd(line, boundary)) { + /* + * Some viruses put information +@@ -1828,6 +2209,12 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + + free((char *)boundary); + ++ if (haveTooManyMIMEPartsPerMessage(multiparts, mctx->ctx)) { ++ DO_FREE(messages); ++ rc = VIRUS; ++ break; ++ } ++ + /* + * Preprocess. Anything special to be done before + * we handle the multiparts? +@@ -1906,25 +2293,28 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + htmltextPart = getTextPart(messages, multiparts); + + if (htmltextPart >= 0 && messages) { +- if (messageGetBody(messages[htmltextPart])) ++ if (messageGetBody(messages[htmltextPart])) { + + aText = textAddMessage(aText, messages[htmltextPart]); +- } else ++ } ++ } else { + /* + * There isn't an HTML bit. If there's a + * multipart bit, it'll may be in there + * somewhere + */ +- for (i = 0; i < multiparts; i++) ++ for (i = 0; i < multiparts; i++) { + if (messageGetMimeType(messages[i]) == MULTIPART) { + aMessage = messages[i]; + htmltextPart = i; + break; + } ++ } ++ } + +- if (htmltextPart == -1) ++ if (htmltextPart == -1) { + cli_dbgmsg("No HTML code found to be scanned\n"); +- else { ++ } else { + #if HAVE_JSON + /* Send root HTML file for preclassification */ + if (mctx->ctx->wrkproperty) +@@ -1950,6 +2340,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * Content-Type: multipart/related; + * type="multipart/alternative" + */ ++ /* fall through */ + case DIGEST: + /* + * According to section 5.1.5 RFC2046, the +@@ -1971,6 +2362,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * virus is broken that way, and anyway we + * wish to scan all of the alternatives + */ ++ /* fall through */ + case REPORT: + /* + * According to section 1 of RFC1892, the +@@ -2081,7 +2473,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + rc = FAIL; + if ((strcasecmp(mimeSubtype, "rfc822") == 0) || + (strcasecmp(mimeSubtype, "delivery-status") == 0)) { +- message *m = parseEmailHeaders(mainMessage, mctx->rfc821Table); ++ message *m = parseEmailHeaders(mainMessage, mctx->rfc821Table, &heuristicFound); + if (m) { + cli_dbgmsg("Decode rfc822\n"); + +@@ -2096,6 +2488,8 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + rc = parseEmailBody(m, NULL, mctx, recursion_level + 1); + + messageDestroy(m); ++ } else if (heuristicFound) { ++ rc = VIRUS; + } + break; + } else if (strcasecmp(mimeSubtype, "disposition-notification") == 0) { +@@ -2134,6 +2528,7 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * Content-Type: application/unknown; + * so let's try our best to salvage something + */ ++ /* fall through */ + case APPLICATION: + /*cptr = messageGetMimeSubtype(mainMessage); + +@@ -2734,11 +3129,14 @@ strstrip(char *s) + * Returns 0 for OK, -1 for error + */ + static int +-parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg) ++parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg, cli_ctx *ctx, bool *heuristicFound) + { + char *copy, *p, *buf; + const char *ptr; + int commandNumber; ++ size_t argCnt = 0; ++ ++ *heuristicFound = FALSE; + + cli_dbgmsg("parseMimeHeader: cmd='%s', arg='%s'\n", cmd, arg); + +@@ -2746,15 +3144,17 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c + if (copy) { + commandNumber = tableFind(rfc821Table, copy); + free(copy); +- } else ++ } else { + commandNumber = tableFind(rfc821Table, cmd); ++ } + + copy = rfc822comments(arg, NULL); + +- if (copy) ++ if (copy) { + ptr = copy; +- else ++ } else { + ptr = arg; ++ } + + buf = NULL; + +@@ -2889,6 +3289,11 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c + while (cli_strtokbuf(ptr, i++, ";", buf) != NULL) { + cli_dbgmsg("mimeArgs = '%s'\n", buf); + ++ argCnt++; ++ if (haveTooManyMIMEArguments(argCnt, ctx)) { ++ *heuristicFound = TRUE; ++ break; ++ } + messageAddArguments(m, buf); + } + } +@@ -3277,7 +3682,7 @@ rfc1341(message *m, const char *dir) + while ((dent = readdir(dd))) { + #endif + FILE *fin; +- char buffer[BUFSIZ], fullname[NAME_MAX + 1]; ++ char buffer[BUFSIZ], fullname[PATH_MAX + 1]; + int nblanks; + STATBUF statb; + const char *dentry_idpart; +@@ -3802,8 +4207,9 @@ static const char *getMimeTypeStr(mime_type mimetype) + const struct tableinit *entry = mimeTypeStr; + + while (entry->key) { +- if (mimetype == entry->value) ++ if (mimetype == ((mime_type)entry->value)) { + return entry->key; ++ } + entry++; + } + return "UNKNOWN"; +@@ -3817,8 +4223,9 @@ static const char *getEncTypeStr(encoding_type enctype) + const struct tableinit *entry = encTypeStr; + + while (entry->key) { +- if (enctype == entry->value) ++ if (enctype == ((encoding_type)entry->value)) { + return entry->key; ++ } + entry++; + } + return "UNKNOWN"; +@@ -3838,7 +4245,6 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + message *aMessage = messages[i]; + const int doPhishingScan = mctx->ctx->engine->dboptions & CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE); + #if HAVE_JSON +- const char *mtype = NULL; + json_object *thisobj = NULL, *saveobj = mctx->wrkobj; + + if (mctx->wrkobj != NULL) { +@@ -4058,8 +4464,9 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + messages[i] = NULL; + } else { + *rc = parseEmailBody(NULL, NULL, mctx, recursion_level + 1); +- if (mainMessage && (mainMessage != messageIn)) ++ if (mainMessage && (mainMessage != messageIn)) { + messageDestroy(mainMessage); ++ } + mainMessage = NULL; + } + #if HAVE_JSON +@@ -4080,18 +4487,21 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + + if (thisobj != NULL) { + /* attempt to determine container size - prevents incorrect type reporting */ +- if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) ++ if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) { + arrlen = json_object_array_length(arrobj); ++ } + } + + #endif + if (fb) { + /* aMessage doesn't always have a ctx set */ + fileblobSetCTX(fb, mctx->ctx); +- if (fileblobScanAndDestroy(fb) == CL_VIRUS) ++ if (fileblobScanAndDestroy(fb) == CL_VIRUS) { + *rc = VIRUS; +- if (!addToText) ++ } ++ if (!addToText) { + mctx->files++; ++ } + } + #if HAVE_JSON + if (thisobj != NULL) { +@@ -4099,20 +4509,24 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + const char *dtype = NULL; + + /* attempt to acquire container type */ +- if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) +- if (json_object_array_length(arrobj) > arrlen) ++ if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) { ++ if (json_object_array_length(arrobj) > ((int)arrlen)) { + entry = json_object_array_get_idx(arrobj, arrlen); ++ } ++ } + if (entry) { + json_object_object_get_ex(entry, "FileType", &entry); +- if (entry) ++ if (entry) { + dtype = json_object_get_string(entry); ++ } + } + cli_jsonint(thisobj, "ContainedObjectsIndex", arrlen); + cli_jsonstr(thisobj, "ClamAVFileType", dtype ? dtype : "UNKNOWN"); + } + #endif +- if (messageContainsVirus(aMessage)) ++ if (messageContainsVirus(aMessage)) { + *rc = VIRUS; ++ } + } + messageDestroy(aMessage); + messages[i] = NULL; +@@ -4216,5 +4630,7 @@ newline_in_header(const char *line) + if (strncmp(line, "Date: ", 6) == 0) + return TRUE; + ++ cli_dbgmsg("newline_in_header, returning \"%s\"\n", line); ++ + return FALSE; + } diff --git a/CVE-2019-15961-2.patch b/CVE-2019-15961-2.patch new file mode 100644 index 0000000000000000000000000000000000000000..5e1f653840317ee554b1b1b662f7fe53daec3737 --- /dev/null +++ b/CVE-2019-15961-2.patch @@ -0,0 +1,199 @@ +From 482fcd413b07e9fd3ef9850e6d01a45f4e187108 Mon Sep 17 00:00:00 2001 +From: Andy Ragusa +Date: Tue, 19 Nov 2019 15:55:47 -0800 +Subject: [PATCH] Modified mbox.c only mark files as infected with heuristic + alerts if heuristic alerts are enabled. + +--- + libclamav/mbox.c | 52 ++++++++++++++++++++++-------------------------- + 1 file changed, 24 insertions(+), 28 deletions(-) + +diff --git a/libclamav/mbox.c b/libclamav/mbox.c +index 684f0d7e34..fc63245255 100644 +--- a/libclamav/mbox.c ++++ b/libclamav/mbox.c +@@ -232,11 +232,11 @@ static blob *getHrefs(message *m, tag_arguments_t *hrefs); + static void hrefs_done(blob *b, tag_arguments_t *hrefs); + static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html); + +-static bool haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx); +-static bool hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx); +-static bool haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx); +-static bool haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx); +-static bool haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx); ++static bool haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx, mbox_status * rc); ++static bool hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx, bool * heuristicFound); ++static bool haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx, bool * heuristicFound); ++static bool haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx, bool * heuristicFound); ++static bool haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx, bool * heuristicFound); + + /* Maximum line length according to RFC2821 */ + #define RFC2821LENGTH 1000 +@@ -769,7 +769,7 @@ doContinueMultipleEmptyOptions(const char *const line, bool *lastWasOnlySemi) + } + + static bool +-hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx) ++hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx, bool * heuristicFound) + { + + if (line) { +@@ -782,6 +782,7 @@ hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx) + if ((*lineFoldCnt) >= HEURISTIC_EMAIL_MAX_LINE_FOLDS_PER_HEADER) { + if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { + cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxLineFoldCnt"); ++ *heuristicFound = TRUE; + } + + return TRUE; +@@ -791,12 +792,13 @@ hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx) + } + + static bool +-haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx) ++haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx, bool * heuristicFound) + { + + if (totalLen > HEURISTIC_EMAIL_MAX_HEADER_BYTES) { + if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { + cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxHeaderBytes"); ++ *heuristicFound = TRUE; + } + + return TRUE; +@@ -805,12 +807,13 @@ haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx) + } + + static bool +-haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx) ++haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx, bool * heuristicFound) + { + + if (totalHeaderCnt > HEURISTIC_EMAIL_MAX_HEADERS) { + if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { + cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxEmailHeaders"); ++ *heuristicFound = TRUE; + } + + return TRUE; +@@ -819,12 +822,13 @@ haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx) + } + + static bool +-haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx) ++haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx, mbox_status * rc) + { + + if (mimePartCnt >= HEURISTIC_EMAIL_MAX_MIME_PARTS_PER_MESSAGE) { + if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { + cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxMIMEPartsPerMessage"); ++ *rc = VIRUS; + } + + return TRUE; +@@ -833,12 +837,13 @@ haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx) + } + + static bool +-haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx) ++haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx, bool * heuristicFound) + { + + if (argCnt >= HEURISTIC_EMAIL_MAX_ARGUMENTS_PER_HEADER) { + if (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS) { + cli_append_virus(ctx, "Heuristics.Email.ExceedsMaxMIMEArguments"); ++ *heuristicFound = TRUE; + } + + return TRUE; +@@ -899,8 +904,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + continue; + } + +- if (hitLineFoldCnt(line, &lineFoldCnt, ctx)) { +- *heuristicFound = TRUE; ++ if (hitLineFoldCnt(line, &lineFoldCnt, ctx, heuristicFound )) { + break; + } + +@@ -947,8 +951,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + DO_VERIFY_POINTER(header); + + totalHeaderCnt++; +- if (haveTooManyEmailHeaders(totalHeaderCnt, ctx)) { +- *heuristicFound = TRUE; ++ if (haveTooManyEmailHeaders(totalHeaderCnt, ctx, heuristicFound)) { + break; + } + needContinue = (parseEmailHeader(ret, header, rfc821, ctx, heuristicFound) < 0); +@@ -1037,8 +1040,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + + if (lineAdded) { + totalHeaderBytes += strlen(line); +- if (haveTooManyHeaderBytes(totalHeaderBytes, ctx)) { +- *heuristicFound = TRUE; ++ if (haveTooManyHeaderBytes(totalHeaderBytes, ctx, heuristicFound)) { + break; + } + } +@@ -1069,8 +1071,7 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + + if (0 == needContinue) { + totalHeaderCnt++; +- if (haveTooManyEmailHeaders(totalHeaderCnt, ctx)) { +- *heuristicFound = TRUE; ++ if (haveTooManyEmailHeaders(totalHeaderCnt, ctx, heuristicFound)) { + break; + } + needContinue = (parseEmailHeader(ret, header, rfc821, ctx, heuristicFound) < 0); +@@ -1205,8 +1206,7 @@ parseEmailHeaders(message *m, const table_t *rfc821, bool *heuristicFound) + continue; + } + +- if (hitLineFoldCnt(line, &lineFoldCnt, m->ctx)) { +- *heuristicFound = TRUE; ++ if (hitLineFoldCnt(line, &lineFoldCnt, m->ctx, heuristicFound)) { + break; + } + +@@ -1283,8 +1283,7 @@ parseEmailHeaders(message *m, const table_t *rfc821, bool *heuristicFound) + } + + if (lineAdded) { +- if (haveTooManyHeaderBytes(fulllinelength, m->ctx)) { +- *heuristicFound = TRUE; ++ if (haveTooManyHeaderBytes(fulllinelength, m->ctx, heuristicFound)) { + break; + } + } +@@ -1306,8 +1305,7 @@ parseEmailHeaders(message *m, const table_t *rfc821, bool *heuristicFound) + } + + totalHeaderCnt++; +- if (haveTooManyEmailHeaders(totalHeaderCnt, m->ctx)) { +- *heuristicFound = TRUE; ++ if (haveTooManyEmailHeaders(totalHeaderCnt, m->ctx, heuristicFound)) { + break; + } + if (parseEmailHeader(ret, fullline, rfc821, m->ctx, heuristicFound) < 0) { +@@ -2209,9 +2207,8 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + + free((char *)boundary); + +- if (haveTooManyMIMEPartsPerMessage(multiparts, mctx->ctx)) { ++ if (haveTooManyMIMEPartsPerMessage(multiparts, mctx->ctx, &rc)) { + DO_FREE(messages); +- rc = VIRUS; + break; + } + +@@ -3290,8 +3287,7 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c + cli_dbgmsg("mimeArgs = '%s'\n", buf); + + argCnt++; +- if (haveTooManyMIMEArguments(argCnt, ctx)) { +- *heuristicFound = TRUE; ++ if (haveTooManyMIMEArguments(argCnt, ctx, heuristicFound )) { + break; + } + messageAddArguments(m, buf); diff --git a/CVE-2019-15961-pre-1.patch b/CVE-2019-15961-pre-1.patch new file mode 100644 index 0000000000000000000000000000000000000000..7e8a54d894cbd1445fb98f64a78c054fa679cd94 --- /dev/null +++ b/CVE-2019-15961-pre-1.patch @@ -0,0 +1,94 @@ +From 4619f636cb3a2df8162a3677b6c2918868a953da Mon Sep 17 00:00:00 2001 +From: Micah Snyder +Date: Thu, 31 Oct 2019 16:05:29 -0400 +Subject: [PATCH] Fixes null-dereference in mail message parser. + +--- + libclamav/mbox.c | 16 ++++++++-------- + libclamav/message.c | 4 +++- + 2 files changed, 11 insertions(+), 9 deletions(-) + +diff --git a/libclamav/mbox.c b/libclamav/mbox.c +index d9746f1e95..7fee0cab4c 100644 +--- a/libclamav/mbox.c ++++ b/libclamav/mbox.c +@@ -3,7 +3,7 @@ + * Copyright (C) 2007-2013 Sourcefire, Inc. + * + * Authors: Nigel Horne +- * ++ * + * Acknowledgements: Some ideas came from Stephen White , + * Michael Dankov , Gianluigi Tiesi , + * Everton da Silva Marques, Thomas Lamy , +@@ -586,7 +586,7 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + */ + messageDestroy(body); + } +- ++ + if((retcode == CL_CLEAN) && ctx->found_possibly_unwanted && + (*ctx->virname == NULL || SCAN_ALLMATCHES)) { + retcode = cli_append_virus(ctx, "Heuristics.Phishing.Email"); +@@ -1840,8 +1840,8 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * must be listed here */ + break; + default: +- /* this is a subtype that we +- * don't handle anyway, ++ /* this is a subtype that we ++ * don't handle anyway, + * don't store */ + if(messages[multiparts]) { + messageDestroy(messages[multiparts]); +@@ -3617,7 +3617,7 @@ getline_from_mbox(char *buffer, size_t buffer_len, fmap_t *map, size_t *at) + src = cursrc = fmap_need_off_once(map, *at, input_len); + + /* we check for eof from the result of GETC() +- * if(feof(fin)) ++ * if(feof(fin)) + return NULL;*/ + if(!src) { + cli_dbgmsg("getline_from_mbox: fmap need failed\n"); +@@ -3629,7 +3629,7 @@ getline_from_mbox(char *buffer, size_t buffer_len, fmap_t *map, size_t *at) + } + + curbuf = buffer; +- ++ + for(i=0; ijobj == NULL) + m->jobj = cli_jsonobj(NULL, NULL); diff --git a/CVE-2019-15961-pre-2.patch b/CVE-2019-15961-pre-2.patch new file mode 100644 index 0000000000000000000000000000000000000000..8f994411dc091d6384fb22742221b2a53ed19612 --- /dev/null +++ b/CVE-2019-15961-pre-2.patch @@ -0,0 +1,6750 @@ +From fd62a171b65353c038cd93455c6493c78acebe26 Mon Sep 17 00:00:00 2001 +From: Andy Ragusa +Date: Tue, 19 Nov 2019 07:33:18 -0800 +Subject: [PATCH] clang-formatted mbox.c + +--- + libclamav/mbox.c | 5547 +++++++++++++++++++++++----------------------- + 1 file changed, 2762 insertions(+), 2785 deletions(-) + +diff --git a/libclamav/mbox.c b/libclamav/mbox.c +index 7fee0cab4c..d4afa78cd2 100644 +--- a/libclamav/mbox.c ++++ b/libclamav/mbox.c +@@ -29,8 +29,8 @@ + #endif + + #ifdef CL_THREAD_SAFE +-#ifndef _REENTRANT +-#define _REENTRANT /* for Solaris 2.8 */ ++#ifndef _REENTRANT ++#define _REENTRANT /* for Solaris 2.8 */ + #endif + #endif + +@@ -39,23 +39,23 @@ + #include + #include + #include +-#ifdef HAVE_STRINGS_H ++#ifdef HAVE_STRINGS_H + #include + #endif +-#ifdef HAVE_STRING_H ++#ifdef HAVE_STRING_H + #include + #endif + #include + #include + #include +-#ifdef HAVE_SYS_PARAM_H ++#ifdef HAVE_SYS_PARAM_H + #include + #endif + #include + #include + #include + +-#ifdef HAVE_UNISTD_H ++#ifdef HAVE_UNISTD_H + #include + #endif + +@@ -63,7 +63,7 @@ + #include + #endif + +-#ifdef CL_THREAD_SAFE ++#ifdef CL_THREAD_SAFE + #include + #endif + +@@ -86,9 +86,9 @@ + + #define DCONF_PHISHING mctx->ctx->dconf->phishing + +-#ifdef CL_DEBUG ++#ifdef CL_DEBUG + +-#if defined(C_LINUX) ++#if defined(C_LINUX) + #include + #endif + +@@ -101,52 +101,53 @@ + #include + #include + +-static void sigsegv(int sig); +-static void print_trace(int use_syslog); ++static void sigsegv(int sig); ++static void print_trace(int use_syslog); + +-/*#define SAVE_TMP */ /* Save the file being worked on in tmp */ ++/*#define SAVE_TMP */ /* Save the file being worked on in tmp */ + #endif + +-#if defined(NO_STRTOK_R) || !defined(CL_THREAD_SAFE) ++#if defined(NO_STRTOK_R) || !defined(CL_THREAD_SAFE) + #undef strtok_r + #undef __strtok_r +-#define strtok_r(a,b,c) strtok(a,b) ++#define strtok_r(a, b, c) strtok(a, b) + #endif + +-#ifdef HAVE_STDBOOL_H +-#ifdef C_BEOS ++#ifdef HAVE_STDBOOL_H ++#ifdef C_BEOS + #include "SupportDefs.h" + #else + #include + #endif + #else +-#ifdef FALSE +-typedef unsigned char bool; ++#ifdef FALSE ++typedef unsigned char bool; + #else +-typedef enum { FALSE = 0, TRUE = 1 } bool; ++typedef enum { FALSE = 0, ++ TRUE = 1 } bool; + #endif + #endif + +-typedef enum { +- FAIL, +- OK, +- OK_ATTACHMENTS_NOT_SAVED, +- VIRUS, +- MAXREC, +- MAXFILES ++typedef enum { ++ FAIL, ++ OK, ++ OK_ATTACHMENTS_NOT_SAVED, ++ VIRUS, ++ MAXREC, ++ MAXFILES + } mbox_status; + + #ifndef isblank +-#define isblank(c) (((c) == ' ') || ((c) == '\t')) ++#define isblank(c) (((c) == ' ') || ((c) == '\t')) + #endif + +-#define SAVE_TO_DISC /* multipart/message are saved in a temporary file */ ++#define SAVE_TO_DISC /* multipart/message are saved in a temporary file */ + + #include "htmlnorm.h" + + #include "phishcheck.h" + +-#ifndef _WIN32 ++#ifndef _WIN32 + #include + #include + #include +@@ -170,19 +171,19 @@ typedef enum { + */ + /*#define NEW_WORLD*/ + +-/*#define SCAN_UNENCODED_BOUNCES *//* ++/*#define SCAN_UNENCODED_BOUNCES */ /* + * Slows things down a lot and only catches unencoded copies + * of EICAR within bounces, which don't matter + */ + +-typedef struct mbox_ctx { +- const char *dir; +- const table_t *rfc821Table; +- const table_t *subtypeTable; +- cli_ctx *ctx; +- unsigned int files; /* number of files extracted */ ++typedef struct mbox_ctx { ++ const char *dir; ++ const table_t *rfc821Table; ++ const table_t *subtypeTable; ++ cli_ctx *ctx; ++ unsigned int files; /* number of files extracted */ + #if HAVE_JSON +- json_object *wrkobj; ++ json_object *wrkobj; + #endif + } mbox_ctx; + +@@ -199,157 +200,134 @@ typedef struct mbox_ctx { + #define UNLOCKFILE(fp) + #endif + +-static int cli_parse_mbox(const char *dir, cli_ctx *ctx); +-static message *parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821Table, const char *firstLine, const char *dir); +-static message *parseEmailHeaders(message *m, const table_t *rfc821Table); +-static int parseEmailHeader(message *m, const char *line, const table_t *rfc821Table); +-static int parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata); +-static mbox_status parseRootMHTML(mbox_ctx *mctx, message *m, text *t); +-static mbox_status parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int recursion_level); +-static int boundaryStart(const char *line, const char *boundary); +-static int boundaryEnd(const char *line, const char *boundary); +-static int initialiseTables(table_t **rfc821Table, table_t **subtypeTable); +-static int getTextPart(message *const messages[], size_t size); +-static size_t strip(char *buf, int len); +-static int parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg); +-static int saveTextPart(mbox_ctx *mctx, message *m, int destroy_text); +-static char *rfc2047(const char *in); +-static char *rfc822comments(const char *in, char *out); +-static int rfc1341(message *m, const char *dir); +-static bool usefulHeader(int commandNumber, const char *cmd); +-static char *getline_from_mbox(char *buffer, size_t len, fmap_t *map, size_t *at); +-static bool isBounceStart(mbox_ctx *mctx, const char *line); +-static bool exportBinhexMessage(mbox_ctx *mctx, message *m); +-static int exportBounceMessage(mbox_ctx *ctx, text *start); +-static const char *getMimeTypeStr(mime_type mimetype); +-static const char *getEncTypeStr(encoding_type enctype); +-static message *do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, mbox_ctx *mctx, message *messageIn, text **tptr, unsigned int recursion_level); +-static int count_quotes(const char *buf); +-static bool next_is_folded_header(const text *t); +-static bool newline_in_header(const char *line); +- +-static blob *getHrefs(message *m, tag_arguments_t *hrefs); +-static void hrefs_done(blob *b, tag_arguments_t *hrefs); +-static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html); ++static int cli_parse_mbox(const char *dir, cli_ctx *ctx); ++static message *parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821Table, const char *firstLine, const char *dir); ++static message *parseEmailHeaders(message *m, const table_t *rfc821Table); ++static int parseEmailHeader(message *m, const char *line, const table_t *rfc821Table); ++static int parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata); ++static mbox_status parseRootMHTML(mbox_ctx *mctx, message *m, text *t); ++static mbox_status parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int recursion_level); ++static int boundaryStart(const char *line, const char *boundary); ++static int boundaryEnd(const char *line, const char *boundary); ++static int initialiseTables(table_t **rfc821Table, table_t **subtypeTable); ++static int getTextPart(message *const messages[], size_t size); ++static size_t strip(char *buf, int len); ++static int parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg); ++static int saveTextPart(mbox_ctx *mctx, message *m, int destroy_text); ++static char *rfc2047(const char *in); ++static char *rfc822comments(const char *in, char *out); ++static int rfc1341(message *m, const char *dir); ++static bool usefulHeader(int commandNumber, const char *cmd); ++static char *getline_from_mbox(char *buffer, size_t len, fmap_t *map, size_t *at); ++static bool isBounceStart(mbox_ctx *mctx, const char *line); ++static bool exportBinhexMessage(mbox_ctx *mctx, message *m); ++static int exportBounceMessage(mbox_ctx *ctx, text *start); ++static const char *getMimeTypeStr(mime_type mimetype); ++static const char *getEncTypeStr(encoding_type enctype); ++static message *do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, mbox_ctx *mctx, message *messageIn, text **tptr, unsigned int recursion_level); ++static int count_quotes(const char *buf); ++static bool next_is_folded_header(const text *t); ++static bool newline_in_header(const char *line); ++ ++static blob *getHrefs(message *m, tag_arguments_t *hrefs); ++static void hrefs_done(blob *b, tag_arguments_t *hrefs); ++static void checkURLs(message *m, mbox_ctx *mctx, mbox_status *rc, int is_html); + + /* Maximum line length according to RFC2821 */ +-#define RFC2821LENGTH 1000 ++#define RFC2821LENGTH 1000 + + /* Hashcodes for our hash tables */ +-#define CONTENT_TYPE 1 +-#define CONTENT_TRANSFER_ENCODING 2 +-#define CONTENT_DISPOSITION 3 ++#define CONTENT_TYPE 1 ++#define CONTENT_TRANSFER_ENCODING 2 ++#define CONTENT_DISPOSITION 3 + + /* Mime sub types */ +-#define PLAIN 1 +-#define ENRICHED 2 +-#define HTML 3 +-#define RICHTEXT 4 +-#define MIXED 5 +-#define ALTERNATIVE 6 /* RFC1521*/ +-#define DIGEST 7 +-#define SIGNED 8 +-#define PARALLEL 9 +-#define RELATED 10 /* RFC2387 */ +-#define REPORT 11 /* RFC1892 */ +-#define APPLEDOUBLE 12 /* Handling of this in only noddy for now */ +-#define FAX MIXED /* +- * RFC3458 +- * Drafts stated to treat is as mixed if it is +- * not known. This disappeared in the final +- * version (except when talking about +- * voice-message), but it is good enough for us +- * since we do no validation of coversheet +- * presence etc. (which also has disappeared +- * in the final version) +- */ +-#define ENCRYPTED 13 /* +- * e.g. RFC2015 +- * Content-Type: multipart/encrypted; +- * boundary="nextPart1383049.XCRrrar2yq"; +- * protocol="application/pgp-encrypted" +- */ +-#define X_BFILE RELATED /* +- * BeOS, expert two parts: the file and it's +- * attributes. The attributes part comes as +- * Content-Type: application/x-be_attribute +- * name="foo" +- * I can't find where it is defined, any +- * pointers would be appreciated. For now +- * we treat it as multipart/related +- */ +-#define KNOWBOT 14 /* Unknown and undocumented format? */ +- +-static const struct tableinit { +- const char *key; +- int value; ++#define PLAIN 1 ++#define ENRICHED 2 ++#define HTML 3 ++#define RICHTEXT 4 ++#define MIXED 5 ++#define ALTERNATIVE 6 /* RFC1521*/ ++#define DIGEST 7 ++#define SIGNED 8 ++#define PARALLEL 9 ++#define RELATED 10 /* RFC2387 */ ++#define REPORT 11 /* RFC1892 */ ++#define APPLEDOUBLE 12 /* Handling of this in only noddy for now */ ++#define FAX MIXED /* \ ++ * RFC3458 \ ++ * Drafts stated to treat is as mixed if it is \ ++ * not known. This disappeared in the final \ ++ * version (except when talking about \ ++ * voice-message), but it is good enough for us \ ++ * since we do no validation of coversheet \ ++ * presence etc. (which also has disappeared \ ++ * in the final version) \ ++ */ ++#define ENCRYPTED 13 /* \ ++ * e.g. RFC2015 \ ++ * Content-Type: multipart/encrypted; \ ++ * boundary="nextPart1383049.XCRrrar2yq"; \ ++ * protocol="application/pgp-encrypted" \ ++ */ ++#define X_BFILE RELATED /* \ ++ * BeOS, expert two parts: the file and it's \ ++ * attributes. The attributes part comes as \ ++ * Content-Type: application/x-be_attribute \ ++ * name="foo" \ ++ * I can't find where it is defined, any \ ++ * pointers would be appreciated. For now \ ++ * we treat it as multipart/related \ ++ */ ++#define KNOWBOT 14 /* Unknown and undocumented format? */ ++ ++static const struct tableinit { ++ const char *key; ++ int value; + } rfc821headers[] = { +- /* TODO: make these regular expressions */ +- { "Content-Type", CONTENT_TYPE }, +- { "Content-Transfer-Encoding", CONTENT_TRANSFER_ENCODING }, +- { "Content-Disposition", CONTENT_DISPOSITION }, +- { NULL, 0 } +-}, mimeSubtypes[] = { /* see RFC2045 */ +- /* subtypes of Text */ +- { "plain", PLAIN }, +- { "enriched", ENRICHED }, +- { "html", HTML }, +- { "richtext", RICHTEXT }, +- /* subtypes of Multipart */ +- { "mixed", MIXED }, +- { "alternative", ALTERNATIVE }, +- { "digest", DIGEST }, +- { "signed", SIGNED }, +- { "parallel", PARALLEL }, +- { "related", RELATED }, +- { "report", REPORT }, +- { "appledouble", APPLEDOUBLE }, +- { "fax-message", FAX }, +- { "encrypted", ENCRYPTED }, +- { "x-bfile", X_BFILE }, /* BeOS */ +- { "knowbot", KNOWBOT }, /* ??? */ +- { "knowbot-metadata", KNOWBOT }, /* ??? */ +- { "knowbot-code", KNOWBOT }, /* ??? */ +- { "knowbot-state", KNOWBOT }, /* ??? */ +- { NULL, 0 } +-}, mimeTypeStr[] = { +- { "NOMIME", NOMIME }, +- { "APPLICATION", APPLICATION }, +- { "AUDIO", AUDIO }, +- { "IMAGE", IMAGE }, +- { "MESSAGE", MESSAGE }, +- { "MULTIPART", MULTIPART }, +- { "TEXT", TEXT }, +- { "VIDEO", VIDEO }, +- { "MEXTENSION", MEXTENSION }, +- { NULL, 0 } +-}, encTypeStr[] = { +- { "NOENCODING", NOENCODING }, +- { "QUOTEDPRINTABLE", QUOTEDPRINTABLE }, +- { "BASE64", BASE64 }, +- { "EIGHTBIT", EIGHTBIT }, +- { "BINARY", BINARY }, +- { "UUENCODE", UUENCODE }, +- { "YENCODE", YENCODE }, +- { "EEXTENSION", EEXTENSION }, +- { "BINHEX", BINHEX }, +- { NULL, 0 } +-}; +- +-#ifdef CL_THREAD_SAFE +-static pthread_mutex_t tables_mutex = PTHREAD_MUTEX_INITIALIZER; ++ /* TODO: make these regular expressions */ ++ {"Content-Type", CONTENT_TYPE}, ++ {"Content-Transfer-Encoding", CONTENT_TRANSFER_ENCODING}, ++ {"Content-Disposition", CONTENT_DISPOSITION}, ++ {NULL, 0}}, ++ mimeSubtypes[] = {/* see RFC2045 */ ++ /* subtypes of Text */ ++ {"plain", PLAIN}, ++ {"enriched", ENRICHED}, ++ {"html", HTML}, ++ {"richtext", RICHTEXT}, ++ /* subtypes of Multipart */ ++ {"mixed", MIXED}, ++ {"alternative", ALTERNATIVE}, ++ {"digest", DIGEST}, ++ {"signed", SIGNED}, ++ {"parallel", PARALLEL}, ++ {"related", RELATED}, ++ {"report", REPORT}, ++ {"appledouble", APPLEDOUBLE}, ++ {"fax-message", FAX}, ++ {"encrypted", ENCRYPTED}, ++ {"x-bfile", X_BFILE}, /* BeOS */ ++ {"knowbot", KNOWBOT}, /* ??? */ ++ {"knowbot-metadata", KNOWBOT}, /* ??? */ ++ {"knowbot-code", KNOWBOT}, /* ??? */ ++ {"knowbot-state", KNOWBOT}, /* ??? */ ++ {NULL, 0}}, ++ mimeTypeStr[] = {{"NOMIME", NOMIME}, {"APPLICATION", APPLICATION}, {"AUDIO", AUDIO}, {"IMAGE", IMAGE}, {"MESSAGE", MESSAGE}, {"MULTIPART", MULTIPART}, {"TEXT", TEXT}, {"VIDEO", VIDEO}, {"MEXTENSION", MEXTENSION}, {NULL, 0}}, encTypeStr[] = {{"NOENCODING", NOENCODING}, {"QUOTEDPRINTABLE", QUOTEDPRINTABLE}, {"BASE64", BASE64}, {"EIGHTBIT", EIGHTBIT}, {"BINARY", BINARY}, {"UUENCODE", UUENCODE}, {"YENCODE", YENCODE}, {"EEXTENSION", EEXTENSION}, {"BINHEX", BINHEX}, {NULL, 0}}; ++ ++#ifdef CL_THREAD_SAFE ++static pthread_mutex_t tables_mutex = PTHREAD_MUTEX_INITIALIZER; + #endif +-static table_t *rfc821 = NULL; +-static table_t *subtype = NULL; ++static table_t *rfc821 = NULL; ++static table_t *subtype = NULL; + +-int +-cli_mbox(const char *dir, cli_ctx *ctx) ++int cli_mbox(const char *dir, cli_ctx *ctx) + { +- if(dir == NULL) { +- cli_dbgmsg("cli_mbox called with NULL dir\n"); +- return CL_ENULLARG; +- } +- return cli_parse_mbox(dir, ctx); ++ if (dir == NULL) { ++ cli_dbgmsg("cli_mbox called with NULL dir\n"); ++ return CL_ENULLARG; ++ } ++ return cli_parse_mbox(dir, ctx); + } + + /* +@@ -370,51 +348,51 @@ cli_mbox(const char *dir, cli_ctx *ctx) + static int + cli_parse_mbox(const char *dir, cli_ctx *ctx) + { +- int retcode; +- message *body; +- char buffer[RFC2821LENGTH + 1]; +- mbox_ctx mctx; +- size_t at = 0; +- fmap_t *map = *ctx->fmap; +- +- cli_dbgmsg("in mbox()\n"); +- +- if(!fmap_gets(map, buffer, &at, sizeof(buffer) - 1)) { +- /* empty message */ +- return CL_CLEAN; +- } +-#ifdef CL_THREAD_SAFE +- pthread_mutex_lock(&tables_mutex); ++ int retcode; ++ message *body; ++ char buffer[RFC2821LENGTH + 1]; ++ mbox_ctx mctx; ++ size_t at = 0; ++ fmap_t *map = *ctx->fmap; ++ ++ cli_dbgmsg("in mbox()\n"); ++ ++ if (!fmap_gets(map, buffer, &at, sizeof(buffer) - 1)) { ++ /* empty message */ ++ return CL_CLEAN; ++ } ++#ifdef CL_THREAD_SAFE ++ pthread_mutex_lock(&tables_mutex); + #endif +- if(rfc821 == NULL) { +- assert(subtype == NULL); +- +- if(initialiseTables(&rfc821, &subtype) < 0) { +- rfc821 = NULL; +- subtype = NULL; +-#ifdef CL_THREAD_SAFE +- pthread_mutex_unlock(&tables_mutex); ++ if (rfc821 == NULL) { ++ assert(subtype == NULL); ++ ++ if (initialiseTables(&rfc821, &subtype) < 0) { ++ rfc821 = NULL; ++ subtype = NULL; ++#ifdef CL_THREAD_SAFE ++ pthread_mutex_unlock(&tables_mutex); + #endif +- return CL_EMEM; +- } +- } +-#ifdef CL_THREAD_SAFE +- pthread_mutex_unlock(&tables_mutex); ++ return CL_EMEM; ++ } ++ } ++#ifdef CL_THREAD_SAFE ++ pthread_mutex_unlock(&tables_mutex); + #endif + +- retcode = CL_SUCCESS; +- body = NULL; ++ retcode = CL_SUCCESS; ++ body = NULL; + +- mctx.dir = dir; +- mctx.rfc821Table = rfc821; +- mctx.subtypeTable = subtype; +- mctx.ctx = ctx; +- mctx.files = 0; ++ mctx.dir = dir; ++ mctx.rfc821Table = rfc821; ++ mctx.subtypeTable = subtype; ++ mctx.ctx = ctx; ++ mctx.files = 0; + #if HAVE_JSON +- mctx.wrkobj = ctx->wrkproperty; ++ mctx.wrkobj = ctx->wrkproperty; + #endif + +- /* ++ /* + * Is it a UNIX style mbox with more than one + * mail message, or just a single mail message? + * +@@ -423,9 +401,9 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + * than one message is handled, e.g. giving a better indication of + * which message within the mailbox is infected + */ +- /*if((strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) {*/ +- if(strncmp(buffer, "From ", 5) == 0) { +- /* ++ /*if((strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) {*/ ++ if (strncmp(buffer, "From ", 5) == 0) { ++ /* + * Have been asked to check a UNIX style mbox file, which + * may contain more than one e-mail message to decode + * +@@ -443,47 +421,47 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + * This would remove a problem with this code that it can + * fill up the tmp directory before it starts scanning + */ +- bool lastLineWasEmpty; +- int messagenumber; +- message *m = messageCreate(); +- +- if(m == NULL) +- return CL_EMEM; +- +- lastLineWasEmpty = FALSE; +- messagenumber = 1; +- messageSetCTX(m, ctx); +- +- do { +- cli_chomp(buffer); +- /*if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) {*/ +- if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0)) { +- cli_dbgmsg("Deal with message number %d\n", messagenumber++); +- /* ++ bool lastLineWasEmpty; ++ int messagenumber; ++ message *m = messageCreate(); ++ ++ if (m == NULL) ++ return CL_EMEM; ++ ++ lastLineWasEmpty = FALSE; ++ messagenumber = 1; ++ messageSetCTX(m, ctx); ++ ++ do { ++ cli_chomp(buffer); ++ /*if(lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0) && isalnum(buffer[5])) {*/ ++ if (lastLineWasEmpty && (strncmp(buffer, "From ", 5) == 0)) { ++ cli_dbgmsg("Deal with message number %d\n", messagenumber++); ++ /* + * End of a message in the mail box + */ +- body = parseEmailHeaders(m, rfc821); +- if(body == NULL) { +- messageReset(m); +- continue; +- } +- messageSetCTX(body, ctx); +- messageDestroy(m); +- if(messageGetBody(body)) { +- mbox_status rc = parseEmailBody(body, NULL, &mctx, 0); +- if(rc == FAIL) { +- messageReset(body); +- m = body; +- continue; +- } else if(rc == VIRUS) { +- cli_dbgmsg("Message number %d is infected\n", +- messagenumber-1); +- retcode = CL_VIRUS; +- m = NULL; +- break; +- } +- } +- /* ++ body = parseEmailHeaders(m, rfc821); ++ if (body == NULL) { ++ messageReset(m); ++ continue; ++ } ++ messageSetCTX(body, ctx); ++ messageDestroy(m); ++ if (messageGetBody(body)) { ++ mbox_status rc = parseEmailBody(body, NULL, &mctx, 0); ++ if (rc == FAIL) { ++ messageReset(body); ++ m = body; ++ continue; ++ } else if (rc == VIRUS) { ++ cli_dbgmsg("Message number %d is infected\n", ++ messagenumber - 1); ++ retcode = CL_VIRUS; ++ m = NULL; ++ break; ++ } ++ } ++ /* + * Starting a new message, throw away all the + * information about the old one. It would + * be best to be able to scan this message +@@ -491,72 +469,72 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + * that haven't been passed here so it can't be + * called + */ +- m = body; +- messageReset(body); +- messageSetCTX(body, ctx); ++ m = body; ++ messageReset(body); ++ messageSetCTX(body, ctx); + +- cli_dbgmsg("Finished processing message\n"); +- } else +- lastLineWasEmpty = (bool)(buffer[0] == '\0'); ++ cli_dbgmsg("Finished processing message\n"); ++ } else ++ lastLineWasEmpty = (bool)(buffer[0] == '\0'); + +- if(isuuencodebegin(buffer)) { +- /* ++ if (isuuencodebegin(buffer)) { ++ /* + * Fast track visa to uudecode. + * TODO: binhex, yenc + */ +- if(uudecodeFile(m, buffer, dir, map, &at) < 0) +- if(messageAddStr(m, buffer) < 0) +- break; +- } else +- /* at this point, the \n has been removed */ +- if(messageAddStr(m, buffer) < 0) +- break; +- } while(fmap_gets(map, buffer, &at, sizeof(buffer) - 1)); +- +- if(retcode == CL_SUCCESS) { +- cli_dbgmsg("Extract attachments from email %d\n", messagenumber); +- body = parseEmailHeaders(m, rfc821); +- } +- if(m) +- messageDestroy(m); +- } else { +- /* ++ if (uudecodeFile(m, buffer, dir, map, &at) < 0) ++ if (messageAddStr(m, buffer) < 0) ++ break; ++ } else ++ /* at this point, the \n has been removed */ ++ if (messageAddStr(m, buffer) < 0) ++ break; ++ } while (fmap_gets(map, buffer, &at, sizeof(buffer) - 1)); ++ ++ if (retcode == CL_SUCCESS) { ++ cli_dbgmsg("Extract attachments from email %d\n", messagenumber); ++ body = parseEmailHeaders(m, rfc821); ++ } ++ if (m) ++ messageDestroy(m); ++ } else { ++ /* + * It's a single message, parse the headers then the body + */ +- if(strncmp(buffer, "P I ", 4) == 0) +- /* ++ if (strncmp(buffer, "P I ", 4) == 0) ++ /* + * CommuniGate Pro format: ignore headers until + * blank line + */ +- while(fmap_gets(map, buffer, &at, sizeof(buffer) - 1) && +- (strchr("\r\n", buffer[0]) == NULL)) +- ; +- /* getline_from_mbox could be using unlocked_stdio(3), ++ while (fmap_gets(map, buffer, &at, sizeof(buffer) - 1) && ++ (strchr("\r\n", buffer[0]) == NULL)) ++ ; ++ /* getline_from_mbox could be using unlocked_stdio(3), + * so lock file here */ +- /* ++ /* + * Ignore any blank lines at the top of the message + */ +- while(strchr("\r\n", buffer[0]) && +- (getline_from_mbox(buffer, sizeof(buffer) - 1, map, &at) != NULL)) +- ; ++ while (strchr("\r\n", buffer[0]) && ++ (getline_from_mbox(buffer, sizeof(buffer) - 1, map, &at) != NULL)) ++ ; + +- buffer[sizeof(buffer) - 1] = '\0'; ++ buffer[sizeof(buffer) - 1] = '\0'; + +- body = parseEmailFile(map, &at, rfc821, buffer, dir); +- } ++ body = parseEmailFile(map, &at, rfc821, buffer, dir); ++ } + +- if(body) { +- /* ++ if (body) { ++ /* + * Write out the last entry in the mailbox + */ +- if((retcode == CL_SUCCESS) && messageGetBody(body)) { +- messageSetCTX(body, ctx); +- switch(parseEmailBody(body, NULL, &mctx, 0)) { +- case OK: +- case OK_ATTACHMENTS_NOT_SAVED: +- break; +- case FAIL: +- /* ++ if ((retcode == CL_SUCCESS) && messageGetBody(body)) { ++ messageSetCTX(body, ctx); ++ switch (parseEmailBody(body, NULL, &mctx, 0)) { ++ case OK: ++ case OK_ATTACHMENTS_NOT_SAVED: ++ break; ++ case FAIL: ++ /* + * beware: cli_magic_scandesc(), + * changes this into CL_CLEAN, so only + * use it to inform the higher levels +@@ -565,37 +543,37 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + * decoding errors on what *is* a valid + * mbox + */ +- retcode = CL_EFORMAT; +- break; +- case MAXREC: +- retcode = CL_EMAXREC; +- break; +- case MAXFILES: +- retcode = CL_EMAXFILES; +- break; +- case VIRUS: +- retcode = CL_VIRUS; +- break; +- } +- } +- +- if(body->isTruncated && retcode == CL_SUCCESS) +- retcode = CL_EMEM; +- /* ++ retcode = CL_EFORMAT; ++ break; ++ case MAXREC: ++ retcode = CL_EMAXREC; ++ break; ++ case MAXFILES: ++ retcode = CL_EMAXFILES; ++ break; ++ case VIRUS: ++ retcode = CL_VIRUS; ++ break; ++ } ++ } ++ ++ if (body->isTruncated && retcode == CL_SUCCESS) ++ retcode = CL_EMEM; ++ /* + * Tidy up and quit + */ +- messageDestroy(body); +- } ++ messageDestroy(body); ++ } + +- if((retcode == CL_CLEAN) && ctx->found_possibly_unwanted && +- (*ctx->virname == NULL || SCAN_ALLMATCHES)) { +- retcode = cli_append_virus(ctx, "Heuristics.Phishing.Email"); +- ctx->found_possibly_unwanted = 0; +- } ++ if ((retcode == CL_CLEAN) && ctx->found_possibly_unwanted && ++ (*ctx->virname == NULL || SCAN_ALLMATCHES)) { ++ retcode = cli_append_virus(ctx, "Heuristics.Phishing.Email"); ++ ctx->found_possibly_unwanted = 0; ++ } + +- cli_dbgmsg("cli_mbox returning %d\n", retcode); ++ cli_dbgmsg("cli_mbox returning %d\n", retcode); + +- return retcode; ++ return retcode; + } + + /* +@@ -607,58 +585,58 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) + static message * + parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *firstLine, const char *dir) + { +- bool inHeader = TRUE; +- bool bodyIsEmpty = TRUE; +- bool lastWasBlank = FALSE, lastBodyLineWasBlank = FALSE; +- message *ret; +- bool anyHeadersFound = FALSE; +- int commandNumber = -1; +- char *fullline = NULL, *boundary = NULL; +- size_t fulllinelength = 0; +- char buffer[RFC2821LENGTH + 1]; +- +- cli_dbgmsg("parseEmailFile\n"); +- +- ret = messageCreate(); +- if(ret == NULL) +- return NULL; +- +- strncpy(buffer, firstLine, sizeof(buffer)-1); +- do { +- const char *line; +- +- (void)cli_chomp(buffer); +- +- if(buffer[0] == '\0') +- line = NULL; +- else +- line = buffer; +- +- /* ++ bool inHeader = TRUE; ++ bool bodyIsEmpty = TRUE; ++ bool lastWasBlank = FALSE, lastBodyLineWasBlank = FALSE; ++ message *ret; ++ bool anyHeadersFound = FALSE; ++ int commandNumber = -1; ++ char *fullline = NULL, *boundary = NULL; ++ size_t fulllinelength = 0; ++ char buffer[RFC2821LENGTH + 1]; ++ ++ cli_dbgmsg("parseEmailFile\n"); ++ ++ ret = messageCreate(); ++ if (ret == NULL) ++ return NULL; ++ ++ strncpy(buffer, firstLine, sizeof(buffer) - 1); ++ do { ++ const char *line; ++ ++ (void)cli_chomp(buffer); ++ ++ if (buffer[0] == '\0') ++ line = NULL; ++ else ++ line = buffer; ++ ++ /* + * Don't blank lines which are only spaces from headers, + * otherwise they'll be treated as the end of header marker + */ +- if(lastWasBlank) { +- lastWasBlank = FALSE; +- if(boundaryStart(buffer, boundary)) { +- cli_dbgmsg("Found a header line with space that should be blank\n"); +- inHeader = FALSE; +- } +- } +- if(inHeader) { +- cli_dbgmsg("parseEmailFile: check '%s' fullline %p\n", +- buffer, fullline); +- /* ++ if (lastWasBlank) { ++ lastWasBlank = FALSE; ++ if (boundaryStart(buffer, boundary)) { ++ cli_dbgmsg("Found a header line with space that should be blank\n"); ++ inHeader = FALSE; ++ } ++ } ++ if (inHeader) { ++ cli_dbgmsg("parseEmailFile: check '%s' fullline %p\n", ++ buffer, fullline); ++ /* + * Ensure wide characters are handled where + * sizeof(char) > 1 + */ +- if(line && isspace(line[0] & 0xFF)) { +- char copy[sizeof(buffer)]; ++ if (line && isspace(line[0] & 0xFF)) { ++ char copy[sizeof(buffer)]; + +- strcpy(copy, buffer); +- strstrip(copy); +- if(copy[0] == '\0') { +- /* ++ strcpy(copy, buffer); ++ strstrip(copy); ++ if (copy[0] == '\0') { ++ /* + * The header line contains only white + * space. This is not the end of the + * headers according to RFC2822, but +@@ -671,189 +649,189 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + * content-type line. So we just have + * to make a best guess. Sigh. + */ +- if(fullline) { +- if(parseEmailHeader(ret, fullline, rfc821) < 0) +- continue; +- +- free(fullline); +- fullline = NULL; +- } +- if(boundary || +- ((boundary = (char *)messageFindArgument(ret, "boundary")) != NULL)) { +- lastWasBlank = TRUE; +- continue; +- } +- } +- } +- if((line == NULL) && (fullline == NULL)) { /* empty line */ +- /* ++ if (fullline) { ++ if (parseEmailHeader(ret, fullline, rfc821) < 0) ++ continue; ++ ++ free(fullline); ++ fullline = NULL; ++ } ++ if (boundary || ++ ((boundary = (char *)messageFindArgument(ret, "boundary")) != NULL)) { ++ lastWasBlank = TRUE; ++ continue; ++ } ++ } ++ } ++ if ((line == NULL) && (fullline == NULL)) { /* empty line */ ++ /* + * A blank line signifies the end of + * the header and the start of the text + */ +- if(!anyHeadersFound) +- /* Ignore the junk at the top */ +- continue; ++ if (!anyHeadersFound) ++ /* Ignore the junk at the top */ ++ continue; + +- cli_dbgmsg("End of header information\n"); +- inHeader = FALSE; +- bodyIsEmpty = TRUE; +- } else { +- char *ptr; +- const char *lookahead; ++ cli_dbgmsg("End of header information\n"); ++ inHeader = FALSE; ++ bodyIsEmpty = TRUE; ++ } else { ++ char *ptr; ++ const char *lookahead; + +- if(fullline == NULL) { +- char cmd[RFC2821LENGTH + 1], out[RFC2821LENGTH + 1]; ++ if (fullline == NULL) { ++ char cmd[RFC2821LENGTH + 1], out[RFC2821LENGTH + 1]; + +- /* ++ /* + * Continuation of line we're ignoring? + */ +- if(isblank(line[0])) +- continue; ++ if (isblank(line[0])) ++ continue; + +- /* ++ /* + * Is this a header we're interested in? + */ +- if((strchr(line, ':') == NULL) || +- (cli_strtokbuf(line, 0, ":", cmd) == NULL)) { +- if(strncmp(line, "From ", 5) == 0) +- anyHeadersFound = TRUE; +- continue; +- } +- +- ptr = rfc822comments(cmd, out); +- commandNumber = tableFind(rfc821, ptr ? ptr : cmd); +- +- switch(commandNumber) { +- case CONTENT_TRANSFER_ENCODING: +- case CONTENT_DISPOSITION: +- case CONTENT_TYPE: +- anyHeadersFound = TRUE; +- break; +- default: +- if(!anyHeadersFound) +- anyHeadersFound = usefulHeader(commandNumber, cmd); +- continue; +- } +- fullline = cli_strdup(line); +- fulllinelength = strlen(line) + 1; +- if(!fullline) { +- if(ret) +- ret->isTruncated = TRUE; +- break; +- } +- } else if(line != NULL) { +- fulllinelength += strlen(line) + 1; +- ptr = cli_realloc(fullline, fulllinelength); +- if(ptr == NULL) +- continue; +- fullline = ptr; +- cli_strlcat(fullline, line, fulllinelength); +- } +- +- assert(fullline != NULL); +- +- if((lookahead = fmap_need_off_once(map, *at, 1))) { +- /* ++ if ((strchr(line, ':') == NULL) || ++ (cli_strtokbuf(line, 0, ":", cmd) == NULL)) { ++ if (strncmp(line, "From ", 5) == 0) ++ anyHeadersFound = TRUE; ++ continue; ++ } ++ ++ ptr = rfc822comments(cmd, out); ++ commandNumber = tableFind(rfc821, ptr ? ptr : cmd); ++ ++ switch (commandNumber) { ++ case CONTENT_TRANSFER_ENCODING: ++ case CONTENT_DISPOSITION: ++ case CONTENT_TYPE: ++ anyHeadersFound = TRUE; ++ break; ++ default: ++ if (!anyHeadersFound) ++ anyHeadersFound = usefulHeader(commandNumber, cmd); ++ continue; ++ } ++ fullline = cli_strdup(line); ++ fulllinelength = strlen(line) + 1; ++ if (!fullline) { ++ if (ret) ++ ret->isTruncated = TRUE; ++ break; ++ } ++ } else if (line != NULL) { ++ fulllinelength += strlen(line) + 1; ++ ptr = cli_realloc(fullline, fulllinelength); ++ if (ptr == NULL) ++ continue; ++ fullline = ptr; ++ cli_strlcat(fullline, line, fulllinelength); ++ } ++ ++ assert(fullline != NULL); ++ ++ if ((lookahead = fmap_need_off_once(map, *at, 1))) { ++ /* + * Section B.2 of RFC822 says TAB or + * SPACE means a continuation of the + * previous entry. + * + * Add all the arguments on the line + */ +- if(isblank(*lookahead)) +- continue; +- } ++ if (isblank(*lookahead)) ++ continue; ++ } + +- /* ++ /* + * Handle broken headers, where the next + * line isn't indented by whitespace + */ +- if(fullline[strlen(fullline) - 1] == ';') +- /* Add arguments to this line */ +- continue; +- +- if(line && (count_quotes(fullline) & 1)) +- continue; +- +- ptr = rfc822comments(fullline, NULL); +- if(ptr) { +- free(fullline); +- fullline = ptr; +- } +- +- if(parseEmailHeader(ret, fullline, rfc821) < 0) +- continue; +- +- free(fullline); +- fullline = NULL; +- } +- } else if(line && isuuencodebegin(line)) { +- /* ++ if (fullline[strlen(fullline) - 1] == ';') ++ /* Add arguments to this line */ ++ continue; ++ ++ if (line && (count_quotes(fullline) & 1)) ++ continue; ++ ++ ptr = rfc822comments(fullline, NULL); ++ if (ptr) { ++ free(fullline); ++ fullline = ptr; ++ } ++ ++ if (parseEmailHeader(ret, fullline, rfc821) < 0) ++ continue; ++ ++ free(fullline); ++ fullline = NULL; ++ } ++ } else if (line && isuuencodebegin(line)) { ++ /* + * Fast track visa to uudecode. + * TODO: binhex, yenc + */ +- bodyIsEmpty = FALSE; +- if(uudecodeFile(ret, line, dir, map, at) < 0) +- if(messageAddStr(ret, line) < 0) +- break; +- } else { +- if(line == NULL) { +- /* ++ bodyIsEmpty = FALSE; ++ if (uudecodeFile(ret, line, dir, map, at) < 0) ++ if (messageAddStr(ret, line) < 0) ++ break; ++ } else { ++ if (line == NULL) { ++ /* + * Although this would save time and RAM, some + * phish signatures have been built which need + * the blank lines + */ +- if(lastBodyLineWasBlank && +- (messageGetMimeType(ret) != TEXT)) { +- cli_dbgmsg("Ignoring consecutive blank lines in the body\n"); +- continue; +- } +- lastBodyLineWasBlank = TRUE; +- } else { +- if(bodyIsEmpty) { +- /* ++ if (lastBodyLineWasBlank && ++ (messageGetMimeType(ret) != TEXT)) { ++ cli_dbgmsg("Ignoring consecutive blank lines in the body\n"); ++ continue; ++ } ++ lastBodyLineWasBlank = TRUE; ++ } else { ++ if (bodyIsEmpty) { ++ /* + * Broken message: new line in the + * middle of the headers, so the first + * line of the body is in fact + * the last lines of the header + */ +- if(newline_in_header(line)) +- continue; +- bodyIsEmpty = FALSE; +- } +- lastBodyLineWasBlank = FALSE; +- } +- +- if(messageAddStr(ret, line) < 0) +- break; +- } +- } while(getline_from_mbox(buffer, sizeof(buffer) - 1, map, at) != NULL); +- +- if(boundary) +- free(boundary); +- +- if(fullline) { +- if(*fullline) switch(commandNumber) { +- case CONTENT_TRANSFER_ENCODING: +- case CONTENT_DISPOSITION: +- case CONTENT_TYPE: +- cli_dbgmsg("parseEmailFile: Fullline unparsed '%s'\n", fullline); +- } +- free(fullline); +- } +- +- if(!anyHeadersFound) { +- /* ++ if (newline_in_header(line)) ++ continue; ++ bodyIsEmpty = FALSE; ++ } ++ lastBodyLineWasBlank = FALSE; ++ } ++ ++ if (messageAddStr(ret, line) < 0) ++ break; ++ } ++ } while (getline_from_mbox(buffer, sizeof(buffer) - 1, map, at) != NULL); ++ ++ if (boundary) ++ free(boundary); ++ ++ if (fullline) { ++ if (*fullline) switch (commandNumber) { ++ case CONTENT_TRANSFER_ENCODING: ++ case CONTENT_DISPOSITION: ++ case CONTENT_TYPE: ++ cli_dbgmsg("parseEmailFile: Fullline unparsed '%s'\n", fullline); ++ } ++ free(fullline); ++ } ++ ++ if (!anyHeadersFound) { ++ /* + * False positive in believing we have an e-mail when we don't + */ +- messageDestroy(ret); +- cli_dbgmsg("parseEmailFile: no headers found, assuming it isn't an email\n"); +- return NULL; +- } ++ messageDestroy(ret); ++ cli_dbgmsg("parseEmailFile: no headers found, assuming it isn't an email\n"); ++ return NULL; ++ } + +- cli_dbgmsg("parseEmailFile: return\n"); ++ cli_dbgmsg("parseEmailFile: return\n"); + +- return ret; ++ return ret; + } + + /* +@@ -867,162 +845,162 @@ parseEmailFile(fmap_t *map, size_t *at, const table_t *rfc821, const char *first + static message * + parseEmailHeaders(message *m, const table_t *rfc821) + { +- bool inHeader = TRUE; +- bool bodyIsEmpty = TRUE; +- text *t; +- message *ret; +- bool anyHeadersFound = FALSE; +- int commandNumber = -1; +- char *fullline = NULL; +- size_t fulllinelength = 0; +- +- cli_dbgmsg("parseEmailHeaders\n"); +- +- if(m == NULL) +- return NULL; +- +- ret = messageCreate(); +- +- for(t = messageGetBody(m); t; t = t->t_next) { +- const char *line; +- +- if(t->t_line) +- line = lineGetData(t->t_line); +- else +- line = NULL; +- +- if(inHeader) { +- cli_dbgmsg("parseEmailHeaders: check '%s'\n", +- line ? line : ""); +- if(line == NULL) { +- /* ++ bool inHeader = TRUE; ++ bool bodyIsEmpty = TRUE; ++ text *t; ++ message *ret; ++ bool anyHeadersFound = FALSE; ++ int commandNumber = -1; ++ char *fullline = NULL; ++ size_t fulllinelength = 0; ++ ++ cli_dbgmsg("parseEmailHeaders\n"); ++ ++ if (m == NULL) ++ return NULL; ++ ++ ret = messageCreate(); ++ ++ for (t = messageGetBody(m); t; t = t->t_next) { ++ const char *line; ++ ++ if (t->t_line) ++ line = lineGetData(t->t_line); ++ else ++ line = NULL; ++ ++ if (inHeader) { ++ cli_dbgmsg("parseEmailHeaders: check '%s'\n", ++ line ? line : ""); ++ if (line == NULL) { ++ /* + * A blank line signifies the end of + * the header and the start of the text + */ +- cli_dbgmsg("End of header information\n"); +- if(!anyHeadersFound) { +- cli_dbgmsg("Nothing interesting in the header\n"); +- break; +- } +- inHeader = FALSE; +- bodyIsEmpty = TRUE; +- } else { +- char *ptr; +- +- if(fullline == NULL) { +- char cmd[RFC2821LENGTH + 1]; +- +- /* ++ cli_dbgmsg("End of header information\n"); ++ if (!anyHeadersFound) { ++ cli_dbgmsg("Nothing interesting in the header\n"); ++ break; ++ } ++ inHeader = FALSE; ++ bodyIsEmpty = TRUE; ++ } else { ++ char *ptr; ++ ++ if (fullline == NULL) { ++ char cmd[RFC2821LENGTH + 1]; ++ ++ /* + * Continuation of line we're ignoring? + */ +- if(isblank(line[0])) +- continue; ++ if (isblank(line[0])) ++ continue; + +- /* ++ /* + * Is this a header we're interested in? + */ +- if((strchr(line, ':') == NULL) || +- (cli_strtokbuf(line, 0, ":", cmd) == NULL)) { +- if(strncmp(line, "From ", 5) == 0) +- anyHeadersFound = TRUE; +- continue; +- } +- +- ptr = rfc822comments(cmd, NULL); +- commandNumber = tableFind(rfc821, ptr ? ptr : cmd); +- if(ptr) +- free(ptr); +- +- switch(commandNumber) { +- case CONTENT_TRANSFER_ENCODING: +- case CONTENT_DISPOSITION: +- case CONTENT_TYPE: +- anyHeadersFound = TRUE; +- break; +- default: +- if(!anyHeadersFound) +- anyHeadersFound = usefulHeader(commandNumber, cmd); +- continue; +- } +- fullline = cli_strdup(line); +- fulllinelength = strlen(line) + 1; +- } else if(line) { +- fulllinelength += strlen(line) + 1; +- ptr = cli_realloc(fullline, fulllinelength); +- if(ptr == NULL) +- continue; +- fullline = ptr; +- cli_strlcat(fullline, line, fulllinelength); +- } +- assert(fullline != NULL); +- +- if(next_is_folded_header(t)) +- /* Add arguments to this line */ +- continue; +- +- lineUnlink(t->t_line); +- t->t_line = NULL; +- +- if(count_quotes(fullline) & 1) +- continue; +- +- ptr = rfc822comments(fullline, NULL); +- if(ptr) { +- free(fullline); +- fullline = ptr; +- } +- +- if(parseEmailHeader(ret, fullline, rfc821) < 0) +- continue; +- +- free(fullline); +- fullline = NULL; +- } +- } else { +- if(bodyIsEmpty) { +- if(line == NULL) +- /* throw away leading blank lines */ +- continue; +- /* ++ if ((strchr(line, ':') == NULL) || ++ (cli_strtokbuf(line, 0, ":", cmd) == NULL)) { ++ if (strncmp(line, "From ", 5) == 0) ++ anyHeadersFound = TRUE; ++ continue; ++ } ++ ++ ptr = rfc822comments(cmd, NULL); ++ commandNumber = tableFind(rfc821, ptr ? ptr : cmd); ++ if (ptr) ++ free(ptr); ++ ++ switch (commandNumber) { ++ case CONTENT_TRANSFER_ENCODING: ++ case CONTENT_DISPOSITION: ++ case CONTENT_TYPE: ++ anyHeadersFound = TRUE; ++ break; ++ default: ++ if (!anyHeadersFound) ++ anyHeadersFound = usefulHeader(commandNumber, cmd); ++ continue; ++ } ++ fullline = cli_strdup(line); ++ fulllinelength = strlen(line) + 1; ++ } else if (line) { ++ fulllinelength += strlen(line) + 1; ++ ptr = cli_realloc(fullline, fulllinelength); ++ if (ptr == NULL) ++ continue; ++ fullline = ptr; ++ cli_strlcat(fullline, line, fulllinelength); ++ } ++ assert(fullline != NULL); ++ ++ if (next_is_folded_header(t)) ++ /* Add arguments to this line */ ++ continue; ++ ++ lineUnlink(t->t_line); ++ t->t_line = NULL; ++ ++ if (count_quotes(fullline) & 1) ++ continue; ++ ++ ptr = rfc822comments(fullline, NULL); ++ if (ptr) { ++ free(fullline); ++ fullline = ptr; ++ } ++ ++ if (parseEmailHeader(ret, fullline, rfc821) < 0) ++ continue; ++ ++ free(fullline); ++ fullline = NULL; ++ } ++ } else { ++ if (bodyIsEmpty) { ++ if (line == NULL) ++ /* throw away leading blank lines */ ++ continue; ++ /* + * Broken message: new line in the + * middle of the headers, so the first + * line of the body is in fact + * the last lines of the header + */ +- if(newline_in_header(line)) +- continue; +- bodyIsEmpty = FALSE; +- } +- /*if(t->t_line && isuuencodebegin(t->t_line)) ++ if (newline_in_header(line)) ++ continue; ++ bodyIsEmpty = FALSE; ++ } ++ /*if(t->t_line && isuuencodebegin(t->t_line)) + puts("FIXME: add fast visa here");*/ +- cli_dbgmsg("parseEmailHeaders: finished with headers, moving body\n"); +- messageMoveText(ret, t, m); +- break; +- } +- } +- +- if(fullline) { +- if(*fullline) switch(commandNumber) { +- case CONTENT_TRANSFER_ENCODING: +- case CONTENT_DISPOSITION: +- case CONTENT_TYPE: +- cli_dbgmsg("parseEmailHeaders: Fullline unparsed '%s'\n", fullline); +- } +- free(fullline); +- } +- +- if(!anyHeadersFound) { +- /* ++ cli_dbgmsg("parseEmailHeaders: finished with headers, moving body\n"); ++ messageMoveText(ret, t, m); ++ break; ++ } ++ } ++ ++ if (fullline) { ++ if (*fullline) switch (commandNumber) { ++ case CONTENT_TRANSFER_ENCODING: ++ case CONTENT_DISPOSITION: ++ case CONTENT_TYPE: ++ cli_dbgmsg("parseEmailHeaders: Fullline unparsed '%s'\n", fullline); ++ } ++ free(fullline); ++ } ++ ++ if (!anyHeadersFound) { ++ /* + * False positive in believing we have an e-mail when we don't + */ +- messageDestroy(ret); +- cli_dbgmsg("parseEmailHeaders: no headers found, assuming it isn't an email\n"); +- return NULL; +- } ++ messageDestroy(ret); ++ cli_dbgmsg("parseEmailHeaders: no headers found, assuming it isn't an email\n"); ++ return NULL; ++ } + +- cli_dbgmsg("parseEmailHeaders: return\n"); ++ cli_dbgmsg("parseEmailHeaders: return\n"); + +- return ret; ++ return ret; + } + + /* +@@ -1031,99 +1009,97 @@ parseEmailHeaders(message *m, const table_t *rfc821) + static int + parseEmailHeader(message *m, const char *line, const table_t *rfc821) + { +- int ret; ++ int ret; + #ifdef CL_THREAD_SAFE +- char *strptr; ++ char *strptr; + #endif +- const char *separator; +- char *cmd, *copy, tokenseparator[2]; ++ const char *separator; ++ char *cmd, *copy, tokenseparator[2]; + +- cli_dbgmsg("parseEmailHeader '%s'\n", line); ++ cli_dbgmsg("parseEmailHeader '%s'\n", line); + +- /* ++ /* + * In RFC822 the separator between the key a value is a colon, + * e.g. Content-Transfer-Encoding: base64 + * However some MUA's are lapse about this and virus writers exploit + * this hole, so we need to check all known possibilities + */ +- for(separator = ":= "; *separator; separator++) +- if(strchr(line, *separator) != NULL) +- break; ++ for (separator = ":= "; *separator; separator++) ++ if (strchr(line, *separator) != NULL) ++ break; + +- if(*separator == '\0') +- return -1; ++ if (*separator == '\0') ++ return -1; + +- copy = rfc2047(line); +- if(copy == NULL) +- /* an RFC checker would return -1 here */ +- copy = cli_strdup(line); ++ copy = rfc2047(line); ++ if (copy == NULL) ++ /* an RFC checker would return -1 here */ ++ copy = cli_strdup(line); + +- tokenseparator[0] = *separator; +- tokenseparator[1] = '\0'; ++ tokenseparator[0] = *separator; ++ tokenseparator[1] = '\0'; + +- ret = -1; ++ ret = -1; + +-#ifdef CL_THREAD_SAFE +- cmd = strtok_r(copy, tokenseparator, &strptr); ++#ifdef CL_THREAD_SAFE ++ cmd = strtok_r(copy, tokenseparator, &strptr); + #else +- cmd = strtok(copy, tokenseparator); ++ cmd = strtok(copy, tokenseparator); + #endif + +- if(cmd && (strstrip(cmd) > 0)) { +-#ifdef CL_THREAD_SAFE +- char *arg = strtok_r(NULL, "", &strptr); ++ if (cmd && (strstrip(cmd) > 0)) { ++#ifdef CL_THREAD_SAFE ++ char *arg = strtok_r(NULL, "", &strptr); + #else +- char *arg = strtok(NULL, ""); ++ char *arg = strtok(NULL, ""); + #endif + +- if(arg) +- /* ++ if (arg) ++ /* + * Found a header such as + * Content-Type: multipart/mixed; + * set arg to be + * "multipart/mixed" and cmd to + * be "Content-Type" + */ +- ret = parseMimeHeader(m, cmd, rfc821, arg); +- } +- free(copy); +- return ret; ++ ret = parseMimeHeader(m, cmd, rfc821, arg); ++ } ++ free(copy); ++ return ret; + } + + #if HAVE_LIBXML2 + static const struct key_entry mhtml_keys[] = { +- /* root html tags for microsoft office document */ +- { "html", "RootHTML", MSXML_JSON_ROOT | MSXML_JSON_ATTRIB }, +- +- { "head", "Head", MSXML_JSON_WRKPTR | MSXML_COMMENT_CB }, +- { "meta", "Meta", MSXML_JSON_WRKPTR | MSXML_JSON_MULTI | MSXML_JSON_ATTRIB }, +- { "link", "Link", MSXML_JSON_WRKPTR | MSXML_JSON_MULTI | MSXML_JSON_ATTRIB }, +- { "script", "Script", MSXML_JSON_WRKPTR | MSXML_JSON_MULTI | MSXML_JSON_VALUE } +-}; ++ /* root html tags for microsoft office document */ ++ {"html", "RootHTML", MSXML_JSON_ROOT | MSXML_JSON_ATTRIB}, ++ ++ {"head", "Head", MSXML_JSON_WRKPTR | MSXML_COMMENT_CB}, ++ {"meta", "Meta", MSXML_JSON_WRKPTR | MSXML_JSON_MULTI | MSXML_JSON_ATTRIB}, ++ {"link", "Link", MSXML_JSON_WRKPTR | MSXML_JSON_MULTI | MSXML_JSON_ATTRIB}, ++ {"script", "Script", MSXML_JSON_WRKPTR | MSXML_JSON_MULTI | MSXML_JSON_VALUE}}; + static size_t num_mhtml_keys = sizeof(mhtml_keys) / sizeof(struct key_entry); + + static const struct key_entry mhtml_comment_keys[] = { +- /* embedded xml tags (comment) for microsoft office document */ +- { "o:documentproperties", "DocumentProperties", MSXML_JSON_ROOT | MSXML_JSON_ATTRIB }, +- { "o:author", "Author", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:lastauthor", "LastAuthor", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:revision", "Revision", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:totaltime", "TotalTime", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:created", "Created", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:lastsaved", "LastSaved", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:pages", "Pages", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:words", "Words", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:characters", "Characters", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:company", "Company", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:lines", "Lines", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:paragraphs", "Paragraphs", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:characterswithspaces", "CharactersWithSpaces", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- { "o:version", "Version", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE }, +- +- { "o:officedocumentsettings", "DocumentSettings", MSXML_IGNORE_ELEM }, +- { "w:worddocument", "WordDocument", MSXML_IGNORE_ELEM }, +- { "w:latentstyles", "LatentStyles", MSXML_IGNORE_ELEM } +-}; ++ /* embedded xml tags (comment) for microsoft office document */ ++ {"o:documentproperties", "DocumentProperties", MSXML_JSON_ROOT | MSXML_JSON_ATTRIB}, ++ {"o:author", "Author", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:lastauthor", "LastAuthor", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:revision", "Revision", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:totaltime", "TotalTime", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:created", "Created", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:lastsaved", "LastSaved", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:pages", "Pages", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:words", "Words", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:characters", "Characters", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:company", "Company", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:lines", "Lines", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:paragraphs", "Paragraphs", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:characterswithspaces", "CharactersWithSpaces", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ {"o:version", "Version", MSXML_JSON_WRKPTR | MSXML_JSON_VALUE}, ++ ++ {"o:officedocumentsettings", "DocumentSettings", MSXML_IGNORE_ELEM}, ++ {"w:worddocument", "WordDocument", MSXML_IGNORE_ELEM}, ++ {"w:latentstyles", "LatentStyles", MSXML_IGNORE_ELEM}}; + static size_t num_mhtml_comment_keys = sizeof(mhtml_comment_keys) / sizeof(struct key_entry); + #endif + +@@ -1137,55 +1113,55 @@ static int + parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata) + { + #if HAVE_LIBXML2 +- const char *xmlsrt, *xmlend; +- xmlTextReaderPtr reader; ++ const char *xmlsrt, *xmlend; ++ xmlTextReaderPtr reader; + #if HAVE_JSON +- json_object *thisjobj = (json_object *)wrkjobj; ++ json_object *thisjobj = (json_object *)wrkjobj; + #endif +- int ret = CL_SUCCESS; ++ int ret = CL_SUCCESS; + +- UNUSEDPARAM(cbdata); +- UNUSEDPARAM(wrkjobj); ++ UNUSEDPARAM(cbdata); ++ UNUSEDPARAM(wrkjobj); + +- xmlend = comment; +- while ((xmlsrt = strstr(xmlend, ""))) { +- xmlend = strstr(xmlsrt, ""); +- if (xmlend == NULL) { +- cli_dbgmsg("parseMHTMLComment: unbounded xml tag\n"); +- break; +- } ++ xmlend = comment; ++ while ((xmlsrt = strstr(xmlend, ""))) { ++ xmlend = strstr(xmlsrt, ""); ++ if (xmlend == NULL) { ++ cli_dbgmsg("parseMHTMLComment: unbounded xml tag\n"); ++ break; ++ } + +- reader = xmlReaderForMemory(xmlsrt, xmlend-xmlsrt+6, "comment.xml", NULL, CLAMAV_MIN_XMLREADER_FLAGS); +- if (!reader) { +- cli_dbgmsg("parseMHTMLComment: cannot initialize xmlReader\n"); ++ reader = xmlReaderForMemory(xmlsrt, xmlend - xmlsrt + 6, "comment.xml", NULL, CLAMAV_MIN_XMLREADER_FLAGS); ++ if (!reader) { ++ cli_dbgmsg("parseMHTMLComment: cannot initialize xmlReader\n"); + + #if HAVE_JSON +- if (ctx->wrkproperty != NULL) +- ret = cli_json_parse_error(ctx->wrkproperty, "MHTML_ERROR_XML_READER_MEM"); ++ if (ctx->wrkproperty != NULL) ++ ret = cli_json_parse_error(ctx->wrkproperty, "MHTML_ERROR_XML_READER_MEM"); + #endif +- return ret; // libxml2 failed! +- } +- +- /* comment callback is not set to prevent recursion */ +- /* TODO: should we separate the key dictionaries? */ +- /* TODO: should we use the json object pointer? */ +- ret = cli_msxml_parse_document(ctx, reader, mhtml_comment_keys, num_mhtml_comment_keys, MSXML_FLAG_JSON, NULL); +- +- xmlTextReaderClose(reader); +- xmlFreeTextReader(reader); +- if (ret != CL_SUCCESS) +- return ret; +- } ++ return ret; // libxml2 failed! ++ } ++ ++ /* comment callback is not set to prevent recursion */ ++ /* TODO: should we separate the key dictionaries? */ ++ /* TODO: should we use the json object pointer? */ ++ ret = cli_msxml_parse_document(ctx, reader, mhtml_comment_keys, num_mhtml_comment_keys, MSXML_FLAG_JSON, NULL); ++ ++ xmlTextReaderClose(reader); ++ xmlFreeTextReader(reader); ++ if (ret != CL_SUCCESS) ++ return ret; ++ } + #else +- UNUSEDPARAM(comment); +- UNUSEDPARAM(ctx); +- UNUSEDPARAM(wrkjobj); +- UNUSEDPARAM(cbdata); ++ UNUSEDPARAM(comment); ++ UNUSEDPARAM(ctx); ++ UNUSEDPARAM(wrkjobj); ++ UNUSEDPARAM(cbdata); + +- cli_dbgmsg("in parseMHTMLComment\n"); +- cli_dbgmsg("parseMHTMLComment: parsing html xml-comments requires libxml2!\n"); ++ cli_dbgmsg("in parseMHTMLComment\n"); ++ cli_dbgmsg("parseMHTMLComment: parsing html xml-comments requires libxml2!\n"); + #endif +- return CL_SUCCESS; ++ return CL_SUCCESS; + } + + /* +@@ -1197,117 +1173,117 @@ parseMHTMLComment(const char *comment, cli_ctx *ctx, void *wrkjobj, void *cbdata + static mbox_status + parseRootMHTML(mbox_ctx *mctx, message *m, text *t) + { +- cli_ctx *ctx = mctx->ctx; ++ cli_ctx *ctx = mctx->ctx; + #if HAVE_LIBXML2 + #ifdef LIBXML_HTML_ENABLED +- struct msxml_ctx mxctx; +- blob *input = NULL; +- htmlDocPtr htmlDoc; +- xmlTextReaderPtr reader; +- int ret = CL_SUCCESS; +- mbox_status rc = OK; ++ struct msxml_ctx mxctx; ++ blob *input = NULL; ++ htmlDocPtr htmlDoc; ++ xmlTextReaderPtr reader; ++ int ret = CL_SUCCESS; ++ mbox_status rc = OK; + #if HAVE_JSON +- json_object *rhtml; ++ json_object *rhtml; + #endif + +- cli_dbgmsg("in parseRootMHTML\n"); ++ cli_dbgmsg("in parseRootMHTML\n"); + +- if (ctx == NULL) +- return OK; ++ if (ctx == NULL) ++ return OK; + +- if (m == NULL && t == NULL) +- return OK; ++ if (m == NULL && t == NULL) ++ return OK; + +- if (m != NULL) +- input = messageToBlob(m, 0); +- else /* t != NULL */ +- input = textToBlob(t, NULL, 0); ++ if (m != NULL) ++ input = messageToBlob(m, 0); ++ else /* t != NULL */ ++ input = textToBlob(t, NULL, 0); + +- if (input == NULL) +- return OK; ++ if (input == NULL) ++ return OK; + +- htmlDoc = htmlReadMemory((char*)input->data, input->len, "mhtml.html", NULL, CLAMAV_MIN_XMLREADER_FLAGS); +- if (htmlDoc == NULL) { +- cli_dbgmsg("parseRootMHTML: cannot initialize read html document\n"); ++ htmlDoc = htmlReadMemory((char *)input->data, input->len, "mhtml.html", NULL, CLAMAV_MIN_XMLREADER_FLAGS); ++ if (htmlDoc == NULL) { ++ cli_dbgmsg("parseRootMHTML: cannot initialize read html document\n"); + #if HAVE_JSON +- if (ctx->wrkproperty != NULL) +- ret = cli_json_parse_error(ctx->wrkproperty, "MHTML_ERROR_HTML_READ"); +- if (ret != CL_SUCCESS) +- rc = FAIL; ++ if (ctx->wrkproperty != NULL) ++ ret = cli_json_parse_error(ctx->wrkproperty, "MHTML_ERROR_HTML_READ"); ++ if (ret != CL_SUCCESS) ++ rc = FAIL; + #endif +- blobDestroy(input); +- return rc; +- } ++ blobDestroy(input); ++ return rc; ++ } + + #if HAVE_JSON +- if (mctx->wrkobj) { +- rhtml = cli_jsonobj(mctx->wrkobj, "RootHTML"); +- if (rhtml != NULL) { +- /* MHTML-specific properties */ +- cli_jsonstr(rhtml, "Encoding", (const char*)htmlGetMetaEncoding(htmlDoc)); +- cli_jsonint(rhtml, "CompressMode", xmlGetDocCompressMode(htmlDoc)); +- } +- } ++ if (mctx->wrkobj) { ++ rhtml = cli_jsonobj(mctx->wrkobj, "RootHTML"); ++ if (rhtml != NULL) { ++ /* MHTML-specific properties */ ++ cli_jsonstr(rhtml, "Encoding", (const char *)htmlGetMetaEncoding(htmlDoc)); ++ cli_jsonint(rhtml, "CompressMode", xmlGetDocCompressMode(htmlDoc)); ++ } ++ } + #endif + +- reader = xmlReaderWalker(htmlDoc); +- if (reader == NULL) { +- cli_dbgmsg("parseRootMHTML: cannot initialize xmlTextReader\n"); ++ reader = xmlReaderWalker(htmlDoc); ++ if (reader == NULL) { ++ cli_dbgmsg("parseRootMHTML: cannot initialize xmlTextReader\n"); + #if HAVE_JSON +- if (ctx->wrkproperty != NULL) +- ret = cli_json_parse_error(ctx->wrkproperty, "MHTML_ERROR_XML_READER_IO"); +- if (ret != CL_SUCCESS) +- rc = FAIL; ++ if (ctx->wrkproperty != NULL) ++ ret = cli_json_parse_error(ctx->wrkproperty, "MHTML_ERROR_XML_READER_IO"); ++ if (ret != CL_SUCCESS) ++ rc = FAIL; + #endif +- blobDestroy(input); +- return rc; +- } +- +- memset(&mxctx, 0, sizeof(mxctx)); +- /* no scanning callback set */ +- mxctx.comment_cb = parseMHTMLComment; +- ret = cli_msxml_parse_document(ctx, reader, mhtml_keys, num_mhtml_keys, MSXML_FLAG_JSON | MSXML_FLAG_WALK, &mxctx); +- switch (ret) { +- case CL_SUCCESS: +- case CL_ETIMEOUT: +- case CL_BREAK: +- rc = OK; +- break; +- +- case CL_EMAXREC: +- rc = MAXREC; +- break; +- +- case CL_EMAXFILES: +- rc = MAXFILES; +- break; +- +- case CL_VIRUS: +- rc = VIRUS; +- break; +- +- default: +- rc = FAIL; +- } +- +- xmlTextReaderClose(reader); +- xmlFreeTextReader(reader); +- xmlFreeDoc(htmlDoc); +- blobDestroy(input); +- return rc; ++ blobDestroy(input); ++ return rc; ++ } ++ ++ memset(&mxctx, 0, sizeof(mxctx)); ++ /* no scanning callback set */ ++ mxctx.comment_cb = parseMHTMLComment; ++ ret = cli_msxml_parse_document(ctx, reader, mhtml_keys, num_mhtml_keys, MSXML_FLAG_JSON | MSXML_FLAG_WALK, &mxctx); ++ switch (ret) { ++ case CL_SUCCESS: ++ case CL_ETIMEOUT: ++ case CL_BREAK: ++ rc = OK; ++ break; ++ ++ case CL_EMAXREC: ++ rc = MAXREC; ++ break; ++ ++ case CL_EMAXFILES: ++ rc = MAXFILES; ++ break; ++ ++ case CL_VIRUS: ++ rc = VIRUS; ++ break; ++ ++ default: ++ rc = FAIL; ++ } ++ ++ xmlTextReaderClose(reader); ++ xmlFreeTextReader(reader); ++ xmlFreeDoc(htmlDoc); ++ blobDestroy(input); ++ return rc; + #else /* LIBXML_HTML_ENABLED */ +- UNUSEDPARAM(m); +- UNUSEDPARAM(t); +- cli_dbgmsg("in parseRootMHTML\n"); +- cli_dbgmsg("parseRootMHTML: parsing html documents disabled in libxml2!\n"); ++ UNUSEDPARAM(m); ++ UNUSEDPARAM(t); ++ cli_dbgmsg("in parseRootMHTML\n"); ++ cli_dbgmsg("parseRootMHTML: parsing html documents disabled in libxml2!\n"); + #endif /* LIBXML_HTML_ENABLED */ + #else /* HAVE_LIBXML2 */ +- UNUSEDPARAM(m); +- UNUSEDPARAM(t); +- cli_dbgmsg("in parseRootMHTML\n"); +- cli_dbgmsg("parseRootMHTML: parsing html documents requires libxml2!\n"); ++ UNUSEDPARAM(m); ++ UNUSEDPARAM(t); ++ cli_dbgmsg("in parseRootMHTML\n"); ++ cli_dbgmsg("parseRootMHTML: parsing html documents requires libxml2!\n"); + +- return OK; ++ return OK; + #endif /* HAVE_LIBXML2 */ + } + +@@ -1324,182 +1300,181 @@ parseRootMHTML(mbox_ctx *mctx, message *m, text *t) + static mbox_status + parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int recursion_level) + { +- mbox_status rc; +- text *aText = textIn; +- message *mainMessage = messageIn; +- fileblob *fb; +- bool infected = FALSE; +- const struct cl_engine *engine = mctx->ctx->engine; +- const int doPhishingScan = engine->dboptions&CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE); ++ mbox_status rc; ++ text *aText = textIn; ++ message *mainMessage = messageIn; ++ fileblob *fb; ++ bool infected = FALSE; ++ const struct cl_engine *engine = mctx->ctx->engine; ++ const int doPhishingScan = engine->dboptions & CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE); + #if HAVE_JSON +- json_object *saveobj = mctx->wrkobj; ++ json_object *saveobj = mctx->wrkobj; + #endif + +- cli_dbgmsg("in parseEmailBody, %u files saved so far\n", +- mctx->files); ++ cli_dbgmsg("in parseEmailBody, %u files saved so far\n", ++ mctx->files); + +- /* FIXMELIMITS: this should be better integrated */ +- if(engine->maxreclevel) +- /* ++ /* FIXMELIMITS: this should be better integrated */ ++ if (engine->maxreclevel) ++ /* + * This is approximate + */ +- if(recursion_level > engine->maxreclevel) { ++ if (recursion_level > engine->maxreclevel) { + +- cli_dbgmsg("parseEmailBody: hit maximum recursion level (%u)\n", recursion_level); +- return MAXREC; +- } +- if(engine->maxfiles && (mctx->files >= engine->maxfiles)) { +- /* ++ cli_dbgmsg("parseEmailBody: hit maximum recursion level (%u)\n", recursion_level); ++ return MAXREC; ++ } ++ if (engine->maxfiles && (mctx->files >= engine->maxfiles)) { ++ /* + * FIXME: This is only approx - it may have already + * been exceeded + */ +- cli_dbgmsg("parseEmailBody: number of files exceeded %u\n", engine->maxfiles); +- return MAXFILES; +- } +- +- rc = OK; +- +- /* Anything left to be parsed? */ +- if(mainMessage && (messageGetBody(mainMessage) != NULL)) { +- mime_type mimeType; +- int subtype, inhead, htmltextPart, inMimeHead, i; +- const char *mimeSubtype; +- char *boundary; +- const text *t_line; +- /*bool isAlternative;*/ +- message *aMessage; +- int multiparts = 0; +- message **messages = NULL; /* parts of a multipart message */ +- +- cli_dbgmsg("Parsing mail file\n"); +- +- mimeType = messageGetMimeType(mainMessage); +- mimeSubtype = messageGetMimeSubtype(mainMessage); ++ cli_dbgmsg("parseEmailBody: number of files exceeded %u\n", engine->maxfiles); ++ return MAXFILES; ++ } ++ ++ rc = OK; ++ ++ /* Anything left to be parsed? */ ++ if (mainMessage && (messageGetBody(mainMessage) != NULL)) { ++ mime_type mimeType; ++ int subtype, inhead, htmltextPart, inMimeHead, i; ++ const char *mimeSubtype; ++ char *boundary; ++ const text *t_line; ++ /*bool isAlternative;*/ ++ message *aMessage; ++ int multiparts = 0; ++ message **messages = NULL; /* parts of a multipart message */ ++ ++ cli_dbgmsg("Parsing mail file\n"); ++ ++ mimeType = messageGetMimeType(mainMessage); ++ mimeSubtype = messageGetMimeSubtype(mainMessage); + #if HAVE_JSON +- if (mctx->wrkobj != NULL) { +- mctx->wrkobj = cli_jsonobj(mctx->wrkobj, "Body"); +- cli_jsonstr(mctx->wrkobj, "MimeType", getMimeTypeStr(mimeType)); +- cli_jsonstr(mctx->wrkobj, "MimeSubtype", mimeSubtype); +- cli_jsonstr(mctx->wrkobj, "EncodingType", getEncTypeStr(messageGetEncoding(mainMessage))); +- cli_jsonstr(mctx->wrkobj, "Disposition", messageGetDispositionType(mainMessage)); +- cli_jsonstr(mctx->wrkobj, "Filename", messageHasFilename(mainMessage) ? +- messageGetFilename(mainMessage): "(inline)"); +- } ++ if (mctx->wrkobj != NULL) { ++ mctx->wrkobj = cli_jsonobj(mctx->wrkobj, "Body"); ++ cli_jsonstr(mctx->wrkobj, "MimeType", getMimeTypeStr(mimeType)); ++ cli_jsonstr(mctx->wrkobj, "MimeSubtype", mimeSubtype); ++ cli_jsonstr(mctx->wrkobj, "EncodingType", getEncTypeStr(messageGetEncoding(mainMessage))); ++ cli_jsonstr(mctx->wrkobj, "Disposition", messageGetDispositionType(mainMessage)); ++ cli_jsonstr(mctx->wrkobj, "Filename", messageHasFilename(mainMessage) ? messageGetFilename(mainMessage) : "(inline)"); ++ } + #endif + +- /* pre-process */ +- subtype = tableFind(mctx->subtypeTable, mimeSubtype); +- if((mimeType == TEXT) && (subtype == PLAIN)) { +- /* ++ /* pre-process */ ++ subtype = tableFind(mctx->subtypeTable, mimeSubtype); ++ if ((mimeType == TEXT) && (subtype == PLAIN)) { ++ /* + * This is effectively no encoding, notice that we + * don't check that charset is us-ascii + */ +- cli_dbgmsg("text/plain: Assume no attachments\n"); +- mimeType = NOMIME; +- messageSetMimeSubtype(mainMessage, ""); +- } else if((mimeType == MESSAGE) && +- (strcasecmp(mimeSubtype, "rfc822-headers") == 0)) { +- /* ++ cli_dbgmsg("text/plain: Assume no attachments\n"); ++ mimeType = NOMIME; ++ messageSetMimeSubtype(mainMessage, ""); ++ } else if ((mimeType == MESSAGE) && ++ (strcasecmp(mimeSubtype, "rfc822-headers") == 0)) { ++ /* + * RFC1892/RFC3462: section 2 text/rfc822-headers + * incorrectly sent as message/rfc822-headers + * + * Parse as text/plain, i.e. no mime + */ +- cli_dbgmsg("Changing message/rfc822-headers to text/rfc822-headers\n"); +- mimeType = NOMIME; +- messageSetMimeSubtype(mainMessage, ""); +- } else +- cli_dbgmsg("mimeType = %d\n", (int)mimeType); +- +- switch(mimeType) { +- case NOMIME: +- cli_dbgmsg("Not a mime encoded message\n"); +- aText = textAddMessage(aText, mainMessage); +- +- if(!doPhishingScan) +- break; +- /* ++ cli_dbgmsg("Changing message/rfc822-headers to text/rfc822-headers\n"); ++ mimeType = NOMIME; ++ messageSetMimeSubtype(mainMessage, ""); ++ } else ++ cli_dbgmsg("mimeType = %d\n", (int)mimeType); ++ ++ switch (mimeType) { ++ case NOMIME: ++ cli_dbgmsg("Not a mime encoded message\n"); ++ aText = textAddMessage(aText, mainMessage); ++ ++ if (!doPhishingScan) ++ break; ++ /* + * Fall through: some phishing mails claim they are + * text/plain, when they are in fact html + */ +- case TEXT: +- /* text/plain has been preprocessed as no encoding */ +- if(doPhishingScan) { +- /* ++ case TEXT: ++ /* text/plain has been preprocessed as no encoding */ ++ if (doPhishingScan) { ++ /* + * It would be better to save and scan the + * file and only checkURLs if it's found to be + * clean + */ +- checkURLs(mainMessage, mctx, &rc, (subtype == HTML)); +- /* ++ checkURLs(mainMessage, mctx, &rc, (subtype == HTML)); ++ /* + * There might be html sent without subtype + * html too, so scan them for phishing + */ +- if(rc == VIRUS) +- infected = TRUE; +- } +- break; +- case MULTIPART: +- cli_dbgmsg("Content-type 'multipart' handler\n"); +- boundary = messageFindArgument(mainMessage, "boundary"); ++ if (rc == VIRUS) ++ infected = TRUE; ++ } ++ break; ++ case MULTIPART: ++ cli_dbgmsg("Content-type 'multipart' handler\n"); ++ boundary = messageFindArgument(mainMessage, "boundary"); + + #if HAVE_JSON +- if (mctx->wrkobj != NULL) +- cli_jsonstr(mctx->wrkobj, "Boundary", boundary); ++ if (mctx->wrkobj != NULL) ++ cli_jsonstr(mctx->wrkobj, "Boundary", boundary); + #endif + +- if(boundary == NULL) { +- cli_dbgmsg("Multipart/%s MIME message contains no boundary header\n", +- mimeSubtype); +- /* Broken e-mail message */ +- mimeType = NOMIME; +- /* ++ if (boundary == NULL) { ++ cli_dbgmsg("Multipart/%s MIME message contains no boundary header\n", ++ mimeSubtype); ++ /* Broken e-mail message */ ++ mimeType = NOMIME; ++ /* + * The break means that we will still + * check if the file contains a uuencoded file + */ +- break; +- } ++ break; ++ } + +- cli_chomp(boundary); ++ cli_chomp(boundary); + +- /* Perhaps it should assume mixed? */ +- if(mimeSubtype[0] == '\0') { +- cli_dbgmsg("Multipart has no subtype assuming alternative\n"); +- mimeSubtype = "alternative"; +- messageSetMimeSubtype(mainMessage, "alternative"); +- } ++ /* Perhaps it should assume mixed? */ ++ if (mimeSubtype[0] == '\0') { ++ cli_dbgmsg("Multipart has no subtype assuming alternative\n"); ++ mimeSubtype = "alternative"; ++ messageSetMimeSubtype(mainMessage, "alternative"); ++ } + +- /* ++ /* + * Get to the start of the first message + */ +- t_line = messageGetBody(mainMessage); +- +- if(t_line == NULL) { +- cli_dbgmsg("Multipart MIME message has no body\n"); +- free((char *)boundary); +- mimeType = NOMIME; +- break; +- } +- +- do +- if(t_line->t_line) { +- if(boundaryStart(lineGetData(t_line->t_line), boundary)) +- break; +- /* ++ t_line = messageGetBody(mainMessage); ++ ++ if (t_line == NULL) { ++ cli_dbgmsg("Multipart MIME message has no body\n"); ++ free((char *)boundary); ++ mimeType = NOMIME; ++ break; ++ } ++ ++ do ++ if (t_line->t_line) { ++ if (boundaryStart(lineGetData(t_line->t_line), boundary)) ++ break; ++ /* + * Found a binhex file before + * the first multipart + * TODO: check yEnc + */ +- if(binhexBegin(mainMessage) == t_line) { +- if(exportBinhexMessage(mctx, mainMessage)) { +- /* virus found */ +- rc = VIRUS; +- infected = TRUE; +- break; +- } +- } else if(t_line->t_next && +- (encodingLine(mainMessage) == t_line->t_next)) { +- /* ++ if (binhexBegin(mainMessage) == t_line) { ++ if (exportBinhexMessage(mctx, mainMessage)) { ++ /* virus found */ ++ rc = VIRUS; ++ infected = TRUE; ++ break; ++ } ++ } else if (t_line->t_next && ++ (encodingLine(mainMessage) == t_line->t_next)) { ++ /* + * We look for the next line + * since later on we'll skip + * over the important line when +@@ -1508,39 +1483,39 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * which it would have been in + * an RFC compliant world + */ +- cli_dbgmsg("Found MIME attachment before the first MIME section \"%s\"\n", +- lineGetData(t_line->t_next->t_line)); +- if(messageGetEncoding(mainMessage) == NOENCODING) +- break; +- } +- } +- while((t_line = t_line->t_next) != NULL); +- +- if(t_line == NULL) { +- cli_dbgmsg("Multipart MIME message contains no boundary lines (%s)\n", +- boundary); +- free((char *)boundary); +- mimeType = NOMIME; +- /* ++ cli_dbgmsg("Found MIME attachment before the first MIME section \"%s\"\n", ++ lineGetData(t_line->t_next->t_line)); ++ if (messageGetEncoding(mainMessage) == NOENCODING) ++ break; ++ } ++ } ++ while ((t_line = t_line->t_next) != NULL); ++ ++ if (t_line == NULL) { ++ cli_dbgmsg("Multipart MIME message contains no boundary lines (%s)\n", ++ boundary); ++ free((char *)boundary); ++ mimeType = NOMIME; ++ /* + * The break means that we will still + * check if the file contains a yEnc/binhex file + */ +- break; +- } +- /* ++ break; ++ } ++ /* + * Build up a table of all of the parts of this + * multipart message. Remember, each part may itself + * be a multipart message. + */ +- inhead = 1; +- inMimeHead = 0; ++ inhead = 1; ++ inMimeHead = 0; + +- /* ++ /* + * Re-read this variable in case mimeSubtype has changed + */ +- subtype = tableFind(mctx->subtypeTable, mimeSubtype); ++ subtype = tableFind(mctx->subtypeTable, mimeSubtype); + +- /* ++ /* + * Parse the mainMessage object and create an array + * of objects called messages, one for each of the + * multiparts that mainMessage contains. +@@ -1554,99 +1529,99 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * which of those elements it will be, except in + * the case of mixed, when all parts need to be scanned. + */ +- for(multiparts = 0; t_line && !infected; multiparts++) { +- int lines = 0; +- message **m; +- mbox_status old_rc; +- +- m = cli_realloc(messages, ((multiparts + 1) * sizeof(message *))); +- if(m == NULL) +- break; +- messages = m; +- +- aMessage = messages[multiparts] = messageCreate(); +- if(aMessage == NULL) { +- multiparts--; +- /* if allocation failed the first time, ++ for (multiparts = 0; t_line && !infected; multiparts++) { ++ int lines = 0; ++ message **m; ++ mbox_status old_rc; ++ ++ m = cli_realloc(messages, ((multiparts + 1) * sizeof(message *))); ++ if (m == NULL) ++ break; ++ messages = m; ++ ++ aMessage = messages[multiparts] = messageCreate(); ++ if (aMessage == NULL) { ++ multiparts--; ++ /* if allocation failed the first time, + * there's no point in retrying, just + * break out */ +- break; +- } +- messageSetCTX(aMessage, mctx->ctx); ++ break; ++ } ++ messageSetCTX(aMessage, mctx->ctx); + +- cli_dbgmsg("Now read in part %d\n", multiparts); ++ cli_dbgmsg("Now read in part %d\n", multiparts); + +- /* ++ /* + * Ignore blank lines. There shouldn't be ANY + * but some viruses insert them + */ +- while((t_line = t_line->t_next) != NULL) +- if(t_line->t_line && +- /*(cli_chomp(t_line->t_text) > 0))*/ +- (strlen(lineGetData(t_line->t_line)) > 0)) +- break; +- +- if(t_line == NULL) { +- cli_dbgmsg("Empty part\n"); +- /* ++ while ((t_line = t_line->t_next) != NULL) ++ if (t_line->t_line && ++ /*(cli_chomp(t_line->t_text) > 0))*/ ++ (strlen(lineGetData(t_line->t_line)) > 0)) ++ break; ++ ++ if (t_line == NULL) { ++ cli_dbgmsg("Empty part\n"); ++ /* + * Remove this part unless there's + * a binhex portion somewhere in + * the complete message that we may + * throw away by mistake if the MIME + * encoding information is incorrect + */ +- if(mainMessage && +- (binhexBegin(mainMessage) == NULL)) { +- messageDestroy(aMessage); +- --multiparts; +- } +- continue; +- } +- +- do { +- const char *line = lineGetData(t_line->t_line); +- +- /*cli_dbgmsg("multipart %d: inMimeHead %d inhead %d boundary '%s' line '%s' next '%s'\n", ++ if (mainMessage && ++ (binhexBegin(mainMessage) == NULL)) { ++ messageDestroy(aMessage); ++ --multiparts; ++ } ++ continue; ++ } ++ ++ do { ++ const char *line = lineGetData(t_line->t_line); ++ ++ /*cli_dbgmsg("multipart %d: inMimeHead %d inhead %d boundary '%s' line '%s' next '%s'\n", + multiparts, inMimeHead, inhead, boundary, line, + t_line->t_next && t_line->t_next->t_line ? lineGetData(t_line->t_next->t_line) : "(null)");*/ + +- if(inMimeHead) { /* continuation line */ +- if(line == NULL) { +- /*inhead =*/ inMimeHead = 0; +- continue; +- } +- /* ++ if (inMimeHead) { /* continuation line */ ++ if (line == NULL) { ++ /*inhead =*/inMimeHead = 0; ++ continue; ++ } ++ /* + * Handle continuation lines + * because the previous line + * ended with a ; or this line + * starts with a white space + */ +- cli_dbgmsg("Multipart %d: About to add mime Argument '%s'\n", +- multiparts, line); +- /* ++ cli_dbgmsg("Multipart %d: About to add mime Argument '%s'\n", ++ multiparts, line); ++ /* + * Handle the case when it + * isn't really a continuation + * line: + * Content-Type: application/octet-stream; + * Content-Transfer-Encoding: base64 + */ +- parseEmailHeader(aMessage, line, mctx->rfc821Table); +- +- while(isspace((int)*line)) +- line++; +- +- if(*line == '\0') { +- inhead = inMimeHead = 0; +- continue; +- } +- inMimeHead = FALSE; +- messageAddArgument(aMessage, line); +- } else if(inhead) { /* handling normal headers */ +- /*int quotes;*/ +- char *fullline, *ptr; +- +- if(line == NULL) { +- /* ++ parseEmailHeader(aMessage, line, mctx->rfc821Table); ++ ++ while (isspace((int)*line)) ++ line++; ++ ++ if (*line == '\0') { ++ inhead = inMimeHead = 0; ++ continue; ++ } ++ inMimeHead = FALSE; ++ messageAddArgument(aMessage, line); ++ } else if (inhead) { /* handling normal headers */ ++ /*int quotes;*/ ++ char *fullline, *ptr; ++ ++ if (line == NULL) { ++ /* + * empty line, should the end of the headers, + * but some base64 decoders, e.g. uudeview, are broken + * and will handle this type of entry, decoding the +@@ -1663,15 +1638,15 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * + * UEsDBAoAAAAAAACgPjJ2RHw676gAAO+oAABEAAAAbWFpbF90ZXh0LWluZm8udHh0ICAgICAgICAg + */ +- const text *next = t_line->t_next; ++ const text *next = t_line->t_next; + +- if(next && next->t_line) { +- const char *data = lineGetData(next->t_line); ++ if (next && next->t_line) { ++ const char *data = lineGetData(next->t_line); + +- if((messageGetEncoding(aMessage) == NOENCODING) && +- (messageGetMimeType(aMessage) == APPLICATION) && +- data && strstr(data, "base64")) { +- /* ++ if ((messageGetEncoding(aMessage) == NOENCODING) && ++ (messageGetMimeType(aMessage) == APPLICATION) && ++ data && strstr(data, "base64")) { ++ /* + * Handle this nightmare (note the blank + * line in the header and the incorrect + * content-transfer-encoding header) +@@ -1681,23 +1656,23 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * r-Encoding: base64 + * Content-Disposition: attachment; filename="zipped_files.EXE" + */ +- messageSetEncoding(aMessage, "base64"); +- cli_dbgmsg("Ignoring fake end of headers\n"); +- continue; +- } +- if((strncmp(data, "Content", 7) == 0) || +- (strncmp(data, "filename=", 9) == 0)) { +- cli_dbgmsg("Ignoring fake end of headers\n"); +- continue; +- } +- } +- cli_dbgmsg("Multipart %d: End of header information\n", +- multiparts); +- inhead = 0; +- continue; +- } +- if(isspace((int)*line)) { +- /* ++ messageSetEncoding(aMessage, "base64"); ++ cli_dbgmsg("Ignoring fake end of headers\n"); ++ continue; ++ } ++ if ((strncmp(data, "Content", 7) == 0) || ++ (strncmp(data, "filename=", 9) == 0)) { ++ cli_dbgmsg("Ignoring fake end of headers\n"); ++ continue; ++ } ++ } ++ cli_dbgmsg("Multipart %d: End of header information\n", ++ multiparts); ++ inhead = 0; ++ continue; ++ } ++ if (isspace((int)*line)) { ++ /* + * The first line is + * continuation line. + * This is tricky +@@ -1705,10 +1680,10 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * all we can do is our + * best + */ +- cli_dbgmsg("Part %d starts with a continuation line\n", +- multiparts); +- messageAddArgument(aMessage, line); +- /* ++ cli_dbgmsg("Part %d starts with a continuation line\n", ++ multiparts); ++ messageAddArgument(aMessage, line); ++ /* + * Give it a default + * MIME type since + * that may be the +@@ -1717,68 +1692,68 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * Choose application to + * force a save + */ +- if(messageGetMimeType(aMessage) == NOMIME) +- messageSetMimeType(aMessage, "application"); +- continue; +- } ++ if (messageGetMimeType(aMessage) == NOMIME) ++ messageSetMimeType(aMessage, "application"); ++ continue; ++ } + +- inMimeHead = FALSE; ++ inMimeHead = FALSE; + +- assert(strlen(line) <= RFC2821LENGTH); ++ assert(strlen(line) <= RFC2821LENGTH); + +- fullline = rfc822comments(line, NULL); +- if(fullline == NULL) +- fullline = cli_strdup(line); ++ fullline = rfc822comments(line, NULL); ++ if (fullline == NULL) ++ fullline = cli_strdup(line); + +- /*quotes = count_quotes(fullline);*/ ++ /*quotes = count_quotes(fullline);*/ + +- /* ++ /* + * Fold next lines to the end of this + * if they start with a white space + * or if this line has an odd number of quotes: + * Content-Type: application/octet-stream; name="foo + * " + */ +- while(t_line && next_is_folded_header(t_line)) { +- const char *data; +- size_t datasz; ++ while (t_line && next_is_folded_header(t_line)) { ++ const char *data; ++ size_t datasz; + +- t_line = t_line->t_next; ++ t_line = t_line->t_next; + +- data = lineGetData(t_line->t_line); ++ data = lineGetData(t_line->t_line); + +- if(data[1] == '\0') { +- /* ++ if (data[1] == '\0') { ++ /* + * Broken message: the + * blank line at the end + * of the headers isn't blank - + * it contains a space + */ +- cli_dbgmsg("Multipart %d: headers not terminated by blank line\n", +- multiparts); +- inhead = FALSE; +- break; +- } ++ cli_dbgmsg("Multipart %d: headers not terminated by blank line\n", ++ multiparts); ++ inhead = FALSE; ++ break; ++ } + +- datasz = strlen(fullline) + strlen(data) + 1; +- ptr = cli_realloc(fullline, datasz); ++ datasz = strlen(fullline) + strlen(data) + 1; ++ ptr = cli_realloc(fullline, datasz); + +- if(ptr == NULL) +- break; ++ if (ptr == NULL) ++ break; + +- fullline = ptr; +- cli_strlcat(fullline, data, datasz); ++ fullline = ptr; ++ cli_strlcat(fullline, data, datasz); + +- /*quotes = count_quotes(data);*/ +- } ++ /*quotes = count_quotes(data);*/ ++ } + +- cli_dbgmsg("Multipart %d: About to parse folded header '%s'\n", +- multiparts, fullline); ++ cli_dbgmsg("Multipart %d: About to parse folded header '%s'\n", ++ multiparts, fullline); + +- parseEmailHeader(aMessage, fullline, mctx->rfc821Table); +- free(fullline); +- } else if(boundaryEnd(line, boundary)) { +- /* ++ parseEmailHeader(aMessage, fullline, mctx->rfc821Table); ++ free(fullline); ++ } else if (boundaryEnd(line, boundary)) { ++ /* + * Some viruses put information + * *after* the end of message, + * which presumably some broken +@@ -1786,184 +1761,187 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * can't assume that this + * is the end of the message + */ +- /* t_line = NULL;*/ +- break; +- } else if(boundaryStart(line, boundary)) { +- inhead = 1; +- break; +- } else { +- if(messageAddLine(aMessage, t_line->t_line) < 0) +- break; +- lines++; +- } +- } while((t_line = t_line->t_next) != NULL); +- +- cli_dbgmsg("Part %d has %d lines, rc = %d\n", +- multiparts, lines, (int)rc); +- +- /* ++ /* t_line = NULL;*/ ++ break; ++ } else if (boundaryStart(line, boundary)) { ++ inhead = 1; ++ break; ++ } else { ++ if (messageAddLine(aMessage, t_line->t_line) < 0) ++ break; ++ lines++; ++ } ++ } while ((t_line = t_line->t_next) != NULL); ++ ++ cli_dbgmsg("Part %d has %d lines, rc = %d\n", ++ multiparts, lines, (int)rc); ++ ++ /* + * Only save in the array of messages if some + * decision will be taken on whether to scan. + * If all parts will be scanned then save to + * file straight away + */ +- switch(subtype) { +- case MIXED: +- case ALTERNATIVE: +- case REPORT: +- case DIGEST: +- case APPLEDOUBLE: +- case KNOWBOT: +- case -1: +- old_rc = rc; +- mainMessage = do_multipart(mainMessage, +- messages, multiparts, +- &rc, mctx, messageIn, +- &aText, recursion_level); +- if((rc == OK_ATTACHMENTS_NOT_SAVED) && (old_rc == OK)) +- rc = OK; +- if(messages[multiparts]) { +- messageDestroy(messages[multiparts]); +- messages[multiparts] = NULL; +- } +- --multiparts; +- if(rc == VIRUS) +- infected = TRUE; +- break; +- +- case RELATED: +- case ENCRYPTED: +- case SIGNED: +- case PARALLEL: +- /* all the subtypes that we handle ++ switch (subtype) { ++ case MIXED: ++ case ALTERNATIVE: ++ case REPORT: ++ case DIGEST: ++ case APPLEDOUBLE: ++ case KNOWBOT: ++ case -1: ++ old_rc = rc; ++ mainMessage = do_multipart(mainMessage, ++ messages, multiparts, ++ &rc, mctx, messageIn, ++ &aText, recursion_level); ++ if ((rc == OK_ATTACHMENTS_NOT_SAVED) && (old_rc == OK)) ++ rc = OK; ++ if (messages[multiparts]) { ++ messageDestroy(messages[multiparts]); ++ messages[multiparts] = NULL; ++ } ++ --multiparts; ++ if (rc == VIRUS) ++ infected = TRUE; ++ break; ++ ++ case RELATED: ++ case ENCRYPTED: ++ case SIGNED: ++ case PARALLEL: ++ /* all the subtypes that we handle + * (all from the switch(tableFind...) below) + * must be listed here */ +- break; +- default: +- /* this is a subtype that we ++ break; ++ default: ++ /* this is a subtype that we + * don't handle anyway, + * don't store */ +- if(messages[multiparts]) { +- messageDestroy(messages[multiparts]); +- messages[multiparts] = NULL; +- } +- --multiparts; +- } +- } ++ if (messages[multiparts]) { ++ messageDestroy(messages[multiparts]); ++ messages[multiparts] = NULL; ++ } ++ --multiparts; ++ } ++ } + +- free((char *)boundary); ++ free((char *)boundary); + +- /* ++ /* + * Preprocess. Anything special to be done before + * we handle the multiparts? + */ +- switch(subtype) { +- case KNOWBOT: +- /* TODO */ +- cli_dbgmsg("multipart/knowbot parsed as multipart/mixed for now\n"); +- mimeSubtype = "mixed"; +- break; +- case -1: +- /* ++ switch (subtype) { ++ case KNOWBOT: ++ /* TODO */ ++ cli_dbgmsg("multipart/knowbot parsed as multipart/mixed for now\n"); ++ mimeSubtype = "mixed"; ++ break; ++ case -1: ++ /* + * According to section 7.2.6 of + * RFC1521, unrecognized multiparts + * should be treated as multipart/mixed. + */ +- cli_dbgmsg("Unsupported multipart format `%s', parsed as mixed\n", mimeSubtype); +- mimeSubtype = "mixed"; +- break; +- } ++ cli_dbgmsg("Unsupported multipart format `%s', parsed as mixed\n", mimeSubtype); ++ mimeSubtype = "mixed"; ++ break; ++ } + +- /* ++ /* + * We've finished message we're parsing + */ +- if(mainMessage && (mainMessage != messageIn)) { +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } +- +- cli_dbgmsg("The message has %d parts\n", multiparts); +- +- if(infected || ((multiparts == 0) && (aText == NULL))) { +- if(messages) { +- for(i = 0; i < multiparts; i++) +- if(messages[i]) +- messageDestroy(messages[i]); +- free(messages); +- } +- if(aText && (textIn == NULL)) +- textDestroy(aText); ++ if (mainMessage && (mainMessage != messageIn)) { ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } ++ ++ cli_dbgmsg("The message has %d parts\n", multiparts); ++ ++ if (infected || ((multiparts == 0) && (aText == NULL))) { ++ if (messages) { ++ for (i = 0; i < multiparts; i++) ++ if (messages[i]) ++ messageDestroy(messages[i]); ++ free(messages); ++ } ++ if (aText && (textIn == NULL)) ++ textDestroy(aText); + + #if HAVE_JSON +- mctx->wrkobj = saveobj; ++ mctx->wrkobj = saveobj; + #endif +- /* ++ /* + * Nothing to do + */ +- switch(rc) { +- case VIRUS: return VIRUS; +- case MAXREC: return MAXREC; +- default: return OK_ATTACHMENTS_NOT_SAVED; +- } +- } ++ switch (rc) { ++ case VIRUS: ++ return VIRUS; ++ case MAXREC: ++ return MAXREC; ++ default: ++ return OK_ATTACHMENTS_NOT_SAVED; ++ } ++ } + +- cli_dbgmsg("Find out the multipart type (%s)\n", mimeSubtype); ++ cli_dbgmsg("Find out the multipart type (%s)\n", mimeSubtype); + +- /* ++ /* + * We now have all the parts of the multipart message + * in the messages array: + * message *messages[multiparts] + * Let's decide what to do with them all + */ +- switch(tableFind(mctx->subtypeTable, mimeSubtype)) { +- case RELATED: +- cli_dbgmsg("Multipart related handler\n"); +- /* ++ switch (tableFind(mctx->subtypeTable, mimeSubtype)) { ++ case RELATED: ++ cli_dbgmsg("Multipart related handler\n"); ++ /* + * Have a look to see if there's HTML code + * which will need scanning + */ +- aMessage = NULL; +- assert(multiparts > 0); ++ aMessage = NULL; ++ assert(multiparts > 0); + +- htmltextPart = getTextPart(messages, multiparts); ++ htmltextPart = getTextPart(messages, multiparts); + +- if(htmltextPart >= 0 && messages) { +- if(messageGetBody(messages[htmltextPart])) ++ if (htmltextPart >= 0 && messages) { ++ if (messageGetBody(messages[htmltextPart])) + +- aText = textAddMessage(aText, messages[htmltextPart]); +- } else +- /* ++ aText = textAddMessage(aText, messages[htmltextPart]); ++ } else ++ /* + * There isn't an HTML bit. If there's a + * multipart bit, it'll may be in there + * somewhere + */ +- for(i = 0; i < multiparts; i++) +- if(messageGetMimeType(messages[i]) == MULTIPART) { +- aMessage = messages[i]; +- htmltextPart = i; +- break; +- } +- +- if(htmltextPart == -1) +- cli_dbgmsg("No HTML code found to be scanned\n"); +- else { ++ for (i = 0; i < multiparts; i++) ++ if (messageGetMimeType(messages[i]) == MULTIPART) { ++ aMessage = messages[i]; ++ htmltextPart = i; ++ break; ++ } ++ ++ if (htmltextPart == -1) ++ cli_dbgmsg("No HTML code found to be scanned\n"); ++ else { + #if HAVE_JSON +- /* Send root HTML file for preclassification */ +- if (mctx->ctx->wrkproperty) +- parseRootMHTML(mctx, aMessage, aText); ++ /* Send root HTML file for preclassification */ ++ if (mctx->ctx->wrkproperty) ++ parseRootMHTML(mctx, aMessage, aText); + #endif +- rc = parseEmailBody(aMessage, aText, mctx, recursion_level + 1); +- if((rc == OK) && aMessage) { +- assert(aMessage == messages[htmltextPart]); +- messageDestroy(aMessage); +- messages[htmltextPart] = NULL; +- } else if(rc == VIRUS) { +- infected = TRUE; +- break; +- } +- } +- +- /* ++ rc = parseEmailBody(aMessage, aText, mctx, recursion_level + 1); ++ if ((rc == OK) && aMessage) { ++ assert(aMessage == messages[htmltextPart]); ++ messageDestroy(aMessage); ++ messages[htmltextPart] = NULL; ++ } else if (rc == VIRUS) { ++ infected = TRUE; ++ break; ++ } ++ } ++ ++ /* + * The message is confused about the difference + * between alternative and related. Badtrans.B + * suffers from this problem. +@@ -1972,8 +1950,8 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * Content-Type: multipart/related; + * type="multipart/alternative" + */ +- case DIGEST: +- /* ++ case DIGEST: ++ /* + * According to section 5.1.5 RFC2046, the + * default mime type of multipart/digest parts + * is message/rfc822 +@@ -1984,29 +1962,29 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * OK for our needs since it means each part + * will be scanned + */ +- case ALTERNATIVE: +- cli_dbgmsg("Multipart alternative handler\n"); ++ case ALTERNATIVE: ++ cli_dbgmsg("Multipart alternative handler\n"); + +- /* ++ /* + * Fall through - some clients are broken and + * say alternative instead of mixed. The Klez + * virus is broken that way, and anyway we + * wish to scan all of the alternatives + */ +- case REPORT: +- /* ++ case REPORT: ++ /* + * According to section 1 of RFC1892, the + * syntax of multipart/report is the same + * as multipart/mixed. There are some required + * parameters, but there's no need for us to + * verify that they exist + */ +- case ENCRYPTED: +- /* MUAs without encryption plugins can display as multipart/mixed, ++ case ENCRYPTED: ++ /* MUAs without encryption plugins can display as multipart/mixed, + * just scan it*/ +- case MIXED: +- case APPLEDOUBLE: /* not really supported */ +- /* ++ case MIXED: ++ case APPLEDOUBLE: /* not really supported */ ++ /* + * Look for attachments + * + * Not all formats are supported. If an +@@ -2014,32 +1992,32 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * common enough to implement, it is a simple + * matter to add it + */ +- if(aText) { +- if(mainMessage && (mainMessage != messageIn)) +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } +- +- cli_dbgmsg("Mixed message with %d parts\n", multiparts); +- for(i = 0; i < multiparts; i++) { +- mainMessage = do_multipart(mainMessage, +- messages, i, &rc, mctx, +- messageIn, &aText, recursion_level + 1); +- if(rc == VIRUS) { +- infected = TRUE; +- break; +- } +- if(rc == MAXREC) +- break; +- if (rc == OK_ATTACHMENTS_NOT_SAVED) +- rc = OK; +- } +- +- /* rc = parseEmailBody(NULL, NULL, mctx, recursion_level + 1); */ +- break; +- case SIGNED: +- case PARALLEL: +- /* ++ if (aText) { ++ if (mainMessage && (mainMessage != messageIn)) ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } ++ ++ cli_dbgmsg("Mixed message with %d parts\n", multiparts); ++ for (i = 0; i < multiparts; i++) { ++ mainMessage = do_multipart(mainMessage, ++ messages, i, &rc, mctx, ++ messageIn, &aText, recursion_level + 1); ++ if (rc == VIRUS) { ++ infected = TRUE; ++ break; ++ } ++ if (rc == MAXREC) ++ break; ++ if (rc == OK_ATTACHMENTS_NOT_SAVED) ++ rc = OK; ++ } ++ ++ /* rc = parseEmailBody(NULL, NULL, mctx, recursion_level + 1); */ ++ break; ++ case SIGNED: ++ case PARALLEL: ++ /* + * If we're here it could be because we have a + * multipart/mixed message, consisting of a + * message followed by an attachment. That +@@ -2047,226 +2025,225 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * message and we need to dig out the plain + * text part of that alternative + */ +- if(messages) { +- htmltextPart = getTextPart(messages, multiparts); +- if(htmltextPart == -1) +- htmltextPart = 0; +- rc = parseEmailBody(messages[htmltextPart], aText, mctx, recursion_level + 1); +- } +- break; +- default: +- assert(0); +- } +- +- if(mainMessage && (mainMessage != messageIn)) +- messageDestroy(mainMessage); +- +- if(aText && (textIn == NULL)) { +- if((!infected) && (fb = fileblobCreate()) != NULL) { +- cli_dbgmsg("Save non mime and/or text/plain part\n"); +- fileblobSetFilename(fb, mctx->dir, "textpart"); +- /*fileblobAddData(fb, "Received: by clamd (textpart)\n", 30);*/ +- fileblobSetCTX(fb, mctx->ctx); +- (void)textToFileblob(aText, fb, 1); +- +- fileblobDestroy(fb); +- mctx->files++; +- } +- textDestroy(aText); +- } +- +- for(i = 0; i < multiparts; i++) +- if(messages[i]) +- messageDestroy(messages[i]); +- +- if(messages) +- free(messages); ++ if (messages) { ++ htmltextPart = getTextPart(messages, multiparts); ++ if (htmltextPart == -1) ++ htmltextPart = 0; ++ rc = parseEmailBody(messages[htmltextPart], aText, mctx, recursion_level + 1); ++ } ++ break; ++ default: ++ assert(0); ++ } ++ ++ if (mainMessage && (mainMessage != messageIn)) ++ messageDestroy(mainMessage); ++ ++ if (aText && (textIn == NULL)) { ++ if ((!infected) && (fb = fileblobCreate()) != NULL) { ++ cli_dbgmsg("Save non mime and/or text/plain part\n"); ++ fileblobSetFilename(fb, mctx->dir, "textpart"); ++ /*fileblobAddData(fb, "Received: by clamd (textpart)\n", 30);*/ ++ fileblobSetCTX(fb, mctx->ctx); ++ (void)textToFileblob(aText, fb, 1); ++ ++ fileblobDestroy(fb); ++ mctx->files++; ++ } ++ textDestroy(aText); ++ } ++ ++ for (i = 0; i < multiparts; i++) ++ if (messages[i]) ++ messageDestroy(messages[i]); ++ ++ if (messages) ++ free(messages); + + #if HAVE_JSON +- mctx->wrkobj = saveobj; ++ mctx->wrkobj = saveobj; + #endif +- return rc; ++ return rc; + +- case MESSAGE: +- /* ++ case MESSAGE: ++ /* + * Check for forbidden encodings + */ +- switch(messageGetEncoding(mainMessage)) { +- case NOENCODING: +- case EIGHTBIT: +- case BINARY: +- break; +- default: +- cli_dbgmsg("MIME type 'message' cannot be decoded\n"); +- break; +- } +- rc = FAIL; +- if((strcasecmp(mimeSubtype, "rfc822") == 0) || +- (strcasecmp(mimeSubtype, "delivery-status") == 0)) { +- message *m = parseEmailHeaders(mainMessage, mctx->rfc821Table); +- if(m) { +- cli_dbgmsg("Decode rfc822\n"); +- +- messageSetCTX(m, mctx->ctx); +- +- if(mainMessage && (mainMessage != messageIn)) { +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } else +- messageReset(mainMessage); +- if(messageGetBody(m)) +- rc = parseEmailBody(m, NULL, mctx, recursion_level + 1); +- +- messageDestroy(m); +- } +- break; +- } else if(strcasecmp(mimeSubtype, "disposition-notification") == 0) { +- /* RFC 2298 - handle like a normal email */ +- rc = OK; +- break; +- } else if(strcasecmp(mimeSubtype, "partial") == 0) { +- if(mctx->ctx->options->mail & CL_SCAN_MAIL_PARTIAL_MESSAGE) { +- /* RFC1341 message split over many emails */ +- if(rfc1341(mainMessage, mctx->dir) >= 0) +- rc = OK; +- } else { +- cli_warnmsg("Partial message received from MUA/MTA - message cannot be scanned\n"); +- } +- } else if(strcasecmp(mimeSubtype, "external-body") == 0) +- /* TODO */ +- cli_warnmsg("Attempt to send Content-type message/external-body trapped\n"); +- else +- cli_warnmsg("Unsupported message format `%s' - if you believe this file contains a virus, submit it to www.clamav.net\n", mimeSubtype); +- +- +- if(mainMessage && (mainMessage != messageIn)) +- messageDestroy(mainMessage); +- if(messages) +- free(messages); ++ switch (messageGetEncoding(mainMessage)) { ++ case NOENCODING: ++ case EIGHTBIT: ++ case BINARY: ++ break; ++ default: ++ cli_dbgmsg("MIME type 'message' cannot be decoded\n"); ++ break; ++ } ++ rc = FAIL; ++ if ((strcasecmp(mimeSubtype, "rfc822") == 0) || ++ (strcasecmp(mimeSubtype, "delivery-status") == 0)) { ++ message *m = parseEmailHeaders(mainMessage, mctx->rfc821Table); ++ if (m) { ++ cli_dbgmsg("Decode rfc822\n"); ++ ++ messageSetCTX(m, mctx->ctx); ++ ++ if (mainMessage && (mainMessage != messageIn)) { ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } else ++ messageReset(mainMessage); ++ if (messageGetBody(m)) ++ rc = parseEmailBody(m, NULL, mctx, recursion_level + 1); ++ ++ messageDestroy(m); ++ } ++ break; ++ } else if (strcasecmp(mimeSubtype, "disposition-notification") == 0) { ++ /* RFC 2298 - handle like a normal email */ ++ rc = OK; ++ break; ++ } else if (strcasecmp(mimeSubtype, "partial") == 0) { ++ if (mctx->ctx->options->mail & CL_SCAN_MAIL_PARTIAL_MESSAGE) { ++ /* RFC1341 message split over many emails */ ++ if (rfc1341(mainMessage, mctx->dir) >= 0) ++ rc = OK; ++ } else { ++ cli_warnmsg("Partial message received from MUA/MTA - message cannot be scanned\n"); ++ } ++ } else if (strcasecmp(mimeSubtype, "external-body") == 0) ++ /* TODO */ ++ cli_warnmsg("Attempt to send Content-type message/external-body trapped\n"); ++ else ++ cli_warnmsg("Unsupported message format `%s' - if you believe this file contains a virus, submit it to www.clamav.net\n", mimeSubtype); ++ ++ if (mainMessage && (mainMessage != messageIn)) ++ messageDestroy(mainMessage); ++ if (messages) ++ free(messages); + #if HAVE_JSON +- mctx->wrkobj = saveobj; ++ mctx->wrkobj = saveobj; + #endif +- return rc; ++ return rc; + +- default: +- cli_dbgmsg("Message received with unknown mime encoding - assume application\n"); +- /* ++ default: ++ cli_dbgmsg("Message received with unknown mime encoding - assume application\n"); ++ /* + * Some Yahoo emails attach as + * Content-Type: X-unknown/unknown; + * instead of + * Content-Type: application/unknown; + * so let's try our best to salvage something + */ +- case APPLICATION: +- /*cptr = messageGetMimeSubtype(mainMessage); ++ case APPLICATION: ++ /*cptr = messageGetMimeSubtype(mainMessage); + + if((strcasecmp(cptr, "octet-stream") == 0) || + (strcasecmp(cptr, "x-msdownload") == 0)) {*/ +- { +- fb = messageToFileblob(mainMessage, mctx->dir, 1); +- +- if(fb) { +- cli_dbgmsg("Saving main message as attachment\n"); +- if(fileblobScanAndDestroy(fb) == CL_VIRUS) +- rc = VIRUS; +- mctx->files++; +- if(mainMessage != messageIn) { +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } else +- messageReset(mainMessage); +- } +- } /*else ++ { ++ fb = messageToFileblob(mainMessage, mctx->dir, 1); ++ ++ if (fb) { ++ cli_dbgmsg("Saving main message as attachment\n"); ++ if (fileblobScanAndDestroy(fb) == CL_VIRUS) ++ rc = VIRUS; ++ mctx->files++; ++ if (mainMessage != messageIn) { ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } else ++ messageReset(mainMessage); ++ } ++ } /*else + cli_warnmsg("Discarded application not sent as attachment\n");*/ +- break; +- +- case AUDIO: +- case VIDEO: +- case IMAGE: +- break; +- } +- +- if(messages) { +- /* "can't happen" */ +- cli_warnmsg("messages != NULL\n"); +- free(messages); +- } +- } +- +- if(aText && (textIn == NULL)) { +- /* Look for a bounce in the text (non mime encoded) portion */ +- const text *t; +- /* isBounceStart() is expensive, reduce the number of calls */ +- bool lookahead_definately_is_bounce = FALSE; +- +- for(t = aText; t && (rc != VIRUS); t = t->t_next) { +- const line_t *l = t->t_line; +- const text *lookahead, *topofbounce; +- const char *s; +- bool inheader; +- +- if(l == NULL) { +- /* assert(lookahead_definately_is_bounce == FALSE) */ +- continue; +- } +- +- if(lookahead_definately_is_bounce) +- lookahead_definately_is_bounce = FALSE; +- else if(!isBounceStart(mctx, lineGetData(l))) +- continue; +- +- lookahead = t->t_next; +- if(lookahead) { +- if(isBounceStart(mctx, lineGetData(lookahead->t_line))) { +- lookahead_definately_is_bounce = TRUE; +- /* don't save worthless header lines */ +- continue; +- } +- } else /* don't save a single liner */ +- break; +- +- /* ++ break; ++ ++ case AUDIO: ++ case VIDEO: ++ case IMAGE: ++ break; ++ } ++ ++ if (messages) { ++ /* "can't happen" */ ++ cli_warnmsg("messages != NULL\n"); ++ free(messages); ++ } ++ } ++ ++ if (aText && (textIn == NULL)) { ++ /* Look for a bounce in the text (non mime encoded) portion */ ++ const text *t; ++ /* isBounceStart() is expensive, reduce the number of calls */ ++ bool lookahead_definately_is_bounce = FALSE; ++ ++ for (t = aText; t && (rc != VIRUS); t = t->t_next) { ++ const line_t *l = t->t_line; ++ const text *lookahead, *topofbounce; ++ const char *s; ++ bool inheader; ++ ++ if (l == NULL) { ++ /* assert(lookahead_definately_is_bounce == FALSE) */ ++ continue; ++ } ++ ++ if (lookahead_definately_is_bounce) ++ lookahead_definately_is_bounce = FALSE; ++ else if (!isBounceStart(mctx, lineGetData(l))) ++ continue; ++ ++ lookahead = t->t_next; ++ if (lookahead) { ++ if (isBounceStart(mctx, lineGetData(lookahead->t_line))) { ++ lookahead_definately_is_bounce = TRUE; ++ /* don't save worthless header lines */ ++ continue; ++ } ++ } else /* don't save a single liner */ ++ break; ++ ++ /* + * We've found what looks like the start of a bounce + * message. Only bother saving if it really is a bounce + * message, this helps to speed up scanning of ping-pong + * messages that have lots of bounces within bounces in + * them + */ +- for(; lookahead; lookahead = lookahead->t_next) { +- l = lookahead->t_line; +- +- if(l == NULL) +- break; +- s = lineGetData(l); +- if(strncasecmp(s, "Content-Type:", 13) == 0) { +- /* ++ for (; lookahead; lookahead = lookahead->t_next) { ++ l = lookahead->t_line; ++ ++ if (l == NULL) ++ break; ++ s = lineGetData(l); ++ if (strncasecmp(s, "Content-Type:", 13) == 0) { ++ /* + * Don't bother with text/plain or + * text/html + */ +- if(cli_strcasestr(s, "text/plain") != NULL) +- /* ++ if (cli_strcasestr(s, "text/plain") != NULL) ++ /* + * Don't bother to save the + * unuseful part, read past + * the headers then we'll go + * on to look for the next + * bounce message + */ +- continue; +- if((!doPhishingScan) && +- (cli_strcasestr(s, "text/html") != NULL)) +- continue; +- break; +- } +- } +- +- if(lookahead && (lookahead->t_line == NULL)) { +- cli_dbgmsg("Non mime part bounce message is not mime encoded, so it will not be scanned\n"); +- t = lookahead; +- /* look for next bounce message */ +- continue; +- } +- +- /* ++ continue; ++ if ((!doPhishingScan) && ++ (cli_strcasestr(s, "text/html") != NULL)) ++ continue; ++ break; ++ } ++ } ++ ++ if (lookahead && (lookahead->t_line == NULL)) { ++ cli_dbgmsg("Non mime part bounce message is not mime encoded, so it will not be scanned\n"); ++ t = lookahead; ++ /* look for next bounce message */ ++ continue; ++ } ++ ++ /* + * Prescan the bounce message to see if there's likely + * to be anything nasty. + * This algorithm is hand crafted and may be breakable +@@ -2275,155 +2252,156 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + * significantly speeds up the scanning of multiple + * bounces (i.e. bounces within many bounces) + */ +- for(; lookahead; lookahead = lookahead->t_next) { +- l = lookahead->t_line; +- +- if(l) { +- s = lineGetData(l); +- if((strncasecmp(s, "Content-Type:", 13) == 0) && +- (strstr(s, "multipart/") == NULL) && +- (strstr(s, "message/rfc822") == NULL) && +- (strstr(s, "text/plain") == NULL)) +- break; +- } +- } +- if(lookahead == NULL) { +- cli_dbgmsg("cli_mbox: I believe it's plain text which must be clean\n"); +- /* nothing here, move along please */ +- break; +- } +- if((fb = fileblobCreate()) == NULL) +- break; +- cli_dbgmsg("Save non mime part bounce message\n"); +- fileblobSetFilename(fb, mctx->dir, "bounce"); +- fileblobAddData(fb, (const unsigned char *)"Received: by clamd (bounce)\n", 28); +- fileblobSetCTX(fb, mctx->ctx); +- +- inheader = TRUE; +- topofbounce = NULL; +- do { +- l = t->t_line; +- +- if(l == NULL) { +- if(inheader) { +- inheader = FALSE; +- topofbounce = t; +- } +- } else { +- s = lineGetData(l); +- fileblobAddData(fb, (const unsigned char *)s, strlen(s)); +- } +- fileblobAddData(fb, (const unsigned char *)"\n", 1); +- lookahead = t->t_next; +- if(lookahead == NULL) +- break; +- t = lookahead; +- l = t->t_line; +- if((!inheader) && l) { +- s = lineGetData(l); +- if(isBounceStart(mctx, s)) { +- cli_dbgmsg("Found the start of another bounce candidate (%s)\n", s); +- lookahead_definately_is_bounce = TRUE; +- break; +- } +- } +- } while(!fileblobInfected(fb)); +- +- if(fileblobScanAndDestroy(fb) == CL_VIRUS) +- rc = VIRUS; +- mctx->files++; +- +- if(topofbounce) +- t = topofbounce; +- } +- textDestroy(aText); +- aText = NULL; +- } +- +- /* ++ for (; lookahead; lookahead = lookahead->t_next) { ++ l = lookahead->t_line; ++ ++ if (l) { ++ s = lineGetData(l); ++ if ((strncasecmp(s, "Content-Type:", 13) == 0) && ++ (strstr(s, "multipart/") == NULL) && ++ (strstr(s, "message/rfc822") == NULL) && ++ (strstr(s, "text/plain") == NULL)) ++ break; ++ } ++ } ++ if (lookahead == NULL) { ++ cli_dbgmsg("cli_mbox: I believe it's plain text which must be clean\n"); ++ /* nothing here, move along please */ ++ break; ++ } ++ if ((fb = fileblobCreate()) == NULL) ++ break; ++ cli_dbgmsg("Save non mime part bounce message\n"); ++ fileblobSetFilename(fb, mctx->dir, "bounce"); ++ fileblobAddData(fb, (const unsigned char *)"Received: by clamd (bounce)\n", 28); ++ fileblobSetCTX(fb, mctx->ctx); ++ ++ inheader = TRUE; ++ topofbounce = NULL; ++ do { ++ l = t->t_line; ++ ++ if (l == NULL) { ++ if (inheader) { ++ inheader = FALSE; ++ topofbounce = t; ++ } ++ } else { ++ s = lineGetData(l); ++ fileblobAddData(fb, (const unsigned char *)s, strlen(s)); ++ } ++ fileblobAddData(fb, (const unsigned char *)"\n", 1); ++ lookahead = t->t_next; ++ if (lookahead == NULL) ++ break; ++ t = lookahead; ++ l = t->t_line; ++ if ((!inheader) && l) { ++ s = lineGetData(l); ++ if (isBounceStart(mctx, s)) { ++ cli_dbgmsg("Found the start of another bounce candidate (%s)\n", s); ++ lookahead_definately_is_bounce = TRUE; ++ break; ++ } ++ } ++ } while (!fileblobInfected(fb)); ++ ++ if (fileblobScanAndDestroy(fb) == CL_VIRUS) ++ rc = VIRUS; ++ mctx->files++; ++ ++ if (topofbounce) ++ t = topofbounce; ++ } ++ textDestroy(aText); ++ aText = NULL; ++ } ++ ++ /* + * No attachments - scan the text portions, often files + * are hidden in HTML code + */ +- if(mainMessage && (rc != VIRUS)) { +- text *t_line; ++ if (mainMessage && (rc != VIRUS)) { ++ text *t_line; + +- /* ++ /* + * Look for uu-encoded main file + */ +- if(mainMessage->body_first != NULL && +- (encodingLine(mainMessage) != NULL) && +- ((t_line = bounceBegin(mainMessage)) != NULL)) +- rc = (exportBounceMessage(mctx, t_line) == CL_VIRUS) ? VIRUS : OK; +- else { +- bool saveIt; +- +- if(messageGetMimeType(mainMessage) == MESSAGE) +- /* ++ if (mainMessage->body_first != NULL && ++ (encodingLine(mainMessage) != NULL) && ++ ((t_line = bounceBegin(mainMessage)) != NULL)) ++ rc = (exportBounceMessage(mctx, t_line) == CL_VIRUS) ? VIRUS : OK; ++ else { ++ bool saveIt; ++ ++ if (messageGetMimeType(mainMessage) == MESSAGE) ++ /* + * Quick peek, if the encapsulated + * message has no + * content encoding statement don't + * bother saving to scan, it's safe + */ +- saveIt = (bool)(encodingLine(mainMessage) != NULL); +- else if(mainMessage->body_last != NULL && (t_line = encodingLine(mainMessage)) != NULL) { +- /* ++ saveIt = (bool)(encodingLine(mainMessage) != NULL); ++ else if (mainMessage->body_last != NULL && (t_line = encodingLine(mainMessage)) != NULL) { ++ /* + * Some bounces include the message + * body without the headers. + * FIXME: Unfortunately this generates a + * lot of false positives that a bounce + * has been found when it hasn't. + */ +- if((fb = fileblobCreate()) != NULL) { +- cli_dbgmsg("Found a bounce message with no header at '%s'\n", +- lineGetData(t_line->t_line)); +- fileblobSetFilename(fb, mctx->dir, "bounce"); +- fileblobAddData(fb, +- (const unsigned char *)"Received: by clamd (bounce)\n", +- 28); +- +- fileblobSetCTX(fb, mctx->ctx); +- if(fileblobScanAndDestroy(textToFileblob(t_line, fb, 1)) == CL_VIRUS) +- rc = VIRUS; +- mctx->files++; +- } +- saveIt = FALSE; +- } else +- /* ++ if ((fb = fileblobCreate()) != NULL) { ++ cli_dbgmsg("Found a bounce message with no header at '%s'\n", ++ lineGetData(t_line->t_line)); ++ fileblobSetFilename(fb, mctx->dir, "bounce"); ++ fileblobAddData(fb, ++ (const unsigned char *)"Received: by clamd (bounce)\n", ++ 28); ++ ++ fileblobSetCTX(fb, mctx->ctx); ++ if (fileblobScanAndDestroy(textToFileblob(t_line, fb, 1)) == CL_VIRUS) ++ rc = VIRUS; ++ mctx->files++; ++ } ++ saveIt = FALSE; ++ } else ++ /* + * Save the entire text portion, + * since it it may be an HTML file with + * a JavaScript virus or a phish + */ +- saveIt = TRUE; +- +- if(saveIt) { +- cli_dbgmsg("Saving text part to scan, rc = %d\n", +- (int)rc); +- if(saveTextPart(mctx, mainMessage, 1) == CL_VIRUS) +- rc = VIRUS; +- +- if(mainMessage != messageIn) { +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } else +- messageReset(mainMessage); +- } +- } +- } /*else +- rc = OK_ATTACHMENTS_NOT_SAVED; */ /* nothing saved */ +- +- if(mainMessage && (mainMessage != messageIn)) +- messageDestroy(mainMessage); +- +- if((rc != FAIL) && infected) +- rc = VIRUS; ++ saveIt = TRUE; ++ ++ if (saveIt) { ++ cli_dbgmsg("Saving text part to scan, rc = %d\n", ++ (int)rc); ++ if (saveTextPart(mctx, mainMessage, 1) == CL_VIRUS) ++ rc = VIRUS; ++ ++ if (mainMessage != messageIn) { ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } else ++ messageReset(mainMessage); ++ } ++ } ++ } /*else ++ rc = OK_ATTACHMENTS_NOT_SAVED; */ ++ /* nothing saved */ ++ ++ if (mainMessage && (mainMessage != messageIn)) ++ messageDestroy(mainMessage); ++ ++ if ((rc != FAIL) && infected) ++ rc = VIRUS; + + #if HAVE_JSON +- mctx->wrkobj = saveobj; ++ mctx->wrkobj = saveobj; + #endif + +- cli_dbgmsg("parseEmailBody() returning %d\n", (int)rc); ++ cli_dbgmsg("parseEmailBody() returning %d\n", (int)rc); + +- return rc; ++ return rc; + } + + /* +@@ -2434,16 +2412,16 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re + static int + boundaryStart(const char *line, const char *boundary) + { +- const char *ptr; +- char *out; +- int rc; +- char buf[RFC2821LENGTH + 1]; ++ const char *ptr; ++ char *out; ++ int rc; ++ char buf[RFC2821LENGTH + 1]; + char *newline; + +- if(line == NULL || *line == '\0') +- return 0; /* empty line */ +- if(boundary == NULL) +- return 0; ++ if (line == NULL || *line == '\0') ++ return 0; /* empty line */ ++ if (boundary == NULL) ++ return 0; + + newline = strdup(line); + if (!(newline)) +@@ -2460,39 +2438,39 @@ boundaryStart(const char *line, const char *boundary) + if (newline != line) + cli_chomp(newline); + +- /* cli_dbgmsg("boundaryStart: line = '%s' boundary = '%s'\n", line, boundary); */ ++ /* cli_dbgmsg("boundaryStart: line = '%s' boundary = '%s'\n", line, boundary); */ + +- if((*newline != '-') && (*newline != '(')) { ++ if ((*newline != '-') && (*newline != '(')) { + if (newline != line) + free(newline); +- return 0; ++ return 0; + } + +- if(strchr(newline, '-') == NULL) { ++ if (strchr(newline, '-') == NULL) { + if (newline != line) + free(newline); +- return 0; ++ return 0; + } + +- if(strlen(newline) <= sizeof(buf)) { +- out = NULL; +- ptr = rfc822comments(newline, buf); +- } else +- ptr = out = rfc822comments(newline, NULL); ++ if (strlen(newline) <= sizeof(buf)) { ++ out = NULL; ++ ptr = rfc822comments(newline, buf); ++ } else ++ ptr = out = rfc822comments(newline, NULL); + +- if(ptr == NULL) +- ptr = newline; ++ if (ptr == NULL) ++ ptr = newline; + +- if((*ptr++ != '-') || (*ptr == '\0')) { +- if(out) +- free(out); ++ if ((*ptr++ != '-') || (*ptr == '\0')) { ++ if (out) ++ free(out); + if (newline != line) + free(newline); + +- return 0; +- } ++ return 0; ++ } + +- /* ++ /* + * Gibe.B3 is broken, it has: + * boundary="---- =_NextPart_000_01C31177.9DC7C000" + * but it's boundaries look like +@@ -2512,45 +2490,45 @@ boundaryStart(const char *line, const char *boundary) + * they're not. Irrespective of whatever RFC2822 says, we need to find + * viruses in both types of mails. + */ +- if((strstr(&ptr[1], boundary) != NULL) || (strstr(newline, boundary) != NULL)) { +- const char *k = ptr; ++ if ((strstr(&ptr[1], boundary) != NULL) || (strstr(newline, boundary) != NULL)) { ++ const char *k = ptr; + +- /* ++ /* + * We need to ensure that we don't match --11=-=-=11 when + * looking for --1=-=-=1 in well behaved headers, that's a + * false positive problem mentioned above + */ +- rc = 0; +- do +- if(strcmp(++k, boundary) == 0) { +- rc = 1; +- break; +- } +- while(*k == '-'); +- if(rc == 0) { +- k = &line[1]; +- do +- if(strcmp(++k, boundary) == 0) { +- rc = 1; +- break; +- } +- while(*k == '-'); +- } +- } else if(*ptr++ != '-') +- rc = 0; +- else +- rc = (strcasecmp(ptr, boundary) == 0); +- +- if(out) +- free(out); +- +- if(rc == 1) +- cli_dbgmsg("boundaryStart: found %s in %s\n", boundary, line); ++ rc = 0; ++ do ++ if (strcmp(++k, boundary) == 0) { ++ rc = 1; ++ break; ++ } ++ while (*k == '-'); ++ if (rc == 0) { ++ k = &line[1]; ++ do ++ if (strcmp(++k, boundary) == 0) { ++ rc = 1; ++ break; ++ } ++ while (*k == '-'); ++ } ++ } else if (*ptr++ != '-') ++ rc = 0; ++ else ++ rc = (strcasecmp(ptr, boundary) == 0); ++ ++ if (out) ++ free(out); ++ ++ if (rc == 1) ++ cli_dbgmsg("boundaryStart: found %s in %s\n", boundary, line); + + if (newline != line) + free(newline); + +- return rc; ++ return rc; + } + + /* +@@ -2561,15 +2539,15 @@ boundaryStart(const char *line, const char *boundary) + static int + boundaryEnd(const char *line, const char *boundary) + { +- size_t len; ++ size_t len; + char *newline, *p, *p2; + +- if(line == NULL || *line == '\0') +- return 0; ++ if (line == NULL || *line == '\0') ++ return 0; + + p = newline = strdup(line); + if (!(newline)) { +- p = (char *)line; ++ p = (char *)line; + newline = (char *)line; + } + +@@ -2580,59 +2558,59 @@ boundaryEnd(const char *line, const char *boundary) + *(p2--) = '\0'; + } + +- /* cli_dbgmsg("boundaryEnd: line = '%s' boundary = '%s'\n", newline, boundary); */ ++ /* cli_dbgmsg("boundaryEnd: line = '%s' boundary = '%s'\n", newline, boundary); */ + +- if(*p++ != '-') { ++ if (*p++ != '-') { + if (newline != line) + free(newline); +- return 0; ++ return 0; + } + +- if(*p++ != '-') { ++ if (*p++ != '-') { + if (newline != line) + free(newline); + +- return 0; ++ return 0; + } + +- len = strlen(boundary); +- if(strncasecmp(p, boundary, len) != 0) { ++ len = strlen(boundary); ++ if (strncasecmp(p, boundary, len) != 0) { + if (newline != line) + free(newline); + +- return 0; ++ return 0; + } +- /* ++ /* + * Use < rather than == because some broken mails have white + * space after the boundary + */ +- if(strlen(p) < (len + 2)) { ++ if (strlen(p) < (len + 2)) { + if (newline != line) + free(newline); + +- return 0; ++ return 0; + } + +- p = &p[len]; +- if(*p++ != '-') { ++ p = &p[len]; ++ if (*p++ != '-') { + if (newline != line) + free(newline); + +- return 0; ++ return 0; + } + +- if(*p == '-') { +- /* cli_dbgmsg("boundaryEnd: found %s in %s\n", boundary, p); */ ++ if (*p == '-') { ++ /* cli_dbgmsg("boundaryEnd: found %s in %s\n", boundary, p); */ + if (newline != line) + free(newline); + +- return 1; +- } ++ return 1; ++ } + + if (newline != line) + free(newline); + +- return 0; ++ return 0; + } + + /* +@@ -2641,34 +2619,34 @@ boundaryEnd(const char *line, const char *boundary) + static int + initialiseTables(table_t **rfc821Table, table_t **subtypeTable) + { +- const struct tableinit *tableinit; ++ const struct tableinit *tableinit; + +- /* ++ /* + * Initialise the various look up tables + */ +- *rfc821Table = tableCreate(); +- assert(*rfc821Table != NULL); +- +- for(tableinit = rfc821headers; tableinit->key; tableinit++) +- if(tableInsert(*rfc821Table, tableinit->key, tableinit->value) < 0) { +- tableDestroy(*rfc821Table); +- *rfc821Table = NULL; +- return -1; +- } +- +- *subtypeTable = tableCreate(); +- assert(*subtypeTable != NULL); +- +- for(tableinit = mimeSubtypes; tableinit->key; tableinit++) +- if(tableInsert(*subtypeTable, tableinit->key, tableinit->value) < 0) { +- tableDestroy(*rfc821Table); +- tableDestroy(*subtypeTable); +- *rfc821Table = NULL; +- *subtypeTable = NULL; +- return -1; +- } +- +- return 0; ++ *rfc821Table = tableCreate(); ++ assert(*rfc821Table != NULL); ++ ++ for (tableinit = rfc821headers; tableinit->key; tableinit++) ++ if (tableInsert(*rfc821Table, tableinit->key, tableinit->value) < 0) { ++ tableDestroy(*rfc821Table); ++ *rfc821Table = NULL; ++ return -1; ++ } ++ ++ *subtypeTable = tableCreate(); ++ assert(*subtypeTable != NULL); ++ ++ for (tableinit = mimeSubtypes; tableinit->key; tableinit++) ++ if (tableInsert(*subtypeTable, tableinit->key, tableinit->value) < 0) { ++ tableDestroy(*rfc821Table); ++ tableDestroy(*subtypeTable); ++ *rfc821Table = NULL; ++ *subtypeTable = NULL; ++ return -1; ++ } ++ ++ return 0; + } + + /* +@@ -2682,17 +2660,17 @@ initialiseTables(table_t **rfc821Table, table_t **subtypeTable) + static int + getTextPart(message *const messages[], size_t size) + { +- size_t i; +- int textpart = -1; ++ size_t i; ++ int textpart = -1; + +- for(i = 0; i < size; i++) +- if(messages[i] && (messageGetMimeType(messages[i]) == TEXT)) { +- if(strcasecmp(messageGetMimeSubtype(messages[i]), "html") == 0) +- return (int)i; +- textpart = (int)i; +- } ++ for (i = 0; i < size; i++) ++ if (messages[i] && (messageGetMimeType(messages[i]) == TEXT)) { ++ if (strcasecmp(messageGetMimeSubtype(messages[i]), "html") == 0) ++ return (int)i; ++ textpart = (int)i; ++ } + +- return textpart; ++ return textpart; + } + + /* +@@ -2712,31 +2690,31 @@ getTextPart(message *const messages[], size_t size) + static size_t + strip(char *buf, int len) + { +- register char *ptr; +- register size_t i; +- +- if((buf == NULL) || (len <= 0)) +- return 0; +- +- i = strlen(buf); +- if(len > (int)(i + 1)) +- return i; +- ptr = &buf[--len]; +- +-#if defined(UNIX) || defined(C_LINUX) || defined(C_DARWIN) /* watch - it may be in shared text area */ +- do +- if(*ptr) +- *ptr = '\0'; +- while((--len >= 0) && (!isgraph(*--ptr)) && (*ptr != '\n') && (*ptr != '\r')); +-#else /* more characters can be displayed on DOS */ +- do +-#ifndef REAL_MODE_DOS +- if(*ptr) /* C8.0 puts into a text area */ ++ register char *ptr; ++ register size_t i; ++ ++ if ((buf == NULL) || (len <= 0)) ++ return 0; ++ ++ i = strlen(buf); ++ if (len > (int)(i + 1)) ++ return i; ++ ptr = &buf[--len]; ++ ++#if defined(UNIX) || defined(C_LINUX) || defined(C_DARWIN) /* watch - it may be in shared text area */ ++ do ++ if (*ptr) ++ *ptr = '\0'; ++ while ((--len >= 0) && (!isgraph(*--ptr)) && (*ptr != '\n') && (*ptr != '\r')); ++#else /* more characters can be displayed on DOS */ ++ do ++#ifndef REAL_MODE_DOS ++ if (*ptr) /* C8.0 puts into a text area */ + #endif +- *ptr = '\0'; +- while((--len >= 0) && ((*--ptr == '\0') || isspace((int)(*ptr & 0xFF)))); ++ *ptr = '\0'; ++ while ((--len >= 0) && ((*--ptr == '\0') || isspace((int)(*ptr & 0xFF)))); + #endif +- return((size_t)(len + 1)); ++ return ((size_t)(len + 1)); + } + + /* +@@ -2746,10 +2724,10 @@ strip(char *buf, int len) + size_t + strstrip(char *s) + { +- if(s == (char *)NULL) +- return(0); ++ if (s == (char *)NULL) ++ return (0); + +- return(strip(s, strlen(s) + 1)); ++ return (strip(s, strlen(s) + 1)); + } + + /* +@@ -2758,38 +2736,38 @@ strstrip(char *s) + static int + parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const char *arg) + { +- char *copy, *p, *buf; +- const char *ptr; +- int commandNumber; ++ char *copy, *p, *buf; ++ const char *ptr; ++ int commandNumber; + +- cli_dbgmsg("parseMimeHeader: cmd='%s', arg='%s'\n", cmd, arg); ++ cli_dbgmsg("parseMimeHeader: cmd='%s', arg='%s'\n", cmd, arg); + +- copy = rfc822comments(cmd, NULL); +- if(copy) { +- commandNumber = tableFind(rfc821Table, copy); +- free(copy); +- } else +- commandNumber = tableFind(rfc821Table, cmd); ++ copy = rfc822comments(cmd, NULL); ++ if (copy) { ++ commandNumber = tableFind(rfc821Table, copy); ++ free(copy); ++ } else ++ commandNumber = tableFind(rfc821Table, cmd); + +- copy = rfc822comments(arg, NULL); ++ copy = rfc822comments(arg, NULL); + +- if(copy) +- ptr = copy; +- else +- ptr = arg; ++ if (copy) ++ ptr = copy; ++ else ++ ptr = arg; + +- buf = NULL; ++ buf = NULL; + +- switch(commandNumber) { +- case CONTENT_TYPE: +- /* ++ switch (commandNumber) { ++ case CONTENT_TYPE: ++ /* + * Fix for non RFC1521 compliant mailers + * that send content-type: Text instead + * of content-type: Text/Plain, or + * just simply "Content-Type:" + */ +- if(arg == NULL) +- /* ++ if (arg == NULL) ++ /* + * According to section 4 of RFC1521: + * "Note also that a subtype specification is + * MANDATORY. There are no default subtypes" +@@ -2798,155 +2776,155 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c + * for the subtype because virus writers and + * email client writers don't get it right + */ +- cli_dbgmsg("Empty content-type received, no subtype specified, assuming text/plain; charset=us-ascii\n"); +- else if(strchr(ptr, '/') == NULL) +- /* ++ cli_dbgmsg("Empty content-type received, no subtype specified, assuming text/plain; charset=us-ascii\n"); ++ else if (strchr(ptr, '/') == NULL) ++ /* + * Empty field, such as + * Content-Type: + * which I believe is illegal according to + * RFC1521 + */ +- cli_dbgmsg("Invalid content-type '%s' received, no subtype specified, assuming text/plain; charset=us-ascii\n", ptr); +- else { +- int i; ++ cli_dbgmsg("Invalid content-type '%s' received, no subtype specified, assuming text/plain; charset=us-ascii\n", ptr); ++ else { ++ int i; + +- buf = cli_malloc(strlen(ptr) + 1); +- if(buf == NULL) { ++ buf = cli_malloc(strlen(ptr) + 1); ++ if (buf == NULL) { + cli_errmsg("parseMimeHeader: Unable to allocate memory for buf %llu\n", (long long unsigned)(strlen(ptr) + 1)); +- if(copy) +- free(copy); +- return -1; +- } +- /* ++ if (copy) ++ free(copy); ++ return -1; ++ } ++ /* + * Some clients are broken and + * put white space after the ; + */ +- if(*arg == '/') { +- cli_dbgmsg("Content-type '/' received, assuming application/octet-stream\n"); +- messageSetMimeType(m, "application"); +- messageSetMimeSubtype(m, "octet-stream"); +- } else { +- /* ++ if (*arg == '/') { ++ cli_dbgmsg("Content-type '/' received, assuming application/octet-stream\n"); ++ messageSetMimeType(m, "application"); ++ messageSetMimeSubtype(m, "octet-stream"); ++ } else { ++ /* + * The content type could be in quotes: + * Content-Type: "multipart/mixed" + * FIXME: this is a hack in that ignores + * the quotes, it doesn't handle + * them properly + */ +- while(isspace(*ptr)) +- ptr++; +- if(ptr[0] == '\"') +- ptr++; ++ while (isspace(*ptr)) ++ ptr++; ++ if (ptr[0] == '\"') ++ ptr++; + +- if(ptr[0] != '/') { +- char *s; ++ if (ptr[0] != '/') { ++ char *s; + #ifdef CL_THREAD_SAFE +- char *strptr = NULL; ++ char *strptr = NULL; + #endif + +- s = cli_strtokbuf(ptr, 0, ";", buf); +- /* ++ s = cli_strtokbuf(ptr, 0, ";", buf); ++ /* + * Handle + * Content-Type: foo/bar multipart/mixed + * and + * Content-Type: multipart/mixed foo/bar + */ +- if(s && *s) { +- char *buf2 = cli_strdup(buf); +- +- if(buf2 == NULL) { +- if(copy) +- free(copy); +- free(buf); +- return -1; +- } +- for(;;) { +-#ifdef CL_THREAD_SAFE +- int set = messageSetMimeType(m, strtok_r(s, "/", &strptr)); ++ if (s && *s) { ++ char *buf2 = cli_strdup(buf); ++ ++ if (buf2 == NULL) { ++ if (copy) ++ free(copy); ++ free(buf); ++ return -1; ++ } ++ for (;;) { ++#ifdef CL_THREAD_SAFE ++ int set = messageSetMimeType(m, strtok_r(s, "/", &strptr)); + #else +- int set = messageSetMimeType(m, strtok(s, "/")); ++ int set = messageSetMimeType(m, strtok(s, "/")); + #endif + +-#ifdef CL_THREAD_SAFE +- s = strtok_r(NULL, ";", &strptr); ++#ifdef CL_THREAD_SAFE ++ s = strtok_r(NULL, ";", &strptr); + #else +- s = strtok(NULL, ";"); ++ s = strtok(NULL, ";"); + #endif +- if(s == NULL) +- break; +- if(set) { +- size_t len = strstrip(s) - 1; +- if(s[len] == '\"') { +- s[len] = '\0'; +- len = strstrip(s); +- } +- if(len) { +- if(strchr(s, ' ')) +- messageSetMimeSubtype(m, +- cli_strtokbuf(s, 0, " ", buf2)); +- else +- messageSetMimeSubtype(m, s); +- } +- } +- +- while(*s && !isspace(*s)) +- s++; +- if(*s++ == '\0') +- break; +- if(*s == '\0') +- break; +- } +- free(buf2); +- } +- } +- } +- +- /* ++ if (s == NULL) ++ break; ++ if (set) { ++ size_t len = strstrip(s) - 1; ++ if (s[len] == '\"') { ++ s[len] = '\0'; ++ len = strstrip(s); ++ } ++ if (len) { ++ if (strchr(s, ' ')) ++ messageSetMimeSubtype(m, ++ cli_strtokbuf(s, 0, " ", buf2)); ++ else ++ messageSetMimeSubtype(m, s); ++ } ++ } ++ ++ while (*s && !isspace(*s)) ++ s++; ++ if (*s++ == '\0') ++ break; ++ if (*s == '\0') ++ break; ++ } ++ free(buf2); ++ } ++ } ++ } ++ ++ /* + * Add in all rest of the the arguments. + * e.g. if the header is this: + * Content-Type:', arg='multipart/mixed; boundary=foo + * we find the boundary argument set it + */ +- i = 1; +- while(cli_strtokbuf(ptr, i++, ";", buf) != NULL) { +- cli_dbgmsg("mimeArgs = '%s'\n", buf); +- +- messageAddArguments(m, buf); +- } +- } +- break; +- case CONTENT_TRANSFER_ENCODING: +- messageSetEncoding(m, ptr); +- break; +- case CONTENT_DISPOSITION: +- buf = cli_malloc(strlen(ptr) + 1); +- if(buf == NULL) { ++ i = 1; ++ while (cli_strtokbuf(ptr, i++, ";", buf) != NULL) { ++ cli_dbgmsg("mimeArgs = '%s'\n", buf); ++ ++ messageAddArguments(m, buf); ++ } ++ } ++ break; ++ case CONTENT_TRANSFER_ENCODING: ++ messageSetEncoding(m, ptr); ++ break; ++ case CONTENT_DISPOSITION: ++ buf = cli_malloc(strlen(ptr) + 1); ++ if (buf == NULL) { + cli_errmsg("parseMimeHeader: Unable to allocate memory for buf %llu\n", (long long unsigned)(strlen(ptr) + 1)); +- if(copy) +- free(copy); +- return -1; +- } +- p = cli_strtokbuf(ptr, 0, ";", buf); +- if(p && *p) { +- messageSetDispositionType(m, p); +- messageAddArgument(m, cli_strtokbuf(ptr, 1, ";", buf)); +- } +- if(!messageHasFilename(m)) +- /* ++ if (copy) ++ free(copy); ++ return -1; ++ } ++ p = cli_strtokbuf(ptr, 0, ";", buf); ++ if (p && *p) { ++ messageSetDispositionType(m, p); ++ messageAddArgument(m, cli_strtokbuf(ptr, 1, ";", buf)); ++ } ++ if (!messageHasFilename(m)) ++ /* + * Handle this type of header, without + * a filename (e.g. some Worm.Torvil.D) + * Content-ID: + * Content-Transfer-Encoding: base64 + * Content-Disposition: attachment + */ +- messageAddArgument(m, "filename=unknown"); +- } +- if(copy) +- free(copy); +- if(buf) +- free(buf); +- +- return 0; ++ messageAddArgument(m, "filename=unknown"); ++ } ++ if (copy) ++ free(copy); ++ if (buf) ++ free(buf); ++ ++ return 0; + } + + /* +@@ -2955,19 +2933,19 @@ parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Table, const c + static int + saveTextPart(mbox_ctx *mctx, message *m, int destroy_text) + { +- fileblob *fb; ++ fileblob *fb; + +- messageAddArgument(m, "filename=textportion"); +- if((fb = messageToFileblob(m, mctx->dir, destroy_text)) != NULL) { +- /* ++ messageAddArgument(m, "filename=textportion"); ++ if ((fb = messageToFileblob(m, mctx->dir, destroy_text)) != NULL) { ++ /* + * Save main part to scan that + */ +- cli_dbgmsg("Saving main message\n"); ++ cli_dbgmsg("Saving main message\n"); + +- mctx->files++; +- return fileblobScanAndDestroy(fb); +- } +- return CL_ETMPFILE; ++ mctx->files++; ++ return fileblobScanAndDestroy(fb); ++ } ++ return CL_ETMPFILE; + } + + /* +@@ -2981,73 +2959,74 @@ saveTextPart(mbox_ctx *mctx, message *m, int destroy_text) + static char * + rfc822comments(const char *in, char *out) + { +- const char *iptr; +- char *optr; +- int backslash, inquote, commentlevel; ++ const char *iptr; ++ char *optr; ++ int backslash, inquote, commentlevel; + +- if(in == NULL) +- return NULL; ++ if (in == NULL) ++ return NULL; + +- if(strchr(in, '(') == NULL) +- return NULL; ++ if (strchr(in, '(') == NULL) ++ return NULL; + +- assert(out != in); ++ assert(out != in); + +- while(isspace(*in)) +- in++; ++ while (isspace(*in)) ++ in++; + +- if(out == NULL) { +- out = cli_malloc(strlen(in) + 1); +- if(out == NULL) { ++ if (out == NULL) { ++ out = cli_malloc(strlen(in) + 1); ++ if (out == NULL) { + cli_errmsg("rfc822comments: Unable to allocate memory for out %llu\n", (long long unsigned)(strlen(in) + 1)); +- return NULL; ++ return NULL; + } +- } +- +- backslash = commentlevel = inquote = 0; +- optr = out; +- +- cli_dbgmsg("rfc822comments: contains a comment\n"); +- +- for(iptr = in; *iptr; iptr++) +- if(backslash) { +- if(commentlevel == 0) +- *optr++ = *iptr; +- backslash = 0; +- } else switch(*iptr) { +- case '\\': +- backslash = 1; +- break; +- case '\"': +- *optr++ = '\"'; +- inquote = !inquote; +- break; +- case '(': +- if(inquote) +- *optr++ = '('; +- else +- commentlevel++; +- break; +- case ')': +- if(inquote) +- *optr++ = ')'; +- else if(commentlevel > 0) +- commentlevel--; +- break; +- default: +- if(commentlevel == 0) +- *optr++ = *iptr; +- } +- +- if(backslash) /* last character was a single backslash */ +- *optr++ = '\\'; +- *optr = '\0'; +- +- /*strstrip(out);*/ +- +- cli_dbgmsg("rfc822comments '%s'=>'%s'\n", in, out); +- +- return out; ++ } ++ ++ backslash = commentlevel = inquote = 0; ++ optr = out; ++ ++ cli_dbgmsg("rfc822comments: contains a comment\n"); ++ ++ for (iptr = in; *iptr; iptr++) ++ if (backslash) { ++ if (commentlevel == 0) ++ *optr++ = *iptr; ++ backslash = 0; ++ } else ++ switch (*iptr) { ++ case '\\': ++ backslash = 1; ++ break; ++ case '\"': ++ *optr++ = '\"'; ++ inquote = !inquote; ++ break; ++ case '(': ++ if (inquote) ++ *optr++ = '('; ++ else ++ commentlevel++; ++ break; ++ case ')': ++ if (inquote) ++ *optr++ = ')'; ++ else if (commentlevel > 0) ++ commentlevel--; ++ break; ++ default: ++ if (commentlevel == 0) ++ *optr++ = *iptr; ++ } ++ ++ if (backslash) /* last character was a single backslash */ ++ *optr++ = '\\'; ++ *optr = '\0'; ++ ++ /*strstrip(out);*/ ++ ++ cli_dbgmsg("rfc822comments '%s'=>'%s'\n", in, out); ++ ++ return out; + } + + /* +@@ -3057,110 +3036,109 @@ rfc822comments(const char *in, char *out) + static char * + rfc2047(const char *in) + { +- char *out, *pout; +- size_t len; ++ char *out, *pout; ++ size_t len; + +- if((strstr(in, "=?") == NULL) || (strstr(in, "?=") == NULL)) +- return cli_strdup(in); ++ if ((strstr(in, "=?") == NULL) || (strstr(in, "?=") == NULL)) ++ return cli_strdup(in); + +- cli_dbgmsg("rfc2047 '%s'\n", in); +- out = cli_malloc(strlen(in) + 1); ++ cli_dbgmsg("rfc2047 '%s'\n", in); ++ out = cli_malloc(strlen(in) + 1); + +- if(out == NULL) { ++ if (out == NULL) { + cli_errmsg("rfc2047: Unable to allocate memory for out %llu\n", (long long unsigned)(strlen(in) + 1)); +- return NULL; ++ return NULL; + } + +- pout = out; +- +- /* For each RFC2047 string */ +- while(*in) { +- char encoding, *ptr, *enctext; +- message *m; +- blob *b; +- +- /* Find next RFC2047 string */ +- while(*in) { +- if((*in == '=') && (in[1] == '?')) { +- in += 2; +- break; +- } +- *pout++ = *in++; +- } +- /* Skip over charset, find encoding */ +- while((*in != '?') && *in) +- in++; +- if(*in == '\0') +- break; +- encoding = *++in; +- encoding = (char)tolower(encoding); +- +- if((encoding != 'q') && (encoding != 'b')) { +- cli_warnmsg("Unsupported RFC2047 encoding type '%c' - if you believe this file contains a virus, submit it to www.clamav.net\n", encoding); +- free(out); +- out = NULL; +- break; +- } +- /* Skip to encoded text */ +- if(*++in != '?') +- break; +- if(*++in == '\0') +- break; +- +- enctext = cli_strdup(in); +- if(enctext == NULL) { +- free(out); +- out = NULL; +- break; +- } +- in = strstr(in, "?="); +- if(in == NULL) { +- free(enctext); +- break; +- } +- in += 2; +- ptr = strstr(enctext, "?="); +- assert(ptr != NULL); +- *ptr = '\0'; +- /*cli_dbgmsg("Need to decode '%s' with method '%c'\n", enctext, encoding);*/ +- +- m = messageCreate(); +- if(m == NULL) +- break; +- messageAddStr(m, enctext); +- free(enctext); +- switch(encoding) { +- case 'q': +- messageSetEncoding(m, "quoted-printable"); +- break; +- case 'b': +- messageSetEncoding(m, "base64"); +- break; +- } +- b = messageToBlob(m, 1); +- if (b == NULL) { +- messageDestroy(m); +- break; +- } +- len = blobGetDataSize(b); +- cli_dbgmsg("Decoded as '%*.*s'\n", (int)len, (int)len, +- (const char *)blobGetData(b)); +- memcpy(pout, blobGetData(b), len); +- blobDestroy(b); +- messageDestroy(m); +- if(len > 0 && pout[len - 1] == '\n') +- pout += len - 1; +- else +- pout += len; +- +- } +- if(out == NULL) +- return NULL; +- +- *pout = '\0'; +- +- cli_dbgmsg("rfc2047 returns '%s'\n", out); +- return out; ++ pout = out; ++ ++ /* For each RFC2047 string */ ++ while (*in) { ++ char encoding, *ptr, *enctext; ++ message *m; ++ blob *b; ++ ++ /* Find next RFC2047 string */ ++ while (*in) { ++ if ((*in == '=') && (in[1] == '?')) { ++ in += 2; ++ break; ++ } ++ *pout++ = *in++; ++ } ++ /* Skip over charset, find encoding */ ++ while ((*in != '?') && *in) ++ in++; ++ if (*in == '\0') ++ break; ++ encoding = *++in; ++ encoding = (char)tolower(encoding); ++ ++ if ((encoding != 'q') && (encoding != 'b')) { ++ cli_warnmsg("Unsupported RFC2047 encoding type '%c' - if you believe this file contains a virus, submit it to www.clamav.net\n", encoding); ++ free(out); ++ out = NULL; ++ break; ++ } ++ /* Skip to encoded text */ ++ if (*++in != '?') ++ break; ++ if (*++in == '\0') ++ break; ++ ++ enctext = cli_strdup(in); ++ if (enctext == NULL) { ++ free(out); ++ out = NULL; ++ break; ++ } ++ in = strstr(in, "?="); ++ if (in == NULL) { ++ free(enctext); ++ break; ++ } ++ in += 2; ++ ptr = strstr(enctext, "?="); ++ assert(ptr != NULL); ++ *ptr = '\0'; ++ /*cli_dbgmsg("Need to decode '%s' with method '%c'\n", enctext, encoding);*/ ++ ++ m = messageCreate(); ++ if (m == NULL) ++ break; ++ messageAddStr(m, enctext); ++ free(enctext); ++ switch (encoding) { ++ case 'q': ++ messageSetEncoding(m, "quoted-printable"); ++ break; ++ case 'b': ++ messageSetEncoding(m, "base64"); ++ break; ++ } ++ b = messageToBlob(m, 1); ++ if (b == NULL) { ++ messageDestroy(m); ++ break; ++ } ++ len = blobGetDataSize(b); ++ cli_dbgmsg("Decoded as '%*.*s'\n", (int)len, (int)len, ++ (const char *)blobGetData(b)); ++ memcpy(pout, blobGetData(b), len); ++ blobDestroy(b); ++ messageDestroy(m); ++ if (len > 0 && pout[len - 1] == '\n') ++ pout += len - 1; ++ else ++ pout += len; ++ } ++ if (out == NULL) ++ return NULL; ++ ++ *pout = '\0'; ++ ++ cli_dbgmsg("rfc2047 returns '%s'\n", out); ++ return out; + } + + /* +@@ -3169,252 +3147,252 @@ rfc2047(const char *in) + static int + rfc1341(message *m, const char *dir) + { +- char *arg, *id, *number, *total, *oldfilename; +- const char *tmpdir; +- int n; +- char pdir[NAME_MAX + 1]; +- unsigned char md5_val[16]; +- char *md5_hex; +- +- id = (char *)messageFindArgument(m, "id"); +- if(id == NULL) +- return -1; +- +- tmpdir = cli_gettmpdir(); +- +- snprintf(pdir, sizeof(pdir) - 1, "%s"PATHSEP"clamav-partial", tmpdir); +- +- if((mkdir(pdir, S_IRWXU) < 0) && (errno != EEXIST)) { +- cli_errmsg("Can't create the directory '%s'\n", pdir); +- free(id); +- return -1; +- } else if(errno == EEXIST) { +- STATBUF statb; +- +- if(CLAMSTAT(pdir, &statb) < 0) { +- char err[128]; +- cli_errmsg("Partial directory %s: %s\n", pdir, +- cli_strerror(errno, err, sizeof(err))); +- free(id); +- return -1; +- } +- if(statb.st_mode & 077) +- cli_warnmsg("Insecure partial directory %s (mode 0%o)\n", +- pdir, +-#ifdef ACCESSPERMS +- (int)(statb.st_mode&ACCESSPERMS) ++ char *arg, *id, *number, *total, *oldfilename; ++ const char *tmpdir; ++ int n; ++ char pdir[NAME_MAX + 1]; ++ unsigned char md5_val[16]; ++ char *md5_hex; ++ ++ id = (char *)messageFindArgument(m, "id"); ++ if (id == NULL) ++ return -1; ++ ++ tmpdir = cli_gettmpdir(); ++ ++ snprintf(pdir, sizeof(pdir) - 1, "%s" PATHSEP "clamav-partial", tmpdir); ++ ++ if ((mkdir(pdir, S_IRWXU) < 0) && (errno != EEXIST)) { ++ cli_errmsg("Can't create the directory '%s'\n", pdir); ++ free(id); ++ return -1; ++ } else if (errno == EEXIST) { ++ STATBUF statb; ++ ++ if (CLAMSTAT(pdir, &statb) < 0) { ++ char err[128]; ++ cli_errmsg("Partial directory %s: %s\n", pdir, ++ cli_strerror(errno, err, sizeof(err))); ++ free(id); ++ return -1; ++ } ++ if (statb.st_mode & 077) ++ cli_warnmsg("Insecure partial directory %s (mode 0%o)\n", ++ pdir, ++#ifdef ACCESSPERMS ++ (int)(statb.st_mode & ACCESSPERMS) + #else +- (int)(statb.st_mode & 0777) ++ (int)(statb.st_mode & 0777) + #endif +- ); +- } +- +- number = (char *)messageFindArgument(m, "number"); +- if(number == NULL) { +- free(id); +- return -1; +- } +- +- oldfilename = messageGetFilename(m); +- +- arg = cli_malloc(10 + strlen(id) + strlen(number)); +- if(arg) { +- sprintf(arg, "filename=%s%s", id, number); +- messageAddArgument(m, arg); +- free(arg); +- } +- +- if(oldfilename) { +- cli_dbgmsg("Must reset to %s\n", oldfilename); +- free(oldfilename); +- } +- +- n = atoi(number); ++ ); ++ } ++ ++ number = (char *)messageFindArgument(m, "number"); ++ if (number == NULL) { ++ free(id); ++ return -1; ++ } ++ ++ oldfilename = messageGetFilename(m); ++ ++ arg = cli_malloc(10 + strlen(id) + strlen(number)); ++ if (arg) { ++ sprintf(arg, "filename=%s%s", id, number); ++ messageAddArgument(m, arg); ++ free(arg); ++ } ++ ++ if (oldfilename) { ++ cli_dbgmsg("Must reset to %s\n", oldfilename); ++ free(oldfilename); ++ } ++ ++ n = atoi(number); + cl_hash_data("md5", id, strlen(id), md5_val, NULL); +- md5_hex = cli_str2hex((const char*)md5_val, 16); +- +- if(!md5_hex) { +- free(id); +- free(number); +- return CL_EMEM; +- } +- +- if(messageSavePartial(m, pdir, md5_hex, n) < 0) { +- free(md5_hex); +- free(id); +- free(number); +- return -1; +- } +- +- total = (char *)messageFindArgument(m, "total"); +- cli_dbgmsg("rfc1341: %s, %s of %s\n", id, number, (total) ? total : "?"); +- if(total) { +- int t = atoi(total); +- DIR *dd = NULL; +- +- free(total); +- /* ++ md5_hex = cli_str2hex((const char *)md5_val, 16); ++ ++ if (!md5_hex) { ++ free(id); ++ free(number); ++ return CL_EMEM; ++ } ++ ++ if (messageSavePartial(m, pdir, md5_hex, n) < 0) { ++ free(md5_hex); ++ free(id); ++ free(number); ++ return -1; ++ } ++ ++ total = (char *)messageFindArgument(m, "total"); ++ cli_dbgmsg("rfc1341: %s, %s of %s\n", id, number, (total) ? total : "?"); ++ if (total) { ++ int t = atoi(total); ++ DIR *dd = NULL; ++ ++ free(total); ++ /* + * If it's the last one - reassemble it + * FIXME: this assumes that we receive the parts in order + */ +- if((n == t) && ((dd = opendir(pdir)) != NULL)) { +- FILE *fout; +- char outname[NAME_MAX + 1]; +- time_t now; +- +- sanitiseName(id); +- +- snprintf(outname, sizeof(outname) - 1, "%s"PATHSEP"%s", dir, id); +- +- cli_dbgmsg("outname: %s\n", outname); +- +- fout = fopen(outname, "wb"); +- if(fout == NULL) { +- cli_errmsg("Can't open '%s' for writing", outname); +- free(id); +- free(number); +- free(md5_hex); +- closedir(dd); +- return -1; +- } +- +- time(&now); +- for(n = 1; n <= t; n++) { +- char filename[NAME_MAX + 1]; +- struct dirent *dent; ++ if ((n == t) && ((dd = opendir(pdir)) != NULL)) { ++ FILE *fout; ++ char outname[NAME_MAX + 1]; ++ time_t now; ++ ++ sanitiseName(id); ++ ++ snprintf(outname, sizeof(outname) - 1, "%s" PATHSEP "%s", dir, id); ++ ++ cli_dbgmsg("outname: %s\n", outname); ++ ++ fout = fopen(outname, "wb"); ++ if (fout == NULL) { ++ cli_errmsg("Can't open '%s' for writing", outname); ++ free(id); ++ free(number); ++ free(md5_hex); ++ closedir(dd); ++ return -1; ++ } ++ ++ time(&now); ++ for (n = 1; n <= t; n++) { ++ char filename[NAME_MAX + 1]; ++ struct dirent *dent; + #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2) +- union { +- struct dirent d; +- char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; +- } result; ++ union { ++ struct dirent d; ++ char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; ++ } result; + #endif + +- snprintf(filename, sizeof(filename), "_%s-%u", md5_hex, n); ++ snprintf(filename, sizeof(filename), "_%s-%u", md5_hex, n); + + #ifdef HAVE_READDIR_R_3 +- while((readdir_r(dd, &result.d, &dent) == 0) && dent) { ++ while ((readdir_r(dd, &result.d, &dent) == 0) && dent) { + #elif defined(HAVE_READDIR_R_2) +- while((dent = (struct dirent *)readdir_r(dd, &result.d))) { +-#else /*!HAVE_READDIR_R*/ +- while((dent = readdir(dd))) { ++ while ((dent = (struct dirent *)readdir_r(dd, &result.d))) { ++#else /*!HAVE_READDIR_R*/ ++ while ((dent = readdir(dd))) { + #endif +- FILE *fin; +- char buffer[BUFSIZ], fullname[NAME_MAX + 1]; +- int nblanks; +- STATBUF statb; +- const char *dentry_idpart; ++ FILE *fin; ++ char buffer[BUFSIZ], fullname[NAME_MAX + 1]; ++ int nblanks; ++ STATBUF statb; ++ const char *dentry_idpart; + int test_fd; + +- if(dent->d_ino == 0) +- continue; ++ if (dent->d_ino == 0) ++ continue; + +- if(!strcmp(".",dent->d_name) || +- !strcmp("..", dent->d_name)) +- continue; +- snprintf(fullname, sizeof(fullname) - 1, +- "%s"PATHSEP"%s", pdir, dent->d_name); +- dentry_idpart = strchr(dent->d_name, '_'); ++ if (!strcmp(".", dent->d_name) || ++ !strcmp("..", dent->d_name)) ++ continue; ++ snprintf(fullname, sizeof(fullname) - 1, ++ "%s" PATHSEP "%s", pdir, dent->d_name); ++ dentry_idpart = strchr(dent->d_name, '_'); + +- if(!dentry_idpart || +- strcmp(filename, dentry_idpart) != 0) { +- if(!m->ctx->engine->keeptmp) +- continue; ++ if (!dentry_idpart || ++ strcmp(filename, dentry_idpart) != 0) { ++ if (!m->ctx->engine->keeptmp) ++ continue; + + if ((test_fd = open(fullname, O_RDONLY)) < 0) + continue; + +- if(FSTAT(test_fd, &statb) < 0) { ++ if (FSTAT(test_fd, &statb) < 0) { + close(test_fd); +- continue; ++ continue; + } + +- if(now - statb.st_mtime > (time_t)(7 * 24 * 3600)) { +- if (cli_unlink(fullname)) { +- cli_unlink(outname); +- fclose(fout); +- free(md5_hex); +- free(id); +- free(number); +- closedir(dd); ++ if (now - statb.st_mtime > (time_t)(7 * 24 * 3600)) { ++ if (cli_unlink(fullname)) { ++ cli_unlink(outname); ++ fclose(fout); ++ free(md5_hex); ++ free(id); ++ free(number); ++ closedir(dd); + close(test_fd); +- return -1; +- } +- } ++ return -1; ++ } ++ } + + close(test_fd); +- continue; +- } +- +- fin = fopen(fullname, "rb"); +- if(fin == NULL) { +- cli_errmsg("Can't open '%s' for reading", fullname); +- fclose(fout); +- cli_unlink(outname); +- free(md5_hex); +- free(id); +- free(number); +- closedir(dd); +- return -1; +- } +- nblanks = 0; +- while(fgets(buffer, sizeof(buffer) - 1, fin) != NULL) +- /* ++ continue; ++ } ++ ++ fin = fopen(fullname, "rb"); ++ if (fin == NULL) { ++ cli_errmsg("Can't open '%s' for reading", fullname); ++ fclose(fout); ++ cli_unlink(outname); ++ free(md5_hex); ++ free(id); ++ free(number); ++ closedir(dd); ++ return -1; ++ } ++ nblanks = 0; ++ while (fgets(buffer, sizeof(buffer) - 1, fin) != NULL) ++ /* + * Ensure that trailing newlines + * aren't copied + */ +- if(buffer[0] == '\n') +- nblanks++; +- else { +- if(nblanks) +- do { +- if (putc('\n', fout)==EOF) break; +- } while(--nblanks > 0); +- if (nblanks || fputs(buffer, fout)==EOF) { +- fclose(fin); +- fclose(fout); +- cli_unlink(outname); +- free(md5_hex); +- free(id); +- free(number); +- closedir(dd); +- return -1; +- } +- } +- fclose(fin); +- +- /* don't unlink if leave temps */ +- if(!m->ctx->engine->keeptmp) { +- if(cli_unlink(fullname)) { +- fclose(fout); +- cli_unlink(outname); +- free(md5_hex); +- free(id); +- free(number); +- closedir(dd); +- return -1; +- } +- } +- break; +- } +- rewinddir(dd); +- } +- closedir(dd); +- fclose(fout); +- } +- } +- free(number); +- free(id); +- free(md5_hex); +- +- return 0; ++ if (buffer[0] == '\n') ++ nblanks++; ++ else { ++ if (nblanks) ++ do { ++ if (putc('\n', fout) == EOF) break; ++ } while (--nblanks > 0); ++ if (nblanks || fputs(buffer, fout) == EOF) { ++ fclose(fin); ++ fclose(fout); ++ cli_unlink(outname); ++ free(md5_hex); ++ free(id); ++ free(number); ++ closedir(dd); ++ return -1; ++ } ++ } ++ fclose(fin); ++ ++ /* don't unlink if leave temps */ ++ if (!m->ctx->engine->keeptmp) { ++ if (cli_unlink(fullname)) { ++ fclose(fout); ++ cli_unlink(outname); ++ free(md5_hex); ++ free(id); ++ free(number); ++ closedir(dd); ++ return -1; ++ } ++ } ++ break; ++ } ++ rewinddir(dd); ++ } ++ closedir(dd); ++ fclose(fout); ++ } ++ } ++ free(number); ++ free(id); ++ free(md5_hex); ++ ++ return 0; + } + + static void + hrefs_done(blob *b, tag_arguments_t *hrefs) + { +- if(b) +- blobDestroy(b); +- html_tag_arg_free(hrefs); ++ if (b) ++ blobDestroy(b); ++ html_tag_arg_free(hrefs); + } + + /* extract URLs from static text */ +@@ -3422,28 +3400,28 @@ static void extract_text_urls(const unsigned char *mem, size_t len, tag_argument + { + char url[1024]; + size_t off; +- for (off=0;off + 10 < len;off++) { +- /* check whether this is the start of a URL */ +- int32_t proto = cli_readint32(mem + off); +- /* convert to lowercase */ +- proto |= 0x20202020; +- /* 'http:', 'https:', or 'ftp:' in little-endian */ +- if ((proto == 0x70747468 && +- (mem[off+4] == ':' || (mem[off+5] == 's' && mem[off+6] == ':'))) +- || proto == 0x3a707466) { +- size_t url_len; +- for (url_len=4; off + url_len < len && url_len < (sizeof(url)-1); url_len++) { +- unsigned char c = mem[off + url_len]; +- /* smart compilers will compile this if into ++ for (off = 0; off + 10 < len; off++) { ++ /* check whether this is the start of a URL */ ++ int32_t proto = cli_readint32(mem + off); ++ /* convert to lowercase */ ++ proto |= 0x20202020; ++ /* 'http:', 'https:', or 'ftp:' in little-endian */ ++ if ((proto == 0x70747468 && ++ (mem[off + 4] == ':' || (mem[off + 5] == 's' && mem[off + 6] == ':'))) || ++ proto == 0x3a707466) { ++ size_t url_len; ++ for (url_len = 4; off + url_len < len && url_len < (sizeof(url) - 1); url_len++) { ++ unsigned char c = mem[off + url_len]; ++ /* smart compilers will compile this if into + * a single bt + jb instruction */ +- if (c == ' ' || c == '\n' || c == '\t') +- break; +- } +- memcpy(url, mem + off, url_len); +- url[url_len] = '\0'; +- html_tag_arg_add(hrefs, "href", url); +- off += url_len; +- } ++ if (c == ' ' || c == '\n' || c == '\t') ++ break; ++ } ++ memcpy(url, mem + off, url_len); ++ url[url_len] = '\0'; ++ html_tag_arg_add(hrefs, "href", url); ++ off += url_len; ++ } + } + } + +@@ -3455,44 +3433,44 @@ static void extract_text_urls(const unsigned char *mem, size_t len, tag_argument + static blob * + getHrefs(message *m, tag_arguments_t *hrefs) + { +- unsigned char *mem; +- blob *b = messageToBlob(m, 0); +- size_t len; +- +- if(b == NULL) +- return NULL; +- +- len = blobGetDataSize(b); +- +- if(len == 0) { +- blobDestroy(b); +- return NULL; +- } +- +- /* TODO: make this size customisable */ +- if(len > 100*1024) { +- cli_dbgmsg("Viruses pointed to by URLs not scanned in large message\n"); +- blobDestroy(b); +- return NULL; +- } +- +- hrefs->count = 0; +- hrefs->tag = hrefs->value = NULL; +- hrefs->contents = NULL; +- +- cli_dbgmsg("getHrefs: calling html_normalise_mem\n"); +- mem = blobGetData(b); +- if(!html_normalise_mem(mem, (off_t)len, NULL, hrefs,m->ctx->dconf)) { +- blobDestroy(b); +- return NULL; +- } +- cli_dbgmsg("getHrefs: html_normalise_mem returned\n"); +- if (!hrefs->count && hrefs->scanContents) { +- extract_text_urls(mem, len, hrefs); +- } +- +- /* TODO: Do we need to call remove_html_comments? */ +- return b; ++ unsigned char *mem; ++ blob *b = messageToBlob(m, 0); ++ size_t len; ++ ++ if (b == NULL) ++ return NULL; ++ ++ len = blobGetDataSize(b); ++ ++ if (len == 0) { ++ blobDestroy(b); ++ return NULL; ++ } ++ ++ /* TODO: make this size customisable */ ++ if (len > 100 * 1024) { ++ cli_dbgmsg("Viruses pointed to by URLs not scanned in large message\n"); ++ blobDestroy(b); ++ return NULL; ++ } ++ ++ hrefs->count = 0; ++ hrefs->tag = hrefs->value = NULL; ++ hrefs->contents = NULL; ++ ++ cli_dbgmsg("getHrefs: calling html_normalise_mem\n"); ++ mem = blobGetData(b); ++ if (!html_normalise_mem(mem, (off_t)len, NULL, hrefs, m->ctx->dconf)) { ++ blobDestroy(b); ++ return NULL; ++ } ++ cli_dbgmsg("getHrefs: html_normalise_mem returned\n"); ++ if (!hrefs->count && hrefs->scanContents) { ++ extract_text_urls(mem, len, hrefs); ++ } ++ ++ /* TODO: Do we need to call remove_html_comments? */ ++ return b; + } + + /* +@@ -3502,84 +3480,84 @@ getHrefs(message *m, tag_arguments_t *hrefs) + static void + checkURLs(message *mainMessage, mbox_ctx *mctx, mbox_status *rc, int is_html) + { +- blob *b; +- tag_arguments_t hrefs; ++ blob *b; ++ tag_arguments_t hrefs; + + UNUSEDPARAM(is_html); + +- if(*rc == VIRUS) +- return; ++ if (*rc == VIRUS) ++ return; + +- hrefs.scanContents = mctx->ctx->engine->dboptions&CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE); ++ hrefs.scanContents = mctx->ctx->engine->dboptions & CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE); + +- if(!hrefs.scanContents) +- /* ++ if (!hrefs.scanContents) ++ /* + * Don't waste time extracting hrefs (parsing html), nobody + * will need it + */ +- return; ++ return; + +- hrefs.count = 0; +- hrefs.tag = hrefs.value = NULL; +- hrefs.contents = NULL; ++ hrefs.count = 0; ++ hrefs.tag = hrefs.value = NULL; ++ hrefs.contents = NULL; + +- b = getHrefs(mainMessage, &hrefs); +- if(b) { +- if(hrefs.scanContents) { +- if(phishingScan(mctx->ctx, &hrefs) == CL_VIRUS) { +- /* ++ b = getHrefs(mainMessage, &hrefs); ++ if (b) { ++ if (hrefs.scanContents) { ++ if (phishingScan(mctx->ctx, &hrefs) == CL_VIRUS) { ++ /* + * FIXME: message objects' contents are + * encapsulated so we should not access + * the members directly + */ +- mainMessage->isInfected = TRUE; +- *rc = VIRUS; +- cli_dbgmsg("PH:Phishing found\n"); +- } +- } +- } +- hrefs_done(b,&hrefs); ++ mainMessage->isInfected = TRUE; ++ *rc = VIRUS; ++ cli_dbgmsg("PH:Phishing found\n"); ++ } ++ } ++ } ++ hrefs_done(b, &hrefs); + } + + #ifdef HAVE_BACKTRACE + static void + sigsegv(int sig) + { +- signal(SIGSEGV, SIG_DFL); +- print_trace(1); +- exit(SIGSEGV); ++ signal(SIGSEGV, SIG_DFL); ++ print_trace(1); ++ exit(SIGSEGV); + } + + static void + print_trace(int use_syslog) + { +- void *array[10]; +- size_t size; +- char **strings; +- size_t i; +- pid_t pid = getpid(); ++ void *array[10]; ++ size_t size; ++ char **strings; ++ size_t i; ++ pid_t pid = getpid(); + +- cli_errmsg("Segmentation fault, attempting to print backtrace\n"); ++ cli_errmsg("Segmentation fault, attempting to print backtrace\n"); + +- size = backtrace(array, 10); +- strings = backtrace_symbols(array, size); ++ size = backtrace(array, 10); ++ strings = backtrace_symbols(array, size); + +- cli_errmsg("Backtrace of pid %d:\n", pid); +- if(use_syslog) +- syslog(LOG_ERR, "Backtrace of pid %d:", pid); ++ cli_errmsg("Backtrace of pid %d:\n", pid); ++ if (use_syslog) ++ syslog(LOG_ERR, "Backtrace of pid %d:", pid); + +- for(i = 0; i < size; i++) { +- cli_errmsg("%s\n", strings[i]); +- if(use_syslog) +- syslog(LOG_ERR, "bt[%llu]: %s", (unsigned long long)i, strings[i]); +- } ++ for (i = 0; i < size; i++) { ++ cli_errmsg("%s\n", strings[i]); ++ if (use_syslog) ++ syslog(LOG_ERR, "bt[%llu]: %s", (unsigned long long)i, strings[i]); ++ } + +-#ifdef SAVE_TMP +- cli_errmsg("The errant mail file has been saved\n"); ++#ifdef SAVE_TMP ++ cli_errmsg("The errant mail file has been saved\n"); + #endif +- /* #else TODO: dump the current email */ ++ /* #else TODO: dump the current email */ + +- free(strings); ++ free(strings); + } + #endif + +@@ -3587,21 +3565,21 @@ print_trace(int use_syslog) + static bool + usefulHeader(int commandNumber, const char *cmd) + { +- switch(commandNumber) { +- case CONTENT_TRANSFER_ENCODING: +- case CONTENT_DISPOSITION: +- case CONTENT_TYPE: +- return TRUE; +- default: +- if(strcasecmp(cmd, "From") == 0) +- return TRUE; +- if(strcasecmp(cmd, "Received") == 0) +- return TRUE; +- if(strcasecmp(cmd, "De") == 0) +- return TRUE; +- } +- +- return FALSE; ++ switch (commandNumber) { ++ case CONTENT_TRANSFER_ENCODING: ++ case CONTENT_DISPOSITION: ++ case CONTENT_TYPE: ++ return TRUE; ++ default: ++ if (strcasecmp(cmd, "From") == 0) ++ return TRUE; ++ if (strcasecmp(cmd, "Received") == 0) ++ return TRUE; ++ if (strcasecmp(cmd, "De") == 0) ++ return TRUE; ++ } ++ ++ return FALSE; + } + + /* +@@ -3616,53 +3594,53 @@ getline_from_mbox(char *buffer, size_t buffer_len, fmap_t *map, size_t *at) + size_t input_len = MIN(map->len - *at, buffer_len + 1); + src = cursrc = fmap_need_off_once(map, *at, input_len); + +-/* we check for eof from the result of GETC() ++ /* we check for eof from the result of GETC() + * if(feof(fin)) + return NULL;*/ +- if(!src) { +- cli_dbgmsg("getline_from_mbox: fmap need failed\n"); +- return NULL; ++ if (!src) { ++ cli_dbgmsg("getline_from_mbox: fmap need failed\n"); ++ return NULL; + } +- if((buffer_len == 0) || (buffer == NULL)) { +- cli_errmsg("Invalid call to getline_from_mbox(). Refer to https://www.clamav.net/documents/installing-clamav\n"); +- return NULL; ++ if ((buffer_len == 0) || (buffer == NULL)) { ++ cli_errmsg("Invalid call to getline_from_mbox(). Refer to https://www.clamav.net/documents/installing-clamav\n"); ++ return NULL; + } + + curbuf = buffer; + +- for(i=0; iFrom ", 6) == 0) && !isalnum(line[6])) + return FALSE;*/ + +- len = strlen(line); +- if((len < 6) || (len >= 72)) +- return FALSE; ++ len = strlen(line); ++ if ((len < 6) || (len >= 72)) ++ return FALSE; + +- if((memcmp(line, "From ", 5) == 0) || +- (memcmp(line, ">From ", 6) == 0)) { +- int numSpaces = 0, numDigits = 0; +- +- line += 4; +- +- do +- if(*line == ' ') +- numSpaces++; +- else if(isdigit((*line) & 0xFF)) +- numDigits++; +- while(*++line != '\0'); +- +- if(numSpaces < 6) +- return FALSE; +- if(numDigits < 11) +- return FALSE; +- return TRUE; +- } +- return (bool)(cli_filetype((const unsigned char *)line, len, mctx->ctx->engine) == CL_TYPE_MAIL); ++ if ((memcmp(line, "From ", 5) == 0) || ++ (memcmp(line, ">From ", 6) == 0)) { ++ int numSpaces = 0, numDigits = 0; ++ ++ line += 4; ++ ++ do ++ if (*line == ' ') ++ numSpaces++; ++ else if (isdigit((*line) & 0xFF)) ++ numDigits++; ++ while (*++line != '\0'); ++ ++ if (numSpaces < 6) ++ return FALSE; ++ if (numDigits < 11) ++ return FALSE; ++ return TRUE; ++ } ++ return (bool)(cli_filetype((const unsigned char *)line, len, mctx->ctx->engine) == CL_TYPE_MAIL); + } + + /* +@@ -3720,25 +3698,25 @@ isBounceStart(mbox_ctx *mctx, const char *line) + static bool + exportBinhexMessage(mbox_ctx *mctx, message *m) + { +- bool infected = FALSE; +- fileblob *fb; ++ bool infected = FALSE; ++ fileblob *fb; + +- if(messageGetEncoding(m) == NOENCODING) +- messageSetEncoding(m, "x-binhex"); ++ if (messageGetEncoding(m) == NOENCODING) ++ messageSetEncoding(m, "x-binhex"); + +- fb = messageToFileblob(m, mctx->dir, 0); ++ fb = messageToFileblob(m, mctx->dir, 0); + +- if(fb) { +- cli_dbgmsg("Binhex file decoded to %s\n", +- fileblobGetFilename(fb)); ++ if (fb) { ++ cli_dbgmsg("Binhex file decoded to %s\n", ++ fileblobGetFilename(fb)); + +- if(fileblobScanAndDestroy(fb) == CL_VIRUS) +- infected = TRUE; +- mctx->files++; +- } else +- cli_errmsg("Couldn't decode binhex file to %s\n", mctx->dir); ++ if (fileblobScanAndDestroy(fb) == CL_VIRUS) ++ infected = TRUE; ++ mctx->files++; ++ } else ++ cli_errmsg("Couldn't decode binhex file to %s\n", mctx->dir); + +- return infected; ++ return infected; + } + + /* +@@ -3747,11 +3725,11 @@ exportBinhexMessage(mbox_ctx *mctx, message *m) + static int + exportBounceMessage(mbox_ctx *mctx, text *start) + { +- int rc = CL_CLEAN; +- text *t; +- fileblob *fb; ++ int rc = CL_CLEAN; ++ text *t; ++ fileblob *fb; + +- /* ++ /* + * Attempt to save the original (unbounced) + * message - clamscan will find that in the + * directory and call us again (with any luck) +@@ -3770,80 +3748,80 @@ exportBounceMessage(mbox_ctx *mctx, text *start) + * must remain otherwise non bounce messages + * won't be scanned + */ +- for(t = start; t; t = t->t_next) { +- const char *txt = lineGetData(t->t_line); +- char cmd[RFC2821LENGTH + 1]; +- +- if(txt == NULL) +- continue; +- if(cli_strtokbuf(txt, 0, ":", cmd) == NULL) +- continue; +- +- switch(tableFind(mctx->rfc821Table, cmd)) { +- case CONTENT_TRANSFER_ENCODING: +- if((strstr(txt, "7bit") == NULL) && +- (strstr(txt, "8bit") == NULL)) +- break; +- continue; +- case CONTENT_DISPOSITION: +- break; +- case CONTENT_TYPE: +- if(strstr(txt, "text/plain") != NULL) +- t = NULL; +- break; +- default: +- if(strcasecmp(cmd, "From") == 0) +- start = t; +- else if(strcasecmp(cmd, "Received") == 0) +- start = t; +- continue; +- } +- break; +- } +- if(t && ((fb = fileblobCreate()) != NULL)) { +- cli_dbgmsg("Found a bounce message\n"); +- fileblobSetFilename(fb, mctx->dir, "bounce"); +- fileblobSetCTX(fb, mctx->ctx); +- if(textToFileblob(start, fb, 1) == NULL) { +- cli_dbgmsg("Nothing new to save in the bounce message\n"); +- fileblobDestroy(fb); +- } else +- rc = fileblobScanAndDestroy(fb); +- mctx->files++; +- } else +- cli_dbgmsg("Not found a bounce message\n"); +- +- return rc; ++ for (t = start; t; t = t->t_next) { ++ const char *txt = lineGetData(t->t_line); ++ char cmd[RFC2821LENGTH + 1]; ++ ++ if (txt == NULL) ++ continue; ++ if (cli_strtokbuf(txt, 0, ":", cmd) == NULL) ++ continue; ++ ++ switch (tableFind(mctx->rfc821Table, cmd)) { ++ case CONTENT_TRANSFER_ENCODING: ++ if ((strstr(txt, "7bit") == NULL) && ++ (strstr(txt, "8bit") == NULL)) ++ break; ++ continue; ++ case CONTENT_DISPOSITION: ++ break; ++ case CONTENT_TYPE: ++ if (strstr(txt, "text/plain") != NULL) ++ t = NULL; ++ break; ++ default: ++ if (strcasecmp(cmd, "From") == 0) ++ start = t; ++ else if (strcasecmp(cmd, "Received") == 0) ++ start = t; ++ continue; ++ } ++ break; ++ } ++ if (t && ((fb = fileblobCreate()) != NULL)) { ++ cli_dbgmsg("Found a bounce message\n"); ++ fileblobSetFilename(fb, mctx->dir, "bounce"); ++ fileblobSetCTX(fb, mctx->ctx); ++ if (textToFileblob(start, fb, 1) == NULL) { ++ cli_dbgmsg("Nothing new to save in the bounce message\n"); ++ fileblobDestroy(fb); ++ } else ++ rc = fileblobScanAndDestroy(fb); ++ mctx->files++; ++ } else ++ cli_dbgmsg("Not found a bounce message\n"); ++ ++ return rc; + } + + /* + * Get string representation of mimetype + */ +-static const char *getMimeTypeStr(mime_type mimetype) ++static const char *getMimeTypeStr(mime_type mimetype) + { +- const struct tableinit *entry = mimeTypeStr; +- +- while (entry->key) { +- if (mimetype == entry->value) +- return entry->key; +- entry++; +- } +- return "UNKNOWN"; ++ const struct tableinit *entry = mimeTypeStr; ++ ++ while (entry->key) { ++ if (mimetype == entry->value) ++ return entry->key; ++ entry++; ++ } ++ return "UNKNOWN"; + } + + /* + * Get string representation of encoding type + */ +-static const char *getEncTypeStr(encoding_type enctype) ++static const char *getEncTypeStr(encoding_type enctype) + { +- const struct tableinit *entry = encTypeStr; +- +- while (entry->key) { +- if (enctype == entry->value) +- return entry->key; +- entry++; +- } +- return "UNKNOWN"; ++ const struct tableinit *entry = encTypeStr; ++ ++ while (entry->key) { ++ if (enctype == entry->value) ++ return entry->key; ++ entry++; ++ } ++ return "UNKNOWN"; + } + + /* +@@ -3852,146 +3830,145 @@ static const char *getEncTypeStr(encoding_type enctype) + static message * + do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, mbox_ctx *mctx, message *messageIn, text **tptr, unsigned int recursion_level) + { +- bool addToText = FALSE; +- const char *dtype; +-#ifndef SAVE_TO_DISC +- message *body; ++ bool addToText = FALSE; ++ const char *dtype; ++#ifndef SAVE_TO_DISC ++ message *body; + #endif +- message *aMessage = messages[i]; +- const int doPhishingScan = mctx->ctx->engine->dboptions&CL_DB_PHISHING_URLS && (DCONF_PHISHING&PHISHING_CONF_ENGINE); ++ message *aMessage = messages[i]; ++ const int doPhishingScan = mctx->ctx->engine->dboptions & CL_DB_PHISHING_URLS && (DCONF_PHISHING & PHISHING_CONF_ENGINE); + #if HAVE_JSON +- const char *mtype = NULL; +- json_object *thisobj = NULL, *saveobj = mctx->wrkobj; +- +- if (mctx->wrkobj != NULL) { +- json_object *multiobj = cli_jsonarray(mctx->wrkobj, "Multipart"); +- if (multiobj == NULL) { +- cli_errmsg("Cannot get multipart preclass array\n"); +- *rc = -1; +- return mainMessage; +- } +- +- thisobj = messageGetJObj(aMessage); +- if (thisobj == NULL) { +- cli_dbgmsg("Cannot get message preclass object\n"); +- *rc = -1; +- return mainMessage; +- } +- if (cli_json_addowner(multiobj, thisobj, NULL, -1) != CL_SUCCESS) { +- cli_errmsg("Cannot assign message preclass object to multipart preclass array\n"); +- *rc = -1; +- return mainMessage; +- } +- } ++ const char *mtype = NULL; ++ json_object *thisobj = NULL, *saveobj = mctx->wrkobj; ++ ++ if (mctx->wrkobj != NULL) { ++ json_object *multiobj = cli_jsonarray(mctx->wrkobj, "Multipart"); ++ if (multiobj == NULL) { ++ cli_errmsg("Cannot get multipart preclass array\n"); ++ *rc = -1; ++ return mainMessage; ++ } ++ ++ thisobj = messageGetJObj(aMessage); ++ if (thisobj == NULL) { ++ cli_dbgmsg("Cannot get message preclass object\n"); ++ *rc = -1; ++ return mainMessage; ++ } ++ if (cli_json_addowner(multiobj, thisobj, NULL, -1) != CL_SUCCESS) { ++ cli_errmsg("Cannot assign message preclass object to multipart preclass array\n"); ++ *rc = -1; ++ return mainMessage; ++ } ++ } + #endif + +- if(aMessage == NULL) { ++ if (aMessage == NULL) { + #if HAVE_JSON +- if (thisobj != NULL) +- cli_jsonstr(thisobj, "MimeType", "NULL"); ++ if (thisobj != NULL) ++ cli_jsonstr(thisobj, "MimeType", "NULL"); + #endif +- return mainMessage; +- } ++ return mainMessage; ++ } + +- if(*rc != OK) +- return mainMessage; ++ if (*rc != OK) ++ return mainMessage; + +- cli_dbgmsg("Mixed message part %d is of type %d\n", +- i, messageGetMimeType(aMessage)); ++ cli_dbgmsg("Mixed message part %d is of type %d\n", ++ i, messageGetMimeType(aMessage)); + + #if HAVE_JSON +- if (thisobj != NULL) { +- cli_jsonstr(thisobj, "MimeType", getMimeTypeStr(messageGetMimeType(aMessage))); +- cli_jsonstr(thisobj, "MimeSubtype", messageGetMimeSubtype(aMessage)); +- cli_jsonstr(thisobj, "EncodingType", getEncTypeStr(messageGetEncoding(aMessage))); +- cli_jsonstr(thisobj, "Disposition", messageGetDispositionType(aMessage)); +- cli_jsonstr(thisobj, "Filename", messageHasFilename(aMessage) ? +- messageGetFilename(aMessage): "(inline)"); +- } ++ if (thisobj != NULL) { ++ cli_jsonstr(thisobj, "MimeType", getMimeTypeStr(messageGetMimeType(aMessage))); ++ cli_jsonstr(thisobj, "MimeSubtype", messageGetMimeSubtype(aMessage)); ++ cli_jsonstr(thisobj, "EncodingType", getEncTypeStr(messageGetEncoding(aMessage))); ++ cli_jsonstr(thisobj, "Disposition", messageGetDispositionType(aMessage)); ++ cli_jsonstr(thisobj, "Filename", messageHasFilename(aMessage) ? messageGetFilename(aMessage) : "(inline)"); ++ } + #endif + +- switch(messageGetMimeType(aMessage)) { +- case APPLICATION: +- case AUDIO: +- case IMAGE: +- case VIDEO: +- break; +- case NOMIME: +- cli_dbgmsg("No mime headers found in multipart part %d\n", i); +- if(mainMessage) { +- if(binhexBegin(aMessage)) { +- cli_dbgmsg("Found binhex message in multipart/mixed mainMessage\n"); +- +- if(exportBinhexMessage(mctx, mainMessage)) +- *rc = VIRUS; +- } +- if(mainMessage != messageIn) +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } else if(aMessage) { +- if(binhexBegin(aMessage)) { +- cli_dbgmsg("Found binhex message in multipart/mixed non mime part\n"); +- if(exportBinhexMessage(mctx, aMessage)) +- *rc = VIRUS; +- assert(aMessage == messages[i]); +- messageReset(messages[i]); +- } +- } +- addToText = TRUE; +- if(messageGetBody(aMessage) == NULL) +- /* ++ switch (messageGetMimeType(aMessage)) { ++ case APPLICATION: ++ case AUDIO: ++ case IMAGE: ++ case VIDEO: ++ break; ++ case NOMIME: ++ cli_dbgmsg("No mime headers found in multipart part %d\n", i); ++ if (mainMessage) { ++ if (binhexBegin(aMessage)) { ++ cli_dbgmsg("Found binhex message in multipart/mixed mainMessage\n"); ++ ++ if (exportBinhexMessage(mctx, mainMessage)) ++ *rc = VIRUS; ++ } ++ if (mainMessage != messageIn) ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } else if (aMessage) { ++ if (binhexBegin(aMessage)) { ++ cli_dbgmsg("Found binhex message in multipart/mixed non mime part\n"); ++ if (exportBinhexMessage(mctx, aMessage)) ++ *rc = VIRUS; ++ assert(aMessage == messages[i]); ++ messageReset(messages[i]); ++ } ++ } ++ addToText = TRUE; ++ if (messageGetBody(aMessage) == NULL) ++ /* + * No plain text version + */ +- cli_dbgmsg("No plain text alternative\n"); +- break; +- case TEXT: +- dtype = messageGetDispositionType(aMessage); +- cli_dbgmsg("Mixed message text part disposition \"%s\"\n", +- dtype); +- if(strcasecmp(dtype, "attachment") == 0) +- break; +- if((*dtype == '\0') || (strcasecmp(dtype, "inline") == 0)) { +- const char *cptr; +- +- if(mainMessage && (mainMessage != messageIn)) +- messageDestroy(mainMessage); +- mainMessage = NULL; +- cptr = messageGetMimeSubtype(aMessage); +- cli_dbgmsg("Mime subtype \"%s\"\n", cptr); +- if((tableFind(mctx->subtypeTable, cptr) == PLAIN) && +- (messageGetEncoding(aMessage) == NOENCODING)) { +- /* ++ cli_dbgmsg("No plain text alternative\n"); ++ break; ++ case TEXT: ++ dtype = messageGetDispositionType(aMessage); ++ cli_dbgmsg("Mixed message text part disposition \"%s\"\n", ++ dtype); ++ if (strcasecmp(dtype, "attachment") == 0) ++ break; ++ if ((*dtype == '\0') || (strcasecmp(dtype, "inline") == 0)) { ++ const char *cptr; ++ ++ if (mainMessage && (mainMessage != messageIn)) ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ cptr = messageGetMimeSubtype(aMessage); ++ cli_dbgmsg("Mime subtype \"%s\"\n", cptr); ++ if ((tableFind(mctx->subtypeTable, cptr) == PLAIN) && ++ (messageGetEncoding(aMessage) == NOENCODING)) { ++ /* + * Strictly speaking, a text/plain part + * is not an attachment. We pretend it + * is so that we can decode and scan it + */ +- if(!messageHasFilename(aMessage)) { +- cli_dbgmsg("Adding part to main message\n"); +- addToText = TRUE; +- } else +- cli_dbgmsg("Treating inline as attachment\n"); +- } else { +- const int is_html = (tableFind(mctx->subtypeTable, cptr) == HTML); +- if(doPhishingScan) +- checkURLs(aMessage, mctx, rc, is_html); +- messageAddArgument(aMessage, +- "filename=mixedtextportion"); +- } +- break; +- } +- cli_dbgmsg("Text type %s is not supported\n", dtype); +- return mainMessage; +- case MESSAGE: +- /* Content-Type: message/rfc822 */ +- cli_dbgmsg("Found message inside multipart (encoding type %d)\n", +- messageGetEncoding(aMessage)); +-#ifndef SCAN_UNENCODED_BOUNCES +- switch(messageGetEncoding(aMessage)) { +- case NOENCODING: +- case EIGHTBIT: +- case BINARY: +- if(encodingLine(aMessage) == NULL) { +- /* ++ if (!messageHasFilename(aMessage)) { ++ cli_dbgmsg("Adding part to main message\n"); ++ addToText = TRUE; ++ } else ++ cli_dbgmsg("Treating inline as attachment\n"); ++ } else { ++ const int is_html = (tableFind(mctx->subtypeTable, cptr) == HTML); ++ if (doPhishingScan) ++ checkURLs(aMessage, mctx, rc, is_html); ++ messageAddArgument(aMessage, ++ "filename=mixedtextportion"); ++ } ++ break; ++ } ++ cli_dbgmsg("Text type %s is not supported\n", dtype); ++ return mainMessage; ++ case MESSAGE: ++ /* Content-Type: message/rfc822 */ ++ cli_dbgmsg("Found message inside multipart (encoding type %d)\n", ++ messageGetEncoding(aMessage)); ++#ifndef SCAN_UNENCODED_BOUNCES ++ switch (messageGetEncoding(aMessage)) { ++ case NOENCODING: ++ case EIGHTBIT: ++ case BINARY: ++ if (encodingLine(aMessage) == NULL) { ++ /* + * This means that the message + * has no attachments + * +@@ -4001,39 +3978,39 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + * been set if the message + * itself has been encoded + */ +- cli_dbgmsg("Unencoded multipart/message will not be scanned\n"); +- assert(aMessage == messages[i]); +- messageDestroy(messages[i]); +- messages[i] = NULL; +- return mainMessage; +- } +- /* FALLTHROUGH */ +- default: +- cli_dbgmsg("Encoded multipart/message will be scanned\n"); +- } ++ cli_dbgmsg("Unencoded multipart/message will not be scanned\n"); ++ assert(aMessage == messages[i]); ++ messageDestroy(messages[i]); ++ messages[i] = NULL; ++ return mainMessage; ++ } ++ /* FALLTHROUGH */ ++ default: ++ cli_dbgmsg("Encoded multipart/message will be scanned\n"); ++ } + #endif +-#if 0 ++#if 0 + messageAddStrAtTop(aMessage, + "Received: by clamd (message/rfc822)"); + #endif +-#ifdef SAVE_TO_DISC +- /* ++#ifdef SAVE_TO_DISC ++ /* + * Save this embedded message + * to a temporary file + */ +- if(saveTextPart(mctx, aMessage, 1) == CL_VIRUS) +- *rc = VIRUS; +- assert(aMessage == messages[i]); +- messageDestroy(messages[i]); +- messages[i] = NULL; ++ if (saveTextPart(mctx, aMessage, 1) == CL_VIRUS) ++ *rc = VIRUS; ++ assert(aMessage == messages[i]); ++ messageDestroy(messages[i]); ++ messages[i] = NULL; + #else +- /* ++ /* + * Scan in memory, faster but is open to DoS attacks + * when many nested levels are involved. + */ +- body = parseEmailHeaders(aMessage, mctx->rfc821Table); ++ body = parseEmailHeaders(aMessage, mctx->rfc821Table); + +- /* ++ /* + * We've finished with the + * original copy of the message, + * so throw that away and +@@ -4041,106 +4018,106 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + * message as a message. + * This can save a lot of memory + */ +- assert(aMessage == messages[i]); +- messageDestroy(messages[i]); +- messages[i] = NULL; ++ assert(aMessage == messages[i]); ++ messageDestroy(messages[i]); ++ messages[i] = NULL; + #if HAVE_JSON +- mctx->wrkobj = thisobj; ++ mctx->wrkobj = thisobj; + #endif +- if(body) { +- messageSetCTX(body, mctx->ctx); +- *rc = parseEmailBody(body, NULL, mctx, recursion_level + 1); +- if((*rc == OK) && messageContainsVirus(body)) +- *rc = VIRUS; +- messageDestroy(body); +- } ++ if (body) { ++ messageSetCTX(body, mctx->ctx); ++ *rc = parseEmailBody(body, NULL, mctx, recursion_level + 1); ++ if ((*rc == OK) && messageContainsVirus(body)) ++ *rc = VIRUS; ++ messageDestroy(body); ++ } + #if HAVE_JSON +- mctx->wrkobj = saveobj; ++ mctx->wrkobj = saveobj; + #endif + #endif +- return mainMessage; +- case MULTIPART: +- /* ++ return mainMessage; ++ case MULTIPART: ++ /* + * It's a multi part within a multi part + * Run the message parser on this bit, it won't + * be an attachment + */ +- cli_dbgmsg("Found multipart inside multipart\n"); ++ cli_dbgmsg("Found multipart inside multipart\n"); + #if HAVE_JSON +- mctx->wrkobj = thisobj; ++ mctx->wrkobj = thisobj; + #endif +- if(aMessage) { +- /* ++ if (aMessage) { ++ /* + * The headers were parsed when reading in the + * whole multipart section + */ +- *rc = parseEmailBody(aMessage, *tptr, mctx, recursion_level + 1); +- cli_dbgmsg("Finished recursion, rc = %d\n", (int)*rc); +- assert(aMessage == messages[i]); +- messageDestroy(messages[i]); +- messages[i] = NULL; +- } else { +- *rc = parseEmailBody(NULL, NULL, mctx, recursion_level + 1); +- if(mainMessage && (mainMessage != messageIn)) +- messageDestroy(mainMessage); +- mainMessage = NULL; +- } ++ *rc = parseEmailBody(aMessage, *tptr, mctx, recursion_level + 1); ++ cli_dbgmsg("Finished recursion, rc = %d\n", (int)*rc); ++ assert(aMessage == messages[i]); ++ messageDestroy(messages[i]); ++ messages[i] = NULL; ++ } else { ++ *rc = parseEmailBody(NULL, NULL, mctx, recursion_level + 1); ++ if (mainMessage && (mainMessage != messageIn)) ++ messageDestroy(mainMessage); ++ mainMessage = NULL; ++ } + #if HAVE_JSON +- mctx->wrkobj = saveobj; ++ mctx->wrkobj = saveobj; + #endif +- return mainMessage; +- default: +- cli_dbgmsg("Only text and application attachments are fully supported, type = %d\n", +- messageGetMimeType(aMessage)); +- /* fall through - we may be able to salvage something */ +- } +- +- if(*rc != VIRUS) { +- fileblob *fb = messageToFileblob(aMessage, mctx->dir, 1); ++ return mainMessage; ++ default: ++ cli_dbgmsg("Only text and application attachments are fully supported, type = %d\n", ++ messageGetMimeType(aMessage)); ++ /* fall through - we may be able to salvage something */ ++ } ++ ++ if (*rc != VIRUS) { ++ fileblob *fb = messageToFileblob(aMessage, mctx->dir, 1); + #if HAVE_JSON +- json_object *arrobj; +- int arrlen = 0; ++ json_object *arrobj; ++ int arrlen = 0; + +- if (thisobj != NULL) { +- /* attempt to determine container size - prevents incorrect type reporting */ +- if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) +- arrlen = json_object_array_length(arrobj); +- } ++ if (thisobj != NULL) { ++ /* attempt to determine container size - prevents incorrect type reporting */ ++ if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) ++ arrlen = json_object_array_length(arrobj); ++ } + + #endif +- if(fb) { +- /* aMessage doesn't always have a ctx set */ +- fileblobSetCTX(fb, mctx->ctx); +- if(fileblobScanAndDestroy(fb) == CL_VIRUS) +- *rc = VIRUS; +- if (!addToText) +- mctx->files++; +- } ++ if (fb) { ++ /* aMessage doesn't always have a ctx set */ ++ fileblobSetCTX(fb, mctx->ctx); ++ if (fileblobScanAndDestroy(fb) == CL_VIRUS) ++ *rc = VIRUS; ++ if (!addToText) ++ mctx->files++; ++ } + #if HAVE_JSON +- if (thisobj != NULL) { +- json_object *entry = NULL; +- const char *dtype = NULL; +- +- /* attempt to acquire container type */ +- if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) +- if (json_object_array_length(arrobj) > arrlen) +- entry = json_object_array_get_idx(arrobj, arrlen); +- if (entry) { +- json_object_object_get_ex(entry, "FileType", &entry); +- if (entry) +- dtype = json_object_get_string(entry); +- } +- cli_jsonint(thisobj, "ContainedObjectsIndex", arrlen); +- cli_jsonstr(thisobj, "ClamAVFileType", dtype ? dtype : "UNKNOWN"); +- } ++ if (thisobj != NULL) { ++ json_object *entry = NULL; ++ const char *dtype = NULL; ++ ++ /* attempt to acquire container type */ ++ if (json_object_object_get_ex(mctx->ctx->wrkproperty, "ContainedObjects", &arrobj)) ++ if (json_object_array_length(arrobj) > arrlen) ++ entry = json_object_array_get_idx(arrobj, arrlen); ++ if (entry) { ++ json_object_object_get_ex(entry, "FileType", &entry); ++ if (entry) ++ dtype = json_object_get_string(entry); ++ } ++ cli_jsonint(thisobj, "ContainedObjectsIndex", arrlen); ++ cli_jsonstr(thisobj, "ClamAVFileType", dtype ? dtype : "UNKNOWN"); ++ } + #endif +- if(messageContainsVirus(aMessage)) +- *rc = VIRUS; +- } +- messageDestroy(aMessage); +- messages[i] = NULL; ++ if (messageContainsVirus(aMessage)) ++ *rc = VIRUS; ++ } ++ messageDestroy(aMessage); ++ messages[i] = NULL; + +- return mainMessage; ++ return mainMessage; + } + + /* +@@ -4149,13 +4126,13 @@ do_multipart(message *mainMessage, message **messages, int i, mbox_status *rc, m + static int + count_quotes(const char *buf) + { +- int quotes = 0; ++ int quotes = 0; + +- while(*buf) +- if(*buf++ == '\"') +- quotes++; ++ while (*buf) ++ if (*buf++ == '\"') ++ quotes++; + +- return quotes; ++ return quotes; + } + + /* +@@ -4164,33 +4141,33 @@ count_quotes(const char *buf) + static bool + next_is_folded_header(const text *t) + { +- const text *next = t->t_next; +- const char *data, *ptr; ++ const text *next = t->t_next; ++ const char *data, *ptr; + +- if(next == NULL) +- return FALSE; ++ if (next == NULL) ++ return FALSE; + +- if(next->t_line == NULL) +- return FALSE; ++ if (next->t_line == NULL) ++ return FALSE; + +- data = lineGetData(next->t_line); ++ data = lineGetData(next->t_line); + +- /* ++ /* + * Section B.2 of RFC822 says TAB or SPACE means a continuation of the + * previous entry. + */ +- if(isblank(data[0])) +- return TRUE; ++ if (isblank(data[0])) ++ return TRUE; + +- if(strchr(data, '=') == NULL) +- /* ++ if (strchr(data, '=') == NULL) ++ /* + * Avoid false positives with + * Content-Type: text/html; + * Content-Transfer-Encoding: quoted-printable + */ +- return FALSE; ++ return FALSE; + +- /* ++ /* + * Some are broken and don't fold headers lines + * correctly as per section 2.2.3 of RFC2822. + * Generally they miss the white space at +@@ -4205,23 +4182,23 @@ next_is_folded_header(const text *t) + * Since we're a virus checker not an RFC + * verifier we need to handle these + */ +- data = lineGetData(t->t_line); +- +- ptr = strchr(data, '\0'); +- +- while(--ptr > data) +- switch(*ptr) { +- case ';': +- return TRUE; +- case '\n': +- case ' ': +- case '\r': +- case '\t': +- continue; /* white space at end of line */ +- default: +- return FALSE; +- } +- return FALSE; ++ data = lineGetData(t->t_line); ++ ++ ptr = strchr(data, '\0'); ++ ++ while (--ptr > data) ++ switch (*ptr) { ++ case ';': ++ return TRUE; ++ case '\n': ++ case ' ': ++ case '\r': ++ case '\t': ++ continue; /* white space at end of line */ ++ default: ++ return FALSE; ++ } ++ return FALSE; + } + + /* +@@ -4232,12 +4209,12 @@ next_is_folded_header(const text *t) + static bool + newline_in_header(const char *line) + { +- cli_dbgmsg("newline_in_header, check \"%s\"\n", line); ++ cli_dbgmsg("newline_in_header, check \"%s\"\n", line); + +- if(strncmp(line, "Message-Id: ", 12) == 0) +- return TRUE; +- if(strncmp(line, "Date: ", 6) == 0) +- return TRUE; ++ if (strncmp(line, "Message-Id: ", 12) == 0) ++ return TRUE; ++ if (strncmp(line, "Date: ", 6) == 0) ++ return TRUE; + +- return FALSE; ++ return FALSE; + } diff --git a/clamav.spec b/clamav.spec index b01e442abfe4d6abf5d8bf40f8c952e02d5b010a..f21e245b0364534a78c162a245745eca8d1c7cc0 100644 --- a/clamav.spec +++ b/clamav.spec @@ -1,10 +1,10 @@ Name: clamav Summary: End-user tools for the Clam Antivirus scanner Version: 0.101.4 -Release: 7 -License: GPLv2 +Release: 8 +License: GPLv2 and Public Domain and bzip2-1.0.6 and Zlib and Apache-2.0 URL: https://www.clamav.net/ -Source0: https://www.clamav.net/downloads/production/clamav-%version.tar.gz +Source0: https://www.clamav.net/downloads/production/clamav-%{version}.tar.gz Source1: clamd.sysconfig Source2: clamd.logrotate Source3: main-58.cvd @@ -26,6 +26,10 @@ Patch0003: clamav-0.99-private.patch Patch0004: clamav-0.100.0-umask.patch Patch0005: llvm-glibc.patch Patch0006: clamav-Fix-int64-overflow-check.patch +Patch0007: CVE-2019-15961-pre-1.patch +Patch0008: CVE-2019-15961-pre-2.patch +Patch0009: CVE-2019-15961-1.patch +Patch0010: CVE-2019-15961-2.patch BuildRequires: autoconf automake gettext-devel libtool libtool-ltdl-devel BuildRequires: gcc-c++ zlib-devel bzip2-devel gmp-devel curl-devel json-c-devel @@ -405,6 +409,9 @@ test -e %_var/log/clamav-milter.log || { %changelog +* Thu Feb 18 2021 zhanghua - 0.101.4-8 +- fix CVE-2019-15961 + * Fri Oct 09 2020 lingsheng - 0.101.4-7 - Fix int64 overflow check