From e4413c34ca04474ce76fbbb544788d41d0bdc423 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 5 Dec 2014 15:24:57 +0000 Subject: [PATCH] Add support for avast malware scanner. Bug 1033 Originally by Dominic Benson Rebased for current malware.c by JGH. Testing by Heiko Schlittermann --- doc/doc-docbook/spec.xfpt | 35 +++++++++++- doc/doc-txt/NewStuff | 2 + src/src/malware.c | 113 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 36634a602..e070616c7 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -30326,6 +30326,31 @@ The usual list-parsing of the content (see &<>&) applies. The following scanner types are supported in this release: .vlist +.vitem &%avast%& +.cindex "virus scanners" "avast" +This is the scanner daemon of Avast version 1.1.7 and 1.1.6 +(as reported by "/bin/avast -v"). +You can get a trial version at &url(http://www.avast.com). +This scanner type requires one option, +either a full path to a UNIX socket, +or host and port specifiers separated by white space. +The host may a name or an IP address; the port is either a +single number or a pair of numbers with a dash between. +Any further options are given, on separate lines, +to the daemon as options before the main scan command. +For example: +.code +av_scanner = avast:/var/run/avast4/local.sock:FLAGS -fullfiles:SENSITIVITY -pup +av_scanner = avast:192.168.2.22 5036 +.endd +If you omit the argument, the default path +&_/var/run/avast4/local.sock_& +is used. +If you use a remote host, +you need to make Exim's spool directory available to it, +as the scanner is passed a file path, not file contents. + + .vitem &%aveserver%& .cindex "virus scanners" "Kaspersky" This is the scanner daemon of Kaspersky Version 5. You can get a trial version @@ -30414,9 +30439,13 @@ av_scanner = cmdline:\ .endd .vitem &%drweb%& .cindex "virus scanners" "DrWeb" -The DrWeb daemon scanner (&url(http://www.sald.com/)) interface takes one -argument, either a full path to a UNIX socket, or an IP address and port -separated by white space, as in these examples: +The DrWeb daemon scanner (&url(http://www.sald.com/)) interface +takes one option, +either a full path to a UNIX socket, +or host and port specifiers separated by white space. +The host may be a name or an IP address; the port is either a +single number or a pair of numbers with a dash between. +For example: .code av_scanner = drweb:/var/run/drwebd.sock av_scanner = drweb:192.168.2.20 31337 diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 8cb2d0dbd..c371cb2dd 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -14,6 +14,8 @@ Version 4.86 2. New expansion items $config_file, $config_dir, containing the file and directory name of the main configuration file. Also $exim_version. + 3. New "malware=" support for Avast. + Version 4.85 ------------ diff --git a/src/src/malware.c b/src/src/malware.c index 93bcf8667..339e20333 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -11,7 +11,7 @@ #ifdef WITH_CONTENT_SCAN typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL, - M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD} scanner_t; + M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST} scanner_t; typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t; static struct scan { @@ -31,6 +31,7 @@ static struct scan { M_CLAMD, US"clamd", US"/tmp/clamd", MC_NONE }, { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM }, { M_MKSD, US"mksd", NULL, MC_NONE }, + { M_AVAST, US"avast", US"/var/run/avast/scan.sock", MC_STRM }, { -1, NULL, NULL, MC_NONE } /* end-marker */ }; @@ -1527,7 +1528,110 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) } break; } - } + case M_AVAST: /* "avast" scanner type ----------------------------------- */ + { + int ovector[1*3]; + uschar buf[1024]; + uschar * scanrequest; + const pcre * avast_scan_ok_re, * avast_virus_re; + enum {AVA_HELO, AVA_OPT, AVA_CMD, AVA_RSP, AVA_POS, AVA_NEG} avast_stage; + + if ( !(avast_scan_ok_re = m_pcre_compile(US"\\[\\+\\]", &errstr)) + || !(avast_virus_re = + m_pcre_compile(US"\\[L\\]\\d\\.\\d\\t\\d\\s(.*)", &errstr)) + ) + return malware_errlog_defer(errstr); + + /* wait for result */ + for (avast_stage = AVA_HELO; recv_line(sock, buf, sizeof(buf)) > 0; ) + { + int slen = Ustrlen(buf); + if (slen >= 1) switch (avast_stage) + { + case AVA_HELO: + 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 (Ustrncmp(buf, "200", 3) != 0) + goto endloop; /* require a 200 */ + + sendreq: + /* Check for another option to send. Newline-terminate it. */ + if ((scanrequest = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))) + { + scanrequest = string_sprintf("%s\n", scanrequest); + avast_stage = AVA_OPT; /* just sent option */ + } + else + { + scanrequest = string_sprintf("SCAN %s/scan/%s\r\n", + spool_directory, message_id); + avast_stage = AVA_CMD; /* just sent command */ + } + + /* send config-cmd or scan-request to socket */ + + if (send(sock, scanrequest, Ustrlen(scanrequest), 0) < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to send scan request to socket (%s): %s", + scanner_options, strerror(errno)), + sock); + break; + + case AVA_CMD: + if (Ustrncmp(buf, "210", 3) == 0) + break; /* ignore 210 responses */ + + /* send (pipelined) quit request to socket */ + if (send(sock, "QUIT\n", 5, 0) < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to send quit request to socket (%s): %s", + scanner_options, strerror(errno)), + sock); + malware_name = NULL; + avast_stage = AVA_RSP; /* waiting for actual response line */ + break; + + default: + if (Ustrncmp(buf, "221", 3) == 0) + goto endloop; /* a "quit" response */ + + if ((malware_name = m_pcre_exec(avast_virus_re, buf))) + { + /* remove backslashes from the virus string */ + char *p, *q; + for (p = malware_name; *p; p++) if (*p == '\\') + for (q = p; *q; q++) *q = q[1]; + + avast_stage = AVA_POS; + goto endloop; + } + + if (pcre_exec(avast_scan_ok_re, NULL, CS buf, slen, + 0, 0, ovector, nelements(ovector)) > 0) + avast_stage = AVA_NEG; + break; + } + } + endloop: + + switch(avast_stage) + { + case AVA_HELO: return m_errlog_defer_3(scanent, + US"invalid response from scanner", sock); + case AVA_CMD: return m_errlog_defer_3(scanent, + US"unable to read return code", sock); + case AVA_RSP: return m_errlog_defer_3(scanent, + US"response interrupted", sock); + default: break; + } + } + } /* scanner type switch */ if (sock >= 0) (void) close (sock); @@ -1535,10 +1639,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) } /* match virus name against pattern (caseless ------->----------v) */ - if ( malware_name && (regex_match_and_setup(re, malware_name, 0, -1)) ) { + if (malware_name && regex_match_and_setup(re, malware_name, 0, -1)) + { DEBUG(D_acl) debug_printf("Matched regex to malware [%s] [%s]\n", malware_regex, malware_name); return OK; - } + } else return FAIL; } -- 2.30.2