Refactor clamd socket connection
[users/jgh/exim.git] / src / src / malware.c
index 9d4d16abacca3d419e23ce83d71bdf5b7166451f..298031aa39a2777905cbcdec9fdca5c5696499c6 100644 (file)
@@ -62,8 +62,7 @@ test_byte_order()
   return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
 }
 
-static uschar * malware_name_internal = NULL;
-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
@@ -213,6 +212,11 @@ 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)
@@ -222,40 +226,110 @@ clmd_errlog(const uschar * 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_unixsocket(const uschar * path, uschar ** errstr)
+m_tcpsocket_fromdef(const uschar * hostport, uschar ** errstr)
 {
-int sock;
-struct sockaddr_un server;
+  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;
+  }
 
-if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
-  *errstr = "can't open UNIX socket.";
-  return -1;
+  return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
+                           5, NULL, errstr);
 }
 
-server.sun_family = AF_UNIX;
-Ustrcpy(server.sun_path, path);
-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;
+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;
   }
-return sock;
+
+  server.sun_family = AF_UNIX;
+  Ustrcpy(server.sun_path, path);
+  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;
+  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;
 }
 
 /*************************************************
@@ -263,7 +337,7 @@ return sock;
 *************************************************/
 
 typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL,
-               M_SOPHIE, M_CLAMD, M_MKSD} scanner_t;
+               M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD} scanner_t;
 static struct scan
 {
   scanner_t    scancode;
@@ -279,6 +353,7 @@ static struct scan
   { 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 */
 };
@@ -305,20 +380,18 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
   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 */
+  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 */
@@ -326,17 +399,14 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
     /* 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" */
     return FAIL;
@@ -345,29 +415,24 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
   sep = 0;
 
   /* compile the regex, see if it works */
-  re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-  if (!re)
-    return malware_errlog_defer(
-        string_sprintf("regular expression error in '%s': %s at offset %d",
-            malware_regex, rerror, roffset));
+  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)
+    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));
-    else {
-      debug_printf("Expanded av_scanner global: %s\n", av_scanner_work);
-      /* disable result caching in this case */
-      malware_name = NULL;
-      malware_ok = 0;
-    }
+
+    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, NULL, 0)))
@@ -387,48 +452,14 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
       {
        uschar *fp_scan_option;
-       uschar hostname[256];
-       unsigned int port, portlow, porthigh, connect_ok=0, detected=0, par_count=0;
-       struct hostent *he;
-       struct in_addr in;
+       unsigned int detected=0, par_count=0;
        int sock;
        uschar * scanrequest;
        uschar buf[32768], *strhelper, *strhelper2;
+       uschar * malware_name_internal = NULL;
 
-       /* extract host and port part */
-       if ( sscanf(CS scanner_options, "%255s %u-%u", hostname, &portlow, &porthigh) != 3 ) {
-         if ( sscanf(CS scanner_options, "%s %u", hostname, &portlow) != 2 )
-           return fprotd_errlog_defer(
-             string_sprintf("invalid socket '%s'", scanner_options));
-         porthigh = portlow;
-       }
-
-       /* Lookup the host */
-       if((he = gethostbyname(CS hostname)) == 0)
-         return fprotd_errlog_defer(
-           string_sprintf("failed to lookup host '%s'", hostname));
-
-       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)
-         return fprotd_errlog_defer(
-           string_sprintf("unable to acquire socket (%s)", strerror(errno)));
-
-       /* 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 ) {
-         int err = errno;
-         (void)close(sock);
-         return fprotd_errlog_defer(
-                   string_sprintf("connection to %s, port %u-%u failed (%s)",
-                           inet_ntoa(in), portlow, porthigh, strerror(err)));
-       }
+       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);
@@ -447,7 +478,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
        /* 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 ( recv_line(sock, buf, sizeof(buf)) > 0) {
            if ( Ustrstr(buf, US"<detected type=\"") != NULL )
              detected = 1;
            else if ( detected && (strhelper = Ustrstr(buf, US"<name>")) ) {
@@ -469,43 +500,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     /* v0.0 - initial release -- support for unix sockets      */
       {
        struct sockaddr_un server;
-       int sock, result, ovector[10*3];
-       unsigned int port, fsize;
+       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;
-       uschar hostname[256];
-       struct hostent *he;
-       struct in_addr in;
-       pcre *drweb_re;
+       const pcre *drweb_re;
 
        if (*scanner_options != '/') {
-
-         /* extract host and port part */
-         if( sscanf(CS scanner_options, "%255s %u", hostname, &port) != 2 )
-           return drweb_errlog_defer(
-             string_sprintf("invalid socket '%s'", scanner_options));
-
-         /* Lookup the host */
-         if((he = gethostbyname(CS hostname)) == 0)
-           return drweb_errlog_defer(
-             string_sprintf("failed to lookup host '%s'", hostname));
-
-         in = *(struct in_addr *) he->h_addr_list[0];
-
-         /* Open the drwebd TCP socket */
-         if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0)
-           return drweb_errlog_defer(
-             string_sprintf("unable to acquire socket (%s)", strerror(errno)));
-
-         if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
-           int err = errno;
-           (void)close(sock);
-           return drweb_errlog_defer(
-             string_sprintf("connection to %s, port %u failed (%s)",
-               inet_ntoa(in), port, strerror(err)));
-         }
+         if ((sock = m_tcpsocket_fromdef(scanner_options, &errstr)) < 0)
+           return drweb_errlog_defer(errstr);
 
          /* prepare variables */
          drweb_cmd = htonl(DRWEBD_SCAN_CMD);
@@ -532,8 +537,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
          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);
+         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) ||
@@ -575,8 +580,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
              string_sprintf("unable to send file body to socket (%s)", scanner_options));
          }
          (void)close(drweb_fd);
-       }
-       else {
+
+       } else {
+
          if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
            return drweb_errlog_defer(errstr);
 
@@ -614,22 +620,19 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        drweb_vnum = ntohl(drweb_vnum);
 
        /* "virus(es) found" if virus number is > 0 */
-       if (drweb_vnum)
-       {
+       if (drweb_vnum) {
          int i;
 
          /* setup default virus name */
-         malware_name_internal = "unknown";
-         malware_name = malware_name_internal;
+         malware_name = "unknown";
 
          /* set up match regex */
-         drweb_re = pcre_compile( "infected\\swith\\s*(.+?)$", PCRE_COPT,
-           (const char **)&rerror, &roffset, NULL );
+         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;
+           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);
@@ -653,14 +656,13 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
              pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
 
-             /* the first name we just copy to malware_name */
-             if (i==0)
-               malware_name_internal = string_append(NULL, &size, &off,
+             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_internal = string_append(malware_name_internal,
-                                           &size, &off, 2, "/", 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);
            }
@@ -726,26 +728,20 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
          debug_printf("aveserver: %s\n", buf);
          if (buf[0] == '2')
            break;
-         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;
+         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_internal = string_copy(&buf[4]);
-           malware_name = malware_name_internal;
+           malware_name = string_copy(&buf[4]);
          }
        }
 
-       /* prepare our command */
-       (void)string_format(buf, sizeof(buf), "quit\r\n");
-
        /* and send it */
-       if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
+       if (m_sock_send(sock, "quit\r\n", 6, &errstr) < 0)
          return aves_errlog_defer(errstr);
 
        /* read aveserver's greeting and see if it is ready (2xx greeting) */
@@ -771,7 +767,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        int sock, i, j, bread = 0;
        uschar * file_name;
        uschar av_buffer[1024];
-       pcre * fs_inf;
+       const pcre * fs_inf;
        static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n",
                                        US"CONFIGURE\tTIMEOUT\t0\n",
                                        US"CONFIGURE\tMAXARCH\t5\n",
@@ -817,11 +813,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
        /* 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);
+       fs_inf = m_pcre_compile("\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr);
 
        /* read report, linewise */
        do {
-         int ovector[10*3];
          i = 0;
          memset(av_buffer, 0, sizeof(av_buffer));
          do {
@@ -839,17 +834,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
          /* debug_printf("got line \"%s\"\n",av_buffer); */
 
          /* Really search for virus again? */
-         if (malware_name == NULL) {
+         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, nelements(ovector));
-           if (i >= 2) {
-             /* Got it */
-             pcre_get_substring(CS av_buffer, ovector, i, 1,
-                                 (const char **) &malware_name_internal);
-             malware_name = malware_name_internal;
-           }
-         }
+           malware_name = m_pcre_exec(fs_inf, av_buffer);
        }
        while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL);
        (void)close(sock);
@@ -865,7 +852,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        uschar * scanrequest;
        int kav_rc;
        unsigned long kav_reportlen, bread;
-       pcre *kav_re;
+       const pcre *kav_re;
        uschar *p;
 
        if((sock = m_unixsocket(scanner_options, &errstr)) < 0)
@@ -924,8 +911,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
          int report_flag = 0;
 
          /* setup default virus name */
-         malware_name_internal = string_copy("unknown");
-         malware_name = malware_name_internal;
+         malware_name = "unknown";
 
          report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ];
 
@@ -941,19 +927,13 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
            reportsize is 0 (!?) */
            if (kav_reportlen > 0) {
              /* set up match regex, depends on retcode */
-             kav_re = pcre_compile( kav_rc == 3
-                                    ? "suspicion:\\s*(.+?)\\s*$"
-                                    : "infected:\\s*(.+?)\\s*$",
-                                    PCRE_COPT,
-                                    (const char **)&rerror,
-                                    &roffset,
-                                    NULL );
+             kav_re = m_pcre_compile( kav_rc == 3
+                                      ? "suspicion:\\s*(.+?)\\s*$"
+                                      : "infected:\\s*(.+?)\\s*$",
+                                      &errstr );
 
              /* read report, linewise */
              while (kav_reportlen > 0) {
-               int result = 0;
-               int ovector[10*3];
-
                bread = 0;
                while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) {
                  kav_reportlen--;
@@ -964,13 +944,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                tmpbuf[bread] = '\0';
 
                /* try matcher on the line, grab substring */
-               result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
-                                 ovector, nelements(ovector));
-               if (result >= 2) {
-                 pcre_get_substring(CS tmpbuf, ovector, result, 1,
-                                     (const char **) &malware_name_internal);
+               if ((malware_name = m_pcre_exec(kav_re, tmpbuf)))
                  break;
-               }
              }
            }
          }
@@ -985,9 +960,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     case M_CMDL: /* "cmdline" scanner type ---------------------------------- */
       {
        const uschar *cmdline_scanner = scanner_options;
-       uschar *cmdline_trigger;
        const pcre *cmdline_trigger_re;
-       uschar *cmdline_regex;
        const pcre *cmdline_regex_re;
        uschar * file_name;
        uschar * commandline;
@@ -997,8 +970,6 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        FILE *scanner_record = NULL;
        uschar linebuffer[32767];
        int trigger = 0;
-       int result;
-       int ovector[10*3];
        uschar *p;
        BOOL fits;
 
@@ -1006,26 +977,16 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
          return cmdl_errlog_defer("missing commandline specification");
 
        /* find scanner output trigger */
-       if (!(cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
-         return cmdl_errlog_defer("missing trigger specification");
-
-       /* precompile trigger regex */
-       cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-       if (cmdline_trigger_re == NULL)
-         return cmdl_errlog_defer(
-           string_sprintf("regular expression error in '%s': %s at offset %d",
-             cmdline_trigger, rerror, roffset));
+       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 */
-       if (!(cmdline_regex = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
-         return cmdl_errlog_defer("missing virus name regex specification");
-
-       /* precompile name regex */
-       cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-       if (cmdline_regex_re == NULL)
-         return cmdl_errlog_defer(
-           string_sprintf("regular expression error in '%s': %s at offset %d",
-             cmdline_regex, rerror, roffset));
+       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. */
@@ -1069,7 +1030,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        }
 
        /* look for trigger while recording output */
-       while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out) != NULL) {
+       while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out)) {
          if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) {
            /* short write */
            pclose(scanner_out);
@@ -1089,19 +1050,16 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        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,32767,scanner_record) != NULL) {
+         while(fgets(CS linebuffer, sizeof(linebuffer), scanner_record)) {
            /* try match */
-           result = pcre_exec(cmdline_regex_re, NULL,
-                                 CS linebuffer, Ustrlen(linebuffer), 0, 0,
-                                 ovector, nelements(ovector));
-           if (result >= 2)
-             pcre_get_substring(CS linebuffer, ovector, result, 1,
-                                 (const char **) &malware_name_internal);
+           if ((s = m_pcre_exec(cmdline_regex_re, linebuffer)))
+             malware_name = s;
          }
          (void)fclose(scanner_record);
        }
@@ -1150,9 +1108,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
        /* infected ? */
        if (av_buffer[0] == '1') {
-         if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0';
-         malware_name_internal = string_copy(&av_buffer[2]);
-         malware_name = malware_name_internal;
+         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");
@@ -1177,13 +1136,12 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
        uschar *p, *vname, *result_tag, *response_end;
        struct sockaddr_un server;
-       int sock,bread=0;
+       int sock, bread=0;
        unsigned int port;
        uschar * file_name;
        uschar av_buffer[1024];
        uschar *hostname = "";
-       struct hostent *he;
-       struct in_addr in;
+       host_item connhost;
        uschar *clamav_fbuf;
        int clam_fd, result;
        unsigned int fsize;
@@ -1271,41 +1229,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 
            /* 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) {
-             clmd_errlog(string_sprintf("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];
-
-           /* Open the ClamAV Socket */
-           if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-             clmd_errlog(string_sprintf("unable to acquire socket (%s)",
-                       strerror(errno)));
-             goto try_next_server;
-           }
-
-           if (ip_connect( sock,
-                           AF_INET,
-                           (uschar*)inet_ntoa(in),
-                           clamd_address_vector[current_server]->tcp_port,
-                           5 ) > -1) {
+           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;
-           } else {
-             clmd_errlog(string_sprintf(
-                "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);
            }
 
-  try_next_server:
+           clmd_errlog(errstr);
+
            /* Remove the server from the list. XXX We should free the memory */
            num_servers--;
            int i;
@@ -1369,19 +1303,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                av_buffer2));
          }
 
-         if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-           int err = errno;
+         sockData = m_tcpsocket(connhost.address, port, NULL, &errstr);
+         if (sockData < 0) {
            (void)close(sock);
-           return clmd_errlog_defer(
-             string_sprintf("unable to acquire socket (%s)", strerror(err)));
-         }
-
-         if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
-           int err = errno;
-           (void)close(sockData); (void)close(sock);
-           return clmd_errlog_defer(
-             string_sprintf("connection to %s, port %u failed (%s)",
-                   inet_ntoa(in), port, strerror(err)));
+           return clmd_errlog_defer(errstr);
          }
 
   #define CLOSE_SOCKDATA (void)close(sockData)
@@ -1582,8 +1507,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
            if (p)
              *p = '\0';
          }
-         malware_name_internal = string_copy(vname);
-         malware_name = malware_name_internal;
+         malware_name = string_copy(vname);
          DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name);
 
        } else if (Ustrcmp(result_tag, "ERROR") == 0)
@@ -1602,11 +1526,82 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        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=0;
+       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;
+
+       /* find scanner command line */
+       if (!(sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
+                                           NULL, 0)))
+         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 */
+       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);
+
+       /* We're done sending, close socket for writing. */
+       /* shutdown(sock, SHUT_WR); */
+
+       /* Read the result */
+       memset(av_buffer, 0, sizeof(av_buffer));
+       bread = read(sock, av_buffer, sizeof(av_buffer));
+       (void)close(sock);
+
+       if (!(bread > 0))
+         return sock_errlog_defer(
+           string_sprintf("unable to read from socket (%s)", strerror(errno)));
+
+       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;
+      }
+
     case M_MKSD: /* "mksd" scanner type ------------------------------------- */
       {
        char *mksd_options_end;
        int mksd_maxproc = 1;  /* default, if no option supplied */
-       struct sockaddr_un server;
        int sock;
        int retval;
 
@@ -1636,13 +1631,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       }
     }
 
-    /* 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;
   }
@@ -1750,8 +1743,7 @@ mksd_parse_line (char *line)
         if (((p-line) > 5) && (line[3] == ' '))
           if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) {
             *p = '\0';
-            malware_name_internal = string_copy(line+4);
-           malware_name = malware_name_internal;
+            malware_name = string_copy(line+4);
             return OK;
           }
       }