Malware: new connection type "f-prot6d" for FPSCAND protocol over TCP
[users/jgh/exim.git] / src / src / malware.c
index e1eff16cf9df4c0ae1a0c60048e5d8f6bf10f821..f9c4c414f71633c5af3c8bc21cca3b4a33f55a23 100644 (file)
@@ -13,7 +13,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, M_AVAST} scanner_t;
+               M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST, M_FPROT6D} scanner_t;
 typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t;
 static struct scan
 {
@@ -34,6 +34,7 @@ static struct scan
   { 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 },
+  { M_FPROT6D, US"f-prot6d",   US"localhost 10200",                  MC_TCP },
   { -1,                NULL,           NULL, MC_NONE }         /* end-marker */
 };
 
@@ -84,6 +85,11 @@ static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\
 static const pcre * ava_re_clean = NULL;
 static const pcre * ava_re_virus = NULL;
 
+static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$";
+static const uschar * fprot6d_re_virus_str = US "^\\d+\\s<infected:\\s+(.+?)>\\s+.+$";
+static const pcre * fprot6d_re_error = NULL;
+static const pcre * fprot6d_re_virus = NULL;
+
 
 
 /******************************************************************************/
@@ -231,13 +237,13 @@ while ((rcv = read(fd, p, 1)) > 0)
   }
 if (!ok)
   {
-  DEBUG(D_acl) debug_printf("Malware scan: read %s (%s)\n",
+  DEBUG(D_acl) debug_printf_indent("Malware scan: read %s (%s)\n",
                rcv==0 ? "EOF" : "error", strerror(errno));
   return rcv==0 ? -1 : -2;
   }
 *p = '\0';
 
-DEBUG(D_acl) debug_printf("Malware scan: read '%s'\n", buffer);
+DEBUG(D_acl) debug_printf_indent("Malware scan: read '%s'\n", buffer);
 return p - buffer;
 }
 
@@ -475,7 +481,7 @@ if (*av_scanner == '$')
         expand_string_message));
 
   DEBUG(D_acl)
-    debug_printf("Expanded av_scanner global: %s\n", av_scanner_work);
+    debug_printf_indent("Expanded av_scanner global: %s\n", av_scanner_work);
   /* disable result caching in this case */
   malware_name = NULL;
   malware_ok = FALSE;
@@ -503,8 +509,8 @@ if (!malware_ok)
       break;
     switch(scanent->conn)
     {
-    case MC_TCP:  sock = ip_tcpsocket(scanner_options, &errstr, 5);      break;
-    case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr);        break;
+    case MC_TCP:  sock = ip_tcpsocket(scanner_options, &errstr, 5);    break;
+    case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr);      break;
     case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
     default: /* compiler quietening */ break;
     }
@@ -512,7 +518,7 @@ if (!malware_ok)
       return m_errlog_defer(scanent, CUS callout_address, errstr);
     break;
   }
-  DEBUG(D_acl) debug_printf("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout));
+  DEBUG(D_acl) debug_printf_indent("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout));
 
   switch (scanent->scancode)
     {
@@ -535,7 +541,7 @@ if (!malware_ok)
        par_count++;
        }
       scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest);
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s: %s\n",
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
        scanner_name, scanrequest);
 
       /* send scan request */
@@ -615,7 +621,7 @@ if (!malware_ok)
        drweb_slen = htonl(fsize);
        lseek(drweb_fd, 0, SEEK_SET);
 
-       DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s]\n",
+       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s remote scan [%s]\n",
            scanner_name, scanner_options);
 
        /* send scan request */
@@ -664,7 +670,7 @@ if (!malware_ok)
        {
        drweb_slen = htonl(Ustrlen(eml_filename));
 
-       DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
+       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s local scan [%s]\n",
            scanner_name, scanner_options);
 
        /* send scan request */
@@ -782,7 +788,7 @@ if (!malware_ok)
                                                eml_filename);
 
       /* and send it */
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s %s\n",
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n",
        scanner_name, buf);
       if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
        return m_errlog_defer(scanent, CUS callout_address, errstr);
@@ -842,7 +848,7 @@ if (!malware_ok)
 
       malware_name = NULL;
 
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
          scanner_name, scanner_options);
       /* pass options */
       memset(av_buffer, 0, sizeof(av_buffer));
@@ -938,7 +944,7 @@ if (!malware_ok)
       if (p)
        *p = '\0';
 
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
          scanner_name, scanner_options);
 
       /* send scan request */
@@ -1069,7 +1075,7 @@ if (!malware_ok)
       /* redirect STDERR too */
       commandline = string_sprintf("%s 2>&1", commandline);
 
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
              scanner_name, commandline);
 
       /* store exims signal handlers */
@@ -1172,7 +1178,7 @@ if (!malware_ok)
       if ((p = Ustrrchr(file_name, '/')))
        *p = '\0';
 
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
          scanner_name, scanner_options);
 
       if (  write(sock, file_name, Ustrlen(file_name)) < 0
@@ -1346,7 +1352,7 @@ if (!malware_ok)
          int i = random_number( num_servers );
          clamd_address * cd = cv[i];
 
-         DEBUG(D_acl) debug_printf("trying server name %s, port %u\n",
+         DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n",
                         cd->hostspec, cd->tcp_port);
 
          /* Lookup the host. This is to ensure that we connect to the same IP
@@ -1402,7 +1408,7 @@ if (!malware_ok)
         * that port on a second connection; then in the scan-method-neutral
         * part, read the response back on the original connection. */
 
-       DEBUG(D_acl) debug_printf(
+       DEBUG(D_acl) debug_printf_indent(
            "Malware scan: issuing %s old-style remote scan (PORT)\n",
            scanner_name);
 
@@ -1444,7 +1450,7 @@ if (!malware_ok)
        chunks, <n> a 4-byte number (network order), terminated by a zero-length
        chunk. */
 
-       DEBUG(D_acl) debug_printf(
+       DEBUG(D_acl) debug_printf_indent(
            "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
            scanner_name);
 
@@ -1555,7 +1561,7 @@ if (!malware_ok)
        /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
        file_name = string_sprintf("SCAN %s\n", eml_filename);
 
-       DEBUG(D_acl) debug_printf(
+       DEBUG(D_acl) debug_printf_indent(
            "Malware scan: issuing %s local-path scan [%s]\n",
            scanner_name, scanner_options);
 
@@ -1616,7 +1622,7 @@ if (!malware_ok)
       p = av_buffer + Ustrlen(av_buffer) - 1;
       if (*p == '\n') *p = '\0';
 
-      DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer);
+      DEBUG(D_acl) debug_printf_indent("Malware response: %s\n", av_buffer);
 
       while (isspace(*--p) && (p > av_buffer))
        *p = '\0';
@@ -1653,7 +1659,7 @@ if (!malware_ok)
            *p = '\0';
          }
        malware_name = string_copy(vname);
-       DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name);
+       DEBUG(D_acl) debug_printf_indent("Malware found, name \"%s\"\n", malware_name);
 
        }
       else if (Ustrcmp(result_tag, "ERROR") == 0)
@@ -1664,7 +1670,7 @@ if (!malware_ok)
        {
        /* Everything should be OK */
        malware_name = NULL;
-       DEBUG(D_acl) debug_printf("Malware not found\n");
+       DEBUG(D_acl) debug_printf_indent("Malware not found\n");
 
        }
       else
@@ -1770,7 +1776,7 @@ if (!malware_ok)
 
       malware_name = NULL;
 
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name);
 
       if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK)
        {
@@ -1812,7 +1818,7 @@ if (!malware_ok)
        int slen = Ustrlen(buf);
        if (slen >= 1)
          {
-         DEBUG(D_acl) debug_printf("got from avast: %s\n", buf);
+         DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf);
          switch (avast_stage)
            {
            case AVA_HELO:
@@ -1911,8 +1917,53 @@ if (!malware_ok)
                          sock);
        default:        break;
        }
+      break;
       }
+
+    case M_FPROT6D: /* "f-prot6d" scanner type ----------------------------------- */
+      {
+      int bread;
+      uschar * e;
+      uschar * linebuffer;
+      uschar * scanrequest;
+      uschar av_buffer[1024];
+
+      if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, &errstr)))
+        || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, &errstr))))
+        return malware_errlog_defer(errstr);
+
+      scanrequest = string_sprintf("SCAN FILE %s\n", eml_filename);
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
+        scanner_name, scanrequest);
+
+      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+        return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+
+      if (bread <= 0)
+        return m_errlog_defer_3(scanent, CUS callout_address,
+          string_sprintf("unable to read from socket (%s)", strerror(errno)),
+          sock);
+
+      if (bread == sizeof(av_buffer))
+        return m_errlog_defer_3(scanent, CUS callout_address,
+          US"buffer too small", sock);
+
+      av_buffer[bread] = '\0';
+      linebuffer = string_copy(av_buffer);
+
+      m_sock_send(sock, US"QUIT\n", 5, 0);
+
+      if ((e = m_pcre_exec(fprot6d_re_error, linebuffer)))
+        return m_errlog_defer_3(scanent, CUS callout_address,
+          string_sprintf("scanner reported error (%s)", e), sock);
+
+      if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer)))
+        malware_name = NULL;
+
       break;
+      }  /* f-prot6d */
   }    /* scanner type switch */
 
   if (sock >= 0)
@@ -1923,7 +1974,7 @@ if (!malware_ok)
 /* match virus name against pattern (caseless ------->----------v) */
 if (malware_name && regex_match_and_setup(re, malware_name, 0, -1))
   {
-  DEBUG(D_acl) debug_printf(
+  DEBUG(D_acl) debug_printf_indent(
       "Matched regex to malware [%s] [%s]\n", malware_re, malware_name);
   return OK;
   }
@@ -2028,6 +2079,10 @@ if (!ava_re_clean)
   ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE);
 if (!ava_re_virus)
   ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE);
+if (!fprot6d_re_error)
+  fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE);
+if (!fprot6d_re_virus)
+  fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, FALSE, TRUE);
 }
 
 #endif /*WITH_CONTENT_SCAN*/