From 237e7c0ad8014d8a746dd912867a8cdfadc5061d Mon Sep 17 00:00:00 2001 From: Heiko Schlittermann Date: Sun, 14 Dec 2014 16:55:58 +0000 Subject: [PATCH] Updated Avast scanner interface. Bug 1033 --- doc/doc-docbook/spec.xfpt | 24 ++++-- src/src/malware.c | 164 ++++++++++++++++++++------------------ 2 files changed, 104 insertions(+), 84 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 9cf67ec54..df648d932 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -30330,27 +30330,35 @@ 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, +This is the scanner daemon of Avast. It has been tested with Avast Core +Security (currenty at version 1.1.7). +You can get a trial version at &url(http://www.avast.com) or for Linux +at &url(http://www.avast.com/linux-server-antivirus). +This scanner type takes one option, +which can be 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 +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. 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:/var/run/avast/scan.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_& +&_/var/run/avast/scan.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. +For information about available commands and their options you may use +.code +$ socat UNIX:/var/run/avast/scan.sock STDIO: + FLAGS + SENSITIVITY + PACK +.endd .vitem &%aveserver%& diff --git a/src/src/malware.c b/src/src/malware.c index 9a83b9377..9099c8a5b 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -1533,12 +1533,23 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) 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)) + const pcre * avast_clean_re, * avast_virus_re; + enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage; + + /* According to Martin Tuma @avast the protocol uses "escaped + whitespace", that is, every embedded whitespace is backslash + escaped, as well as backslash is protected by backslash. + The returned lines contain the name of the scanned file, a tab + and the [ ] marker. + [+] - not infected + [L] - infected + [E] - some error occured + Such marker follows the first non-escaped TAB. */ + if ( !(avast_clean_re = + m_pcre_compile(US"(?!\\\\)\\t\\[\\+\\]", &errstr)) || !(avast_virus_re = - m_pcre_compile(US"\\[L\\]\\d\\.\\d\\t\\d\\s(.*)", &errstr)) + m_pcre_compile(US"(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)", + &errstr)) ) return malware_errlog_defer(errstr); @@ -1546,88 +1557,89 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) for (avast_stage = AVA_HELO; recv_line(sock, buf, sizeof(buf)) > 0; ) { int slen = Ustrlen(buf); - if (slen >= 1) switch (avast_stage) + if (slen >= 1) { - 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 */ + DEBUG(D_acl) debug_printf("got from avast: %s\n", buf); + 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_RSP; /* just sent command */ + } - /* 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; + /* 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; - default: - if (Ustrncmp(buf, "221", 3) == 0) - goto endloop; /* a "quit" response */ + case AVA_RSP: + if (Ustrncmp(buf, "210", 3) == 0) + break; /* ignore the "210 SCAN DATA" message */ + + if (pcre_exec(avast_clean_re, NULL, CS buf, slen, + 0, 0, ovector, nelements(ovector)) > 0) + break; + + if ((malware_name = m_pcre_exec(avast_virus_re, 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]; + + avast_stage = AVA_DONE; + goto endloop; + } - if ((malware_name = m_pcre_exec(avast_virus_re, buf))) - { - /* remove backslashes from the virus string */ - uschar *p, *q; - for (p = malware_name; *p; p++) if (*p == '\\') - for (q = p; *q; q++) *q = q[1]; + 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, 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; + } - avast_stage = AVA_POS; + /* here for any unexpected response from the scanner */ 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); + case AVA_HELO: + case AVA_OPT: + case AVA_RSP: return m_errlog_defer_3(scanent, string_sprintf( + "invalid response from scanner: %s\n", buf), sock); default: break; } } -- 2.30.2