X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/0f0c8159c43045f4ad847a0129dca7eddd313285..93a6fce2ebf117f490d7ee11f066f75280d32386:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index 659357633..9e71afc9b 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -38,14 +38,12 @@ static struct scan /* The maximum number of clamd servers that are supported in the configuration */ #define MAX_CLAMD_SERVERS 32 #define MAX_CLAMD_SERVERS_S "32" -/* Maximum length of the hostname that can be specified in the clamd address list */ -#define MAX_CLAMD_ADDRESS_LENGTH 64 -#define MAX_CLAMD_ADDRESS_LENGTH_S "64" -typedef struct clamd_address_container { - uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH+1]; - unsigned int tcp_port; -} clamd_address_container; +typedef struct clamd_address { + uschar * hostspec; + unsigned tcp_port; + unsigned retry; +} clamd_address; #ifndef nelements # define nelements(arr) (sizeof(arr) / sizeof(arr[0])) @@ -64,6 +62,30 @@ typedef struct clamd_address_container { #define DERR_TIMEOUT (1<<9) /* scan timeout has run out */ #define DERR_BAD_CALL (1<<15) /* wrong command */ + +static const uschar * malware_regex_default = US ".+"; +static const pcre * malware_default_re = NULL; + +static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$"; +static const pcre * drweb_re = NULL; + +static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$"; +static const pcre * fsec_re = NULL; + +static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$"; +static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$"; +static const pcre * kav_re_sus = NULL; +static const pcre * kav_re_inf = NULL; + +static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]"; +static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)"; +static const pcre * ava_re_clean = NULL; +static const pcre * ava_re_virus = NULL; + + + +/******************************************************************************/ + /* 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. */ @@ -91,21 +113,21 @@ extern uschar spooled_message_id[17]; static inline int malware_errlog_defer(const uschar * str) { - log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str); - return DEFER; +log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str); +return DEFER; } static int m_errlog_defer(struct scan * scanent, const uschar * str) { - return malware_errlog_defer(string_sprintf("%s: %s", scanent->name, 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); +(void) close(fd_to_close); +return m_errlog_defer(scanent, str); } /*************************************************/ @@ -117,111 +139,61 @@ static inline int m_tcpsocket(const uschar * hostname, unsigned int port, host_item * host, uschar ** errstr) { - return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr); -} - -static int -m_tcpsocket_fromdef(const uschar * hostport, uschar ** errstr) -{ - int scan; - uschar hostname[256]; - unsigned int portlow, porthigh; - - /* extract host and port part */ - scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh); - if ( scan != 3 ) { - if ( scan != 2 ) { - *errstr = string_sprintf("invalid socket '%s'", hostport); - return -1; - } - porthigh = portlow; - } - - return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh, - 5, NULL, errstr); -} - -static int -m_unixsocket(const uschar * path, uschar ** errstr) -{ - int sock; - struct sockaddr_un server; - - if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - *errstr = US"can't open UNIX socket."; - return -1; - } - - server.sun_family = AF_UNIX; - Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1); - server.sun_path[sizeof(server.sun_path)-1] = '\0'; - if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) { - int err = errno; - (void)close(sock); - *errstr = string_sprintf("unable to connect to UNIX socket (%s): %s", - path, strerror(err)); - return -1; - } - return sock; -} - -static inline int -m_streamsocket(const uschar * spec, uschar ** errstr) -{ - return *spec == '/' - ? m_unixsocket(spec, errstr) : m_tcpsocket_fromdef(spec, errstr); +return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr); } static int m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr) { - if (send(sock, buf, cnt, 0) < 0) { - int err = errno; - (void)close(sock); - *errstr = string_sprintf("unable to send to socket (%s): %s", - buf, strerror(err)); - return -1; - } - return sock; +if (send(sock, buf, cnt, 0) < 0) + { + int err = errno; + (void)close(sock); + *errstr = string_sprintf("unable to send to socket (%s): %s", + buf, strerror(err)); + return -1; + } +return sock; } static const pcre * m_pcre_compile(const uschar * re, uschar ** errstr) { - const uschar * rerror; - int roffset; - const pcre * cre; - - cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (!cre) - *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", - re, rerror, roffset); - return cre; +const uschar * rerror; +int roffset; +const pcre * cre; + +cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL); +if (!cre) + *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", + re, rerror, roffset); +return cre; } uschar * m_pcre_exec(const pcre * cre, uschar * text) { - int ovector[10*3]; - int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0, - ovector, nelements(ovector)); - uschar * substr = NULL; - if (i >= 2) /* Got it */ - pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); - return substr; +int ovector[10*3]; +int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0, + ovector, nelements(ovector)); +uschar * substr = NULL; +if (i >= 2) /* Got it */ + pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); +return substr; } static const pcre * -m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr) +m_pcre_nextinlist(const uschar ** list, int * sep, + char * listerr, uschar ** errstr) { - const uschar * list_ele; - const pcre * cre = NULL; +const uschar * list_ele; +const pcre * cre = NULL; - if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) - *errstr = US listerr; - else - cre = m_pcre_compile(CUS list_ele, errstr); - return cre; +if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) + *errstr = US listerr; +else + cre = m_pcre_compile(CUS list_ele, errstr); +return cre; } /* @@ -402,6 +374,26 @@ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0) return mksd_parse_line (scanent, CS av_buffer); } + +static int +clamd_option(clamd_address * cd, const uschar * optstr, int * subsep) +{ +uschar * s; + +cd->retry = 0; +while ((s = string_nextinlist(&optstr, subsep, NULL, 0))) + if (Ustrncmp(s, "retry=", 6) == 0) + { + int sec = readconf_readtime((s += 6), '\0', FALSE); + if (sec < 0) + return FAIL; + cd->retry = sec; + } + else + return FAIL; +return OK; +} + /************************************************* * Scan content for malware * *************************************************/ @@ -423,9 +415,8 @@ malware_internal(const uschar * malware_re, const uschar * eml_filename, int timeout, BOOL faking) { int sep = 0; -uschar *av_scanner_work = av_scanner; +const uschar *av_scanner_work = av_scanner; uschar *scanner_name; -uschar malware_regex_default[] = ".+"; unsigned long mbox_size; FILE *mbox_file; const pcre *re; @@ -452,18 +443,25 @@ if (!malware_re) return FAIL; /* explicitly no matching */ /* special cases (match anything except empty) */ -if ( (strcmpic(malware_re,US"true") == 0) || - (Ustrcmp(malware_re,"*") == 0) || - (Ustrcmp(malware_re,"1") == 0) ) +if ( strcmpic(malware_re,US"true") == 0 + || Ustrcmp(malware_re,"*") == 0 + || Ustrcmp(malware_re,"1") == 0 + ) + { + if ( !malware_default_re + && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr))) + return malware_errlog_defer(errstr); malware_re = malware_regex_default; - -/* Reset sep that is set by previous string_nextinlist() call */ -sep = 0; + re = malware_default_re; + } /* compile the regex, see if it works */ -if (!(re = m_pcre_compile(malware_re, &errstr))) +else if (!(re = m_pcre_compile(malware_re, &errstr))) return malware_errlog_defer(errstr); +/* Reset sep that is set by previous string_nextinlist() call */ +sep = 0; + /* if av_scanner starts with a dollar, expand it first */ if (*av_scanner == '$') { @@ -488,7 +486,8 @@ if (!malware_ok) if (!timeout) timeout = MALWARE_TIMEOUT; tmo = time(NULL) + timeout; - for (scanent = m_scans; ; scanent++) { + for (scanent = m_scans; ; scanent++) + { if (!scanent->name) return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", scanner_name)); @@ -500,9 +499,9 @@ if (!malware_ok) break; switch(scanent->conn) { - case MC_TCP: sock = m_tcpsocket_fromdef(scanner_options, &errstr); break; - case MC_UNIX: sock = m_unixsocket(scanner_options, &errstr); break; - case MC_STRM: sock = m_streamsocket(scanner_options, &errstr); break; + case MC_TCP: sock = ip_tcpsocket(scanner_options, &errstr, 5); break; + case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr); break; + case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5); break; default: /* compiler quietening */ break; } if (sock < 0) @@ -577,7 +576,6 @@ if (!malware_ok) uschar * tmpbuf, *drweb_fbuf; int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, drweb_vnum, drweb_slen, drweb_fin = 0x0000; - const pcre *drweb_re; /* prepare variables */ drweb_cmd = htonl(DRWEBD_SCAN_CMD); @@ -697,7 +695,8 @@ if (!malware_ok) malware_name = US"unknown"; /* set up match regex */ - drweb_re = m_pcre_compile(US"infected\\swith\\s*(.+?)$", &errstr); + if (!drweb_re) + drweb_re = m_pcre_compile(drweb_re_str, &errstr); /* read and concatenate virus names into one string */ for (i = 0; i < drweb_vnum; i++) @@ -833,7 +832,6 @@ if (!malware_ok) int i, j, bread = 0; uschar * file_name; uschar av_buffer[1024]; - const pcre * fs_inf; static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n", US"CONFIGURE\tTIMEOUT\t0\n", US"CONFIGURE\tMAXARCH\t5\n", @@ -870,7 +868,8 @@ if (!malware_ok) /* set up match */ /* todo also SUSPICION\t */ - fs_inf = m_pcre_compile(US"\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr); + if (!fsec_re) + fsec_re = m_pcre_compile(fsec_re_str, &errstr); /* read report, linewise. Apply a timeout as the Fsecure daemon sometimes wants an answer to "PING" but they won't tell us what */ @@ -887,14 +886,14 @@ if (!malware_ok) string_sprintf("unable to read result (%s)", strerror(errno)), sock); - for (p[bread] = '\0'; q = strchr(p, '\n'); p = q+1) + for (p[bread] = '\0'; q = Ustrchr(p, '\n'); p = q+1) { *q = '\0'; /* Really search for virus again? */ if (!malware_name) /* try matcher on the line, grab substring */ - malware_name = m_pcre_exec(fs_inf, p); + malware_name = m_pcre_exec(fsec_re, p); if (Ustrstr(p, "OK\tScan ok.")) goto fsec_found; @@ -989,10 +988,16 @@ if (!malware_ok) if (kav_reportlen > 0) { /* set up match regex, depends on retcode */ - kav_re = m_pcre_compile( kav_rc == 3 - ? US"suspicion:\\s*(.+?)\\s*$" - : US"infected:\\s*(.+?)\\s*$", - &errstr ); + if (kav_rc == 3) + { + if (!kav_re_sus) kav_re_sus = m_pcre_compile(kav_re_sus_str, &errstr); + kav_re = kav_re_sus; + } + else + { + if (!kav_re_inf) kav_re_inf = m_pcre_compile(kav_re_inf_str, &errstr); + kav_re = kav_re_inf; + } /* read report, linewise */ while (kav_reportlen > 0) @@ -1092,17 +1097,16 @@ if (!malware_ok) sizeof(linebuffer), tmo))) { if (rcnt < 0) + { + int err = errno; if (rcnt == -1) break; - else - { - int err = errno; - (void) pclose(scanner_out); - signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, string_sprintf( - "unable to read from scanner (%s): %s", - commandline, strerror(err))); - } + (void) pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, string_sprintf( + "unable to read from scanner (%s): %s", + commandline, strerror(err))); + } if (Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record)) { @@ -1219,8 +1223,7 @@ if (!malware_ok) off_t fsize; unsigned int fsize_uint; BOOL use_scan_command = FALSE; - clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; - int current_server; + clamd_address * cv[MAX_CLAMD_SERVERS]; int num_servers = 0; #ifdef WITH_OLD_CLAMAV_STREAM unsigned int port; @@ -1230,46 +1233,80 @@ if (!malware_ok) uint32_t send_size, send_final_zeroblock; #endif + /*XXX if unixdomain socket, only one server supported. Needs fixing; + there's no reason we should not mix local and remote servers */ + if (*scanner_options == '/') + { + clamd_address * cd; + const uschar * sublist; + int subsep = ' '; + /* Local file; so we def want to use_scan_command and don't want to try * passing IP/port combinations */ use_scan_command = TRUE; + cd = (clamd_address *) store_get(sizeof(clamd_address)); + + /* extract socket-path part */ + sublist = scanner_options; + cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0); + + /* parse options */ + if (clamd_option(cd, sublist, &subsep) != OK) + return m_errlog_defer(scanent, + string_sprintf("bad option '%s'", scanner_options)); + cv[0] = cd; + } else { - const uschar *address = scanner_options; - uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20]; - /* Go through the rest of the list of host/port and construct an array * of servers to try. The first one is the bit we just passed from * scanner_options so process that first and then scan the remainder of * the address buffer */ do { - clamd_address_container *this_clamd; + clamd_address * cd; + const uschar * sublist; + int subsep = ' '; + uschar * s; /* The 'local' option means use the SCAN command over the network * socket (ie common file storage in use) */ - if (strcmpic(address,US"local") == 0) + /*XXX we could accept this also as a local option? */ + if (strcmpic(scanner_options, US"local") == 0) { use_scan_command = TRUE; continue; } - /* XXX: If unsuccessful we should free this memory */ - this_clamd = - (clamd_address_container *)store_get(sizeof(clamd_address_container)); + cd = (clamd_address *) store_get(sizeof(clamd_address)); /* extract host and port part */ - if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u", - this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 ) + sublist = scanner_options; + if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0))) { (void) m_errlog_defer(scanent, - string_sprintf("invalid address '%s'", address)); + string_sprintf("missing address: '%s'", scanner_options)); continue; } + if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0))) + { + (void) m_errlog_defer(scanent, + string_sprintf("missing port: '%s'", scanner_options)); + continue; + } + cd->tcp_port = atoi(CS s); - clamd_address_vector[num_servers] = this_clamd; - num_servers++; + /* parse options */ + /*XXX should these options be common over scanner types? */ + if (clamd_option(cd, sublist, &subsep) != OK) + { + return m_errlog_defer(scanent, + string_sprintf("bad option '%s'", scanner_options)); + continue; + } + + cv[num_servers++] = cd; if (num_servers >= MAX_CLAMD_SERVERS) { (void) m_errlog_defer(scanent, @@ -1277,9 +1314,8 @@ if (!malware_ok) "specified; only using the first " MAX_CLAMD_SERVERS_S ); break; } - } while ((address = string_nextinlist(&av_scanner_work, &sep, - address_buffer, - sizeof(address_buffer))) != NULL); + } while ((scanner_options = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))); /* check if we have at least one server */ if (!num_servers) @@ -1303,43 +1339,53 @@ if (!malware_ok) while (num_servers > 0) { - int i; - /* Randomly pick a server to start with */ - current_server = random_number( num_servers ); + int i = random_number( num_servers ); + clamd_address * cd = cv[i]; - DEBUG(D_acl) - debug_printf("trying server name %s, port %u\n", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port); + DEBUG(D_acl) debug_printf("trying server name %s, port %u\n", + cd->hostspec, cd->tcp_port); /* Lookup the host. This is to ensure that we connect to the same IP * on both connections (as one host could resolve to multiple ips) */ - sock= m_tcpsocket(clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port, - &connhost, &errstr); - if (sock >= 0) + for (;;) { - /* Connection successfully established with a server */ - hostname = clamd_address_vector[current_server]->tcp_addr; - break; + sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr); + if (sock >= 0) + { + /* Connection successfully established with a server */ + hostname = cd->hostspec; + break; + } + if (cd->retry <= 0) break; + while (cd->retry > 0) cd->retry = sleep(cd->retry); } + if (sock >= 0) + break; - (void) m_errlog_defer(scanent, errstr); + log_write(0, LOG_MAIN, "malware acl condition: %s: %s", + scanent->name, errstr); /* Remove the server from the list. XXX We should free the memory */ num_servers--; - for (i = current_server; i < num_servers; i++) - clamd_address_vector[i] = clamd_address_vector[i+1]; + for (; i < num_servers; i++) + cv[i] = cv[i+1]; } if (num_servers == 0) return m_errlog_defer(scanent, US"all servers failed"); } else - { - if ((sock = m_unixsocket(scanner_options, &errstr)) < 0) - return m_errlog_defer(scanent, errstr); - } + for (;;) + { + if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0) + { + hostname = cv[0]->hostspec; + break; + } + if (cv[0]->retry <= 0) + return m_errlog_defer(scanent, errstr); + while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry); + } /* have socket in variable "sock"; command to use is semi-independent of * the socket protocol. We use SCAN if is local (either Unix/local @@ -1712,7 +1758,7 @@ if (!malware_ok) string_sprintf("invalid option '%s'", scanner_options)); } - if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) return m_errlog_defer(scanent, errstr); malware_name = NULL; @@ -1732,7 +1778,6 @@ if (!malware_ok) int ovector[1*3]; uschar buf[1024]; uschar * scanrequest; - const pcre * avast_clean_re, * avast_virus_re; enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage; int nread; @@ -1745,11 +1790,10 @@ if (!malware_ok) [L] - infected [E] - some error occured Such marker follows the first non-escaped TAB. */ - if ( !(avast_clean_re = - m_pcre_compile(US"(?!\\\\)\\t\\[\\+\\]", &errstr)) - || !(avast_virus_re = - m_pcre_compile(US"(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)", - &errstr)) + if ( ( !ava_re_clean + && !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr))) + || ( !ava_re_virus + && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr))) ) return malware_errlog_defer(errstr); @@ -1808,11 +1852,11 @@ if (!malware_ok) if (Ustrncmp(buf, "210", 3) == 0) break; /* ignore the "210 SCAN DATA" message */ - if (pcre_exec(avast_clean_re, NULL, CS buf, slen, + if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, ovector, nelements(ovector)) > 0) break; - if ((malware_name = m_pcre_exec(avast_virus_re, buf))) + if ((malware_name = m_pcre_exec(ava_re_virus, buf))) { /* remove backslash in front of [whitespace|backslash] */ uschar * p, * p0; for (p = malware_name; *p; ++p) @@ -1952,6 +1996,26 @@ malware_in_file(uschar *eml_filename) return ret; } + +void +malware_init(void) +{ +if (!malware_default_re) + malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE); +if (!drweb_re) + drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE); +if (!fsec_re) + fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE); +if (!kav_re_sus) + kav_re_sus = regex_must_compile(kav_re_sus_str, FALSE, TRUE); +if (!kav_re_inf) + kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE); +if (!ava_re_clean) + ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE); +if (!ava_re_virus) + ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE); +} + #endif /*WITH_CONTENT_SCAN*/ /* * vi: aw ai sw=2