DMARC: add results to generic authres string; remove $dmarc_ar_header
[exim.git] / src / src / malware.c
index fa1a7aabaa1f0da13309e4d6b5965b13cb4518c7..d24f09b88794035fd33c89ceed083de2e8022a81 100644 (file)
@@ -4,7 +4,7 @@
 
 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
- * Copyright (c) The Exim Maintainers 2017 - 2018
+ * Copyright (c) The Exim Maintainers 2015 - 2018
  */
 
 /* Code for calling virus (malware) scanners. Called from acl.c. */
@@ -70,8 +70,9 @@ static struct scan
 void
 features_malware(void)
 {
-struct scan * sc;
-uschar * s, * t;
+const struct scan * sc;
+const uschar * s;
+uschar * t;
 uschar buf[64];
 
 spf(buf, sizeof(buf), US"_HAVE_MALWARE_");
@@ -1276,7 +1277,7 @@ badseek:  err = errno;
       sep = pclose(scanner_out);
       signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
       if (sep != 0)
-         return m_errlog_defer(scanent, NULL, 
+         return m_errlog_defer(scanent, NULL,
              sep == -1
              ? string_sprintf("running scanner failed: %s", strerror(sep))
              : string_sprintf("scanner returned error code: %d", sep));
@@ -1361,8 +1362,7 @@ badseek:  err = errno;
 * The zINSTREAM command was introduced with ClamAV 0.95, which marked
 * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095
 * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that
-* the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless
-* WITH_OLD_CLAMAV_STREAM is defined.
+* the TCP-connected daemon is actually local; otherwise we use zINSTREAM
 * See Exim bug 926 for details.  */
 
       uschar *p, *vname, *result_tag;
@@ -1377,13 +1377,7 @@ badseek:  err = errno;
       BOOL use_scan_command = FALSE;
       clamd_address * cv[MAX_CLAMD_SERVERS];
       int num_servers = 0;
-#ifdef WITH_OLD_CLAMAV_STREAM
-      unsigned int port;
-      uschar av_buffer2[1024];
-      int sockData;
-#else
       uint32_t send_size, send_final_zeroblock;
-#endif
       blob cmd_str;
 
       /*XXX if unixdomain socket, only one server supported. Needs fixing;
@@ -1438,13 +1432,13 @@ badseek:  err = errno;
          sublist = scanner_options;
          if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
            {
-           (void) m_errlog_defer(scanent, NULL, 
+           (void) m_errlog_defer(scanent, NULL,
                      string_sprintf("missing address: '%s'", scanner_options));
            continue;
            }
          if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
            {
-           (void) m_errlog_defer(scanent, NULL, 
+           (void) m_errlog_defer(scanent, NULL,
                      string_sprintf("missing port: '%s'", scanner_options));
            continue;
            }
@@ -1482,11 +1476,7 @@ badseek:  err = errno;
 
       /* Set up the very first data we will be sending */
       if (!use_scan_command)
-#ifdef WITH_OLD_CLAMAV_STREAM
-       { cmd_str.data = US"STREAM\n"; cmd_str.len = 7; }
-#else
        { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; }
-#endif
       else
        {
        cmd_str.data = string_sprintf("SCAN %s\n", eml_filename);
@@ -1557,50 +1547,6 @@ badseek:  err = errno;
 
       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. */
-
-       DEBUG(D_acl) debug_printf_indent(
-           "Malware scan: issuing %s old-style remote scan (PORT)\n",
-           scanner_name);
-
-       /* Pass the string to ClamAV (7 = "STREAM\n"), if not already sent */
-       if (cmd_str.len)
-         if (m_sock_send(sock, cmd_str.data, cmd_str.len, &errstr) < 0)
-           return m_errlog_defer(scanent, CUS callout_address, errstr);
-
-       memset(av_buffer2, 0, sizeof(av_buffer2));
-       bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));
-
-       if (bread < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address,
-           string_sprintf("unable to read PORT from socket (%s)",
-               strerror(errno)),
-           sock);
-
-       if (bread == sizeof(av_buffer2))
-         return m_errlog_defer_3(scanent, CUS callout_address,
-                 "buffer too small", sock);
-
-       if (!(*av_buffer2))
-         return m_errlog_defer_3(scanent, CUS callout_address,
-                 "ClamAV returned null", sock);
-
-       av_buffer2[bread] = '\0';
-       if(sscanf(CS av_buffer2, "PORT %u\n", &port) != 1)
-         return m_errlog_defer_3(scanent, CUS callout_address,
-           string_sprintf("Expected port information from clamd, got '%s'",
-             av_buffer2),
-           sock);
-
-       sockData = m_tcpsocket(connhost.address, port, NULL, &errstr, NULL);
-       if (sockData < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address, errstr, sock);
-
-# 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. */
@@ -1617,14 +1563,10 @@ badseek:  err = errno;
                strerror(errno)),
              sock);
 
-# define CLOSE_SOCKDATA /**/
-#endif
-
        /* calc file size */
        if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
          {
          int err = errno;
-         CLOSE_SOCKDATA;
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("can't open spool file %s: %s",
              eml_filename, strerror(err)),
@@ -1634,7 +1576,7 @@ badseek:  err = errno;
          {
          int err;
 b_seek:   err = errno;
-         CLOSE_SOCKDATA; (void)close(clam_fd);
+         (void)close(clam_fd);
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
              eml_filename, strerror(err)),
@@ -1643,7 +1585,7 @@ b_seek:   err = errno;
        fsize_uint = (unsigned int) fsize;
        if ((off_t)fsize_uint != fsize)
          {
-         CLOSE_SOCKDATA; (void)close(clam_fd);
+         (void)close(clam_fd);
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("seeking spool file %s, size overflow",
              eml_filename),
@@ -1654,7 +1596,7 @@ b_seek:   err = errno;
 
        if (!(clamav_fbuf = US malloc(fsize_uint)))
          {
-         CLOSE_SOCKDATA; (void)close(clam_fd);
+         (void)close(clam_fd);
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("unable to allocate memory %u for file (%s)",
              fsize_uint, eml_filename),
@@ -1664,7 +1606,7 @@ b_seek:   err = errno;
        if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
          {
          int err = errno;
-         free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd);
+         free(clamav_fbuf); (void)close(clam_fd);
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("can't read spool file %s: %s",
              eml_filename, strerror(err)),
@@ -1673,16 +1615,6 @@ b_seek:   err = errno;
        (void)close(clam_fd);
 
        /* send file body to socket */
-#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, NULL,
-           string_sprintf("unable to send file body to socket (%s:%u)",
-             hostname, port),
-           sock);
-         }
-#else
        send_size = htonl(fsize_uint);
        send_final_zeroblock = 0;
        if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
@@ -1694,12 +1626,8 @@ b_seek:   err = errno;
            string_sprintf("unable to send file body to socket (%s)", hostname),
            sock);
          }
-#endif
 
        free(clamav_fbuf);
-
-       CLOSE_SOCKDATA;
-#undef CLOSE_SOCKDATA
        }
       else
        { /* use scan command */
@@ -1965,6 +1893,7 @@ b_seek:   err = errno;
       uschar * scanrequest;
       enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage;
       int nread;
+      int more_data;
 
       /* According to Martin Tuma @avast the protocol uses "escaped
       whitespace", that is, every embedded whitespace is backslash
@@ -1974,7 +1903,9 @@ b_seek:   err = errno;
       [+] - not infected
       [L] - infected
       [E] - some error occured
-      Such marker follows the first non-escaped TAB.  */
+      Such marker follows the first non-escaped TAB.  For more information
+      see avast-protocol(5)
+      */
       if (  (  !ava_re_clean
             && !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr)))
         || (  !ava_re_virus
@@ -1990,17 +1921,25 @@ b_seek:   err = errno;
        int slen = Ustrlen(buf);
        if (slen >= 1)
          {
-         DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf);
+
+          /* Multi line responses are bracketed between 210 … and nnn … */
+          if (Ustrncmp(buf, "210", 3) == 0)
+            {
+            more_data = 1;
+            continue;
+            }
+          else if (more_data && isdigit(buf[0])) more_data = 0;
+
          switch (avast_stage)
            {
            case AVA_HELO:
+              if (more_data) continue;
              if (Ustrncmp(buf, "220", 3) != 0)
                goto endloop;                   /* require a 220 */
              goto sendreq;
 
            case AVA_OPT:
-             if (Ustrncmp(buf, "210", 3) == 0)
-               break;                          /* ignore 210 responses */
+              if (more_data) continue;
              if (Ustrncmp(buf, "200", 3) != 0)
                goto endloop;                   /* require a 200 */
 
@@ -2013,11 +1952,13 @@ b_seek:   err = errno;
                {
                scanrequest = string_sprintf("%s\n", scanrequest);
                avast_stage = AVA_OPT;          /* just sent option */
+               DEBUG(D_acl) debug_printf_indent("send to avast OPTION: %s", scanrequest);
                }
              else
                {
                scanrequest = string_sprintf("SCAN %s\n", eml_dir);
                avast_stage = AVA_RSP;          /* just sent command */
+               DEBUG(D_acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir);
                }
 
              /* send config-cmd or scan-request to socket */
@@ -2033,41 +1974,42 @@ b_seek:   err = errno;
              }
 
            case AVA_RSP:
-             if (Ustrncmp(buf, "210", 3) == 0)
-               break;  /* ignore the "210 SCAN DATA" message */
+
+             if (Ustrncmp(buf, "200", 3) == 0)
+               { /* we're done finally */
+               if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */
+                 return m_errlog_defer_3(scanent, CUS callout_address,
+                         string_sprintf(
+                             "unable to send quit request to socket (%s): %s",
+                             scanner_options, strerror(errno)),
+                             sock);
+
+               avast_stage = AVA_DONE;
+                goto endloop;
+               }
+
+              if (malware_name) break;  /* found malware already, nothing to do anymore */
 
              if (pcre_exec(ava_re_clean, NULL, CS buf, slen,
                    0, 0, ovector, nelem(ovector)) > 0)
                break;
 
-             if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
+             if (malware_name = m_pcre_exec(ava_re_virus, buf))
                { /* remove backslash in front of [whitespace|backslash] */
                uschar * p, * p0;
                for (p = malware_name; *p; ++p)
                  if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
                    for (p0 = p; *p0; ++p0) *p0 = p0[1];
 
-               avast_stage = AVA_DONE;
-               goto endloop;
-               }
-
-             if (Ustrncmp(buf, "200 SCAN OK", 11) == 0)
-               { /* we're done finally */
-               if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */
-                 return m_errlog_defer_3(scanent, CUS callout_address,
-                         string_sprintf(
-                             "unable to send quit request to socket (%s): %s",
-                             scanner_options, strerror(errno)),
-                             sock);
-               malware_name = NULL;
-               avast_stage = AVA_DONE;
-               goto endloop;
+               DEBUG(D_acl)
+                 debug_printf_indent("unescaped malware name: '%s'\n", malware_name);
+               break;
                }
 
-             /* here for any unexpected response from the scanner */
+             /* here also for any unexpected response from the scanner */
              goto endloop;
 
-           case AVA_DONE:      log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
+           default:    log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
                            __FILE__, __LINE__, __FUNCTION__);
            }
          }
@@ -2204,13 +2146,13 @@ if (!kav_re_sus)
 if (!kav_re_inf)
   kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE);
 #endif
-#ifndef DISABLE_MAL_AVA
+#ifndef DISABLE_MAL_AVAST
 if (!ava_re_clean)
   ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE);
 if (!ava_re_virus)
   ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE);
 #endif
-#ifndef DISABLE_MAL_FPROT6D
+#ifndef DISABLE_MAL_FFROT6D
 if (!fprot6d_re_error)
   fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE);
 if (!fprot6d_re_virus)