X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/94e1f16d6033683bdebaf5092f64c58bc044dd2d..e68def51cb753d730249565e630b549a73857ec1:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index ccb7e6d20..a4080d040 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -4,16 +4,53 @@ /* Copyright (c) Tom Kistner 2003 - 2015 * License: GPL - * Copyright (c) The Exim Maintainers 2016 + * Copyright (c) The Exim Maintainers 2015 - 2018 */ /* Code for calling virus (malware) scanners. Called from acl.c. */ #include "exim.h" -#ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN /* entire file */ -typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL, - M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST, M_FPROT6D} scanner_t; +typedef enum { +#ifndef DISABLE_MAL_FFROTD + M_FPROTD, +#endif +#ifndef DISABLE_MAL_FFROT6D + M_FPROT6D, +#endif +#ifndef DISABLE_MAL_DRWEB + M_DRWEB, +#endif +#ifndef DISABLE_MAL_AVE + M_AVES, +#endif +#ifndef DISABLE_MAL_FSECURE + M_FSEC, +#endif +#ifndef DISABLE_MAL_KAV + M_KAVD, +#endif +#ifndef DISABLE_MAL_SOPHIE + M_SOPHIE, +#endif +#ifndef DISABLE_MAL_CLAM + M_CLAMD, +#endif +#ifndef DISABLE_MAL_MKS + M_MKSD, +#endif +#ifndef DISABLE_MAL_AVAST + M_AVAST, +#endif +#ifndef DISABLE_MAL_SOCK + M_SOCK, +#endif +#ifndef DISABLE_MAL_CMDLINE + M_CMDL, +#endif + M_DUMMY + } scanner_t; typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t; static struct scan { @@ -23,82 +60,144 @@ static struct scan contype_t conn; } m_scans[] = { +#ifndef DISABLE_MAL_FFROTD { M_FPROTD, US"f-protd", US"localhost 10200-10204", MC_TCP }, +#endif +#ifndef DISABLE_MAL_FFROT6D + { M_FPROT6D, US"f-prot6d", US"localhost 10200", MC_TCP }, +#endif +#ifndef DISABLE_MAL_DRWEB { M_DRWEB, US"drweb", US"/usr/local/drweb/run/drwebd.sock", MC_STRM }, +#endif +#ifndef DISABLE_MAL_AVE { M_AVES, US"aveserver", US"/var/run/aveserver", MC_UNIX }, +#endif +#ifndef DISABLE_MAL_FSECURE { M_FSEC, US"fsecure", US"/var/run/.fsav", MC_UNIX }, +#endif +#ifndef DISABLE_MAL_KAV { M_KAVD, US"kavdaemon", US"/var/run/AvpCtl", MC_UNIX }, - { M_CMDL, US"cmdline", NULL, MC_NONE }, +#endif +#ifndef DISABLE_MAL_SOPHIE { M_SOPHIE, US"sophie", US"/var/run/sophie", MC_UNIX }, +#endif +#ifndef DISABLE_MAL_CLAM { M_CLAMD, US"clamd", US"/tmp/clamd", MC_NONE }, - { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM }, +#endif +#ifndef DISABLE_MAL_MKS { M_MKSD, US"mksd", NULL, MC_NONE }, +#endif +#ifndef DISABLE_MAL_AVAST { M_AVAST, US"avast", US"/var/run/avast/scan.sock", MC_STRM }, - { M_FPROT6D, US"f-prot6d", US"localhost 10200", MC_TCP }, +#endif +#ifndef DISABLE_MAL_SOCK + { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM }, +#endif +#ifndef DISABLE_MAL_CMDLINE + { M_CMDL, US"cmdline", NULL, MC_NONE }, +#endif { -1, NULL, NULL, MC_NONE } /* end-marker */ }; +/******************************************************************************/ +# ifdef MACRO_PREDEF /* build solely to predefine macros */ + +# include "macro_predef.h" + +void +features_malware(void) +{ +const uschar * s; +uschar * t; +uschar buf[64]; + +spf(buf, sizeof(buf), US"_HAVE_MALWARE_"); + +for (const struct scan * sc = m_scans; sc->scancode != -1; sc++) + { + for (s = sc->name, t = buf+14; *s; s++) if (*s != '-') + *t++ = toupper(*s); + *t = '\0'; + builtin_macro_create(buf); + } +} + +/******************************************************************************/ +# else /*!MACRO_PREDEF, main build*/ + + +#define MALWARE_TIMEOUT 120 /* default timeout, seconds */ + +static const uschar * malware_regex_default = US ".+"; +static const pcre * malware_default_re = NULL; + + + +#ifndef DISABLE_MAL_CLAM /* The maximum number of clamd servers that are supported in the configuration */ -#define MAX_CLAMD_SERVERS 32 -#define MAX_CLAMD_SERVERS_S "32" +# define MAX_CLAMD_SERVERS 32 +# define MAX_CLAMD_SERVERS_S "32" typedef struct clamd_address { uschar * hostspec; unsigned tcp_port; unsigned retry; } clamd_address; - -#ifndef nelements -# define nelements(arr) (sizeof(arr) / sizeof(arr[0])) #endif -#define MALWARE_TIMEOUT 120 /* default timeout, seconds */ - - -#define DRWEBD_SCAN_CMD (1) /* scan file, buffer or diskfile */ -#define DRWEBD_RETURN_VIRUSES (1<<0) /* ask daemon return to us viruses names from report */ -#define DRWEBD_IS_MAIL (1<<19) /* say to daemon that format is "archive MAIL" */ - -#define DERR_READ_ERR (1<<0) /* read error */ -#define DERR_NOMEMORY (1<<2) /* no memory */ -#define DERR_TIMEOUT (1<<9) /* scan timeout has run out */ -#define DERR_BAD_CALL (1<<15) /* wrong command */ +#ifndef DISABLE_MAL_DRWEB +# define DRWEBD_SCAN_CMD (1) /* scan file, buffer or diskfile */ +# define DRWEBD_RETURN_VIRUSES (1<<0) /* ask daemon return to us viruses names from report */ +# define DRWEBD_IS_MAIL (1<<19) /* say to daemon that format is "archive MAIL" */ - -static const uschar * malware_regex_default = US ".+"; -static const pcre * malware_default_re = NULL; +# define DERR_READ_ERR (1<<0) /* read error */ +# define DERR_NOMEMORY (1<<2) /* no memory */ +# define DERR_TIMEOUT (1<<9) /* scan timeout has run out */ +# define DERR_BAD_CALL (1<<15) /* wrong command */ static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$"; static const pcre * drweb_re = NULL; +#endif +#ifndef DISABLE_MAL_FSECURE static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$"; static const pcre * fsec_re = NULL; +#endif +#ifndef DISABLE_MAL_KAV 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; +#endif +#ifndef DISABLE_MAL_AVAST 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 uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)"; +static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)"; static const pcre * ava_re_clean = NULL; static const pcre * ava_re_virus = NULL; +static const pcre * ava_re_error = NULL; +#endif +#ifndef DISABLE_MAL_FFROT6D static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$"; static const uschar * fprot6d_re_virus_str = US "^\\d+\\s\\s+.+$"; static const pcre * fprot6d_re_error = NULL; static const pcre * fprot6d_re_virus = NULL; +#endif /******************************************************************************/ +#ifndef DISABLE_MAL_KAV /* 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 +# define BIG_MY_ENDIAN 0 +# define LITTLE_MY_ENDIAN 1 static int test_byte_order(void); static inline int test_byte_order() @@ -107,6 +206,7 @@ test_byte_order() char *byte = CS &word; return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); } +#endif BOOL malware_ok = FALSE; @@ -117,41 +217,69 @@ extern int spool_mbox_ok; extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1]; +/* Some (currently avast only) use backslash escaped whitespace, +this function undoes these escapes */ + +static inline void +unescape(uschar *p) +{ +uschar *p0; +for (; *p; ++p) + if (*p == '\\' && (isspace(p[1]) || p[1] == '\\')) + for (p0 = p; *p0; ++p0) *p0 = p0[1]; +} +/* --- malware_*_defer --- */ static inline int -malware_errlog_defer(const uschar * str) +malware_panic_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 * hostport, +static inline int +malware_log_defer(const uschar * str) +{ +log_write(0, LOG_MAIN, "malware acl condition: %s", str); +return DEFER; +} +/* --- m_*_defer --- */ +static inline int +m_panic_defer(struct scan * scanent, const uschar * hostport, const uschar * str) { -return malware_errlog_defer(string_sprintf("%s %s : %s", +return malware_panic_defer(string_sprintf("%s %s : %s", scanent->name, hostport ? hostport : CUS"", str)); } -static int -m_errlog_defer_3(struct scan * scanent, const uschar * hostport, +static inline int +m_log_defer(struct scan * scanent, const uschar * hostport, + const uschar * str) +{ +return malware_log_defer(string_sprintf("%s %s : %s", + scanent->name, hostport ? hostport : CUS"", str)); +} +/* --- m_*_defer_3 */ +static inline int +m_panic_defer_3(struct scan * scanent, const uschar * hostport, const uschar * str, int fd_to_close) { (void) close(fd_to_close); -return m_errlog_defer(scanent, hostport, str); +return m_panic_defer(scanent, hostport, str); } /*************************************************/ +#ifndef DISABLE_MAL_CLAM /* 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, const blob * fastopen) + host_item * host, uschar ** errstr, const blob * fastopen_blob) { return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, - host, errstr, fastopen); + host, errstr, fastopen_blob); } +#endif static int m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr) @@ -174,8 +302,7 @@ const uschar * rerror; int roffset; const pcre * cre; -cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL); -if (!cre) +if (!(cre = pcre_compile(CS re, PCRE_COPT, CCSS &rerror, &roffset, NULL))) *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", re, rerror, roffset); return cre; @@ -186,10 +313,10 @@ 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)); + ovector, nelem(ovector)); uschar * substr = NULL; if (i >= 2) /* Got it */ - pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); + pcre_get_substring(CS text, ovector, i, 1, CCSS &substr); return substr; } @@ -221,13 +348,13 @@ return cre; -2 on timeout or error */ static int -recv_line(int fd, uschar * buffer, int bsize, int tmo) +recv_line(int fd, uschar * buffer, int bsize, time_t tmo) { uschar * p = buffer; ssize_t rcv; BOOL ok = FALSE; -if (!fd_ready(fd, tmo-time(NULL))) +if (!fd_ready(fd, tmo)) return -2; /*XXX tmo handling assumes we always get a whole line */ @@ -254,18 +381,19 @@ return p - buffer; /* return TRUE iff size as requested */ static BOOL -recv_len(int sock, void * buf, int size, int tmo) +recv_len(int sock, void * buf, int size, time_t tmo) { -return fd_ready(sock, tmo-time(NULL)) +return fd_ready(sock, tmo) ? recv(sock, buf, size, 0) == size : FALSE; } +#ifndef DISABLE_MAL_MKS /* ============= private routines for the "mksd" scanner type ============== */ -#include +# include static inline int mksd_writev (int sock, struct iovec * iov, int iovcnt) @@ -279,7 +407,7 @@ for (;;) while (i < 0 && errno == EINTR); if (i <= 0) { - (void) malware_errlog_defer( + (void) malware_panic_defer( US"unable to write to mksd UNIX socket (/var/run/mksd/socket)"); return -1; } @@ -301,17 +429,18 @@ for (;;) } static inline int -mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo) +mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, time_t tmo) { +client_conn_ctx cctx = {.sock = sock}; int offset = 0; int i; do { - i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL)); + i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo); if (i <= 0) { - (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)"); + (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)"); return -1; } @@ -319,7 +448,7 @@ do /* offset == av_buffer_size -> buffer full */ if (offset == av_buffer_size) { - (void) malware_errlog_defer(US"malformed reply received from mksd"); + (void) malware_panic_defer(US"malformed reply received from mksd"); return -1; } } while (av_buffer[offset-1] != '\n'); @@ -342,7 +471,7 @@ switch (*line) case 'A': /* ERR */ if ((p = strchr (line, '\n')) != NULL) *p = '\0'; - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, string_sprintf("scanner failed: %s", line)); default: /* VIR */ @@ -360,14 +489,14 @@ switch (*line) return OK; } } - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, string_sprintf("malformed reply received: %s", line)); } } static int mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename, - int tmo) + time_t tmo) { struct iovec iov[3]; const char *cmd = "MSQ\n"; @@ -388,8 +517,10 @@ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0) return mksd_parse_line (scanent, CS av_buffer); } +#endif /* MKSD */ +#ifndef DISABLE_MAL_CLAM static int clamd_option(clamd_address * cd, const uschar * optstr, int * subsep) { @@ -408,6 +539,9 @@ while ((s = string_nextinlist(&optstr, subsep, NULL, 0))) return FAIL; return OK; } +#endif + + /************************************************* * Scan content for malware * @@ -438,7 +572,7 @@ const pcre *re; uschar * errstr; struct scan * scanent; const uschar * scanner_options; -int sock = -1; +client_conn_ctx malware_daemon_ctx = {.sock = -1}; time_t tmo; uschar * eml_filename, * eml_dir; @@ -448,7 +582,7 @@ if (!malware_re) /* Ensure the eml mbox file is spooled up */ if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename))) - return malware_errlog_defer(US"error while creating mbox spool file"); + return malware_panic_defer(US"error while creating mbox spool file"); /* None of our current scanners need the mbox file as a stream (they use the name), so we can close it right away. Get the directory too. */ @@ -468,20 +602,20 @@ if ( strcmpic(malware_re,US"true") == 0 { if ( !malware_default_re && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr))) - return malware_errlog_defer(errstr); + return malware_panic_defer(errstr); malware_re = malware_regex_default; re = malware_default_re; } /* compile the regex, see if it works */ else if (!(re = m_pcre_compile(malware_re, &errstr))) - return malware_errlog_defer(errstr); + return malware_panic_defer(errstr); /* if av_scanner starts with a dollar, expand it first */ if (*av_scanner == '$') { if (!(av_scanner_work = expand_string(av_scanner))) - return malware_errlog_defer( + return malware_panic_defer( string_sprintf("av_scanner starts with $, but expansion failed: %s", expand_string_message)); @@ -497,14 +631,14 @@ if (!malware_ok) { /* find the scanner type from the av_scanner option */ if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - return malware_errlog_defer(US"av_scanner configuration variable is empty"); + return malware_panic_defer(US"av_scanner configuration variable is empty"); if (!timeout) timeout = MALWARE_TIMEOUT; tmo = time(NULL) + timeout; for (scanent = m_scans; ; scanent++) { if (!scanent->name) - return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", + return malware_panic_defer(string_sprintf("unknown scanner type '%s'", scanner_name)); if (strcmpic(scanner_name, US scanent->name) != 0) continue; @@ -519,18 +653,23 @@ if (!malware_ok) DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options); switch(scanent->conn) { - 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; + case MC_TCP: + malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5, NULL); break; + case MC_UNIX: + malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr); break; + case MC_STRM: + malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5, NULL); break; + default: + /* compiler quietening */ break; } - if (sock < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (malware_daemon_ctx.sock < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); break; } switch (scanent->scancode) { +#ifndef DISABLE_MAL_FFROTD case M_FPROTD: /* "f-protd" scanner type -------------------------------- */ { uschar *fp_scan_option; @@ -554,10 +693,10 @@ if (!malware_ok) scanner_name, scanrequest); /* send scan request */ - if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); - while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0) + while ((len = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) >= 0) if (len > 0) { if (Ustrstr(buf, US" 0 */ if (drweb_vnum) { - int i; gstring * g = NULL; /* setup default virus name */ @@ -720,26 +908,28 @@ badseek: err = errno; drweb_re = m_pcre_compile(drweb_re_str, &errstr); /* read and concatenate virus names into one string */ - for (i = 0; i < drweb_vnum; i++) + for (int i = 0; i < drweb_vnum; i++) { int ovector[10*3]; /* read the size of report */ - if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"cannot read report size", sock); + if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo)) + return m_panic_defer_3(scanent, CUS callout_address, + US"cannot read report size", malware_daemon_ctx.sock); drweb_slen = ntohl(drweb_slen); - tmpbuf = store_get(drweb_slen); + + /* assume tainted, since it is external input */ + tmpbuf = store_get(drweb_slen, TRUE); /* read report body */ - if (!recv_len(sock, tmpbuf, drweb_slen, tmo)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"cannot read report string", sock); + if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo)) + return m_panic_defer_3(scanent, CUS callout_address, + US"cannot read report string", malware_daemon_ctx.sock); tmpbuf[drweb_slen] = '\0'; /* try matcher on the line, grab substring */ result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, - ovector, nelements(ovector)); + ovector, nelem(ovector)); if (result >= 2) { const char * pre_malware_nb; @@ -747,7 +937,7 @@ badseek: err = errno; pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); if (i==0) /* the first name we just copy to malware_name */ - g = string_cat(NULL, pre_malware_nb); + g = string_cat(NULL, US pre_malware_nb); /*XXX could be string_append_listele? */ else /* concatenate each new virus name to previous */ @@ -771,16 +961,18 @@ badseek: err = errno; * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR * and others are ignored */ if (drweb_s) - return m_errlog_defer_3(scanent, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s), - sock); + malware_daemon_ctx.sock); /* no virus found */ malware_name = NULL; } break; } /* drweb */ +#endif +#ifndef DISABLE_MAL_AVE case M_AVES: /* "aveserver" scanner type -------------------------------- */ { uschar buf[32768]; @@ -788,13 +980,13 @@ badseek: err = errno; /* read aveserver's greeting and see if it is ready (2xx greeting) */ buf[0] = 0; - recv_line(sock, buf, sizeof(buf), tmo); + recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo); if (buf[0] != '2') /* aveserver is having problems */ - return m_errlog_defer_3(scanent, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unavailable (Responded: %s).", ((buf[0] != 0) ? buf : US "nothing") ), - sock); + malware_daemon_ctx.sock); /* prepare our command */ (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n", @@ -803,19 +995,19 @@ badseek: err = errno; /* and send it */ DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n", scanner_name, buf); - if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, buf, Ustrlen(buf), &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); malware_name = NULL; result = 0; /* read response lines, find malware name and final response */ - while (recv_line(sock, buf, sizeof(buf), tmo) > 0) + while (recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo) > 0) { if (buf[0] == '2') break; if (buf[0] == '5') /* aveserver is having problems */ { - result = m_errlog_defer(scanent, CUS callout_address, + result = m_panic_defer(scanent, CUS callout_address, string_sprintf("unable to scan file %s (Responded: %s).", eml_filename, buf)); break; @@ -828,30 +1020,32 @@ badseek: err = errno; } } - if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, US"quit\r\n", 6, &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); /* read aveserver's greeting and see if it is ready (2xx greeting) */ buf[0] = 0; - recv_line(sock, buf, sizeof(buf), tmo); + recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo); if (buf[0] != '2') /* aveserver is having problems */ - return m_errlog_defer_3(scanent, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to quit dialogue (Responded: %s).", ((buf[0] != 0) ? buf : US "nothing") ), - sock); + malware_daemon_ctx.sock); if (result == DEFER) { - (void)close(sock); + (void)close(malware_daemon_ctx.sock); return DEFER; } break; } /* aveserver */ +#endif +#ifndef DISABLE_MAL_FSECURE case M_FSEC: /* "fsecure" scanner type ---------------------------------- */ { - int i, j, bread = 0; + int i, bread = 0; uschar * file_name; uschar av_buffer[1024]; static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n", @@ -865,19 +1059,19 @@ badseek: err = errno; scanner_name, scanner_options); /* pass options */ memset(av_buffer, 0, sizeof(av_buffer)); - for (i = 0; i != nelements(cmdopt); i++) + for (i = 0; i != nelem(cmdopt); i++) { - if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); + bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); if (bread > 0) av_buffer[bread]='\0'; if (bread < 0) - return m_errlog_defer_3(scanent, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read answer %d (%s)", i, strerror(errno)), - sock); - for (j = 0; j < bread; j++) + malware_daemon_ctx.sock); + for (int j = 0; j < bread; j++) if (av_buffer[j] == '\r' || av_buffer[j] == '\n') av_buffer[j] ='@'; } @@ -885,8 +1079,8 @@ badseek: err = errno; /* pass the mailfile to fsecure */ file_name = string_sprintf("SCAN\t%s\n", eml_filename); - if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, file_name, Ustrlen(file_name), &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); /* set up match */ /* todo also SUSPICION\t */ @@ -903,10 +1097,10 @@ badseek: err = errno; { errno = ETIMEDOUT; i = av_buffer+sizeof(av_buffer)-p; - if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0) - return m_errlog_defer_3(scanent, CUS callout_address, + if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo)) < 0) + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read result (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1) { @@ -931,7 +1125,9 @@ badseek: err = errno; fsec_found: break; } /* fsecure */ +#endif +#ifndef DISABLE_MAL_KAV case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */ { time_t t; @@ -961,28 +1157,28 @@ badseek: err = errno; scanner_name, scanner_options); /* send scan request */ - if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); /* wait for result */ - if (!recv_len(sock, tmpbuf, 2, tmo)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"unable to read 2 bytes from socket.", sock); + if (!recv_len(malware_daemon_ctx.sock, tmpbuf, 2, tmo)) + return m_panic_defer_3(scanent, CUS callout_address, + US"unable to read 2 bytes from socket.", malware_daemon_ctx.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, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, US"please reconfigure kavdaemon to NOT disinfect or remove infected files.", - sock); + malware_daemon_ctx.sock); case 1: - return m_errlog_defer_3(scanent, CUS callout_address, - US"reported 'scanning not completed' (code 1).", sock); + return m_panic_defer_3(scanent, CUS callout_address, + US"reported 'scanning not completed' (code 1).", malware_daemon_ctx.sock); case 7: - return m_errlog_defer_3(scanent, CUS callout_address, - US"reported 'kavdaemon damaged' (code 7).", sock); + return m_panic_defer_3(scanent, CUS callout_address, + US"reported 'kavdaemon damaged' (code 7).", malware_daemon_ctx.sock); } /* code 8 is not handled, since it is ambiguous. It appears mostly on @@ -1002,9 +1198,9 @@ badseek: err = errno; if (report_flag == 1) { /* read report size */ - if (!recv_len(sock, &kav_reportlen, 4, tmo)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"cannot read report size", sock); + if (!recv_len(malware_daemon_ctx.sock, &kav_reportlen, 4, tmo)) + return m_panic_defer_3(scanent, CUS callout_address, + US"cannot read report size", malware_daemon_ctx.sock); /* it's possible that avp returns av_buffer[1] == 1 but the reportsize is 0 (!?) */ @@ -1027,7 +1223,7 @@ badseek: err = errno; /* coverity[tainted_data] */ while (kav_reportlen > 0) { - if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0) + if ((bread = recv_line(malware_daemon_ctx.sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0) break; kav_reportlen -= bread+1; @@ -1043,7 +1239,9 @@ badseek: err = errno; break; } +#endif +#ifndef DISABLE_MAL_CMDLINE case M_CMDL: /* "cmdline" scanner type ---------------------------------- */ { const uschar *cmdline_scanner = scanner_options; @@ -1062,19 +1260,19 @@ badseek: err = errno; uschar *p; if (!cmdline_scanner) - return m_errlog_defer(scanent, NULL, errstr); + return m_panic_defer(scanent, NULL, errstr); /* find scanner output trigger */ cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing trigger specification", &errstr); if (!cmdline_trigger_re) - return m_errlog_defer(scanent, NULL, errstr); + return m_panic_defer(scanent, NULL, errstr); /* find scanner name regex */ cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing virus name regex specification", &errstr); if (!cmdline_regex_re) - return m_errlog_defer(scanent, NULL, errstr); + return m_panic_defer(scanent, NULL, errstr); /* prepare scanner call; despite the naming, file_name holds a directory name which is documented as the value given to %s. */ @@ -1099,7 +1297,7 @@ badseek: err = errno; { int err = errno; signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, string_sprintf("call (%s) failed: %s.", commandline, strerror(err))); } scanner_fd = fileno(scanner_out); @@ -1111,7 +1309,7 @@ badseek: err = errno; int err = errno; (void) pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, NULL, string_sprintf( + return m_panic_defer(scanent, NULL, string_sprintf( "opening scanner output file (%s) failed: %s.", file_name, strerror(err))); } @@ -1127,7 +1325,7 @@ badseek: err = errno; break; (void) pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, NULL, string_sprintf( + return m_panic_defer(scanent, NULL, string_sprintf( "unable to read from scanner (%s): %s", commandline, strerror(err))); } @@ -1137,7 +1335,7 @@ badseek: err = errno; /* short write */ (void) pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); - return m_errlog_defer(scanent, NULL, string_sprintf( + return m_panic_defer(scanent, NULL, string_sprintf( "short write on scanner output file (%s).", file_name)); } putc('\n', scanner_record); @@ -1152,7 +1350,7 @@ badseek: err = errno; sep = pclose(scanner_out); signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); if (sep != 0) - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, sep == -1 ? string_sprintf("running scanner failed: %s", strerror(sep)) : string_sprintf("scanner returned error code: %d", sep)); @@ -1164,20 +1362,19 @@ badseek: err = errno; 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))) + scanner_record = Ufopen(file_name, "rb"); + while (Ufgets(linebuffer, sizeof(linebuffer), scanner_record)) + if ((s = m_pcre_exec(cmdline_regex_re, linebuffer))) /* try match */ malware_name = s; - } (void)fclose(scanner_record); } else /* no virus found */ malware_name = NULL; break; } /* cmdline */ +#endif +#ifndef DISABLE_MAL_SOPHIE case M_SOPHIE: /* "sophie" scanner type --------------------------------- */ { int bread = 0; @@ -1193,19 +1390,19 @@ badseek: err = errno; DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n", scanner_name, scanner_options); - if ( write(sock, file_name, Ustrlen(file_name)) < 0 - || write(sock, "\n", 1) != 1 + if ( write(malware_daemon_ctx.sock, file_name, Ustrlen(file_name)) < 0 + || write(malware_daemon_ctx.sock, "\n", 1) != 1 ) - return m_errlog_defer_3(scanent, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to write to UNIX socket (%s)", scanner_options), - sock); + malware_daemon_ctx.sock); /* wait for result */ memset(av_buffer, 0, sizeof(av_buffer)); - if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0) - return m_errlog_defer_3(scanent, CUS callout_address, + if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo)) <= 0) + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read from UNIX socket (%s)", scanner_options), - sock); + malware_daemon_ctx.sock); /* infected ? */ if (av_buffer[0] == '1') { @@ -1215,14 +1412,16 @@ badseek: err = errno; malware_name = string_copy(&av_buffer[2]); } else if (!strncmp(CS av_buffer, "-1", 2)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"scanner reported error", sock); + return m_panic_defer_3(scanent, CUS callout_address, + US"scanner reported error", malware_daemon_ctx.sock); else /* all ok, no virus */ malware_name = NULL; break; } +#endif +#ifndef DISABLE_MAL_CLAM case M_CLAMD: /* "clamd" scanner type ----------------------------------- */ { /* This code was originally contributed by David Saez */ @@ -1233,13 +1432,11 @@ badseek: err = errno; * 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. +* the TCP-connected daemon is actually local; otherwise we use zINSTREAM * See Exim bug 926 for details. */ uschar *p, *vname, *result_tag; int bread=0; - uschar * file_name; uschar av_buffer[1024]; uschar *hostname = US""; host_item connhost; @@ -1250,13 +1447,7 @@ badseek: err = errno; BOOL use_scan_command = FALSE; clamd_address * cv[MAX_CLAMD_SERVERS]; int num_servers = 0; -#ifdef WITH_OLD_CLAMAV_STREAM - unsigned int port; - uschar av_buffer2[1024]; - int sockData; -#else uint32_t send_size, send_final_zeroblock; -#endif blob cmd_str; /*XXX if unixdomain socket, only one server supported. Needs fixing; @@ -1271,7 +1462,7 @@ badseek: err = errno; /* 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)); + cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE); /* extract socket-path part */ sublist = scanner_options; @@ -1279,7 +1470,7 @@ badseek: err = errno; /* parse options */ if (clamd_option(cd, sublist, &subsep) != OK) - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, string_sprintf("bad option '%s'", scanner_options)); cv[0] = cd; } @@ -1305,19 +1496,19 @@ badseek: err = errno; continue; } - cd = (clamd_address *) store_get(sizeof(clamd_address)); + cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE); /* extract host and port part */ sublist = scanner_options; if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0))) { - (void) m_errlog_defer(scanent, NULL, + (void) m_panic_defer(scanent, NULL, string_sprintf("missing address: '%s'", scanner_options)); continue; } if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0))) { - (void) m_errlog_defer(scanent, NULL, + (void) m_panic_defer(scanent, NULL, string_sprintf("missing port: '%s'", scanner_options)); continue; } @@ -1326,13 +1517,13 @@ badseek: err = errno; /* parse options */ /*XXX should these options be common over scanner types? */ if (clamd_option(cd, sublist, &subsep) != OK) - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, string_sprintf("bad option '%s'", scanner_options)); cv[num_servers++] = cd; if (num_servers >= MAX_CLAMD_SERVERS) { - (void) m_errlog_defer(scanent, NULL, + (void) m_panic_defer(scanent, NULL, US"More than " MAX_CLAMD_SERVERS_S " clamd servers " "specified; only using the first " MAX_CLAMD_SERVERS_S ); break; @@ -1342,24 +1533,20 @@ badseek: err = errno; /* check if we have at least one server */ if (!num_servers) - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, US"no useable server addresses in malware configuration option."); } /* See the discussion of response formats below to see why we really don't like colons in filenames when passing filenames to ClamAV. */ if (use_scan_command && Ustrchr(eml_filename, ':')) - return m_errlog_defer(scanent, NULL, + return m_panic_defer(scanent, NULL, string_sprintf("local/SCAN mode incompatible with" \ " : in path to email filename [%s]", eml_filename)); /* Set up the very first data we will be sending */ if (!use_scan_command) -#ifdef WITH_OLD_CLAMAV_STREAM - { cmd_str.data = US"STREAM\n"; cmd_str.len = 7; } -#else { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; } -#endif else { cmd_str.data = string_sprintf("SCAN %s\n", eml_filename); @@ -1385,7 +1572,8 @@ badseek: err = errno; * on both connections (as one host could resolve to multiple ips) */ for (;;) { - if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port, + /*XXX we trust that the cmd_str is ideempotent */ + if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr, &cmd_str)) >= 0) { /* Connection successfully established with a server */ @@ -1396,10 +1584,10 @@ badseek: err = errno; if (cd->retry <= 0) break; while (cd->retry > 0) cd->retry = sleep(cd->retry); } - if (sock >= 0) + if (malware_daemon_ctx.sock >= 0) break; - (void) m_errlog_defer(scanent, CUS callout_address, errstr); + (void) m_panic_defer(scanent, CUS callout_address, errstr); /* Remove the server from the list. XXX We should free the memory */ num_servers--; @@ -1408,18 +1596,18 @@ badseek: err = errno; } if (num_servers == 0) - return m_errlog_defer(scanent, NULL, US"all servers failed"); + return m_panic_defer(scanent, NULL, US"all servers failed"); } else for (;;) { - if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0) + if ((malware_daemon_ctx.sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0) { hostname = cv[0]->hostspec; break; } if (cv[0]->retry <= 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + return m_panic_defer(scanent, CUS callout_address, errstr); while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry); } @@ -1430,50 +1618,6 @@ badseek: err = errno; 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_indent( - "Malware scan: issuing %s old-style remote scan (PORT)\n", - scanner_name); - - /* Pass the string to ClamAV (7 = "STREAM\n"), if not already sent */ - if (cmd_str.len) - if (m_sock_send(sock, cmd_str.data, cmd_str.len, &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); - - memset(av_buffer2, 0, sizeof(av_buffer2)); - bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL)); - - if (bread < 0) - return m_errlog_defer_3(scanent, CUS callout_address, - string_sprintf("unable to read PORT from socket (%s)", - strerror(errno)), - sock); - - if (bread == sizeof(av_buffer2)) - return m_errlog_defer_3(scanent, CUS callout_address, - "buffer too small", sock); - - if (!(*av_buffer2)) - return m_errlog_defer_3(scanent, CUS callout_address, - "ClamAV returned null", sock); - - av_buffer2[bread] = '\0'; - if(sscanf(CS av_buffer2, "PORT %u\n", &port) != 1) - return m_errlog_defer_3(scanent, CUS callout_address, - string_sprintf("Expected port information from clamd, got '%s'", - av_buffer2), - sock); - - sockData = m_tcpsocket(connhost.address, port, NULL, &errstr, NULL); - if (sockData < 0) - return m_errlog_defer_3(scanent, CUS callout_address, 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. */ @@ -1484,95 +1628,76 @@ badseek: err = errno; /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */ if (cmd_str.len) - if (send(sock, cmd_str.data, cmd_str.len, 0) < 0) - return m_errlog_defer_3(scanent, CUS hostname, + if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0) + return m_panic_defer_3(scanent, CUS hostname, string_sprintf("unable to send zINSTREAM to socket (%s)", strerror(errno)), - sock); - -# define CLOSE_SOCKDATA /**/ -#endif + malware_daemon_ctx.sock); /* calc file size */ - if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0) + if ((clam_fd = exim_open2(CS eml_filename, O_RDONLY)) < 0) { int err = errno; - CLOSE_SOCKDATA; - return m_errlog_defer_3(scanent, NULL, + return m_panic_defer_3(scanent, NULL, string_sprintf("can't open spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) { int err; b_seek: err = errno; - CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, + (void)close(clam_fd); + return m_panic_defer_3(scanent, NULL, string_sprintf("can't seek spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } fsize_uint = (unsigned int) fsize; if ((off_t)fsize_uint != fsize) { - CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, + (void)close(clam_fd); + return m_panic_defer_3(scanent, NULL, string_sprintf("seeking spool file %s, size overflow", eml_filename), - sock); + malware_daemon_ctx.sock); } if (lseek(clam_fd, 0, SEEK_SET) < 0) goto b_seek; - if (!(clamav_fbuf = US malloc(fsize_uint))) + if (!(clamav_fbuf = store_malloc(fsize_uint))) { - CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, + (void)close(clam_fd); + return m_panic_defer_3(scanent, NULL, string_sprintf("unable to allocate memory %u for file (%s)", fsize_uint, eml_filename), - sock); + malware_daemon_ctx.sock); } if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) { int err = errno; - free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, + store_free(clamav_fbuf); (void)close(clam_fd); + return m_panic_defer_3(scanent, NULL, string_sprintf("can't read spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } (void)close(clam_fd); /* send file body to socket */ -#ifdef WITH_OLD_CLAMAV_STREAM - if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0) - { - free(clamav_fbuf); CLOSE_SOCKDATA; - return m_errlog_defer_3(scanent, NULL, - string_sprintf("unable to send file body to socket (%s:%u)", - hostname, port), - sock); - } -#else send_size = htonl(fsize_uint); send_final_zeroblock = 0; - if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || - (send(sock, clamav_fbuf, fsize_uint, 0) < 0) || - (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) + if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) || + (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) || + (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) { - free(clamav_fbuf); - return m_errlog_defer_3(scanent, NULL, + store_free(clamav_fbuf); + return m_panic_defer_3(scanent, NULL, string_sprintf("unable to send file body to socket (%s)", hostname), - sock); + malware_daemon_ctx.sock); } -#endif - - free(clamav_fbuf); - - CLOSE_SOCKDATA; -#undef CLOSE_SOCKDATA + store_free(clamav_fbuf); } else { /* use scan command */ @@ -1596,10 +1721,10 @@ b_seek: err = errno; scanner_name, scanner_options); if (cmd_str.len) - if (send(sock, cmd_str.data, cmd_str.len, 0) < 0) - return m_errlog_defer_3(scanent, CUS callout_address, + if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0) + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to write to socket (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); /* Do not shut down the socket for writing; a user report noted that * clamd 0.70 does not react well to this. */ @@ -1609,17 +1734,18 @@ b_seek: err = errno; /* Read the result */ memset(av_buffer, 0, sizeof(av_buffer)); - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); - (void)close(sock); - sock = -1; + bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); + (void)close(malware_daemon_ctx.sock); + malware_daemon_ctx.sock = -1; + malware_daemon_ctx.tls_ctx = NULL; if (bread <= 0) - return m_errlog_defer(scanent, CUS callout_address, + return m_panic_defer(scanent, CUS callout_address, string_sprintf("unable to read from socket (%s)", errno == 0 ? "EOF" : strerror(errno))); if (bread == sizeof(av_buffer)) - return m_errlog_defer(scanent, CUS callout_address, + return m_panic_defer(scanent, CUS callout_address, US"buffer too small"); /* We're now assured of a NULL at the end of av_buffer */ @@ -1644,7 +1770,7 @@ b_seek: err = errno; passing a filename to clamd). */ if (!(*av_buffer)) - return m_errlog_defer(scanent, CUS callout_address, + return m_panic_defer(scanent, CUS callout_address, US"ClamAV returned null"); /* strip newline at the end (won't be present for zINSTREAM) @@ -1661,7 +1787,7 @@ b_seek: err = errno; /* colon in returned output? */ if(!(p = Ustrchr(av_buffer,':'))) - return m_errlog_defer(scanent, CUS callout_address, string_sprintf( + return m_panic_defer(scanent, CUS callout_address, string_sprintf( "ClamAV returned malformed result (missing colon): %s", av_buffer)); @@ -1694,7 +1820,7 @@ b_seek: err = errno; } else if (Ustrcmp(result_tag, "ERROR") == 0) - return m_errlog_defer(scanent, CUS callout_address, + return m_panic_defer(scanent, CUS callout_address, string_sprintf("ClamAV returned: %s", av_buffer)); else if (Ustrcmp(result_tag, "OK") == 0) @@ -1705,12 +1831,14 @@ b_seek: err = errno; } else - return m_errlog_defer(scanent, CUS callout_address, + return m_panic_defer(scanent, CUS callout_address, string_sprintf("unparseable response from ClamAV: {%s}", av_buffer)); break; } /* clamd */ +#endif +#ifndef DISABLE_MAL_SOCK 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 @@ -1734,8 +1862,8 @@ b_seek: err = errno; uschar * s = Ustrchr(sockline_scanner, '%'); if (s++) if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%')) - return m_errlog_defer_3(scanent, NULL, - US"unsafe sock scanner call spec", sock); + return m_panic_defer_3(scanent, NULL, + US"unsafe sock scanner call spec", malware_daemon_ctx.sock); } else sockline_scanner = sockline_scanner_default; @@ -1746,13 +1874,13 @@ b_seek: err = errno; sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing trigger specification", &errstr); if (!sockline_trig_re) - return m_errlog_defer_3(scanent, NULL, errstr, sock); + return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.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, NULL, errstr, sock); + return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock); /* prepare scanner call - security depends on expansions check above */ commandline = string_sprintf( CS sockline_scanner, CS eml_filename); @@ -1760,20 +1888,20 @@ b_seek: err = errno; string_printing(commandline)); /* Pass the command string to the socket */ - if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if (m_sock_send(malware_daemon_ctx.sock, commandline, Ustrlen(commandline), &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); /* Read the result */ - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); + bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); if (bread <= 0) - return m_errlog_defer_3(scanent, CUS callout_address, + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read from socket (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); if (bread == sizeof(av_buffer)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"buffer too small", sock); + return m_panic_defer_3(scanent, CUS callout_address, + US"buffer too small", malware_daemon_ctx.sock); av_buffer[bread] = '\0'; linebuffer = string_copy(av_buffer); DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ", @@ -1791,7 +1919,9 @@ b_seek: err = errno; malware_name = NULL; break; } +#endif +#ifndef DISABLE_MAL_MKS case M_MKSD: /* "mksd" scanner type ------------------------------------- */ { char *mksd_options_end; @@ -1806,32 +1936,36 @@ b_seek: err = errno; || mksd_maxproc < 1 || mksd_maxproc > 32 ) - return m_errlog_defer(scanent, CUS callout_address, + return m_panic_defer(scanent, CUS callout_address, string_sprintf("invalid option '%s'", scanner_options)); } - if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); + if((malware_daemon_ctx.sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); malware_name = NULL; DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name); - if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK) + if ((retval = mksd_scan_packed(scanent, malware_daemon_ctx.sock, eml_filename, tmo)) != OK) { - close (sock); + close (malware_daemon_ctx.sock); return retval; } break; } +#endif +#ifndef DISABLE_MAL_AVAST case M_AVAST: /* "avast" scanner type ----------------------------------- */ { - int ovector[1*3]; uschar buf[1024]; uschar * scanrequest; enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage; int nread; + uschar * error_message = NULL; + BOOL more_data = FALSE; + BOOL strict = TRUE; /* According to Martin Tuma @avast the protocol uses "escaped whitespace", that is, every embedded whitespace is backslash @@ -1840,34 +1974,75 @@ b_seek: err = errno; and the [ ] marker. [+] - not infected [L] - infected - [E] - some error occured - Such marker follows the first non-escaped TAB. */ + [E] - some error occurred + Such marker follows the first non-escaped TAB. For more information + see avast-protocol(5) + + We observed two cases: + -> SCAN /file + <- /file [E]0.0 Error 13 Permission denied + <- 451 SCAN Engine error 13 permission denied + + -> SCAN /file + <- /file… [E]3.0 Error 41120 The file is a decompression bomb + <- /file… [+]2.0 + <- /file… [+]2.0 0 Eicar Test Virus!!! + <- 200 SCAN OK + + If the scanner returns 4xx, DEFER is a good decision, combined + with a panic log entry, to get the admin's attention. + + If the scanner returns 200, we reject it as malware, if found any, + or, in case of an error, we set the malware message to the error + string. + + Some of the >= 42000 errors are message related - usually some + broken archives etc, but some of them are e.g. license related. + Once the license expires the engine starts returning errors for + every scanning attempt. I¹ have the full list of the error codes + but it is not a public API and is subject to change. It is hard + for me to say what you should do in case of an engine error. You + can have a “Treat * unscanned file as infection” policy or “Treat + unscanned file as clean” policy. ¹) Jakub Bednar + + */ + 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))) + || ( !ava_re_error + && !(ava_re_error = m_pcre_compile(ava_re_error_str, &errstr))) ) - return malware_errlog_defer(errstr); + return malware_panic_defer(errstr); /* wait for result */ for (avast_stage = AVA_HELO; - (nread = recv_line(sock, buf, sizeof(buf), tmo)) > 0; + (nread = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) > 0; ) { int slen = Ustrlen(buf); if (slen >= 1) { - DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf); + + /* Multi line responses are bracketed between 210 … and nnn … */ + if (Ustrncmp(buf, "210", 3) == 0) + { + more_data = 1; + continue; + } + else if (more_data && isdigit(buf[0])) more_data = 0; + switch (avast_stage) { case AVA_HELO: + if (more_data) continue; if (Ustrncmp(buf, "220", 3) != 0) goto endloop; /* require a 220 */ goto sendreq; case AVA_OPT: - if (Ustrncmp(buf, "210", 3) == 0) - break; /* ignore 210 responses */ + if (more_data) continue; if (Ustrncmp(buf, "200", 3) != 0) goto endloop; /* require a 200 */ @@ -1878,134 +2053,103 @@ b_seek: err = errno; if ((scanrequest = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) { + if (Ustrcmp(scanrequest, "pass_unscanned") == 0) + { + DEBUG(D_acl) debug_printf_indent("pass unscanned files as clean\n"); + strict = FALSE; + goto sendreq; + } scanrequest = string_sprintf("%s\n", scanrequest); avast_stage = AVA_OPT; /* just sent option */ + DEBUG(D_acl) debug_printf_indent("send to avast OPTION: %s", scanrequest); } else { scanrequest = string_sprintf("SCAN %s\n", eml_dir); avast_stage = AVA_RSP; /* just sent command */ + DEBUG(D_acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir); } /* send config-cmd or scan-request to socket */ len = Ustrlen(scanrequest); - if (send(sock, scanrequest, len, 0) < 0) + if (send(malware_daemon_ctx.sock, scanrequest, len, 0) == -1) { scanrequest[len-1] = '\0'; - return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf( + return m_panic_defer_3(scanent, CUS callout_address, string_sprintf( "unable to send request '%s' to socket (%s): %s", - scanrequest, scanner_options, strerror(errno)), sock); + scanrequest, scanner_options, strerror(errno)), malware_daemon_ctx.sock); } break; } case AVA_RSP: - if (Ustrncmp(buf, "210", 3) == 0) - break; /* ignore the "210 SCAN DATA" message */ - if (pcre_exec(ava_re_clean, NULL, CS buf, slen, - 0, 0, ovector, nelements(ovector)) > 0) - break; + if (isdigit(buf[0])) /* We're done */ + goto endloop; - 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) - if (*p == '\\' && (isspace(p[1]) || p[1] == '\\')) - for (p0 = p; *p0; ++p0) *p0 = p0[1]; + if (malware_name) /* Nothing else matters, just read on */ + break; - avast_stage = AVA_DONE; - goto endloop; - } + if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0) + break; - if (Ustrncmp(buf, "200 SCAN OK", 11) == 0) - { /* we're done finally */ - if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */ - return m_errlog_defer_3(scanent, CUS callout_address, - string_sprintf( - "unable to send quit request to socket (%s): %s", - scanner_options, strerror(errno)), - sock); - malware_name = NULL; - avast_stage = AVA_DONE; - goto endloop; - } + if ((malware_name = m_pcre_exec(ava_re_virus, buf))) + { + unescape(malware_name); + DEBUG(D_acl) + debug_printf_indent("unescaped malware name: '%s'\n", malware_name); + break; + } + + if (strict) /* treat scanner errors as malware */ + { + if ((malware_name = m_pcre_exec(ava_re_error, buf))) + { + unescape(malware_name); + DEBUG(D_acl) + debug_printf_indent("unescaped error message: '%s'\n", malware_name); + break; + } + } + else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0) + { + log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf); + break; + } + + /* here also for any unexpected response from the scanner */ + DEBUG(D_acl) debug_printf("avast response not handled: '%s'\n", buf); - /* here for any unexpected response from the scanner */ goto endloop; - case AVA_DONE: log_write(0, LOG_PANIC, "%s:%d:%s: should not happen", + default: log_write(0, LOG_PANIC, "%s:%d:%s: should not happen", __FILE__, __LINE__, __FUNCTION__); } } } - endloop: - - switch(avast_stage) - { - case AVA_HELO: - case AVA_OPT: - case AVA_RSP: return m_errlog_defer_3(scanent, CUS callout_address, - nread >= 0 - ? string_sprintf( - "invalid response from scanner: '%s'", buf) - : nread == -1 - ? US"EOF from scanner" - : US"timeout from scanner", - sock); - default: break; - } - break; - } - case M_FPROT6D: /* "f-prot6d" scanner type ----------------------------------- */ - { - int bread; - uschar * e; - uschar * linebuffer; - uschar * scanrequest; - uschar av_buffer[1024]; - - if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, &errstr))) - || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, &errstr)))) - return malware_errlog_defer(errstr); - - scanrequest = string_sprintf("SCAN FILE %s\n", eml_filename); - DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n", - scanner_name, scanrequest); - - if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0) - return m_errlog_defer(scanent, CUS callout_address, errstr); - - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); - - if (bread <= 0) - return m_errlog_defer_3(scanent, CUS callout_address, - string_sprintf("unable to read from socket (%s)", strerror(errno)), - sock); + endloop: - if (bread == sizeof(av_buffer)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"buffer too small", sock); + if (nread == -1) error_message = US"EOF from scanner"; + else if (nread < 0) error_message = US"timeout from scanner"; + else if (nread == 0) error_message = US"got nothing from scanner"; + else if (buf[0] != '2') error_message = buf; - av_buffer[bread] = '\0'; - linebuffer = string_copy(av_buffer); - - m_sock_send(sock, US"QUIT\n", 5, 0); + DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n"); + if (send(malware_daemon_ctx.sock, "QUIT\n", 5, 0) == -1) + return m_panic_defer_3(scanent, CUS callout_address, + string_sprintf("unable to send quit request to socket (%s): %s", + scanner_options, strerror(errno)), malware_daemon_ctx.sock); - if ((e = m_pcre_exec(fprot6d_re_error, linebuffer))) - return m_errlog_defer_3(scanent, CUS callout_address, - string_sprintf("scanner reported error (%s)", e), sock); + if (error_message) + return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock); - if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer))) - malware_name = NULL; - - break; - } /* f-prot6d */ + } +#endif } /* scanner type switch */ - if (sock >= 0) - (void) close (sock); + if (malware_daemon_ctx.sock >= 0) + (void) close (malware_daemon_ctx.sock); malware_ok = TRUE; /* set "been here, done that" marker */ } @@ -2076,7 +2220,7 @@ sender_address = US"malware-sender@example.net"; return_path = US""; recipients_list = NULL; receive_add_recipient(US"malware-victim@example.net", -1); -enable_dollar_recipients = TRUE; +f.enable_dollar_recipients = TRUE; ret = malware_internal(US"*", eml_filename, 0); @@ -2100,24 +2244,48 @@ malware_init(void) { if (!malware_default_re) malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE); + +#ifndef DISABLE_MAL_DRWEB if (!drweb_re) drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE); +#endif +#ifndef DISABLE_MAL_FSECURE if (!fsec_re) fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE); +#endif +#ifndef DISABLE_MAL_KAV 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); +#endif +#ifndef DISABLE_MAL_AVAST if (!ava_re_clean) ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE); if (!ava_re_virus) ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE); +if (!ava_re_error) + ava_re_error = regex_must_compile(ava_re_error_str, FALSE, TRUE); +#endif +#ifndef DISABLE_MAL_FFROT6D if (!fprot6d_re_error) fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE); if (!fprot6d_re_virus) fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, FALSE, TRUE); +#endif } + +void +malware_show_supported(FILE * f) +{ +fprintf(f, "Malware:"); +for (struct scan * sc = m_scans; sc->scancode != (scanner_t)-1; sc++) fprintf(f, " %s", sc->name); +fprintf(f, "\n"); +} + + +# endif /*!MACRO_PREDEF*/ #endif /*WITH_CONTENT_SCAN*/ /* * vi: aw ai sw=2