Apply timeout consistently to all malware scanner types
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 27 Dec 2014 20:47:19 +0000 (20:47 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Mon, 12 Jan 2015 18:58:37 +0000 (18:58 +0000)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/functions.h
src/src/ip.c
src/src/malware.c

index 460b1bfab7ca11f4eddf2cd55aa2384e108f5a1a..64f74f555322275b247a7281af376c234d81bfe7 100644 (file)
@@ -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'&&`>:[...]`&
index 4dad461d1e2f0736c1cf46493f802bca8b6803a7..6061afd9792b33cc51a42b90545e63daa084d83e 100644 (file)
@@ -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
index 34767257961df792126690ed2c8354d0b143b9e8..6d10df4e3f708e7788dcff8b403bc8168e1c8779 100644 (file)
@@ -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 *);
index ec034abc480a019337f60ed8b66e25ad21cb860c..a50e209a21fc1a0ab207ed3aaa73d511e7f19fc7 100644 (file)
@@ -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. */
index 1a3dc7f9b02c8b8070e5168a9e9adaf45de70a17..30586feda08f32ba134f6ac15991e0d6bc334dfd 100644 (file)
@@ -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);