[exim-cvs] Add support for avast malware scanner. Bug 1033

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Add support for avast malware scanner. Bug 1033
Gitweb: http://git.exim.org/exim.git/commitdiff/b6fbf22d63de88b77d79cd0b1d2337e589cac6d7
Commit:     b6fbf22d63de88b77d79cd0b1d2337e589cac6d7
Parent:     168dec3b8f4d729ccb7e56181b8ab4c4956726d1
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Tue Dec 23 20:16:36 2014 +0000
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Mon Jan 12 18:58:36 2015 +0000


        Add support for avast malware scanner.  Bug 1033


        Originally by Dominic Benson <dominic@???>
        Rebased for current malware.c by JGH.
        Testing by Heiko Schlittermann <hs@???>
---
 doc/doc-docbook/spec.xfpt |   33 +++++++++++
 doc/doc-txt/NewStuff      |    2 +
 src/src/malware.c         |  131 +++++++++++++++++++++++++++++++++++++++++++--
 src/src/readconf.c        |    4 +-
 src/src/spam.c            |    2 +-
 5 files changed, 165 insertions(+), 7 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index aece633..460b1bf 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -30353,6 +30353,39 @@ 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. 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 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/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/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%&
 .cindex "virus scanners" "Kaspersky"
 This is the scanner daemon of Kaspersky Version 5. You can get a trial version
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 8cb2d0d..c371cb2 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 93bcf86..167f47f 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,128 @@ 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_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"(?!\\\\)\\t\\[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) 
+      {
+      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:
+          {
+          int len;
+          /* 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\n",
+            spool_directory, message_id);
+        avast_stage = AVA_RSP;        /* just sent command */
+        }
+
+          /* send config-cmd or scan-request to socket */
+          len = Ustrlen(scanrequest);
+          if (send(sock, scanrequest, len, 0) < 0)
+        {
+        scanrequest[len-1] = '\0';
+        return m_errlog_defer_3(scanent, string_sprintf(
+              "unable to send request '%s' to socket (%s): %s",
+              scanrequest, scanner_options, strerror(errno)), sock);
+        }
+          break;
+          }
+
+        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 (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;
+        }
+
+          /* here for any unexpected response from the scanner */
+          goto endloop;
+        }
+    }
+      }
+      endloop:
+
+      switch(avast_stage)
+    {
+        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;
+    }
+      }
+    }    /* scanner type switch */


     if (sock >= 0)
       (void) close (sock);
@@ -1535,10 +1657,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;
 }
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 0b37780..687b352 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -3009,9 +3009,9 @@ if (config_file != NULL)
   uschar *p;
   config_filename = config_main_filename = string_copy(filename);


-  p = strrchr(filename, '/');
+  p = Ustrrchr(filename, '/');
   config_main_directory = p ? string_copyn(filename, p - filename) 
-                            : string_copy(".");
+                            : string_copy(US".");
   }
 else
   {
diff --git a/src/src/spam.c b/src/src/spam.c
index 45a0693..c0c3fb3 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -48,7 +48,7 @@ spam(uschar **listptr)
   fd_set select_fd;
 #endif
   uschar *spamd_address_work;
-  static const char * loglabel = US"spam acl condition:";
+  static const uschar * loglabel = US"spam acl condition:";


/* stop compiler warning */
result = 0;