X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/70817078bcd173fa33c9dabdf1e8068c901a83a1..bb07bcd32250965a896b0856dd1b839b5795e2f4:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index 43f031494..f9b99193d 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])) @@ -84,6 +85,11 @@ static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\ 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; + /******************************************************************************/ @@ -98,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); } @@ -108,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); } /*************************************************/ @@ -139,113 +147,68 @@ uses the returned in_addr to get a second connection to the same system. */ static inline int m_tcpsocket(const uschar * hostname, unsigned int port, - host_item * host, uschar ** errstr) + host_item * host, uschar ** errstr, const blob * fastopen_blob) { - 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, fastopen_blob); } 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; } /* @@ -279,13 +242,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; } @@ -379,7 +342,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 */ @@ -397,7 +360,7 @@ switch (*line) return OK; } } - return m_errlog_defer(scanent, + return m_errlog_defer(scanent, NULL, string_sprintf("malformed reply received: %s", line)); } } @@ -426,6 +389,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 * *************************************************/ @@ -435,19 +418,19 @@ 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; unsigned long mbox_size; FILE *mbox_file; @@ -457,21 +440,24 @@ 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" */ -/* make sure the eml mbox file is spooled up */ -if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL))) +/* Ensure the eml mbox file is spooled up */ + +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) */ @@ -491,9 +477,6 @@ if ( strcmpic(malware_re,US"true") == 0 else if (!(re = m_pcre_compile(malware_re, &errstr))) return malware_errlog_defer(errstr); -/* Reset sep that is set by previous string_nextinlist() call */ -sep = 0; - /* if av_scanner starts with a dollar, expand it first */ if (*av_scanner == '$') { @@ -503,7 +486,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; @@ -518,28 +501,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) { @@ -562,12 +550,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) @@ -616,16 +604,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); @@ -634,15 +623,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 */ @@ -652,15 +642,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); @@ -671,7 +661,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); @@ -682,17 +672,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 */ @@ -701,19 +690,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); @@ -721,6 +710,7 @@ if (!malware_ok) if (drweb_vnum) { int i; + gstring * g = NULL; /* setup default virus name */ malware_name = US"unknown"; @@ -732,17 +722,18 @@ if (!malware_ok) /* read and concatenate virus names into one string */ for (i = 0; i < drweb_vnum; i++) { - int size = 0, off = 0, ovector[10*3]; + int 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'; @@ -756,16 +747,16 @@ if (!malware_ok) pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); if (i==0) /* the first name we just copy to malware_name */ - malware_name = string_append(NULL, &size, &off, - 1, pre_malware_nb); + g = string_cat(NULL, pre_malware_nb); + /*XXX could be string_append_listele? */ else /* concatenate each new virus name to previous */ - malware_name = string_append(malware_name, &size, &off, - 2, "/", pre_malware_nb); + g = string_append(g, 2, "/", pre_malware_nb); pcre_free_substring(pre_malware_nb); } } + malware_name = string_from_gstring(g); } else { @@ -780,7 +771,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); @@ -800,9 +791,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 */ @@ -810,10 +801,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; @@ -824,7 +815,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; @@ -838,16 +829,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) @@ -870,7 +861,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)); @@ -878,12 +869,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++) @@ -895,7 +886,7 @@ 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 */ @@ -910,14 +901,14 @@ 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'; @@ -947,7 +938,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; @@ -965,16 +957,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 */ @@ -982,18 +974,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) */ @@ -1011,7 +1003,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 @@ -1030,7 +1022,9 @@ if (!malware_ok) 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) @@ -1068,19 +1062,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. */ @@ -1094,7 +1088,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 */ @@ -1105,20 +1099,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))); } @@ -1128,24 +1121,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); @@ -1160,7 +1152,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)); @@ -1198,20 +1190,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); @@ -1223,7 +1215,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; @@ -1244,7 +1237,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]; @@ -1255,8 +1248,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; @@ -1265,71 +1257,115 @@ if (!malware_ok) #else uint32_t send_size, send_final_zeroblock; #endif + blob cmd_str; + + /*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, - string_sprintf("invalid address '%s'", address)); + (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, 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)); + /* Set up the very first data we will be sending */ + if (!use_scan_command) +#ifdef WITH_OLD_CLAMAV_STREAM + { cmd_str.data = US"STREAM\n"; cmd_str.len = 7; } +#else + { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; } +#endif + else + { + cmd_str.data = string_sprintf("SCAN %s\n", eml_filename); + cmd_str.len = Ustrlen(cmd_str.data); + } + /* We have some network servers specified */ if (num_servers) { @@ -1339,44 +1375,53 @@ 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; + if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port, + &connhost, &errstr, &cmd_str)) >= 0) + { + /* Connection successfully established with a server */ + hostname = cd->hostspec; + cmd_str.len = 0; + break; + } + if (cd->retry <= 0) break; + while (cd->retry > 0) cd->retry = sleep(cd->retry); } + if (sock >= 0) + break; - log_write(0, LOG_MAIN, "malware acl condition: %s: %s", - scanent->name, 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 @@ -1390,39 +1435,42 @@ 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); + /* Pass the string to ClamAV (7 = "STREAM\n"), if not already sent */ + if (cmd_str.len) + if (m_sock_send(sock, cmd_str.data, cmd_str.len, &errstr) < 0) + 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, + if(sscanf(CS av_buffer2, "PORT %u\n", &port) != 1) + 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); + sockData = m_tcpsocket(connhost.address, port, NULL, &errstr, NULL); 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 */ @@ -1430,16 +1478,17 @@ 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, - string_sprintf("unable to send zINSTREAM to socket (%s)", - strerror(errno)), - sock); + /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */ + if (cmd_str.len) + if (send(sock, cmd_str.data, cmd_str.len, 0) < 0) + return m_errlog_defer_3(scanent, CUS hostname, + string_sprintf("unable to send zINSTREAM to socket (%s)", + strerror(errno)), + sock); # define CLOSE_SOCKDATA /**/ #endif @@ -1449,16 +1498,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); @@ -1467,17 +1517,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); @@ -1487,7 +1538,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); @@ -1499,7 +1550,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); @@ -1512,7 +1563,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); } @@ -1538,17 +1589,17 @@ if (!malware_ok) scanned twice, in the broken out files and from the original .eml. Since ClamAV now handles emails (and has for quite some time) we can just use the email file itself. */ - /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ - file_name = string_sprintf("SCAN %s\n", eml_filename); + /* Pass the string to ClamAV (7 = "SCAN \n" + \0), if not already sent */ - 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, - string_sprintf("unable to write to socket (%s)", strerror(errno)), - sock); + if (cmd_str.len) + if (send(sock, cmd_str.data, cmd_str.len, 0) < 0) + return m_errlog_defer_3(scanent, CUS callout_address, + string_sprintf("unable to write to socket (%s)", strerror(errno)), + sock); /* Do not shut down the socket for writing; a user report noted that * clamd 0.70 does not react well to this. */ @@ -1563,12 +1614,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. @@ -1592,7 +1644,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 @@ -1600,16 +1653,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)); @@ -1638,22 +1690,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; @@ -1674,57 +1726,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; @@ -1745,16 +1806,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) { @@ -1794,9 +1855,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: @@ -1822,8 +1883,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 */ } @@ -1832,7 +1892,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); } @@ -1850,18 +1910,19 @@ if (!malware_ok) 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); @@ -1872,6 +1933,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__); } } } @@ -1879,9 +1943,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) @@ -1891,8 +1955,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) @@ -1903,7 +2012,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; } @@ -1929,15 +2038,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; } @@ -1959,32 +2063,35 @@ 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; } @@ -2005,6 +2112,10 @@ 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*/