X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/dbc4b90d7238e249a9406cc3770ca4445b87864f..1e83d68b72d24d6255d2e78facbe01656515ab4f:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index 61b0c2994..eb0c33cea 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/malware.c,v 1.20 2010/06/06 22:46:34 pdp Exp $ */ +/* $Cambridge: exim/src/src/malware.c,v 1.21 2010/06/07 00:12:42 pdp Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -106,23 +106,23 @@ malware_in_file(uschar *eml_filename) { uschar message_id_buf[64]; int ret; - scan_options[0] = "*"; + scan_options[0] = US"*"; scan_options[1] = NULL; /* spool_mbox() assumes various parameters exist, when creating the relevant directory and the email within */ (void) string_format(message_id_buf, sizeof(message_id_buf), - US"dummy-%d", pseudo_random_number(INT_MAX)); + "dummy-%d", pseudo_random_number(INT_MAX)); message_id = message_id_buf; - sender_address = "malware-sender@example.net"; - return_path = ""; + sender_address = US"malware-sender@example.net"; + return_path = US""; recipients_list = NULL; - receive_add_recipient("malware-victim@example.net", -1); + receive_add_recipient(US"malware-victim@example.net", -1); enable_dollar_recipients = TRUE; ret = malware_internal(scan_options, eml_filename, TRUE); - strncpy(spooled_message_id, message_id, sizeof(spooled_message_id)); + Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id)); spool_mbox_ok = 1; /* don't set no_mbox_unspool; at present, there's no way for it to become set, but if that changes, then it should apply to these tests too */ @@ -891,7 +891,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) log_write(0, LOG_MAIN|LOG_PANIC, "malware filename does not fit in buffer [malware_internal() kavdaemon]"); } - p = strrchr(scanrequest, '/'); + p = Ustrrchr(scanrequest, '/'); if (p) *p = '\0'; @@ -1098,7 +1098,8 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) "malware filename does not fit in buffer [malware_internal() cmdline]"); return DEFER; } - p = strrchr(eml_filename, '/'); + Ustrcpy(file_name, eml_filename); + p = Ustrrchr(file_name, '/'); if (p) *p = '\0'; fits = string_format(commandline, sizeof(commandline), CS cmdline_scanner, file_name); @@ -1199,7 +1200,6 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) struct sockaddr_un server; int sock, len; uschar *p; - BOOL fits; uschar file_name[1024]; uschar av_buffer[1024]; @@ -1236,7 +1236,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) return DEFER; } memcpy(file_name, eml_filename, len); - p = strrchr(file_name, '/'); + p = Ustrrchr(file_name, '/'); if (p) *p = '\0'; @@ -1298,7 +1298,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) uschar *clamd_options; uschar clamd_options_buffer[1024]; uschar clamd_options_default[] = "/tmp/clamd"; - uschar *p,*vname; + uschar *p, *vname, *result_tag, *response_end; struct sockaddr_un server; int sock,bread=0; unsigned int port; @@ -1311,12 +1311,12 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) uschar clamd_options2_buffer[1024]; uschar clamd_options2_default[] = ""; uschar *clamav_fbuf; - uschar scanrequest[1024]; - int sockData, clam_fd, result; + int clam_fd, result; unsigned int fsize; BOOL use_scan_command, fits; #ifdef WITH_OLD_CLAMAV_STREAM uschar av_buffer2[1024]; + int sockData; #else uint32_t send_size, send_final_zeroblock; #endif @@ -1338,6 +1338,15 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) else use_scan_command = FALSE; + /* See the discussion of response formats below to see why we really don't + like colons in filenames when passing filenames to ClamAV. */ + if (use_scan_command && Ustrchr(eml_filename, ':')) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: local/SCAN mode incompatible with" \ + " : in path to email filename [%s]", eml_filename); + return DEFER; + } + /* socket does not start with '/' -> network socket */ if (*clamd_options != '/') { @@ -1615,10 +1624,25 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) return DEFER; } - /* Check the result. ClamAV Returns - infected: -> ": FOUND" - not-infected: -> ": OK" - error: -> ": ERROR */ + /* Check the result. ClamAV returns one of two result formats. + In the basic mode, the response is of the form: + infected: -> ": FOUND" + not-infected: -> ": OK" + error: -> ": ERROR + If the ExtendedDetectionInfo option has been turned on, then we get: + ": (:) FOUND" + for the infected case. Compare: +/tmp/eicar.com: Eicar-Test-Signature FOUND +/tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND + + In the streaming case, clamd uses the filename "stream" which you should + be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The + client app will replace "stream" with the original filename before returning + results to stdout, but the trace shows the data). + + We will assume that the pathname passed to clamd from Exim does not contain + a colon. We will have whined loudly above if the eml_filename does (and we're + passing a filename to clamd). */ if (!(*av_buffer)) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -1626,50 +1650,76 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) return DEFER; } - /* strip newline at the end (won't be present for zINSTREAM) */ + /* strip newline at the end (won't be present for zINSTREAM) + (also any trailing whitespace, which shouldn't exist, but we depend upon + this below, so double-check) */ p = av_buffer + Ustrlen(av_buffer) - 1; - if( *p == '\n' ) *p = '\0'; + if (*p == '\n') *p = '\0'; DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); + while (isspace(*--p) && (p > av_buffer)) + *p = '\0'; + if (*p) ++p; + response_end = p; + /* colon in returned output? */ - if((p = Ustrrchr(av_buffer,':')) == NULL) { + if((p = Ustrchr(av_buffer,':')) == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned malformed result: %s", + "malware acl condition: clamd: ClamAV returned malformed result (missing colon): %s", av_buffer); return DEFER; } /* strip filename */ - ++p; - while (*p == ' ') ++p; + while (*p && isspace(*++p)) /**/; vname = p; - if ((p = Ustrstr(vname, "FOUND"))!=NULL) { - *p=0; - for (--p;p>vname && *p<=32;p--) *p=0; - for (;*vname==32;vname++); - Ustrcpy(malware_name_buffer,vname); - malware_name = malware_name_buffer; - DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); - } - else { - if (Ustrstr(vname, "ERROR")!=NULL) { - /* ClamAV reports ERROR - Find line start */ - for (;*vname!='\n' && vname>av_buffer; vname--); - if (*vname=='\n') vname++; - - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned %s",vname); - return DEFER; - } - else { - /* Everything should be OK */ - malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware not found\n"); - } + + /* It would be bad to encounter a virus with "FOUND" in part of the name, + but we should at least be resistant to it. */ + p = Ustrrchr(vname, ' '); + if (p) + result_tag = p + 1; + else + result_tag = vname; + + if (Ustrcmp(result_tag, "FOUND") == 0) { + /* p should still be the whitespace before the result_tag */ + while (isspace(*p)) --p; + *++p = '\0'; + /* Strip off the extended information too, which will be in parens + after the virus name, with no intervening whitespace. */ + if (*--p == ')') { + /* "(hash:size)", so previous '(' will do; if not found, we have + a curious virus name, but not an error. */ + p = Ustrrchr(vname, '('); + if (p) + *p = '\0'; + } + Ustrncpy(malware_name_buffer, vname, sizeof(malware_name_buffer)-1); + malware_name = malware_name_buffer; + DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); + + } else if (Ustrcmp(result_tag, "ERROR") == 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned: %s", + av_buffer); + return DEFER; + + } else if (Ustrcmp(result_tag, "OK") == 0) { + /* Everything should be OK */ + malware_name = NULL; + DEBUG(D_acl) debug_printf("Malware not found\n"); + + } else { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unparseable response from ClamAV: {%s}", + av_buffer); + return DEFER; } - } + + } /* clamd */ + /* ----------------------------------------------------------------------- */