X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/10ac8d7f410e55043b0b182fb77f2e8fb6a7aed4..ba47f2909371787b76d213ea012c6b950f9f8080:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index e39cea814..8b5ec27c4 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -2,18 +2,56 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) Tom Kistner 2003 - 2015 +/* + * Copyright (c) The Exim Maintainers 2015 - 2022 + * Copyright (c) Tom Kistner 2003 - 2015 * License: GPL - * Copyright (c) The Exim Maintainers 2016 */ /* 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 +61,143 @@ 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[EXIM_DRIVERNAME_MAX]; + +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 pcre2_code * 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" */ +#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" */ -#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 * 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; +static const pcre2_code * 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; +static const pcre2_code * 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; +static const pcre2_code * kav_re_sus = NULL; +static const pcre2_code * 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 pcre * ava_re_clean = NULL; -static const pcre * ava_re_virus = NULL; +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 pcre2_code * ava_re_clean = NULL; +static const pcre2_code * ava_re_virus = NULL; +static const pcre2_code * 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; +static const pcre2_code * fprot6d_re_error = NULL; +static const pcre2_code * 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,31 +217,54 @@ 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 */ + +#ifndef DISABLE_MAL_AVAST +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]; +} +#endif +/* --- 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, +/* --- m_*_defer_3 */ +static inline int +m_panic_defer_3(struct scan * scanent, const uschar * hostport, const uschar * str, int fd_to_close) { +DEBUG(D_acl) debug_print_socket(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. */ @@ -149,9 +272,17 @@ static inline int m_tcpsocket(const uschar * hostname, unsigned int port, host_item * host, uschar ** errstr, const blob * fastopen_blob) { -return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, +int fd = ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr, fastopen_blob); +#ifdef EXIM_TFO_FREEBSD +/* Under some fault conditions, FreeBSD 12.2 seen to send a (non-TFO) SYN +and, getting no response, wait for a long time. Impose a 5s max. */ +if (fd >= 0) + (void) poll_one_fd(fd, POLLOUT, 5 * 1000); +#endif +return fd; } +#endif static int m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr) @@ -167,38 +298,33 @@ if (send(sock, buf, cnt, 0) < 0) return sock; } -static const pcre * -m_pcre_compile(const uschar * re, uschar ** errstr) +static const pcre2_code * +m_pcre_compile(const uschar * re, BOOL cacheable, 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; +return regex_compile(re, cacheable ? MCS_CACHEABLE : MCS_NOFLAGS, errstr, + pcre_gen_cmp_ctx); } uschar * -m_pcre_exec(const pcre * cre, uschar * text) +m_pcre_exec(const pcre2_code * 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; +pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx); +int i = pcre2_match(cre, text, PCRE2_ZERO_TERMINATED, 0, 0, md, pcre_gen_mtc_ctx); +PCRE2_UCHAR * substr = NULL; +PCRE2_SIZE slen; + if (i >= 2) /* Got it */ - pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); -return substr; + pcre2_substring_get_bynumber(md, 1, &substr, &slen); /* uses same ctx as md */ +/* pcre2_match_data_free(md); gen ctx needs no free */ +return US substr; } -static const pcre * +static const pcre2_code * m_pcre_nextinlist(const uschar ** list, int * sep, - char * listerr, uschar ** errstr) + BOOL cacheable, char * listerr, uschar ** errstr) { const uschar * list_ele; -const pcre * cre = NULL; +const pcre2_code * cre = NULL; if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) *errstr = US listerr; @@ -206,11 +332,12 @@ else { DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ", string_printing(list_ele)); - cre = m_pcre_compile(CUS list_ele, errstr); + cre = m_pcre_compile(CUS list_ele, cacheable, errstr); } return cre; } + /* Simple though inefficient wrapper for reading a line. Drop CRs and the trailing newline. Can return early on buffer full. Null-terminate. @@ -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 */ @@ -242,8 +369,12 @@ while ((rcv = read(fd, p, 1)) > 0) } if (!ok) { - DEBUG(D_acl) debug_printf_indent("Malware scan: read %s (%s)\n", + DEBUG(D_acl) + { + debug_printf_indent("Malware scan: read %s (%s)\n", rcv==0 ? "EOF" : "error", strerror(errno)); + debug_print_socket(fd); + } return rcv==0 ? -1 : -2; } *p = '\0'; @@ -253,19 +384,22 @@ return p - buffer; } /* return TRUE iff size as requested */ +#ifndef DISABLE_MAL_DRWEB 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; } +#endif +#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 +413,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 +435,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 +454,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 +477,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 +495,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 +523,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 +545,9 @@ while ((s = string_nextinlist(&optstr, subsep, NULL, 0))) return FAIL; return OK; } +#endif + + /************************************************* * Scan content for malware * @@ -418,6 +558,7 @@ is via malware(), or there's malware_in_file() used for testing/debugging. Arguments: malware_re match condition for "malware=" + cacheable the RE did not use any dynamic elements during expansion scan_filename the file holding the email to be scanned, if we're faking this up for the -bmalware test, else NULL timeout if nonzero, non-default timeoutl @@ -426,19 +567,20 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ static int -malware_internal(const uschar * malware_re, const uschar * scan_filename, - int timeout) +malware_internal(const uschar * malware_re, BOOL cacheable, + const uschar * scan_filename, int timeout) { int sep = 0; const uschar *av_scanner_work = av_scanner; +BOOL av_scanner_textonly; uschar *scanner_name; unsigned long mbox_size; FILE *mbox_file; -const pcre *re; +const pcre2_code *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 +590,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. */ @@ -457,31 +599,31 @@ the name), so we can close it right away. Get the directory too. */ eml_dir = string_copyn(eml_filename, Ustrrchr(eml_filename, '/') - eml_filename); /* parse 1st option */ -if (strcmpic(malware_re, US"false") == 0 || Ustrcmp(malware_re,"0") == 0) +if (strcmpic(malware_re, US"false") == 0 || Ustrcmp(malware_re, "0") == 0) return FAIL; /* explicitly no matching */ /* special cases (match anything except empty) */ -if ( strcmpic(malware_re,US"true") == 0 - || Ustrcmp(malware_re,"*") == 0 - || Ustrcmp(malware_re,"1") == 0 +if ( strcmpic(malware_re, US"true") == 0 + || Ustrcmp(malware_re, "*") == 0 + || Ustrcmp(malware_re, "1") == 0 ) { if ( !malware_default_re - && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr))) - return malware_errlog_defer(errstr); + && !(malware_default_re = m_pcre_compile(malware_regex_default, FALSE, &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); +else if (!(re = m_pcre_compile(malware_re, cacheable, &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( + if (!(av_scanner_work = expand_string_2(av_scanner, &av_scanner_textonly))) + return malware_panic_defer( string_sprintf("av_scanner starts with $, but expansion failed: %s", expand_string_message)); @@ -491,20 +633,22 @@ if (*av_scanner == '$') malware_name = NULL; malware_ok = FALSE; } +else + av_scanner_textonly = TRUE; /* 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, 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 +663,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 +703,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 */ malware_name = US"unknown"; /* set up match regex */ if (!drweb_re) - drweb_re = m_pcre_compile(drweb_re_str, &errstr); + drweb_re = m_pcre_compile(drweb_re_str, FALSE, &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 size = 0, off = 0, ovector[10*3]; + pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx); + /* 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, GET_TAINTED); /* 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)); + result = pcre2_match(drweb_re, (PCRE2_SPTR)tmpbuf, PCRE2_ZERO_TERMINATED, + 0, 0, md, pcre_gen_mtc_ctx); if (result >= 2) { - const char * pre_malware_nb; - - pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); + PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); if (i==0) /* the first name we just copy to malware_name */ - malware_name = string_append(NULL, &size, &off, - 1, pre_malware_nb); + g = string_catn(NULL, US ovec[2], ovec[3] - ovec[2]); 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); + { + g = string_catn(g, US"/", 1); + g = string_catn(g, US ovec[2], ovec[3] - ovec[2]); + } } + /* pcre2_match_data_free(md); gen ctx needs no free */ } + malware_name = string_from_gstring(g); } else { @@ -769,16 +968,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]; @@ -786,13 +987,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", @@ -801,19 +1002,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; @@ -826,30 +1027,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", @@ -863,19 +1066,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] ='@'; } @@ -883,13 +1086,13 @@ 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 */ if (!fsec_re) - fsec_re = m_pcre_compile(fsec_re_str, &errstr); + fsec_re = m_pcre_compile(fsec_re_str, FALSE, &errstr); /* read report, linewise. Apply a timeout as the Fsecure daemon sometimes wants an answer to "PING" but they won't tell us what */ @@ -901,10 +1104,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) { @@ -929,7 +1132,9 @@ badseek: err = errno; fsec_found: break; } /* fsecure */ +#endif +#ifndef DISABLE_MAL_KAV case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */ { time_t t; @@ -938,7 +1143,7 @@ badseek: err = errno; int kav_rc; unsigned long kav_reportlen; int bread; - const pcre *kav_re; + const pcre2_code *kav_re; uschar *p; /* get current date and time, build scan request */ @@ -959,28 +1164,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 @@ -1000,9 +1205,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 (!?) */ @@ -1011,12 +1216,12 @@ badseek: err = errno; /* set up match regex, depends on retcode */ if (kav_rc == 3) { - if (!kav_re_sus) kav_re_sus = m_pcre_compile(kav_re_sus_str, &errstr); + if (!kav_re_sus) kav_re_sus = m_pcre_compile(kav_re_sus_str, FALSE, &errstr); kav_re = kav_re_sus; } else { - if (!kav_re_inf) kav_re_inf = m_pcre_compile(kav_re_inf_str, &errstr); + if (!kav_re_inf) kav_re_inf = m_pcre_compile(kav_re_inf_str, FALSE, &errstr); kav_re = kav_re_inf; } @@ -1025,7 +1230,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; @@ -1041,12 +1246,14 @@ badseek: err = errno; break; } +#endif +#ifndef DISABLE_MAL_CMDLINE case M_CMDL: /* "cmdline" scanner type ---------------------------------- */ { const uschar *cmdline_scanner = scanner_options; - const pcre *cmdline_trigger_re; - const pcre *cmdline_regex_re; + const pcre2_code *cmdline_trigger_re; + const pcre2_code *cmdline_regex_re; uschar * file_name; uschar * commandline; void (*eximsigchld)(int); @@ -1060,19 +1267,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, + cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep, av_scanner_textonly, "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, + cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep, av_scanner_textonly, "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. */ @@ -1097,7 +1304,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); @@ -1109,7 +1316,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))); } @@ -1125,7 +1332,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))); } @@ -1135,7 +1342,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); @@ -1150,7 +1357,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)); @@ -1162,20 +1369,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; @@ -1191,19 +1397,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') { @@ -1213,14 +1419,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 */ @@ -1231,30 +1439,20 @@ 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; - uschar *clamav_fbuf; - int clam_fd, result; - off_t fsize; + int clam_fd; unsigned int fsize_uint; 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; @@ -1267,9 +1465,9 @@ badseek: err = errno; int subsep = ' '; /* Local file; so we def want to use_scan_command and don't want to try - * passing IP/port combinations */ + passing IP/port combinations */ use_scan_command = TRUE; - cd = (clamd_address *) store_get(sizeof(clamd_address)); + cd = (clamd_address *) store_get(sizeof(clamd_address), GET_UNTAINTED); /* extract socket-path part */ sublist = scanner_options; @@ -1277,7 +1475,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; } @@ -1303,19 +1501,19 @@ badseek: err = errno; continue; } - cd = (clamd_address *) store_get(sizeof(clamd_address)); + cd = (clamd_address *) store_get(sizeof(clamd_address), GET_UNTAINTED); /* 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; } @@ -1324,13 +1522,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; @@ -1340,36 +1538,33 @@ 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); - cmd_str.len = Ustrlen(cmd_str.data); + int n; + cmd_str.data = string_sprintf("SCAN %s\n%n", eml_filename, &n); + cmd_str.len = n; /* .len is a size_t */ } /* 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. */ + 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) { @@ -1380,24 +1575,26 @@ badseek: err = errno; 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) */ + on both connections (as one host could resolve to multiple ips) */ for (;;) { - if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port, - &connhost, &errstr, &cmd_str)) >= 0) + /*XXX we trust that the cmd_str is idempotent */ + if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port, + &connhost, &errstr, + use_scan_command ? &cmd_str : NULL)) >= 0) { /* Connection successfully established with a server */ hostname = cd->hostspec; - cmd_str.len = 0; + if (use_scan_command) cmd_str.len = 0; break; } 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--; @@ -1406,176 +1603,133 @@ 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); } /* 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. */ + 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_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 */ + struct stat st; +#if defined(EXIM_TCP_CORK) && !defined(OS_SENDFILE) + BOOL corked = TRUE; +#endif /* New protocol: "zINSTREAM\n" followed by a sequence of chunks, a 4-byte number (network order), terminated by a zero-length - chunk. */ + chunk. We only send one chunk. */ DEBUG(D_acl) debug_printf_indent( "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", scanner_name); +#if defined(EXIM_TCP_CORK) + (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, + US &on, sizeof(on)); +#endif /* 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) + if (fstat(clam_fd, &st) < 0) { - int err; -b_seek: err = errno; - CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, - string_sprintf("can't seek spool file %s: %s", + int err = errno; + (void)close(clam_fd); + return m_panic_defer_3(scanent, NULL, + string_sprintf("can't stat spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } - fsize_uint = (unsigned int) fsize; - if ((off_t)fsize_uint != fsize) + fsize_uint = (unsigned int) st.st_size; + if ((off_t)fsize_uint != st.st_size) { - CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, - string_sprintf("seeking spool file %s, size overflow", - eml_filename), - sock); + (void)close(clam_fd); + return m_panic_defer_3(scanent, NULL, + string_sprintf("stat spool file %s, size overflow", eml_filename), + malware_daemon_ctx.sock); } - if (lseek(clam_fd, 0, SEEK_SET) < 0) - goto b_seek; - if (!(clamav_fbuf = US malloc(fsize_uint))) - { - CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, - string_sprintf("unable to allocate memory %u for file (%s)", - fsize_uint, eml_filename), - sock); - } + /* send file size */ + send_size = htonl(fsize_uint); + if (send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) + return m_panic_defer_3(scanent, NULL, + string_sprintf("unable to send file size to socket (%s)", hostname), + malware_daemon_ctx.sock); - if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) + /* send file body */ + while (fsize_uint) { - int err = errno; - free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd); - return m_errlog_defer_3(scanent, NULL, - string_sprintf("can't read spool file %s: %s", - eml_filename, strerror(err)), - sock); - } - (void)close(clam_fd); +#ifdef OS_SENDFILE + int n = os_sendfile(malware_daemon_ctx.sock, clam_fd, NULL, (size_t)fsize_uint); + if (n < 0) + return m_panic_defer_3(scanent, NULL, + string_sprintf("unable to send file body to socket (%s): %s", hostname, strerror(errno)), + malware_daemon_ctx.sock); + fsize_uint -= n; +#else + int n = MIN(fsize_uint, big_buffer_size); + if ((n = read(clam_fd, big_buffer, n)) < 0) + return m_panic_defer_3(scanent, NULL, + string_sprintf("can't read spool file %s: %s", + eml_filename, strerror(errno)), + malware_daemon_ctx.sock); + if (send(malware_daemon_ctx.sock, big_buffer, (size_t)n, 0) < 0) + return m_panic_defer_3(scanent, NULL, + string_sprintf("unable to send file body to socket (%s): %s", hostname, strerror(errno)), + malware_daemon_ctx.sock); + fsize_uint -= n; +# ifdef EXIM_TCP_CORK + if (corked) + { + corked = FALSE; + (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, + US &off, sizeof(off)); + } +# endif +#endif /*!OS_SENDFILE*/ - /* 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)) - { - free(clamav_fbuf); - return m_errlog_defer_3(scanent, NULL, - string_sprintf("unable to send file body to socket (%s)", hostname), - sock); - } + if (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0) + return m_panic_defer_3(scanent, NULL, + string_sprintf("unable to send file terminator to socket (%s)", hostname), + malware_daemon_ctx.sock); +#ifdef OS_SENDFILE + (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, + US &off, sizeof(off)); #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 */ + scan-method-neutral part, read the response back */ /* ================================================================= */ @@ -1594,30 +1748,31 @@ 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. */ + 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. */ + 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), 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 */ @@ -1642,12 +1797,13 @@ 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) (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'; @@ -1658,8 +1814,8 @@ b_seek: err = errno; if (*p) ++p; /* colon in returned output? */ - if(!(p = Ustrchr(av_buffer,':'))) - return m_errlog_defer(scanent, CUS callout_address, string_sprintf( + if (!(p = Ustrchr(av_buffer,':'))) + return m_panic_defer(scanent, CUS callout_address, string_sprintf( "ClamAV returned malformed result (missing colon): %s", av_buffer)); @@ -1692,7 +1848,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) @@ -1703,12 +1859,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 @@ -1720,8 +1878,8 @@ b_seek: err = errno; uschar * linebuffer; uschar * sockline_scanner; uschar sockline_scanner_default[] = "%s\n"; - const pcre *sockline_trig_re; - const pcre *sockline_name_re; + const pcre2_code *sockline_trig_re; + const pcre2_code *sockline_name_re; /* find scanner command line */ if ( (sockline_scanner = string_nextinlist(&av_scanner_work, &sep, @@ -1732,8 +1890,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; @@ -1741,16 +1899,16 @@ b_seek: err = errno; string_printing(sockline_scanner)); /* find scanner output trigger */ - sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep, + sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep, av_scanner_textonly, "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, + sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep, av_scanner_textonly, "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); @@ -1758,20 +1916,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: ", @@ -1789,7 +1947,9 @@ b_seek: err = errno; malware_name = NULL; break; } +#endif +#ifndef DISABLE_MAL_MKS case M_MKSD: /* "mksd" scanner type ------------------------------------- */ { char *mksd_options_end; @@ -1804,32 +1964,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 @@ -1838,34 +2002,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_clean = m_pcre_compile(ava_re_clean_str, FALSE, &errstr))) || ( !ava_re_virus - && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr))) + && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, FALSE, &errstr))) + || ( !ava_re_error + && !(ava_re_error = m_pcre_compile(ava_re_error_str, FALSE, &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 */ @@ -1876,134 +2081,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 (regex_match(ava_re_clean, buf, slen, NULL)) + 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 (regex_match(ava_re_error, buf, slen, NULL)) + { + 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); - - if (bread == sizeof(av_buffer)) - return m_errlog_defer_3(scanent, CUS callout_address, - US"buffer too small", sock); + endloop: - av_buffer[bread] = '\0'; - linebuffer = string_copy(av_buffer); + 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; - 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 (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer))) - malware_name = NULL; + if (error_message) + return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock); - 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 */ } @@ -2028,15 +2202,16 @@ filename; it's a wrapper around the malware_file function. Arguments: malware_re match condition for "malware=" + cacheable the RE did not use any dynamic elements during expansion timeout if nonzero, timeout in seconds Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ int -malware(const uschar * malware_re, int timeout) +malware(const uschar * malware_re, BOOL cacheable, int timeout) { -int ret = malware_internal(malware_re, NULL, timeout); +int ret = malware_internal(malware_re, cacheable, NULL, timeout); if (ret == DEFER) av_failed = TRUE; return ret; @@ -2074,9 +2249,9 @@ 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); +ret = malware_internal(US"*", TRUE, eml_filename, 0); Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id)); spool_mbox_ok = 1; @@ -2097,25 +2272,50 @@ void malware_init(void) { if (!malware_default_re) - malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE); + malware_default_re = regex_must_compile(malware_regex_default, MCS_NOFLAGS, TRUE); + +#ifndef DISABLE_MAL_DRWEB if (!drweb_re) - drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE); + drweb_re = regex_must_compile(drweb_re_str, MCS_NOFLAGS, TRUE); +#endif +#ifndef DISABLE_MAL_FSECURE if (!fsec_re) - fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE); + fsec_re = regex_must_compile(fsec_re_str, MCS_NOFLAGS, TRUE); +#endif +#ifndef DISABLE_MAL_KAV if (!kav_re_sus) - kav_re_sus = regex_must_compile(kav_re_sus_str, FALSE, TRUE); + kav_re_sus = regex_must_compile(kav_re_sus_str, MCS_NOFLAGS, TRUE); if (!kav_re_inf) - kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE); + kav_re_inf = regex_must_compile(kav_re_inf_str, MCS_NOFLAGS, TRUE); +#endif +#ifndef DISABLE_MAL_AVAST if (!ava_re_clean) - ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE); + ava_re_clean = regex_must_compile(ava_re_clean_str, MCS_NOFLAGS, TRUE); if (!ava_re_virus) - ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE); + ava_re_virus = regex_must_compile(ava_re_virus_str, MCS_NOFLAGS, TRUE); +if (!ava_re_error) + ava_re_error = regex_must_compile(ava_re_error_str, MCS_NOFLAGS, TRUE); +#endif +#ifndef DISABLE_MAL_FFROT6D if (!fprot6d_re_error) - fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE); + fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, MCS_NOFLAGS, TRUE); if (!fprot6d_re_virus) - fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, FALSE, TRUE); + fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, MCS_NOFLAGS, TRUE); +#endif } + +gstring * +malware_show_supported(gstring * g) +{ +g = string_cat(g, US"Malware:"); +for (struct scan * sc = m_scans; sc->scancode != (scanner_t)-1; sc++) + g = string_fmt_append(g, " %s", sc->name); +return string_cat(g, US"\n"); +} + + +# endif /*!MACRO_PREDEF*/ #endif /*WITH_CONTENT_SCAN*/ /* * vi: aw ai sw=2