X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/3346ab0144239462a8690e011bff1df8fd504076..b322aac87b2d3e5a1cc4f3ebdee2fd81651bdd7c:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index 6e8b3f36d..864564ffc 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -1,5 +1,3 @@ -/* $Cambridge: exim/src/src/malware.c,v 1.21 2010/06/07 00:12:42 pdp Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ @@ -71,17 +69,22 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) int malware(uschar **listptr) { uschar scan_filename[1024]; BOOL fits; + int ret; fits = string_format(scan_filename, sizeof(scan_filename), CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); if (!fits) { + av_failed = TRUE; log_write(0, LOG_MAIN|LOG_PANIC, "malware filename does not fit in buffer [malware()]"); return DEFER; } - return malware_internal(listptr, scan_filename, FALSE); + ret = malware_internal(listptr, scan_filename, FALSE); + if (ret == DEFER) av_failed = TRUE; + + return ret; } @@ -592,7 +595,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) } } else { - char *drweb_s = NULL; + const char *drweb_s = NULL; if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; @@ -1298,7 +1301,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; @@ -1338,6 +1341,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 +1627,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 +1653,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 */ + /* ----------------------------------------------------------------------- */ @@ -1860,14 +1913,14 @@ static int mksd_parse_line (char *line) static int mksd_scan_packed(int sock, uschar *scan_filename) { struct iovec iov[3]; - char *cmd = "MSQ\n"; + const char *cmd = "MSQ\n"; uschar av_buffer[1024]; - iov[0].iov_base = cmd; + iov[0].iov_base = (void *) cmd; iov[0].iov_len = 3; iov[1].iov_base = CS scan_filename; iov[1].iov_len = Ustrlen(scan_filename); - iov[2].iov_base = cmd + 3; + iov[2].iov_base = (void *) (cmd + 3); iov[2].iov_len = 1; if (mksd_writev (sock, iov, 3) < 0)