/* 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. */
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_");
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));
* 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;
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;
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;
}
/* 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);
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. */
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)),
{
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)),
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),
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),
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)),
(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) ||
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 */
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
[+] - 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
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 */
{
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 */
}
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__);
}
}
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)