Apply timeout to Fsecure malware response. Bug 1549
[exim.git] / src / src / malware.c
index cae0cdd99f2e76ff3546bd5e056350d66e96ddaf..1a3dc7f9b02c8b8070e5168a9e9adaf45de70a17 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 */
 };
 
@@ -42,7 +43,7 @@ static struct scan
 #define MAX_CLAMD_ADDRESS_LENGTH_S "64"
 
 typedef struct clamd_address_container {
-  uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH];
+  uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH+1];
   unsigned int tcp_port;
 } clamd_address_container;
 
@@ -456,7 +457,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     /* v0.0 - initial release -- support for unix sockets      */
       {
        int result;
-       unsigned int fsize;
+       off_t fsize;
+       unsigned int fsize_uint;
        uschar * tmpbuf, *drweb_fbuf;
        int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd,
            drweb_vnum, drweb_slen, drweb_fin = 0x0000;
@@ -484,6 +486,14 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                eml_filename, strerror(err)),
              sock);
          }
+         fsize_uint = (unsigned int) fsize;
+         if ((off_t)fsize_uint != fsize) {
+           (void)close(drweb_fd);
+           return m_errlog_defer_3(scanent,
+             string_sprintf("seeking spool file %s, size overflow",
+               eml_filename),
+             sock);
+         }
          drweb_slen = htonl(fsize);
          lseek(drweb_fd, 0, SEEK_SET);
 
@@ -501,11 +511,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
              sock);
          }
 
-         if (!(drweb_fbuf = (uschar *) malloc (fsize))) {
+         if (!(drweb_fbuf = (uschar *) malloc (fsize_uint))) {
            (void)close(drweb_fd);
            return m_errlog_defer_3(scanent,
              string_sprintf("unable to allocate memory %u for file (%s)",
-               fsize, eml_filename),
+               fsize_uint, eml_filename),
              sock);
          }
 
@@ -700,12 +710,13 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                                        US"CONFIGURE\tTIMEOUT\t0\n",
                                        US"CONFIGURE\tMAXARCH\t5\n",
                                        US"CONFIGURE\tMIME\t1\n" };
+       time_t tmo;
 
        malware_name = NULL;
 
        DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
            scanner_name, scanner_options);
-
+       tmo = time(NULL) + MALWARE_TIMEOUT;
        /* pass options */
        memset(av_buffer, 0, sizeof(av_buffer));
        for (i=0; i != nelements(cmdopt); i++) {
@@ -734,24 +745,46 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        /* todo also SUSPICION\t */
        fs_inf = m_pcre_compile(US"\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr);
 
-       /* read report, linewise */
-       do {
-         i = 0;
-         memset(av_buffer, 0, sizeof(av_buffer));
-         do {
-           if ((bread= ip_recv(sock, &av_buffer[i], 1, MALWARE_TIMEOUT)) < 0)
+       /* read report, linewise. Apply a timeout as the Fsecure daemon
+       sometimes wants an answer to "PING" but they won't tell us what */
+       {
+         uschar * p = av_buffer;
+         uschar * q;
+
+         for (;;)
+           {
+           int t = tmo - time(NULL);
+
+           errno = ETIME;
+           i =  av_buffer+sizeof(av_buffer)-p;
+           if (  t <= 0
+              || (bread= ip_recv(sock, p, i-1, t)) < 0
+              )
              return m_errlog_defer_3(scanent,
                string_sprintf("unable to read result (%s)", strerror(errno)),
                sock);
-         } while (++i < sizeof(av_buffer)-1  &&  av_buffer[i-1] != '\n');
-         av_buffer[i-1] = '\0';
 
-         /* Really search for virus again? */
-         if (malware_name == NULL)
-           /* try matcher on the line, grab substring */
-           malware_name = m_pcre_exec(fs_inf, av_buffer);
+           for (p[bread] = '\0'; q = strchr(p, '\n'); p = q+1)
+             {
+             *q = '\0';
+
+             /* Really search for virus again? */
+             if (!malware_name)
+               /* try matcher on the line, grab substring */
+               malware_name = m_pcre_exec(fs_inf, p);
+
+             if (Ustrstr(p, "OK\tScan ok."))
+               goto fsec_found;
+             }
+
+           /* copy down the trailing partial line then read another chunk */
+           i =  av_buffer+sizeof(av_buffer)-p;
+           memmove(av_buffer, p, i);
+           p = av_buffer+i;
+         }
        }
-       while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL);
+
+      fsec_found:
        break;
       }        /* fsecure */
 
@@ -1029,19 +1062,20 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
        uschar *p, *vname, *result_tag, *response_end;
        int bread=0;
-       unsigned int port;
        uschar * file_name;
        uschar av_buffer[1024];
        uschar *hostname = US"";
        host_item connhost;
        uschar *clamav_fbuf;
        int clam_fd, result;
-       unsigned int fsize;
+       off_t fsize;
+       unsigned int fsize_uint;
        BOOL use_scan_command = FALSE;
        clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS];
        int current_server;
        int num_servers = 0;
   #ifdef WITH_OLD_CLAMAV_STREAM
+       unsigned int port;
        uschar av_buffer2[1024];
        int sockData;
   #else
@@ -1231,17 +1265,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                eml_filename, strerror(err)),
              sock);
          }
+         fsize_uint = (unsigned int) fsize;
+         if ((off_t)fsize_uint != fsize) {
+           CLOSE_SOCKDATA; (void)close(clam_fd);
+           return m_errlog_defer_3(scanent,
+             string_sprintf("seeking spool file %s, size overflow",
+               eml_filename),
+             sock);
+         }
          lseek(clam_fd, 0, SEEK_SET);
 
-         if (!(clamav_fbuf = (uschar *) malloc (fsize))) {
+         if (!(clamav_fbuf = (uschar *) malloc (fsize_uint))) {
            CLOSE_SOCKDATA; (void)close(clam_fd);
            return m_errlog_defer_3(scanent,
              string_sprintf("unable to allocate memory %u for file (%s)",
-               fsize, eml_filename),
+               fsize_uint, eml_filename),
              sock);
          }
 
-         if ((result = read(clam_fd, clamav_fbuf, fsize)) < 0) {
+         if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) {
            int err = errno;
            free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd);
            return m_errlog_defer_3(scanent,
@@ -1253,7 +1295,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
          /* send file body to socket */
   #ifdef WITH_OLD_CLAMAV_STREAM
-         if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
+         if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0) {
            free(clamav_fbuf); CLOSE_SOCKDATA;
            return m_errlog_defer_3(scanent,
              string_sprintf("unable to send file body to socket (%s:%u)",
@@ -1261,10 +1303,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
              sock);
          }
   #else
-         send_size = htonl(fsize);
+         send_size = htonl(fsize_uint);
          send_final_zeroblock = 0;
          if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
-             (send(sock, clamav_fbuf, fsize, 0) < 0) ||
+             (send(sock, clamav_fbuf, fsize_uint, 0) < 0) ||
              (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
            {
            free(clamav_fbuf);
@@ -1425,9 +1467,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
                                            NULL, 0)))
        {       /* check for no expansions apart from one %s */
-         char * s = index(CS sockline_scanner, '%');
+         uschar * s = Ustrchr(sockline_scanner, '%');
          if (s++)
-           if ((*s != 's' && *s != '%') || index(s+1, '%'))
+           if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
              return m_errlog_defer_3(scanent,
                                    US"unsafe sock scanner call spec", sock);
        }
@@ -1509,7 +1551,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);
@@ -1517,10 +1680,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;
 }