From b1f8e4f8ec26ecb99e56a0ed3a5140b65ec95a97 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 9 Mar 2014 21:45:33 +0000 Subject: [PATCH 1/1] Refactor malware.c and introduce new scanner type "sock". Bugs 1418 and 1396 --- doc/doc-docbook/spec.xfpt | 18 + doc/doc-txt/ChangeLog | 3 + doc/doc-txt/NewStuff | 5 + src/src/acl.c | 18 +- src/src/expand.c | 77 +- src/src/functions.h | 4 +- src/src/ip.c | 101 +- src/src/malware.c | 3037 ++++++++++++++++--------------------- src/src/string.c | 4 +- 9 files changed, 1483 insertions(+), 1784 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 345effd0e..cab1c82dd 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -29810,6 +29810,24 @@ av_scanner = mksd:2 .endd You can safely omit this option (the default value is 1). +.vitem &%sock%& +.cindex "virus scanners" "simple socket-connected" +This is a general-purpose way of talking to simple scanner daemons +running on the local machine. +There are four options: +an address (which may be an IP addres and port, or the path of a Unix socket), +a commandline to send (may include a single %s which will be replaced with +the path to the mail file to be scanned), +an RE to trigger on from the returned data, +an RE to extract malware_name from the returned data. +For example: +.code +av_scanner = sock:127.0.0.1 6001:%s:(SPAM|VIRUS):(.*)\$ +.endd +Default for the socket specifier is &_/tmp/malware.sock_&. +Default for the commandline is &_%s\n_&. +Both regular-expressions are required. + .vitem &%sophie%& .cindex "virus scanners" "Sophos and Sophie" Sophie is a daemon that uses Sophos' &%libsavi%& library to scan for viruses. diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 493861fec..04a7ce02e 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -54,6 +54,9 @@ JH/05 Bugzilla 305: Log incoming-TLS details on rejects, subject to log JH/06 Log outbound-TLS and port details, subject to log selectors, for a failed delivery. +JH/07 Add malware type "sock" for talking to simple daemon. + + Exim version 4.82 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 04ac831dc..c4de902c0 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -22,6 +22,11 @@ Version 4.83 3. New expansion operator ${utf8clean:string} to replace malformed UTF8 codepoints with valid ones. + 4. New malware type "sock". Talks over a Unix or TCP socket, sending one + command line and matching a regex against the return data for trigger + and a second regex to extract malware_name. The mail spoofile name can + be included in the command line. + Version 4.82 ------------ diff --git a/src/src/acl.c b/src/src/acl.c index 386754fcf..fd958aa39 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -2857,9 +2857,9 @@ uschar *portstr; uschar *portend; host_item *h; int portnum; -int host_af; int len; int r, s; +uschar * errstr; hostname = string_nextinlist(&arg, &sep, NULL, 0); portstr = string_nextinlist(&arg, &sep, NULL, 0); @@ -2906,14 +2906,18 @@ if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN) HDEBUG(D_acl) debug_printf("udpsend [%s]:%d %s\n", h->address, portnum, arg); -host_af = (Ustrchr(h->address, ':') == NULL)? AF_INET:AF_INET6; -r = s = ip_socket(SOCK_DGRAM, host_af); -if (r < 0) goto defer; -r = ip_connect(s, host_af, h->address, portnum, 1); +r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum, + 1, NULL, &errstr); if (r < 0) goto defer; len = Ustrlen(arg); r = send(s, arg, len, 0); -if (r < 0) goto defer; +if (r < 0) + { + errstr = US strerror(errno); + close(s); + goto defer; + } +close(s); if (r < len) { *log_msgptr = @@ -2927,7 +2931,7 @@ HDEBUG(D_acl) return OK; defer: -*log_msgptr = string_sprintf("\"udpsend\" failed: %s", strerror(errno)); +*log_msgptr = string_sprintf("\"udpsend\" failed: %s", errstr); return DEFER; } diff --git a/src/src/expand.c b/src/src/expand.c index ee7b2fdc6..bd097db43 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -4499,10 +4499,7 @@ while (*s != 0) if (Ustrncmp(sub_arg[0], "inet:", 5) == 0) { - BOOL connected = FALSE; - int namelen, port; - host_item shost; - host_item *h; + int port; uschar *server_name = sub_arg[0] + 5; uschar *port_name = Ustrrchr(server_name, ':'); @@ -4539,76 +4536,9 @@ while (*s != 0) port = ntohs(service_info->s_port); } - /* Sort out the server. */ - - shost.next = NULL; - shost.address = NULL; - shost.port = port; - shost.mx = -1; - - namelen = Ustrlen(server_name); - - /* Anything enclosed in [] must be an IP address. */ - - if (server_name[0] == '[' && - server_name[namelen - 1] == ']') - { - server_name[namelen - 1] = 0; - server_name++; - if (string_is_ip_address(server_name, NULL) == 0) - { - expand_string_message = - string_sprintf("malformed IP address \"%s\"", server_name); - goto EXPAND_FAILED; - } - shost.name = shost.address = server_name; - } - - /* Otherwise check for an unadorned IP address */ - - else if (string_is_ip_address(server_name, NULL) != 0) - shost.name = shost.address = server_name; - - /* Otherwise lookup IP address(es) from the name */ - - else - { - shost.name = server_name; - if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, - FALSE) != HOST_FOUND) - { - expand_string_message = - string_sprintf("no IP address found for host %s", shost.name); - goto EXPAND_FAILED; - } - } - - /* Try to connect to the server - test each IP till one works */ - - for (h = &shost; h != NULL; h = h->next) - { - int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET; - if ((fd = ip_socket(SOCK_STREAM, af)) == -1) - { - expand_string_message = string_sprintf("failed to create socket: " - "%s", strerror(errno)); + if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port, + timeout, NULL, &expand_string_message)) < 0) goto SOCK_FAIL; - } - - if (ip_connect(fd, af, h->address, port, timeout) == 0) - { - connected = TRUE; - break; - } - } - - if (!connected) - { - expand_string_message = string_sprintf("failed to connect to " - "socket %s: couldn't connect to any host", sub_arg[0], - strerror(errno)); - goto SOCK_FAIL; - } } /* Handle a Unix domain socket */ @@ -5355,7 +5285,6 @@ while (*s != 0) uschar *list, *expr, *temp; uschar *save_iterate_item = iterate_item; uschar *save_lookup_value = lookup_value; - BOOL dummy; while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; diff --git a/src/src/functions.h b/src/src/functions.h index c6cb30119..a94fac3f2 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -171,6 +171,8 @@ extern int host_scan_for_local_hosts(host_item *, host_item **, BOOL *); extern void invert_address(uschar *, uschar *); extern int ip_bind(int, int, uschar *, int); extern int ip_connect(int, int, uschar *, int, int); +extern int ip_connectedsocket(int, const uschar *, int, int, + int, host_item *, uschar **); extern int ip_get_address_family(int); extern void ip_keepalive(int, uschar *, BOOL); extern int ip_recv(int, uschar *, int, int); @@ -358,7 +360,7 @@ extern uschar *string_dequote(uschar **); extern BOOL string_format(uschar *, int, const char *, ...) ALMOST_PRINTF(3,4); extern uschar *string_format_size(int, uschar *); extern int string_interpret_escape(uschar **); -extern int string_is_ip_address(uschar *, int *); +extern int string_is_ip_address(const uschar *, int *); extern uschar *string_log_address(address_item *, BOOL, BOOL); extern uschar *string_nextinlist(uschar **, int *, uschar *, int); extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3); diff --git a/src/src/ip.c b/src/src/ip.c index d6e4e7ad8..a820998ca 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -172,7 +172,7 @@ Arguments: af AF_INET6 or AF_INET for the socket type address the remote address, in text form port the remote port - timeout a timeout + timeout a timeout (zero for indefinite timeout) Returns: 0 on success; -1 on failure, with errno set */ @@ -248,6 +248,105 @@ return -1; } +/* Create a socket and connect to host (name or number, ipv6 ok) + at one of port-range. +Arguments: + type SOCK_DGRAM or SOCK_STREAM + af AF_INET6 or AF_INET for the socket type + address the remote address, in text form + portlo,porthi the remote port range + timeout a timeout + connhost if not NULL, host_item filled in with connection details + errstr pointer for allocated string on error + +Return: + socket fd, or -1 on failure (having allocated an error string) +*/ +int +ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi, + int timeout, host_item * connhost, uschar ** errstr) +{ +int namelen, port; +host_item shost; +host_item *h; +int af = 0, fd, fd4 = -1, fd6 = -1; + +shost.next = NULL; +shost.address = NULL; +shost.port = portlo; +shost.mx = -1; + +namelen = Ustrlen(hostname); + +/* Anything enclosed in [] must be an IP address. */ + +if (hostname[0] == '[' && + hostname[namelen - 1] == ']') + { + uschar * host = string_copy(hostname); + host[namelen - 1] = 0; + host++; + if (string_is_ip_address(host, NULL) == 0) + { + *errstr = string_sprintf("malformed IP address \"%s\"", hostname); + return -1; + } + shost.name = shost.address = host; + } + +/* Otherwise check for an unadorned IP address */ + +else if (string_is_ip_address(hostname, NULL) != 0) + shost.name = shost.address = string_copy(hostname); + +/* Otherwise lookup IP address(es) from the name */ + +else + { + shost.name = string_copy(hostname); + if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, + FALSE) != HOST_FOUND) + { + *errstr = string_sprintf("no IP address found for host %s", shost.name); + return -1; + } + } + +/* Try to connect to the server - test each IP till one works */ + +for (h = &shost; h != NULL; h = h->next) + { + fd = (Ustrchr(h->address, ':') != 0) + ? (fd6 < 0) ? (fd6 = ip_socket(SOCK_STREAM, af = AF_INET6)) : fd6 + : (fd4 < 0) ? (fd4 = ip_socket(SOCK_STREAM, af = AF_INET )) : fd4; + + if (fd < 0) + { + *errstr = string_sprintf("failed to create socket: %s", strerror(errno)); + goto bad; + } + + for(port = portlo; port <= porthi; port++) + if (ip_connect(fd, af, h->address, port, timeout) == 0) + { + if (fd != fd6) close(fd6); + if (fd != fd4) close(fd4); + if (connhost) { + h->port = port; + *connhost = *h; + connhost->next = NULL; + } + return fd; + } + } + +*errstr = string_sprintf("failed to connect to " + "%s: couldn't connect to any host", hostname, strerror(errno)); + +bad: + close(fd4); close(fd6); return -1; +} + /************************************************* * Set keepalive on a socket * diff --git a/src/src/malware.c b/src/src/malware.c index 2b4d28233..01209d684 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -10,6 +10,30 @@ #include "exim.h" #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} scanner_t; +typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t; +static struct scan +{ + scanner_t scancode; + const uschar * name; + const uschar * options_default; + contype_t conn; +} m_scans[] = +{ + { M_FPROTD, US"f-protd", US"localhost 10200-10204", MC_TCP }, + { M_DRWEB, US"drweb", US"/usr/local/drweb/run/drwebd.sock", MC_STRM }, + { M_AVES, US"aveserver", US"/var/run/aveserver", MC_UNIX }, + { M_FSEC, US"fsecure", US"/var/run/.fsav", MC_UNIX }, + { M_KAVD, US"kavdaemon", US"/var/run/AvpCtl", MC_UNIX }, + { M_CMDL, US"cmdline", NULL, MC_NONE }, + { M_SOPHIE, US"sophie", US"/var/run/sophie", MC_UNIX }, + { M_CLAMD, US"clamd", US"/tmp/clamd", MC_NONE }, + { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM }, + { M_MKSD, US"mksd", NULL, MC_NONE }, + { -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" @@ -23,12 +47,11 @@ typedef struct clamd_address_container { } clamd_address_container; /* declaration of private routines */ -static int mksd_scan_packed(int sock, uschar *scan_filename); +static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename); static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking); -/* SHUT_WR seems to be undefined on Unixware? */ -#ifndef SHUT_WR -#define SHUT_WR 1 +#ifndef nelements +# define nelements(arr) (sizeof(arr) / sizeof(arr[0])) #endif @@ -44,20 +67,21 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) #define DERR_TIMEOUT (1<<9) /* scan timeout has run out */ #define DERR_BAD_CALL (1<<15) /* wrong command */ -/* Routine to check whether a system is big- or litte-endian. +/* 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. */ #define BIG_MY_ENDIAN 0 #define LITTLE_MY_ENDIAN 1 -int test_byte_order(void); -int test_byte_order() { - short int word = 0x0001; - char *byte = (char *) &word; - return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); +static int test_byte_order(void); +static inline int +test_byte_order() +{ + short int word = 0x0001; + char *byte = (char *) &word; + return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); } -uschar malware_name_buffer[256]; -int malware_ok = 0; +BOOL malware_ok = FALSE; /* Gross hacks for the -bmalware option; perhaps we should just create the scan directory normally for that case, but look into rigging up the @@ -78,21 +102,14 @@ Arguments: Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ -int malware(uschar **listptr) { - uschar scan_filename[1024]; - BOOL fits; +int +malware(uschar **listptr) +{ + uschar * scan_filename; int ret; - fits = string_format(scan_filename, sizeof(scan_filename), - CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); - if (!fits) - { - av_failed = TRUE; - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware()]"); - return DEFER; - } - + scan_filename = string_sprintf("%s/scan/%s/%s.eml", + spool_directory, message_id, message_id); ret = malware_internal(listptr, scan_filename, FALSE); if (ret == DEFER) av_failed = TRUE; @@ -116,7 +133,8 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ int -malware_in_file(uschar *eml_filename) { +malware_in_file(uschar *eml_filename) +{ uschar *scan_options[2]; uschar message_id_buf[64]; int ret; @@ -150,6 +168,142 @@ malware_in_file(uschar *eml_filename) { } +static inline int +malware_errlog_defer(const uschar * str) +{ + 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) +{ + return malware_errlog_defer(string_sprintf("%s: %s", scanent->name, str)); +} +static int +m_errlog_defer_3(struct scan * scanent, const uschar * str, + int fd_to_close) +{ + (void) close(fd_to_close); + return m_errlog_defer(scanent, str); +} + +/*************************************************/ + +/* Only used by the Clamav code, which is working from a list of servers and +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) +{ + 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); +} + +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; +} + +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; +} + +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; +} + +static const pcre * +m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr) +{ + 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; +} + /************************************************* * Scan content for malware * *************************************************/ @@ -165,1728 +319,1218 @@ Arguments: Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ -static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) { +static int +malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) +{ int sep = 0; uschar *list = *listptr; uschar *av_scanner_work = av_scanner; uschar *scanner_name; - uschar scanner_name_buffer[16]; uschar *malware_regex; - uschar malware_regex_buffer[64]; uschar malware_regex_default[] = ".+"; unsigned long mbox_size; FILE *mbox_file; - int roffset; const pcre *re; - const uschar *rerror; + uschar * errstr; + struct scan * scanent; + const uschar * scanner_options; + int sock = -1; /* make sure the eml mbox file is spooled up */ - mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL); - if (mbox_file == NULL) { - /* error while spooling */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: error while creating mbox spool file"); - return DEFER; - }; + if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL))) + 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); /* extract the malware regex to match against from the option list */ - if ((malware_regex = string_nextinlist(&list, &sep, - malware_regex_buffer, - sizeof(malware_regex_buffer))) != NULL) { + if (!(malware_regex = string_nextinlist(&list, &sep, NULL, 0))) + return FAIL; /* empty means "don't match anything" */ - /* parse 1st option */ + /* parse 1st option */ if ( (strcmpic(malware_regex,US"false") == 0) || - (Ustrcmp(malware_regex,"0") == 0) ) { - /* explicitly no matching */ - return FAIL; - }; - - /* special cases (match anything except empty) */ - if ( (strcmpic(malware_regex,US"true") == 0) || - (Ustrcmp(malware_regex,"*") == 0) || - (Ustrcmp(malware_regex,"1") == 0) ) { - malware_regex = malware_regex_default; - }; - } - else { - /* empty means "don't match anything" */ - return FAIL; - }; + (Ustrcmp(malware_regex,"0") == 0) ) + return FAIL; /* explicitly no matching */ + + /* special cases (match anything except empty) */ + if ( (strcmpic(malware_regex,US"true") == 0) || + (Ustrcmp(malware_regex,"*") == 0) || + (Ustrcmp(malware_regex,"1") == 0) ) + malware_regex = malware_regex_default; /* Reset sep that is set by previous string_nextinlist() call */ sep = 0; /* compile the regex, see if it works */ - re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (re == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset); - return DEFER; - }; + if (!(re = m_pcre_compile(malware_regex, &errstr))) + return malware_errlog_defer(errstr); /* if av_scanner starts with a dollar, expand it first */ if (*av_scanner == '$') { - av_scanner_work = expand_string(av_scanner); - if (av_scanner_work == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: av_scanner starts with $, but expansion failed: %s", expand_string_message); - return DEFER; - } - else { - debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); - /* disable result caching in this case */ - malware_name = NULL; - malware_ok = 0; - }; + if (!(av_scanner_work = expand_string(av_scanner))) + return malware_errlog_defer( + string_sprintf("av_scanner starts with $, but expansion failed: %s", + expand_string_message)); + + debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); + /* disable result caching in this case */ + malware_name = NULL; + malware_ok = FALSE; } - /* Do not scan twice. */ - if (malware_ok == 0) { + /* Do not scan twice (unless av_scanner is dynamic). */ + if (!malware_ok) { /* find the scanner type from the av_scanner option */ - if ((scanner_name = string_nextinlist(&av_scanner_work, &sep, - scanner_name_buffer, - sizeof(scanner_name_buffer))) == NULL) { - /* no scanner given */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: av_scanner configuration variable is empty"); - return DEFER; - }; - - /* "f-protd" scanner type ----------------------------------------------- */ - if (strcmpic(scanner_name, US"f-protd") == 0) { - uschar *fp_options, *fp_scan_option; - uschar fp_scan_option_buffer[1024]; - uschar fp_options_buffer[1024]; - uschar fp_options_default[] = "localhost 10200-10204"; - uschar hostname[256]; - unsigned int port, portlow, porthigh, connect_ok=0, detected=0, par_count = 0; - struct hostent *he; - struct in_addr in; - int sock; - uschar scanrequest[2048], buf[32768], *strhelper, *strhelper2; - - if ((fp_options = string_nextinlist(&av_scanner_work, &sep, - fp_options_buffer, sizeof(fp_options_buffer))) == NULL) { - /* no options supplied, use default options */ - fp_options = fp_options_default; - }; - - /* extract host and port part */ - if ( sscanf(CS fp_options, "%s %u-%u", hostname, &portlow, &porthigh) != 3 ) { - if ( sscanf(CS fp_options, "%s %u", hostname, &portlow) != 2 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: invalid socket '%s'", fp_options); - return DEFER; - } - porthigh = portlow; - } - - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: failed to lookup host '%s'", hostname); - return DEFER; - } - - in = *(struct in_addr *) he->h_addr_list[0]; - port = portlow; - - - /* Open the f-protd TCP socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } - - /* Try to connect to all portslow-high until connection is established */ - for (port = portlow; !connect_ok && port < porthigh; port++) { - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) >= 0) { - connect_ok = 1; - } - } - - if ( !connect_ok ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: connection to %s, port %u-%u failed (%s)", - inet_ntoa(in), portlow, porthigh, strerror(errno)); - (void)close(sock); - return DEFER; - } - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name); - (void)string_format(scanrequest, 1024, CS"GET %s", eml_filename); - - while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep, - fp_scan_option_buffer, sizeof(fp_scan_option_buffer))) != NULL) { - if ( par_count ) { - Ustrcat(scanrequest, "%20"); - } else { - Ustrcat(scanrequest, "?"); - } - Ustrcat(scanrequest, fp_scan_option); - par_count++; - } - Ustrcat(scanrequest, " HTTP/1.0\r\n\r\n"); - - /* send scan request */ - if (send(sock, &scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: unable to send command to socket (%s)", scanrequest); - return DEFER; - } - - /* We get a lot of empty lines, so we need this hack to check for any data at all */ - while( recv(sock, buf, 1, MSG_PEEK) > 0 ) { - if ( recv_line(sock, buf, 32768) > 0) { - if ( Ustrstr(buf, US"")) ) { - if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { - *strhelper2 = '\0'; - Ustrcpy(malware_name_buffer, strhelper + 6); - } - } else if ( Ustrstr(buf, US"") ) { - malware_name = malware_name_buffer; - } else { - malware_name = NULL; - } - } - } - } - (void)close(sock); - } - /* "drweb" scanner type ----------------------------------------------- */ - /* v0.1 - added support for tcp sockets */ - /* v0.0 - initial release -- support for unix sockets */ - else if (strcmpic(scanner_name,US"drweb") == 0) { - uschar *drweb_options; - uschar drweb_options_buffer[1024]; - uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock"; - struct sockaddr_un server; - int sock, result, ovector[30]; - unsigned int port, fsize; - uschar tmpbuf[1024], *drweb_fbuf; - uschar drweb_match_string[128]; - int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, - drweb_vnum, drweb_slen, drweb_fin = 0x0000; - unsigned long bread; - uschar hostname[256]; - struct hostent *he; - struct in_addr in; - pcre *drweb_re; - - if ((drweb_options = string_nextinlist(&av_scanner_work, &sep, - drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) { - /* no options supplied, use default options */ - drweb_options = drweb_options_default; - }; - - if (*drweb_options != '/') { - - /* extract host and port part */ - if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: invalid socket '%s'", drweb_options); - return DEFER; - } - - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: failed to lookup host '%s'", hostname); - return DEFER; - } - - in = *(struct in_addr *) he->h_addr_list[0]; - - /* Open the drwebd TCP socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } - - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(errno)); - return DEFER; - } - - /* prepare variables */ - drweb_cmd = htonl(DRWEBD_SCAN_CMD); - drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); - - /* calc file size */ - drweb_fd = open(CS eml_filename, O_RDONLY); - if (drweb_fd == -1) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't open spool file %s: %s", - eml_filename, strerror(errno)); - return DEFER; - } - fsize = lseek(drweb_fd, 0, SEEK_END); - if (fsize == -1) { - (void)close(sock); - (void)close(drweb_fd); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't seek spool file %s: %s", - eml_filename, strerror(errno)); - return DEFER; - } - drweb_slen = htonl(fsize); - lseek(drweb_fd, 0, SEEK_SET); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s %u]\n", - scanner_name, hostname, port); - - /* send scan request */ - if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || - (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || - (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || - (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { - (void)close(sock); - (void)close(drweb_fd); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); - return DEFER; - } - - drweb_fbuf = (uschar *) malloc (fsize); - if (!drweb_fbuf) { - (void)close(sock); - (void)close(drweb_fd); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to allocate memory %u for file (%s)", - fsize, eml_filename); - return DEFER; - } - - result = read (drweb_fd, drweb_fbuf, fsize); - if (result == -1) { - (void)close(sock); - (void)close(drweb_fd); - free(drweb_fbuf); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't read spool file %s: %s", - eml_filename, strerror(errno)); - return DEFER; - } - (void)close(drweb_fd); - - /* send file body to socket */ - if (send(sock, drweb_fbuf, fsize, 0) < 0) { - (void)close(sock); - free(drweb_fbuf); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options); - return DEFER; - } - (void)close(drweb_fd); - } - else { - /* open the drwebd UNIX socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't open UNIX socket"); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, drweb_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno); - return DEFER; - } - - /* prepare variables */ - drweb_cmd = htonl(DRWEBD_SCAN_CMD); - drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); - drweb_slen = htonl(Ustrlen(eml_filename)); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n", - scanner_name, drweb_options); - - /* send scan request */ - if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || - (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || - (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)) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); - return DEFER; - } - } - - /* wait for result */ - if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to read return code"); - return DEFER; - } - drweb_rc = ntohl(drweb_rc); - - if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to read the number of viruses"); - return DEFER; - } - drweb_vnum = ntohl(drweb_vnum); - - /* "virus(es) found" if virus number is > 0 */ - if (drweb_vnum) - { - int i; - uschar pre_malware_nb[256]; - - malware_name = malware_name_buffer; - - /* setup default virus name */ - Ustrcpy(malware_name_buffer,"unknown"); - - /* read and concatenate virus names into one string */ - for (i=0;iname) + return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", + scanner_name)); + if (strcmpic(scanner_name, US scanent->name) != 0) + continue; + if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) + scanner_options = scanent->options_default; + if (scanent->conn == MC_NONE) + break; + switch(scanent->conn) { - /* read the size of report */ - if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen))) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: cannot read report size"); - return DEFER; - }; - drweb_slen = ntohl(drweb_slen); - - /* read report body */ - if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: cannot read report string"); - return DEFER; - }; - tmpbuf[drweb_slen] = '\0'; - - /* set up match regex, depends on retcode */ - Ustrcpy(drweb_match_string, "infected\\swith\\s*(.+?)$"); - - drweb_re = pcre_compile( CS drweb_match_string, - PCRE_COPT, - (const char **)&rerror, - &roffset, - NULL ); - - /* try matcher on the line, grab substring */ - result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); - if (result >= 2) { - pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255); - } - /* the first name we just copy to malware_name */ - if (i==0) - Ustrcpy(CS malware_name_buffer, CS pre_malware_nb); - else { - /* concatenate each new virus name to previous */ - int slen = Ustrlen(malware_name_buffer); - if (slen < (slen+Ustrlen(pre_malware_nb))) { - Ustrcat(malware_name_buffer, "/"); - Ustrcat(malware_name_buffer, pre_malware_nb); - } - } + 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; } + if (sock < 0) + return m_errlog_defer(scanent, errstr); + break; } - else { - const char *drweb_s = NULL; - - if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; - if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; - if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout"; - if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command"; - /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED. - * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM, - * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR - * and others are ignored */ - if (drweb_s) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s); - (void)close(sock); - return DEFER; - } - /* no virus found */ - malware_name = NULL; - }; - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - else if (strcmpic(scanner_name,US"aveserver") == 0) { - uschar *kav_options; - uschar kav_options_buffer[1024]; - uschar kav_options_default[] = "/var/run/aveserver"; - uschar buf[32768]; - struct sockaddr_un server; - int sock; - int result; - - if ((kav_options = string_nextinlist(&av_scanner_work, &sep, - kav_options_buffer, - sizeof(kav_options_buffer))) == NULL) { - /* no options supplied, use default options */ - kav_options = kav_options_default; - }; - - /* open the aveserver socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, kav_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to aveserver UNIX socket (%s). errno=%d", kav_options, errno); - return DEFER; - } - - /* read aveserver's greeting and see if it is ready (2xx greeting) */ - recv_line(sock, buf, 32768); - - if (buf[0] != '2') { - /* aveserver is having problems */ - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: aveserver is unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ); - return DEFER; - }; - /* prepare our command */ - (void)string_format(buf, 32768, "SCAN bPQRSTUW %s\r\n", eml_filename); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name); - - /* and send it */ - if (send(sock, buf, Ustrlen(buf), 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options); - return DEFER; - } - - malware_name = NULL; - result = 0; - /* read response lines, find malware name and final response */ - while (recv_line(sock, buf, 32768) > 0) { - debug_printf("aveserver: %s\n", buf); - if (buf[0] == '2') { - break; - } else if (buf[0] == '5') { - /* aveserver is having problems */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to scan file %s (Responded: %s).", - eml_filename, buf); - result = DEFER; - break; - } else if (Ustrncmp(buf,"322",3) == 0) { - uschar *p = Ustrchr(&buf[4],' '); - *p = '\0'; - Ustrcpy(malware_name_buffer,&buf[4]); - malware_name = malware_name_buffer; - }; - } - - /* prepare our command */ - (void)string_format(buf, 32768, "quit\r\n"); - - /* and send it */ - if (send(sock, buf, Ustrlen(buf), 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options); - return DEFER; - } - - /* read aveserver's greeting and see if it is ready (2xx greeting) */ - recv_line(sock, buf, 32768); - - if (buf[0] != '2') { - /* aveserver is having problems */ - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to quit aveserver dialogue (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ); - return DEFER; - }; - - (void)close(sock); + switch (scanent->scancode) { + case M_FPROTD: /* "f-protd" scanner type -------------------------------- */ + { + uschar *fp_scan_option; + unsigned int detected=0, par_count=0; + uschar * scanrequest; + uschar buf[32768], *strhelper, *strhelper2; + uschar * malware_name_internal = NULL; + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name); + scanrequest = string_sprintf("GET %s", eml_filename); + + while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))) { + scanrequest = string_sprintf("%s%s%s", scanrequest, + par_count ? "%20" : "?", fp_scan_option); + par_count++; + } + scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest); + + /* send scan request */ + if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + /* We get a lot of empty lines, so we need this hack to check for any data at all */ + while( recv(sock, buf, 1, MSG_PEEK) > 0 ) { + if ( recv_line(sock, buf, sizeof(buf)) > 0) { + if ( Ustrstr(buf, US"")) ) { + if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { + *strhelper2 = '\0'; + malware_name_internal = string_copy(strhelper+6); + } + } else if ( Ustrstr(buf, US"") + ? malware_name_internal : NULL; + } + } + break; + } /* f-protd */ - if (result == DEFER) return DEFER; - } - /* "fsecure" scanner type ------------------------------------------------- */ - else if (strcmpic(scanner_name,US"fsecure") == 0) { - uschar *fsecure_options; - uschar fsecure_options_buffer[1024]; - uschar fsecure_options_default[] = "/var/run/.fsav"; - struct sockaddr_un server; - int sock, i, j, bread = 0; - uschar file_name[1024]; - uschar av_buffer[1024]; - pcre *fs_inf; - static uschar *cmdoptions[] = { US"CONFIGURE\tARCHIVE\t1\n", - US"CONFIGURE\tTIMEOUT\t0\n", - US"CONFIGURE\tMAXARCH\t5\n", - US"CONFIGURE\tMIME\t1\n" }; - - malware_name = NULL; - if ((fsecure_options = string_nextinlist(&av_scanner_work, &sep, - fsecure_options_buffer, - sizeof(fsecure_options_buffer))) == NULL) { - /* no options supplied, use default options */ - fsecure_options = fsecure_options_default; - }; - - /* open the fsecure socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to open fsecure socket %s (%s)", - fsecure_options, strerror(errno)); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, fsecure_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to fsecure socket %s (%s)", - fsecure_options, strerror(errno)); - return DEFER; - } + case M_DRWEB: /* "drweb" scanner type ----------------------------------- */ + /* v0.1 - added support for tcp sockets */ + /* v0.0 - initial release -- support for unix sockets */ + { + int result; + unsigned int fsize; + uschar * tmpbuf, *drweb_fbuf; + int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, + drweb_vnum, drweb_slen, drweb_fin = 0x0000; + unsigned long bread; + const pcre *drweb_re; + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); + + if (*scanner_options != '/') { + + /* calc file size */ + if ((drweb_fd = open(CS eml_filename, O_RDONLY)) == -1) + return m_errlog_defer_3(scanent, + 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; + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("can't seek spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + drweb_slen = htonl(fsize); + lseek(drweb_fd, 0, SEEK_SET); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s]\n", + scanner_name, scanner_options); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("unable to send commands to socket (%s)", scanner_options), + sock); + } + + if (!(drweb_fbuf = (uschar *) malloc (fsize))) { + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("unable to allocate memory %u for file (%s)", + fsize, eml_filename), + sock); + } + + if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1) { + int err = errno; + (void)close(drweb_fd); + free(drweb_fbuf); + return m_errlog_defer_3(scanent, + string_sprintf("can't read spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + (void)close(drweb_fd); + + /* send file body to socket */ + if (send(sock, drweb_fbuf, fsize, 0) < 0) { + free(drweb_fbuf); + return m_errlog_defer_3(scanent, + 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", + scanner_name, scanner_options); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (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("unable to send commands to socket (%s)", scanner_options), + sock); + } - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, fsecure_options); - - /* pass options */ - memset(av_buffer, 0, sizeof(av_buffer)); - for (i=0; i != 4; i++) { - /* debug_printf("send option \"%s\"",cmdoptions[i]); */ - if (write(sock, cmdoptions[i], Ustrlen(cmdoptions[i])) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write fsecure option %d to %s (%s)", - i, fsecure_options, strerror(errno)); - return DEFER; - }; - - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); - if (bread >0) av_buffer[bread]='\0'; - if (bread < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read fsecure answer %d (%s)", i, strerror(errno)); - return DEFER; - }; - for (j=0;j= 2) { - /* Got it */ - pcre_copy_substring(CS av_buffer, ovector, i, 1, CS malware_name_buffer, 255); - malware_name = malware_name_buffer; - }; - }; - } - while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL); - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - - /* "kavdaemon" scanner type ------------------------------------------------ */ - else if (strcmpic(scanner_name,US"kavdaemon") == 0) { - uschar *kav_options; - uschar kav_options_buffer[1024]; - uschar kav_options_default[] = "/var/run/AvpCtl"; - struct sockaddr_un server; - int sock; - time_t t; - uschar tmpbuf[1024]; - uschar scanrequest[1024]; - uschar kav_match_string[128]; - int kav_rc; - unsigned long kav_reportlen, bread; - pcre *kav_re; - uschar *p; - int fits; - - if ((kav_options = string_nextinlist(&av_scanner_work, &sep, - kav_options_buffer, - sizeof(kav_options_buffer))) == NULL) { - /* no options supplied, use default options */ - kav_options = kav_options_default; - }; - - /* open the kavdaemon socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, kav_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno); - return DEFER; - } + /* wait for result */ + if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) + return m_errlog_defer_3(scanent, + US"unable to read return code", sock); + drweb_rc = ntohl(drweb_rc); + + if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) + return m_errlog_defer_3(scanent, + US"unable to read the number of viruses", sock); + drweb_vnum = ntohl(drweb_vnum); + + /* "virus(es) found" if virus number is > 0 */ + if (drweb_vnum) { + int i; + + /* setup default virus name */ + malware_name = US"unknown"; + + /* set up match regex */ + drweb_re = m_pcre_compile(US"infected\\swith\\s*(.+?)$", &errstr); + + /* read and concatenate virus names into one string */ + for (i=0;i= 2) { + const char * pre_malware_nb; + + 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); + + else /* concatenate each new virus name to previous */ + malware_name = string_append(malware_name, &size, &off, + 2, "/", pre_malware_nb); + + pcre_free_substring(pre_malware_nb); + } + } + } + else { + const char *drweb_s = NULL; + + if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; + if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; + if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout"; + if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command"; + /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED. + * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM, + * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR + * and others are ignored */ + if (drweb_s) + return m_errlog_defer_3(scanent, + string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s), + sock); + + /* no virus found */ + malware_name = NULL; + } + break; + } /* drweb */ - /* get current date and time, build scan request */ - time(&t); - /* pdp note: before the eml_filename parameter, this scanned the - directory; not finding documentation, so we'll strip off the directory. - The side-effect is that the test framework scanning may end up in - scanning more than was requested, but for the normal interface, this is - fine. */ - strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s", localtime(&t)); - fits = string_format(scanrequest, 1024,CS tmpbuf, eml_filename); - if (!fits) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() kavdaemon]"); - } - p = Ustrrchr(scanrequest, '/'); - if (p) - *p = '\0'; + case M_AVES: /* "aveserver" scanner type -------------------------------- */ + { + uschar buf[32768]; + int result; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, kav_options); + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, sizeof(buf)); - /* send scan request */ - if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options); - return DEFER; - } + if (buf[0] != '2') /* aveserver is having problems */ + return m_errlog_defer_3(scanent, + string_sprintf("unavailable (Responded: %s).", + ((buf[0] != 0) ? buf : (uschar *)"nothing") ), + sock); - /* wait for result */ - if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read 2 bytes from kavdaemon socket."); - return DEFER; - } + /* prepare our command */ + (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n", + eml_filename); - /* get errorcode from one nibble */ - if (test_byte_order() == LITTLE_MY_ENDIAN) { - kav_rc = tmpbuf[0] & 0x0F; - } - else { - kav_rc = tmpbuf[1] & 0x0F; - }; - - /* improper kavdaemon configuration */ - if ( (kav_rc == 5) || (kav_rc == 6) ) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files."); - return DEFER; - }; - - if (kav_rc == 1) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: kavdaemon reported 'scanning not completed' (code 1)."); - return DEFER; - }; - - if (kav_rc == 7) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7)."); - return DEFER; - }; - - /* code 8 is not handled, since it is ambigous. It appears mostly on - bounces where part of a file has been cut off */ - - /* "virus found" return codes (2-4) */ - if ((kav_rc > 1) && (kav_rc < 5)) { - int report_flag = 0; - - /* setup default virus name */ - Ustrcpy(malware_name_buffer,"unknown"); - malware_name = malware_name_buffer; - - if (test_byte_order() == LITTLE_MY_ENDIAN) { - report_flag = tmpbuf[1]; - } - else { - report_flag = tmpbuf[0]; - }; - - /* read the report, if available */ - if( report_flag == 1 ) { - /* read report size */ - if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: cannot read report size from kavdaemon"); - return DEFER; - }; - - /* it's possible that avp returns av_buffer[1] == 1 but the - reportsize is 0 (!?) */ - if (kav_reportlen > 0) { - /* set up match regex, depends on retcode */ - if( kav_rc == 3 ) - Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$"); - else - Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$"); - - kav_re = pcre_compile( CS kav_match_string, - PCRE_COPT, - (const char **)&rerror, - &roffset, - NULL ); - - /* read report, linewise */ - while (kav_reportlen > 0) { - int result = 0; - int ovector[30]; - - bread = 0; - while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { - kav_reportlen--; - if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; - bread++; - }; - bread++; - tmpbuf[bread] = '\0'; - - /* try matcher on the line, grab substring */ - result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); - if (result >= 2) { - pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255); - break; - }; - }; - }; - }; - } - else { - /* no virus found */ - malware_name = NULL; - }; + DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name); - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - - - /* "cmdline" scanner type ------------------------------------------------ */ - else if (strcmpic(scanner_name,US"cmdline") == 0) { - uschar *cmdline_scanner; - uschar cmdline_scanner_buffer[1024]; - uschar *cmdline_trigger; - uschar cmdline_trigger_buffer[1024]; - const pcre *cmdline_trigger_re; - uschar *cmdline_regex; - uschar cmdline_regex_buffer[1024]; - const pcre *cmdline_regex_re; - uschar file_name[1024]; - uschar commandline[1024]; - void (*eximsigchld)(int); - void (*eximsigpipe)(int); - FILE *scanner_out = NULL; - FILE *scanner_record = NULL; - uschar linebuffer[32767]; - int trigger = 0; - int result; - int ovector[30]; - uschar *p; - BOOL fits; - - /* find scanner command line */ - if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep, - cmdline_scanner_buffer, - sizeof(cmdline_scanner_buffer))) == NULL) { - /* no command line supplied */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: missing commandline specification for cmdline scanner type."); - return DEFER; - }; - - /* find scanner output trigger */ - if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, - cmdline_trigger_buffer, - sizeof(cmdline_trigger_buffer))) == NULL) { - /* no trigger regex supplied */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: missing trigger specification for cmdline scanner type."); - return DEFER; - }; - - /* precompile trigger regex */ - cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (cmdline_trigger_re == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger, rerror, roffset); - return DEFER; - }; - - /* find scanner name regex */ - if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep, - cmdline_regex_buffer, - sizeof(cmdline_regex_buffer))) == NULL) { - /* no name regex supplied */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: missing virus name regex specification for cmdline scanner type."); - return DEFER; - }; - - /* precompile name regex */ - cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (cmdline_regex_re == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex, rerror, roffset); - return DEFER; - }; - - /* prepare scanner call; despite the naming, file_name holds a directory - name which is documented as the value given to %s. */ - if (Ustrlen(eml_filename) > sizeof(file_name) - 1) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() cmdline]"); - return DEFER; - } - Ustrcpy(file_name, eml_filename); - p = Ustrrchr(file_name, '/'); - if (p) - *p = '\0'; - fits = string_format(commandline, sizeof(commandline), CS cmdline_scanner, file_name); - if (!fits) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "cmdline scanner command-line does not fit in buffer"); - return DEFER; - } - - /* redirect STDERR too */ - if (Ustrlen(commandline) + 5 > sizeof(commandline)) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "cmdline scanner command-line does not fit in buffer (STDERR redirect)"); - return DEFER; - } - Ustrcat(commandline," 2>&1"); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline); - - /* store exims signal handlers */ - eximsigchld = signal(SIGCHLD,SIG_DFL); - eximsigpipe = signal(SIGPIPE,SIG_DFL); - - scanner_out = popen(CS commandline,"r"); - if (scanner_out == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno)); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return DEFER; - }; - - (void)string_format(file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id); - scanner_record = modefopen(file_name,"wb",SPOOL_MODE); - - if (scanner_record == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno)); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return DEFER; - }; - - /* look for trigger while recording output */ - while(fgets(CS linebuffer,32767,scanner_out) != NULL) { - if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { - /* short write */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: short write on scanner output file (%s).", file_name); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return DEFER; - }; - /* try trigger match */ - if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) - trigger = 1; - }; - - (void)fclose(scanner_record); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - - if (trigger) { - /* setup default virus name */ - Ustrcpy(malware_name_buffer,"unknown"); - malware_name = malware_name_buffer; - - /* re-open the scanner output file, look for name match */ - scanner_record = fopen(CS file_name,"rb"); - while(fgets(CS linebuffer,32767,scanner_record) != NULL) { - /* try match */ - result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30); - if (result >= 2) { - pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255); - }; - }; - (void)fclose(scanner_record); - } - else { - /* no virus found */ - malware_name = NULL; - }; - } - /* ----------------------------------------------------------------------- */ - - - /* "sophie" scanner type ------------------------------------------------- */ - else if (strcmpic(scanner_name,US"sophie") == 0) { - uschar *sophie_options; - uschar sophie_options_buffer[1024]; - uschar sophie_options_default[] = "/var/run/sophie"; - int bread = 0; - struct sockaddr_un server; - int sock, len; - uschar *p; - uschar file_name[1024]; - uschar av_buffer[1024]; - - if ((sophie_options = string_nextinlist(&av_scanner_work, &sep, - sophie_options_buffer, - sizeof(sophie_options_buffer))) == NULL) { - /* no options supplied, use default options */ - sophie_options = sophie_options_default; - } + /* and send it */ + if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) + return m_errlog_defer(scanent, errstr); - /* open the sophie socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, sophie_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno); - return DEFER; - } + malware_name = NULL; + result = 0; + /* read response lines, find malware name and final response */ + while (recv_line(sock, buf, sizeof(buf)) > 0) { + debug_printf("aveserver: %s\n", buf); + if (buf[0] == '2') + break; + if (buf[0] == '5') { /* aveserver is having problems */ + result = m_errlog_defer(scanent, + string_sprintf("unable to scan file %s (Responded: %s).", + eml_filename, buf)); + break; + } else if (Ustrncmp(buf,"322",3) == 0) { + uschar *p = Ustrchr(&buf[4],' '); + *p = '\0'; + malware_name = string_copy(&buf[4]); + } + } - /* pass the scan directory to sophie */ - len = Ustrlen(eml_filename) + 1; - if (len > sizeof(file_name)) - { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() sophie]"); - return DEFER; - } - memcpy(file_name, eml_filename, len); - p = Ustrrchr(file_name, '/'); - if (p) - *p = '\0'; + /* and send it */ + if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0) + return m_errlog_defer(scanent, errstr); - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, sophie_options); + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, sizeof(buf)); - if ( write(sock, file_name, Ustrlen(file_name)) < 0 - || write(sock, "\n", 1) != 1 - ) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options); - return DEFER; - } + if (buf[0] != '2') /* aveserver is having problems */ + return m_errlog_defer_3(scanent, + string_sprintf("unable to quit dialogue (Responded: %s).", + ((buf[0] != 0) ? buf : (uschar *)"nothing") ), + sock); - /* wait for result */ - memset(av_buffer, 0, sizeof(av_buffer)); - if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options); - return DEFER; - } + if (result == DEFER) { + (void)close(sock); + return DEFER; + } + break; + } /* aveserver */ - (void)close(sock); + case M_FSEC: /* "fsecure" scanner type ---------------------------------- */ + { + 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", + US"CONFIGURE\tMIME\t1\n" }; - /* infected ? */ - if (av_buffer[0] == '1') { - if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; - Ustrcpy(malware_name_buffer,&av_buffer[2]); - malware_name = malware_name_buffer; - } - else if (!strncmp(CS av_buffer, "-1", 2)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: malware acl condition: sophie reported error"); - return DEFER; - } - else { - /* all ok, no virus */ - malware_name = NULL; - } - } - /* ----------------------------------------------------------------------- */ - - - /* "clamd" scanner type ------------------------------------------------- */ - /* This code was originally contributed by David Saez */ - /* There are three scanning methods available to us: - * (1) Use the SCAN command, pointing to a file in the filesystem - * (2) Use the STREAM command, send the data on a separate port - * (3) Use the zINSTREAM command, send the data inline - * The zINSTREAM command was introduced with ClamAV 0.95, which marked - * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095 - * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that - * the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless - * WITH_OLD_CLAMAV_STREAM is defined. - * See Exim bug 926 for details. */ - else if (strcmpic(scanner_name,US"clamd") == 0) { - uschar *clamd_options = NULL; - uschar clamd_options_buffer[1024]; - uschar clamd_options_default[] = "/tmp/clamd"; - uschar *p, *vname, *result_tag, *response_end; - struct sockaddr_un server; - int sock,bread=0; - unsigned int port; - uschar file_name[1024]; - uschar av_buffer[1024]; - uschar *hostname = ""; - struct hostent *he; - struct in_addr in; - uschar *clamav_fbuf; - int clam_fd, result; - unsigned int fsize; - BOOL use_scan_command = FALSE, fits; - clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; - int current_server; - int num_servers = 0; -#ifdef WITH_OLD_CLAMAV_STREAM - uschar av_buffer2[1024]; - int sockData; -#else - uint32_t send_size, send_final_zeroblock; -#endif + malware_name = NULL; - if ((clamd_options = string_nextinlist(&av_scanner_work, &sep, - clamd_options_buffer, - sizeof(clamd_options_buffer))) == NULL) { - /* no options supplied, use default options */ - clamd_options = clamd_options_default; - } + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); + + /* pass options */ + memset(av_buffer, 0, sizeof(av_buffer)); + for (i=0; i != nelements(cmdopt); i++) { + + if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); + if (bread >0) av_buffer[bread]='\0'; + if (bread < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read answer %d (%s)", i, strerror(errno)), + sock); + for (j=0;j%s:%s", CS tmpbuf, eml_filename); + p = Ustrrchr(scanrequest, '/'); + if (p) + *p = '\0'; + + DEBUG(D_acl) debug_printf("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); + + /* wait for result */ + if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) + return m_errlog_defer_3(scanent, + US"unable to read 2 bytes from socket.", sock); + + /* get errorcode from one nibble */ + kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F; + switch(kav_rc) + { + case 5: case 6: /* improper kavdaemon configuration */ + return m_errlog_defer_3(scanent, + US"please reconfigure kavdaemon to NOT disinfect or remove infected files.", + sock); + case 1: + return m_errlog_defer_3(scanent, + US"reported 'scanning not completed' (code 1).", sock); + case 7: + return m_errlog_defer_3(scanent, + US"reported 'kavdaemon damaged' (code 7).", sock); + } - /* 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 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: invalid address '%s'", address); - continue; - } + /* code 8 is not handled, since it is ambigous. It appears mostly on + bounces where part of a file has been cut off */ + + /* "virus found" return codes (2-4) */ + if ((kav_rc > 1) && (kav_rc < 5)) { + int report_flag = 0; + + /* setup default virus name */ + malware_name = US"unknown"; + + report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ]; + + /* read the report, if available */ + if( report_flag == 1 ) { + /* read report size */ + if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) + return m_errlog_defer_3(scanent, + US"cannot read report size", sock); + + /* it's possible that avp returns av_buffer[1] == 1 but the + reportsize is 0 (!?) */ + 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 ); + + /* read report, linewise */ + while (kav_reportlen > 0) { + bread = 0; + while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { + kav_reportlen--; + if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; + bread++; + } + bread++; + tmpbuf[bread] = '\0'; + + /* try matcher on the line, grab substring */ + if ((malware_name = m_pcre_exec(kav_re, tmpbuf))) + break; + } + } + } + } + else /* no virus found */ + malware_name = NULL; - clamd_address_vector[num_servers] = this_clamd; - num_servers++; - if (num_servers >= MAX_CLAMD_SERVERS) { - log_write(0, LOG_MAIN|LOG_PANIC, - "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); - - /* check if we have at least one server */ - if (!num_servers) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: no useable clamd server addresses in malware configuration option."); - return DEFER; - } + break; } - /* See the discussion of response formats below to see why we really don't - like colons in filenames when passing filenames to ClamAV. */ - if (use_scan_command && Ustrchr(eml_filename, ':')) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: local/SCAN mode incompatible with" \ - " : in path to email filename [%s]", eml_filename); - return DEFER; - } + case M_CMDL: /* "cmdline" scanner type ---------------------------------- */ + { + const uschar *cmdline_scanner = scanner_options; + const pcre *cmdline_trigger_re; + const pcre *cmdline_regex_re; + uschar * file_name; + uschar * commandline; + void (*eximsigchld)(int); + void (*eximsigpipe)(int); + FILE *scanner_out = NULL; + FILE *scanner_record = NULL; + uschar linebuffer[32767]; + int trigger = 0; + uschar *p; + + if (!cmdline_scanner) + return m_errlog_defer(scanent, 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); + + /* 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); + + /* prepare scanner call; despite the naming, file_name holds a directory + name which is documented as the value given to %s. */ + + file_name = string_copy(eml_filename); + p = Ustrrchr(file_name, '/'); + if (p) + *p = '\0'; + commandline = string_sprintf(CS cmdline_scanner, file_name); + + /* redirect STDERR too */ + commandline = string_sprintf("%s 2>&1", commandline); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline); + + /* store exims signal handlers */ + eximsigchld = signal(SIGCHLD,SIG_DFL); + eximsigpipe = signal(SIGPIPE,SIG_DFL); + + if (!(scanner_out = popen(CS commandline,"r"))) { + int err = errno; + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, + string_sprintf("call (%s) failed: %s.", commandline, strerror(err))); + } - /* We have some network servers specified */ - if (num_servers) { - - /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd - * only supports AF_INET, but we should probably be looking to the - * future and rewriting this to be protocol-independent anyway. */ - - while ( num_servers > 0 ) { - /* Randomly pick a server to start with */ - current_server = random_number( num_servers ); - - debug_printf("trying server name %s, port %u\n", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->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) */ - if((he = gethostbyname(CS clamd_address_vector[current_server]->tcp_addr)) - == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: failed to lookup host '%s'", - clamd_address_vector[current_server]->tcp_addr - ); - goto try_next_server; - } + file_name = string_sprintf("%s/scan/%s/%s_scanner_output", + spool_directory, message_id, message_id); + scanner_record = modefopen(file_name, "wb", SPOOL_MODE); + + if (scanner_record == NULL) { + int err = errno; + (void) pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, + string_sprintf("opening scanner output file (%s) failed: %s.", + file_name, strerror(err))); + } - in = *(struct in_addr *) he->h_addr_list[0]; + /* look for trigger while recording output */ + while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out)) { + 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("short write on scanner output file (%s).", file_name)); + } + /* try trigger match */ + if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) + trigger = 1; + } - /* Open the ClamAV Socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - goto try_next_server; - } + (void)fclose(scanner_record); + sep = pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + if (sep != 0) + if (sep == -1) + return m_errlog_defer(scanent, + string_sprintf("running scanner failed: %s", strerror(sep))); + else + return m_errlog_defer(scanent, + string_sprintf("scanner returned error code: %d", sep)); + + if (trigger) { + uschar * s; + /* setup default virus name */ + malware_name = US"unknown"; + + /* re-open the scanner output file, look for name match */ + scanner_record = fopen(CS file_name, "rb"); + while(fgets(CS linebuffer, sizeof(linebuffer), scanner_record)) { + /* try match */ + if ((s = m_pcre_exec(cmdline_regex_re, linebuffer))) + malware_name = s; + } + (void)fclose(scanner_record); + } + else /* no virus found */ + malware_name = NULL; + break; + } /* cmdline */ - if (ip_connect( sock, - AF_INET, - (uschar*)inet_ntoa(in), - clamd_address_vector[current_server]->tcp_port, - 5 ) > -1) { - /* Connection successfully established with a server */ - hostname = clamd_address_vector[current_server]->tcp_addr; - break; - } else { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: connection to %s, port %u failed (%s)", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port, - strerror(errno)); - - (void)close(sock); - } + case M_SOPHIE: /* "sophie" scanner type --------------------------------- */ + { + int bread = 0; + uschar *p; + uschar * file_name; + uschar av_buffer[1024]; + + /* pass the scan directory to sophie */ + file_name = string_copy(eml_filename); + if ((p = Ustrrchr(file_name, '/'))) + *p = '\0'; + + DEBUG(D_acl) debug_printf("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, + 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), MALWARE_TIMEOUT)) > 0)) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read from UNIX socket (%s)", scanner_options), + sock); + + /* infected ? */ + if (av_buffer[0] == '1') { + uschar * s = Ustrchr(av_buffer, '\n'); + if (s) + *s = '\0'; + 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); + else /* all ok, no virus */ + malware_name = NULL; -try_next_server: - /* Remove the server from the list. XXX We should free the memory */ - num_servers--; - int i; - for( i = current_server; i < num_servers; i++ ) - clamd_address_vector[i] = clamd_address_vector[i+1]; - } - - if ( num_servers == 0 ) { - log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: all clamd servers failed"); - return DEFER; - } - } else { - /* open the local socket */ - if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } - - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, clamd_options); - - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)", - clamd_options, strerror(errno) ); - return DEFER; - } + break; } - /* have socket in variable "sock"; command to use is semi-independent of - * the socket protocol. We use SCAN if is local (either Unix/local - * domain socket, or explicitly told local) else we stream the data. - * How we stream the data depends upon how we were built. */ - - if (!use_scan_command) { - -#ifdef WITH_OLD_CLAMAV_STREAM - /* "STREAM\n" command, get back a "PORT \n" response, send data to - * 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("Malware scan: issuing %s old-style remote scan (PORT)\n", - scanner_name); - - /* Pass the string to ClamAV (7 = "STREAM\n") */ - if (send(sock, "STREAM\n", 7, 0) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - memset(av_buffer2, 0, sizeof(av_buffer2)); - bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT); - - if (bread < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to read PORT from socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - - if (bread == sizeof(av_buffer)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: buffer too small"); - (void)close(sock); - return DEFER; - } - - if (!(*av_buffer2)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned null"); - (void)close(sock); - return DEFER; - } - - av_buffer2[bread] = '\0'; - if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: Expected port information from clamd, got '%s'", av_buffer2); - (void)close(sock); - return DEFER; - }; - - if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - - if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(errno)); - (void)close(sockData); (void)close(sock); - return DEFER; - } - -#define CLOSE_SOCKDATA (void)close(sockData) -#else /* WITH_OLD_CLAMAV_STREAM not defined */ - /* New protocol: "zINSTREAM\n" followed by a sequence of - chunks, a 4-byte number (network order), terminated by a zero-length - chunk. */ - - DEBUG(D_acl) debug_printf("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) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to send zINSTREAM to socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - -#define CLOSE_SOCKDATA /**/ -#endif - - /* calc file size */ - clam_fd = open(CS eml_filename, O_RDONLY); - if (clam_fd == -1) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: can't open spool file %s: %s", - eml_filename, strerror(errno)); - CLOSE_SOCKDATA; (void)close(sock); - return DEFER; - } - fsize = lseek(clam_fd, 0, SEEK_END); - if (fsize == -1) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: can't seek spool file %s: %s", - eml_filename, strerror(errno)); - CLOSE_SOCKDATA; (void)close(sock); - return DEFER; - } - lseek(clam_fd, 0, SEEK_SET); - - clamav_fbuf = (uschar *) malloc (fsize); - if (!clamav_fbuf) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to allocate memory %u for file (%s)", - fsize, eml_filename); - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - return DEFER; - } - - result = read (clam_fd, clamav_fbuf, fsize); - if (result == -1) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: can't read spool file %s: %s", - eml_filename, strerror(errno)); - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - free(clamav_fbuf); - return DEFER; - } - (void)close(clam_fd); - - /* send file body to socket */ -#ifdef WITH_OLD_CLAMAV_STREAM - if (send(sockData, clamav_fbuf, fsize, 0) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port); - CLOSE_SOCKDATA; (void)close(sock); - free(clamav_fbuf); - return DEFER; - } -#else - send_size = htonl(fsize); - send_final_zeroblock = 0; - if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || - (send(sock, clamav_fbuf, fsize, 0) < 0) || - (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port); - (void)close(sock); - free(clamav_fbuf); - return DEFER; - } -#endif + case M_CLAMD: /* "clamd" scanner type ----------------------------------- */ + { + /* This code was originally contributed by David Saez */ + /* There are three scanning methods available to us: + * (1) Use the SCAN command, pointing to a file in the filesystem + * (2) Use the STREAM command, send the data on a separate port + * (3) Use the zINSTREAM command, send the data inline + * The zINSTREAM command was introduced with ClamAV 0.95, which marked + * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095 + * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that + * the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless + * WITH_OLD_CLAMAV_STREAM is defined. + * See Exim bug 926 for details. */ + + uschar *p, *vname, *result_tag, *response_end; + int bread=0; + unsigned int port; + uschar * file_name; + uschar av_buffer[1024]; + uschar *hostname = US""; + host_item connhost; + uschar *clamav_fbuf; + int clam_fd, result; + unsigned int fsize; + BOOL use_scan_command = FALSE; + clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; + int current_server; + int num_servers = 0; + #ifdef WITH_OLD_CLAMAV_STREAM + uschar av_buffer2[1024]; + int sockData; + #else + uint32_t send_size, send_final_zeroblock; + #endif + + if (*scanner_options == '/') + /* Local file; so we def want to use_scan_command and don't want to try + * passing IP/port combinations */ + use_scan_command = TRUE; + 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; + + /* The 'local' option means use the SCAN command over the network + * socket (ie common file storage in use) */ + if (strcmpic(address,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)); + + /* 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 ) { + (void) m_errlog_defer(scanent, + string_sprintf("invalid address '%s'", address)); + continue; + } + + clamd_address_vector[num_servers] = this_clamd; + num_servers++; + if (num_servers >= MAX_CLAMD_SERVERS) { + (void) m_errlog_defer(scanent, + 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); + + /* check if we have at least one server */ + if (!num_servers) + return m_errlog_defer(scanent, + US"no useable server addresses in malware configuration option."); + } - free(clamav_fbuf); - - CLOSE_SOCKDATA; -#undef CLOSE_SOCKDATA - - } else { /* use scan command */ - /* Send a SCAN command pointing to a filename; then in the then in the - * scan-method-neutral part, read the response back */ - -/* ================================================================= */ - - /* Prior to the reworking post-Exim-4.72, this scanned a directory, - which dates to when ClamAV needed us to break apart the email into the - MIME parts (eg, with the now deprecated demime condition coming first). - Some time back, ClamAV gained the ability to deconstruct the emails, so - doing this would actually have resulted in the mail attachments being - 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) */ - fits = string_format(file_name, sizeof(file_name), "SCAN %s\n", - eml_filename); - if (!fits) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() clamd]"); - } - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n", - scanner_name, clamd_options); - - if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", - strerror(errno)); - return DEFER; - } - - /* Do not shut down the socket for writing; a user report noted that - * clamd 0.70 does not react well to this. */ - } - /* Commands have been sent, no matter which scan method or connection - * type we're using; now just read the result, independent of method. */ - - /* Read the result */ - memset(av_buffer, 0, sizeof(av_buffer)); - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); - (void)close(sock); - - if (!(bread > 0)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to read from socket (%s)", - strerror(errno)); - return DEFER; - } + /* 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, + string_sprintf("local/SCAN mode incompatible with" \ + " : in path to email filename [%s]", eml_filename)); + + /* We have some network servers specified */ + if (num_servers) { + + /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd + * only supports AF_INET, but we should probably be looking to the + * future and rewriting this to be protocol-independent anyway. */ + + while ( num_servers > 0 ) { + /* Randomly pick a server to start with */ + current_server = random_number( num_servers ); + + debug_printf("trying server name %s, port %u\n", + clamd_address_vector[current_server]->tcp_addr, + clamd_address_vector[current_server]->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) { + /* Connection successfully established with a server */ + hostname = clamd_address_vector[current_server]->tcp_addr; + break; + } + + (void) m_errlog_defer(scanent, errstr); + + /* Remove the server from the list. XXX We should free the memory */ + num_servers--; + int i; + for( i = current_server; i < num_servers; i++ ) + clamd_address_vector[i] = clamd_address_vector[i+1]; + } + + if ( num_servers == 0 ) + return m_errlog_defer(scanent, US"all servers failed"); + + } else { + if ((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return m_errlog_defer(scanent, errstr); + } - if (bread == sizeof(av_buffer)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: buffer too small"); - return DEFER; + /* have socket in variable "sock"; command to use is semi-independent of + * the socket protocol. We use SCAN if is local (either Unix/local + * domain socket, or explicitly told local) else we stream the data. + * How we stream the data depends upon how we were built. */ + + if (!use_scan_command) { + + #ifdef WITH_OLD_CLAMAV_STREAM + /* "STREAM\n" command, get back a "PORT \n" response, send data to + * 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("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); + + memset(av_buffer2, 0, sizeof(av_buffer2)); + bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT); + + if (bread < 0) + return m_errlog_defer_3(scanent, + 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); + + if (!(*av_buffer2)) + return m_errlog_defer_3(scanent, "ClamAV returned null", sock); + + av_buffer2[bread] = '\0'; + if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) + return m_errlog_defer_3(scanent, + 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); + + #define CLOSE_SOCKDATA (void)close(sockData) + #else /* WITH_OLD_CLAMAV_STREAM not defined */ + /* New protocol: "zINSTREAM\n" followed by a sequence of + chunks, a 4-byte number (network order), terminated by a zero-length + chunk. */ + + DEBUG(D_acl) debug_printf("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); + + #define CLOSE_SOCKDATA /**/ + #endif + + /* calc file size */ + if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0) { + int err = errno; + CLOSE_SOCKDATA; + return m_errlog_defer_3(scanent, + 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; + CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("can't seek spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + lseek(clam_fd, 0, SEEK_SET); + + if (!(clamav_fbuf = (uschar *) malloc (fsize))) { + CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("unable to allocate memory %u for file (%s)", + fsize, eml_filename), + sock); + } + + if ((result = read(clam_fd, clamav_fbuf, fsize)) < 0) { + int err = errno; + free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("can't read spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + (void)close(clam_fd); + + /* send file body to socket */ + #ifdef WITH_OLD_CLAMAV_STREAM + if (send(sockData, clamav_fbuf, fsize, 0) < 0) { + free(clamav_fbuf); CLOSE_SOCKDATA; + return m_errlog_defer_3(scanent, + string_sprintf("unable to send file body to socket (%s:%u)", + hostname, port), + sock); + } + #else + send_size = htonl(fsize); + send_final_zeroblock = 0; + if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || + (send(sock, clamav_fbuf, fsize, 0) < 0) || + (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) + { + free(clamav_fbuf); + return m_errlog_defer_3(scanent, + string_sprintf("unable to send file body to socket (%s:%u)", + hostname, port), + sock); + } + #endif + + free(clamav_fbuf); + + CLOSE_SOCKDATA; + #undef CLOSE_SOCKDATA + + } else { /* use scan command */ + /* Send a SCAN command pointing to a filename; then in the then in the + * scan-method-neutral part, read the response back */ + + /* ================================================================= */ + + /* Prior to the reworking post-Exim-4.72, this scanned a directory, + which dates to when ClamAV needed us to break apart the email into the + MIME parts (eg, with the now deprecated demime condition coming first). + Some time back, ClamAV gained the ability to deconstruct the emails, so + doing this would actually have resulted in the mail attachments being + 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); + + DEBUG(D_acl) debug_printf("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); + + /* Do not shut down the socket for writing; a user report noted that + * clamd 0.70 does not react well to this. */ + } + /* Commands have been sent, no matter which scan method or connection + * type we're using; now just read the result, independent of method. */ + + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); + (void)close(sock); + sock = -1; + + if (!(bread > 0)) + return m_errlog_defer(scanent, + string_sprintf("unable to read from socket (%s)", strerror(errno))); + + if (bread == sizeof(av_buffer)) + return m_errlog_defer(scanent, 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. + In the basic mode, the response is of the form: + infected: -> ": FOUND" + not-infected: -> ": OK" + error: -> ": ERROR + If the ExtendedDetectionInfo option has been turned on, then we get: + ": (:) FOUND" + for the infected case. Compare: + /tmp/eicar.com: Eicar-Test-Signature FOUND + /tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND + + In the streaming case, clamd uses the filename "stream" which you should + be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The + client app will replace "stream" with the original filename before returning + results to stdout, but the trace shows the data). + + We will assume that the pathname passed to clamd from Exim does not contain + a colon. We will have whined loudly above if the eml_filename does (and we're + passing a filename to clamd). */ + + if (!(*av_buffer)) + return m_errlog_defer(scanent, 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 + this below, so double-check) */ + p = av_buffer + Ustrlen(av_buffer) - 1; + if (*p == '\n') *p = '\0'; + + DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); + + while (isspace(*--p) && (p > av_buffer)) + *p = '\0'; + if (*p) ++p; + response_end = p; + + /* colon in returned output? */ + if((p = Ustrchr(av_buffer,':')) == NULL) + return m_errlog_defer(scanent, + string_sprintf("ClamAV returned malformed result (missing colon): %s", + av_buffer)); + + /* strip filename */ + while (*p && isspace(*++p)) /**/; + vname = p; + + /* It would be bad to encounter a virus with "FOUND" in part of the name, + but we should at least be resistant to it. */ + p = Ustrrchr(vname, ' '); + result_tag = p ? p+1 : vname; + + if (Ustrcmp(result_tag, "FOUND") == 0) { + /* p should still be the whitespace before the result_tag */ + while (isspace(*p)) --p; + *++p = '\0'; + /* Strip off the extended information too, which will be in parens + after the virus name, with no intervening whitespace. */ + if (*--p == ')') { + /* "(hash:size)", so previous '(' will do; if not found, we have + a curious virus name, but not an error. */ + p = Ustrrchr(vname, '('); + if (p) + *p = '\0'; + } + malware_name = string_copy(vname); + DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); + + } else if (Ustrcmp(result_tag, "ERROR") == 0) + return m_errlog_defer(scanent, + 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"); + + } else + return m_errlog_defer(scanent, + string_sprintf("unparseable response from ClamAV: {%s}", av_buffer)); + + break; + } /* clamd */ + + case M_SOCK: /* "sock" scanner type ------------------------------------- */ + /* This code was derived by Martin Poole from the clamd code contributed + by David Saez and the cmdline code + */ + { + int bread; + uschar * commandline; + uschar av_buffer[1024]; + uschar * linebuffer; + uschar * sockline_scanner; + uschar sockline_scanner_default[] = "%s\n"; + const pcre *sockline_trig_re; + const pcre *sockline_name_re; + + /* find scanner command line */ + if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))) + { /* check for no expansions apart from one %s */ + char * s = index(CS sockline_scanner, '%'); + if (s++) + if ((*s != 's' && *s != '%') || index(s+1, '%')) + return m_errlog_defer_3(scanent, + US"unsafe sock scanner call spec", sock); + } + else + sockline_scanner = sockline_scanner_default; + + /* 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); + + /* 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); + + /* 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); + + + /* Pass the command string to the socket */ + if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = read(sock, av_buffer, sizeof(av_buffer)); + + if (!(bread > 0)) + return m_errlog_defer_3(scanent, + 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); + linebuffer = string_copy(av_buffer); + + /* 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"; + } + else /* no virus found */ + malware_name = NULL; + break; } - /* Check the result. ClamAV returns one of two result formats. - In the basic mode, the response is of the form: - infected: -> ": FOUND" - not-infected: -> ": OK" - error: -> ": ERROR - If the ExtendedDetectionInfo option has been turned on, then we get: - ": (:) FOUND" - for the infected case. Compare: -/tmp/eicar.com: Eicar-Test-Signature FOUND -/tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND - - In the streaming case, clamd uses the filename "stream" which you should - be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The - client app will replace "stream" with the original filename before returning - results to stdout, but the trace shows the data). - - We will assume that the pathname passed to clamd from Exim does not contain - a colon. We will have whined loudly above if the eml_filename does (and we're - passing a filename to clamd). */ - - if (!(*av_buffer)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned null"); - return DEFER; - } + case M_MKSD: /* "mksd" scanner type ------------------------------------- */ + { + char *mksd_options_end; + int mksd_maxproc = 1; /* default, if no option supplied */ + int sock; + int retval; + + if (scanner_options) { + mksd_maxproc = (int)strtol(CS scanner_options, &mksd_options_end, 10); + if ( *scanner_options == '\0' + || *mksd_options_end != '\0' + || mksd_maxproc < 1 + || mksd_maxproc > 32 + ) + return m_errlog_defer(scanent, + string_sprintf("invalid option '%s'", scanner_options)); + } - /* strip newline at the end (won't be present for zINSTREAM) - (also any trailing whitespace, which shouldn't exist, but we depend upon - this below, so double-check) */ - p = av_buffer + Ustrlen(av_buffer) - 1; - if (*p == '\n') *p = '\0'; - - DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); - - while (isspace(*--p) && (p > av_buffer)) - *p = '\0'; - if (*p) ++p; - response_end = p; - - /* colon in returned output? */ - if((p = Ustrchr(av_buffer,':')) == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned malformed result (missing colon): %s", - av_buffer); - return DEFER; - } + if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + return m_errlog_defer(scanent, errstr); - /* strip filename */ - while (*p && isspace(*++p)) /**/; - vname = p; - - /* It would be bad to encounter a virus with "FOUND" in part of the name, - but we should at least be resistant to it. */ - p = Ustrrchr(vname, ' '); - if (p) - result_tag = p + 1; - else - result_tag = vname; - - if (Ustrcmp(result_tag, "FOUND") == 0) { - /* p should still be the whitespace before the result_tag */ - while (isspace(*p)) --p; - *++p = '\0'; - /* Strip off the extended information too, which will be in parens - after the virus name, with no intervening whitespace. */ - if (*--p == ')') { - /* "(hash:size)", so previous '(' will do; if not found, we have - a curious virus name, but not an error. */ - p = Ustrrchr(vname, '('); - if (p) - *p = '\0'; - } - Ustrncpy(malware_name_buffer, vname, sizeof(malware_name_buffer)-1); - malware_name = malware_name_buffer; - DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); - - } else if (Ustrcmp(result_tag, "ERROR") == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned: %s", - av_buffer); - return DEFER; - - } else if (Ustrcmp(result_tag, "OK") == 0) { - /* Everything should be OK */ malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware not found\n"); - } else { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unparseable response from ClamAV: {%s}", - av_buffer); - return DEFER; - } + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); - } /* clamd */ - - /* ----------------------------------------------------------------------- */ - - - /* "mksd" scanner type --------------------------------------------------- */ - else if (strcmpic(scanner_name,US"mksd") == 0) { - uschar *mksd_options; - char *mksd_options_end; - uschar mksd_options_buffer[32]; - int mksd_maxproc = 1; /* default, if no option supplied */ - struct sockaddr_un server; - int sock; - int retval; - - if ((mksd_options = string_nextinlist(&av_scanner_work, &sep, - mksd_options_buffer, - sizeof(mksd_options_buffer))) != NULL) { - mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10); - if ((*mksd_options == '\0') || (*mksd_options_end != '\0') || - (mksd_maxproc < 1) || (mksd_maxproc > 32)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: mksd: invalid option '%s'", mksd_options); - return DEFER; - } - } - - /* open the mksd socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, "/var/run/mksd/socket"); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno); - return DEFER; + if ((retval = mksd_scan_packed(scanent, sock, eml_filename)) != OK) { + close (sock); + return retval; + } + break; } - - malware_name = NULL; - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); - - retval = mksd_scan_packed(sock, eml_filename); - - if (retval != OK) - return retval; } - /* ----------------------------------------------------------------------- */ - /* "unknown" scanner type ------------------------------------------------- */ - else { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware condition: unknown scanner type '%s'", scanner_name); - return DEFER; - }; - /* ----------------------------------------------------------------------- */ - - /* set "been here, done that" marker */ - malware_ok = 1; - }; + if (sock >= 0) + (void) close (sock); + malware_ok = TRUE; /* set "been here, done that" marker */ + } /* match virus name against pattern (caseless ------->----------v) */ - if ( (malware_name != NULL) && - (regex_match_and_setup(re, malware_name, 0, -1)) ) { + if ( malware_name && (regex_match_and_setup(re, malware_name, 0, -1)) ) { DEBUG(D_acl) debug_printf("Matched regex to malware [%s] [%s]\n", malware_regex, malware_name); return OK; } - else { + else return FAIL; - }; } /* simple wrapper for reading lines from sockets */ -int recv_line(int sock, uschar *buffer, int size) { +int +recv_line(int sock, uschar *buffer, int size) +{ uschar *p = buffer; memset(buffer,0,size); @@ -1895,7 +1539,7 @@ int recv_line(int sock, uschar *buffer, int size) { if ((p-buffer) > (size-2)) break; if (*p == '\n') break; if (*p != '\r') p++; - }; + } *p = '\0'; return (p-buffer); @@ -1906,7 +1550,8 @@ int recv_line(int sock, uschar *buffer, int size) { #include -static int mksd_writev (int sock, struct iovec *iov, int iovcnt) +static inline int +mksd_writev (int sock, struct iovec *iov, int iovcnt) { int i; @@ -1915,9 +1560,7 @@ static int mksd_writev (int sock, struct iovec *iov, int iovcnt) i = writev (sock, iov, iovcnt); while ((i < 0) && (errno == EINTR)); if (i <= 0) { - close (sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)"); + (void) malware_errlog_defer(US"unable to write to mksd UNIX socket (/var/run/mksd/socket)"); return -1; } @@ -1935,25 +1578,22 @@ static int mksd_writev (int sock, struct iovec *iov, int iovcnt) } } -static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) +static inline int +mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) { int offset = 0; int i; do { if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) { - close (sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)"); + (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)"); return -1; } offset += i; /* offset == av_buffer_size -> buffer full */ if (offset == av_buffer_size) { - close (sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: malformed reply received from mksd"); + (void) malware_errlog_defer(US"malformed reply received from mksd"); return -1; } } while (av_buffer[offset-1] != '\n'); @@ -1962,41 +1602,39 @@ static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) return offset; } -static int mksd_parse_line (char *line) +static inline int +mksd_parse_line(struct scan * scanent, char *line) { char *p; switch (*line) { - case 'O': - /* OK */ + case 'O': /* OK */ return OK; + case 'E': - case 'A': - /* ERR */ + case 'A': /* ERR */ if ((p = strchr (line, '\n')) != NULL) - (*p) = '\0'; - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: mksd scanner failed: %s", line); - return DEFER; - default: - /* VIR */ + *p = '\0'; + return m_errlog_defer(scanent, + string_sprintf("scanner failed: %s", line)); + + default: /* VIR */ if ((p = strchr (line, '\n')) != NULL) { - (*p) = '\0'; - if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' ')) + *p = '\0'; + if (((p-line) > 5) && (line[3] == ' ')) if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) { - (*p) = '\0'; - Ustrcpy (malware_name_buffer, line+4); - malware_name = malware_name_buffer; + *p = '\0'; + malware_name = string_copy(US line+4); return OK; } } - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: malformed reply received from mksd: %s", line); - return DEFER; + return m_errlog_defer(scanent, + string_sprintf("malformed reply received: %s", line)); } } -static int mksd_scan_packed(int sock, uschar *scan_filename) +static int +mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename) { struct iovec iov[3]; const char *cmd = "MSQ\n"; @@ -2015,9 +1653,10 @@ static int mksd_scan_packed(int sock, uschar *scan_filename) if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0) return DEFER; - close (sock); - - return mksd_parse_line (CS av_buffer); + return mksd_parse_line (scanent, CS av_buffer); } -#endif +#endif /*WITH_CONTENT_SCAN*/ +/* + * vi: aw ai sw=2 + */ diff --git a/src/src/string.c b/src/src/string.c index 0e73e2c79..94d61b1a3 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -34,7 +34,7 @@ Returns: 0 if the string is not a textual representation of an IP address */ int -string_is_ip_address(uschar *s, int *maskptr) +string_is_ip_address(const uschar *s, int *maskptr) { int i; int yield = 4; @@ -44,7 +44,7 @@ offset. */ if (maskptr != NULL) { - uschar *ss = s + Ustrlen(s); + const uschar *ss = s + Ustrlen(s); *maskptr = 0; if (s != ss && isdigit(*(--ss))) { -- 2.30.2