+/*
+ 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
+ -1 on EOF
+ -2 on timeout or error
+*/
+static int
+recv_line(int fd, uschar * buffer, int bsize, int tmo)
+{
+uschar * p = buffer;
+ssize_t rcv;
+BOOL ok = FALSE;
+
+if (!fd_ready(fd, tmo-time(NULL)))
+ return -2;
+
+/*XXX tmo handling assumes we always get a whole line */
+/* read until \n */
+errno = 0;
+while ((rcv = read(fd, p, 1)) > 0)
+ {
+ ok = TRUE;
+ if (p-buffer > bsize-2) break;
+ if (*p == '\n') break;
+ if (*p != '\r') p++;
+ }
+if (!ok)
+ {
+ DEBUG(D_acl) debug_printf("Malware scan: read %s (%s)\n",
+ rcv==0 ? "EOF" : "error", strerror(errno));
+ return rcv==0 ? -1 : -2;
+ }
+*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;
+}
+
+
+
+/* ============= private routines for the "mksd" scanner type ============== */
+
+#include <sys/uio.h>
+
+static inline int
+mksd_writev (int sock, struct iovec * iov, int iovcnt)
+{
+int i;
+
+for (;;)
+ {
+ do
+ i = writev (sock, iov, iovcnt);
+ while (i < 0 && errno == EINTR);
+ if (i <= 0)
+ {
+ (void) malware_errlog_defer(
+ US"unable to write to mksd UNIX socket (/var/run/mksd/socket)");
+ return -1;
+ }
+ for (;;) /* check for short write */
+ if (i >= iov->iov_len)
+ {
+ if (--iovcnt == 0)
+ return 0;
+ i -= iov->iov_len;
+ iov++;
+ }
+ else
+ {
+ iov->iov_len -= i;
+ iov->iov_base = CS iov->iov_base + i;
+ break;
+ }
+ }
+}
+
+static inline int
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
+{
+int offset = 0;
+int i;
+
+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)
+ {
+ (void) malware_errlog_defer(US"malformed reply received from mksd");
+ return -1;
+ }
+ } while (av_buffer[offset-1] != '\n');
+
+av_buffer[offset] = '\0';
+return offset;
+}
+
+static inline int
+mksd_parse_line(struct scan * scanent, char * line)
+{
+char *p;
+
+switch (*line)
+ {
+ case 'O': /* OK */
+ return OK;
+
+ case 'E':
+ case 'A': /* ERR */
+ if ((p = strchr (line, '\n')) != NULL)
+ *p = '\0';
+ return m_errlog_defer(scanent,
+ string_sprintf("scanner failed: %s", line));
+
+ default: /* VIR */
+ if ((p = strchr (line, '\n')) != NULL)
+ {
+ *p = '\0';
+ if ( p-line > 5
+ && line[3] == ' '
+ && (p = strchr(line+4, ' ')) != NULL
+ && p-line > 4
+ )
+ {
+ *p = '\0';
+ malware_name = string_copy(US line+4);
+ return OK;
+ }
+ }
+ return m_errlog_defer(scanent,
+ string_sprintf("malformed reply received: %s", line));
+ }
+}
+
+static int
+mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename,
+ int tmo)
+{
+struct iovec iov[3];
+const char *cmd = "MSQ\n";
+uschar av_buffer[1024];
+
+iov[0].iov_base = (void *) cmd;
+iov[0].iov_len = 3;
+iov[1].iov_base = (void *) scan_filename;
+iov[1].iov_len = Ustrlen(scan_filename);
+iov[2].iov_base = (void *) (cmd + 3);
+iov[2].iov_len = 1;
+
+if (mksd_writev (sock, iov, 3) < 0)
+ return DEFER;
+
+if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0)
+ return DEFER;
+
+return mksd_parse_line (scanent, CS av_buffer);
+}
+