string handling: strlen for gstring
[users/jgh/exim.git] / src / src / malware.c
index b626b18a8ea451d6d66d92bf89507aaf665b8eea..cfff9ee5d7b50083ba8eebfa1e4122f166277dff 100644 (file)
@@ -4,16 +4,53 @@
 
 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2015 - 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;
+typedef enum {
+#ifndef DISABLE_MAL_FFROTD
+       M_FPROTD,
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+       M_FPROT6D,
+#endif
+#ifndef DISABLE_MAL_DRWEB
+       M_DRWEB,
+#endif
+#ifndef DISABLE_MAL_AVE
+       M_AVES,
+#endif
+#ifndef DISABLE_MAL_FSECURE
+       M_FSEC,
+#endif
+#ifndef DISABLE_MAL_KAV
+       M_KAVD,
+#endif
+#ifndef DISABLE_MAL_SOPHIE
+       M_SOPHIE,
+#endif
+#ifndef DISABLE_MAL_CLAM
+       M_CLAMD,
+#endif
+#ifndef DISABLE_MAL_MKS
+       M_MKSD,
+#endif
+#ifndef DISABLE_MAL_AVAST
+       M_AVAST,
+#endif
+#ifndef DISABLE_MAL_SOCK
+       M_SOCK,
+#endif
+#ifndef DISABLE_MAL_CMDLINE
+       M_CMDL,
+#endif
+       M_DUMMY
+       } scanner_t;
 typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t;
 static struct scan
 {
@@ -23,82 +60,144 @@ 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)
+{
+const uschar * s;
+uschar * t;
+uschar buf[64];
+
+spf(buf, sizeof(buf), US"_HAVE_MALWARE_");
+
+for (const struct scan * 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 uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)";
+static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)";
 static const pcre * ava_re_clean = NULL;
 static const pcre * ava_re_virus = NULL;
+static const pcre * ava_re_error = 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
 
 
 
 /******************************************************************************/
 
+#ifndef DISABLE_MAL_KAV
 /* Routine to check whether a system is big- or little-endian.
    Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html
    Needed for proper kavdaemon implementation. Sigh. */
-#define BIG_MY_ENDIAN      0
-#define LITTLE_MY_ENDIAN   1
+# define BIG_MY_ENDIAN      0
+# define LITTLE_MY_ENDIAN   1
 static int test_byte_order(void);
 static inline int
 test_byte_order()
@@ -107,6 +206,7 @@ test_byte_order()
   char *byte = CS  &word;
   return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
 }
+#endif
 
 BOOL malware_ok = FALSE;
 
@@ -117,40 +217,69 @@ extern int spool_mbox_ok;
 extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
 
 
+/* Some (currently avast only) use backslash escaped whitespace,
+this function undoes these escapes */
 
+static inline void
+unescape(uschar *p)
+{
+uschar *p0;
+for (; *p; ++p)
+  if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
+    for (p0 = p; *p0; ++p0) *p0 = p0[1];
+}
+
+/* --- malware_*_defer --- */
 static inline int
-malware_errlog_defer(const uschar * str)
+malware_panic_defer(const uschar * str)
 {
 log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
 return DEFER;
 }
-
-static int
-m_errlog_defer(struct scan * scanent, const uschar * hostport,
+static inline int
+malware_log_defer(const uschar * str)
+{
+log_write(0, LOG_MAIN, "malware acl condition: %s", str);
+return DEFER;
+}
+/* --- m_*_defer --- */
+static inline int
+m_panic_defer(struct scan * scanent, const uschar * hostport,
   const uschar * str)
 {
-return malware_errlog_defer(string_sprintf("%s %s : %s",
+return malware_panic_defer(string_sprintf("%s %s : %s",
   scanent->name, hostport ? hostport : CUS"", str));
 }
-static int
-m_errlog_defer_3(struct scan * scanent, const uschar * hostport,
+static inline int
+m_log_defer(struct scan * scanent, const uschar * hostport,
+  const uschar * str)
+{
+return malware_log_defer(string_sprintf("%s %s : %s",
+  scanent->name, hostport ? hostport : CUS"", str));
+}
+/* --- m_*_defer_3 */
+static inline int
+m_panic_defer_3(struct scan * scanent, const uschar * hostport,
   const uschar * str, int fd_to_close)
 {
 (void) close(fd_to_close);
-return m_errlog_defer(scanent, hostport, str);
+return m_panic_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.
 */
 static inline int
 m_tcpsocket(const uschar * hostname, unsigned int port,
-       host_item * host, uschar ** errstr)
+       host_item * host, uschar ** errstr, const blob * fastopen_blob)
 {
-return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr);
+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)
@@ -173,8 +302,7 @@ const uschar * rerror;
 int roffset;
 const pcre * cre;
 
-cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-if (!cre)
+if (!(cre = pcre_compile(CS re, PCRE_COPT, CCSS &rerror, &roffset, NULL)))
   *errstr= string_sprintf("regular expression error in '%s': %s at offset %d",
       re, rerror, roffset);
 return cre;
@@ -185,10 +313,10 @@ 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);
+  pcre_get_substring(CS text, ovector, i, 1, CCSS &substr);
 return substr;
 }
 
@@ -220,13 +348,13 @@ return cre;
          -2 on timeout or error
 */
 static int
-recv_line(int fd, uschar * buffer, int bsize, int tmo)
+recv_line(int fd, uschar * buffer, int bsize, time_t tmo)
 {
 uschar * p = buffer;
 ssize_t rcv;
 BOOL ok = FALSE;
 
-if (!fd_ready(fd, tmo-time(NULL)))
+if (!fd_ready(fd, tmo))
   return -2;
 
 /*XXX tmo handling assumes we always get a whole line */
@@ -253,18 +381,19 @@ return p - buffer;
 
 /* return TRUE iff size as requested */
 static BOOL
-recv_len(int sock, void * buf, int size, int tmo)
+recv_len(int sock, void * buf, int size, time_t tmo)
 {
-return fd_ready(sock, tmo-time(NULL))
+return fd_ready(sock, tmo)
   ? recv(sock, buf, size, 0) == size
   : FALSE;
 }
 
 
 
+#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)
@@ -278,7 +407,7 @@ for (;;)
   while (i < 0 && errno == EINTR);
   if (i <= 0)
     {
-    (void) malware_errlog_defer(
+    (void) malware_panic_defer(
            US"unable to write to mksd UNIX socket (/var/run/mksd/socket)");
     return -1;
     }
@@ -300,17 +429,18 @@ for (;;)
 }
 
 static inline int
-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, time_t tmo)
 {
+client_conn_ctx cctx = {.sock = sock};
 int offset = 0;
 int i;
 
 do
   {
-  i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+  i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo);
   if (i <= 0)
     {
-    (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+    (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
     return -1;
     }
 
@@ -318,7 +448,7 @@ do
   /* offset == av_buffer_size -> buffer full */
   if (offset == av_buffer_size)
     {
-    (void) malware_errlog_defer(US"malformed reply received from mksd");
+    (void) malware_panic_defer(US"malformed reply received from mksd");
     return -1;
     }
   } while (av_buffer[offset-1] != '\n');
@@ -341,7 +471,7 @@ switch (*line)
   case 'A': /* ERR */
     if ((p = strchr (line, '\n')) != NULL)
       *p = '\0';
-    return m_errlog_defer(scanent, NULL,
+    return m_panic_defer(scanent, NULL,
       string_sprintf("scanner failed: %s", line));
 
   default: /* VIR */
@@ -359,14 +489,14 @@ switch (*line)
        return OK;
        }
       }
-    return m_errlog_defer(scanent, NULL,
+    return m_panic_defer(scanent, NULL,
       string_sprintf("malformed reply received: %s", line));
   }
 }
 
 static int
 mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename,
-  int tmo)
+  time_t tmo)
 {
 struct iovec iov[3];
 const char *cmd = "MSQ\n";
@@ -387,8 +517,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)
 {
@@ -407,6 +539,9 @@ while ((s = string_nextinlist(&optstr, subsep, NULL, 0)))
     return FAIL;
 return OK;
 }
+#endif
+
+
 
 /*************************************************
 *          Scan content for malware              *
@@ -437,7 +572,7 @@ const pcre *re;
 uschar * errstr;
 struct scan * scanent;
 const uschar * scanner_options;
-int sock = -1;
+client_conn_ctx malware_daemon_ctx = {.sock = -1};
 time_t tmo;
 uschar * eml_filename, * eml_dir;
 
@@ -447,7 +582,7 @@ if (!malware_re)
 /* Ensure the eml mbox file is spooled up */
 
 if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename)))
-  return malware_errlog_defer(US"error while creating mbox spool file");
+  return malware_panic_defer(US"error while creating mbox spool file");
 
 /* None of our current scanners need the mbox file as a stream (they use
 the name), so we can close it right away.  Get the directory too. */
@@ -467,20 +602,20 @@ if (  strcmpic(malware_re,US"true") == 0
   {
   if (  !malware_default_re
      && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr)))
-    return malware_errlog_defer(errstr);
+    return malware_panic_defer(errstr);
   malware_re = malware_regex_default;
   re = malware_default_re;
   }
 
 /* compile the regex, see if it works */
 else if (!(re = m_pcre_compile(malware_re, &errstr)))
-  return malware_errlog_defer(errstr);
+  return malware_panic_defer(errstr);
 
 /* if av_scanner starts with a dollar, expand it first */
 if (*av_scanner == '$')
   {
   if (!(av_scanner_work = expand_string(av_scanner)))
-    return malware_errlog_defer(
+    return malware_panic_defer(
         string_sprintf("av_scanner starts with $, but expansion failed: %s",
         expand_string_message));
 
@@ -496,14 +631,14 @@ if (!malware_ok)
   {
   /* find the scanner type from the av_scanner option */
   if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
-    return malware_errlog_defer(US"av_scanner configuration variable is empty");
+    return malware_panic_defer(US"av_scanner configuration variable is empty");
   if (!timeout) timeout = MALWARE_TIMEOUT;
   tmo = time(NULL) + timeout;
 
   for (scanent = m_scans; ; scanent++)
     {
     if (!scanent->name)
-      return malware_errlog_defer(string_sprintf("unknown scanner type '%s'",
+      return malware_panic_defer(string_sprintf("unknown scanner type '%s'",
        scanner_name));
     if (strcmpic(scanner_name, US scanent->name) != 0)
       continue;
@@ -518,18 +653,23 @@ if (!malware_ok)
     DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
     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_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
-    default: /* compiler quietening */ break;
+    case MC_TCP:
+      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5);     break;
+    case MC_UNIX:
+      malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr);       break;
+    case MC_STRM:
+      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
+    default:
+      /* compiler quietening */ break;
     }
-    if (sock < 0)
-      return m_errlog_defer(scanent, CUS callout_address, errstr);
+    if (malware_daemon_ctx.sock < 0)
+      return m_panic_defer(scanent, CUS callout_address, errstr);
     break;
   }
 
   switch (scanent->scancode)
     {
+#ifndef DISABLE_MAL_FFROTD
     case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
       {
       uschar *fp_scan_option;
@@ -553,10 +693,10 @@ if (!malware_ok)
        scanner_name, scanrequest);
 
       /* send scan request */
-      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+      if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
-      while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
+      while ((len = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) >= 0)
        if (len > 0)
          {
          if (Ustrstr(buf, US"<detected type=\"") != NULL)
@@ -578,12 +718,61 @@ if (!malware_ok)
          }
       if (len < -1)
        {
-       (void)close(sock);
+       (void)close(malware_daemon_ctx.sock);
        return DEFER;
        }
       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_panic_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(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+        return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+
+      if (bread <= 0)
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("unable to read from socket (%s)", strerror(errno)),
+          malware_daemon_ctx.sock);
+
+      if (bread == sizeof(av_buffer))
+        return m_panic_defer_3(scanent, CUS callout_address,
+          US"buffer too small", malware_daemon_ctx.sock);
+
+      av_buffer[bread] = '\0';
+      linebuffer = string_copy(av_buffer);
+
+      m_sock_send(malware_daemon_ctx.sock, US"QUIT\n", 5, 0);
+
+      if ((e = m_pcre_exec(fprot6d_re_error, linebuffer)))
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("scanner reported error (%s)", e), malware_daemon_ctx.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      */
@@ -602,30 +791,30 @@ if (!malware_ok)
       if (*scanner_options != '/')
        {
        /* calc file size */
-       if ((drweb_fd = open(CCS eml_filename, O_RDONLY)) == -1)
-         return m_errlog_defer_3(scanent, NULL,
+       if ((drweb_fd = exim_open2(CCS eml_filename, O_RDONLY)) == -1)
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't open spool file %s: %s",
              eml_filename, strerror(errno)),
-           sock);
+           malware_daemon_ctx.sock);
 
        if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
          {
          int err;
 badseek:  err = errno;
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
              eml_filename, strerror(err)),
-           sock);
+           malware_daemon_ctx.sock);
          }
        fsize_uint = (unsigned int) fsize;
        if ((off_t)fsize_uint != fsize)
          {
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("seeking spool file %s, size overflow",
              eml_filename),
-           sock);
+           malware_daemon_ctx.sock);
          }
        drweb_slen = htonl(fsize);
        if (lseek(drweb_fd, 0, SEEK_SET) < 0)
@@ -635,46 +824,47 @@ badseek:  err = errno;
            scanner_name, scanner_options);
 
        /* send scan request */
-       if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
-           (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
-           (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
-           (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+       if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
          {
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send commands to socket (%s)", scanner_options),
-           sock);
+           malware_daemon_ctx.sock);
          }
 
-       if (!(drweb_fbuf = US malloc(fsize_uint)))
+       if (!(drweb_fbuf = store_malloc(fsize_uint)))
          {
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("unable to allocate memory %u for file (%s)",
              fsize_uint, eml_filename),
-           sock);
+           malware_daemon_ctx.sock);
          }
 
        if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1)
          {
          int err = errno;
          (void)close(drweb_fd);
-         free(drweb_fbuf);
-         return m_errlog_defer_3(scanent, NULL,
+         store_free(drweb_fbuf);
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't read spool file %s: %s",
              eml_filename, strerror(err)),
-           sock);
+           malware_daemon_ctx.sock);
          }
        (void)close(drweb_fd);
 
        /* send file body to socket */
-       if (send(sock, drweb_fbuf, fsize, 0) < 0)
+       if (send(malware_daemon_ctx.sock, drweb_fbuf, fsize, 0) < 0)
          {
-         free(drweb_fbuf);
-         return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+         store_free(drweb_fbuf);
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send file body to socket (%s)", scanner_options),
-           sock);
+           malware_daemon_ctx.sock);
          }
+       store_free(drweb_fbuf);
        }
       else
        {
@@ -684,31 +874,31 @@ badseek:  err = errno;
            scanner_name, scanner_options);
 
        /* send scan request */
-       if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
-           (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
-           (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
-           (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
-           (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
-         return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+       if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send commands to socket (%s)", scanner_options),
-           sock);
+           malware_daemon_ctx.sock);
        }
 
       /* wait for result */
-      if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
-       return m_errlog_defer_3(scanent, CUS callout_address,
-                   US"unable to read return code", sock);
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_rc, sizeof(drweb_rc), tmo))
+       return m_panic_defer_3(scanent, CUS callout_address,
+                   US"unable to read return code", malware_daemon_ctx.sock);
       drweb_rc = ntohl(drweb_rc);
 
-      if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
-       return m_errlog_defer_3(scanent, CUS callout_address,
-                           US"unable to read the number of viruses", sock);
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
+       return m_panic_defer_3(scanent, CUS callout_address,
+                           US"unable to read the number of viruses", malware_daemon_ctx.sock);
       drweb_vnum = ntohl(drweb_vnum);
 
       /* "virus(es) found" if virus number is > 0 */
       if (drweb_vnum)
        {
-       int i;
+       gstring * g = NULL;
 
        /* setup default virus name */
        malware_name = US"unknown";
@@ -718,25 +908,28 @@ badseek:  err = errno;
          drweb_re = m_pcre_compile(drweb_re_str, &errstr);
 
        /* read and concatenate virus names into one string */
-       for (i = 0; i < drweb_vnum; i++)
+       for (int i = 0; i < drweb_vnum; i++)
          {
-         int size = 0, off = 0, ovector[10*3];
+         int ovector[10*3];
+
          /* read the size of report */
-         if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
-           return m_errlog_defer_3(scanent, CUS callout_address,
-                             US"cannot read report size", sock);
+         if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo))
+           return m_panic_defer_3(scanent, CUS callout_address,
+                             US"cannot read report size", malware_daemon_ctx.sock);
          drweb_slen = ntohl(drweb_slen);
-         tmpbuf = store_get(drweb_slen);
+
+         /* assume tainted, since it is external input */
+         tmpbuf = store_get(drweb_slen, TRUE);
 
          /* read report body */
-         if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
-           return m_errlog_defer_3(scanent, CUS callout_address,
-                             US"cannot read report string", sock);
+         if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo))
+           return m_panic_defer_3(scanent, CUS callout_address,
+                             US"cannot read report string", malware_daemon_ctx.sock);
          tmpbuf[drweb_slen] = '\0';
 
          /* 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;
@@ -744,16 +937,16 @@ badseek:  err = errno;
            pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
 
            if (i==0)   /* the first name we just copy to malware_name */
-             malware_name = string_append(NULL, &size, &off,
-                                         1, pre_malware_nb);
+             g = string_cat(NULL, US pre_malware_nb);
 
+           /*XXX could be string_append_listele? */
            else        /* concatenate each new virus name to previous */
-             malware_name = string_append(malware_name, &size, &off,
-                                         2, "/", pre_malware_nb);
+             g = string_append(g, 2, "/", pre_malware_nb);
 
            pcre_free_substring(pre_malware_nb);
            }
          }
+         malware_name = string_from_gstring(g);
        }
       else
        {
@@ -768,16 +961,18 @@ badseek:  err = errno;
         * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR
         * and others are ignored */
        if (drweb_s)
-         return m_errlog_defer_3(scanent, CUS callout_address,
+         return m_panic_defer_3(scanent, CUS callout_address,
            string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
-           sock);
+           malware_daemon_ctx.sock);
 
        /* no virus found */
        malware_name = NULL;
        }
       break;
       }        /* drweb */
+#endif
 
+#ifndef DISABLE_MAL_AVE
     case M_AVES: /* "aveserver" scanner type -------------------------------- */
       {
       uschar buf[32768];
@@ -785,13 +980,13 @@ badseek:  err = errno;
 
       /* read aveserver's greeting and see if it is ready (2xx greeting) */
       buf[0] = 0;
-      recv_line(sock, buf, sizeof(buf), tmo);
+      recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);
 
       if (buf[0] != '2')               /* aveserver is having problems */
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unavailable (Responded: %s).",
                          ((buf[0] != 0) ? buf : US "nothing") ),
-         sock);
+         malware_daemon_ctx.sock);
 
       /* prepare our command */
       (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n",
@@ -800,19 +995,19 @@ badseek:  err = errno;
       /* and send it */
       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);
+      if (m_sock_send(malware_daemon_ctx.sock, buf, Ustrlen(buf), &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       malware_name = NULL;
       result = 0;
       /* read response lines, find malware name and final response */
-      while (recv_line(sock, buf, sizeof(buf), tmo) > 0)
+      while (recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo) > 0)
        {
        if (buf[0] == '2')
          break;
        if (buf[0] == '5')              /* aveserver is having problems */
          {
-         result = m_errlog_defer(scanent, CUS callout_address,
+         result = m_panic_defer(scanent, CUS callout_address,
             string_sprintf("unable to scan file %s (Responded: %s).",
                             eml_filename, buf));
          break;
@@ -825,30 +1020,32 @@ badseek:  err = errno;
          }
        }
 
-      if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+      if (m_sock_send(malware_daemon_ctx.sock, US"quit\r\n", 6, &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* read aveserver's greeting and see if it is ready (2xx greeting) */
       buf[0] = 0;
-      recv_line(sock, buf, sizeof(buf), tmo);
+      recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);
 
       if (buf[0] != '2')               /* aveserver is having problems */
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to quit dialogue (Responded: %s).",
                        ((buf[0] != 0) ? buf : US "nothing") ),
-         sock);
+         malware_daemon_ctx.sock);
 
       if (result == DEFER)
        {
-       (void)close(sock);
+       (void)close(malware_daemon_ctx.sock);
        return DEFER;
        }
       break;
       }        /* aveserver */
+#endif
 
+#ifndef DISABLE_MAL_FSECURE
     case M_FSEC: /* "fsecure" scanner type ---------------------------------- */
       {
-      int i, j, bread = 0;
+      int i, bread = 0;
       uschar * file_name;
       uschar av_buffer[1024];
       static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n",
@@ -862,19 +1059,19 @@ 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)
-         return m_errlog_defer(scanent, CUS callout_address, errstr);
+       if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+         return m_panic_defer(scanent, CUS callout_address, errstr);
 
-       bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+       bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
        if (bread > 0) av_buffer[bread]='\0';
        if (bread < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address,
+         return m_panic_defer_3(scanent, CUS callout_address,
            string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
-           sock);
-       for (j = 0; j < bread; j++)
+           malware_daemon_ctx.sock);
+       for (int j = 0; j < bread; j++)
          if (av_buffer[j] == '\r' || av_buffer[j] == '\n')
            av_buffer[j] ='@';
        }
@@ -882,8 +1079,8 @@ badseek:  err = errno;
       /* pass the mailfile to fsecure */
       file_name = string_sprintf("SCAN\t%s\n", eml_filename);
 
-      if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+      if (m_sock_send(malware_daemon_ctx.sock, file_name, Ustrlen(file_name), &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* set up match */
       /* todo also SUSPICION\t */
@@ -900,10 +1097,10 @@ badseek:  err = errno;
          {
          errno = ETIMEDOUT;
          i =  av_buffer+sizeof(av_buffer)-p;
-         if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
-           return m_errlog_defer_3(scanent, CUS callout_address,
+         if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo)) < 0)
+           return m_panic_defer_3(scanent, CUS callout_address,
              string_sprintf("unable to read result (%s)", strerror(errno)),
-             sock);
+             malware_daemon_ctx.sock);
 
          for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1)
            {
@@ -928,7 +1125,9 @@ badseek:  err = errno;
       fsec_found:
        break;
       }        /* fsecure */
+#endif
 
+#ifndef DISABLE_MAL_KAV
     case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */
       {
       time_t t;
@@ -958,28 +1157,28 @@ badseek:  err = errno;
          scanner_name, scanner_options);
 
       /* send scan request */
-      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+      if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* wait for result */
-      if (!recv_len(sock, tmpbuf, 2, tmo))
-       return m_errlog_defer_3(scanent, CUS callout_address,
-                           US"unable to read 2 bytes from socket.", sock);
+      if (!recv_len(malware_daemon_ctx.sock, tmpbuf, 2, tmo))
+       return m_panic_defer_3(scanent, CUS callout_address,
+                           US"unable to read 2 bytes from socket.", malware_daemon_ctx.sock);
 
       /* get errorcode from one nibble */
       kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F;
       switch(kav_rc)
       {
       case 5: case 6: /* improper kavdaemon configuration */
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
-               sock);
+               malware_daemon_ctx.sock);
       case 1:
-       return m_errlog_defer_3(scanent, CUS callout_address,
-               US"reported 'scanning not completed' (code 1).", sock);
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"reported 'scanning not completed' (code 1).", malware_daemon_ctx.sock);
       case 7:
-       return m_errlog_defer_3(scanent, CUS callout_address,
-               US"reported 'kavdaemon damaged' (code 7).", sock);
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"reported 'kavdaemon damaged' (code 7).", malware_daemon_ctx.sock);
       }
 
       /* code 8 is not handled, since it is ambiguous. It appears mostly on
@@ -999,9 +1198,9 @@ badseek:  err = errno;
        if (report_flag == 1)
          {
          /* read report size */
-         if (!recv_len(sock, &kav_reportlen, 4, tmo))
-           return m_errlog_defer_3(scanent, CUS callout_address,
-                 US"cannot read report size", sock);
+         if (!recv_len(malware_daemon_ctx.sock, &kav_reportlen, 4, tmo))
+           return m_panic_defer_3(scanent, CUS callout_address,
+                 US"cannot read report size", malware_daemon_ctx.sock);
 
          /* it's possible that avp returns av_buffer[1] == 1 but the
          reportsize is 0 (!?) */
@@ -1024,7 +1223,7 @@ badseek:  err = errno;
            /* coverity[tainted_data] */
            while (kav_reportlen > 0)
              {
-             if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
+             if ((bread = recv_line(malware_daemon_ctx.sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
                break;
              kav_reportlen -= bread+1;
 
@@ -1040,7 +1239,9 @@ badseek:  err = errno;
 
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_CMDLINE
     case M_CMDL: /* "cmdline" scanner type ---------------------------------- */
       {
       const uschar *cmdline_scanner = scanner_options;
@@ -1059,19 +1260,19 @@ badseek:  err = errno;
       uschar *p;
 
       if (!cmdline_scanner)
-       return m_errlog_defer(scanent, NULL, errstr);
+       return m_panic_defer(scanent, NULL, errstr);
 
       /* find scanner output trigger */
       cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                                "missing trigger specification", &errstr);
       if (!cmdline_trigger_re)
-       return m_errlog_defer(scanent, NULL, errstr);
+       return m_panic_defer(scanent, NULL, errstr);
 
       /* find scanner name regex */
       cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                          "missing virus name regex specification", &errstr);
       if (!cmdline_regex_re)
-       return m_errlog_defer(scanent, NULL, errstr);
+       return m_panic_defer(scanent, NULL, errstr);
 
       /* prepare scanner call; despite the naming, file_name holds a directory
       name which is documented as the value given to %s. */
@@ -1096,7 +1297,7 @@ badseek:  err = errno;
        {
        int err = errno;
        signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-       return m_errlog_defer(scanent, NULL,
+       return m_panic_defer(scanent, NULL,
          string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
        }
       scanner_fd = fileno(scanner_out);
@@ -1108,7 +1309,7 @@ badseek:  err = errno;
        int err = errno;
        (void) pclose(scanner_out);
        signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-       return m_errlog_defer(scanent, NULL, string_sprintf(
+       return m_panic_defer(scanent, NULL, string_sprintf(
            "opening scanner output file (%s) failed: %s.",
            file_name, strerror(err)));
        }
@@ -1124,7 +1325,7 @@ badseek:  err = errno;
            break;
          (void) pclose(scanner_out);
          signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-         return m_errlog_defer(scanent, NULL, string_sprintf(
+         return m_panic_defer(scanent, NULL, string_sprintf(
              "unable to read from scanner (%s): %s",
              commandline, strerror(err)));
          }
@@ -1134,7 +1335,7 @@ badseek:  err = errno;
          /* short write */
          (void) pclose(scanner_out);
          signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-         return m_errlog_defer(scanent, NULL, string_sprintf(
+         return m_panic_defer(scanent, NULL, string_sprintf(
            "short write on scanner output file (%s).", file_name));
          }
        putc('\n', scanner_record);
@@ -1149,7 +1350,7 @@ badseek:  err = errno;
       sep = pclose(scanner_out);
       signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
       if (sep != 0)
-         return m_errlog_defer(scanent, NULL, 
+         return m_panic_defer(scanent, NULL,
              sep == -1
              ? string_sprintf("running scanner failed: %s", strerror(sep))
              : string_sprintf("scanner returned error code: %d", sep));
@@ -1161,20 +1362,19 @@ badseek:  err = errno;
        malware_name = US"unknown";
 
        /* re-open the scanner output file, look for name match */
-       scanner_record = fopen(CS file_name, "rb");
-       while (fgets(CS linebuffer, sizeof(linebuffer), scanner_record))
-         {
-         /* try match */
-         if ((s = m_pcre_exec(cmdline_regex_re, linebuffer)))
+       scanner_record = Ufopen(file_name, "rb");
+       while (Ufgets(linebuffer, sizeof(linebuffer), scanner_record))
+         if ((s = m_pcre_exec(cmdline_regex_re, linebuffer))) /* try match */
            malware_name = s;
-         }
        (void)fclose(scanner_record);
        }
       else /* no virus found */
        malware_name = NULL;
       break;
       }        /* cmdline */
+#endif
 
+#ifndef DISABLE_MAL_SOPHIE
     case M_SOPHIE: /* "sophie" scanner type --------------------------------- */
       {
       int bread = 0;
@@ -1190,19 +1390,19 @@ badseek:  err = errno;
       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
-        || write(sock, "\n", 1) != 1
+      if (  write(malware_daemon_ctx.sock, file_name, Ustrlen(file_name)) < 0
+        || write(malware_daemon_ctx.sock, "\n", 1) != 1
         )
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to write to UNIX socket (%s)", scanner_options),
-         sock);
+         malware_daemon_ctx.sock);
 
       /* wait for result */
       memset(av_buffer, 0, sizeof(av_buffer));
-      if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
-       return m_errlog_defer_3(scanent, CUS callout_address,
+      if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo)) <= 0)
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
-         sock);
+         malware_daemon_ctx.sock);
 
       /* infected ? */
       if (av_buffer[0] == '1') {
@@ -1212,14 +1412,16 @@ badseek:  err = errno;
        malware_name = string_copy(&av_buffer[2]);
       }
       else if (!strncmp(CS av_buffer, "-1", 2))
-       return m_errlog_defer_3(scanent, CUS callout_address,
-               US"scanner reported error", sock);
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"scanner reported error", malware_daemon_ctx.sock);
       else /* all ok, no virus */
        malware_name = NULL;
 
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_CLAM
     case M_CLAMD: /* "clamd" scanner type ----------------------------------- */
       {
 /* This code was originally contributed by David Saez */
@@ -1230,13 +1432,11 @@ badseek:  err = errno;
 * The zINSTREAM command was introduced with ClamAV 0.95, which marked
 * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095
 * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that
-* the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless
-* WITH_OLD_CLAMAV_STREAM is defined.
+* the TCP-connected daemon is actually local; otherwise we use zINSTREAM
 * See Exim bug 926 for details.  */
 
       uschar *p, *vname, *result_tag;
       int bread=0;
-      uschar * file_name;
       uschar av_buffer[1024];
       uschar *hostname = US"";
       host_item connhost;
@@ -1247,13 +1447,8 @@ badseek:  err = errno;
       BOOL use_scan_command = FALSE;
       clamd_address * cv[MAX_CLAMD_SERVERS];
       int num_servers = 0;
-#ifdef WITH_OLD_CLAMAV_STREAM
-      unsigned int port;
-      uschar av_buffer2[1024];
-      int sockData;
-#else
       uint32_t send_size, send_final_zeroblock;
-#endif
+      blob cmd_str;
 
       /*XXX if unixdomain socket, only one server supported. Needs fixing;
       there's no reason we should not mix local and remote servers */
@@ -1267,7 +1462,7 @@ badseek:  err = errno;
        /* Local file; so we def want to use_scan_command and don't want to try
         * passing IP/port combinations */
        use_scan_command = TRUE;
-       cd = (clamd_address *) store_get(sizeof(clamd_address));
+       cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE);
 
        /* extract socket-path part */
        sublist = scanner_options;
@@ -1275,7 +1470,7 @@ badseek:  err = errno;
 
        /* parse options */
        if (clamd_option(cd, sublist, &subsep) != OK)
-         return m_errlog_defer(scanent, NULL,
+         return m_panic_defer(scanent, NULL,
            string_sprintf("bad option '%s'", scanner_options));
        cv[0] = cd;
        }
@@ -1301,19 +1496,19 @@ badseek:  err = errno;
            continue;
            }
 
-         cd = (clamd_address *) store_get(sizeof(clamd_address));
+         cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE);
 
          /* extract host and port part */
          sublist = scanner_options;
          if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
            {
-           (void) m_errlog_defer(scanent, NULL, 
+           (void) m_panic_defer(scanent, NULL,
                      string_sprintf("missing address: '%s'", scanner_options));
            continue;
            }
          if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
            {
-           (void) m_errlog_defer(scanent, NULL, 
+           (void) m_panic_defer(scanent, NULL,
                      string_sprintf("missing port: '%s'", scanner_options));
            continue;
            }
@@ -1322,13 +1517,13 @@ badseek:  err = errno;
          /* parse options */
          /*XXX should these options be common over scanner types? */
          if (clamd_option(cd, sublist, &subsep) != OK)
-           return m_errlog_defer(scanent, NULL,
+           return m_panic_defer(scanent, NULL,
              string_sprintf("bad option '%s'", scanner_options));
 
          cv[num_servers++] = cd;
          if (num_servers >= MAX_CLAMD_SERVERS)
            {
-           (void) m_errlog_defer(scanent, NULL,
+           (void) m_panic_defer(scanent, NULL,
                  US"More than " MAX_CLAMD_SERVERS_S " clamd servers "
                  "specified; only using the first " MAX_CLAMD_SERVERS_S );
            break;
@@ -1338,17 +1533,26 @@ badseek:  err = errno;
 
        /* check if we have at least one server */
        if (!num_servers)
-         return m_errlog_defer(scanent, NULL,
+         return m_panic_defer(scanent, NULL,
            US"no useable server addresses in malware configuration option.");
        }
 
       /* See the discussion of response formats below to see why we really
       don't like colons in filenames when passing filenames to ClamAV. */
       if (use_scan_command && Ustrchr(eml_filename, ':'))
-       return m_errlog_defer(scanent, NULL,
+       return m_panic_defer(scanent, NULL,
          string_sprintf("local/SCAN mode incompatible with" \
            " : in path to email filename [%s]", eml_filename));
 
+      /* Set up the very first data we will be sending */
+      if (!use_scan_command)
+       { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; }
+      else
+       {
+       cmd_str.data = string_sprintf("SCAN %s\n", eml_filename);
+       cmd_str.len = Ustrlen(cmd_str.data);
+       }
+
       /* We have some network servers specified */
       if (num_servers)
        {
@@ -1358,7 +1562,7 @@ badseek:  err = errno;
 
        while (num_servers > 0)
          {
-         int i = random_number( num_servers );
+         int i = random_number(num_servers);
          clamd_address * cd = cv[i];
 
          DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n",
@@ -1368,20 +1572,22 @@ badseek:  err = errno;
           * on both connections (as one host could resolve to multiple ips) */
          for (;;)
            {
-           sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr);
-           if (sock >= 0)
+           /*XXX we trust that the cmd_str is ideempotent */
+           if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
+                                   &connhost, &errstr, &cmd_str)) >= 0)
              {
              /* Connection successfully established with a server */
              hostname = cd->hostspec;
+             cmd_str.len = 0;
              break;
              }
            if (cd->retry <= 0) break;
            while (cd->retry > 0) cd->retry = sleep(cd->retry);
            }
-         if (sock >= 0)
+         if (malware_daemon_ctx.sock >= 0)
            break;
 
-         (void) m_errlog_defer(scanent, CUS callout_address, errstr);
+         (void) m_panic_defer(scanent, CUS callout_address, errstr);
 
          /* Remove the server from the list. XXX We should free the memory */
          num_servers--;
@@ -1390,18 +1596,18 @@ badseek:  err = errno;
          }
 
        if (num_servers == 0)
-         return m_errlog_defer(scanent, NULL, US"all servers failed");
+         return m_panic_defer(scanent, NULL, US"all servers failed");
        }
       else
        for (;;)
          {
-         if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+         if ((malware_daemon_ctx.sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
            {
            hostname = cv[0]->hostspec;
            break;
            }
          if (cv[0]->retry <= 0)
-           return m_errlog_defer(scanent, CUS callout_address, errstr);
+           return m_panic_defer(scanent, CUS callout_address, errstr);
          while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry);
          }
 
@@ -1412,49 +1618,6 @@ badseek:  err = errno;
 
       if (!use_scan_command)
        {
-#ifdef WITH_OLD_CLAMAV_STREAM
-       /* "STREAM\n" command, get back a "PORT <N>\n" response, send data to
-        * 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_indent(
-           "Malware scan: issuing %s old-style remote scan (PORT)\n",
-           scanner_name);
-
-       /* Pass the string to ClamAV (7 = "STREAM\n") */
-       if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0)
-         return m_errlog_defer(scanent, CUS callout_address, errstr);
-
-       memset(av_buffer2, 0, sizeof(av_buffer2));
-       bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));
-
-       if (bread < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address,
-           string_sprintf("unable to read PORT from socket (%s)",
-               strerror(errno)),
-           sock);
-
-       if (bread == sizeof(av_buffer2))
-         return m_errlog_defer_3(scanent, CUS callout_address,
-                 "buffer too small", sock);
-
-       if (!(*av_buffer2))
-         return m_errlog_defer_3(scanent, CUS callout_address,
-                 "ClamAV returned null", sock);
-
-       av_buffer2[bread] = '\0';
-       if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 )
-         return m_errlog_defer_3(scanent, CUS callout_address,
-           string_sprintf("Expected port information from clamd, got '%s'",
-             av_buffer2),
-           sock);
-
-       sockData = m_tcpsocket(connhost.address, port, NULL, &errstr);
-       if (sockData < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address, errstr, sock);
-
-# define CLOSE_SOCKDATA (void)close(sockData)
-#else /* WITH_OLD_CLAMAV_STREAM not defined */
        /* New protocol: "zINSTREAM\n" followed by a sequence of <length><data>
        chunks, <n> a 4-byte number (network order), terminated by a zero-length
        chunk. */
@@ -1463,96 +1626,78 @@ badseek:  err = errno;
            "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
            scanner_name);
 
-       /* Pass the string to ClamAV (10 = "zINSTREAM\0") */
-       if (send(sock, "zINSTREAM", 10, 0) < 0)
-         return m_errlog_defer_3(scanent, CUS hostname,
-           string_sprintf("unable to send zINSTREAM to socket (%s)",
-             strerror(errno)),
-           sock);
-
-# define CLOSE_SOCKDATA /**/
-#endif
+       /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
+       if (cmd_str.len)
+         if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
+           return m_panic_defer_3(scanent, CUS hostname,
+             string_sprintf("unable to send zINSTREAM to socket (%s)",
+               strerror(errno)),
+             malware_daemon_ctx.sock);
 
        /* calc file size */
-       if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
+       if ((clam_fd = exim_open2(CS eml_filename, O_RDONLY)) < 0)
          {
          int err = errno;
-         CLOSE_SOCKDATA;
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't open spool file %s: %s",
              eml_filename, strerror(err)),
-           sock);
+           malware_daemon_ctx.sock);
          }
        if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
          {
          int err;
 b_seek:   err = errno;
-         CLOSE_SOCKDATA; (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
              eml_filename, strerror(err)),
-           sock);
+           malware_daemon_ctx.sock);
          }
        fsize_uint = (unsigned int) fsize;
        if ((off_t)fsize_uint != fsize)
          {
-         CLOSE_SOCKDATA; (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("seeking spool file %s, size overflow",
              eml_filename),
-           sock);
+           malware_daemon_ctx.sock);
          }
        if (lseek(clam_fd, 0, SEEK_SET) < 0)
          goto b_seek;
 
-       if (!(clamav_fbuf = US malloc(fsize_uint)))
+       if (!(clamav_fbuf = store_malloc(fsize_uint)))
          {
-         CLOSE_SOCKDATA; (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("unable to allocate memory %u for file (%s)",
              fsize_uint, eml_filename),
-           sock);
+           malware_daemon_ctx.sock);
          }
 
        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, NULL,
+         store_free(clamav_fbuf); (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't read spool file %s: %s",
              eml_filename, strerror(err)),
-           sock);
+           malware_daemon_ctx.sock);
          }
        (void)close(clam_fd);
 
        /* send file body to socket */
-#ifdef WITH_OLD_CLAMAV_STREAM
-       if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0)
-         {
-         free(clamav_fbuf); CLOSE_SOCKDATA;
-         return m_errlog_defer_3(scanent, NULL,
-           string_sprintf("unable to send file body to socket (%s:%u)",
-             hostname, port),
-           sock);
-         }
-#else
        send_size = htonl(fsize_uint);
        send_final_zeroblock = 0;
-       if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
-           (send(sock, clamav_fbuf, fsize_uint, 0) < 0) ||
-           (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
+       if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
          {
-         free(clamav_fbuf);
-         return m_errlog_defer_3(scanent, NULL,
+         store_free(clamav_fbuf);
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("unable to send file body to socket (%s)", hostname),
-           sock);
+           malware_daemon_ctx.sock);
          }
-#endif
-
-       free(clamav_fbuf);
-
-       CLOSE_SOCKDATA;
-#undef CLOSE_SOCKDATA
+       store_free(clamav_fbuf);
        }
       else
        { /* use scan command */
@@ -1569,17 +1714,17 @@ b_seek:   err = errno;
        scanned twice, in the broken out files and from the original .eml.
        Since ClamAV now handles emails (and has for quite some time) we can
        just use the email file itself. */
-       /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
-       file_name = string_sprintf("SCAN %s\n", eml_filename);
+       /* Pass the string to ClamAV (7 = "SCAN \n" + \0), if not already sent */
 
        DEBUG(D_acl) debug_printf_indent(
            "Malware scan: issuing %s local-path scan [%s]\n",
            scanner_name, scanner_options);
 
-       if (send(sock, file_name, Ustrlen(file_name), 0) < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address,
-           string_sprintf("unable to write to socket (%s)", strerror(errno)),
-           sock);
+       if (cmd_str.len)
+         if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
+           return m_panic_defer_3(scanent, CUS callout_address,
+             string_sprintf("unable to write to socket (%s)", strerror(errno)),
+             malware_daemon_ctx.sock);
 
        /* Do not shut down the socket for writing; a user report noted that
         * clamd 0.70 does not react well to this. */
@@ -1589,17 +1734,18 @@ b_seek:   err = errno;
 
       /* Read the result */
       memset(av_buffer, 0, sizeof(av_buffer));
-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
-      (void)close(sock);
-      sock = -1;
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+      (void)close(malware_daemon_ctx.sock);
+      malware_daemon_ctx.sock = -1;
+      malware_daemon_ctx.tls_ctx = NULL;
 
       if (bread <= 0)
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
          string_sprintf("unable to read from socket (%s)",
          errno == 0 ? "EOF" : strerror(errno)));
 
       if (bread == sizeof(av_buffer))
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
                US"buffer too small");
       /* We're now assured of a NULL at the end of av_buffer */
 
@@ -1624,7 +1770,7 @@ b_seek:   err = errno;
       passing a filename to clamd). */
 
       if (!(*av_buffer))
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
                US"ClamAV returned null");
 
       /* strip newline at the end (won't be present for zINSTREAM)
@@ -1641,7 +1787,7 @@ b_seek:   err = errno;
 
       /* colon in returned output? */
       if(!(p = Ustrchr(av_buffer,':')))
-       return m_errlog_defer(scanent, CUS callout_address, string_sprintf(
+       return m_panic_defer(scanent, CUS callout_address, string_sprintf(
                  "ClamAV returned malformed result (missing colon): %s",
                  av_buffer));
 
@@ -1674,7 +1820,7 @@ b_seek:   err = errno;
 
        }
       else if (Ustrcmp(result_tag, "ERROR") == 0)
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
          string_sprintf("ClamAV returned: %s", av_buffer));
 
       else if (Ustrcmp(result_tag, "OK") == 0)
@@ -1685,12 +1831,14 @@ b_seek:   err = errno;
 
        }
       else
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
          string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
 
       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
@@ -1714,8 +1862,8 @@ b_seek:   err = errno;
        uschar * s = Ustrchr(sockline_scanner, '%');
        if (s++)
          if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
-           return m_errlog_defer_3(scanent, NULL,
-                                 US"unsafe sock scanner call spec", sock);
+           return m_panic_defer_3(scanent, NULL,
+                                 US"unsafe sock scanner call spec", malware_daemon_ctx.sock);
       }
       else
        sockline_scanner = sockline_scanner_default;
@@ -1726,13 +1874,13 @@ b_seek:   err = errno;
       sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                                "missing trigger specification", &errstr);
       if (!sockline_trig_re)
-       return m_errlog_defer_3(scanent, NULL, errstr, sock);
+       return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);
 
       /* find virus name regex */
       sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                          "missing virus name regex specification", &errstr);
       if (!sockline_name_re)
-       return m_errlog_defer_3(scanent, NULL, errstr, sock);
+       return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);
 
       /* prepare scanner call - security depends on expansions check above */
       commandline = string_sprintf( CS sockline_scanner, CS eml_filename);
@@ -1740,20 +1888,20 @@ b_seek:   err = errno;
        string_printing(commandline));
 
       /* Pass the command string to the socket */
-      if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+      if (m_sock_send(malware_daemon_ctx.sock, commandline, Ustrlen(commandline), &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* Read the result */
-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
 
       if (bread <= 0)
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to read from socket (%s)", strerror(errno)),
-         sock);
+         malware_daemon_ctx.sock);
 
       if (bread == sizeof(av_buffer))
-       return m_errlog_defer_3(scanent, CUS callout_address,
-               US"buffer too small", sock);
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"buffer too small", malware_daemon_ctx.sock);
       av_buffer[bread] = '\0';
       linebuffer = string_copy(av_buffer);
       DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ",
@@ -1771,7 +1919,9 @@ b_seek:   err = errno;
        malware_name = NULL;
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_MKS
     case M_MKSD: /* "mksd" scanner type ------------------------------------- */
       {
       char *mksd_options_end;
@@ -1786,32 +1936,36 @@ b_seek:   err = errno;
           || mksd_maxproc < 1
           || mksd_maxproc > 32
           )
-         return m_errlog_defer(scanent, CUS callout_address,
+         return m_panic_defer(scanent, CUS callout_address,
            string_sprintf("invalid option '%s'", scanner_options));
        }
 
-      if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+      if((malware_daemon_ctx.sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       malware_name = NULL;
 
       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)
+      if ((retval = mksd_scan_packed(scanent, malware_daemon_ctx.sock, eml_filename, tmo)) != OK)
        {
-       close (sock);
+       close (malware_daemon_ctx.sock);
        return retval;
        }
       break;
       }
+#endif
 
+#ifndef DISABLE_MAL_AVAST
     case M_AVAST: /* "avast" scanner type ----------------------------------- */
       {
-      int ovector[1*3];
       uschar buf[1024];
       uschar * scanrequest;
       enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage;
       int nread;
+      uschar * error_message = NULL;
+      BOOL more_data = FALSE;
+      BOOL strict = TRUE;
 
       /* According to Martin Tuma @avast the protocol uses "escaped
       whitespace", that is, every embedded whitespace is backslash
@@ -1820,34 +1974,75 @@ b_seek:   err = errno;
       and the [ ] marker.
       [+] - not infected
       [L] - infected
-      [E] - some error occured
-      Such marker follows the first non-escaped TAB.  */
+      [E] - some error occurred
+      Such marker follows the first non-escaped TAB.  For more information
+      see avast-protocol(5)
+
+      We observed two cases:
+      -> SCAN /file
+      <- /file [E]0.0 Error 13 Permission denied
+      <- 451 SCAN Engine error 13 permission denied
+
+      -> SCAN /file
+      <- /file… [E]3.0 Error 41120 The file is a decompression bomb
+      <- /file… [+]2.0
+      <- /file… [+]2.0 0 Eicar Test Virus!!!
+      <- 200 SCAN OK
+
+      If the scanner returns 4xx, DEFER is a good decision, combined
+      with a panic log entry, to get the admin's attention.
+
+      If the scanner returns 200, we reject it as malware, if found any,
+      or, in case of an error, we set the malware message to the error
+      string.
+
+      Some of the >= 42000 errors are message related - usually some
+      broken archives etc, but some of them are e.g. license related.
+      Once the license expires the engine starts returning errors for
+      every scanning attempt.  I¹ have the full list of the error codes
+      but it is not a public API and is subject to change. It is hard
+      for me to say what you should do in case of an engine error. You
+      can have a “Treat * unscanned file as infection” policy or “Treat
+      unscanned file as clean” policy.  ¹) Jakub Bednar
+
+       */
+
       if (  (  !ava_re_clean
             && !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr)))
         || (  !ava_re_virus
            && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr)))
+        || (  !ava_re_error
+           && !(ava_re_error = m_pcre_compile(ava_re_error_str, &errstr)))
         )
-       return malware_errlog_defer(errstr);
+       return malware_panic_defer(errstr);
 
       /* wait for result */
       for (avast_stage = AVA_HELO;
-          (nread = recv_line(sock, buf, sizeof(buf), tmo)) > 0;
+          (nread = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) > 0;
          )
        {
        int slen = Ustrlen(buf);
        if (slen >= 1)
          {
-         DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf);
+
+          /* Multi line responses are bracketed between 210 … and nnn … */
+          if (Ustrncmp(buf, "210", 3) == 0)
+            {
+            more_data = 1;
+            continue;
+            }
+          else if (more_data && isdigit(buf[0])) more_data = 0;
+
          switch (avast_stage)
            {
            case AVA_HELO:
+              if (more_data) continue;
              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 (more_data) continue;
              if (Ustrncmp(buf, "200", 3) != 0)
                goto endloop;                   /* require a 200 */
 
@@ -1858,134 +2053,103 @@ b_seek:   err = errno;
              if ((scanrequest = string_nextinlist(&av_scanner_work, &sep,
                                NULL, 0)))
                {
+                if (Ustrcmp(scanrequest, "pass_unscanned") == 0)
+                  {
+                  DEBUG(D_acl) debug_printf_indent("pass unscanned files as clean\n");
+                  strict = FALSE;
+                  goto sendreq;
+                  }
                scanrequest = string_sprintf("%s\n", scanrequest);
                avast_stage = AVA_OPT;          /* just sent option */
+               DEBUG(D_acl) debug_printf_indent("send to avast OPTION: %s", scanrequest);
                }
              else
                {
                scanrequest = string_sprintf("SCAN %s\n", eml_dir);
                avast_stage = AVA_RSP;          /* just sent command */
+               DEBUG(D_acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir);
                }
 
              /* send config-cmd or scan-request to socket */
              len = Ustrlen(scanrequest);
-             if (send(sock, scanrequest, len, 0) < 0)
+             if (send(malware_daemon_ctx.sock, scanrequest, len, 0) == -1)
                {
                scanrequest[len-1] = '\0';
-               return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+               return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
                      "unable to send request '%s' to socket (%s): %s",
-                     scanrequest, scanner_options, strerror(errno)), sock);
+                     scanrequest, scanner_options, strerror(errno)), malware_daemon_ctx.sock);
                }
              break;
              }
 
            case AVA_RSP:
-             if (Ustrncmp(buf, "210", 3) == 0)
-               break;  /* ignore the "210 SCAN DATA" message */
 
-             if (pcre_exec(ava_re_clean, NULL, CS buf, slen,
-                   0, 0, ovector, nelements(ovector)) > 0)
-               break;
+             if (isdigit(buf[0]))  /* We're done */
+                goto endloop;
 
-             if ((malware_name = m_pcre_exec(ava_re_virus, 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];
+              if (malware_name)     /* Nothing else matters, just read on */
+                break;
 
-               avast_stage = AVA_DONE;
-               goto endloop;
-               }
+             if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+               break;
 
-             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, CUS callout_address,
-                         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;
-               }
+              if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
+                {
+                unescape(malware_name);
+                DEBUG(D_acl)
+                  debug_printf_indent("unescaped malware name: '%s'\n", malware_name);
+                break;
+                }
+
+              if (strict)           /* treat scanner errors as malware */
+                {
+                if ((malware_name = m_pcre_exec(ava_re_error, buf)))
+                  {
+                  unescape(malware_name);
+                  DEBUG(D_acl)
+                    debug_printf_indent("unescaped error message: '%s'\n", malware_name);
+                  break;
+                  }
+                }
+              else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+                {
+                log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf);
+                break;
+                }
+
+             /* here also for any unexpected response from the scanner */
+              DEBUG(D_acl) debug_printf("avast response not handled: '%s'\n", buf);
 
-             /* here for any unexpected response from the scanner */
              goto endloop;
 
-           case AVA_DONE:      log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
+           default:    log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
                            __FILE__, __LINE__, __FUNCTION__);
            }
          }
        }
-      endloop:
-
-      switch(avast_stage)
-       {
-       case AVA_HELO:
-       case AVA_OPT:
-       case AVA_RSP:   return m_errlog_defer_3(scanent, CUS callout_address,
-                           nread >= 0
-                           ? string_sprintf(
-                               "invalid response from scanner: '%s'", buf)
-                           : nread == -1
-                           ? US"EOF from scanner"
-                           : US"timeout from scanner",
-                         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);
+      endloop:
 
-      m_sock_send(sock, US"QUIT\n", 5, 0);
+      if (nread == -1) error_message = US"EOF from scanner";
+      else if (nread < 0) error_message = US"timeout from scanner";
+      else if (nread == 0) error_message = US"got nothing from scanner";
+      else if (buf[0] != '2') error_message = buf;
 
-      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);
+      DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n");
+      if (send(malware_daemon_ctx.sock, "QUIT\n", 5, 0) == -1)
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("unable to send quit request to socket (%s): %s",
+            scanner_options, strerror(errno)), malware_daemon_ctx.sock);
 
-      if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer)))
-        malware_name = NULL;
+      if (error_message)
+        return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock);
 
-      break;
-      }  /* f-prot6d */
+      }
+#endif
   }    /* scanner type switch */
 
-  if (sock >= 0)
-    (void) close (sock);
+  if (malware_daemon_ctx.sock >= 0)
+    (void) close (malware_daemon_ctx.sock);
   malware_ok = TRUE;                   /* set "been here, done that" marker */
   }
 
@@ -2056,7 +2220,7 @@ sender_address = US"malware-sender@example.net";
 return_path = US"";
 recipients_list = NULL;
 receive_add_recipient(US"malware-victim@example.net", -1);
-enable_dollar_recipients = TRUE;
+f.enable_dollar_recipients = TRUE;
 
 ret = malware_internal(US"*", eml_filename, 0);
 
@@ -2080,24 +2244,48 @@ 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_AVAST
 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 (!ava_re_error)
+  ava_re_error = regex_must_compile(ava_re_error_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_FFROT6D
 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)
+{
+fprintf(f, "Malware:");
+for (struct scan * sc = m_scans; sc->scancode != (scanner_t)-1; sc++) fprintf(f, " %s", sc->name);
+fprintf(f, "\n");
+}
+
+
+# endif        /*!MACRO_PREDEF*/
 #endif /*WITH_CONTENT_SCAN*/
 /*
  * vi: aw ai sw=2