Add support for avast malware scanner. Bug 1033
[exim.git] / src / src / malware.c
index 93bcf8667d5588050ea5b5e73e28b4c39dafc7f6..167f47f2c05b79e4c057c652632e22e965a78ae1 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,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;
 }