+ 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;
+
+ 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))) {
+ 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);
+
+ /* 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 )
+ 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;
+ } /* 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;
+ unsigned int fsize;
+ uschar * tmpbuf, *drweb_fbuf;
+ int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd,
+ drweb_vnum, drweb_slen, drweb_fin = 0x0000;
+ unsigned long bread;
+ 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(CS 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);
+ }
+ 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))) {
+ (void)close(drweb_fd);
+ return m_errlog_defer_3(scanent,
+ string_sprintf("unable to allocate memory %u for file (%s)",
+ fsize, 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 ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc)))
+ 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)))
+ 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 ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen)))
+ 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)
+ 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;
+
+ /* read aveserver's greeting and see if it is ready (2xx greeting) */
+ recv_line(sock, buf, sizeof(buf));
+
+ if (buf[0] != '2') /* aveserver is having problems */
+ return m_errlog_defer_3(scanent,
+ string_sprintf("unavailable (Responded: %s).",
+ ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
+ sock);
+
+ /* prepare our command */
+ (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n",
+ eml_filename);
+
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name);
+
+ /* and send it */
+ if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
+ return m_errlog_defer(scanent, errstr);
+
+ malware_name = NULL;
+ result = 0;
+ /* read response lines, find malware name and final response */
+ while (recv_line(sock, buf, sizeof(buf)) > 0) {
+ debug_printf("aveserver: %s\n", buf);
+ if (buf[0] == '2')
+ break;
+ 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],' ');
+ *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));
+
+ if (buf[0] != '2') /* aveserver is having problems */
+ return m_errlog_defer_3(scanent,
+ string_sprintf("unable to quit dialogue (Responded: %s).",
+ ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
+ sock);
+
+ if (result == DEFER) {
+ (void)close(sock);
+ return DEFER;
+ }
+ break;
+ } /* aveserver */
+
+ case M_FSEC: /* "fsecure" scanner type ---------------------------------- */
+ {
+ int i, j, bread = 0;
+ uschar * file_name;
+ uschar av_buffer[1024];
+ const pcre * fs_inf;
+ static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n",
+ US"CONFIGURE\tTIMEOUT\t0\n",
+ US"CONFIGURE\tMAXARCH\t5\n",
+ US"CONFIGURE\tMIME\t1\n" };
+
+ malware_name = NULL;
+
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+ scanner_name, scanner_options);
+
+ /* pass options */
+ memset(av_buffer, 0, sizeof(av_buffer));
+ 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';
+ 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'))
+ av_buffer[j] ='@';
+ }
+
+ /* pass the mailfile to fsecure */
+ file_name = string_sprintf("SCAN\t%s\n", eml_filename);
+
+ if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
+ return m_errlog_defer(scanent, errstr);
+
+ /* set up match */
+ /* todo also SUSPICION\t */
+ fs_inf = m_pcre_compile(US"\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr);
+
+ /* read report, linewise */
+ do {
+ i = 0;
+ memset(av_buffer, 0, sizeof(av_buffer));
+ do {
+ if ((bread= ip_recv(sock, &av_buffer[i], 1, MALWARE_TIMEOUT)) < 0)
+ return m_errlog_defer_3(scanent,
+ string_sprintf("unable to read result (%s)", strerror(errno)),
+ sock);
+ } while (++i < sizeof(av_buffer)-1 && av_buffer[i-1] != '\n');
+ av_buffer[i-1] = '\0';
+
+ /* Really search for virus again? */
+ if (malware_name == NULL)
+ /* try matcher on the line, grab substring */
+ malware_name = m_pcre_exec(fs_inf, av_buffer);
+ }
+ while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL);
+ break;
+ } /* fsecure */
+
+ case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */
+ {
+ time_t t;
+ uschar tmpbuf[1024];
+ uschar * scanrequest;
+ int kav_rc;
+ unsigned long kav_reportlen, bread;
+ const pcre *kav_re;
+ uschar *p;
+
+ /* get current date and time, build scan request */
+ time(&t);
+ /* pdp note: before the eml_filename parameter, this scanned the
+ directory; not finding documentation, so we'll strip off the directory.
+ The side-effect is that the test framework scanning may end up in
+ scanning more than was requested, but for the normal interface, this is
+ fine. */
+
+ strftime(CS tmpbuf, sizeof(tmpbuf), "%d %b %H:%M:%S", localtime(&t));
+ scanrequest = string_sprintf("<0>%s:%s", CS tmpbuf, eml_filename);
+ p = Ustrrchr(scanrequest, '/');
+ if (p)
+ *p = '\0';
+
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+ scanner_name, scanner_options);
+
+ /* send scan request */
+ if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+ return m_errlog_defer(scanent, errstr);
+
+ /* wait for result */
+ if ((bread = recv(sock, tmpbuf, 2, 0) != 2))
+ return m_errlog_defer_3(scanent,
+ US"unable to read 2 bytes from socket.", sock);
+
+ /* get errorcode from one nibble */
+ kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F;
+ switch(kav_rc)
+ {
+ case 5: case 6: /* improper kavdaemon configuration */
+ return m_errlog_defer_3(scanent,
+ US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
+ sock);
+ case 1:
+ return m_errlog_defer_3(scanent,
+ US"reported 'scanning not completed' (code 1).", sock);
+ case 7:
+ return m_errlog_defer_3(scanent,
+ US"reported 'kavdaemon damaged' (code 7).", sock);
+ }
+
+ /* code 8 is not handled, since it is ambigous. It appears mostly on
+ bounces where part of a file has been cut off */
+
+ /* "virus found" return codes (2-4) */
+ if ((kav_rc > 1) && (kav_rc < 5)) {
+ int report_flag = 0;
+
+ /* setup default virus name */
+ malware_name = US"unknown";
+
+ report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ];
+
+ /* read the report, if available */
+ if( report_flag == 1 ) {
+ /* read report size */
+ if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4)
+ 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) {
+ /* set up match regex, depends on retcode */
+ kav_re = m_pcre_compile( kav_rc == 3
+ ? US"suspicion:\\s*(.+?)\\s*$"
+ : US"infected:\\s*(.+?)\\s*$",
+ &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';
+
+ /* try matcher on the line, grab substring */
+ if ((malware_name = m_pcre_exec(kav_re, tmpbuf)))
+ break;
+ }
+ }
+ }
+ }
+ else /* no virus found */
+ malware_name = NULL;
+
+ break;