malware: avoid slurping entire spoolfile for sending to ClamAV
[exim.git] / src / src / malware.c
index 2e783e398eb9784ae58caddf4c0708a6a9804077..2883f225af4b7c526940c530ad65583f4a790ebc 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);
 }
@@ -302,8 +298,7 @@ const uschar * rerror;
 int roffset;
 const pcre * cre;
 
-cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
-if (!cre)
+if (!(cre = pcre_compile(CS re, PCRE_COPT, CCSS &rerror, &roffset, NULL)))
   *errstr= string_sprintf("regular expression error in '%s': %s at offset %d",
       re, rerror, roffset);
 return cre;
@@ -317,7 +312,7 @@ int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0,
              ovector, nelem(ovector));
 uschar * substr = NULL;
 if (i >= 2)                            /* Got it */
-  pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr);
+  pcre_get_substring(CS text, ovector, i, 1, CCSS &substr);
 return substr;
 }
 
@@ -339,6 +334,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.
@@ -349,13 +345,13 @@ return cre;
          -2 on timeout or error
 */
 static int
-recv_line(int fd, uschar * buffer, int bsize, int tmo)
+recv_line(int fd, uschar * buffer, int bsize, time_t tmo)
 {
 uschar * p = buffer;
 ssize_t rcv;
 BOOL ok = FALSE;
 
-if (!fd_ready(fd, tmo-time(NULL)))
+if (!fd_ready(fd, tmo))
   return -2;
 
 /*XXX tmo handling assumes we always get a whole line */
@@ -370,8 +366,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';
@@ -381,13 +381,15 @@ return p - buffer;
 }
 
 /* return TRUE iff size as requested */
+#ifndef DISABLE_MAL_DRWEB
 static BOOL
-recv_len(int sock, void * buf, int size, int tmo)
+recv_len(int sock, void * buf, int size, time_t tmo)
 {
-return fd_ready(sock, tmo-time(NULL))
+return fd_ready(sock, tmo)
   ? recv(sock, buf, size, 0) == size
   : FALSE;
 }
+#endif
 
 
 
@@ -430,7 +432,7 @@ for (;;)
 }
 
 static inline int
-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, time_t tmo)
 {
 client_conn_ctx cctx = {.sock = sock};
 int offset = 0;
@@ -438,7 +440,7 @@ int i;
 
 do
   {
-  i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+  i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo);
   if (i <= 0)
     {
     (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
@@ -497,7 +499,7 @@ switch (*line)
 
 static int
 mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename,
-  int tmo)
+  time_t tmo)
 {
 struct iovec iov[3];
 const char *cmd = "MSQ\n";
@@ -655,11 +657,11 @@ if (!malware_ok)
     switch(scanent->conn)
     {
     case MC_TCP:
-      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5);     break;
+      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5, NULL); break;
     case MC_UNIX:
       malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr);       break;
     case MC_STRM:
-      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
+      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5, NULL); break;
     default:
       /* compiler quietening */ break;
     }
@@ -746,7 +748,7 @@ if (!malware_ok)
       if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
         return m_panic_defer(scanent, CUS callout_address, errstr);
 
-      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
 
       if (bread <= 0)
         return m_panic_defer_3(scanent, CUS callout_address,
@@ -792,7 +794,7 @@ if (!malware_ok)
       if (*scanner_options != '/')
        {
        /* calc file size */
-       if ((drweb_fd = open(CCS eml_filename, O_RDONLY)) == -1)
+       if ((drweb_fd = exim_open2(CCS eml_filename, O_RDONLY)) == -1)
          return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't open spool file %s: %s",
              eml_filename, strerror(errno)),
@@ -836,7 +838,7 @@ badseek:  err = errno;
            malware_daemon_ctx.sock);
          }
 
-       if (!(drweb_fbuf = US malloc(fsize_uint)))
+       if (!(drweb_fbuf = store_malloc(fsize_uint)))
          {
          (void)close(drweb_fd);
          return m_panic_defer_3(scanent, NULL,
@@ -849,7 +851,7 @@ badseek:  err = errno;
          {
          int err = errno;
          (void)close(drweb_fd);
-         free(drweb_fbuf);
+         store_free(drweb_fbuf);
          return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't read spool file %s: %s",
              eml_filename, strerror(err)),
@@ -860,11 +862,12 @@ badseek:  err = errno;
        /* send file body to socket */
        if (send(malware_daemon_ctx.sock, drweb_fbuf, fsize, 0) < 0)
          {
-         free(drweb_fbuf);
+         store_free(drweb_fbuf);
          return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send file body to socket (%s)", scanner_options),
            malware_daemon_ctx.sock);
          }
+       store_free(drweb_fbuf);
        }
       else
        {
@@ -917,7 +920,9 @@ badseek:  err = errno;
            return m_panic_defer_3(scanent, CUS callout_address,
                              US"cannot read report size", malware_daemon_ctx.sock);
          drweb_slen = ntohl(drweb_slen);
-         tmpbuf = store_get(drweb_slen);
+
+         /* assume tainted, since it is external input */
+         tmpbuf = store_get(drweb_slen, TRUE);
 
          /* read report body */
          if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo))
@@ -1063,7 +1068,7 @@ badseek:  err = errno;
        if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
          return m_panic_defer(scanent, CUS callout_address, errstr);
 
-       bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+       bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
        if (bread > 0) av_buffer[bread]='\0';
        if (bread < 0)
          return m_panic_defer_3(scanent, CUS callout_address,
@@ -1095,7 +1100,7 @@ badseek:  err = errno;
          {
          errno = ETIMEDOUT;
          i =  av_buffer+sizeof(av_buffer)-p;
-         if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0)
+         if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo)) < 0)
            return m_panic_defer_3(scanent, CUS callout_address,
              string_sprintf("unable to read result (%s)", strerror(errno)),
              malware_daemon_ctx.sock);
@@ -1360,13 +1365,10 @@ badseek:  err = errno;
        malware_name = US"unknown";
 
        /* re-open the scanner output file, look for name match */
-       scanner_record = fopen(CS file_name, "rb");
-       while (fgets(CS linebuffer, sizeof(linebuffer), scanner_record))
-         {
-         /* try match */
-         if ((s = m_pcre_exec(cmdline_regex_re, linebuffer)))
+       scanner_record = Ufopen(file_name, "rb");
+       while (Ufgets(linebuffer, sizeof(linebuffer), scanner_record))
+         if ((s = m_pcre_exec(cmdline_regex_re, linebuffer))) /* try match */
            malware_name = s;
-         }
        (void)fclose(scanner_record);
        }
       else /* no virus found */
@@ -1400,7 +1402,7 @@ badseek:  err = errno;
 
       /* wait for result */
       memset(av_buffer, 0, sizeof(av_buffer));
-      if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
+      if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo)) <= 0)
        return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
          malware_daemon_ctx.sock);
@@ -1443,7 +1445,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];
@@ -1463,7 +1464,7 @@ badseek:  err = errno;
        /* Local file; so we def want to use_scan_command and don't want to try
         * passing IP/port combinations */
        use_scan_command = TRUE;
-       cd = (clamd_address *) store_get(sizeof(clamd_address));
+       cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE);
 
        /* extract socket-path part */
        sublist = scanner_options;
@@ -1497,7 +1498,7 @@ badseek:  err = errno;
            continue;
            }
 
-         cd = (clamd_address *) store_get(sizeof(clamd_address));
+         cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE);
 
          /* extract host and port part */
          sublist = scanner_options;
@@ -1619,6 +1620,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. */
@@ -1635,8 +1637,7 @@ badseek:  err = errno;
                strerror(errno)),
              malware_daemon_ctx.sock);
 
-       /* calc file size */
-       if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
+       if ((clam_fd = exim_open2(CS eml_filename, O_RDONLY)) < 0)
          {
          int err = errno;
          return m_panic_defer_3(scanent, NULL,
@@ -1644,62 +1645,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 = US 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;
-         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))
-         {
-         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);
-         }
-
-       free(clamav_fbuf);
        }
       else
        { /* use scan command */
@@ -1736,7 +1726,7 @@ b_seek:   err = errno;
 
       /* Read the result */
       memset(av_buffer, 0, sizeof(av_buffer));
-      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
       (void)close(malware_daemon_ctx.sock);
       malware_daemon_ctx.sock = -1;
       malware_daemon_ctx.tls_ctx = NULL;
@@ -1894,7 +1884,7 @@ b_seek:   err = errno;
        return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* Read the result */
-      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
 
       if (bread <= 0)
        return m_panic_defer_3(scanent, CUS callout_address,
@@ -2278,12 +2268,13 @@ if (!fprot6d_re_virus)
 }
 
 
-void
-malware_show_supported(FILE * f)
+gstring *
+malware_show_supported(gstring * g)
 {
-fprintf(f, "Malware:");
-for (struct scan * sc = m_scans; sc->scancode != (scanner_t)-1; sc++) fprintf(f, " %s", sc->name);
-fprintf(f, "\n");
+g = string_cat(g, US"Malware:");
+for (struct scan * sc = m_scans; sc->scancode != (scanner_t)-1; sc++)
+  g = string_fmt_append(g, " %s", sc->name);
+return string_cat(g, US"\n");
 }