[exim-cvs] Apply timeout consistently to all malware scanner…

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Apply timeout consistently to all malware scanner types
Gitweb: http://git.exim.org/exim.git/commitdiff/4e71661f10e9df59f0e7c31cc708f4fcc0c49089
Commit:     4e71661f10e9df59f0e7c31cc708f4fcc0c49089
Parent:     376e377e2de95486a752620f8e52d270cdab5c7c
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sat Dec 27 20:47:19 2014 +0000
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Mon Jan 12 18:58:37 2015 +0000


    Apply timeout consistently to all malware scanner types
---
 doc/doc-docbook/spec.xfpt |    6 +-
 doc/doc-txt/ChangeLog     |    2 +
 src/src/functions.h       |    5 +-
 src/src/ip.c              |   60 ++++--
 src/src/malware.c         |  509 ++++++++++++++++++++++++++-------------------
 5 files changed, 346 insertions(+), 236 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 460b1bf..64f74f5 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -30335,10 +30335,12 @@ It supports a &"generic"& interface to scanners called via the shell, and
specialized interfaces for &"daemon"& type virus scanners, which are resident
in memory and thus are much faster.

+A timeout of 2 minutes is applied to a scanner call;
+if it expires then a defer action is taken.

 .oindex "&%av_scanner%&"
-You can set the &%av_scanner%& option in first part of the Exim configuration
-file to specify which scanner to use, together with any additional options that
+You can set the &%av_scanner%& option in the main part of the configuration
+to specify which scanner to use, together with any additional options that
 are needed. The basic syntax is as follows:
 .display
 &`av_scanner = <`&&'scanner-type'&&`>:<`&&'option1'&&`>:<`&&'option2'&&`>:[...]`&
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 4dad461..6061afd 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -35,6 +35,8 @@ JH/08 The EXPERIMENTAL_DSN compile option is no longer needed; all Delivery
       under the control of the dsn_advertise_hosts option, and routers may
       have a dsn_lasthop option.


+JH/09 A timeout of 2 minutes is now applied to all malware scanner types.
+


 Exim version 4.85
diff --git a/src/src/functions.h b/src/src/functions.h
index 3476725..6d10df4 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -172,6 +172,8 @@ extern uschar *expand_string_copy(uschar *);
 extern int_eximarith_t expand_string_integer(uschar *, BOOL);
 extern void    modify_variable(uschar *, void *);


+extern BOOL    fd_ready(int, int);
+
 extern int     filter_interpret(uschar *, int, address_item **, uschar **);
 extern BOOL    filter_personal(string_item *, BOOL);
 extern BOOL    filter_runtest(int, uschar *, BOOL, BOOL);
@@ -275,9 +277,6 @@ extern void    queue_count(void);
 extern void    queue_run(uschar *, uschar *, BOOL);


 extern int     random_number(int);
-#ifdef WITH_CONTENT_SCAN
-extern int     recv_line(int, uschar *, int);
-#endif
 extern int     rda_interpret(redirect_block *, int, uschar *, uschar *,
                  uschar *, uschar *, uschar *, ugid_block *, address_item **,
                  uschar **, error_block **, int *, uschar *);
diff --git a/src/src/ip.c b/src/src/ip.c
index ec034ab..a50e209 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -384,39 +384,37 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
 *         Receive from a socket with timeout     *
 *************************************************/


-/* The timeout is implemented using select(), and we loop to cover select()
-getting interrupted, and the possibility of select() returning with a positive
-result but no ready descriptor. Is this in fact possible?
-
+/*
 Arguments:
-  sock        the socket
-  buffer      to read into
-  bufsize     the buffer size
-  timeout     the timeout
-
-Returns:      > 0 => that much data read
-              <= 0 on error or EOF; errno set - zero for EOF
+  fd          the file descriptor
+  timeout     the timeout, seconds
+Returns:      TRUE => ready for i/o
+              FALSE => timed out, or other error
 */
-
-int
-ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+BOOL
+fd_ready(int fd, int timeout)
 {
 fd_set select_inset;
 struct timeval tv;
 time_t start_recv = time(NULL);
 int rc;


+if (timeout <= 0)
+ {
+ errno = ETIMEDOUT;
+ return FALSE;
+ }
/* Wait until the socket is ready */

for (;;)
{
FD_ZERO (&select_inset);
- FD_SET (sock, &select_inset);
+ FD_SET (fd, &select_inset);
tv.tv_sec = timeout;
tv.tv_usec = 0;

- DEBUG(D_transport) debug_printf("waiting for data on socket\n");
- rc = select(sock + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
+ DEBUG(D_transport) debug_printf("waiting for data on fd\n");
+ rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);

   /* If some interrupt arrived, just retry. We presume this to be rare,
   but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
@@ -440,13 +438,37 @@ for (;;)
   if (rc <= 0)
     {
     errno = ETIMEDOUT;
-    return -1;
+    return FALSE;
     }


/* If the socket is ready, break out of the loop. */

-  if (FD_ISSET(sock, &select_inset)) break;
+  if (FD_ISSET(fd, &select_inset)) break;
   }
+return TRUE;
+}
+
+/* The timeout is implemented using select(), and we loop to cover select()
+getting interrupted, and the possibility of select() returning with a positive
+result but no ready descriptor. Is this in fact possible?
+
+Arguments:
+  sock        the socket
+  buffer      to read into
+  bufsize     the buffer size
+  timeout     the timeout
+
+Returns:      > 0 => that much data read
+              <= 0 on error or EOF; errno set - zero for EOF
+*/
+
+int
+ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+{
+int rc;
+
+if (!fd_ready(sock, timeout))
+  return -1;


/* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
close down of the connection), set errno to zero; otherwise leave it alone. */
diff --git a/src/src/malware.c b/src/src/malware.c
index 1a3dc7f..30586fe 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -48,7 +48,7 @@ typedef struct clamd_address_container {
} clamd_address_container;

/* declaration of private routines */
-static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename);
+static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename, int tmo);
static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking);

#ifndef nelements
@@ -305,6 +305,46 @@ m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr)
return cre;
}

+/*
+ Simple though inefficient wrapper for reading a line.  Drop CRs and the
+ trailing newline. Can return early on buffer full. Null-terminate.
+ Apply initial timeout if no data ready.
+
+ Return: number of chars - zero for an empty line or EOF
+*/
+static int
+recv_line(int fd, uschar * buffer, int bsize, int tmo)
+{
+uschar * p = buffer;
+ssize_t rcv;
+
+if (!fd_ready(fd, tmo-time(NULL)))
+  return -1;
+
+    /*XXX tmo handling assumes we always get a whole line */
+/* read until \n */
+while ((rcv = read(fd, p, 1)) > 0)
+  {
+  if (p-buffer > bsize-2) break;
+  if (*p == '\n') break;
+  if (*p != '\r') p++;
+  }
+*p = '\0';
+
+DEBUG(D_acl) debug_printf("Malware scan: read '%s'\n", buffer);
+return p - buffer;
+}
+
+/* return TRUE iff size as requested */
+static BOOL
+recv_len(int sock, void * buf, int size, int tmo)
+{
+return fd_ready(sock, tmo-time(NULL))
+  ? recv(sock, buf, size, 0) == size
+  : FALSE;
+}
+
+
 /*************************************************
 *          Scan content for malware              *
 *************************************************/
@@ -336,6 +376,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
   struct scan * scanent;
   const uschar * scanner_options;
   int sock = -1;
+  time_t tmo;


   /* make sure the eml mbox file is spooled up */
   if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL)))
@@ -386,6 +427,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     /* find the scanner type from the av_scanner option */
     if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
       return malware_errlog_defer(US"av_scanner configuration variable is empty");
+    tmo = time(NULL) + MALWARE_TIMEOUT;


     for (scanent = m_scans; ; scanent++) {
       if (!scanent->name)
@@ -410,67 +452,75 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     }
     DEBUG(D_lookup) debug_printf("Malware scan: %s\n", scanner_name);


-    switch (scanent->scancode) {
-    case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
+    switch (scanent->scancode)
       {
+      case M_FPROTD: /* "f-protd" scanner type ------------------------------ */
+    {
     uschar *fp_scan_option;
     unsigned int detected=0, par_count=0;
     uschar * scanrequest;
     uschar buf[32768], *strhelper, *strhelper2;
     uschar * malware_name_internal = NULL;
+    int len;


-    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))) {
+                  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);
+    DEBUG(D_acl) debug_printf("Malware scan: issuing %s: %s\n",
+      scanner_name, scanrequest);


     /* send scan request */
     if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
       return m_errlog_defer(scanent, 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 )
+    while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
+      if (len > 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) {
+        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\">")
+        else if (Ustrstr(buf, US"<summary code=\""))
+          {
+          malware_name = Ustrstr(buf, US"<summary code=\"11\">")
           ? malware_name_internal : NULL;
-      }
-    }
+          break;
+          }
+        }
     break;
-      }    /* f-protd */
+    }    /* f-protd */


-    case M_DRWEB: /* "drweb" scanner type ----------------------------------- */
+      case M_DRWEB: /* "drweb" scanner type --------------------------------- */
     /* v0.1 - added support for tcp sockets          */
     /* v0.0 - initial release -- support for unix sockets      */
-      {
+    {
     int result;
     off_t fsize;
     unsigned int fsize_uint;
     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;


     /* prepare variables */
     drweb_cmd = htonl(DRWEBD_SCAN_CMD);
     drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);


-    if (*scanner_options != '/') {
-
+    if (*scanner_options != '/')
+      {
       /* calc file size */
       if ((drweb_fd = open(CS eml_filename, O_RDONLY)) == -1)
         return m_errlog_defer_3(scanent,
@@ -478,22 +528,24 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
         eml_filename, strerror(errno)),
           sock);


-      if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1) {
+      if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
+        {
         int err = errno;
         (void)close(drweb_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("can't seek spool file %s: %s",
         eml_filename, strerror(err)),
           sock);
-      }
+        }
       fsize_uint = (unsigned int) fsize;
-      if ((off_t)fsize_uint != fsize) {
+      if ((off_t)fsize_uint != fsize)
+        {
         (void)close(drweb_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("seeking spool file %s, size overflow",
         eml_filename),
           sock);
-      }
+        }
       drweb_slen = htonl(fsize);
       lseek(drweb_fd, 0, SEEK_SET);


@@ -504,22 +556,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       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)) {
+          (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+        {
         (void)close(drweb_fd);
-        return m_errlog_defer_3(scanent,
-          string_sprintf("unable to send commands to socket (%s)", scanner_options),
+        return m_errlog_defer_3(scanent, string_sprintf(
+          "unable to send commands to socket (%s)", scanner_options),
           sock);
-      }
+        }


-      if (!(drweb_fbuf = (uschar *) malloc (fsize_uint))) {
+      if (!(drweb_fbuf = (uschar *) malloc (fsize_uint)))
+        {
         (void)close(drweb_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("unable to allocate memory %u for file (%s)",
         fsize_uint, eml_filename),
           sock);
-      }
+        }


-      if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1) {
+      if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1)
+        {
         int err = errno;
         (void)close(drweb_fd);
         free(drweb_fbuf);
@@ -527,20 +582,21 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
           string_sprintf("can't read spool file %s: %s",
         eml_filename, strerror(err)),
           sock);
-      }
+        }
       (void)close(drweb_fd);


       /* send file body to socket */
-      if (send(sock, drweb_fbuf, fsize, 0) < 0) {
+      if (send(sock, drweb_fbuf, fsize, 0) < 0)
+        {
         free(drweb_fbuf);
-        return m_errlog_defer_3(scanent,
-          string_sprintf("unable to send file body to socket (%s)", scanner_options),
-        sock);
-      }
+        return m_errlog_defer_3(scanent, string_sprintf(
+          "unable to send file body to socket (%s)", scanner_options),
+          sock);
+        }
       (void)close(drweb_fd);
-
-    } else {
-
+      }
+    else
+      {
       drweb_slen = htonl(Ustrlen(eml_filename));


       DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
@@ -552,24 +608,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
           (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
           (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
           (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
-        return m_errlog_defer_3(scanent,
-          string_sprintf("unable to send commands to socket (%s)", scanner_options),
+        return m_errlog_defer_3(scanent, string_sprintf(
+          "unable to send commands to socket (%s)", scanner_options),
           sock);
-    }
+      }


     /* wait for result */
-    if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc)))
+    if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
       return m_errlog_defer_3(scanent,
               US"unable to read return code", sock);
     drweb_rc = ntohl(drweb_rc);


-    if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum)))
+    if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
       return m_errlog_defer_3(scanent,
                   US"unable to read the number of viruses", sock);
     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 */
@@ -579,18 +636,18 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       drweb_re = m_pcre_compile(US"infected\\swith\\s*(.+?)$", &errstr);


       /* read and concatenate virus names into one string */
-      for (i=0;i<drweb_vnum;i++)
-      {
+      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)))
+        if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
           return m_errlog_defer_3(scanent,
                 US"cannot read report size", sock);
         drweb_slen = ntohl(drweb_slen);
         tmpbuf = store_get(drweb_slen);


         /* read report body */
-        if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen)
+        if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
           return m_errlog_defer_3(scanent,
                 US"cannot read report string", sock);
         tmpbuf[drweb_slen] = '\0';
@@ -598,7 +655,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
         /* 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) {
+        if (result >= 2)
+          {
           const char * pre_malware_nb;


           pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
@@ -612,10 +670,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                         2, "/", pre_malware_nb);


           pcre_free_substring(pre_malware_nb);
+          }
         }
       }
-    }
-    else {
+    else
+      {
       const char *drweb_s = NULL;


       if (drweb_rc & DERR_READ_ERR) drweb_s = "read error";
@@ -633,9 +692,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)


       /* no virus found */
       malware_name = NULL;
-    }
+      }
     break;
-      }    /* drweb */
+    }    /* drweb */


     case M_AVES: /* "aveserver" scanner type -------------------------------- */
       {
@@ -643,7 +702,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     int result;


     /* read aveserver's greeting and see if it is ready (2xx greeting) */
-    recv_line(sock, buf, sizeof(buf));
+    recv_line(sock, buf, sizeof(buf), tmo);


     if (buf[0] != '2')        /* aveserver is having problems */
       return m_errlog_defer_3(scanent,
@@ -664,28 +723,32 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     malware_name = NULL;
     result = 0;
     /* read response lines, find malware name and final response */
-    while (recv_line(sock, buf, sizeof(buf)) > 0) {
+    while (recv_line(sock, buf, sizeof(buf), tmo) > 0)
+      {
       debug_printf("aveserver: %s\n", buf);
       if (buf[0] == '2')
         break;
-      if (buf[0] == '5') {        /* aveserver is having problems */
+      if (buf[0] == '5')        /* aveserver is having problems */
+        {
         result = m_errlog_defer(scanent,
            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],' ');
+        }
+      if (Ustrncmp(buf,"322",3) == 0)
+        {
+        uschar *p = Ustrchr(&buf[4], ' ');
         *p = '\0';
         malware_name = string_copy(&buf[4]);
+        }
       }
-    }


     /* and send it */
     if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
       return m_errlog_defer(scanent, errstr);


     /* read aveserver's greeting and see if it is ready (2xx greeting) */
-    recv_line(sock, buf, sizeof(buf));
+    recv_line(sock, buf, sizeof(buf), tmo);


     if (buf[0] != '2')        /* aveserver is having problems */
       return m_errlog_defer_3(scanent,
@@ -693,10 +756,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
               ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
         sock);


-    if (result == DEFER) {
+    if (result == DEFER)
+      {
       (void)close(sock);
       return DEFER;
-    }
+      }
     break;
       }    /* aveserver */


@@ -710,30 +774,29 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                     US"CONFIGURE\tTIMEOUT\t0\n",
                     US"CONFIGURE\tMAXARCH\t5\n",
                     US"CONFIGURE\tMIME\t1\n" };
-    time_t tmo;


     malware_name = NULL;


     DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
         scanner_name, scanner_options);
-    tmo = time(NULL) + MALWARE_TIMEOUT;
     /* pass options */
     memset(av_buffer, 0, sizeof(av_buffer));
-    for (i=0; i != nelements(cmdopt); i++) {
+    for (i = 0; i != nelements(cmdopt); i++)
+      {


       if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
         return m_errlog_defer(scanent, errstr);


-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
-      if (bread >0) av_buffer[bread]='\0';
+      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      if (bread > 0) av_buffer[bread]='\0';
       if (bread < 0)
         return m_errlog_defer_3(scanent,
           string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
           sock);
-      for (j=0;j<bread;j++)
-        if((av_buffer[j]=='\r')||(av_buffer[j]=='\n'))
+      for (j = 0; j < bread; j++)
+        if (av_buffer[j] == '\r' || av_buffer[j] == '\n')
           av_buffer[j] ='@';
-    }
+      }


     /* pass the mailfile to fsecure */
     file_name = string_sprintf("SCAN\t%s\n", eml_filename);
@@ -753,13 +816,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)


       for (;;)
         {
-        int t = tmo - time(NULL);
-
         errno = ETIME;
         i =  av_buffer+sizeof(av_buffer)-p;
-        if (  t <= 0
-           || (bread= ip_recv(sock, p, i-1, t)) < 0
-           )
+        if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
           return m_errlog_defer_3(scanent,
         string_sprintf("unable to read result (%s)", strerror(errno)),
         sock);
@@ -820,7 +879,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       return m_errlog_defer(scanent, errstr);


     /* wait for result */
-    if ((bread = recv(sock, tmpbuf, 2, 0) != 2))
+    if (!recv_len(sock, tmpbuf, 2, tmo))
       return m_errlog_defer_3(scanent,
                   US"unable to read 2 bytes from socket.", sock);


@@ -844,7 +903,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     bounces where part of a file has been cut off */


     /* "virus found" return codes (2-4) */
-    if ((kav_rc > 1) && (kav_rc < 5)) {
+    if (kav_rc > 1 && kav_rc < 5)
+      {
       int report_flag = 0;


       /* setup default virus name */
@@ -853,15 +913,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ];


       /* read the report, if available */
-      if( report_flag == 1 ) {
+      if (report_flag == 1)
+        {
         /* read report size */
-        if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4)
+        if (!recv_len(sock, &kav_reportlen, 4, tmo))
           return m_errlog_defer_3(scanent,
             US"cannot read report size", sock);


         /* it's possible that avp returns av_buffer[1] == 1 but the
         reportsize is 0 (!?) */
-        if (kav_reportlen > 0) {
+        if (kav_reportlen > 0)
+          {
           /* set up match regex, depends on retcode */
           kav_re = m_pcre_compile( kav_rc == 3
                        ? US"suspicion:\\s*(.+?)\\s*$"
@@ -869,23 +931,19 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
                        &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';
+          while (kav_reportlen > 0)
+            {
+        if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
+          break;
+        kav_reportlen -= bread+1;


         /* try matcher on the line, grab substring */
         if ((malware_name = m_pcre_exec(kav_re, tmpbuf)))
           break;
+        }
           }
         }
       }
-    }
     else /* no virus found */
       malware_name = NULL;


@@ -902,8 +960,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     void (*eximsigchld)(int);
     void (*eximsigpipe)(int);
     FILE *scanner_out = NULL;
+    int scanner_fd;
     FILE *scanner_record = NULL;
     uschar linebuffer[32767];
+    int rcnt;
     int trigger = 0;
     uschar *p;


@@ -934,45 +994,62 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     /* 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);
+    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);


-    if (!(scanner_out = popen(CS commandline,"r"))) {
+    if (!(scanner_out = popen(CS commandline,"r")))
+      {
       int err = errno;
       signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
       return m_errlog_defer(scanent,
         string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
-    }
+      }
+    scanner_fd = fileno(scanner_out);


     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) {
+    if (!(scanner_record = modefopen(file_name, "wb", SPOOL_MODE)))
+      {
       int err = errno;
       (void) pclose(scanner_out);
       signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-      return m_errlog_defer(scanent,
-        string_sprintf("opening scanner output file (%s) failed: %s.",
+      return m_errlog_defer(scanent, string_sprintf(
+          "opening scanner output file (%s) failed: %s.",
           file_name, strerror(err)));
-    }
+      }


     /* look for trigger while recording output */
-    while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out)) {
-      if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) {
+    while ((rcnt = recv_line(scanner_fd, linebuffer,
+            sizeof(linebuffer), tmo)))
+      {
+      if (rcnt < 0)
+        {
+        (void) pclose(scanner_out);
+        signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
+        return m_errlog_defer(scanent, string_sprintf(
+        "unable to read from scanner (%s)", scanner_options));
+        }
+
+      if (Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record))
+        {
         /* short write */
         (void) pclose(scanner_out);
         signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-        return m_errlog_defer(scanent,
-          string_sprintf("short write on scanner output file (%s).", file_name));
-      }
+        return m_errlog_defer(scanent, string_sprintf(
+          "short write on scanner output file (%s).", file_name));
+        }
+      putc('\n', scanner_record);
       /* try trigger match */
-      if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1))
+      if (  !trigger
+         && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)
+         )
         trigger = 1;
-    }
+      }


     (void)fclose(scanner_record);
     sep = pclose(scanner_out);
@@ -983,20 +1060,22 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
         ? string_sprintf("running scanner failed: %s", strerror(sep))
         : string_sprintf("scanner returned error code: %d", sep));


-    if (trigger) {
+    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)) {
+      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;
@@ -1026,7 +1105,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)


     /* wait for result */
     memset(av_buffer, 0, sizeof(av_buffer));
-    if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0))
+    if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
       return m_errlog_defer_3(scanent,
         string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
         sock);
@@ -1086,7 +1165,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       /* 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 {
+    else
+      {
       const uschar *address = scanner_options;
       uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20];


@@ -1094,15 +1174,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
        * 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 {
+      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) {
+        if (strcmpic(address,US"local") == 0)
+          {
           use_scan_command = TRUE;
           continue;
-        }
+          }


         /* XXX: If unsuccessful we should free this memory */
         this_clamd =
@@ -1110,21 +1192,23 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)


         /* 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 ) {
+           this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 )
+          {
           (void) m_errlog_defer(scanent,
               string_sprintf("invalid address '%s'", address));
           continue;
-        }
+          }


         clamd_address_vector[num_servers] = this_clamd;
         num_servers++;
-        if (num_servers >= MAX_CLAMD_SERVERS) {
+        if (num_servers >= MAX_CLAMD_SERVERS)
+          {
           (void) m_errlog_defer(scanent,
             US"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,
+          }
+        } while ((address = string_nextinlist(&av_scanner_work, &sep,
                       address_buffer,
                       sizeof(address_buffer))) != NULL);


@@ -1132,23 +1216,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       if (!num_servers)
         return m_errlog_defer(scanent,
           US"no useable server addresses in malware configuration option.");
-    }
+      }


-    /* See the discussion of response formats below to see why we really don't
-    like colons in filenames when passing filenames to ClamAV. */
+    /* See the discussion of response formats below to see why we really
+    don't like colons in filenames when passing filenames to ClamAV. */
     if (use_scan_command && Ustrchr(eml_filename, ':'))
       return m_errlog_defer(scanent,
         string_sprintf("local/SCAN mode incompatible with" \
           " : in path to email filename [%s]", eml_filename));


     /* We have some network servers specified */
-    if (num_servers) {
-
+    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 ) {
+      while (num_servers > 0)
+        {
+        int i;
         /* Randomly pick a server to start with */
         current_server = random_number( num_servers );


@@ -1161,37 +1247,38 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
         sock= m_tcpsocket(clamd_address_vector[current_server]->tcp_addr,
                 clamd_address_vector[current_server]->tcp_port,
                 &connhost, &errstr);
-        if (sock >= 0) {
+        if (sock >= 0)
+          {
           /* Connection successfully established with a server */
           hostname = clamd_address_vector[current_server]->tcp_addr;
           break;
-        }
+          }


         (void) m_errlog_defer(scanent, 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++ )
+        for (i = current_server; i < num_servers; i++)
           clamd_address_vector[i] = clamd_address_vector[i+1];
-      }
+        }


-      if ( num_servers == 0 )
+      if (num_servers == 0)
         return m_errlog_defer(scanent, US"all servers failed");
-
-    } else {
+      }
+    else
+      {
       if ((sock = m_unixsocket(scanner_options, &errstr)) < 0)
         return m_errlog_defer(scanent, errstr);
-    }
+      }


     /* 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
+    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. */
@@ -1204,7 +1291,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
         return m_errlog_defer(scanent, errstr);


       memset(av_buffer2, 0, sizeof(av_buffer2));
-      bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);
+      bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));


       if (bread < 0)
         return m_errlog_defer_3(scanent,
@@ -1229,8 +1316,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       if (sockData < 0)
         return m_errlog_defer_3(scanent, errstr, sock);


-  #define CLOSE_SOCKDATA (void)close(sockData)
-  #else /* WITH_OLD_CLAMAV_STREAM not defined */
+# 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. */
@@ -1245,64 +1332,70 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
         strerror(errno)),
           sock);


- #define CLOSE_SOCKDATA /**/
- #endif
+# define CLOSE_SOCKDATA /**/
+#endif

       /* calc file size */
-      if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0) {
+      if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
+        {
         int err = errno;
         CLOSE_SOCKDATA;
         return m_errlog_defer_3(scanent,
           string_sprintf("can't open spool file %s: %s",
         eml_filename, strerror(err)),
           sock);
-      }
-      if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) {
+        }
+      if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
+        {
         int err = errno;
         CLOSE_SOCKDATA; (void)close(clam_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("can't seek spool file %s: %s",
         eml_filename, strerror(err)),
           sock);
-      }
+        }
       fsize_uint = (unsigned int) fsize;
-      if ((off_t)fsize_uint != fsize) {
+      if ((off_t)fsize_uint != fsize)
+        {
         CLOSE_SOCKDATA; (void)close(clam_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("seeking spool file %s, size overflow",
         eml_filename),
           sock);
-      }
+        }
       lseek(clam_fd, 0, SEEK_SET);


-      if (!(clamav_fbuf = (uschar *) malloc (fsize_uint))) {
+      if (!(clamav_fbuf = (uschar *) malloc (fsize_uint)))
+        {
         CLOSE_SOCKDATA; (void)close(clam_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("unable to allocate memory %u for file (%s)",
         fsize_uint, eml_filename),
           sock);
-      }
+        }


-      if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) {
+      if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
+        {
         int err = errno;
         free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd);
         return m_errlog_defer_3(scanent,
           string_sprintf("can't read spool file %s: %s",
         eml_filename, strerror(err)),
           sock);
-      }
+        }
       (void)close(clam_fd);


       /* send file body to socket */
-  #ifdef WITH_OLD_CLAMAV_STREAM
-      if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0) {
+#ifdef WITH_OLD_CLAMAV_STREAM
+      if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0)
+        {
         free(clamav_fbuf); CLOSE_SOCKDATA;
         return m_errlog_defer_3(scanent,
           string_sprintf("unable to send file body to socket (%s:%u)",
         hostname, port),
           sock);
-      }
-  #else
+        }
+#else
       send_size = htonl(fsize_uint);
       send_final_zeroblock = 0;
       if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
@@ -1314,14 +1407,15 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
           string_sprintf("unable to send file body to socket (%s)", hostname),
           sock);
         }
-  #endif
+#endif


       free(clamav_fbuf);


       CLOSE_SOCKDATA;
-  #undef CLOSE_SOCKDATA
-
-    } else { /* use scan command */
+#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 */


@@ -1348,17 +1442,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)

       /* 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);
+    bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
     (void)close(sock);
     sock = -1;


-    if (!(bread > 0))
+    if (bread <= 0)
       return m_errlog_defer(scanent,
         string_sprintf("unable to read from socket (%s)", strerror(errno)));


@@ -1404,8 +1498,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)

     /* colon in returned output? */
     if((p = Ustrchr(av_buffer,':')) == NULL)
-      return m_errlog_defer(scanent,
-        string_sprintf("ClamAV returned malformed result (missing colon): %s",
+      return m_errlog_defer(scanent, string_sprintf(
+            "ClamAV returned malformed result (missing colon): %s",
             av_buffer));


     /* strip filename */
@@ -1417,32 +1511,37 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     p = Ustrrchr(vname, ' ');
     result_tag = p ? p+1 : vname;


-    if (Ustrcmp(result_tag, "FOUND") == 0) {
+    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 == ')') {
+      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)
+      }
+    else if (Ustrcmp(result_tag, "ERROR") == 0)
       return m_errlog_defer(scanent,
         string_sprintf("ClamAV returned: %s", av_buffer));


-    else if (Ustrcmp(result_tag, "OK") == 0) {
+    else if (Ustrcmp(result_tag, "OK") == 0)
+      {
       /* Everything should be OK */
       malware_name = NULL;
       DEBUG(D_acl) debug_printf("Malware not found\n");


-    } else
+      }
+    else
       return m_errlog_defer(scanent,
         string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));


@@ -1498,16 +1597,16 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       return m_errlog_defer(scanent, errstr);


     /* Read the result */
-    memset(av_buffer, 0, sizeof(av_buffer));
-    bread = read(sock, av_buffer, sizeof(av_buffer));
+    bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));


-    if (!(bread > 0))
+    if (bread <= 0)
       return m_errlog_defer_3(scanent,
         string_sprintf("unable to read from socket (%s)", strerror(errno)),
         sock);


     if (bread == sizeof(av_buffer))
       return m_errlog_defer_3(scanent, US"buffer too small", sock);
+    av_buffer[bread] = '\0';
     linebuffer = string_copy(av_buffer);


     /* try trigger match */
@@ -1524,10 +1623,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
       {
     char *mksd_options_end;
     int mksd_maxproc = 1;  /* default, if no option supplied */
-    int sock;
     int retval;


-    if (scanner_options) {
+    if (scanner_options)
+      {
       mksd_maxproc = (int)strtol(CS scanner_options, &mksd_options_end, 10);
       if (  *scanner_options == '\0'
          || *mksd_options_end != '\0'
@@ -1536,7 +1635,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
          )
         return m_errlog_defer(scanent,
           string_sprintf("invalid option '%s'", scanner_options));
-    }
+      }


     if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
       return m_errlog_defer(scanent, errstr);
@@ -1545,10 +1644,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)


     DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);


-    if ((retval = mksd_scan_packed(scanent, sock, eml_filename)) != OK) {
+    if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK)
+      {
       close (sock);
       return retval;
-    }
+      }
     break;
       }
     case M_AVAST: /* "avast" scanner type ----------------------------------- */
@@ -1577,7 +1677,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
     return malware_errlog_defer(errstr);


       /* wait for result */
-      for (avast_stage = AVA_HELO; recv_line(sock, buf, sizeof(buf)) > 0; )
+      for (avast_stage = AVA_HELO; recv_line(sock, buf, sizeof(buf), tmo) > 0; )
     {
     int slen = Ustrlen(buf);
     if (slen >= 1) 
@@ -1690,25 +1790,6 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
 }



-/* simple wrapper for reading lines from sockets */
-int
-recv_line(int sock, uschar *buffer, int size)
-{
-  uschar *p = buffer;
-
-  memset(buffer,0,size);
-  /* read until \n */
-  while(recv(sock,p,1,0) > -1) {
-    if ((p-buffer) > (size-2)) break;
-    if (*p == '\n') break;
-    if (*p != '\r') p++;
-  }
-  *p = '\0';
-
-  return (p-buffer);
-}
-
-
 /* ============= private routines for the "mksd" scanner type ============== */


#include <sys/uio.h>
@@ -1742,24 +1823,28 @@ mksd_writev (int sock, struct iovec *iov, int iovcnt)
}

static inline int
-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
{
int offset = 0;
int i;

-  do {
-    if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) {
+  do
+    {
+    i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+    if (i <= 0)
+      {
       (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
       return -1;
-    }
+      }


     offset += i;
     /* offset == av_buffer_size -> buffer full */
-    if (offset == av_buffer_size) {
+    if (offset == av_buffer_size)
+      {
       (void) malware_errlog_defer(US"malformed reply received from mksd");
       return -1;
-    }
-  } while (av_buffer[offset-1] != '\n');
+      }
+    } while (av_buffer[offset-1] != '\n');


av_buffer[offset] = '\0';
return offset;
@@ -1797,7 +1882,7 @@ mksd_parse_line(struct scan * scanent, char *line)
}

 static int
-mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename)
+mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename, int tmo)
 {
   struct iovec iov[3];
   const char *cmd = "MSQ\n";
@@ -1813,7 +1898,7 @@ mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename)
   if (mksd_writev (sock, iov, 3) < 0)
     return DEFER;


-  if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)
+  if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0)
     return DEFER;


return mksd_parse_line (scanent, CS av_buffer);