FreeBSD: harden against ClamAV connection errors
[exim.git] / src / src / malware.c
index afe9e00028d3903ea8e70614b29347e121f6fad6..dfa8e2b4b79b30e55c242a4e46e46bb6ebbfd605 100644 (file)
@@ -4,7 +4,7 @@
 
 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
- * Copyright (c) The Exim Maintainers 2015 - 2018
+ * Copyright (c) The Exim Maintainers 2015 - 2020
  */
 
 /* Code for calling virus (malware) scanners. Called from acl.c. */
@@ -220,6 +220,7 @@ extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
 /* Some (currently avast only) use backslash escaped whitespace,
 this function undoes these escapes */
 
+#ifndef DISABLE_MAL_AVAST
 static inline void
 unescape(uschar *p)
 {
@@ -228,6 +229,7 @@ for (; *p; ++p)
   if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
     for (p0 = p; *p0; ++p0) *p0 = p0[1];
 }
+#endif
 
 /* --- malware_*_defer --- */
 static inline int
@@ -250,18 +252,12 @@ m_panic_defer(struct scan * scanent, const uschar * hostport,
 return malware_panic_defer(string_sprintf("%s %s : %s",
   scanent->name, hostport ? hostport : CUS"", str));
 }
-static inline int
-m_log_defer(struct scan * scanent, const uschar * hostport,
-  const uschar * str)
-{
-return malware_log_defer(string_sprintf("%s %s : %s",
-  scanent->name, hostport ? hostport : CUS"", str));
-}
 /* --- m_*_defer_3 */
 static inline int
 m_panic_defer_3(struct scan * scanent, const uschar * hostport,
   const uschar * str, int fd_to_close)
 {
+DEBUG(D_acl) debug_print_socket(fd_to_close);
 (void) close(fd_to_close);
 return m_panic_defer(scanent, hostport, str);
 }
@@ -276,8 +272,19 @@ static inline int
 m_tcpsocket(const uschar * hostname, unsigned int port,
        host_item * host, uschar ** errstr, const blob * fastopen_blob)
 {
-return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
+int fd = ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
                          host, errstr, fastopen_blob);
+#ifdef EXIM_TFO_FREEBSD
+/* Under some fault conditions, FreeBSD 12.2 seen to send a (non-TFO) SYN
+and, getting no response, wait for a long time.  Impose a 5s max. */
+if (fd >= 0)
+  {
+  struct timeval tv = {.tv_sec = 5};
+  fd_set fds;
+  FD_ZERO(fds); FD_SET(fd, &fds); (void) select(fd+1, NULL, &fds, NULL, &tv);
+  }
+#endif
+return fd;
 }
 #endif
 
@@ -338,6 +345,7 @@ else
 return cre;
 }
 
+
 /*
  Simple though inefficient wrapper for reading a line.  Drop CRs and the
  trailing newline. Can return early on buffer full. Null-terminate.
@@ -369,8 +377,12 @@ while ((rcv = read(fd, p, 1)) > 0)
   }
 if (!ok)
   {
-  DEBUG(D_acl) debug_printf_indent("Malware scan: read %s (%s)\n",
+  DEBUG(D_acl)
+    {
+    debug_printf_indent("Malware scan: read %s (%s)\n",
                rcv==0 ? "EOF" : "error", strerror(errno));
+    debug_print_socket(fd);
+    }
   return rcv==0 ? -1 : -2;
   }
 *p = '\0';
@@ -380,6 +392,7 @@ return p - buffer;
 }
 
 /* return TRUE iff size as requested */
+#ifndef DISABLE_MAL_DRWEB
 static BOOL
 recv_len(int sock, void * buf, int size, time_t tmo)
 {
@@ -387,6 +400,7 @@ return fd_ready(sock, tmo)
   ? recv(sock, buf, size, 0) == size
   : FALSE;
 }
+#endif
 
 
 
@@ -1442,7 +1456,6 @@ badseek:  err = errno;
       host_item connhost;
       uschar *clamav_fbuf;
       int clam_fd, result;
-      off_t fsize;
       unsigned int fsize_uint;
       BOOL use_scan_command = FALSE;
       clamd_address * cv[MAX_CLAMD_SERVERS];
@@ -1618,6 +1631,7 @@ badseek:  err = errno;
 
       if (!use_scan_command)
        {
+       struct stat st;
        /* 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. */
@@ -1634,7 +1648,6 @@ badseek:  err = errno;
                strerror(errno)),
              malware_daemon_ctx.sock);
 
-       /* calc file size */
        if ((clam_fd = exim_open2(CS eml_filename, O_RDONLY)) < 0)
          {
          int err = errno;
@@ -1643,61 +1656,51 @@ badseek:  err = errno;
              eml_filename, strerror(err)),
            malware_daemon_ctx.sock);
          }
-       if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
+       if (fstat(clam_fd, &st) < 0)
          {
-         int err;
-b_seek:   err = errno;
+         int err = errno;
          (void)close(clam_fd);
          return m_panic_defer_3(scanent, NULL,
-           string_sprintf("can't seek spool file %s: %s",
+           string_sprintf("can't stat spool file %s: %s",
              eml_filename, strerror(err)),
            malware_daemon_ctx.sock);
          }
-       fsize_uint = (unsigned int) fsize;
-       if ((off_t)fsize_uint != fsize)
+       fsize_uint = (unsigned int) st.st_size;
+       if ((off_t)fsize_uint != st.st_size)
          {
          (void)close(clam_fd);
          return m_panic_defer_3(scanent, NULL,
-           string_sprintf("seeking spool file %s, size overflow",
-             eml_filename),
+           string_sprintf("stat spool file %s, size overflow", eml_filename),
            malware_daemon_ctx.sock);
          }
-       if (lseek(clam_fd, 0, SEEK_SET) < 0)
-         goto b_seek;
 
-       if (!(clamav_fbuf = store_malloc(fsize_uint)))
-         {
-         (void)close(clam_fd);
+       /* send file body to socket */
+       send_size = htonl(fsize_uint);
+       if (send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0)
          return m_panic_defer_3(scanent, NULL,
-           string_sprintf("unable to allocate memory %u for file (%s)",
-             fsize_uint, eml_filename),
+           string_sprintf("unable to send file size to socket (%s)", hostname),
            malware_daemon_ctx.sock);
-         }
 
-       if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
+       while (fsize_uint)
          {
-         int err = errno;
-         store_free(clamav_fbuf); (void)close(clam_fd);
-         return m_panic_defer_3(scanent, NULL,
-           string_sprintf("can't read spool file %s: %s",
-             eml_filename, strerror(err)),
-           malware_daemon_ctx.sock);
+         unsigned n = MIN(fsize_uint, big_buffer_size);
+         if ((n = read(clam_fd, big_buffer, n)) < 0)
+           return m_panic_defer_3(scanent, NULL,
+             string_sprintf("can't read spool file %s: %s",
+               eml_filename, strerror(errno)),
+             malware_daemon_ctx.sock);
+         if ((n = send(malware_daemon_ctx.sock, clamav_fbuf, n, 0)) < 0)
+           return m_panic_defer_3(scanent, NULL,
+             string_sprintf("unable to send file body to socket (%s)", hostname),
+             malware_daemon_ctx.sock);
+         fsize_uint -= n;
          }
-       (void)close(clam_fd);
 
-       /* send file body to socket */
-       send_size = htonl(fsize_uint);
        send_final_zeroblock = 0;
-       if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) ||
-           (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) ||
-           (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
-         {
-         store_free(clamav_fbuf);
+       if (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)
          return m_panic_defer_3(scanent, NULL,
-           string_sprintf("unable to send file body to socket (%s)", hostname),
+           string_sprintf("unable to send file terminator to socket (%s)", hostname),
            malware_daemon_ctx.sock);
-         }
-       store_free(clamav_fbuf);
        }
       else
        { /* use scan command */