TFO: on FreeBSD avoid client TFO-mode connects unless a TCP_FASTOPEN setsocketopt...
[exim.git] / src / src / malware.c
index f9c4c414f71633c5af3c8bc21cca3b4a33f55a23..e39cea8148f095adfcedb112be9f55a4639cf4e3 100644 (file)
@@ -104,7 +104,7 @@ static inline int
 test_byte_order()
 {
   short int word = 0x0001;
-  char *byte = (char *) &word;
+  char *byte = CS  &word;
   return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
 }
 
@@ -147,9 +147,10 @@ uses the returned in_addr to get a second connection to the same system.
 */
 static inline int
 m_tcpsocket(const uschar * hostname, unsigned int port,
-       host_item * host, uschar ** errstr)
+       host_item * host, uschar ** errstr, const blob * fastopen_blob)
 {
-return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr);
+return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
+                         host, errstr, fastopen_blob);
 }
 
 static int
@@ -202,7 +203,11 @@ const pcre * cre = NULL;
 if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
   *errstr = US listerr;
 else
+  {
+  DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ",
+    string_printing(list_ele));
   cre = m_pcre_compile(CUS list_ele, errstr);
+  }
 return cre;
 }
 
@@ -413,16 +418,16 @@ 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
+  scan_filename  the file holding the email to be scanned, if we're faking
+               this up for the -bmalware test, else NULL
   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)
+malware_internal(const uschar * malware_re, const uschar * scan_filename,
+  int timeout)
 {
 int sep = 0;
 const uschar *av_scanner_work = av_scanner;
@@ -435,21 +440,24 @@ struct scan * scanent;
 const uschar * scanner_options;
 int sock = -1;
 time_t tmo;
+uschar * eml_filename, * eml_dir;
+
+if (!malware_re)
+  return FAIL;         /* empty means "don't match anything" */
 
-/* make sure the eml mbox file is spooled up */
-if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL)))
+/* Ensure the eml mbox file is spooled up */
+
+if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename)))
   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);
+/* None of our current scanners need the mbox file as a stream (they use
+the name), so we can close it right away.  Get the directory too. */
 
-if (!malware_re)
-  return FAIL;         /* empty means "don't match anything" */
+(void) fclose(mbox_file);
+eml_dir = string_copyn(eml_filename, Ustrrchr(eml_filename, '/') - eml_filename);
 
 /* parse 1st option */
-  if ( (strcmpic(malware_re, US"false") == 0) ||
-     (Ustrcmp(malware_re,"0") == 0) )
+if (strcmpic(malware_re, US"false") == 0  ||  Ustrcmp(malware_re,"0") == 0)
   return FAIL;         /* explicitly no matching */
 
 /* special cases (match anything except empty) */
@@ -469,9 +477,6 @@ if (  strcmpic(malware_re,US"true") == 0
 else if (!(re = m_pcre_compile(malware_re, &errstr)))
   return malware_errlog_defer(errstr);
 
-/* Reset sep that is set by previous string_nextinlist() call */
-sep = 0;
-
 /* if av_scanner starts with a dollar, expand it first */
 if (*av_scanner == '$')
   {
@@ -503,10 +508,15 @@ if (!malware_ok)
        scanner_name));
     if (strcmpic(scanner_name, US scanent->name) != 0)
       continue;
+    DEBUG(D_acl) debug_printf_indent("Malware scan:  %s tmo=%s\n",
+      scanner_name, readconf_printtime(timeout));
+
     if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
       scanner_options = scanent->options_default;
     if (scanent->conn == MC_NONE)
       break;
+
+    DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
     switch(scanent->conn)
     {
     case MC_TCP:  sock = ip_tcpsocket(scanner_options, &errstr, 5);    break;
@@ -518,7 +528,6 @@ if (!malware_ok)
       return m_errlog_defer(scanent, CUS callout_address, errstr);
     break;
   }
-  DEBUG(D_acl) debug_printf_indent("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout));
 
   switch (scanent->scancode)
     {
@@ -602,7 +611,8 @@ if (!malware_ok)
 
        if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
          {
-         int err = errno;
+         int err;
+badseek:  err = errno;
          (void)close(drweb_fd);
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
@@ -619,7 +629,8 @@ if (!malware_ok)
            sock);
          }
        drweb_slen = htonl(fsize);
-       lseek(drweb_fd, 0, SEEK_SET);
+       if (lseek(drweb_fd, 0, SEEK_SET) < 0)
+         goto badseek;
 
        DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s remote scan [%s]\n",
            scanner_name, scanner_options);
@@ -780,7 +791,7 @@ if (!malware_ok)
       if (buf[0] != '2')               /* aveserver is having problems */
        return m_errlog_defer_3(scanent, CUS callout_address,
          string_sprintf("unavailable (Responded: %s).",
-                         ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
+                         ((buf[0] != 0) ? buf : US "nothing") ),
          sock);
 
       /* prepare our command */
@@ -825,7 +836,7 @@ if (!malware_ok)
       if (buf[0] != '2')               /* aveserver is having problems */
        return m_errlog_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to quit dialogue (Responded: %s).",
-                       ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
+                       ((buf[0] != 0) ? buf : US "nothing") ),
          sock);
 
       if (result == DEFER)
@@ -1091,8 +1102,7 @@ if (!malware_ok)
        }
       scanner_fd = fileno(scanner_out);
 
-      file_name = string_sprintf("%s/scan/%s/%s_scanner_output",
-                               spool_directory, message_id, message_id);
+      file_name = string_sprintf("%s/%s_scanner_output", eml_dir, message_id);
 
       if (!(scanner_record = modefopen(file_name, "wb", SPOOL_MODE)))
        {
@@ -1245,6 +1255,7 @@ if (!malware_ok)
 #else
       uint32_t send_size, send_final_zeroblock;
 #endif
+      blob cmd_str;
 
       /*XXX if unixdomain socket, only one server supported. Needs fixing;
       there's no reason we should not mix local and remote servers */
@@ -1340,6 +1351,19 @@ if (!malware_ok)
          string_sprintf("local/SCAN mode incompatible with" \
            " : in path to email filename [%s]", eml_filename));
 
+      /* 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);
+       cmd_str.len = Ustrlen(cmd_str.data);
+       }
+
       /* We have some network servers specified */
       if (num_servers)
        {
@@ -1349,7 +1373,7 @@ if (!malware_ok)
 
        while (num_servers > 0)
          {
-         int i = random_number( num_servers );
+         int i = random_number(num_servers);
          clamd_address * cd = cv[i];
 
          DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n",
@@ -1359,11 +1383,12 @@ if (!malware_ok)
           * on both connections (as one host could resolve to multiple ips) */
          for (;;)
            {
-           sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr);
-           if (sock >= 0)
+           if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
+                                   &connhost, &errstr, &cmd_str)) >= 0)
              {
              /* Connection successfully established with a server */
              hostname = cd->hostspec;
+             cmd_str.len = 0;
              break;
              }
            if (cd->retry <= 0) break;
@@ -1412,9 +1437,10 @@ if (!malware_ok)
            "Malware scan: issuing %s old-style remote scan (PORT)\n",
            scanner_name);
 
-       /* Pass the string to ClamAV (7 = "STREAM\n") */
-       if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0)
-         return m_errlog_defer(scanent, CUS callout_address, errstr);
+       /* 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));
@@ -1434,13 +1460,13 @@ if (!malware_ok)
                  "ClamAV returned null", sock);
 
        av_buffer2[bread] = '\0';
-       if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 )
+       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);
+       sockData = m_tcpsocket(connhost.address, port, NULL, &errstr, NULL);
        if (sockData < 0)
          return m_errlog_defer_3(scanent, CUS callout_address, errstr, sock);
 
@@ -1454,12 +1480,13 @@ if (!malware_ok)
            "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
            scanner_name);
 
-       /* Pass the string to ClamAV (10 = "zINSTREAM\0") */
-       if (send(sock, "zINSTREAM", 10, 0) < 0)
-         return m_errlog_defer_3(scanent, CUS hostname,
-           string_sprintf("unable to send zINSTREAM to socket (%s)",
-             strerror(errno)),
-           sock);
+       /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
+       if (cmd_str.len)
+         if (send(sock, cmd_str.data, cmd_str.len, 0) < 0)
+           return m_errlog_defer_3(scanent, CUS hostname,
+             string_sprintf("unable to send zINSTREAM to socket (%s)",
+               strerror(errno)),
+             sock);
 
 # define CLOSE_SOCKDATA /**/
 #endif
@@ -1476,7 +1503,8 @@ if (!malware_ok)
          }
        if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
          {
-         int err = errno;
+         int err;
+b_seek:   err = errno;
          CLOSE_SOCKDATA; (void)close(clam_fd);
          return m_errlog_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
@@ -1492,7 +1520,8 @@ if (!malware_ok)
              eml_filename),
            sock);
          }
-       lseek(clam_fd, 0, SEEK_SET);
+       if (lseek(clam_fd, 0, SEEK_SET) < 0)
+         goto b_seek;
 
        if (!(clamav_fbuf = US malloc(fsize_uint)))
          {
@@ -1558,17 +1587,17 @@ if (!malware_ok)
        scanned twice, in the broken out files and from the original .eml.
        Since ClamAV now handles emails (and has for quite some time) we can
        just use the email file itself. */
-       /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
-       file_name = string_sprintf("SCAN %s\n", eml_filename);
+       /* Pass the string to ClamAV (7 = "SCAN \n" + \0), if not already sent */
 
        DEBUG(D_acl) debug_printf_indent(
            "Malware scan: issuing %s local-path scan [%s]\n",
            scanner_name, scanner_options);
 
-       if (send(sock, file_name, Ustrlen(file_name), 0) < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address,
-           string_sprintf("unable to write to socket (%s)", strerror(errno)),
-           sock);
+       if (cmd_str.len)
+         if (send(sock, cmd_str.data, cmd_str.len, 0) < 0)
+           return m_errlog_defer_3(scanent, CUS callout_address,
+             string_sprintf("unable to write to socket (%s)", strerror(errno)),
+             sock);
 
        /* Do not shut down the socket for writing; a user report noted that
         * clamd 0.70 does not react well to this. */
@@ -1695,8 +1724,10 @@ if (!malware_ok)
       const pcre *sockline_name_re;
 
       /* find scanner command line */
-      if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
-                                         NULL, 0)))
+      if (  (sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
+                                         NULL, 0))
+        && *sockline_scanner
+        )
       {        /* check for no expansions apart from one %s */
        uschar * s = Ustrchr(sockline_scanner, '%');
        if (s++)
@@ -1706,6 +1737,8 @@ if (!malware_ok)
       }
       else
        sockline_scanner = sockline_scanner_default;
+      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "cmdline: ",
+       string_printing(sockline_scanner));
 
       /* find scanner output trigger */
       sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
@@ -1720,9 +1753,9 @@ if (!malware_ok)
        return m_errlog_defer_3(scanent, NULL, errstr, sock);
 
       /* prepare scanner call - security depends on expansions check above */
-      commandline = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
-      commandline = string_sprintf( CS sockline_scanner, CS commandline);
-
+      commandline = string_sprintf( CS sockline_scanner, CS eml_filename);
+      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "expanded: ",
+       string_printing(commandline));
 
       /* Pass the command string to the socket */
       if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0)
@@ -1741,12 +1774,16 @@ if (!malware_ok)
                US"buffer too small", sock);
       av_buffer[bread] = '\0';
       linebuffer = string_copy(av_buffer);
+      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ",
+       string_printing(linebuffer));
 
       /* try trigger match */
       if (regex_match_and_setup(sockline_trig_re, linebuffer, 0, -1))
        {
        if (!(malware_name = m_pcre_exec(sockline_name_re, av_buffer)))
          malware_name = US "unknown";
+       DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "name: ",
+         string_printing(malware_name));
        }
       else /* no virus found */
        malware_name = NULL;
@@ -1844,8 +1881,7 @@ if (!malware_ok)
                }
              else
                {
-               scanrequest = string_sprintf("SCAN %s/scan/%s\n",
-                   spool_directory, message_id);
+               scanrequest = string_sprintf("SCAN %s\n", eml_dir);
                avast_stage = AVA_RSP;          /* just sent command */
                }
 
@@ -2000,14 +2036,9 @@ Returns:      Exim message processing code (OK, FAIL, DEFER, ...)
 int
 malware(const uschar * malware_re, int timeout)
 {
-uschar * scan_filename;
-int ret;
+int ret = malware_internal(malware_re, NULL, timeout);
 
-scan_filename = string_sprintf("%s/scan/%s/%s.eml",
-                 spool_directory, message_id, message_id);
-ret = malware_internal(malware_re, scan_filename, timeout, FALSE);
 if (ret == DEFER) av_failed = TRUE;
-
 return ret;
 }
 
@@ -2045,7 +2076,7 @@ recipients_list = NULL;
 receive_add_recipient(US"malware-victim@example.net", -1);
 enable_dollar_recipients = TRUE;
 
-ret = malware_internal(US"*", eml_filename, 0,  TRUE);
+ret = malware_internal(US"*", eml_filename, 0);
 
 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
 spool_mbox_ok = 1;