{
const uschar * s;
uschar * t;
-uschar buf[64];
+uschar buf[EXIM_DRIVERNAME_MAX];
spf(buf, sizeof(buf), US"_HAVE_MALWARE_");
static const pcre * malware_default_re = NULL;
-
#ifndef DISABLE_MAL_CLAM
/* The maximum number of clamd servers that are supported in the configuration */
# define MAX_CLAMD_SERVERS 32
/* Some (currently avast only) use backslash escaped whitespace,
this function undoes these escapes */
+#ifndef DISABLE_MAL_AVAST
static inline void
unescape(uschar *p)
{
if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
for (p0 = p; *p0; ++p0) *p0 = p0[1];
}
+#endif
/* --- malware_*_defer --- */
static inline int
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);
}
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
return cre;
}
+
/*
Simple though inefficient wrapper for reading a line. Drop CRs and the
trailing newline. Can return early on buffer full. Null-terminate.
}
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';
}
/* return TRUE iff size as requested */
+#ifndef DISABLE_MAL_DRWEB
static BOOL
recv_len(int sock, void * buf, int size, time_t tmo)
{
? recv(sock, buf, size, 0) == size
: FALSE;
}
+#endif
uschar av_buffer[1024];
uschar *hostname = US"";
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];
{ cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; }
else
{
- cmd_str.data = string_sprintf("SCAN %s\n", eml_filename);
- cmd_str.len = Ustrlen(cmd_str.data);
+ int n;
+ cmd_str.data = string_sprintf("SCAN %s\n%n", eml_filename, &n);
+ cmd_str.len = n; /* .len is a size_t */
}
/* We have some network servers specified */
if (num_servers)
{
/* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd
- * only supports AF_INET, but we should probably be looking to the
- * future and rewriting this to be protocol-independent anyway. */
+ only supports AF_INET, but we should probably be looking to the
+ future and rewriting this to be protocol-independent anyway. */
while (num_servers > 0)
{
cd->hostspec, cd->tcp_port);
/* Lookup the host. This is to ensure that we connect to the same IP
- * on both connections (as one host could resolve to multiple ips) */
+ on both connections (as one host could resolve to multiple ips) */
for (;;)
{
- /*XXX we trust that the cmd_str is ideempotent */
+ /*XXX we trust that the cmd_str is idempotent */
if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
- &connhost, &errstr, &cmd_str)) >= 0)
+ &connhost, &errstr,
+ use_scan_command ? &cmd_str : NULL)) >= 0)
{
/* Connection successfully established with a server */
hostname = cd->hostspec;
- cmd_str.len = 0;
+ if (use_scan_command) cmd_str.len = 0;
break;
}
if (cd->retry <= 0) break;
}
/* have socket in variable "sock"; command to use is semi-independent of
- * the socket protocol. We use SCAN if is local (either Unix/local
- * domain socket, or explicitly told local) else we stream the data.
- * How we stream the data depends upon how we were built. */
+ the socket protocol. We use SCAN if is local (either Unix/local
+ domain socket, or explicitly told local) else we stream the data.
+ How we stream the data depends upon how we were built. */
if (!use_scan_command)
{
+ struct stat st;
+#if defined(EXIM_TCP_CORK) && !defined(OS_SENDFILE)
+ BOOL corked = TRUE;
+#endif
/* 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. */
+ chunk. We only send one chunk. */
DEBUG(D_acl) debug_printf_indent(
"Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
scanner_name);
+#if defined(EXIM_TCP_CORK)
+ (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK,
+ US &on, sizeof(on));
+#endif
/* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
if (cmd_str.len)
if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
strerror(errno)),
malware_daemon_ctx.sock);
- /* calc file size */
if ((clam_fd = exim_open2(CS eml_filename, O_RDONLY)) < 0)
{
int 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 size */
+ 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)
+ /* send file body */
+ 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);
+#ifdef OS_SENDFILE
+ int n = os_sendfile(malware_daemon_ctx.sock, clam_fd, NULL, (size_t)fsize_uint);
+ if (n < 0)
+ return m_panic_defer_3(scanent, NULL,
+ string_sprintf("unable to send file body to socket (%s): %s", hostname, strerror(errno)),
+ malware_daemon_ctx.sock);
+ fsize_uint -= n;
+#else
+ int 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 (send(malware_daemon_ctx.sock, big_buffer, (size_t)n, 0) < 0)
+ return m_panic_defer_3(scanent, NULL,
+ string_sprintf("unable to send file body to socket (%s): %s", hostname, strerror(errno)),
+ malware_daemon_ctx.sock);
+ fsize_uint -= n;
+# ifdef EXIM_TCP_CORK
+ if (corked)
+ {
+ corked = FALSE;
+ (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK,
+ US &off, sizeof(off));
+ }
+# endif
+#endif /*!OS_SENDFILE*/
+
}
- (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);
+#ifdef OS_SENDFILE
+ (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK,
+ US &off, sizeof(off));
+#endif
}
else
{ /* use scan command */
/* Send a SCAN command pointing to a filename; then in the then in the
- * scan-method-neutral part, read the response back */
+ scan-method-neutral part, read the response back */
/* ================================================================= */
malware_daemon_ctx.sock);
/* Do not shut down the socket for writing; a user report noted that
- * clamd 0.70 does not react well to this. */
+ clamd 0.70 does not react well to this. */
}
/* Commands have been sent, no matter which scan method or connection
- * type we're using; now just read the result, independent of method. */
+ type we're using; now just read the result, independent of method. */
/* Read the result */
memset(av_buffer, 0, sizeof(av_buffer));
/* strip newline at the end (won't be present for zINSTREAM)
(also any trailing whitespace, which shouldn't exist, but we depend upon
this below, so double-check) */
+
p = av_buffer + Ustrlen(av_buffer) - 1;
if (*p == '\n') *p = '\0';
if (*p) ++p;
/* colon in returned output? */
- if(!(p = Ustrchr(av_buffer,':')))
+ if (!(p = Ustrchr(av_buffer,':')))
return m_panic_defer(scanent, CUS callout_address, string_sprintf(
"ClamAV returned malformed result (missing colon): %s",
av_buffer));