X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/0f0c8159c43045f4ad847a0129dca7eddd313285..0d68983b8b7c2040e81090f1d5aa8d2c4043eab3:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index 659357633..b626b18a8 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -2,8 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) Tom Kistner 2003-2014 */ -/* License: GPL */ +/* Copyright (c) Tom Kistner 2003 - 2015 + * License: GPL + * Copyright (c) The Exim Maintainers 2016 + */ /* Code for calling virus (malware) scanners. Called from acl.c. */ @@ -11,7 +13,7 @@ #ifdef WITH_CONTENT_SCAN typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL, - M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST} scanner_t; + M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST, M_FPROT6D} scanner_t; typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t; static struct scan { @@ -32,20 +34,19 @@ static struct scan { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM }, { M_MKSD, US"mksd", NULL, MC_NONE }, { M_AVAST, US"avast", US"/var/run/avast/scan.sock", MC_STRM }, + { M_FPROT6D, US"f-prot6d", US"localhost 10200", MC_TCP }, { -1, NULL, NULL, MC_NONE } /* end-marker */ }; /* The maximum number of clamd servers that are supported in the configuration */ #define MAX_CLAMD_SERVERS 32 #define MAX_CLAMD_SERVERS_S "32" -/* Maximum length of the hostname that can be specified in the clamd address list */ -#define MAX_CLAMD_ADDRESS_LENGTH 64 -#define MAX_CLAMD_ADDRESS_LENGTH_S "64" -typedef struct clamd_address_container { - uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH+1]; - unsigned int tcp_port; -} clamd_address_container; +typedef struct clamd_address { + uschar * hostspec; + unsigned tcp_port; + unsigned retry; +} clamd_address; #ifndef nelements # define nelements(arr) (sizeof(arr) / sizeof(arr[0])) @@ -64,6 +65,35 @@ typedef struct clamd_address_container { #define DERR_TIMEOUT (1<<9) /* scan timeout has run out */ #define DERR_BAD_CALL (1<<15) /* wrong command */ + +static const uschar * malware_regex_default = US ".+"; +static const pcre * malware_default_re = NULL; + +static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$"; +static const pcre * drweb_re = NULL; + +static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$"; +static const pcre * fsec_re = NULL; + +static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$"; +static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$"; +static const pcre * kav_re_sus = NULL; +static const pcre * kav_re_inf = NULL; + +static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]"; +static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)"; +static const pcre * ava_re_clean = NULL; +static const pcre * ava_re_virus = NULL; + +static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$"; +static const uschar * fprot6d_re_virus_str = US "^\\d+\\s\\s+.+$"; +static const pcre * fprot6d_re_error = NULL; +static const pcre * fprot6d_re_virus = NULL; + + + +/******************************************************************************/ + /* Routine to check whether a system is big- or little-endian. Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html Needed for proper kavdaemon implementation. Sigh. */ @@ -74,7 +104,7 @@ static inline int test_byte_order() { short int word = 0x0001; - char *byte = (char *) &word; + char *byte = CS &word; return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); } @@ -84,28 +114,30 @@ BOOL malware_ok = FALSE; the scan directory normally for that case, but look into rigging up the needed header variables if not already set on the command-line? */ extern int spool_mbox_ok; -extern uschar spooled_message_id[17]; +extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1]; static inline int malware_errlog_defer(const uschar * str) { - log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str); - return DEFER; +log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str); +return DEFER; } static int -m_errlog_defer(struct scan * scanent, const uschar * str) +m_errlog_defer(struct scan * scanent, const uschar * hostport, + const uschar * str) { - return malware_errlog_defer(string_sprintf("%s: %s", scanent->name, str)); +return malware_errlog_defer(string_sprintf("%s %s : %s", + scanent->name, hostport ? hostport : CUS"", str)); } static int -m_errlog_defer_3(struct scan * scanent, const uschar * str, - int fd_to_close) +m_errlog_defer_3(struct scan * scanent, const uschar * hostport, + const uschar * str, int fd_to_close) { - (void) close(fd_to_close); - return m_errlog_defer(scanent, str); +(void) close(fd_to_close); +return m_errlog_defer(scanent, hostport, str); } /*************************************************/ @@ -117,111 +149,65 @@ static inline int m_tcpsocket(const uschar * hostname, unsigned int port, host_item * host, uschar ** errstr) { - return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr); -} - -static int -m_tcpsocket_fromdef(const uschar * hostport, uschar ** errstr) -{ - int scan; - uschar hostname[256]; - unsigned int portlow, porthigh; - - /* extract host and port part */ - scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh); - if ( scan != 3 ) { - if ( scan != 2 ) { - *errstr = string_sprintf("invalid socket '%s'", hostport); - return -1; - } - porthigh = portlow; - } - - return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh, - 5, NULL, errstr); -} - -static int -m_unixsocket(const uschar * path, uschar ** errstr) -{ - int sock; - struct sockaddr_un server; - - if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - *errstr = US"can't open UNIX socket."; - return -1; - } - - server.sun_family = AF_UNIX; - Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1); - server.sun_path[sizeof(server.sun_path)-1] = '\0'; - if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) { - int err = errno; - (void)close(sock); - *errstr = string_sprintf("unable to connect to UNIX socket (%s): %s", - path, strerror(err)); - return -1; - } - return sock; -} - -static inline int -m_streamsocket(const uschar * spec, uschar ** errstr) -{ - return *spec == '/' - ? m_unixsocket(spec, errstr) : m_tcpsocket_fromdef(spec, errstr); +return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr); } static int m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr) { - if (send(sock, buf, cnt, 0) < 0) { - int err = errno; - (void)close(sock); - *errstr = string_sprintf("unable to send to socket (%s): %s", - buf, strerror(err)); - return -1; - } - return sock; +if (send(sock, buf, cnt, 0) < 0) + { + int err = errno; + (void)close(sock); + *errstr = string_sprintf("unable to send to socket (%s): %s", + buf, strerror(err)); + return -1; + } +return sock; } static const pcre * m_pcre_compile(const uschar * re, uschar ** errstr) { - const uschar * rerror; - int roffset; - const pcre * cre; - - cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (!cre) - *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", - re, rerror, roffset); - return cre; +const uschar * rerror; +int roffset; +const pcre * cre; + +cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL); +if (!cre) + *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", + re, rerror, roffset); +return cre; } uschar * m_pcre_exec(const pcre * cre, uschar * text) { - int ovector[10*3]; - int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0, - ovector, nelements(ovector)); - uschar * substr = NULL; - if (i >= 2) /* Got it */ - pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); - return substr; +int ovector[10*3]; +int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0, + ovector, nelements(ovector)); +uschar * substr = NULL; +if (i >= 2) /* Got it */ + pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); +return substr; } static const pcre * -m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr) +m_pcre_nextinlist(const uschar ** list, int * sep, + char * listerr, uschar ** errstr) { - const uschar * list_ele; - const pcre * cre = NULL; +const uschar * list_ele; +const pcre * cre = NULL; - if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) - *errstr = US listerr; - else - cre = m_pcre_compile(CUS list_ele, errstr); - return cre; +if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) + *errstr = US listerr; +else + { + DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ", + string_printing(list_ele)); + cre = m_pcre_compile(CUS list_ele, errstr); + } +return cre; } /* @@ -255,13 +241,13 @@ while ((rcv = read(fd, p, 1)) > 0) } if (!ok) { - DEBUG(D_acl) debug_printf("Malware scan: read %s (%s)\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: read %s (%s)\n", rcv==0 ? "EOF" : "error", strerror(errno)); return rcv==0 ? -1 : -2; } *p = '\0'; -DEBUG(D_acl) debug_printf("Malware scan: read '%s'\n", buffer); +DEBUG(D_acl) debug_printf_indent("Malware scan: read '%s'\n", buffer); return p - buffer; } @@ -355,7 +341,7 @@ switch (*line) case 'A': /* ERR */ if ((p = strchr (line, '\n')) != NULL) *p = '\0'; - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, string_sprintf("scanner failed: %s", line)); default: /* VIR */ @@ -373,7 +359,7 @@ switch (*line) return OK; } } - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, string_sprintf("malformed reply received: %s", line)); } } @@ -402,6 +388,26 @@ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0) return mksd_parse_line (scanent, CS av_buffer); } + +static int +clamd_option(clamd_address * cd, const uschar * optstr, int * subsep) +{ +uschar * s; + +cd->retry = 0; +while ((s = string_nextinlist(&optstr, subsep, NULL, 0))) + if (Ustrncmp(s, "retry=", 6) == 0) + { + int sec = readconf_readtime((s += 6), '\0', FALSE); + if (sec < 0) + return FAIL; + cd->retry = sec; + } + else + return FAIL; +return OK; +} + /************************************************* * Scan content for malware * *************************************************/ @@ -411,21 +417,20 @@ is via malware(), or there's malware_in_file() used for testing/debugging. Arguments: malware_re match condition for "malware=" - eml_filename the file holding the email to be scanned + scan_filename the file holding the email to be scanned, if we're faking + this up for the -bmalware test, else NULL timeout if nonzero, non-default timeoutl - faking whether or not we're faking this up for the -bmalware test Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ static int -malware_internal(const uschar * malware_re, const uschar * eml_filename, - int timeout, BOOL faking) +malware_internal(const uschar * malware_re, const uschar * scan_filename, + int timeout) { int sep = 0; -uschar *av_scanner_work = av_scanner; +const uschar *av_scanner_work = av_scanner; uschar *scanner_name; -uschar malware_regex_default[] = ".+"; unsigned long mbox_size; FILE *mbox_file; const pcre *re; @@ -434,34 +439,41 @@ struct scan * scanent; const uschar * scanner_options; int sock = -1; time_t tmo; +uschar * eml_filename, * eml_dir; + +if (!malware_re) + return FAIL; /* empty means "don't match anything" */ + +/* Ensure the eml mbox file is spooled up */ -/* make sure the eml mbox file is spooled up */ -if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL))) +if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename))) return malware_errlog_defer(US"error while creating mbox spool file"); -/* none of our current scanners need the mbox - file as a stream, so we can close it right away */ -(void)fclose(mbox_file); +/* None of our current scanners need the mbox file as a stream (they use +the name), so we can close it right away. Get the directory too. */ -if (!malware_re) - return FAIL; /* empty means "don't match anything" */ +(void) fclose(mbox_file); +eml_dir = string_copyn(eml_filename, Ustrrchr(eml_filename, '/') - eml_filename); /* parse 1st option */ - if ( (strcmpic(malware_re, US"false") == 0) || - (Ustrcmp(malware_re,"0") == 0) ) +if (strcmpic(malware_re, US"false") == 0 || Ustrcmp(malware_re,"0") == 0) return FAIL; /* explicitly no matching */ /* special cases (match anything except empty) */ -if ( (strcmpic(malware_re,US"true") == 0) || - (Ustrcmp(malware_re,"*") == 0) || - (Ustrcmp(malware_re,"1") == 0) ) +if ( strcmpic(malware_re,US"true") == 0 + || Ustrcmp(malware_re,"*") == 0 + || Ustrcmp(malware_re,"1") == 0 + ) + { + if ( !malware_default_re + && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr))) + return malware_errlog_defer(errstr); malware_re = malware_regex_default; - -/* Reset sep that is set by previous string_nextinlist() call */ -sep = 0; + re = malware_default_re; + } /* compile the regex, see if it works */ -if (!(re = m_pcre_compile(malware_re, &errstr))) +else if (!(re = m_pcre_compile(malware_re, &errstr))) return malware_errlog_defer(errstr); /* if av_scanner starts with a dollar, expand it first */ @@ -473,7 +485,7 @@ if (*av_scanner == '$') expand_string_message)); DEBUG(D_acl) - debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); + debug_printf_indent("Expanded av_scanner global: %s\n", av_scanner_work); /* disable result caching in this case */ malware_name = NULL; malware_ok = FALSE; @@ -488,28 +500,33 @@ if (!malware_ok) if (!timeout) timeout = MALWARE_TIMEOUT; tmo = time(NULL) + timeout; - for (scanent = m_scans; ; scanent++) { + for (scanent = m_scans; ; scanent++) + { if (!scanent->name) return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", scanner_name)); if (strcmpic(scanner_name, US scanent->name) != 0) continue; + DEBUG(D_acl) debug_printf_indent("Malware scan: %s tmo=%s\n", + scanner_name, readconf_printtime(timeout)); + if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) scanner_options = scanent->options_default; if (scanent->conn == MC_NONE) break; + + DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options); switch(scanent->conn) { - case MC_TCP: sock = m_tcpsocket_fromdef(scanner_options, &errstr); break; - case MC_UNIX: sock = m_unixsocket(scanner_options, &errstr); break; - case MC_STRM: sock = m_streamsocket(scanner_options, &errstr); break; + case MC_TCP: sock = ip_tcpsocket(scanner_options, &errstr, 5); break; + case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr); break; + case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5); break; default: /* compiler quietening */ break; } if (sock < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); break; } - DEBUG(D_acl) debug_printf("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout)); switch (scanent->scancode) { @@ -532,12 +549,12 @@ if (!malware_ok) par_count++; } scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest); - DEBUG(D_acl) debug_printf("Malware scan: issuing %s: %s\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n", scanner_name, scanrequest); /* send scan request */ if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0) if (len > 0) @@ -577,7 +594,6 @@ if (!malware_ok) uschar * tmpbuf, *drweb_fbuf; int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, drweb_vnum, drweb_slen, drweb_fin = 0x0000; - const pcre *drweb_re; /* prepare variables */ drweb_cmd = htonl(DRWEBD_SCAN_CMD); @@ -587,16 +603,17 @@ if (!malware_ok) { /* calc file size */ if ((drweb_fd = open(CCS eml_filename, O_RDONLY)) == -1) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("can't open spool file %s: %s", eml_filename, strerror(errno)), sock); if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1) { - int err = errno; + int err; +badseek: err = errno; (void)close(drweb_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("can't seek spool file %s: %s", eml_filename, strerror(err)), sock); @@ -605,15 +622,16 @@ if (!malware_ok) if ((off_t)fsize_uint != fsize) { (void)close(drweb_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("seeking spool file %s, size overflow", eml_filename), sock); } drweb_slen = htonl(fsize); - lseek(drweb_fd, 0, SEEK_SET); + if (lseek(drweb_fd, 0, SEEK_SET) < 0) + goto badseek; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s]\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s remote scan [%s]\n", scanner_name, scanner_options); /* send scan request */ @@ -623,15 +641,15 @@ if (!malware_ok) (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { (void)close(drweb_fd); - return m_errlog_defer_3(scanent, string_sprintf( + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf( "unable to send commands to socket (%s)", scanner_options), sock); } - if (!(drweb_fbuf = (uschar *) malloc (fsize_uint))) + if (!(drweb_fbuf = US malloc(fsize_uint))) { (void)close(drweb_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("unable to allocate memory %u for file (%s)", fsize_uint, eml_filename), sock); @@ -642,7 +660,7 @@ if (!malware_ok) int err = errno; (void)close(drweb_fd); free(drweb_fbuf); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("can't read spool file %s: %s", eml_filename, strerror(err)), sock); @@ -653,17 +671,16 @@ if (!malware_ok) if (send(sock, drweb_fbuf, fsize, 0) < 0) { free(drweb_fbuf); - return m_errlog_defer_3(scanent, string_sprintf( + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf( "unable to send file body to socket (%s)", scanner_options), sock); } - (void)close(drweb_fd); } else { drweb_slen = htonl(Ustrlen(eml_filename)); - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s local scan [%s]\n", scanner_name, scanner_options); /* send scan request */ @@ -672,19 +689,19 @@ if (!malware_ok) (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) || (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) || (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) - return m_errlog_defer_3(scanent, string_sprintf( + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf( "unable to send commands to socket (%s)", scanner_options), sock); } /* wait for result */ if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo)) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"unable to read return code", sock); drweb_rc = ntohl(drweb_rc); if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo)) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"unable to read the number of viruses", sock); drweb_vnum = ntohl(drweb_vnum); @@ -697,7 +714,8 @@ if (!malware_ok) malware_name = US"unknown"; /* set up match regex */ - drweb_re = m_pcre_compile(US"infected\\swith\\s*(.+?)$", &errstr); + if (!drweb_re) + drweb_re = m_pcre_compile(drweb_re_str, &errstr); /* read and concatenate virus names into one string */ for (i = 0; i < drweb_vnum; i++) @@ -705,14 +723,14 @@ if (!malware_ok) int size = 0, off = 0, ovector[10*3]; /* read the size of report */ if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo)) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"cannot read report size", sock); drweb_slen = ntohl(drweb_slen); tmpbuf = store_get(drweb_slen); /* read report body */ if (!recv_len(sock, tmpbuf, drweb_slen, tmo)) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"cannot read report string", sock); tmpbuf[drweb_slen] = '\0'; @@ -750,7 +768,7 @@ if (!malware_ok) * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR * and others are ignored */ if (drweb_s) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s), sock); @@ -770,9 +788,9 @@ if (!malware_ok) recv_line(sock, buf, sizeof(buf), tmo); if (buf[0] != '2') /* aveserver is having problems */ - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unavailable (Responded: %s).", - ((buf[0] != 0) ? buf : (uschar *)"nothing") ), + ((buf[0] != 0) ? buf : US "nothing") ), sock); /* prepare our command */ @@ -780,10 +798,10 @@ if (!malware_ok) eml_filename); /* and send it */ - DEBUG(D_acl) debug_printf("Malware scan: issuing %s %s\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n", scanner_name, buf); if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); malware_name = NULL; result = 0; @@ -794,7 +812,7 @@ if (!malware_ok) break; if (buf[0] == '5') /* aveserver is having problems */ { - result = m_errlog_defer(scanent, + result = m_errlog_defer(scanent, CUS callout_address, string_sprintf("unable to scan file %s (Responded: %s).", eml_filename, buf)); break; @@ -808,16 +826,16 @@ if (!malware_ok) } if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); /* read aveserver's greeting and see if it is ready (2xx greeting) */ buf[0] = 0; recv_line(sock, buf, sizeof(buf), tmo); if (buf[0] != '2') /* aveserver is having problems */ - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to quit dialogue (Responded: %s).", - ((buf[0] != 0) ? buf : (uschar *)"nothing") ), + ((buf[0] != 0) ? buf : US "nothing") ), sock); if (result == DEFER) @@ -833,7 +851,6 @@ if (!malware_ok) int i, j, bread = 0; uschar * file_name; uschar av_buffer[1024]; - const pcre * fs_inf; static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n", US"CONFIGURE\tTIMEOUT\t0\n", US"CONFIGURE\tMAXARCH\t5\n", @@ -841,7 +858,7 @@ if (!malware_ok) malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n", scanner_name, scanner_options); /* pass options */ memset(av_buffer, 0, sizeof(av_buffer)); @@ -849,12 +866,12 @@ if (!malware_ok) { if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); if (bread > 0) av_buffer[bread]='\0'; if (bread < 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to read answer %d (%s)", i, strerror(errno)), sock); for (j = 0; j < bread; j++) @@ -866,11 +883,12 @@ if (!malware_ok) file_name = string_sprintf("SCAN\t%s\n", eml_filename); if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); /* set up match */ /* todo also SUSPICION\t */ - fs_inf = m_pcre_compile(US"\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr); + if (!fsec_re) + fsec_re = m_pcre_compile(fsec_re_str, &errstr); /* read report, linewise. Apply a timeout as the Fsecure daemon sometimes wants an answer to "PING" but they won't tell us what */ @@ -880,21 +898,21 @@ if (!malware_ok) for (;;) { - errno = ETIME; + errno = ETIMEDOUT; i = av_buffer+sizeof(av_buffer)-p; if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to read result (%s)", strerror(errno)), sock); - for (p[bread] = '\0'; q = strchr(p, '\n'); p = q+1) + for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1) { *q = '\0'; /* Really search for virus again? */ if (!malware_name) /* try matcher on the line, grab substring */ - malware_name = m_pcre_exec(fs_inf, p); + malware_name = m_pcre_exec(fsec_re, p); if (Ustrstr(p, "OK\tScan ok.")) goto fsec_found; @@ -917,7 +935,8 @@ if (!malware_ok) uschar tmpbuf[1024]; uschar * scanrequest; int kav_rc; - unsigned long kav_reportlen, bread; + unsigned long kav_reportlen; + int bread; const pcre *kav_re; uschar *p; @@ -935,16 +954,16 @@ if (!malware_ok) if (p) *p = '\0'; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n", scanner_name, scanner_options); /* send scan request */ if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); /* wait for result */ if (!recv_len(sock, tmpbuf, 2, tmo)) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"unable to read 2 bytes from socket.", sock); /* get errorcode from one nibble */ @@ -952,18 +971,18 @@ if (!malware_ok) switch(kav_rc) { case 5: case 6: /* improper kavdaemon configuration */ - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"please reconfigure kavdaemon to NOT disinfect or remove infected files.", sock); case 1: - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"reported 'scanning not completed' (code 1).", sock); case 7: - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"reported 'kavdaemon damaged' (code 7).", sock); } - /* code 8 is not handled, since it is ambigous. It appears mostly on + /* code 8 is not handled, since it is ambiguous. It appears mostly on bounces where part of a file has been cut off */ /* "virus found" return codes (2-4) */ @@ -981,7 +1000,7 @@ if (!malware_ok) { /* read report size */ if (!recv_len(sock, &kav_reportlen, 4, tmo)) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, US"cannot read report size", sock); /* it's possible that avp returns av_buffer[1] == 1 but the @@ -989,12 +1008,20 @@ if (!malware_ok) if (kav_reportlen > 0) { /* set up match regex, depends on retcode */ - kav_re = m_pcre_compile( kav_rc == 3 - ? US"suspicion:\\s*(.+?)\\s*$" - : US"infected:\\s*(.+?)\\s*$", - &errstr ); + if (kav_rc == 3) + { + if (!kav_re_sus) kav_re_sus = m_pcre_compile(kav_re_sus_str, &errstr); + kav_re = kav_re_sus; + } + else + { + if (!kav_re_inf) kav_re_inf = m_pcre_compile(kav_re_inf_str, &errstr); + kav_re = kav_re_inf; + } - /* read report, linewise */ + /* read report, linewise. Using size from stream to read amount of data + from same stream is safe enough. */ + /* coverity[tainted_data] */ while (kav_reportlen > 0) { if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0) @@ -1032,19 +1059,19 @@ if (!malware_ok) uschar *p; if (!cmdline_scanner) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, NULL, errstr); /* find scanner output trigger */ cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing trigger specification", &errstr); if (!cmdline_trigger_re) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, NULL, errstr); /* find scanner name regex */ cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing virus name regex specification", &errstr); if (!cmdline_regex_re) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, NULL, errstr); /* prepare scanner call; despite the naming, file_name holds a directory name which is documented as the value given to %s. */ @@ -1058,7 +1085,7 @@ if (!malware_ok) /* redirect STDERR too */ commandline = string_sprintf("%s 2>&1", commandline); - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline); /* store exims signal handlers */ @@ -1069,20 +1096,19 @@ if (!malware_ok) { int err = errno; signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, string_sprintf("call (%s) failed: %s.", commandline, strerror(err))); } scanner_fd = fileno(scanner_out); - file_name = string_sprintf("%s/scan/%s/%s_scanner_output", - spool_directory, message_id, message_id); + file_name = string_sprintf("%s/%s_scanner_output", eml_dir, message_id); if (!(scanner_record = modefopen(file_name, "wb", SPOOL_MODE))) { int err = errno; (void) pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, string_sprintf( + return m_errlog_defer(scanent, NULL, string_sprintf( "opening scanner output file (%s) failed: %s.", file_name, strerror(err))); } @@ -1092,24 +1118,23 @@ if (!malware_ok) sizeof(linebuffer), tmo))) { if (rcnt < 0) + { + int err = errno; if (rcnt == -1) break; - else - { - int err = errno; - (void) pclose(scanner_out); - signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, string_sprintf( - "unable to read from scanner (%s): %s", - commandline, strerror(err))); - } + (void) pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, NULL, string_sprintf( + "unable to read from scanner (%s): %s", + commandline, strerror(err))); + } if (Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record)) { /* short write */ (void) pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, string_sprintf( + return m_errlog_defer(scanent, NULL, string_sprintf( "short write on scanner output file (%s).", file_name)); } putc('\n', scanner_record); @@ -1124,7 +1149,7 @@ if (!malware_ok) sep = pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); if (sep != 0) - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, sep == -1 ? string_sprintf("running scanner failed: %s", strerror(sep)) : string_sprintf("scanner returned error code: %d", sep)); @@ -1162,20 +1187,20 @@ if (!malware_ok) if ((p = Ustrrchr(file_name, '/'))) *p = '\0'; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n", scanner_name, scanner_options); if ( write(sock, file_name, Ustrlen(file_name)) < 0 || write(sock, "\n", 1) != 1 ) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to write to UNIX socket (%s)", scanner_options), sock); /* wait for result */ memset(av_buffer, 0, sizeof(av_buffer)); if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to read from UNIX socket (%s)", scanner_options), sock); @@ -1187,7 +1212,8 @@ if (!malware_ok) malware_name = string_copy(&av_buffer[2]); } else if (!strncmp(CS av_buffer, "-1", 2)) - return m_errlog_defer_3(scanent, US"scanner reported error", sock); + return m_errlog_defer_3(scanent, CUS callout_address, + US"scanner reported error", sock); else /* all ok, no virus */ malware_name = NULL; @@ -1208,7 +1234,7 @@ if (!malware_ok) * WITH_OLD_CLAMAV_STREAM is defined. * See Exim bug 926 for details. */ - uschar *p, *vname, *result_tag, *response_end; + uschar *p, *vname, *result_tag; int bread=0; uschar * file_name; uschar av_buffer[1024]; @@ -1219,8 +1245,7 @@ if (!malware_ok) off_t fsize; unsigned int fsize_uint; BOOL use_scan_command = FALSE; - clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; - int current_server; + clamd_address * cv[MAX_CLAMD_SERVERS]; int num_servers = 0; #ifdef WITH_OLD_CLAMAV_STREAM unsigned int port; @@ -1230,67 +1255,97 @@ if (!malware_ok) uint32_t send_size, send_final_zeroblock; #endif + /*XXX if unixdomain socket, only one server supported. Needs fixing; + there's no reason we should not mix local and remote servers */ + if (*scanner_options == '/') + { + clamd_address * cd; + const uschar * sublist; + int subsep = ' '; + /* Local file; so we def want to use_scan_command and don't want to try * passing IP/port combinations */ use_scan_command = TRUE; + cd = (clamd_address *) store_get(sizeof(clamd_address)); + + /* extract socket-path part */ + sublist = scanner_options; + cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0); + + /* parse options */ + if (clamd_option(cd, sublist, &subsep) != OK) + return m_errlog_defer(scanent, NULL, + string_sprintf("bad option '%s'", scanner_options)); + cv[0] = cd; + } else { - const uschar *address = scanner_options; - uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20]; - /* Go through the rest of the list of host/port and construct an array * of servers to try. The first one is the bit we just passed from * scanner_options so process that first and then scan the remainder of * the address buffer */ do { - clamd_address_container *this_clamd; + clamd_address * cd; + const uschar * sublist; + int subsep = ' '; + uschar * s; /* The 'local' option means use the SCAN command over the network * socket (ie common file storage in use) */ - if (strcmpic(address,US"local") == 0) + /*XXX we could accept this also as a local option? */ + if (strcmpic(scanner_options, US"local") == 0) { use_scan_command = TRUE; continue; } - /* XXX: If unsuccessful we should free this memory */ - this_clamd = - (clamd_address_container *)store_get(sizeof(clamd_address_container)); + cd = (clamd_address *) store_get(sizeof(clamd_address)); /* extract host and port part */ - if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u", - this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 ) + sublist = scanner_options; + if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0))) + { + (void) m_errlog_defer(scanent, NULL, + string_sprintf("missing address: '%s'", scanner_options)); + continue; + } + if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0))) { - (void) m_errlog_defer(scanent, - string_sprintf("invalid address '%s'", address)); + (void) m_errlog_defer(scanent, NULL, + string_sprintf("missing port: '%s'", scanner_options)); continue; } + cd->tcp_port = atoi(CS s); - clamd_address_vector[num_servers] = this_clamd; - num_servers++; + /* parse options */ + /*XXX should these options be common over scanner types? */ + if (clamd_option(cd, sublist, &subsep) != OK) + return m_errlog_defer(scanent, NULL, + string_sprintf("bad option '%s'", scanner_options)); + + cv[num_servers++] = cd; if (num_servers >= MAX_CLAMD_SERVERS) { - (void) m_errlog_defer(scanent, + (void) m_errlog_defer(scanent, NULL, US"More than " MAX_CLAMD_SERVERS_S " clamd servers " "specified; only using the first " MAX_CLAMD_SERVERS_S ); break; } - } while ((address = string_nextinlist(&av_scanner_work, &sep, - address_buffer, - sizeof(address_buffer))) != NULL); + } while ((scanner_options = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))); /* check if we have at least one server */ if (!num_servers) - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, US"no useable server addresses in malware configuration option."); } /* 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, ':')) - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, string_sprintf("local/SCAN mode incompatible with" \ " : in path to email filename [%s]", eml_filename)); @@ -1303,43 +1358,52 @@ if (!malware_ok) while (num_servers > 0) { - int i; - /* Randomly pick a server to start with */ - current_server = random_number( num_servers ); + int i = random_number( num_servers ); + clamd_address * cd = cv[i]; - DEBUG(D_acl) - debug_printf("trying server name %s, port %u\n", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port); + DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n", + cd->hostspec, cd->tcp_port); /* Lookup the host. This is to ensure that we connect to the same IP * on both connections (as one host could resolve to multiple ips) */ - sock= m_tcpsocket(clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port, - &connhost, &errstr); - if (sock >= 0) + for (;;) { - /* Connection successfully established with a server */ - hostname = clamd_address_vector[current_server]->tcp_addr; - break; + sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr); + if (sock >= 0) + { + /* Connection successfully established with a server */ + hostname = cd->hostspec; + break; + } + if (cd->retry <= 0) break; + while (cd->retry > 0) cd->retry = sleep(cd->retry); } + if (sock >= 0) + break; - (void) m_errlog_defer(scanent, errstr); + (void) m_errlog_defer(scanent, CUS callout_address, errstr); /* Remove the server from the list. XXX We should free the memory */ num_servers--; - for (i = current_server; i < num_servers; i++) - clamd_address_vector[i] = clamd_address_vector[i+1]; + for (; i < num_servers; i++) + cv[i] = cv[i+1]; } if (num_servers == 0) - return m_errlog_defer(scanent, US"all servers failed"); + return m_errlog_defer(scanent, NULL, US"all servers failed"); } else - { - if ((sock = m_unixsocket(scanner_options, &errstr)) < 0) - return m_errlog_defer(scanent, errstr); - } + for (;;) + { + if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0) + { + hostname = cv[0]->hostspec; + break; + } + if (cv[0]->retry <= 0) + return m_errlog_defer(scanent, CUS callout_address, errstr); + while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry); + } /* have socket in variable "sock"; command to use is semi-independent of * the socket protocol. We use SCAN if is local (either Unix/local @@ -1353,39 +1417,41 @@ if (!malware_ok) * that port on a second connection; then in the scan-method-neutral * part, read the response back on the original connection. */ - DEBUG(D_acl) debug_printf( + DEBUG(D_acl) debug_printf_indent( "Malware scan: issuing %s old-style remote scan (PORT)\n", scanner_name); /* Pass the string to ClamAV (7 = "STREAM\n") */ if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); memset(av_buffer2, 0, sizeof(av_buffer2)); bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL)); if (bread < 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to read PORT from socket (%s)", strerror(errno)), sock); if (bread == sizeof(av_buffer2)) - return m_errlog_defer_3(scanent, "buffer too small", sock); + return m_errlog_defer_3(scanent, CUS callout_address, + "buffer too small", sock); if (!(*av_buffer2)) - return m_errlog_defer_3(scanent, "ClamAV returned null", sock); + return m_errlog_defer_3(scanent, CUS callout_address, + "ClamAV returned null", sock); av_buffer2[bread] = '\0'; if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("Expected port information from clamd, got '%s'", av_buffer2), sock); sockData = m_tcpsocket(connhost.address, port, NULL, &errstr); if (sockData < 0) - return m_errlog_defer_3(scanent, errstr, sock); + return m_errlog_defer_3(scanent, CUS callout_address, errstr, sock); # define CLOSE_SOCKDATA (void)close(sockData) #else /* WITH_OLD_CLAMAV_STREAM not defined */ @@ -1393,13 +1459,13 @@ if (!malware_ok) chunks, a 4-byte number (network order), terminated by a zero-length chunk. */ - DEBUG(D_acl) debug_printf( + DEBUG(D_acl) debug_printf_indent( "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", scanner_name); /* Pass the string to ClamAV (10 = "zINSTREAM\0") */ if (send(sock, "zINSTREAM", 10, 0) < 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS hostname, string_sprintf("unable to send zINSTREAM to socket (%s)", strerror(errno)), sock); @@ -1412,16 +1478,17 @@ if (!malware_ok) { int err = errno; CLOSE_SOCKDATA; - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("can't open spool file %s: %s", eml_filename, strerror(err)), sock); } if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) { - int err = errno; + int err; +b_seek: err = errno; CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("can't seek spool file %s: %s", eml_filename, strerror(err)), sock); @@ -1430,17 +1497,18 @@ if (!malware_ok) if ((off_t)fsize_uint != fsize) { CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("seeking spool file %s, size overflow", eml_filename), sock); } - lseek(clam_fd, 0, SEEK_SET); + if (lseek(clam_fd, 0, SEEK_SET) < 0) + goto b_seek; - if (!(clamav_fbuf = (uschar *) malloc (fsize_uint))) + if (!(clamav_fbuf = US malloc(fsize_uint))) { CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("unable to allocate memory %u for file (%s)", fsize_uint, eml_filename), sock); @@ -1450,7 +1518,7 @@ if (!malware_ok) { int err = errno; free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("can't read spool file %s: %s", eml_filename, strerror(err)), sock); @@ -1462,7 +1530,7 @@ if (!malware_ok) if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0) { free(clamav_fbuf); CLOSE_SOCKDATA; - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("unable to send file body to socket (%s:%u)", hostname, port), sock); @@ -1475,7 +1543,7 @@ if (!malware_ok) (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) { free(clamav_fbuf); - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, string_sprintf("unable to send file body to socket (%s)", hostname), sock); } @@ -1504,12 +1572,12 @@ if (!malware_ok) /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ file_name = string_sprintf("SCAN %s\n", eml_filename); - DEBUG(D_acl) debug_printf( + DEBUG(D_acl) debug_printf_indent( "Malware scan: issuing %s local-path scan [%s]\n", scanner_name, scanner_options); if (send(sock, file_name, Ustrlen(file_name), 0) < 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to write to socket (%s)", strerror(errno)), sock); @@ -1526,12 +1594,13 @@ if (!malware_ok) sock = -1; if (bread <= 0) - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, CUS callout_address, string_sprintf("unable to read from socket (%s)", errno == 0 ? "EOF" : strerror(errno))); if (bread == sizeof(av_buffer)) - return m_errlog_defer(scanent, US"buffer too small"); + return m_errlog_defer(scanent, CUS callout_address, + US"buffer too small"); /* We're now assured of a NULL at the end of av_buffer */ /* Check the result. ClamAV returns one of two result formats. @@ -1555,7 +1624,8 @@ if (!malware_ok) passing a filename to clamd). */ if (!(*av_buffer)) - return m_errlog_defer(scanent, US"ClamAV returned null"); + return m_errlog_defer(scanent, CUS callout_address, + US"ClamAV returned null"); /* strip newline at the end (won't be present for zINSTREAM) (also any trailing whitespace, which shouldn't exist, but we depend upon @@ -1563,16 +1633,15 @@ if (!malware_ok) p = av_buffer + Ustrlen(av_buffer) - 1; if (*p == '\n') *p = '\0'; - DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); + DEBUG(D_acl) debug_printf_indent("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 = Ustrchr(av_buffer,':')) == NULL) - return m_errlog_defer(scanent, string_sprintf( + if(!(p = Ustrchr(av_buffer,':'))) + return m_errlog_defer(scanent, CUS callout_address, string_sprintf( "ClamAV returned malformed result (missing colon): %s", av_buffer)); @@ -1601,22 +1670,22 @@ if (!malware_ok) *p = '\0'; } malware_name = string_copy(vname); - DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); + DEBUG(D_acl) debug_printf_indent("Malware found, name \"%s\"\n", malware_name); } else if (Ustrcmp(result_tag, "ERROR") == 0) - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, CUS callout_address, string_sprintf("ClamAV returned: %s", av_buffer)); else if (Ustrcmp(result_tag, "OK") == 0) { /* Everything should be OK */ malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware not found\n"); + DEBUG(D_acl) debug_printf_indent("Malware not found\n"); } else - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, CUS callout_address, string_sprintf("unparseable response from ClamAV: {%s}", av_buffer)); break; @@ -1637,57 +1706,66 @@ if (!malware_ok) const pcre *sockline_name_re; /* find scanner command line */ - if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep, - NULL, 0))) + if ( (sockline_scanner = string_nextinlist(&av_scanner_work, &sep, + NULL, 0)) + && *sockline_scanner + ) { /* check for no expansions apart from one %s */ uschar * s = Ustrchr(sockline_scanner, '%'); if (s++) if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%')) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, NULL, US"unsafe sock scanner call spec", sock); } else sockline_scanner = sockline_scanner_default; + DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "cmdline: ", + string_printing(sockline_scanner)); /* find scanner output trigger */ sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing trigger specification", &errstr); if (!sockline_trig_re) - return m_errlog_defer_3(scanent, errstr, sock); + return m_errlog_defer_3(scanent, NULL, errstr, sock); /* find virus name regex */ sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing virus name regex specification", &errstr); if (!sockline_name_re) - return m_errlog_defer_3(scanent, errstr, sock); + return m_errlog_defer_3(scanent, NULL, errstr, sock); /* prepare scanner call - security depends on expansions check above */ - commandline = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id, message_id); - commandline = string_sprintf( CS sockline_scanner, CS commandline); - + commandline = string_sprintf( CS sockline_scanner, CS eml_filename); + DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "expanded: ", + string_printing(commandline)); /* Pass the command string to the socket */ if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0) - return m_errlog_defer(scanent, errstr); + return m_errlog_defer(scanent, CUS callout_address, errstr); /* Read the result */ bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); if (bread <= 0) - return m_errlog_defer_3(scanent, + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf("unable to read from socket (%s)", strerror(errno)), sock); if (bread == sizeof(av_buffer)) - return m_errlog_defer_3(scanent, US"buffer too small", sock); + return m_errlog_defer_3(scanent, CUS callout_address, + US"buffer too small", sock); av_buffer[bread] = '\0'; linebuffer = string_copy(av_buffer); + DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ", + string_printing(linebuffer)); /* try trigger match */ if (regex_match_and_setup(sockline_trig_re, linebuffer, 0, -1)) { if (!(malware_name = m_pcre_exec(sockline_name_re, av_buffer))) malware_name = US "unknown"; + DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "name: ", + string_printing(malware_name)); } else /* no virus found */ malware_name = NULL; @@ -1708,16 +1786,16 @@ if (!malware_ok) || mksd_maxproc < 1 || mksd_maxproc > 32 ) - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, CUS callout_address, string_sprintf("invalid option '%s'", scanner_options)); } - if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) - return m_errlog_defer(scanent, errstr); + if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + return m_errlog_defer(scanent, CUS callout_address, errstr); malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name); if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK) { @@ -1732,7 +1810,6 @@ if (!malware_ok) int ovector[1*3]; uschar buf[1024]; uschar * scanrequest; - const pcre * avast_clean_re, * avast_virus_re; enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage; int nread; @@ -1745,11 +1822,10 @@ if (!malware_ok) [L] - infected [E] - some error occured Such marker follows the first non-escaped TAB. */ - if ( !(avast_clean_re = - m_pcre_compile(US"(?!\\\\)\\t\\[\\+\\]", &errstr)) - || !(avast_virus_re = - m_pcre_compile(US"(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)", - &errstr)) + if ( ( !ava_re_clean + && !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr))) + || ( !ava_re_virus + && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr))) ) return malware_errlog_defer(errstr); @@ -1759,9 +1835,9 @@ if (!malware_ok) ) { int slen = Ustrlen(buf); - if (slen >= 1) + if (slen >= 1) { - DEBUG(D_acl) debug_printf("got from avast: %s\n", buf); + DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf); switch (avast_stage) { case AVA_HELO: @@ -1787,8 +1863,7 @@ if (!malware_ok) } else { - scanrequest = string_sprintf("SCAN %s/scan/%s\n", - spool_directory, message_id); + scanrequest = string_sprintf("SCAN %s\n", eml_dir); avast_stage = AVA_RSP; /* just sent command */ } @@ -1797,7 +1872,7 @@ if (!malware_ok) if (send(sock, scanrequest, len, 0) < 0) { scanrequest[len-1] = '\0'; - return m_errlog_defer_3(scanent, string_sprintf( + return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf( "unable to send request '%s' to socket (%s): %s", scanrequest, scanner_options, strerror(errno)), sock); } @@ -1808,25 +1883,26 @@ if (!malware_ok) if (Ustrncmp(buf, "210", 3) == 0) break; /* ignore the "210 SCAN DATA" message */ - if (pcre_exec(avast_clean_re, NULL, CS buf, slen, + if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, ovector, nelements(ovector)) > 0) break; - if ((malware_name = m_pcre_exec(avast_virus_re, buf))) + if ((malware_name = m_pcre_exec(ava_re_virus, buf))) { /* remove backslash in front of [whitespace|backslash] */ uschar * p, * p0; - for (p = malware_name; *p; ++p) + for (p = malware_name; *p; ++p) if (*p == '\\' && (isspace(p[1]) || p[1] == '\\')) for (p0 = p; *p0; ++p0) *p0 = p0[1]; - + avast_stage = AVA_DONE; goto endloop; } - if (Ustrncmp(buf, "200 SCAN OK", 11) == 0) + if (Ustrncmp(buf, "200 SCAN OK", 11) == 0) { /* we're done finally */ if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */ - return m_errlog_defer_3(scanent, string_sprintf( + return m_errlog_defer_3(scanent, CUS callout_address, + string_sprintf( "unable to send quit request to socket (%s): %s", scanner_options, strerror(errno)), sock); @@ -1837,6 +1913,9 @@ if (!malware_ok) /* here for any unexpected response from the scanner */ goto endloop; + + case AVA_DONE: log_write(0, LOG_PANIC, "%s:%d:%s: should not happen", + __FILE__, __LINE__, __FUNCTION__); } } } @@ -1844,9 +1923,9 @@ if (!malware_ok) switch(avast_stage) { - case AVA_HELO: + case AVA_HELO: case AVA_OPT: - case AVA_RSP: return m_errlog_defer_3(scanent, + case AVA_RSP: return m_errlog_defer_3(scanent, CUS callout_address, nread >= 0 ? string_sprintf( "invalid response from scanner: '%s'", buf) @@ -1856,8 +1935,53 @@ if (!malware_ok) sock); default: break; } + break; } + + case M_FPROT6D: /* "f-prot6d" scanner type ----------------------------------- */ + { + int bread; + uschar * e; + uschar * linebuffer; + uschar * scanrequest; + uschar av_buffer[1024]; + + if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, &errstr))) + || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, &errstr)))) + return malware_errlog_defer(errstr); + + scanrequest = string_sprintf("SCAN FILE %s\n", eml_filename); + DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n", + scanner_name, scanrequest); + + if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0) + return m_errlog_defer(scanent, CUS callout_address, errstr); + + bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); + + if (bread <= 0) + return m_errlog_defer_3(scanent, CUS callout_address, + string_sprintf("unable to read from socket (%s)", strerror(errno)), + sock); + + if (bread == sizeof(av_buffer)) + return m_errlog_defer_3(scanent, CUS callout_address, + US"buffer too small", sock); + + av_buffer[bread] = '\0'; + linebuffer = string_copy(av_buffer); + + m_sock_send(sock, US"QUIT\n", 5, 0); + + if ((e = m_pcre_exec(fprot6d_re_error, linebuffer))) + return m_errlog_defer_3(scanent, CUS callout_address, + string_sprintf("scanner reported error (%s)", e), sock); + + if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer))) + malware_name = NULL; + break; + } /* f-prot6d */ } /* scanner type switch */ if (sock >= 0) @@ -1868,7 +1992,7 @@ if (!malware_ok) /* match virus name against pattern (caseless ------->----------v) */ if (malware_name && regex_match_and_setup(re, malware_name, 0, -1)) { - DEBUG(D_acl) debug_printf( + DEBUG(D_acl) debug_printf_indent( "Matched regex to malware [%s] [%s]\n", malware_re, malware_name); return OK; } @@ -1894,15 +2018,10 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) int malware(const uschar * malware_re, int timeout) { - uschar * scan_filename; - int ret; - - scan_filename = string_sprintf("%s/scan/%s/%s.eml", - spool_directory, message_id, message_id); - ret = malware_internal(malware_re, scan_filename, timeout, FALSE); - if (ret == DEFER) av_failed = TRUE; +int ret = malware_internal(malware_re, NULL, timeout); - return ret; +if (ret == DEFER) av_failed = TRUE; +return ret; } @@ -1924,32 +2043,59 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) int malware_in_file(uschar *eml_filename) { - uschar message_id_buf[64]; - int ret; - - /* 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), - "dummy-%d", vaguely_random_number(INT_MAX)); - message_id = message_id_buf; - sender_address = US"malware-sender@example.net"; - return_path = US""; - recipients_list = NULL; - receive_add_recipient(US"malware-victim@example.net", -1); - enable_dollar_recipients = TRUE; - - ret = malware_internal(US"*", eml_filename, 0, TRUE); - - 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 */ - unspool_mbox(); - - /* silence static analysis tools */ - message_id = NULL; - - return ret; +uschar message_id_buf[64]; +int ret; + +/* 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), + "dummy-%d", vaguely_random_number(INT_MAX)); +message_id = message_id_buf; +sender_address = US"malware-sender@example.net"; +return_path = US""; +recipients_list = NULL; +receive_add_recipient(US"malware-victim@example.net", -1); +enable_dollar_recipients = TRUE; + +ret = malware_internal(US"*", eml_filename, 0); + +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 */ + +unspool_mbox(); + +/* silence static analysis tools */ +message_id = NULL; + +return ret; +} + + +void +malware_init(void) +{ +if (!malware_default_re) + malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE); +if (!drweb_re) + drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE); +if (!fsec_re) + fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE); +if (!kav_re_sus) + kav_re_sus = regex_must_compile(kav_re_sus_str, FALSE, TRUE); +if (!kav_re_inf) + kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE); +if (!ava_re_clean) + ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE); +if (!ava_re_virus) + ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE); +if (!fprot6d_re_error) + fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE); +if (!fprot6d_re_virus) + fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, FALSE, TRUE); } #endif /*WITH_CONTENT_SCAN*/