Add support for avast malware scanner. Bug 1033
authorJeremy Harris <jgh146exb@wizmail.org>
Fri, 5 Dec 2014 15:24:57 +0000 (15:24 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Fri, 5 Dec 2014 15:24:57 +0000 (15:24 +0000)
Originally by Dominic Benson <dominic@lenny.cus.org>
Rebased for current malware.c by JGH.
Testing by Heiko Schlittermann <hs@schlittermann.de>

doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/malware.c

index 36634a602f61b49ed5637a85837a773552337c1f..e070616c79054c19cabc06d9b14cc7cd1db9c6d9 100644 (file)
@@ -30326,6 +30326,31 @@ The usual list-parsing of the content (see &<<SECTlistconstruct>>&) 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
index 8cb2d0dbdd40b4ef380481c8635a0270a5efb07d..c371cb2dde1d3a3383a7ce70ed5df1776222ccce 100644 (file)
@@ -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
 ------------
 
index 93bcf8667d5588050ea5b5e73e28b4c39dafc7f6..339e2033331f7df7e393d39529c60813ed61c564 100644 (file)
@@ -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;
 }