+ 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);
+}
+
+/*************************************************
+* Scan content for malware *
+*************************************************/
+
+/* This is an internal interface for scanning an email; the normal interface
+is via malware(), or there's malware_in_file() used for testing/debugging.
+
+Arguments:
+ malware_re match condition for "malware="
+ eml_filename the file holding the email to be scanned
+ timeout if nonzero, non-default timeoutl
+ faking whether or not we're faking this up for the -bmalware test
+
+Returns: Exim message processing code (OK, FAIL, DEFER, ...)
+ where true means malware was found (condition applies)
+*/
+static int
+malware_internal(const uschar * malware_re, const uschar * eml_filename,
+ int timeout, BOOL faking)
+{
+int sep = 0;
+uschar *av_scanner_work = av_scanner;
+uschar *scanner_name;
+uschar malware_regex_default[] = ".+";
+unsigned long mbox_size;
+FILE *mbox_file;
+const pcre *re;
+uschar * errstr;
+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)))
+ return malware_errlog_defer(US"error while creating mbox spool file");
+
+/* none of our current scanners need the mbox
+ file as a stream, so we can close it right away */
+(void)fclose(mbox_file);
+
+if (!malware_re)
+ return FAIL; /* empty means "don't match anything" */
+
+/* parse 1st option */
+ if ( (strcmpic(malware_re, US"false") == 0) ||
+ (Ustrcmp(malware_re,"0") == 0) )
+ return FAIL; /* explicitly no matching */
+
+/* special cases (match anything except empty) */
+if ( (strcmpic(malware_re,US"true") == 0) ||
+ (Ustrcmp(malware_re,"*") == 0) ||
+ (Ustrcmp(malware_re,"1") == 0) )
+ malware_re = malware_regex_default;
+
+/* Reset sep that is set by previous string_nextinlist() call */
+sep = 0;
+
+/* compile the regex, see if it works */
+if (!(re = m_pcre_compile(malware_re, &errstr)))
+ return malware_errlog_defer(errstr);
+
+/* if av_scanner starts with a dollar, expand it first */
+if (*av_scanner == '$')
+ {
+ if (!(av_scanner_work = expand_string(av_scanner)))
+ return malware_errlog_defer(
+ string_sprintf("av_scanner starts with $, but expansion failed: %s",
+ expand_string_message));
+
+ DEBUG(D_acl)
+ debug_printf("Expanded av_scanner global: %s\n", av_scanner_work);
+ /* disable result caching in this case */
+ malware_name = NULL;
+ malware_ok = FALSE;
+ }
+
+/* Do not scan twice (unless av_scanner is dynamic). */
+if (!malware_ok)
+ {
+ /* 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");
+ if (!timeout) timeout = MALWARE_TIMEOUT;
+ tmo = time(NULL) + timeout;
+
+ for (scanent = m_scans; ; scanent++) {
+ if (!scanent->name)
+ return malware_errlog_defer(string_sprintf("unknown scanner type '%s'",
+ scanner_name));
+ if (strcmpic(scanner_name, US scanent->name) != 0)
+ continue;
+ if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
+ scanner_options = scanent->options_default;
+ if (scanent->conn == MC_NONE)
+ break;
+ switch(scanent->conn)
+ {
+ case MC_TCP: sock = m_tcpsocket_fromdef(scanner_options, &errstr); break;
+ case MC_UNIX: sock = m_unixsocket(scanner_options, &errstr); break;
+ case MC_STRM: sock = m_streamsocket(scanner_options, &errstr); break;
+ default: /* compiler quietening */ break;
+ }
+ if (sock < 0)
+ return m_errlog_defer(scanent, errstr);
+ break;
+ }
+ DEBUG(D_acl) debug_printf("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout));
+
+ 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;
+
+ scanrequest = string_sprintf("GET %s", eml_filename);
+
+ while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep,
+ NULL, 0)))
+ {
+ scanrequest = string_sprintf("%s%s%s", scanrequest,
+ par_count ? "%20" : "?", fp_scan_option);
+ par_count++;
+ }
+ scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest);
+ 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);
+
+ 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)
+ {
+ *strhelper2 = '\0';
+ malware_name_internal = string_copy(strhelper+6);
+ }
+ }
+ else if (Ustrstr(buf, US"<summary code=\""))
+ {
+ malware_name = Ustrstr(buf, US"<summary code=\"11\">")
+ ? malware_name_internal : NULL;
+ break;
+ }
+ }
+ if (len < -1)
+ {
+ (void)close(sock);
+ return DEFER;
+ }
+ break;
+ } /* f-protd */
+
+ 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;
+ const pcre *drweb_re;
+
+ /* prepare variables */
+ drweb_cmd = htonl(DRWEBD_SCAN_CMD);
+ drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
+
+ if (*scanner_options != '/')
+ {
+ /* calc file size */
+ if ((drweb_fd = open(CCS eml_filename, O_RDONLY)) == -1)
+ return m_errlog_defer_3(scanent,
+ string_sprintf("can't open spool file %s: %s",
+ eml_filename, strerror(errno)),
+ sock);
+
+ 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)
+ {
+ (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);
+
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s]\n",
+ scanner_name, scanner_options);
+
+ /* send scan request */
+ if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+ (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+ (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
+ (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+ {
+ (void)close(drweb_fd);
+ 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)))
+ {
+ (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)
+ {
+ int err = errno;
+ (void)close(drweb_fd);
+ free(drweb_fbuf);
+ return m_errlog_defer_3(scanent,
+ 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)
+ {
+ free(drweb_fbuf);
+ return m_errlog_defer_3(scanent, string_sprintf(
+ "unable to send file body to socket (%s)", scanner_options),
+ sock);
+ }
+ (void)close(drweb_fd);
+ }
+ else
+ {
+ drweb_slen = htonl(Ustrlen(eml_filename));
+
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
+ scanner_name, scanner_options);
+
+ /* send scan request */
+ if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+ (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+ (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
+ (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
+ (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
+ return m_errlog_defer_3(scanent, string_sprintf(
+ "unable to send commands to socket (%s)", scanner_options),
+ sock);
+ }
+
+ /* wait for result */
+ 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 (!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)
+ {
+ int i;
+
+ /* setup default virus name */
+ malware_name = US"unknown";
+
+ /* set up match regex */
+ 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++)
+ {
+ int size = 0, off = 0, ovector[10*3];
+ /* read the size of report */
+ 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 (!recv_len(sock, tmpbuf, drweb_slen, tmo))
+ return m_errlog_defer_3(scanent,
+ US"cannot read report string", sock);
+ tmpbuf[drweb_slen] = '\0';
+
+ /* try matcher on the line, grab substring */
+ result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
+ ovector, nelements(ovector));
+ if (result >= 2)
+ {
+ const char * pre_malware_nb;
+
+ pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
+
+ if (i==0) /* the first name we just copy to malware_name */
+ malware_name = string_append(NULL, &size, &off,
+ 1, pre_malware_nb);
+
+ else /* concatenate each new virus name to previous */
+ malware_name = string_append(malware_name, &size, &off,
+ 2, "/", pre_malware_nb);
+
+ pcre_free_substring(pre_malware_nb);
+ }
+ }
+ }
+ else
+ {
+ const char *drweb_s = NULL;
+
+ if (drweb_rc & DERR_READ_ERR) drweb_s = "read error";
+ if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory";
+ if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout";
+ if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command";
+ /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED.
+ * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM,
+ * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR
+ * and others are ignored */
+ if (drweb_s)
+ return m_errlog_defer_3(scanent,
+ string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
+ sock);
+
+ /* no virus found */
+ malware_name = NULL;
+ }
+ break;
+ } /* drweb */
+
+ case M_AVES: /* "aveserver" scanner type -------------------------------- */
+ {
+ uschar buf[32768];
+ int result;