Re: [exim-dev] New malware scanner type "sock"

Góra strony
Delete this message
Reply to this message
Autor: Jeremy Harris
Data:  
Dla: exim-dev
Temat: Re: [exim-dev] New malware scanner type "sock"
On 02/02/14 22:01, Phil Pennock wrote:
> On 2014-02-02 at 20:44 +0000, Jeremy Harris wrote:
>> I've attached it as a patch. Does anyone feel strongly that it should
>> not go into the exim sourcebase?
>
> No, but some fixes would be good before it goes in.


Thanks for the review. Attached is v2, now with a fairly
invasive refactor of malware.c Still TODO is the documentation.
The patch is against bb7b941.

I've run it past the testsuite but don't feel this is sufficient
before letting it corrupt the mainline. Therefore I'd like
reviewers and testers. I'll be able to test the malware "clamav/SCAN"
and (the new) "sock" variants.

This leaves:

f-protd
drweb
aveserver
fsecure
kavdaemon
cmdline
sophie
clamd/STREAM
clamd/zINSTREAM
mksd

Please mail either the list or me directly with any discovered bugs,
problems, poor coding practices, behaviour changes worthy of
documentation, or other suggestions. Please include a note of
the areas inspected (I'm not expecting everybody to cover everything!)

I'll collate the coverage.

--
Thanks,
Jeremy
diff --git a/src/src/acl.c b/src/src/acl.c
index 29e0617..f25debd 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -2846,9 +2846,9 @@ uschar *portstr;
uschar *portend;
host_item *h;
int portnum;
-int host_af;
int len;
int r, s;
+uschar * errstr;

hostname = string_nextinlist(&arg, &sep, NULL, 0);
portstr = string_nextinlist(&arg, &sep, NULL, 0);
@@ -2895,14 +2895,17 @@ if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
HDEBUG(D_acl)
debug_printf("udpsend [%s]:%d %s\n", h->address, portnum, arg);

-host_af = (Ustrchr(h->address, ':') == NULL)? AF_INET:AF_INET6;
-r = s = ip_socket(SOCK_DGRAM, host_af);
-if (r < 0) goto defer;
-r = ip_connect(s, host_af, h->address, portnum, 1);
+r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, 1, NULL, &errstr);
if (r < 0) goto defer;
len = Ustrlen(arg);
r = send(s, arg, len, 0);
-if (r < 0) goto defer;
+if (r < 0)
+ {
+ errstr = strerror(errno);
+ close(s);
+ goto defer;
+ }
+close(s);
if (r < len)
{
*log_msgptr =
@@ -2916,7 +2919,7 @@ HDEBUG(D_acl)
return OK;

defer:
-*log_msgptr = string_sprintf("\"udpsend\" failed: %s", strerror(errno));
+*log_msgptr = string_sprintf("\"udpsend\" failed: %s", errstr);
return DEFER;
}

diff --git a/src/src/expand.c b/src/src/expand.c
index 7e1b323..30735db 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -4537,76 +4537,9 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }


-          /* Sort out the server. */
-
-          shost.next = NULL;
-          shost.address = NULL;
-          shost.port = port;
-          shost.mx = -1;
-
-          namelen = Ustrlen(server_name);
-
-          /* Anything enclosed in [] must be an IP address. */
-
-          if (server_name[0] == '[' &&
-              server_name[namelen - 1] == ']')
-            {
-            server_name[namelen - 1] = 0;
-            server_name++;
-            if (string_is_ip_address(server_name, NULL) == 0)
-              {
-              expand_string_message =
-                string_sprintf("malformed IP address \"%s\"", server_name);
-              goto EXPAND_FAILED;
-              }
-            shost.name = shost.address = server_name;
-            }
-
-          /* Otherwise check for an unadorned IP address */
-
-          else if (string_is_ip_address(server_name, NULL) != 0)
-            shost.name = shost.address = server_name;
-
-          /* Otherwise lookup IP address(es) from the name */
-
-          else
-            {
-            shost.name = server_name;
-            if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL,
-                FALSE) != HOST_FOUND)
-              {
-              expand_string_message =
-                string_sprintf("no IP address found for host %s", shost.name);
-              goto EXPAND_FAILED;
-              }
-            }
-
-          /* Try to connect to the server - test each IP till one works */
-
-          for (h = &shost; h != NULL; h = h->next)
-            {
-            int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET;
-            if ((fd = ip_socket(SOCK_STREAM, af)) == -1)
-              {
-              expand_string_message = string_sprintf("failed to create socket: "
-                "%s", strerror(errno));
+      if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+          timeout, NULL, &expand_string_message)) < 0)
               goto SOCK_FAIL;
-              }
-
-            if (ip_connect(fd, af, h->address, port, timeout) == 0)
-              {
-              connected = TRUE;
-              break;
-              }
-            }
-
-          if (!connected)
-            {
-            expand_string_message = string_sprintf("failed to connect to "
-              "socket %s: couldn't connect to any host", sub_arg[0],
-              strerror(errno));
-            goto SOCK_FAIL;
-            }
           }


         /* Handle a Unix domain socket */
diff --git a/src/src/functions.h b/src/src/functions.h
index 9d933fe..f646d25 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -171,6 +171,8 @@ extern int     host_scan_for_local_hosts(host_item *, host_item **, BOOL *);
 extern void    invert_address(uschar *, uschar *);
 extern int     ip_bind(int, int, uschar *, int);
 extern int     ip_connect(int, int, uschar *, int, int);
+extern int     ip_connected_socket(int, const uschar *, int, int, int,
+          uschar **);
 extern int     ip_get_address_family(int);
 extern void    ip_keepalive(int, uschar *, BOOL);
 extern int     ip_recv(int, uschar *, int, int);
@@ -358,7 +360,7 @@ extern uschar *string_dequote(uschar **);
 extern BOOL    string_format(uschar *, int, const char *, ...) ALMOST_PRINTF(3,4);
 extern uschar *string_format_size(int, uschar *);
 extern int     string_interpret_escape(uschar **);
-extern int     string_is_ip_address(uschar *, int *);
+extern int     string_is_ip_address(const uschar *, int *);
 extern uschar *string_log_address(address_item *, BOOL, BOOL);
 extern uschar *string_nextinlist(uschar **, int *, uschar *, int);
 extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3);
diff --git a/src/src/ip.c b/src/src/ip.c
index d6e4e7a..6706b44 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -172,7 +172,7 @@ Arguments:
   af          AF_INET6 or AF_INET for the socket type
   address     the remote address, in text form
   port        the remote port
-  timeout     a timeout
+  timeout     a timeout (zero for indefinite timeout)


 Returns:      0 on success; -1 on failure, with errno set
 */
@@ -248,6 +248,105 @@ return -1;
 }



+/* Create a socket and connect to host (name or number, ipv6 ok)
+   at one of port-range.
+Arguments:
+  type          SOCK_DGRAM or SOCK_STREAM
+  af            AF_INET6 or AF_INET for the socket type
+  address       the remote address, in text form
+  portlo,porthi the remote port range
+  timeout       a timeout
+  connhost    if not NULL, host_item filled in with connection details
+  errstr        pointer for allocated string on error
+
+Return:
+  socket fd, or -1 on failure (having allocated an error string)
+*/
+int
+ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
+    int timeout, host_item * connhost, uschar ** errstr)
+{
+int namelen, port;
+host_item shost;
+host_item *h;
+int af, fd, fd4 = -1, fd6 = -1;
+
+shost.next = NULL;
+shost.address = NULL;
+shost.port = portlo;
+shost.mx = -1;
+
+namelen = Ustrlen(hostname);
+
+/* Anything enclosed in [] must be an IP address. */
+
+if (hostname[0] == '[' &&
+    hostname[namelen - 1] == ']')
+  {
+  uschar * host = string_copy(hostname);
+  host[namelen - 1] = 0;
+  host++;
+  if (string_is_ip_address(host, NULL) == 0)
+    {
+    *errstr = string_sprintf("malformed IP address \"%s\"", hostname);
+    return -1;
+    }
+  shost.name = shost.address = host;
+  }
+
+/* Otherwise check for an unadorned IP address */
+
+else if (string_is_ip_address(hostname, NULL) != 0)
+  shost.name = shost.address = string_copy(hostname);
+
+/* Otherwise lookup IP address(es) from the name */
+
+else
+  {
+  shost.name = string_copy(hostname);
+  if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL,
+      FALSE) != HOST_FOUND)
+    {
+    *errstr = string_sprintf("no IP address found for host %s", shost.name);
+    return -1;
+    }
+  }
+
+/* Try to connect to the server - test each IP till one works */
+
+for (h = &shost; h != NULL; h = h->next)
+  {
+  fd = (Ustrchr(h->address, ':') != 0)
+    ? (fd6 < 0) ? (fd6 = ip_socket(SOCK_STREAM, af = AF_INET6)) : fd6
+    : (fd4 < 0) ? (fd4 = ip_socket(SOCK_STREAM, af = AF_INET )) : fd4;
+
+  if (fd < 0)
+    {
+    *errstr = string_sprintf("failed to create socket: %s", strerror(errno));
+    goto bad;
+    }
+
+  for(port = portlo; port <= porthi; port++)
+    if (ip_connect(fd, af, h->address, port, timeout) == 0)
+      {
+      if (fd != fd6) close(fd6);
+      if (fd != fd4) close(fd4);
+      if (connhost) {
+    h->port = port;
+    *connhost = *h;
+    connhost->next = NULL;
+    }
+      return fd;
+      }
+  }
+
+*errstr = string_sprintf("failed to connect to "
+  "%s: couldn't connect to any host", hostname, strerror(errno));
+
+bad:
+  close(fd4); close(fd6); return -1;
+}
+


 /*************************************************
 *         Set keepalive on a socket              *
diff --git a/src/src/malware.c b/src/src/malware.c
index 2b4d282..93e30c0 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -28,7 +28,11 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)


/* SHUT_WR seems to be undefined on Unixware? */
#ifndef SHUT_WR
-#define SHUT_WR 1
+# define SHUT_WR 1
+#endif
+
+#ifndef nelements
+# define nelements(arr) (sizeof(arr) / sizeof(arr[0]))
#endif


@@ -50,14 +54,15 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 #define BIG_MY_ENDIAN      0
 #define LITTLE_MY_ENDIAN   1
 int test_byte_order(void);
-int test_byte_order() {
-      short int word = 0x0001;
-      char *byte = (char *) &word;
-      return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
+int
+test_byte_order()
+{
+  short int word = 0x0001;
+  char *byte = (char *) &word;
+  return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
 }


-uschar malware_name_buffer[256];
-int malware_ok = 0;
+BOOL malware_ok = FALSE;

 /* Gross hacks for the -bmalware option; perhaps we should just create
 the scan directory normally for that case, but look into rigging up the
@@ -78,21 +83,14 @@ Arguments:
 Returns:      Exim message processing code (OK, FAIL, DEFER, ...)
               where true means malware was found (condition applies)
 */
-int malware(uschar **listptr) {
-  uschar scan_filename[1024];
-  BOOL fits;
+int
+malware(uschar **listptr)
+{
+  uschar * scan_filename;
   int ret;


-  fits = string_format(scan_filename, sizeof(scan_filename),
-      CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
-  if (!fits)
-    {
-    av_failed = TRUE;
-    log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware filename does not fit in buffer [malware()]");
-    return DEFER;
-  }
-
+  scan_filename = string_sprintf("%s/scan/%s/%s.eml",
+                                spool_directory, message_id, message_id);
   ret = malware_internal(listptr, scan_filename, FALSE);
   if (ret == DEFER) av_failed = TRUE;


@@ -116,7 +114,8 @@ Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
                 where true means malware was found (condition applies)
 */
 int
-malware_in_file(uschar *eml_filename) {
+malware_in_file(uschar *eml_filename)
+{
   uschar *scan_options[2];
   uschar message_id_buf[64];
   int ret;
@@ -150,10 +149,216 @@ malware_in_file(uschar *eml_filename) {
 }



+static void
+malware_errlog(const uschar * str)
+{
+  log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
+}
+static inline int
+malware_errlog_defer(const uschar * str)
+{
+  malware_errlog(str);
+  return DEFER;
+}
+
+static int
+m_scanner_errlog_defer(const uschar * scanner, const uschar * str)
+{
+  return malware_errlog_defer(string_sprintf("%s: %s", scanner, str));
+}
+
+static int
+fprotd_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("f-protd", str);
+}
+static int
+drweb_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("drweb", str);
+}
+static int
+aves_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("aveserver", str);
+}
+static int
+fsec_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("fsecure", str);
+}
+static int
+kavd_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("kavdaemon", str);
+}
+static int
+cmdl_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("commandline", str);
+}
+static int
+soph_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("sophie", str);
+}
+static int
+clmd_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("clamd", str);
+}
+static int
+mksd_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("mksd", str);
+}
+static int
+sock_errlog_defer(const uschar * str)
+{
+  return m_scanner_errlog_defer("sock", str);
+}
+
+static void
+clmd_errlog(const uschar * str)
+{
+  log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: clamd: %s", str);
+}
+
+/*************************************************/
+
+/* 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)
+{
+  return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr);
+}
+
+static int
+m_tcpsocket_fromdef(const uschar * hostport, uschar ** errstr)
+{
+  int scan;
+  uschar hostname[256];
+  unsigned int portlow, porthigh;
+
+  /* extract host and port part */
+  scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh);
+  if ( scan != 3 ) {
+    if ( scan != 2 ) {
+      *errstr = string_sprintf("invalid socket '%s'", hostport);
+      return -1;
+    }
+    porthigh = portlow;
+  }
+
+  return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
+                5, NULL, errstr);
+}
+
+static int
+m_unixsocket(const uschar * path, uschar ** errstr)
+{
+  int sock;
+  struct sockaddr_un server;
+
+  if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+    *errstr = "can't open UNIX socket.";
+    return -1;
+  }
+
+  server.sun_family = AF_UNIX;
+  Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1);
+  server.sun_path[sizeof(server.sun_path)-1] = '\0';
+  if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) {
+    int err = errno;
+    (void)close(sock);
+    *errstr =  string_sprintf("unable to connect to UNIX socket (%s): %s",
+          path, strerror(err));
+    return -1;
+    }
+  return sock;
+}
+
+static int
+m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr)
+{
+  if (send(sock, buf, cnt, 0) < 0) {
+    int err = errno;
+    (void)close(sock);
+    *errstr = string_sprintf("unable to send to socket (%s): %s",
+       buf, strerror(err));
+    return -1;
+    }
+  return sock;
+}
+
+static const pcre *
+m_pcre_compile(const uschar * re, uschar ** errstr)
+{
+  const uschar * rerror;
+  int roffset;
+  const pcre * cre;
+
+  cre = pcre_compile(re, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
+  if (!cre)
+    *errstr= string_sprintf("regular expression error in '%s': %s at offset %d",
+    re, rerror, roffset);
+  return cre;
+}
+
+uschar *
+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));
+  uschar * substr = NULL;
+  if (i >= 2)                /* Got it */
+    pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr);
+  return substr;
+}
+
+static const pcre *
+m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr)
+{
+  const uschar * list_ele;
+  const pcre * cre = NULL;
+
+  if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
+    *errstr = US listerr;
+  else
+    cre = m_pcre_compile(CS list_ele, errstr);
+  return cre;
+}
+
 /*************************************************
 *          Scan content for malware              *
 *************************************************/


+typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL,
+        M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD} scanner_t;
+static struct scan
+{
+  scanner_t    scancode;
+  const char *    name;
+  const char *    options_default;
+} m_scans[] =
+{
+  { M_FPROTD,    "f-protd",    "localhost 10200-10204" },
+  { M_DRWEB,    "drweb",    "/usr/local/drweb/run/drwebd.sock" },
+  { M_AVES,    "aveserver",    "/var/run/aveserver" },
+  { M_FSEC,    "fsecure",    "/var/run/.fsav" },
+  { M_KAVD,    "kavdaemon",    "/var/run/AvpCtl" },
+  { M_CMDL,    "cmdline",    NULL },
+  { M_SOPHIE,    "sophie",    "/var/run/sophie" },
+  { M_CLAMD,    "clamd",    "/tmp/clamd" },
+  { M_SOCK,    "sock",        "/tmp/malware.sock" },
+  { M_MKSD,    "mksd",        NULL },
+  { -1,        NULL,        NULL }        /* end-marker */
+};
+
 /* This is an internal interface for scanning an email; the normal interface
 is via malware(), or there's malware_in_file() used for testing/debugging.


@@ -165,1728 +370,1291 @@ Arguments:
 Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
                 where true means malware was found (condition applies)
 */
-static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) {
+static int
+malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
+{
   int sep = 0;
   uschar *list = *listptr;
   uschar *av_scanner_work = av_scanner;
   uschar *scanner_name;
-  uschar scanner_name_buffer[16];
   uschar *malware_regex;
-  uschar malware_regex_buffer[64];
   uschar malware_regex_default[] = ".+";
   unsigned long mbox_size;
   FILE *mbox_file;
-  int roffset;
   const pcre *re;
-  const uschar *rerror;
+  uschar * errstr;
+  struct scan * scanent;
+  const uschar * scanner_options;


   /* make sure the eml mbox file is spooled up */
-  mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL);
-  if (mbox_file == NULL) {
-    /* error while spooling */
-    log_write(0, LOG_MAIN|LOG_PANIC,
-           "malware acl condition: error while creating mbox spool file");
-    return DEFER;
-  };
+  if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL)))
+    return malware_errlog_defer("error while creating mbox spool file");
+
   /* none of our current scanners need the mbox
      file as a stream, so we can close it right away */
+  /*XXX drweb and clamd do!! */
   (void)fclose(mbox_file);


   /* extract the malware regex to match against from the option list */
-  if ((malware_regex = string_nextinlist(&list, &sep,
-                                         malware_regex_buffer,
-                                         sizeof(malware_regex_buffer))) != NULL) {
+  if (!(malware_regex = string_nextinlist(&list, &sep, NULL, 0))) {


     /* parse 1st option */
     if ( (strcmpic(malware_regex,US"false") == 0) ||
-         (Ustrcmp(malware_regex,"0") == 0) ) {
-      /* explicitly no matching */
-      return FAIL;
-    };
+         (Ustrcmp(malware_regex,"0") == 0) )
+      return FAIL;        /* explicitly no matching */


     /* special cases (match anything except empty) */
     if ( (strcmpic(malware_regex,US"true") == 0) ||
          (Ustrcmp(malware_regex,"*") == 0) ||
-         (Ustrcmp(malware_regex,"1") == 0) ) {
+         (Ustrcmp(malware_regex,"1") == 0) )
       malware_regex = malware_regex_default;
-    };
   }
-  else {
-    /* empty means "don't match anything" */
+  else /* empty means "don't match anything" */
     return FAIL;
-  };


/* Reset sep that is set by previous string_nextinlist() call */
sep = 0;

   /* compile the regex, see if it works */
-  re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-  if (re == NULL) {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset);
-    return DEFER;
-  };
+  if (!(re = m_pcre_compile(CS malware_regex, &errstr)))
+    return malware_errlog_defer(errstr);


   /* if av_scanner starts with a dollar, expand it first */
   if (*av_scanner == '$') {
-    av_scanner_work = expand_string(av_scanner);
-    if (av_scanner_work == NULL) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-           "malware acl condition: av_scanner starts with $, but expansion failed: %s", expand_string_message);
-      return DEFER;
-    }
-    else {
-      debug_printf("Expanded av_scanner global: %s\n", av_scanner_work);
-      /* disable result caching in this case */
-      malware_name = NULL;
-      malware_ok = 0;
-    };
+    if (!(av_scanner_work = expand_string(av_scanner)))
+      return malware_errlog_defer(
+           string_sprintf("av_scanner starts with $, but expansion failed: %s",
+       expand_string_message));
+
+    debug_printf("Expanded av_scanner global: %s\n", av_scanner_work);
+    /* disable result caching in this case */
+    malware_name = NULL;
+    malware_ok = FALSE;
   }


/* Do not scan twice. */
- if (malware_ok == 0) {
+ if (!malware_ok) {

     /* find the scanner type from the av_scanner option */
-    if ((scanner_name = string_nextinlist(&av_scanner_work, &sep,
-                                          scanner_name_buffer,
-                                          sizeof(scanner_name_buffer))) == NULL) {
-      /* no scanner given */
-      log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: av_scanner configuration variable is empty");
-      return DEFER;
-    };
-
-  /* "f-protd" scanner type ----------------------------------------------- */
-  if (strcmpic(scanner_name, US"f-protd") == 0) {
-    uschar *fp_options, *fp_scan_option;
-    uschar fp_scan_option_buffer[1024];
-    uschar fp_options_buffer[1024];
-    uschar fp_options_default[] = "localhost 10200-10204";
-    uschar hostname[256];
-    unsigned int port, portlow, porthigh, connect_ok=0, detected=0, par_count = 0;
-    struct hostent *he;
-    struct in_addr in;
-    int sock;
-    uschar scanrequest[2048], buf[32768], *strhelper, *strhelper2;
-
-    if ((fp_options = string_nextinlist(&av_scanner_work, &sep,
-      fp_options_buffer, sizeof(fp_options_buffer))) == NULL) {
-      /* no options supplied, use default options */
-      fp_options = fp_options_default;
-    };
-
-    /* extract host and port part */
-    if ( sscanf(CS fp_options, "%s %u-%u", hostname, &portlow, &porthigh) != 3 ) {
-      if ( sscanf(CS fp_options, "%s %u", hostname, &portlow) != 2 ) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: f-protd: invalid socket '%s'", fp_options);
-        return DEFER;
-      }
-      porthigh = portlow;
-    }
-
-    /* Lookup the host */
-    if((he = gethostbyname(CS hostname)) == 0) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: f-protd: failed to lookup host '%s'", hostname);
-      return DEFER;
-    }
-
-    in = *(struct in_addr *) he->h_addr_list[0];
-    port = portlow;
-
-
-    /* Open the f-protd TCP socket */
-    if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: f-protd: unable to acquire socket (%s)",
-        strerror(errno));
-      return DEFER;
-    }
-
-    /* Try to connect to all portslow-high until connection is established */
-    for (port = portlow; !connect_ok && port < porthigh; port++) {
-      if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) >= 0) {
-        connect_ok = 1;
-      }
-    }
-
-    if ( !connect_ok ) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: f-protd: connection to %s, port %u-%u failed (%s)",
-        inet_ntoa(in), portlow, porthigh, strerror(errno));
-      (void)close(sock);
-      return DEFER;
-    }
-
-    DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name);
-    (void)string_format(scanrequest, 1024, CS"GET %s", eml_filename);
-
-    while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep,
-      fp_scan_option_buffer, sizeof(fp_scan_option_buffer))) != NULL) {
-      if ( par_count ) {
-        Ustrcat(scanrequest, "%20");
-      } else {
-        Ustrcat(scanrequest, "?");
-      }
-      Ustrcat(scanrequest, fp_scan_option);
-      par_count++;
-    }
-    Ustrcat(scanrequest, " HTTP/1.0\r\n\r\n");
-
-    /* send scan request */
-    if (send(sock, &scanrequest, Ustrlen(scanrequest)+1, 0) < 0) {
-      (void)close(sock);
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: f-protd: unable to send command to socket (%s)", scanrequest);
-      return DEFER;
-    }
-
-    /* We get a lot of empty lines, so we need this hack to check for any data at all */
-    while( recv(sock, buf, 1, MSG_PEEK) > 0 ) {
-      if ( recv_line(sock, buf, 32768) > 0) {
-        if ( Ustrstr(buf, US"<detected type=\"") != NULL ) {
-          detected = 1;
-        } else if ( detected && (strhelper = Ustrstr(buf, US"<name>")) ) {
-          if ((strhelper2 = Ustrstr(buf, US"</name>")) != NULL) {
-            *strhelper2 = '\0';
-            Ustrcpy(malware_name_buffer, strhelper + 6);
-          }
-        } else if ( Ustrstr(buf, US"<summary code=\"") ) {
-          if ( Ustrstr(buf, US"<summary code=\"11\">") ) {
-            malware_name = malware_name_buffer;
-          } else {
-            malware_name = NULL;
-          }
-        }
-      }
-    }
-    (void)close(sock);
-  }
-  /* "drweb" scanner type ----------------------------------------------- */
-  /* v0.1 - added support for tcp sockets          */
-  /* v0.0 - initial release -- support for unix sockets      */
-  else if (strcmpic(scanner_name,US"drweb") == 0) {
-    uschar *drweb_options;
-    uschar drweb_options_buffer[1024];
-    uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock";
-    struct sockaddr_un server;
-    int sock, result, ovector[30];
-    unsigned int port, fsize;
-    uschar tmpbuf[1024], *drweb_fbuf;
-    uschar drweb_match_string[128];
-    int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd,
-        drweb_vnum, drweb_slen, drweb_fin = 0x0000;
-    unsigned long bread;
-    uschar hostname[256];
-    struct hostent *he;
-    struct in_addr in;
-    pcre *drweb_re;
-
-    if ((drweb_options = string_nextinlist(&av_scanner_work, &sep,
-      drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) {
-      /* no options supplied, use default options */
-      drweb_options = drweb_options_default;
-    };
-
-    if (*drweb_options != '/') {
-
-      /* extract host and port part */
-      if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: invalid socket '%s'", drweb_options);
-        return DEFER;
-      }
-
-      /* Lookup the host */
-      if((he = gethostbyname(CS hostname)) == 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: failed to lookup host '%s'", hostname);
-        return DEFER;
-      }
-
-      in = *(struct in_addr *) he->h_addr_list[0];
-
-      /* Open the drwebd TCP socket */
-      if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: unable to acquire socket (%s)",
-          strerror(errno));
-        return DEFER;
-      }
-
-      if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: connection to %s, port %u failed (%s)",
-          inet_ntoa(in), port, strerror(errno));
-        return DEFER;
-      }
-
-      /* prepare variables */
-      drweb_cmd = htonl(DRWEBD_SCAN_CMD);
-      drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
-
-      /* calc file size */
-      drweb_fd = open(CS eml_filename, O_RDONLY);
-      if (drweb_fd == -1) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: can't open spool file %s: %s",
-          eml_filename, strerror(errno));
-        return DEFER;
-      }
-      fsize = lseek(drweb_fd, 0, SEEK_END);
-      if (fsize == -1) {
-        (void)close(sock);
-        (void)close(drweb_fd);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: can't seek spool file %s: %s",
-          eml_filename, strerror(errno));
-        return DEFER;
-      }
-      drweb_slen = htonl(fsize);
-      lseek(drweb_fd, 0, SEEK_SET);
-
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s %u]\n",
-          scanner_name, hostname, port);
-
-      /* 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)) {
-        (void)close(sock);
-        (void)close(drweb_fd);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options);
-        return DEFER;
-      }
-
-      drweb_fbuf = (uschar *) malloc (fsize);
-      if (!drweb_fbuf) {
-        (void)close(sock);
-        (void)close(drweb_fd);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: unable to allocate memory %u for file (%s)",
-          fsize, eml_filename);
-        return DEFER;
-      }
-
-      result = read (drweb_fd, drweb_fbuf, fsize);
-      if (result == -1) {
-        (void)close(sock);
-        (void)close(drweb_fd);
-        free(drweb_fbuf);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: can't read spool file %s: %s",
-          eml_filename, strerror(errno));
-        return DEFER;
-      }
-      (void)close(drweb_fd);
-
-      /* send file body to socket */
-      if (send(sock, drweb_fbuf, fsize, 0) < 0) {
-        (void)close(sock);
-        free(drweb_fbuf);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options);
-        return DEFER;
-      }
-      (void)close(drweb_fd);
+    if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
+      return malware_errlog_defer("av_scanner configuration variable is empty");
+
+    for (scanent = m_scans; ; scanent++) {
+      if (!scanent->name)
+    return malware_errlog_defer(string_sprintf("unknown scanner type '%s'",
+      scanner_name));
+      if (strcmpic(scanner_name, US scanent->name) == 0)
+    break;
     }
-    else {
-      /* open the drwebd UNIX socket */
-      sock = socket(AF_UNIX, SOCK_STREAM, 0);
-      if (sock < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: can't open UNIX socket");
-        return DEFER;
-      }
-      server.sun_family = AF_UNIX;
-      Ustrcpy(server.sun_path, drweb_options);
-      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno);
-        return DEFER;
-      }
-
-      /* prepare variables */
-      drweb_cmd = htonl(DRWEBD_SCAN_CMD);
-      drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
-      drweb_slen = htonl(Ustrlen(eml_filename));
-
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
-          scanner_name, drweb_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)) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options);
-        return DEFER;
-      }
-    }
-
-    /* wait for result */
-    if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) {
-      (void)close(sock);
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: drweb: unable to read return code");
-      return DEFER;
-    }
-    drweb_rc = ntohl(drweb_rc);
-
-    if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) {
-      (void)close(sock);
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: drweb: unable to read the number of viruses");
-      return DEFER;
-    }
-    drweb_vnum = ntohl(drweb_vnum);
+    if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
+      scanner_options = scanent->options_default;


-    /* "virus(es) found" if virus number is > 0 */
-    if (drweb_vnum)
-    {
-      int i;
-      uschar pre_malware_nb[256];
-
-      malware_name = malware_name_buffer;
-
-      /* setup default virus name */
-      Ustrcpy(malware_name_buffer,"unknown");
-
-      /* read and concatenate virus names into one string */
-      for (i=0;i<drweb_vnum;i++)
+    switch (scanent->scancode) {
+    case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
       {
-        /* read the size of report */
-        if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen))) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: drweb: cannot read report size");
-          return DEFER;
-        };
-        drweb_slen = ntohl(drweb_slen);
-
-        /* read report body */
-        if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: drweb: cannot read report string");
-          return DEFER;
-        };
-        tmpbuf[drweb_slen] = '\0';
-
-        /* set up match regex, depends on retcode */
-        Ustrcpy(drweb_match_string, "infected\\swith\\s*(.+?)$");
-
-        drweb_re = pcre_compile( CS drweb_match_string,
-          PCRE_COPT,
-          (const char **)&rerror,
-          &roffset,
-          NULL );
-
-        /* try matcher on the line, grab substring */
-        result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30);
-        if (result >= 2) {
-          pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255);
-        }
-        /* the first name we just copy to malware_name */
-        if (i==0)
-          Ustrcpy(CS malware_name_buffer, CS pre_malware_nb);
-        else {
-          /* concatenate each new virus name to previous */
-          int slen = Ustrlen(malware_name_buffer);
-          if (slen < (slen+Ustrlen(pre_malware_nb))) {
-            Ustrcat(malware_name_buffer, "/");
-            Ustrcat(malware_name_buffer, pre_malware_nb);
-          }
-        }
-      }
-    }
-    else {
-      const char *drweb_s = NULL;
-
-      if (drweb_rc & DERR_READ_ERR) drweb_s = "read error";
-      if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory";
-      if (drweb_rc & DERR_TIMEOUT)  drweb_s = "timeout";
-      if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command";
-      /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED.
-       * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM,
-       * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR
-       * and others are ignored */
-      if (drweb_s) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: drweb: drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s);
-        (void)close(sock);
-        return DEFER;
-      }
-      /* no virus found */
-      malware_name = NULL;
-    };
-    (void)close(sock);
-  }
-  /* ----------------------------------------------------------------------- */
-    else if (strcmpic(scanner_name,US"aveserver") == 0) {
-      uschar *kav_options;
-      uschar kav_options_buffer[1024];
-      uschar kav_options_default[] = "/var/run/aveserver";
-      uschar buf[32768];
-      struct sockaddr_un server;
-      int sock;
-      int result;
-
-      if ((kav_options = string_nextinlist(&av_scanner_work, &sep,
-                                           kav_options_buffer,
-                                           sizeof(kav_options_buffer))) == NULL) {
-        /* no options supplied, use default options */
-        kav_options = kav_options_default;
-      };
-
-      /* open the aveserver socket */
-      sock = socket(AF_UNIX, SOCK_STREAM, 0);
-      if (sock < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: can't open UNIX socket.");
-        return DEFER;
-      }
-      server.sun_family = AF_UNIX;
-      Ustrcpy(server.sun_path, kav_options);
-      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to connect to aveserver UNIX socket (%s). errno=%d", kav_options, errno);
-        return DEFER;
+    uschar *fp_scan_option;
+    unsigned int detected=0, par_count=0;
+    int sock;
+    uschar * scanrequest;
+    uschar buf[32768], *strhelper, *strhelper2;
+    uschar * malware_name_internal = NULL;
+
+    if ((sock = m_tcpsocket_fromdef(scanner_options, &errstr)) < 0)
+      return fprotd_errlog_defer(errstr);
+
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name);
+    scanrequest = string_sprintf("GET %s", eml_filename);
+
+    while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep,
+                  NULL, 0))) {
+      scanrequest = string_sprintf("%s%s%s", scanrequest,
+                    par_count ? "%20" : "?", fp_scan_option);
+      par_count++;
+    }
+    scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest);
+
+    /* send scan request */
+    if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+      return fprotd_errlog_defer(errstr);
+
+    /* We get a lot of empty lines, so we need this hack to check for any data at all */
+    while( recv(sock, buf, 1, MSG_PEEK) > 0 ) {
+      if ( recv_line(sock, buf, sizeof(buf)) > 0) {
+        if ( Ustrstr(buf, US"<detected type=\"") != NULL )
+          detected = 1;
+        else if ( detected && (strhelper = Ustrstr(buf, US"<name>")) ) {
+          if ((strhelper2 = Ustrstr(buf, US"</name>")) != NULL) {
+        *strhelper2 = '\0';
+        malware_name_internal = string_copy(strhelper+6);
+          }
+        } else if ( Ustrstr(buf, US"<summary code=\"") )
+        malware_name = Ustrstr(buf, US"<summary code=\"11\">")
+          ? malware_name_internal : NULL;
+      }
+    }
+    (void)close(sock);
+    break;
       }


-      /* read aveserver's greeting and see if it is ready (2xx greeting) */
-      recv_line(sock, buf, 32768);
-
-      if (buf[0] != '2') {
-        /* aveserver is having problems */
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: aveserver is unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") );
-        return DEFER;
-      };
-
-      /* prepare our command */
-      (void)string_format(buf, 32768, "SCAN bPQRSTUW %s\r\n", eml_filename);
+    case M_DRWEB: /* "drweb" scanner type ----------------------------------- */
+    /* v0.1 - added support for tcp sockets          */
+    /* v0.0 - initial release -- support for unix sockets      */
+      {
+    struct sockaddr_un server;
+    int sock, result;
+    unsigned int fsize;
+    uschar * tmpbuf, *drweb_fbuf;
+    int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd,
+        drweb_vnum, drweb_slen, drweb_fin = 0x0000;
+    unsigned long bread;
+    const pcre *drweb_re;
+
+    if (*scanner_options != '/') {
+      if ((sock = m_tcpsocket_fromdef(scanner_options, &errstr)) < 0)
+        return drweb_errlog_defer(errstr);
+
+      /* prepare variables */
+      drweb_cmd = htonl(DRWEBD_SCAN_CMD);
+      drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
+
+      /* calc file size */
+      drweb_fd = open(CS eml_filename, O_RDONLY);
+      if (drweb_fd == -1) {
+        int err = errno;
+        (void)close(sock);
+        return drweb_errlog_defer(
+          string_sprintf("can't open spool file %s: %s",
+        eml_filename, strerror(err)));
+      }
+      fsize = lseek(drweb_fd, 0, SEEK_END);
+      if (fsize == -1) {
+        int err = errno;
+        (void)close(sock);
+        (void)close(drweb_fd);
+        return drweb_errlog_defer(
+          string_sprintf("can't seek spool file %s: %s",
+        eml_filename, strerror(err)));
+      }
+      drweb_slen = htonl(fsize);
+      lseek(drweb_fd, 0, SEEK_SET);
+
+      DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s]\n",
+          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)) {
+        (void)close(sock);
+        (void)close(drweb_fd);
+        return drweb_errlog_defer(
+          string_sprintf("unable to send commands to socket (%s)", scanner_options));
+      }
+
+      drweb_fbuf = (uschar *) malloc (fsize);
+      if (!drweb_fbuf) {
+        (void)close(sock);
+        (void)close(drweb_fd);
+        return drweb_errlog_defer(
+          string_sprintf("unable to allocate memory %u for file (%s)",
+        fsize, eml_filename));
+      }
+
+      result = read (drweb_fd, drweb_fbuf, fsize);
+      if (result == -1) {
+        int err = errno;
+        (void)close(sock);
+        (void)close(drweb_fd);
+        free(drweb_fbuf);
+        return drweb_errlog_defer(
+          string_sprintf("can't read spool file %s: %s",
+        eml_filename, strerror(err)));
+      }
+      (void)close(drweb_fd);
+
+      /* send file body to socket */
+      if (send(sock, drweb_fbuf, fsize, 0) < 0) {
+        (void)close(sock);
+        free(drweb_fbuf);
+        return drweb_errlog_defer(
+          string_sprintf("unable to send file body to socket (%s)", scanner_options));
+      }
+      (void)close(drweb_fd);
+
+    } else {
+
+      if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
+        return drweb_errlog_defer(errstr);
+
+      /* prepare variables */
+      drweb_cmd = htonl(DRWEBD_SCAN_CMD);
+      drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
+      drweb_slen = htonl(Ustrlen(eml_filename));
+
+      DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
+          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)) {
+        (void)close(sock);
+        return drweb_errlog_defer(
+          string_sprintf("unable to send commands to socket (%s)", scanner_options));
+      }
+    }


-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name);
+    /* wait for result */
+    if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) {
+      (void)close(sock);
+      return drweb_errlog_defer("unable to read return code");
+    }
+    drweb_rc = ntohl(drweb_rc);


-      /* and send it */
-      if (send(sock, buf, Ustrlen(buf), 0) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options);
-        return DEFER;
+    if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) {
+      (void)close(sock);
+      return drweb_errlog_defer("unable to read the number of viruses");
+    }
+    drweb_vnum = ntohl(drweb_vnum);
+
+    /* "virus(es) found" if virus number is > 0 */
+    if (drweb_vnum) {
+      int i;
+
+      /* setup default virus name */
+      malware_name = "unknown";
+
+      /* set up match regex */
+      drweb_re = m_pcre_compile( "infected\\swith\\s*(.+?)$", &errstr);
+
+      /* read and concatenate virus names into one string */
+      for (i=0;i<drweb_vnum;i++)
+      {
+        int size = 0, off = 0, ovector[10*3];
+        /* read the size of report */
+        if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen))) {
+          (void)close(sock);
+          return drweb_errlog_defer("cannot read report size");
+        }
+        drweb_slen = ntohl(drweb_slen);
+        tmpbuf = store_get(drweb_slen);
+
+        /* read report body */
+        if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen) {
+          (void)close(sock);
+          return drweb_errlog_defer("cannot read report string");
+        }
+        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));
+        if (result >= 2) {
+          const char * pre_malware_nb;
+
+          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);
+
+          else    /* concatenate each new virus name to previous */
+        malware_name = string_append(malware_name, &size, &off,
+                        2, "/", pre_malware_nb);
+
+          pcre_free_substring(pre_malware_nb);
+        }
+      }
+    }
+    else {
+      const char *drweb_s = NULL;
+
+      if (drweb_rc & DERR_READ_ERR) drweb_s = "read error";
+      if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory";
+      if (drweb_rc & DERR_TIMEOUT)  drweb_s = "timeout";
+      if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command";
+      /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED.
+       * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM,
+       * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR
+       * and others are ignored */
+      if (drweb_s) {
+        (void)close(sock);
+        return drweb_errlog_defer(
+          string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s));
+      }
+      /* no virus found */
+      malware_name = NULL;
+    }
+    (void)close(sock);
+    break;
       }


-      malware_name = NULL;
-      result = 0;
-      /* read response lines, find malware name and final response */
-      while (recv_line(sock, buf, 32768) > 0) {
-        debug_printf("aveserver: %s\n", buf);
-        if (buf[0] == '2') {
-    break;
-  } else if (buf[0] == '5') {
-          /* aveserver is having problems */
-          log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to scan file %s (Responded: %s).",
-       eml_filename, buf);
-          result = DEFER;
-    break;
-  } else if (Ustrncmp(buf,"322",3) == 0) {
-          uschar *p = Ustrchr(&buf[4],' ');
-          *p = '\0';
-          Ustrcpy(malware_name_buffer,&buf[4]);
-          malware_name = malware_name_buffer;
-  };
-      }
+    case M_AVES: /* "aveserver" scanner type -------------------------------- */
+      {
+    uschar buf[32768];
+    struct sockaddr_un server;
+    int sock;
+    int result;
+
+    if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
+      return aves_errlog_defer(errstr);
+
+    /* read aveserver's greeting and see if it is ready (2xx greeting) */
+    recv_line(sock, buf, sizeof(buf));
+
+    if (buf[0] != '2') {
+      /* aveserver is having problems */
+      (void)close(sock);
+      return aves_errlog_defer(
+        string_sprintf("unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ));
+    }


-      /* prepare our command */
-      (void)string_format(buf, 32768, "quit\r\n");
+    /* prepare our command */
+    (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n",
+                          eml_filename);


-      /* and send it */
-      if (send(sock, buf, Ustrlen(buf), 0) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options);
-        return DEFER;
-      }
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name);


-      /* read aveserver's greeting and see if it is ready (2xx greeting) */
-      recv_line(sock, buf, 32768);
+    /* and send it */
+    if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
+      return aves_errlog_defer(errstr);


-      if (buf[0] != '2') {
-        /* aveserver is having problems */
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to quit aveserver dialogue (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") );
-        return DEFER;
-      };
-
-      (void)close(sock);
+    malware_name = NULL;
+    result = 0;
+    /* read response lines, find malware name and final response */
+    while (recv_line(sock, buf, sizeof(buf)) > 0) {
+      debug_printf("aveserver: %s\n", buf);
+      if (buf[0] == '2')
+        break;
+      if (buf[0] == '5') {        /* aveserver is having problems */
+        result = aves_errlog_defer(
+           string_sprintf("unable to scan file %s (Responded: %s).",
+                   eml_filename, buf));
+        break;
+      } else if (Ustrncmp(buf,"322",3) == 0) {
+        uschar *p = Ustrchr(&buf[4],' ');
+        *p = '\0';
+        malware_name = string_copy(&buf[4]);
+      }
+    }


-      if (result == DEFER) return DEFER;
-    }
-    /* "fsecure" scanner type ------------------------------------------------- */
-    else if (strcmpic(scanner_name,US"fsecure") == 0) {
-      uschar *fsecure_options;
-      uschar fsecure_options_buffer[1024];
-      uschar fsecure_options_default[] = "/var/run/.fsav";
-      struct sockaddr_un server;
-      int sock, i, j, bread = 0;
-      uschar file_name[1024];
-      uschar av_buffer[1024];
-      pcre *fs_inf;
-      static uschar *cmdoptions[] = { US"CONFIGURE\tARCHIVE\t1\n",
-                                      US"CONFIGURE\tTIMEOUT\t0\n",
-                                      US"CONFIGURE\tMAXARCH\t5\n",
-                                      US"CONFIGURE\tMIME\t1\n" };
-
-      malware_name = NULL;
-      if ((fsecure_options = string_nextinlist(&av_scanner_work, &sep,
-                                               fsecure_options_buffer,
-                                               sizeof(fsecure_options_buffer))) == NULL) {
-         /* no options supplied, use default options */
-         fsecure_options = fsecure_options_default;
-      };
-
-      /* open the fsecure socket */
-      sock = socket(AF_UNIX, SOCK_STREAM, 0);
-      if (sock < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: unable to open fsecure socket %s (%s)",
-                  fsecure_options, strerror(errno));
-        return DEFER;
-      }
-      server.sun_family = AF_UNIX;
-      Ustrcpy(server.sun_path, fsecure_options);
-      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: unable to connect to fsecure socket %s (%s)",
-                  fsecure_options, strerror(errno));
-        return DEFER;
-      }
+    /* and send it */
+    if (m_sock_send(sock, "quit\r\n", 6, &errstr) < 0)
+      return aves_errlog_defer(errstr);


-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
-          scanner_name, fsecure_options);
-
-      /* pass options */
-      memset(av_buffer, 0, sizeof(av_buffer));
-      for (i=0; i != 4; i++) {
-        /* debug_printf("send option \"%s\"",cmdoptions[i]); */
-        if (write(sock, cmdoptions[i], Ustrlen(cmdoptions[i])) < 0) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: unable to write fsecure option %d to %s (%s)",
-                    i, fsecure_options, strerror(errno));
-          return DEFER;
-        };
-
-        bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
-        if (bread >0) av_buffer[bread]='\0';
-        if (bread < 0) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: unable to read fsecure answer %d (%s)", i, strerror(errno));
-          return DEFER;
-        };
-        for (j=0;j<bread;j++) if((av_buffer[j]=='\r')||(av_buffer[j]=='\n')) av_buffer[j] ='@';
-        /* debug_printf("read answer %d read=%d \"%s\"\n", i, bread, av_buffer ); */
-        /* while (Ustrstr(av_buffer, "OK\tServer configured.@") == NULL); */
-      };
-
-      /* pass the mailfile to fsecure */
-      (void)string_format(file_name,1024,"SCAN\t%s\n", eml_filename);
-      /* debug_printf("send scan %s",file_name); */
-      if (write(sock, file_name, Ustrlen(file_name)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: unable to write fsecure scan to %s (%s)",
-                  fsecure_options, strerror(errno));
-        return DEFER;
-      };
-
-      /* set up match */
-      /* todo also SUSPICION\t */
-      fs_inf = pcre_compile("\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-
-      /* read report, linewise */
-      do {
-        int ovector[30];
-        i = 0;
-        memset(av_buffer, 0, sizeof(av_buffer));
-        do {
-          bread=ip_recv(sock, &av_buffer[i], 1, MALWARE_TIMEOUT);
-          if (bread < 0) {
-            (void)close(sock);
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                      "malware acl condition: unable to read fsecure result (%s)", strerror(errno));
-            return DEFER;
-          };
-          i++;
-        }
-        while ((i < sizeof(av_buffer)-1 ) && (av_buffer[i-1] != '\n'));
-        av_buffer[i-1] = '\0';
-        /* debug_printf("got line \"%s\"\n",av_buffer); */
-
-        /* Really search for virus again? */
-        if (malware_name == NULL) {
-          /* try matcher on the line, grab substring */
-          i = pcre_exec(fs_inf, NULL, CS av_buffer, Ustrlen(av_buffer), 0, 0, ovector, 30);
-          if (i >= 2) {
-            /* Got it */
-            pcre_copy_substring(CS av_buffer, ovector, i, 1, CS malware_name_buffer, 255);
-            malware_name = malware_name_buffer;
-          };
-        };
-      }
-      while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL);
-      (void)close(sock);
-    }
-    /* ----------------------------------------------------------------------- */
-
-    /* "kavdaemon" scanner type ------------------------------------------------ */
-    else if (strcmpic(scanner_name,US"kavdaemon") == 0) {
-      uschar *kav_options;
-      uschar kav_options_buffer[1024];
-      uschar kav_options_default[] = "/var/run/AvpCtl";
-      struct sockaddr_un server;
-      int sock;
-      time_t t;
-      uschar tmpbuf[1024];
-      uschar scanrequest[1024];
-      uschar kav_match_string[128];
-      int kav_rc;
-      unsigned long kav_reportlen, bread;
-      pcre *kav_re;
-      uschar *p;
-      int fits;
-
-      if ((kav_options = string_nextinlist(&av_scanner_work, &sep,
-                                           kav_options_buffer,
-                                           sizeof(kav_options_buffer))) == NULL) {
-        /* no options supplied, use default options */
-        kav_options = kav_options_default;
-      };
-
-      /* open the kavdaemon socket */
-      sock = socket(AF_UNIX, SOCK_STREAM, 0);
-      if (sock < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: can't open UNIX socket.");
-        return DEFER;
-      }
-      server.sun_family = AF_UNIX;
-      Ustrcpy(server.sun_path, kav_options);
-      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno);
-        return DEFER;
-      }
+    /* read aveserver's greeting and see if it is ready (2xx greeting) */
+    recv_line(sock, buf, sizeof(buf));


-      /* get current date and time, build scan request */
-      time(&t);
-      /* pdp note: before the eml_filename parameter, this scanned the
-      directory; not finding documentation, so we'll strip off the directory.
-      The side-effect is that the test framework scanning may end up in
-      scanning more than was requested, but for the normal interface, this is
-      fine. */
-      strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s", localtime(&t));
-      fits = string_format(scanrequest, 1024,CS tmpbuf, eml_filename);
-      if (!fits) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware filename does not fit in buffer [malware_internal() kavdaemon]");
-      }
-      p = Ustrrchr(scanrequest, '/');
-      if (p)
-        *p = '\0';
+    if (buf[0] != '2') {
+      /* aveserver is having problems */
+      (void)close(sock);
+      return aves_errlog_defer(
+        string_sprintf("unable to quit dialogue (Responded: %s).",
+              ((buf[0] != 0) ? buf : (uschar *)"nothing") ));
+    }


-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
-          scanner_name, kav_options);
+    (void)close(sock);


-      /* send scan request */
-      if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options);
-        return DEFER;
+    if (result == DEFER) return DEFER;
+    break;
       }


-      /* wait for result */
-      if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to read 2 bytes from kavdaemon socket.");
-        return DEFER;
-      }
+    case M_FSEC: /* "fsecure" scanner type ---------------------------------- */
+      {
+    struct sockaddr_un server;
+    int sock, i, j, bread = 0;
+    uschar * file_name;
+    uschar av_buffer[1024];
+    const pcre * fs_inf;
+    static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n",
+                    US"CONFIGURE\tTIMEOUT\t0\n",
+                    US"CONFIGURE\tMAXARCH\t5\n",
+                    US"CONFIGURE\tMIME\t1\n" };


-      /* get errorcode from one nibble */
-      if (test_byte_order() == LITTLE_MY_ENDIAN) {
-        kav_rc = tmpbuf[0] & 0x0F;
-      }
-      else {
-        kav_rc = tmpbuf[1] & 0x0F;
-      };
-
-      /* improper kavdaemon configuration */
-      if ( (kav_rc == 5) || (kav_rc == 6) ) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files.");
-        return DEFER;
-      };
-
-      if (kav_rc == 1) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: kavdaemon reported 'scanning not completed' (code 1).");
-        return DEFER;
-      };
-
-      if (kav_rc == 7) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7).");
-        return DEFER;
-      };
-
-      /* code 8 is not handled, since it is ambigous. It appears mostly on
-      bounces where part of a file has been cut off */
-
-      /* "virus found" return codes (2-4) */
-      if ((kav_rc > 1) && (kav_rc < 5)) {
-        int report_flag = 0;
-
-        /* setup default virus name */
-        Ustrcpy(malware_name_buffer,"unknown");
-        malware_name = malware_name_buffer;
-
-        if (test_byte_order() == LITTLE_MY_ENDIAN) {
-          report_flag = tmpbuf[1];
-        }
-        else {
-          report_flag = tmpbuf[0];
-        };
-
-        /* read the report, if available */
-        if( report_flag == 1 ) {
-          /* read report size */
-          if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) {
-            (void)close(sock);
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: cannot read report size from kavdaemon");
-            return DEFER;
-          };
-
-          /* it's possible that avp returns av_buffer[1] == 1 but the
-          reportsize is 0 (!?) */
-          if (kav_reportlen > 0) {
-            /* set up match regex, depends on retcode */
-            if( kav_rc == 3 )
-              Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$");
-            else
-              Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$");
-
-            kav_re = pcre_compile( CS kav_match_string,
-                                   PCRE_COPT,
-                                   (const char **)&rerror,
-                                   &roffset,
-                                   NULL );
-
-            /* read report, linewise */
-            while (kav_reportlen > 0) {
-              int result = 0;
-              int ovector[30];
-
-              bread = 0;
-              while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) {
-                kav_reportlen--;
-                if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break;
-                bread++;
-              };
-              bread++;
-              tmpbuf[bread] = '\0';
-
-              /* try matcher on the line, grab substring */
-              result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30);
-              if (result >= 2) {
-                pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255);
-                break;
-              };
-            };
-          };
-        };
-      }
-      else {
-        /* no virus found */
-        malware_name = NULL;
-      };
+    malware_name = NULL;


-      (void)close(sock);
-    }
-    /* ----------------------------------------------------------------------- */
-
-
-    /* "cmdline" scanner type ------------------------------------------------ */
-    else if (strcmpic(scanner_name,US"cmdline") == 0) {
-      uschar *cmdline_scanner;
-      uschar cmdline_scanner_buffer[1024];
-      uschar *cmdline_trigger;
-      uschar cmdline_trigger_buffer[1024];
-      const pcre *cmdline_trigger_re;
-      uschar *cmdline_regex;
-      uschar cmdline_regex_buffer[1024];
-      const pcre *cmdline_regex_re;
-      uschar file_name[1024];
-      uschar commandline[1024];
-      void (*eximsigchld)(int);
-      void (*eximsigpipe)(int);
-      FILE *scanner_out = NULL;
-      FILE *scanner_record = NULL;
-      uschar linebuffer[32767];
-      int trigger = 0;
-      int result;
-      int ovector[30];
-      uschar *p;
-      BOOL fits;
-
-      /* find scanner command line */
-      if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep,
-                                          cmdline_scanner_buffer,
-                                          sizeof(cmdline_scanner_buffer))) == NULL) {
-        /* no command line supplied */
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: missing commandline specification for cmdline scanner type.");
-        return DEFER;
-      };
-
-      /* find scanner output trigger */
-      if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep,
-                                          cmdline_trigger_buffer,
-                                          sizeof(cmdline_trigger_buffer))) == NULL) {
-        /* no trigger regex supplied */
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: missing trigger specification for cmdline scanner type.");
-        return DEFER;
-      };
-
-      /* precompile trigger regex */
-      cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-      if (cmdline_trigger_re == NULL) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                 "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger, rerror, roffset);
-        return DEFER;
-      };
-
-      /* find scanner name regex */
-      if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep,
-                                             cmdline_regex_buffer,
-                                             sizeof(cmdline_regex_buffer))) == NULL) {
-        /* no name regex supplied */
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: missing virus name regex specification for cmdline scanner type.");
-        return DEFER;
-      };
-
-      /* precompile name regex */
-      cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-      if (cmdline_regex_re == NULL) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                 "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex, rerror, roffset);
-        return DEFER;
-      };
-
-      /* prepare scanner call; despite the naming, file_name holds a directory
-      name which is documented as the value given to %s. */
-      if (Ustrlen(eml_filename) > sizeof(file_name) - 1)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware filename does not fit in buffer [malware_internal() cmdline]");
-        return DEFER;
-        }
-      Ustrcpy(file_name, eml_filename);
-      p = Ustrrchr(file_name, '/');
-      if (p)
-        *p = '\0';
-      fits = string_format(commandline, sizeof(commandline), CS cmdline_scanner, file_name);
-      if (!fits)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-            "cmdline scanner command-line does not fit in buffer");
-        return DEFER;
-        }
-
-      /* redirect STDERR too */
-      if (Ustrlen(commandline) + 5 > sizeof(commandline))
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-            "cmdline scanner command-line does not fit in buffer (STDERR redirect)");
-        return DEFER;
-        }
-      Ustrcat(commandline," 2>&1");
-
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline);
-
-      /* store exims signal handlers */
-      eximsigchld = signal(SIGCHLD,SIG_DFL);
-      eximsigpipe = signal(SIGPIPE,SIG_DFL);
-
-      scanner_out = popen(CS commandline,"r");
-      if (scanner_out == NULL) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                 "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno));
-        signal(SIGCHLD,eximsigchld);
-        signal(SIGPIPE,eximsigpipe);
-        return DEFER;
-      };
-
-      (void)string_format(file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id);
-      scanner_record = modefopen(file_name,"wb",SPOOL_MODE);
-
-      if (scanner_record == NULL) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                 "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno));
-        pclose(scanner_out);
-        signal(SIGCHLD,eximsigchld);
-        signal(SIGPIPE,eximsigpipe);
-        return DEFER;
-      };
-
-      /* look for trigger while recording output */
-      while(fgets(CS linebuffer,32767,scanner_out) != NULL) {
-        if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) {
-          /* short write */
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                 "malware acl condition: short write on scanner output file (%s).", file_name);
-          pclose(scanner_out);
-          signal(SIGCHLD,eximsigchld);
-          signal(SIGPIPE,eximsigpipe);
-          return DEFER;
-        };
-        /* try trigger match */
-        if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1))
-          trigger = 1;
-      };
-
-      (void)fclose(scanner_record);
-      pclose(scanner_out);
-      signal(SIGCHLD,eximsigchld);
-      signal(SIGPIPE,eximsigpipe);
-
-      if (trigger) {
-        /* setup default virus name */
-        Ustrcpy(malware_name_buffer,"unknown");
-        malware_name = malware_name_buffer;
-
-        /* re-open the scanner output file, look for name match */
-        scanner_record = fopen(CS file_name,"rb");
-        while(fgets(CS linebuffer,32767,scanner_record) != NULL) {
-          /* try match */
-          result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30);
-          if (result >= 2) {
-            pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255);
-          };
-        };
-        (void)fclose(scanner_record);
-      }
-      else {
-        /* no virus found */
-        malware_name = NULL;
-      };
-    }
-    /* ----------------------------------------------------------------------- */
-
-
-    /* "sophie" scanner type ------------------------------------------------- */
-    else if (strcmpic(scanner_name,US"sophie") == 0) {
-      uschar *sophie_options;
-      uschar sophie_options_buffer[1024];
-      uschar sophie_options_default[] = "/var/run/sophie";
-      int bread = 0;
-      struct sockaddr_un server;
-      int sock, len;
-      uschar *p;
-      uschar file_name[1024];
-      uschar av_buffer[1024];
-
-      if ((sophie_options = string_nextinlist(&av_scanner_work, &sep,
-                                          sophie_options_buffer,
-                                          sizeof(sophie_options_buffer))) == NULL) {
-        /* no options supplied, use default options */
-        sophie_options = sophie_options_default;
-      }
+    if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
+      return fsec_errlog_defer(errstr);
+
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+        scanner_name, scanner_options);
+
+    /* pass options */
+    memset(av_buffer, 0, sizeof(av_buffer));
+    for (i=0; i != nelements(cmdopt); i++) {
+      /* debug_printf("send option \"%s\"",cmdopt[i]); */
+
+      if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+        return fsec_errlog_defer(errstr);
+
+      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
+      if (bread >0) av_buffer[bread]='\0';
+      if (bread < 0) {
+        int err = errno;
+        (void)close(sock);
+        return fsec_errlog_defer(
+          string_sprintf("unable to read answer %d (%s)", i, strerror(errno)));
+      }
+      for (j=0;j<bread;j++)
+        if((av_buffer[j]=='\r')||(av_buffer[j]=='\n'))
+          av_buffer[j] ='@';
+      /* debug_printf("read answer %d read=%d \"%s\"\n", i, bread, av_buffer ); */
+      /* while (Ustrstr(av_buffer, "OK\tServer configured.@") == NULL); */
+    }


-      /* open the sophie socket */
-      sock = socket(AF_UNIX, SOCK_STREAM, 0);
-      if (sock < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: can't open UNIX socket.");
-        return DEFER;
-      }
-      server.sun_family = AF_UNIX;
-      Ustrcpy(server.sun_path, sophie_options);
-      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno);
-        return DEFER;
+    /* pass the mailfile to fsecure */
+    file_name = string_sprintf("SCAN\t%s\n", eml_filename);
+
+    /* debug_printf("send scan %s", file_name); */
+    if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
+      return fsec_errlog_defer(errstr);
+
+    /* set up match */
+    /* todo also SUSPICION\t */
+    fs_inf = m_pcre_compile("\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr);
+
+    /* read report, linewise */
+    do {
+      i = 0;
+      memset(av_buffer, 0, sizeof(av_buffer));
+      do {
+        bread=ip_recv(sock, &av_buffer[i], 1, MALWARE_TIMEOUT);
+        if (bread < 0) {
+          int err = errno;
+          (void)close(sock);
+          return fsec_errlog_defer(
+        string_sprintf("unable to read result (%s)", strerror(err)));
+        }
+        i++;
+      }
+      while ((i < sizeof(av_buffer)-1 ) && (av_buffer[i-1] != '\n'));
+      av_buffer[i-1] = '\0';
+      /* debug_printf("got line \"%s\"\n",av_buffer); */
+
+      /* Really search for virus again? */
+      if (malware_name == NULL)
+        /* try matcher on the line, grab substring */
+        malware_name = m_pcre_exec(fs_inf, av_buffer);
+    }
+    while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL);
+    (void)close(sock);
+    break;
       }


-      /* pass the scan directory to sophie */
-      len = Ustrlen(eml_filename) + 1;
-      if (len > sizeof(file_name))
-        {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware filename does not fit in buffer [malware_internal() sophie]");
-        return DEFER;
-        }
-      memcpy(file_name, eml_filename, len);
-      p = Ustrrchr(file_name, '/');
-      if (p)
-        *p = '\0';
+    case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */
+      {
+    struct sockaddr_un server;
+    int sock;
+    time_t t;
+    uschar tmpbuf[1024];
+    uschar * scanrequest;
+    int kav_rc;
+    unsigned long kav_reportlen, bread;
+    const pcre *kav_re;
+    uschar *p;
+
+    if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
+      return kavd_errlog_defer(errstr);
+
+    /* get current date and time, build scan request */
+    time(&t);
+    /* pdp note: before the eml_filename parameter, this scanned the
+    directory; not finding documentation, so we'll strip off the directory.
+    The side-effect is that the test framework scanning may end up in
+    scanning more than was requested, but for the normal interface, this is
+    fine. */
+    strftime(CS tmpbuf, sizeof(tmpbuf), "%d %b %H:%M:%S", localtime(&t));
+    scanrequest = string_sprintf("<0>%s:%s", CS tmpbuf, eml_filename);
+    p = Ustrrchr(scanrequest, '/');
+    if (p)
+      *p = '\0';
+
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+        scanner_name, scanner_options);
+
+    /* send scan request */
+    if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+      return kavd_errlog_defer(errstr);
+
+    /* wait for result */
+    if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) {
+      (void)close(sock);
+      return kavd_errlog_defer("unable to read 2 bytes from socket.");
+    }


-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
-          scanner_name, sophie_options);
+    /* get errorcode from one nibble */
+    kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F;


-      if (  write(sock, file_name, Ustrlen(file_name)) < 0
-     || write(sock, "\n", 1) != 1
-         ) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options);
-        return DEFER;
-      }
+    /* improper kavdaemon configuration */
+    if ( (kav_rc == 5) || (kav_rc == 6) ) {
+      (void)close(sock);
+      return kavd_errlog_defer("please reconfigure kavdaemon to NOT disinfect or remove infected files.");
+    }


-      /* wait for result */
-      memset(av_buffer, 0, sizeof(av_buffer));
-      if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options);
-        return DEFER;
-      }
+    if (kav_rc == 1) {
+      (void)close(sock);
+      return kavd_errlog_defer("reported 'scanning not completed' (code 1).");
+    }


-      (void)close(sock);
+    if (kav_rc == 7) {
+      (void)close(sock);
+      return kavd_errlog_defer("reported 'kavdaemon damaged' (code 7).");
+    }


-      /* infected ? */
-      if (av_buffer[0] == '1') {
-        if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0';
-        Ustrcpy(malware_name_buffer,&av_buffer[2]);
-        malware_name = malware_name_buffer;
-      }
-      else if (!strncmp(CS av_buffer, "-1", 2)) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: malware acl condition: sophie reported error");
-        return DEFER;
-      }
-      else {
-        /* all ok, no virus */
-        malware_name = NULL;
-      }
-    }
-    /* ----------------------------------------------------------------------- */
-
-
-    /* "clamd" scanner type ------------------------------------------------- */
-    /* This code was originally contributed by David Saez */
-    /* There are three scanning methods available to us:
-     *  (1) Use the SCAN command, pointing to a file in the filesystem
-     *  (2) Use the STREAM command, send the data on a separate port
-     *  (3) Use the zINSTREAM command, send the data inline
-     * 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.
-     * See Exim bug 926 for details.  */
-    else if (strcmpic(scanner_name,US"clamd") == 0) {
-      uschar *clamd_options = NULL;
-      uschar clamd_options_buffer[1024];
-      uschar clamd_options_default[] = "/tmp/clamd";
-      uschar *p, *vname, *result_tag, *response_end;
-      struct sockaddr_un server;
-      int sock,bread=0;
-      unsigned int port;
-      uschar file_name[1024];
-      uschar av_buffer[1024];
-      uschar *hostname = "";
-      struct hostent *he;
-      struct in_addr in;
-      uschar *clamav_fbuf;
-      int clam_fd, result;
-      unsigned int fsize;
-      BOOL use_scan_command = FALSE, fits;
-      clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS];
-      int current_server;
-      int num_servers = 0;
-#ifdef WITH_OLD_CLAMAV_STREAM
-      uschar av_buffer2[1024];
-      int sockData;
-#else
-      uint32_t send_size, send_final_zeroblock;
-#endif
+    /* code 8 is not handled, since it is ambigous. It appears mostly on
+    bounces where part of a file has been cut off */
+
+    /* "virus found" return codes (2-4) */
+    if ((kav_rc > 1) && (kav_rc < 5)) {
+      int report_flag = 0;
+
+      /* setup default virus name */
+      malware_name = "unknown";
+
+      report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ];
+
+      /* read the report, if available */
+      if( report_flag == 1 ) {
+        /* read report size */
+        if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) {
+          (void)close(sock);
+          return kavd_errlog_defer("cannot read report size");
+        }
+
+        /* it's possible that avp returns av_buffer[1] == 1 but the
+        reportsize is 0 (!?) */
+        if (kav_reportlen > 0) {
+          /* set up match regex, depends on retcode */
+          kav_re = m_pcre_compile( kav_rc == 3
+                       ? "suspicion:\\s*(.+?)\\s*$"
+                       : "infected:\\s*(.+?)\\s*$",
+                       &errstr );
+
+          /* read report, linewise */
+          while (kav_reportlen > 0) {
+        bread = 0;
+        while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) {
+          kav_reportlen--;
+          if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break;
+          bread++;
+        }
+        bread++;
+        tmpbuf[bread] = '\0';
+
+        /* try matcher on the line, grab substring */
+        if ((malware_name = m_pcre_exec(kav_re, tmpbuf)))
+          break;
+          }
+        }
+      }
+    }
+    else /* no virus found */
+      malware_name = NULL;


-      if ((clamd_options = string_nextinlist(&av_scanner_work, &sep,
-                                             clamd_options_buffer,
-                                             sizeof(clamd_options_buffer))) == NULL) {
-        /* no options supplied, use default options */
-        clamd_options = clamd_options_default;
+    (void)close(sock);
+    break;
       }


-      if (*clamd_options == '/')
-        /* Local file; so we def want to use_scan_command and don't want to try
-         * passing IP/port combinations */
-        use_scan_command = TRUE;
-      else {
-        uschar *address = clamd_options;
-        uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20];
-
-        /* Go through the rest of the list of host/port and construct an array
-         * of servers to try. The first one is the bit we just passed from
-         * clamd_options so process that first and then scan the remainder of
-         * the address buffer */
-        do {
-          clamd_address_container *this_clamd;
-
-          /* The 'local' option means use the SCAN command over the network
-           * socket (ie common file storage in use) */
-          if (strcmpic(address,US"local") == 0) {
-            use_scan_command = TRUE;
-            continue;
-          }
-
-          /* XXX: If unsuccessful we should free this memory */
-          this_clamd =
-              (clamd_address_container *)store_get(sizeof(clamd_address_container));
+    case M_CMDL: /* "cmdline" scanner type ---------------------------------- */
+      {
+    const uschar *cmdline_scanner = scanner_options;
+    const pcre *cmdline_trigger_re;
+    const pcre *cmdline_regex_re;
+    uschar * file_name;
+    uschar * commandline;
+    void (*eximsigchld)(int);
+    void (*eximsigpipe)(int);
+    FILE *scanner_out = NULL;
+    FILE *scanner_record = NULL;
+    uschar linebuffer[32767];
+    int trigger = 0;
+    uschar *p;
+    BOOL fits;
+
+    if (!cmdline_scanner)
+      return cmdl_errlog_defer("missing commandline specification");
+
+    /* find scanner output trigger */
+    cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep,
+                  "missing trigger specification", &errstr);
+    if (!cmdline_trigger_re)
+      return cmdl_errlog_defer(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 cmdl_errlog_defer(errstr);
+
+    /* prepare scanner call; despite the naming, file_name holds a directory
+    name which is documented as the value given to %s. */
+
+    file_name = string_copy(eml_filename);
+    p = Ustrrchr(file_name, '/');
+    if (p)
+      *p = '\0';
+    commandline = string_sprintf(CS cmdline_scanner, file_name);
+
+    /* redirect STDERR too */
+    commandline = string_sprintf("%s 2>&1", commandline);
+
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline);
+
+    /* store exims signal handlers */
+    eximsigchld = signal(SIGCHLD,SIG_DFL);
+    eximsigpipe = signal(SIGPIPE,SIG_DFL);
+
+    scanner_out = popen(CS commandline,"r");
+    if (scanner_out == NULL) {
+      int err = errno;
+      signal(SIGCHLD,eximsigchld);
+      signal(SIGPIPE,eximsigpipe);
+      return cmdl_errlog_defer(
+        string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
+    }


-          /* extract host and port part */
-          if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u", this_clamd->tcp_addr,
-                                            &(this_clamd->tcp_port)) != 2 ) {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                      "malware acl condition: clamd: invalid address '%s'", address);
-            continue;
-          }
+    file_name = string_sprintf("%s/scan/%s/%s_scanner_output",
+                  spool_directory, message_id, message_id);
+    scanner_record = modefopen(file_name, "wb", SPOOL_MODE);
+
+    if (scanner_record == NULL) {
+      int err = errno;
+      pclose(scanner_out);
+      signal(SIGCHLD,eximsigchld);
+      signal(SIGPIPE,eximsigpipe);
+      return cmdl_errlog_defer(
+        string_sprintf("opening scanner output file (%s) failed: %s.",
+          file_name, strerror(err)));
+    }


-          clamd_address_vector[num_servers] = this_clamd;
-          num_servers++;
-          if (num_servers >= MAX_CLAMD_SERVERS) {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                  "More than " MAX_CLAMD_SERVERS_S " clamd servers specified; "
-                  "only using the first " MAX_CLAMD_SERVERS_S );
-            break;
-          }
-        } while ((address = string_nextinlist(&av_scanner_work, &sep,
-                                        address_buffer,
-                                        sizeof(address_buffer))) != NULL);
-
-        /* check if we have at least one server */
-        if (!num_servers) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: clamd: no useable clamd server addresses in malware configuration option.");
-          return DEFER;
-        }
-      }
+    /* look for trigger while recording output */
+    while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out)) {
+      if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) {
+        /* short write */
+        pclose(scanner_out);
+        signal(SIGCHLD,eximsigchld);
+        signal(SIGPIPE,eximsigpipe);
+        return cmdl_errlog_defer(
+          string_sprintf("short write on scanner output file (%s).", file_name));
+      }
+      /* try trigger match */
+      if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1))
+        trigger = 1;
+    }


-      /* 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, ':')) {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-        "malware acl condition: clamd: local/SCAN mode incompatible with" \
-        " : in path to email filename [%s]", eml_filename);
-    return DEFER;
+    (void)fclose(scanner_record);
+    pclose(scanner_out);
+    signal(SIGCHLD,eximsigchld);
+    signal(SIGPIPE,eximsigpipe);
+
+    if (trigger) {
+      uschar * s;
+      /* setup default virus name */
+      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)))
+          malware_name = s;
+      }
+      (void)fclose(scanner_record);
+    }
+    else /* no virus found */
+      malware_name = NULL;
+    break;
       }


-      /* We have some network servers specified */
-      if (num_servers) {
-
-        /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd
-         * only supports AF_INET, but we should probably be looking to the
-         * future and rewriting this to be protocol-independent anyway. */
-
-        while ( num_servers > 0 ) {
-          /* Randomly pick a server to start with */
-          current_server = random_number( num_servers );
-
-          debug_printf("trying server name %s, port %u\n",
-                       clamd_address_vector[current_server]->tcp_addr,
-                       clamd_address_vector[current_server]->tcp_port);
-
-          /* Lookup the host. This is to ensure that we connect to the same IP
-           * on both connections (as one host could resolve to multiple ips) */
-          if((he = gethostbyname(CS clamd_address_vector[current_server]->tcp_addr))
-                          == 0) {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: clamd: failed to lookup host '%s'",
-                    clamd_address_vector[current_server]->tcp_addr
-                    );
-            goto try_next_server;
-          }
-
-          in = *(struct in_addr *) he->h_addr_list[0];
+    case M_SOPHIE: /* "sophie" scanner type --------------------------------- */
+      {
+    int bread = 0;
+    struct sockaddr_un server;
+    int sock;
+    uschar *p;
+    uschar * file_name;
+    uschar av_buffer[1024];
+
+    if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
+      return soph_errlog_defer(errstr);
+
+    /* pass the scan directory to sophie */
+    file_name = string_copy(eml_filename);
+    if ((p = Ustrrchr(file_name, '/')))
+      *p = '\0';
+
+    DEBUG(D_acl) debug_printf("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
+       ) {
+      (void)close(sock);
+      return soph_errlog_defer(
+        string_sprintf("unable to write to UNIX socket (%s)", scanner_options));
+    }


-          /* Open the ClamAV Socket */
-          if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                      "malware acl condition: clamd: unable to acquire socket (%s)",
-                      strerror(errno));
-            goto try_next_server;
-          }
+    /* wait for result */
+    memset(av_buffer, 0, sizeof(av_buffer));
+    if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) {
+      (void)close(sock);
+      return soph_errlog_defer(
+        string_sprintf("unable to read from UNIX socket (%s)", scanner_options));
+    }


-          if (ip_connect( sock,
-                          AF_INET,
-                          (uschar*)inet_ntoa(in),
-                          clamd_address_vector[current_server]->tcp_port,
-                          5 ) > -1) {
-            /* Connection successfully established with a server */
-            hostname = clamd_address_vector[current_server]->tcp_addr;
-            break;
-          } else {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-               "malware acl condition: clamd: connection to %s, port %u failed (%s)",
-               clamd_address_vector[current_server]->tcp_addr,
-               clamd_address_vector[current_server]->tcp_port,
-               strerror(errno));
-
-            (void)close(sock);
-          }
+    (void)close(sock);


-try_next_server:
-          /* Remove the server from the list. XXX We should free the memory */
-          num_servers--;
-          int i;
-          for( i = current_server; i < num_servers; i++ )
-            clamd_address_vector[i] = clamd_address_vector[i+1];
-        }
-
-        if ( num_servers == 0 ) {
-          log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: all clamd servers failed");
-            return DEFER;
-        }
-      } else {
-        /* open the local socket */
-        if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: clamd: unable to acquire socket (%s)",
-                    strerror(errno));
-          return DEFER;
-        }
-
-        server.sun_family = AF_UNIX;
-        Ustrcpy(server.sun_path, clamd_options);
-
-        if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)",
-                    clamd_options, strerror(errno) );
-          return DEFER;
-        }
+    /* infected ? */
+    if (av_buffer[0] == '1') {
+      uschar * s = Ustrchr(av_buffer, '\n');
+      if (s)
+        *s = '\0';
+      malware_name = string_copy(&av_buffer[2]);
+    }
+    else if (!strncmp(CS av_buffer, "-1", 2))
+      return soph_errlog_defer("scanner reported error");
+    else /* all ok, no virus */
+      malware_name = NULL;
+    break;
       }


-      /* have socket in variable "sock"; command to use is semi-independent of
-       * the socket protocol.  We use SCAN if is local (either Unix/local
-       * domain socket, or explicitly told local) else we stream the data.
-       * How we stream the data depends upon how we were built.  */
-
-      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("Malware scan: issuing %s old-style remote scan (PORT)\n",
-            scanner_name);
-
-        /* Pass the string to ClamAV (7 = "STREAM\n") */
-        if (send(sock, "STREAM\n", 7, 0) < 0) {
-          log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
-                strerror(errno));
-          (void)close(sock);
-          return DEFER;
-        }
-        memset(av_buffer2, 0, sizeof(av_buffer2));
-        bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);
-
-        if (bread < 0) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: clamd: unable to read PORT from socket (%s)",
-                strerror(errno));
-          (void)close(sock);
-          return DEFER;
-        }
-
-        if (bread == sizeof(av_buffer)) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: clamd: buffer too small");
-          (void)close(sock);
-          return DEFER;
-        }
-
-        if (!(*av_buffer2)) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: clamd: ClamAV returned null");
-          (void)close(sock);
-          return DEFER;
-        }
-
-        av_buffer2[bread] = '\0';
-        if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: Expected port information from clamd, got '%s'", av_buffer2);
-          (void)close(sock);
-          return DEFER;
-        };
-
-        if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: unable to acquire socket (%s)",
-                  strerror(errno));
-          (void)close(sock);
-          return DEFER;
-        }
-
-        if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: connection to %s, port %u failed (%s)",
-                  inet_ntoa(in), port, strerror(errno));
-          (void)close(sockData); (void)close(sock);
-          return DEFER;
-        }
-
-#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. */
-
-        DEBUG(D_acl) debug_printf("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) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-              "malware acl condition: clamd: unable to send zINSTREAM to socket (%s)",
-              strerror(errno));
-          (void)close(sock);
-          return DEFER;
-        }
-
-#define CLOSE_SOCKDATA /**/
-#endif
-
-        /* calc file size */
-        clam_fd = open(CS eml_filename, O_RDONLY);
-        if (clam_fd == -1) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: clamd: can't open spool file %s: %s",
-            eml_filename, strerror(errno));
-          CLOSE_SOCKDATA; (void)close(sock);
-          return DEFER;
-        }
-        fsize = lseek(clam_fd, 0, SEEK_END);
-        if (fsize == -1) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: clamd: can't seek spool file %s: %s",
-            eml_filename, strerror(errno));
-          CLOSE_SOCKDATA; (void)close(sock);
-          return DEFER;
-        }
-        lseek(clam_fd, 0, SEEK_SET);
-
-        clamav_fbuf = (uschar *) malloc (fsize);
-        if (!clamav_fbuf) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: clamd: unable to allocate memory %u for file (%s)",
-            fsize, eml_filename);
-          CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
-          return DEFER;
-        }
-
-        result = read (clam_fd, clamav_fbuf, fsize);
-        if (result == -1) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: clamd: can't read spool file %s: %s",
-            eml_filename, strerror(errno));
-          CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
-          free(clamav_fbuf);
-          return DEFER;
-        }
-        (void)close(clam_fd);
-
-        /* send file body to socket */
-#ifdef WITH_OLD_CLAMAV_STREAM
-        if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
-          CLOSE_SOCKDATA; (void)close(sock);
-          free(clamav_fbuf);
-          return DEFER;
-        }
-#else
-        send_size = htonl(fsize);
-        send_final_zeroblock = 0;
-        if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
-            (send(sock, clamav_fbuf, fsize, 0) < 0) ||
-            (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
-          {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
-          (void)close(sock);
-          free(clamav_fbuf);
-          return DEFER;
-          }
-#endif
+    case M_CLAMD: /* "clamd" scanner type ----------------------------------- */
+      {
+      /* This code was originally contributed by David Saez */
+      /* There are three scanning methods available to us:
+       *  (1) Use the SCAN command, pointing to a file in the filesystem
+       *  (2) Use the STREAM command, send the data on a separate port
+       *  (3) Use the zINSTREAM command, send the data inline
+       * 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.
+       * See Exim bug 926 for details.  */
+
+    uschar *p, *vname, *result_tag, *response_end;
+    struct sockaddr_un server;
+    int sock, bread=0;
+    unsigned int port;
+    uschar * file_name;
+    uschar av_buffer[1024];
+    uschar *hostname = "";
+    host_item connhost;
+    uschar *clamav_fbuf;
+    int clam_fd, result;
+    unsigned int fsize;
+    BOOL use_scan_command = FALSE;
+    clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS];
+    int current_server;
+    int num_servers = 0;
+  #ifdef WITH_OLD_CLAMAV_STREAM
+    uschar av_buffer2[1024];
+    int sockData;
+  #else
+    uint32_t send_size, send_final_zeroblock;
+  #endif
+
+    if (*scanner_options == '/')
+      /* Local file; so we def want to use_scan_command and don't want to try
+       * passing IP/port combinations */
+      use_scan_command = TRUE;
+    else {
+      const uschar *address = scanner_options;
+      uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20];
+
+      /* Go through the rest of the list of host/port and construct an array
+       * of servers to try. The first one is the bit we just passed from
+       * scanner_options so process that first and then scan the remainder of
+       * the address buffer */
+      do {
+        clamd_address_container *this_clamd;
+
+        /* The 'local' option means use the SCAN command over the network
+         * socket (ie common file storage in use) */
+        if (strcmpic(address,US"local") == 0) {
+          use_scan_command = TRUE;
+          continue;
+        }
+
+        /* XXX: If unsuccessful we should free this memory */
+        this_clamd =
+        (clamd_address_container *)store_get(sizeof(clamd_address_container));
+
+        /* extract host and port part */
+        if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u",
+           this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 ) {
+          clmd_errlog(string_sprintf("invalid address '%s'", address));
+          continue;
+        }
+
+        clamd_address_vector[num_servers] = this_clamd;
+        num_servers++;
+        if (num_servers >= MAX_CLAMD_SERVERS) {
+          clmd_errlog("More than " MAX_CLAMD_SERVERS_S " clamd servers "
+            "specified; only using the first " MAX_CLAMD_SERVERS_S );
+          break;
+        }
+      } while ((address = string_nextinlist(&av_scanner_work, &sep,
+                      address_buffer,
+                      sizeof(address_buffer))) != NULL);
+
+      /* check if we have at least one server */
+      if (!num_servers)
+        return clmd_errlog_defer("no useable server addresses in malware configuration option.");
+    }


-        free(clamav_fbuf);
-
-        CLOSE_SOCKDATA;
-#undef CLOSE_SOCKDATA
-
-      } else { /* use scan command */
-        /* Send a SCAN command pointing to a filename; then in the then in the
-         * scan-method-neutral part, read the response back */
-
-/* ================================================================= */
-
-        /* Prior to the reworking post-Exim-4.72, this scanned a directory,
-        which dates to when ClamAV needed us to break apart the email into the
-        MIME parts (eg, with the now deprecated demime condition coming first).
-        Some time back, ClamAV gained the ability to deconstruct the emails, so
-        doing this would actually have resulted in the mail attachments being
-        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) */
-        fits = string_format(file_name, sizeof(file_name), "SCAN %s\n",
-            eml_filename);
-        if (!fits) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,
-              "malware filename does not fit in buffer [malware_internal() clamd]");
-        }
-
-        DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n",
-            scanner_name, clamd_options);
-
-        if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
-          (void)close(sock);
-          log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
-                    strerror(errno));
-          return DEFER;
-        }
-
-        /* Do not shut down the socket for writing; a user report noted that
-         * clamd 0.70 does not react well to this. */
-      }
-      /* Commands have been sent, no matter which scan method or connection
-       * type we're using; now just read the result, independent of method. */
-
-      /* Read the result */
-      memset(av_buffer, 0, sizeof(av_buffer));
-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
-      (void)close(sock);
-
-      if (!(bread  > 0)) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: unable to read from socket (%s)",
-                  strerror(errno));
-        return DEFER;
-      }
+    /* 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 clmd_errlog_defer(
+        string_sprintf("local/SCAN mode incompatible with" \
+          " : in path to email filename [%s]", eml_filename));
+
+    /* We have some network servers specified */
+    if (num_servers) {
+
+      /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd
+       * only supports AF_INET, but we should probably be looking to the
+       * future and rewriting this to be protocol-independent anyway. */
+
+      while ( num_servers > 0 ) {
+        /* Randomly pick a server to start with */
+        current_server = random_number( num_servers );
+
+        debug_printf("trying server name %s, port %u\n",
+             clamd_address_vector[current_server]->tcp_addr,
+             clamd_address_vector[current_server]->tcp_port);
+
+        /* Lookup the host. This is to ensure that we connect to the same IP
+         * on both connections (as one host could resolve to multiple ips) */
+        sock= m_tcpsocket(CS clamd_address_vector[current_server]->tcp_addr,
+                clamd_address_vector[current_server]->tcp_port,
+                &connhost, &errstr);
+        if (sock >= 0) {
+          /* Connection successfully established with a server */
+          hostname = clamd_address_vector[current_server]->tcp_addr;
+          break;
+        }
+
+        clmd_errlog(errstr);
+
+        /* Remove the server from the list. XXX We should free the memory */
+        num_servers--;
+        int i;
+        for( i = current_server; i < num_servers; i++ )
+          clamd_address_vector[i] = clamd_address_vector[i+1];
+      }
+
+      if ( num_servers == 0 )
+        return clmd_errlog_defer("all servers failed");
+
+    } else {
+      if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
+        return clmd_errlog_defer(errstr);
+    }


-      if (bread == sizeof(av_buffer)) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: buffer too small");
-        return DEFER;
+    /* have socket in variable "sock"; command to use is semi-independent of
+     * the socket protocol.  We use SCAN if is local (either Unix/local
+     * domain socket, or explicitly told local) else we stream the data.
+     * How we stream the data depends upon how we were built.  */
+
+    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("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 clmd_errlog_defer(errstr);
+
+      memset(av_buffer2, 0, sizeof(av_buffer2));
+      bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);
+
+      if (bread < 0) {
+        int err = errno;
+        (void)close(sock);
+        return clmd_errlog_defer(
+          string_sprintf("unable to read PORT from socket (%s)",
+          strerror(err)));
+      }
+
+      if (bread == sizeof(av_buffer2)) {
+        (void)close(sock);
+        return clmd_errlog_defer("buffer too small");
+      }
+
+      if (!(*av_buffer2)) {
+        (void)close(sock);
+        return clmd_errlog_defer("ClamAV returned null");
+      }
+
+      av_buffer2[bread] = '\0';
+      if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) {
+        (void)close(sock);
+        return clmd_errlog_defer(
+          string_sprintf("Expected port information from clamd, got '%s'",
+        av_buffer2));
+      }
+
+      sockData = m_tcpsocket(connhost.address, port, NULL, &errstr);
+      if (sockData < 0) {
+        (void)close(sock);
+        return clmd_errlog_defer(errstr);
+      }
+
+  #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. */
+
+      DEBUG(D_acl) debug_printf("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) {
+        int err = errno;
+        (void)close(sock);
+        return clmd_errlog_defer(
+          string_sprintf("unable to send zINSTREAM to socket (%s)",
+        strerror(err)));
+      }
+
+  #define CLOSE_SOCKDATA /**/
+  #endif
+
+      /* calc file size */
+      clam_fd = open(CS eml_filename, O_RDONLY);
+      if (clam_fd == -1) {
+        int err = errno;
+        CLOSE_SOCKDATA; (void)close(sock);
+        return clmd_errlog_defer(
+          string_sprintf("can't open spool file %s: %s",
+        eml_filename, strerror(err)));
+      }
+      fsize = lseek(clam_fd, 0, SEEK_END);
+      if (fsize == -1) {
+        int err = errno;
+        CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
+        return clmd_errlog_defer(
+          string_sprintf("can't seek spool file %s: %s",
+        eml_filename, strerror(errno)));
+      }
+      lseek(clam_fd, 0, SEEK_SET);
+
+      clamav_fbuf = (uschar *) malloc (fsize);
+      if (!clamav_fbuf) {
+        CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
+        return clmd_errlog_defer(
+          string_sprintf("unable to allocate memory %u for file (%s)",
+        fsize, eml_filename));
+      }
+
+      result = read (clam_fd, clamav_fbuf, fsize);
+      if (result == -1) {
+        int err = errno;
+        CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
+        free(clamav_fbuf);
+        return clmd_errlog_defer(
+          string_sprintf("can't read spool file %s: %s",
+        eml_filename, strerror(err)));
+      }
+      (void)close(clam_fd);
+
+      /* send file body to socket */
+  #ifdef WITH_OLD_CLAMAV_STREAM
+      if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
+        CLOSE_SOCKDATA; (void)close(sock);
+        free(clamav_fbuf);
+        return clmd_errlog_defer(
+          string_sprintf("unable to send file body to socket (%s:%u)",
+        hostname, port);
+      }
+  #else
+      send_size = htonl(fsize);
+      send_final_zeroblock = 0;
+      if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
+          (send(sock, clamav_fbuf, fsize, 0) < 0) ||
+          (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
+        {
+        (void)close(sock);
+        free(clamav_fbuf);
+        return clmd_errlog_defer(
+          string_sprintf("unable to send file body to socket (%s:%u)",
+        hostname, port));
+        }
+  #endif
+
+      free(clamav_fbuf);
+
+      CLOSE_SOCKDATA;
+  #undef CLOSE_SOCKDATA
+
+    } else { /* use scan command */
+      /* Send a SCAN command pointing to a filename; then in the then in the
+       * scan-method-neutral part, read the response back */
+
+  /* ================================================================= */
+
+      /* Prior to the reworking post-Exim-4.72, this scanned a directory,
+      which dates to when ClamAV needed us to break apart the email into the
+      MIME parts (eg, with the now deprecated demime condition coming first).
+      Some time back, ClamAV gained the ability to deconstruct the emails, so
+      doing this would actually have resulted in the mail attachments being
+      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);
+
+      DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n",
+          scanner_name, scanner_options);
+
+      if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
+        int err = errno;
+        (void)close(sock);
+        return clmd_errlog_defer(
+          string_sprintf("unable to write to socket (%s)", strerror(err)));
+      }
+
+      /* Do not shut down the socket for writing; a user report noted that
+       * clamd 0.70 does not react well to this. */
+    }
+    /* Commands have been sent, no matter which scan method or connection
+     * type we're using; now just read the result, independent of method. */
+
+    /* Read the result */
+    memset(av_buffer, 0, sizeof(av_buffer));
+    bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
+    (void)close(sock);
+
+    if (!(bread > 0))
+      return clmd_errlog_defer(
+        string_sprintf("unable to read from socket (%s)", strerror(errno)));
+
+    if (bread == sizeof(av_buffer))
+      return clmd_errlog_defer("buffer too small");
+    /* We're now assured of a NULL at the end of av_buffer */
+
+    /* Check the result. ClamAV returns one of two result formats.
+    In the basic mode, the response is of the form:
+      infected: -> "<filename>: <virusname> FOUND"
+      not-infected: -> "<filename>: OK"
+      error: -> "<filename>: <errcode> ERROR
+    If the ExtendedDetectionInfo option has been turned on, then we get:
+      "<filename>: <virusname>(<virushash>:<virussize>) FOUND"
+    for the infected case.  Compare:
+  /tmp/eicar.com: Eicar-Test-Signature FOUND
+  /tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND
+
+    In the streaming case, clamd uses the filename "stream" which you should
+    be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }.  (The
+    client app will replace "stream" with the original filename before returning
+    results to stdout, but the trace shows the data).
+
+    We will assume that the pathname passed to clamd from Exim does not contain
+    a colon.  We will have whined loudly above if the eml_filename does (and we're
+    passing a filename to clamd). */
+
+    if (!(*av_buffer))
+      return clmd_errlog_defer("ClamAV returned null");
+
+    /* strip newline at the end (won't be present for zINSTREAM)
+    (also any trailing whitespace, which shouldn't exist, but we depend upon
+    this below, so double-check) */
+    p = av_buffer + Ustrlen(av_buffer) - 1;
+    if (*p == '\n') *p = '\0';
+
+    DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer);
+
+    while (isspace(*--p) && (p > av_buffer))
+      *p = '\0';
+    if (*p) ++p;
+    response_end = p;
+
+    /* colon in returned output? */
+    if((p = Ustrchr(av_buffer,':')) == NULL)
+      return clmd_errlog_defer(
+        string_sprintf("ClamAV returned malformed result (missing colon): %s",
+            av_buffer));
+
+    /* strip filename */
+    while (*p && isspace(*++p)) /**/;
+    vname = p;
+
+    /* It would be bad to encounter a virus with "FOUND" in part of the name,
+    but we should at least be resistant to it. */
+    p = Ustrrchr(vname, ' ');
+    result_tag = p ? p+1 : vname;
+
+    if (Ustrcmp(result_tag, "FOUND") == 0) {
+      /* p should still be the whitespace before the result_tag */
+      while (isspace(*p)) --p;
+      *++p = '\0';
+      /* Strip off the extended information too, which will be in parens
+      after the virus name, with no intervening whitespace. */
+      if (*--p == ')') {
+        /* "(hash:size)", so previous '(' will do; if not found, we have
+        a curious virus name, but not an error. */
+        p = Ustrrchr(vname, '(');
+        if (p)
+          *p = '\0';
+      }
+      malware_name = string_copy(vname);
+      DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name);
+
+    } else if (Ustrcmp(result_tag, "ERROR") == 0)
+      return clmd_errlog_defer(
+        string_sprintf("ClamAV returned: %s", av_buffer));
+
+    else if (Ustrcmp(result_tag, "OK") == 0) {
+      /* Everything should be OK */
+      malware_name = NULL;
+      DEBUG(D_acl) debug_printf("Malware not found\n");
+
+    } else
+      return clmd_errlog_defer(
+        string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
+
+    break;
+      } /* clamd */
+
+    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
+      */
+      {
+    int sock, bread;
+    uschar * commandline;
+    uschar av_buffer[1024];
+    uschar * linebuffer;
+    uschar * sockline_scanner;
+    uschar sockline_scanner_default[] = "%s\n";
+    const pcre *sockline_trig_re;
+    const pcre *sockline_name_re;
+    int err;
+
+    /* find scanner command line */
+    if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
+                        NULL, 0)))
+    {    /* check for no expansions apart from one %s */
+      char * s = index(sockline_scanner, '%');
+      if (s++)
+        if (*s != 's' && *s != '%'  ||  index(s+1, '%'))
+          return sock_errlog_defer("unsafe sock scanner call spec");
+    }
+    else
+      sockline_scanner = sockline_scanner_default;
+
+    /* find scanner output trigger */
+    sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
+                  "missing trigger specification", &errstr);
+    if (!sockline_trig_re)
+      return sock_errlog_defer(errstr);
+
+    /* 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 sock_errlog_defer(errstr);
+
+    /* prepare scanner call - security depends on expansions check above */
+    commandline = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
+    commandline = string_sprintf( CS sockline_scanner, CS commandline);
+
+
+    /* socket does not start with '/' -> network socket */
+    sock = *scanner_options != '/'
+      ? m_tcpsocket_fromdef(scanner_options, &errstr)
+      : m_unixsocket(scanner_options, &errstr);
+    if (sock < 0)
+      return sock_errlog_defer(errstr);
+
+    /* Pass the command string to the socket */
+    if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0)
+      return sock_errlog_defer(errstr);
+
+    /* Read the result */
+    memset(av_buffer, 0, sizeof(av_buffer));
+    bread = read(sock, av_buffer, sizeof(av_buffer));
+    err = errno;
+    (void)close(sock);
+
+    if (!(bread > 0))
+      return sock_errlog_defer(
+        string_sprintf("unable to read from socket (%s)", strerror(err)));
+
+    if (bread == sizeof(av_buffer))
+      return sock_errlog_defer("buffer too small");
+    linebuffer = string_copy(av_buffer);
+
+    /* try trigger match */
+    if (regex_match_and_setup(sockline_trig_re, linebuffer, 0, -1)) {
+      if (!(malware_name = m_pcre_exec(sockline_name_re, av_buffer)))
+        malware_name = US "unknown";
+    }
+    else /* no virus found */
+      malware_name = NULL;
       }


-      /* Check the result. ClamAV returns one of two result formats.
-      In the basic mode, the response is of the form:
-        infected: -> "<filename>: <virusname> FOUND"
-        not-infected: -> "<filename>: OK"
-        error: -> "<filename>: <errcode> ERROR
-      If the ExtendedDetectionInfo option has been turned on, then we get:
-        "<filename>: <virusname>(<virushash>:<virussize>) FOUND"
-      for the infected case.  Compare:
-/tmp/eicar.com: Eicar-Test-Signature FOUND
-/tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND
-
-      In the streaming case, clamd uses the filename "stream" which you should
-      be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }.  (The
-      client app will replace "stream" with the original filename before returning
-      results to stdout, but the trace shows the data).
-
-      We will assume that the pathname passed to clamd from Exim does not contain
-      a colon.  We will have whined loudly above if the eml_filename does (and we're
-      passing a filename to clamd). */
-
-      if (!(*av_buffer)) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: ClamAV returned null");
-        return DEFER;
-      }
+    case M_MKSD: /* "mksd" scanner type ------------------------------------- */
+      {
+    char *mksd_options_end;
+    int mksd_maxproc = 1;  /* default, if no option supplied */
+    int sock;
+    int retval;
+
+    if (scanner_options) {
+      mksd_maxproc = (int)strtol(CS scanner_options, &mksd_options_end, 10);
+      if (  *scanner_options == '\0'
+         || *mksd_options_end != '\0'
+         || mksd_maxproc < 1
+         || mksd_maxproc > 32
+         )
+        return mksd_errlog_defer(
+          string_sprintf("invalid option '%s'", scanner_options));
+    }


-      /* strip newline at the end (won't be present for zINSTREAM)
-      (also any trailing whitespace, which shouldn't exist, but we depend upon
-      this below, so double-check) */
-      p = av_buffer + Ustrlen(av_buffer) - 1;
-      if (*p == '\n') *p = '\0';
-
-      DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer);
-
-      while (isspace(*--p) && (p > av_buffer))
-    *p = '\0';
-      if (*p) ++p;
-      response_end = p;
-
-      /* colon in returned output? */
-      if((p = Ustrchr(av_buffer,':')) == NULL) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: ClamAV returned malformed result (missing colon): %s",
-                  av_buffer);
-        return DEFER;
-      }
+    if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
+      return mksd_errlog_defer(errstr);


-      /* strip filename */
-      while (*p && isspace(*++p)) /**/;
-      vname = p;
-
-      /* It would be bad to encounter a virus with "FOUND" in part of the name,
-      but we should at least be resistant to it. */
-      p = Ustrrchr(vname, ' ');
-      if (p)
-    result_tag = p + 1;
-      else
-    result_tag = vname;
-
-      if (Ustrcmp(result_tag, "FOUND") == 0) {
-    /* p should still be the whitespace before the result_tag */
-    while (isspace(*p)) --p;
-    *++p = '\0';
-        /* Strip off the extended information too, which will be in parens
-        after the virus name, with no intervening whitespace. */
-    if (*--p == ')') {
-      /* "(hash:size)", so previous '(' will do; if not found, we have
-      a curious virus name, but not an error. */
-      p = Ustrrchr(vname, '(');
-      if (p)
-        *p = '\0';
-    }
-    Ustrncpy(malware_name_buffer, vname, sizeof(malware_name_buffer)-1);
-    malware_name = malware_name_buffer;
-    DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name);
-
-      } else if (Ustrcmp(result_tag, "ERROR") == 0) {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: clamd: ClamAV returned: %s",
-          av_buffer);
-    return DEFER;
-
-      } else if (Ustrcmp(result_tag, "OK") == 0) {
-    /* Everything should be OK */
     malware_name = NULL;
-    DEBUG(D_acl) debug_printf("Malware not found\n");


-      } else {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-          "malware acl condition: clamd: unparseable response from ClamAV: {%s}",
-          av_buffer);
-    return DEFER;
-      }
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);


-    } /* clamd */
-
-    /* ----------------------------------------------------------------------- */
-
-
-    /* "mksd" scanner type --------------------------------------------------- */
-    else if (strcmpic(scanner_name,US"mksd") == 0) {
-      uschar *mksd_options;
-      char *mksd_options_end;
-      uschar mksd_options_buffer[32];
-      int mksd_maxproc = 1;  /* default, if no option supplied */
-      struct sockaddr_un server;
-      int sock;
-      int retval;
-
-      if ((mksd_options = string_nextinlist(&av_scanner_work, &sep,
-                                            mksd_options_buffer,
-                                            sizeof(mksd_options_buffer))) != NULL) {
-        mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10);
-        if ((*mksd_options == '\0') || (*mksd_options_end != '\0') ||
-      (mksd_maxproc < 1) || (mksd_maxproc > 32)) {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: mksd: invalid option '%s'", mksd_options);
-          return DEFER;
-        }
-      }
+    retval = mksd_scan_packed(sock, eml_filename);


-      /* open the mksd socket */
-      sock = socket(AF_UNIX, SOCK_STREAM, 0);
-      if (sock < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: can't open UNIX socket.");
-        return DEFER;
-      }
-      server.sun_family = AF_UNIX;
-      Ustrcpy(server.sun_path, "/var/run/mksd/socket");
-      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-        (void)close(sock);
-        log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno);
-        return DEFER;
+    if (retval != OK)
+      return retval;
+    break;
       }
-
-      malware_name = NULL;
-
-      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);
-
-      retval = mksd_scan_packed(sock, eml_filename);
-
-      if (retval != OK)
-        return retval;
     }
-    /* ----------------------------------------------------------------------- */


-    /* "unknown" scanner type ------------------------------------------------- */
-    else {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-             "malware condition: unknown scanner type '%s'", scanner_name);
-      return DEFER;
-    };
-    /* ----------------------------------------------------------------------- */
-
-    /* set "been here, done that" marker */
-    malware_ok = 1;
-  };
+    malware_ok = TRUE;            /* set "been here, done that" marker */
+  }


   /* match virus name against pattern (caseless ------->----------v) */
-  if ( (malware_name != NULL) &&
-       (regex_match_and_setup(re, malware_name, 0, -1)) ) {
+  if ( malware_name && (regex_match_and_setup(re, malware_name, 0, -1)) ) {
     DEBUG(D_acl) debug_printf("Matched regex to malware [%s] [%s]\n", malware_regex, malware_name);
     return OK;
   }
-  else {
+  else
     return FAIL;
-  };
 }



/* simple wrapper for reading lines from sockets */
-int recv_line(int sock, uschar *buffer, int size) {
+int
+recv_line(int sock, uschar *buffer, int size)
+{
uschar *p = buffer;

   memset(buffer,0,size);
@@ -1895,7 +1663,7 @@ int recv_line(int sock, uschar *buffer, int size) {
     if ((p-buffer) > (size-2)) break;
     if (*p == '\n') break;
     if (*p != '\r') p++;
-  };
+  }
   *p = '\0';


return (p-buffer);
@@ -1906,7 +1674,8 @@ int recv_line(int sock, uschar *buffer, int size) {

#include <sys/uio.h>

-static int mksd_writev (int sock, struct iovec *iov, int iovcnt)
+static int
+mksd_writev (int sock, struct iovec *iov, int iovcnt)
{
int i;

@@ -1916,8 +1685,7 @@ static int mksd_writev (int sock, struct iovec *iov, int iovcnt)
     while ((i < 0) && (errno == EINTR));
     if (i <= 0) {
       close (sock);
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)");
+      malware_errlog("unable to write to mksd UNIX socket (/var/run/mksd/socket)");
       return -1;
     }


@@ -1935,7 +1703,8 @@ static int mksd_writev (int sock, struct iovec *iov, int iovcnt)
}
}

-static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
+static int
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
 {
   int offset = 0;
   int i;
@@ -1943,8 +1712,7 @@ static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
   do {
     if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) {
       close (sock);
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+      malware_errlog("unable to read from mksd UNIX socket (/var/run/mksd/socket)");
       return -1;
     }


@@ -1952,8 +1720,7 @@ static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
     /* offset == av_buffer_size -> buffer full */
     if (offset == av_buffer_size) {
       close (sock);
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: malformed reply received from mksd");
+      malware_errlog("malformed reply received from mksd");
       return -1;
     }
   } while (av_buffer[offset-1] != '\n');
@@ -1962,41 +1729,38 @@ static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
   return offset;
 }


-static int mksd_parse_line (char *line)
+static int
+mksd_parse_line (char *line)
{
char *p;

   switch (*line) {
-    case 'O':
-      /* OK */
+    case 'O': /* OK */
       return OK;
+
     case 'E':
-    case 'A':
-      /* ERR */
+    case 'A': /* ERR */
       if ((p = strchr (line, '\n')) != NULL)
-        (*p) = '\0';
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: mksd scanner failed: %s", line);
-      return DEFER;
-    default:
-      /* VIR */
+        *p = '\0';
+      return mksd_errlog_defer(string_sprintf("scanner failed: %s", line));
+
+    default: /* VIR */
       if ((p = strchr (line, '\n')) != NULL) {
-        (*p) = '\0';
-        if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' '))
+        *p = '\0';
+        if (((p-line) > 5) && (line[3] == ' '))
           if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) {
-            (*p) = '\0';
-            Ustrcpy (malware_name_buffer, line+4);
-      malware_name = malware_name_buffer;
+            *p = '\0';
+            malware_name = string_copy(line+4);
             return OK;
           }
       }
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: malformed reply received from mksd: %s", line);
-      return DEFER;
+      return mksd_errlog_defer(
+    string_sprintf("malformed reply received from mksd: %s", line));
   }
 }


-static int mksd_scan_packed(int sock, uschar *scan_filename)
+static int
+mksd_scan_packed(int sock, uschar *scan_filename)
{
struct iovec iov[3];
const char *cmd = "MSQ\n";
@@ -2020,4 +1784,4 @@ static int mksd_scan_packed(int sock, uschar *scan_filename)
return mksd_parse_line (CS av_buffer);
}

-#endif
+#endif /*WITH_CONTENT_SCAN*/
diff --git a/src/src/string.c b/src/src/string.c
index 0e73e2c..94d61b1 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -34,7 +34,7 @@ Returns:    0 if the string is not a textual representation of an IP address
 */


int
-string_is_ip_address(uschar *s, int *maskptr)
+string_is_ip_address(const uschar *s, int *maskptr)
{
int i;
int yield = 4;
@@ -44,7 +44,7 @@ offset. */

 if (maskptr != NULL)
   {
-  uschar *ss = s + Ustrlen(s);
+  const uschar *ss = s + Ustrlen(s);
   *maskptr = 0;
   if (s != ss && isdigit(*(--ss)))
     {