Updated Avast scanner interface. Bug 1033
authorHeiko Schlittermann <hs+exim@schlittermann.de>
Sun, 14 Dec 2014 16:55:58 +0000 (16:55 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 14 Dec 2014 16:57:15 +0000 (16:57 +0000)
doc/doc-docbook/spec.xfpt
src/src/malware.c

index 9cf67ec54bb4b39a056f756827cf211a08ae788c..df648d932dd2d661569b7fad92fc3d04cdd1d7e3 100644 (file)
@@ -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%&
index 9a83b9377e7e972ef247a5e3392b694ee2d40c27..9099c8a5b9e0ca9cafe303a1526b15b71101e06b 100644 (file)
@@ -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;
        }
       }