Feature macros, show-supported and build-time selection for malware interfaces
[exim.git] / src / src / malware.c
index 7ae8200ae952b468c085b7ff5695692f9e2fc07f..fa1a7aabaa1f0da13309e4d6b5965b13cb4518c7 100644 (file)
@@ -4,13 +4,13 @@
 
 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
- * Copyright (c) The Exim Maintainers 2017
+ * Copyright (c) The Exim Maintainers 2017 - 2018
  */
 
 /* Code for calling virus (malware) scanners. Called from acl.c. */
 
 #include "exim.h"
-#ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN       /* entire file */
 
 typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL,
                M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST, M_FPROT6D} scanner_t;
@@ -23,72 +23,130 @@ static struct scan
   contype_t    conn;
 } m_scans[] =
 {
+#ifndef DISABLE_MAL_FFROTD
   { M_FPROTD,  US"f-protd",    US"localhost 10200-10204",            MC_TCP },
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+  { M_FPROT6D, US"f-prot6d",   US"localhost 10200",                  MC_TCP },
+#endif
+#ifndef DISABLE_MAL_DRWEB
   { M_DRWEB,   US"drweb",      US"/usr/local/drweb/run/drwebd.sock", MC_STRM },
+#endif
+#ifndef DISABLE_MAL_AVE
   { M_AVES,    US"aveserver",  US"/var/run/aveserver",               MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_FSECURE
   { M_FSEC,    US"fsecure",    US"/var/run/.fsav",                   MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_KAV
   { M_KAVD,    US"kavdaemon",  US"/var/run/AvpCtl",                  MC_UNIX },
-  { M_CMDL,    US"cmdline",    NULL,                                 MC_NONE },
+#endif
+#ifndef DISABLE_MAL_SOPHIE
   { M_SOPHIE,  US"sophie",     US"/var/run/sophie",                  MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_CLAM
   { M_CLAMD,   US"clamd",      US"/tmp/clamd",                       MC_NONE },
-  { M_SOCK,    US"sock",       US"/tmp/malware.sock",                MC_STRM },
+#endif
+#ifndef DISABLE_MAL_MKS
   { M_MKSD,    US"mksd",       NULL,                                 MC_NONE },
+#endif
+#ifndef DISABLE_MAL_AVAST
   { M_AVAST,   US"avast",      US"/var/run/avast/scan.sock",         MC_STRM },
-  { M_FPROT6D, US"f-prot6d",   US"localhost 10200",                  MC_TCP },
+#endif
+#ifndef DISABLE_MAL_SOCK
+  { M_SOCK,    US"sock",       US"/tmp/malware.sock",                MC_STRM },
+#endif
+#ifndef DISABLE_MAL_CMDLINE
+  { M_CMDL,    US"cmdline",    NULL,                                 MC_NONE },
+#endif
   { -1,                NULL,           NULL, MC_NONE }         /* end-marker */
 };
 
+/******************************************************************************/
+# ifdef MACRO_PREDEF           /* build solely to predefine macros */
+
+#  include "macro_predef.h"
+
+void
+features_malware(void)
+{
+struct scan * sc;
+uschar * s, * t;
+uschar buf[64];
+
+spf(buf, sizeof(buf), US"_HAVE_MALWARE_");
+
+for (sc = m_scans; sc->scancode != -1; sc++)
+  {
+  for(s = sc->name, t = buf+14; *s; s++) if (*s != '-') *t++ = toupper(*s);
+  *t = '\0';
+  builtin_macro_create(buf);
+  }
+}
+
+/******************************************************************************/
+# else /*!MACRO_PREDEF, main build*/
+
+
+#define MALWARE_TIMEOUT 120    /* default timeout, seconds */
+
+static const uschar * malware_regex_default = US ".+";
+static const pcre * malware_default_re = NULL;
+
+
+
+#ifndef DISABLE_MAL_CLAM
 /* The maximum number of clamd servers that are supported in the configuration */
-#define MAX_CLAMD_SERVERS 32
-#define MAX_CLAMD_SERVERS_S "32"
+# define MAX_CLAMD_SERVERS 32
+# define MAX_CLAMD_SERVERS_S "32"
 
 typedef struct clamd_address {
   uschar * hostspec;
   unsigned tcp_port;
   unsigned retry;
 } clamd_address;
-
-#ifndef nelements
-# define nelements(arr) (sizeof(arr) / sizeof(arr[0]))
 #endif
 
 
-#define MALWARE_TIMEOUT 120    /* default timeout, seconds */
-
-
-#define DRWEBD_SCAN_CMD             (1)     /* scan file, buffer or diskfile */
-#define DRWEBD_RETURN_VIRUSES       (1<<0)   /* ask daemon return to us viruses names from report */
-#define DRWEBD_IS_MAIL              (1<<19)  /* say to daemon that format is "archive MAIL" */
+#ifndef DISABLE_MAL_DRWEB
+# define DRWEBD_SCAN_CMD             (1)     /* scan file, buffer or diskfile */
+# define DRWEBD_RETURN_VIRUSES       (1<<0)   /* ask daemon return to us viruses names from report */
+# define DRWEBD_IS_MAIL              (1<<19)  /* say to daemon that format is "archive MAIL" */
 
-#define DERR_READ_ERR               (1<<0)   /* read error */
-#define DERR_NOMEMORY               (1<<2)   /* no memory */
-#define DERR_TIMEOUT                (1<<9)   /* scan timeout has run out */
-#define DERR_BAD_CALL               (1<<15)  /* wrong command */
-
-
-static const uschar * malware_regex_default = US ".+";
-static const pcre * malware_default_re = NULL;
+# define DERR_READ_ERR               (1<<0)   /* read error */
+# define DERR_NOMEMORY               (1<<2)   /* no memory */
+# define DERR_TIMEOUT                (1<<9)   /* scan timeout has run out */
+# define DERR_BAD_CALL               (1<<15)  /* wrong command */
 
 static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$";
 static const pcre * drweb_re = NULL;
+#endif
 
+#ifndef DISABLE_MAL_FSECURE
 static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$";
 static const pcre * fsec_re = NULL;
+#endif
 
+#ifndef DISABLE_MAL_KAV
 static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$";
 static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$";
 static const pcre * kav_re_sus = NULL;
 static const pcre * kav_re_inf = NULL;
+#endif
 
+#ifndef DISABLE_MAL_AVAST
 static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]";
 static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)";
 static const pcre * ava_re_clean = NULL;
 static const pcre * ava_re_virus = NULL;
+#endif
 
+#ifndef DISABLE_MAL_FFROT6D
 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;
+#endif
 
 
 
@@ -142,6 +200,7 @@ return m_errlog_defer(scanent, hostport, str);
 
 /*************************************************/
 
+#ifndef DISABLE_MAL_CLAM
 /* Only used by the Clamav code, which is working from a list of servers and
 uses the returned in_addr to get a second connection to the same system.
 */
@@ -152,6 +211,7 @@ m_tcpsocket(const uschar * hostname, unsigned int port,
 return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
                          host, errstr, fastopen_blob);
 }
+#endif
 
 static int
 m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr)
@@ -186,7 +246,7 @@ m_pcre_exec(const pcre * cre, uschar * text)
 {
 int ovector[10*3];
 int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0,
-             ovector, nelements(ovector));
+             ovector, nelem(ovector));
 uschar * substr = NULL;
 if (i >= 2)                            /* Got it */
   pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr);
@@ -263,9 +323,10 @@ return fd_ready(sock, tmo-time(NULL))
 
 
 
+#ifndef DISABLE_MAL_MKS
 /* ============= private routines for the "mksd" scanner type ============== */
 
-#include <sys/uio.h>
+# include <sys/uio.h>
 
 static inline int
 mksd_writev (int sock, struct iovec * iov, int iovcnt)
@@ -388,8 +449,10 @@ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0)
 
 return mksd_parse_line (scanent, CS av_buffer);
 }
+#endif /* MKSD */
 
 
+#ifndef DISABLE_MAL_CLAM
 static int
 clamd_option(clamd_address * cd, const uschar * optstr, int * subsep)
 {
@@ -408,6 +471,9 @@ while ((s = string_nextinlist(&optstr, subsep, NULL, 0)))
     return FAIL;
 return OK;
 }
+#endif
+
+
 
 /*************************************************
 *          Scan content for malware              *
@@ -531,6 +597,7 @@ if (!malware_ok)
 
   switch (scanent->scancode)
     {
+#ifndef DISABLE_MAL_FFROTD
     case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
       {
       uschar *fp_scan_option;
@@ -584,7 +651,56 @@ if (!malware_ok)
        }
       break;
       }        /* f-protd */
+#endif
+
+#ifndef DISABLE_MAL_FFROT6D
+    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 */
+#endif
+
+#ifndef DISABLE_MAL_DRWEB
     case M_DRWEB: /* "drweb" scanner type ----------------------------------- */
   /* v0.1 - added support for tcp sockets          */
   /* v0.0 - initial release -- support for unix sockets      */
@@ -739,7 +855,7 @@ badseek:  err = errno;
 
          /* try matcher on the line, grab substring */
          result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
-                                 ovector, nelements(ovector));
+                                 ovector, nelem(ovector));
          if (result >= 2)
            {
            const char * pre_malware_nb;
@@ -780,7 +896,9 @@ badseek:  err = errno;
        }
       break;
       }        /* drweb */
+#endif
 
+#ifndef DISABLE_MAL_AVE
     case M_AVES: /* "aveserver" scanner type -------------------------------- */
       {
       uschar buf[32768];
@@ -848,7 +966,9 @@ badseek:  err = errno;
        }
       break;
       }        /* aveserver */
+#endif
 
+#ifndef DISABLE_MAL_FSECURE
     case M_FSEC: /* "fsecure" scanner type ---------------------------------- */
       {
       int i, j, bread = 0;
@@ -865,7 +985,7 @@ badseek:  err = errno;
          scanner_name, scanner_options);
       /* pass options */
       memset(av_buffer, 0, sizeof(av_buffer));
-      for (i = 0; i != nelements(cmdopt); i++)
+      for (i = 0; i != nelem(cmdopt); i++)
        {
 
        if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
@@ -931,7 +1051,9 @@ badseek:  err = errno;
       fsec_found:
        break;
       }        /* fsecure */
+#endif
 
+#ifndef DISABLE_MAL_KAV
     case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */
       {
       time_t t;
@@ -1043,7 +1165,9 @@ badseek:  err = errno;
 
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_CMDLINE
     case M_CMDL: /* "cmdline" scanner type ---------------------------------- */
       {
       const uschar *cmdline_scanner = scanner_options;
@@ -1177,7 +1301,9 @@ badseek:  err = errno;
        malware_name = NULL;
       break;
       }        /* cmdline */
+#endif
 
+#ifndef DISABLE_MAL_SOPHIE
     case M_SOPHIE: /* "sophie" scanner type --------------------------------- */
       {
       int bread = 0;
@@ -1222,7 +1348,9 @@ badseek:  err = errno;
 
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_CLAM
     case M_CLAMD: /* "clamd" scanner type ----------------------------------- */
       {
 /* This code was originally contributed by David Saez */
@@ -1709,7 +1837,9 @@ b_seek:   err = errno;
 
       break;
       } /* clamd */
+#endif
 
+#ifndef DISABLE_MAL_SOCK
     case M_SOCK: /* "sock" scanner type ------------------------------------- */
     /* This code was derived by Martin Poole from the clamd code contributed
        by David Saez and the cmdline code
@@ -1790,7 +1920,9 @@ b_seek:   err = errno;
        malware_name = NULL;
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_MKS
     case M_MKSD: /* "mksd" scanner type ------------------------------------- */
       {
       char *mksd_options_end;
@@ -1823,7 +1955,9 @@ b_seek:   err = errno;
        }
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_AVAST
     case M_AVAST: /* "avast" scanner type ----------------------------------- */
       {
       int ovector[1*3];
@@ -1903,7 +2037,7 @@ b_seek:   err = errno;
                break;  /* ignore the "210 SCAN DATA" message */
 
              if (pcre_exec(ava_re_clean, NULL, CS buf, slen,
-                   0, 0, ovector, nelements(ovector)) > 0)
+                   0, 0, ovector, nelem(ovector)) > 0)
                break;
 
              if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
@@ -1956,51 +2090,7 @@ b_seek:   err = errno;
        }
       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 */
+#endif
   }    /* scanner type switch */
 
   if (sock >= 0)
@@ -2099,24 +2189,47 @@ malware_init(void)
 {
 if (!malware_default_re)
   malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE);
+
+#ifndef DISABLE_MAL_DRWEB
 if (!drweb_re)
   drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_FSECURE
 if (!fsec_re)
   fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_KAV
 if (!kav_re_sus)
   kav_re_sus = regex_must_compile(kav_re_sus_str, FALSE, TRUE);
 if (!kav_re_inf)
   kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_AVA
 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);
+#endif
+#ifndef DISABLE_MAL_FPROT6D
 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
+}
+
+
+void
+malware_show_supported(FILE * f)
+{
+struct scan * sc;
+fprintf(f, "Malware:");
+for (sc = m_scans; sc->scancode != -1; sc++) fprintf(f, " %s", sc->name);
+fprintf(f, "\n");
 }
 
+
+# endif        /*!MACRO_PREDEF*/
 #endif /*WITH_CONTENT_SCAN*/
 /*
  * vi: aw ai sw=2