summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2014-12-27 20:47:19 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2015-01-12 18:58:37 +0000
commit4e71661f10e9df59f0e7c31cc708f4fcc0c49089 (patch)
tree2916161589b147a596564fb386fde2acd7e59c66 /src
parent376e377e2de95486a752620f8e52d270cdab5c7c (diff)
Apply timeout consistently to all malware scanner types
Diffstat (limited to 'src')
-rw-r--r--src/src/functions.h5
-rw-r--r--src/src/ip.c60
-rw-r--r--src/src/malware.c509
3 files changed, 340 insertions, 234 deletions
diff --git a/src/src/functions.h b/src/src/functions.h
index 347672579..6d10df4e3 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -172,6 +172,8 @@ extern uschar *expand_string_copy(uschar *);
extern int_eximarith_t expand_string_integer(uschar *, BOOL);
extern void modify_variable(uschar *, void *);
+extern BOOL fd_ready(int, int);
+
extern int filter_interpret(uschar *, int, address_item **, uschar **);
extern BOOL filter_personal(string_item *, BOOL);
extern BOOL filter_runtest(int, uschar *, BOOL, BOOL);
@@ -275,9 +277,6 @@ extern void queue_count(void);
extern void queue_run(uschar *, uschar *, BOOL);
extern int random_number(int);
-#ifdef WITH_CONTENT_SCAN
-extern int recv_line(int, uschar *, int);
-#endif
extern int rda_interpret(redirect_block *, int, uschar *, uschar *,
uschar *, uschar *, uschar *, ugid_block *, address_item **,
uschar **, error_block **, int *, uschar *);
diff --git a/src/src/ip.c b/src/src/ip.c
index ec034abc4..a50e209a2 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -384,39 +384,37 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
* Receive from a socket with timeout *
*************************************************/
-/* The timeout is implemented using select(), and we loop to cover select()
-getting interrupted, and the possibility of select() returning with a positive
-result but no ready descriptor. Is this in fact possible?
-
+/*
Arguments:
- sock the socket
- buffer to read into
- bufsize the buffer size
- timeout the timeout
-
-Returns: > 0 => that much data read
- <= 0 on error or EOF; errno set - zero for EOF
+ fd the file descriptor
+ timeout the timeout, seconds
+Returns: TRUE => ready for i/o
+ FALSE => timed out, or other error
*/
-
-int
-ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+BOOL
+fd_ready(int fd, int timeout)
{
fd_set select_inset;
struct timeval tv;
time_t start_recv = time(NULL);
int rc;
+if (timeout <= 0)
+ {
+ errno = ETIMEDOUT;
+ return FALSE;
+ }
/* Wait until the socket is ready */
for (;;)
{
FD_ZERO (&select_inset);
- FD_SET (sock, &select_inset);
+ FD_SET (fd, &select_inset);
tv.tv_sec = timeout;
tv.tv_usec = 0;
- DEBUG(D_transport) debug_printf("waiting for data on socket\n");
- rc = select(sock + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
+ DEBUG(D_transport) debug_printf("waiting for data on fd\n");
+ rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
/* If some interrupt arrived, just retry. We presume this to be rare,
but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
@@ -440,13 +438,37 @@ for (;;)
if (rc <= 0)
{
errno = ETIMEDOUT;
- return -1;
+ return FALSE;
}
/* If the socket is ready, break out of the loop. */
- if (FD_ISSET(sock, &select_inset)) break;
+ if (FD_ISSET(fd, &select_inset)) break;
}
+return TRUE;
+}
+
+/* The timeout is implemented using select(), and we loop to cover select()
+getting interrupted, and the possibility of select() returning with a positive
+result but no ready descriptor. Is this in fact possible?
+
+Arguments:
+ sock the socket
+ buffer to read into
+ bufsize the buffer size
+ timeout the timeout
+
+Returns: > 0 => that much data read
+ <= 0 on error or EOF; errno set - zero for EOF
+*/
+
+int
+ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+{
+int rc;
+
+if (!fd_ready(sock, timeout))
+ return -1;
/* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
close down of the connection), set errno to zero; otherwise leave it alone. */
diff --git a/src/src/malware.c b/src/src/malware.c
index 1a3dc7f9b..30586feda 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -48,7 +48,7 @@ typedef struct clamd_address_container {
} clamd_address_container;
/* declaration of private routines */
-static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename);
+static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename, int tmo);
static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking);
#ifndef nelements
@@ -305,6 +305,46 @@ m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr)
return cre;
}
+/*
+ Simple though inefficient wrapper for reading a line. Drop CRs and the
+ trailing newline. Can return early on buffer full. Null-terminate.
+ Apply initial timeout if no data ready.
+
+ Return: number of chars - zero for an empty line or EOF
+*/
+static int
+recv_line(int fd, uschar * buffer, int bsize, int tmo)
+{
+uschar * p = buffer;
+ssize_t rcv;
+
+if (!fd_ready(fd, tmo-time(NULL)))
+ return -1;
+
+ /*XXX tmo handling assumes we always get a whole line */
+/* read until \n */
+while ((rcv = read(fd, p, 1)) > 0)
+ {
+ if (p-buffer > bsize-2) break;
+ if (*p == '\n') break;
+ if (*p != '\r') p++;
+ }
+*p = '\0';
+
+DEBUG(D_acl) debug_printf("Malware scan: read '%s'\n", buffer);
+return p - buffer;
+}
+
+/* return TRUE iff size as requested */
+static BOOL
+recv_len(int sock, void * buf, int size, int tmo)
+{
+return fd_ready(sock, tmo-time(NULL))
+ ? recv(sock, buf, size, 0) == size
+ : FALSE;
+}
+
+
/*************************************************
* Scan content for malware *
*************************************************/
@@ -336,6 +376,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
struct scan * scanent;
const uschar * scanner_options;
int sock = -1;
+ time_t tmo;
/* make sure the eml mbox file is spooled up */
if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL)))
@@ -386,6 +427,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* find the scanner type from the av_scanner option */
if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
return malware_errlog_defer(US"av_scanner configuration variable is empty");
+ tmo = time(NULL) + MALWARE_TIMEOUT;
for (scanent = m_scans; ; scanent++) {
if (!scanent->name)
@@ -410,67 +452,75 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
}
DEBUG(D_lookup) debug_printf("Malware scan: %s\n", scanner_name);
- switch (scanent->scancode) {
- case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
+ switch (scanent->scancode)
{
+ case M_FPROTD: /* "f-protd" scanner type ------------------------------ */
+ {
uschar *fp_scan_option;
unsigned int detected=0, par_count=0;
uschar * scanrequest;
uschar buf[32768], *strhelper, *strhelper2;
uschar * malware_name_internal = NULL;
+ int len;
- DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name);
scanrequest = string_sprintf("GET %s", eml_filename);
while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep,
- NULL, 0))) {
+ NULL, 0)))
+ {
scanrequest = string_sprintf("%s%s%s", scanrequest,
par_count ? "%20" : "?", fp_scan_option);
par_count++;
- }
+ }
scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest);
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s: %s\n",
+ scanner_name, scanrequest);
/* send scan request */
if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
return m_errlog_defer(scanent, errstr);
- /* We get a lot of empty lines, so we need this hack to check for any data at all */
- while( recv(sock, buf, 1, MSG_PEEK) > 0 ) {
- if ( recv_line(sock, buf, sizeof(buf)) > 0) {
- if ( Ustrstr(buf, US"<detected type=\"") != NULL )
+ while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
+ if (len > 0)
+ {
+ if (Ustrstr(buf, US"<detected type=\"") != NULL)
detected = 1;
- else if ( detected && (strhelper = Ustrstr(buf, US"<name>")) ) {
- if ((strhelper2 = Ustrstr(buf, US"</name>")) != NULL) {
+ else if (detected && (strhelper = Ustrstr(buf, US"<name>")))
+ {
+ if ((strhelper2 = Ustrstr(buf, US"</name>")) != NULL)
+ {
*strhelper2 = '\0';
malware_name_internal = string_copy(strhelper+6);
+ }
}
- } else if ( Ustrstr(buf, US"<summary code=\"") )
- malware_name = Ustrstr(buf, US"<summary code=\"11\">")
+ else if (Ustrstr(buf, US"<summary code=\""))
+ {
+ malware_name = Ustrstr(buf, US"<summary code=\"11\">")
? malware_name_internal : NULL;
- }
- }
+ break;
+ }
+ }
break;
- } /* f-protd */
+ } /* f-protd */
- case M_DRWEB: /* "drweb" scanner type ----------------------------------- */
+ case M_DRWEB: /* "drweb" scanner type --------------------------------- */
/* v0.1 - added support for tcp sockets */
/* v0.0 - initial release -- support for unix sockets */
- {
+ {
int result;
off_t fsize;
unsigned int fsize_uint;
uschar * tmpbuf, *drweb_fbuf;
int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd,
drweb_vnum, drweb_slen, drweb_fin = 0x0000;
- unsigned long bread;
const pcre *drweb_re;
/* prepare variables */
drweb_cmd = htonl(DRWEBD_SCAN_CMD);
drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
- if (*scanner_options != '/') {
-
+ if (*scanner_options != '/')
+ {
/* calc file size */
if ((drweb_fd = open(CS eml_filename, O_RDONLY)) == -1)
return m_errlog_defer_3(scanent,
@@ -478,22 +528,24 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
eml_filename, strerror(errno)),
sock);
- if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1) {
+ if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
+ {
int err = errno;
(void)close(drweb_fd);
return m_errlog_defer_3(scanent,
string_sprintf("can't seek spool file %s: %s",
eml_filename, strerror(err)),
sock);
- }
+ }
fsize_uint = (unsigned int) fsize;
- if ((off_t)fsize_uint != fsize) {
+ if ((off_t)fsize_uint != fsize)
+ {
(void)close(drweb_fd);
return m_errlog_defer_3(scanent,
string_sprintf("seeking spool file %s, size overflow",
eml_filename),
sock);
- }
+ }
drweb_slen = htonl(fsize);
lseek(drweb_fd, 0, SEEK_SET);
@@ -504,22 +556,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
(send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
(send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
- (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) {
+ (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+ {
(void)close(drweb_fd);
- return m_errlog_defer_3(scanent,
- string_sprintf("unable to send commands to socket (%s)", scanner_options),
+ return m_errlog_defer_3(scanent, string_sprintf(
+ "unable to send commands to socket (%s)", scanner_options),
sock);
- }
+ }
- if (!(drweb_fbuf = (uschar *) malloc (fsize_uint))) {
+ if (!(drweb_fbuf = (uschar *) malloc (fsize_uint)))
+ {
(void)close(drweb_fd);
return m_errlog_defer_3(scanent,
string_sprintf("unable to allocate memory %u for file (%s)",
fsize_uint, eml_filename),
sock);
- }
+ }
- if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1) {
+ if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1)
+ {
int err = errno;
(void)close(drweb_fd);
free(drweb_fbuf);
@@ -527,20 +582,21 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
string_sprintf("can't read spool file %s: %s",
eml_filename, strerror(err)),
sock);
- }
+ }
(void)close(drweb_fd);
/* send file body to socket */
- if (send(sock, drweb_fbuf, fsize, 0) < 0) {
+ if (send(sock, drweb_fbuf, fsize, 0) < 0)
+ {
free(drweb_fbuf);
- return m_errlog_defer_3(scanent,
- string_sprintf("unable to send file body to socket (%s)", scanner_options),
- sock);
- }
+ return m_errlog_defer_3(scanent, string_sprintf(
+ "unable to send file body to socket (%s)", scanner_options),
+ sock);
+ }
(void)close(drweb_fd);
-
- } else {
-
+ }
+ else
+ {
drweb_slen = htonl(Ustrlen(eml_filename));
DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
@@ -552,24 +608,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
(send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
(send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
(send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
- return m_errlog_defer_3(scanent,
- string_sprintf("unable to send commands to socket (%s)", scanner_options),
+ return m_errlog_defer_3(scanent, string_sprintf(
+ "unable to send commands to socket (%s)", scanner_options),
sock);
- }
+ }
/* wait for result */
- if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc)))
+ if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
return m_errlog_defer_3(scanent,
US"unable to read return code", sock);
drweb_rc = ntohl(drweb_rc);
- if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum)))
+ if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
return m_errlog_defer_3(scanent,
US"unable to read the number of viruses", sock);
drweb_vnum = ntohl(drweb_vnum);
/* "virus(es) found" if virus number is > 0 */
- if (drweb_vnum) {
+ if (drweb_vnum)
+ {
int i;
/* setup default virus name */
@@ -579,18 +636,18 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
drweb_re = m_pcre_compile(US"infected\\swith\\s*(.+?)$", &errstr);
/* read and concatenate virus names into one string */
- for (i=0;i<drweb_vnum;i++)
- {
+ for (i = 0; i < drweb_vnum; i++)
+ {
int size = 0, off = 0, ovector[10*3];
/* read the size of report */
- if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen)))
+ if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
return m_errlog_defer_3(scanent,
US"cannot read report size", sock);
drweb_slen = ntohl(drweb_slen);
tmpbuf = store_get(drweb_slen);
/* read report body */
- if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen)
+ if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
return m_errlog_defer_3(scanent,
US"cannot read report string", sock);
tmpbuf[drweb_slen] = '\0';
@@ -598,7 +655,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* try matcher on the line, grab substring */
result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
ovector, nelements(ovector));
- if (result >= 2) {
+ if (result >= 2)
+ {
const char * pre_malware_nb;
pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
@@ -612,10 +670,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
2, "/", pre_malware_nb);
pcre_free_substring(pre_malware_nb);
+ }
}
}
- }
- else {
+ else
+ {
const char *drweb_s = NULL;
if (drweb_rc & DERR_READ_ERR) drweb_s = "read error";
@@ -633,9 +692,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* no virus found */
malware_name = NULL;
- }
+ }
break;
- } /* drweb */
+ } /* drweb */
case M_AVES: /* "aveserver" scanner type -------------------------------- */
{
@@ -643,7 +702,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
int result;
/* read aveserver's greeting and see if it is ready (2xx greeting) */
- recv_line(sock, buf, sizeof(buf));
+ recv_line(sock, buf, sizeof(buf), tmo);
if (buf[0] != '2') /* aveserver is having problems */
return m_errlog_defer_3(scanent,
@@ -664,28 +723,32 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
malware_name = NULL;
result = 0;
/* read response lines, find malware name and final response */
- while (recv_line(sock, buf, sizeof(buf)) > 0) {
+ while (recv_line(sock, buf, sizeof(buf), tmo) > 0)
+ {
debug_printf("aveserver: %s\n", buf);
if (buf[0] == '2')
break;
- if (buf[0] == '5') { /* aveserver is having problems */
+ if (buf[0] == '5') /* aveserver is having problems */
+ {
result = m_errlog_defer(scanent,
string_sprintf("unable to scan file %s (Responded: %s).",
eml_filename, buf));
break;
- } else if (Ustrncmp(buf,"322",3) == 0) {
- uschar *p = Ustrchr(&buf[4],' ');
+ }
+ if (Ustrncmp(buf,"322",3) == 0)
+ {
+ uschar *p = Ustrchr(&buf[4], ' ');
*p = '\0';
malware_name = string_copy(&buf[4]);
+ }
}
- }
/* and send it */
if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
return m_errlog_defer(scanent, errstr);
/* read aveserver's greeting and see if it is ready (2xx greeting) */
- recv_line(sock, buf, sizeof(buf));
+ recv_line(sock, buf, sizeof(buf), tmo);
if (buf[0] != '2') /* aveserver is having problems */
return m_errlog_defer_3(scanent,
@@ -693,10 +756,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
((buf[0] != 0) ? buf : (uschar *)"nothing") ),
sock);
- if (result == DEFER) {
+ if (result == DEFER)
+ {
(void)close(sock);
return DEFER;
- }
+ }
break;
} /* aveserver */
@@ -710,30 +774,29 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
US"CONFIGURE\tTIMEOUT\t0\n",
US"CONFIGURE\tMAXARCH\t5\n",
US"CONFIGURE\tMIME\t1\n" };
- time_t tmo;
malware_name = NULL;
DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
scanner_name, scanner_options);
- tmo = time(NULL) + MALWARE_TIMEOUT;
/* pass options */
memset(av_buffer, 0, sizeof(av_buffer));
- for (i=0; i != nelements(cmdopt); i++) {
+ for (i = 0; i != nelements(cmdopt); i++)
+ {
if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
return m_errlog_defer(scanent, errstr);
- bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
- if (bread >0) av_buffer[bread]='\0';
+ bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+ if (bread > 0) av_buffer[bread]='\0';
if (bread < 0)
return m_errlog_defer_3(scanent,
string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
sock);
- for (j=0;j<bread;j++)
- if((av_buffer[j]=='\r')||(av_buffer[j]=='\n'))
+ for (j = 0; j < bread; j++)
+ if (av_buffer[j] == '\r' || av_buffer[j] == '\n')
av_buffer[j] ='@';
- }
+ }
/* pass the mailfile to fsecure */
file_name = string_sprintf("SCAN\t%s\n", eml_filename);
@@ -753,13 +816,9 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
for (;;)
{
- int t = tmo - time(NULL);
-
errno = ETIME;
i = av_buffer+sizeof(av_buffer)-p;
- if ( t <= 0
- || (bread= ip_recv(sock, p, i-1, t)) < 0
- )
+ if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
return m_errlog_defer_3(scanent,
string_sprintf("unable to read result (%s)", strerror(errno)),
sock);
@@ -820,7 +879,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
return m_errlog_defer(scanent, errstr);
/* wait for result */
- if ((bread = recv(sock, tmpbuf, 2, 0) != 2))
+ if (!recv_len(sock, tmpbuf, 2, tmo))
return m_errlog_defer_3(scanent,
US"unable to read 2 bytes from socket.", sock);
@@ -844,7 +903,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
bounces where part of a file has been cut off */
/* "virus found" return codes (2-4) */
- if ((kav_rc > 1) && (kav_rc < 5)) {
+ if (kav_rc > 1 && kav_rc < 5)
+ {
int report_flag = 0;
/* setup default virus name */
@@ -853,15 +913,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ];
/* read the report, if available */
- if( report_flag == 1 ) {
+ if (report_flag == 1)
+ {
/* read report size */
- if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4)
+ if (!recv_len(sock, &kav_reportlen, 4, tmo))
return m_errlog_defer_3(scanent,
US"cannot read report size", sock);
/* it's possible that avp returns av_buffer[1] == 1 but the
reportsize is 0 (!?) */
- if (kav_reportlen > 0) {
+ if (kav_reportlen > 0)
+ {
/* set up match regex, depends on retcode */
kav_re = m_pcre_compile( kav_rc == 3
? US"suspicion:\\s*(.+?)\\s*$"
@@ -869,23 +931,19 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
&errstr );
/* read report, linewise */
- while (kav_reportlen > 0) {
- bread = 0;
- while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) {
- kav_reportlen--;
- if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break;
- bread++;
- }
- bread++;
- tmpbuf[bread] = '\0';
+ while (kav_reportlen > 0)
+ {
+ if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
+ break;
+ kav_reportlen -= bread+1;
/* try matcher on the line, grab substring */
if ((malware_name = m_pcre_exec(kav_re, tmpbuf)))
break;
+ }
}
}
}
- }
else /* no virus found */
malware_name = NULL;
@@ -902,8 +960,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
void (*eximsigchld)(int);
void (*eximsigpipe)(int);
FILE *scanner_out = NULL;
+ int scanner_fd;
FILE *scanner_record = NULL;
uschar linebuffer[32767];
+ int rcnt;
int trigger = 0;
uschar *p;
@@ -934,45 +994,62 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* redirect STDERR too */
commandline = string_sprintf("%s 2>&1", commandline);
- DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline);
+ DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
+ scanner_name, commandline);
/* store exims signal handlers */
eximsigchld = signal(SIGCHLD,SIG_DFL);
eximsigpipe = signal(SIGPIPE,SIG_DFL);
- if (!(scanner_out = popen(CS commandline,"r"))) {
+ if (!(scanner_out = popen(CS commandline,"r")))
+ {
int err = errno;
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
return m_errlog_defer(scanent,
string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
- }
+ }
+ scanner_fd = fileno(scanner_out);
file_name = string_sprintf("%s/scan/%s/%s_scanner_output",
spool_directory, message_id, message_id);
- scanner_record = modefopen(file_name, "wb", SPOOL_MODE);
- if (scanner_record == NULL) {
+ if (!(scanner_record = modefopen(file_name, "wb", SPOOL_MODE)))
+ {
int err = errno;
(void) pclose(scanner_out);
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
- return m_errlog_defer(scanent,
- string_sprintf("opening scanner output file (%s) failed: %s.",
+ return m_errlog_defer(scanent, string_sprintf(
+ "opening scanner output file (%s) failed: %s.",
file_name, strerror(err)));
- }
+ }
/* look for trigger while recording output */
- while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out)) {
- if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) {
+ while ((rcnt = recv_line(scanner_fd, linebuffer,
+ sizeof(linebuffer), tmo)))
+ {
+ if (rcnt < 0)
+ {
+ (void) pclose(scanner_out);
+ signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
+ return m_errlog_defer(scanent, string_sprintf(
+ "unable to read from scanner (%s)", scanner_options));
+ }
+
+ if (Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record))
+ {
/* short write */
(void) pclose(scanner_out);
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
- return m_errlog_defer(scanent,
- string_sprintf("short write on scanner output file (%s).", file_name));
- }
+ return m_errlog_defer(scanent, string_sprintf(
+ "short write on scanner output file (%s).", file_name));
+ }
+ putc('\n', scanner_record);
/* try trigger match */
- if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1))
+ if ( !trigger
+ && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)
+ )
trigger = 1;
- }
+ }
(void)fclose(scanner_record);
sep = pclose(scanner_out);
@@ -983,20 +1060,22 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
? string_sprintf("running scanner failed: %s", strerror(sep))
: string_sprintf("scanner returned error code: %d", sep));
- if (trigger) {
+ if (trigger)
+ {
uschar * s;
/* setup default virus name */
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)) {
+ while (fgets(CS linebuffer, sizeof(linebuffer), scanner_record))
+ {
/* try match */
if ((s = m_pcre_exec(cmdline_regex_re, linebuffer)))
malware_name = s;
- }
+ }
(void)fclose(scanner_record);
- }
+ }
else /* no virus found */
malware_name = NULL;
break;
@@ -1026,7 +1105,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* wait for result */
memset(av_buffer, 0, sizeof(av_buffer));
- if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0))
+ if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
return m_errlog_defer_3(scanent,
string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
sock);
@@ -1086,7 +1165,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* Local file; so we def want to use_scan_command and don't want to try
* passing IP/port combinations */
use_scan_command = TRUE;
- else {
+ else
+ {
const uschar *address = scanner_options;
uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20];
@@ -1094,15 +1174,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
* of servers to try. The first one is the bit we just passed from
* scanner_options so process that first and then scan the remainder of
* the address buffer */
- do {
+ do
+ {
clamd_address_container *this_clamd;
/* The 'local' option means use the SCAN command over the network
* socket (ie common file storage in use) */
- if (strcmpic(address,US"local") == 0) {
+ if (strcmpic(address,US"local") == 0)
+ {
use_scan_command = TRUE;
continue;
- }
+ }
/* XXX: If unsuccessful we should free this memory */
this_clamd =
@@ -1110,21 +1192,23 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* extract host and port part */
if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u",
- this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 ) {
+ this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 )
+ {
(void) m_errlog_defer(scanent,
string_sprintf("invalid address '%s'", address));
continue;
- }
+ }
clamd_address_vector[num_servers] = this_clamd;
num_servers++;
- if (num_servers >= MAX_CLAMD_SERVERS) {
+ if (num_servers >= MAX_CLAMD_SERVERS)
+ {
(void) m_errlog_defer(scanent,
US"More than " MAX_CLAMD_SERVERS_S " clamd servers "
"specified; only using the first " MAX_CLAMD_SERVERS_S );
break;
- }
- } while ((address = string_nextinlist(&av_scanner_work, &sep,
+ }
+ } while ((address = string_nextinlist(&av_scanner_work, &sep,
address_buffer,
sizeof(address_buffer))) != NULL);
@@ -1132,23 +1216,25 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
if (!num_servers)
return m_errlog_defer(scanent,
US"no useable server addresses in malware configuration option.");
- }
+ }
- /* See the discussion of response formats below to see why we really don't
- like colons in filenames when passing filenames to ClamAV. */
+ /* See the discussion of response formats below to see why we really
+ don't like colons in filenames when passing filenames to ClamAV. */
if (use_scan_command && Ustrchr(eml_filename, ':'))
return m_errlog_defer(scanent,
string_sprintf("local/SCAN mode incompatible with" \
" : in path to email filename [%s]", eml_filename));
/* We have some network servers specified */
- if (num_servers) {
-
+ 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. */
- while ( num_servers > 0 ) {
+ while (num_servers > 0)
+ {
+ int i;
/* Randomly pick a server to start with */
current_server = random_number( num_servers );
@@ -1161,37 +1247,38 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
sock= m_tcpsocket(clamd_address_vector[current_server]->tcp_addr,
clamd_address_vector[current_server]->tcp_port,
&connhost, &errstr);
- if (sock >= 0) {
+ if (sock >= 0)
+ {
/* Connection successfully established with a server */
hostname = clamd_address_vector[current_server]->tcp_addr;
break;
- }
+ }
(void) m_errlog_defer(scanent, errstr);
/* Remove the server from the list. XXX We should free the memory */
num_servers--;
- int i;
- for( i = current_server; i < num_servers; i++ )
+ for (i = current_server; i < num_servers; i++)
clamd_address_vector[i] = clamd_address_vector[i+1];
- }
+ }
- if ( num_servers == 0 )
+ if (num_servers == 0)
return m_errlog_defer(scanent, US"all servers failed");
-
- } else {
+ }
+ else
+ {
if ((sock = m_unixsocket(scanner_options, &errstr)) < 0)
return m_errlog_defer(scanent, errstr);
- }
+ }
/* 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. */
- if (!use_scan_command) {
-
- #ifdef WITH_OLD_CLAMAV_STREAM
+ 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. */
@@ -1204,7 +1291,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
return m_errlog_defer(scanent, errstr);
memset(av_buffer2, 0, sizeof(av_buffer2));
- bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);
+ bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));
if (bread < 0)
return m_errlog_defer_3(scanent,
@@ -1229,8 +1316,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
if (sockData < 0)
return m_errlog_defer_3(scanent, errstr, sock);
- #define CLOSE_SOCKDATA (void)close(sockData)
- #else /* WITH_OLD_CLAMAV_STREAM not defined */
+# 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. */
@@ -1245,64 +1332,70 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
strerror(errno)),
sock);
- #define CLOSE_SOCKDATA /**/
- #endif
+# define CLOSE_SOCKDATA /**/
+#endif
/* calc file size */
- if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0) {
+ if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
+ {
int err = errno;
CLOSE_SOCKDATA;
return m_errlog_defer_3(scanent,
string_sprintf("can't open spool file %s: %s",
eml_filename, strerror(err)),
sock);
- }
- if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) {
+ }
+ if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
+ {
int err = errno;
CLOSE_SOCKDATA; (void)close(clam_fd);
return m_errlog_defer_3(scanent,
string_sprintf("can't seek spool file %s: %s",
eml_filename, strerror(err)),
sock);
- }
+ }
fsize_uint = (unsigned int) fsize;
- if ((off_t)fsize_uint != fsize) {
+ if ((off_t)fsize_uint != fsize)
+ {
CLOSE_SOCKDATA; (void)close(clam_fd);
return m_errlog_defer_3(scanent,
string_sprintf("seeking spool file %s, size overflow",
eml_filename),
sock);
- }
+ }
lseek(clam_fd, 0, SEEK_SET);
- if (!(clamav_fbuf = (uschar *) malloc (fsize_uint))) {
+ if (!(clamav_fbuf = (uschar *) malloc (fsize_uint)))
+ {
CLOSE_SOCKDATA; (void)close(clam_fd);
return m_errlog_defer_3(scanent,
string_sprintf("unable to allocate memory %u for file (%s)",
fsize_uint, eml_filename),
sock);
- }
+ }
- if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) {
+ if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
+ {
int err = errno;
free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd);
return m_errlog_defer_3(scanent,
string_sprintf("can't read spool file %s: %s",
eml_filename, strerror(err)),
sock);
- }
+ }
(void)close(clam_fd);
/* send file body to socket */
- #ifdef WITH_OLD_CLAMAV_STREAM
- if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0) {
+#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,
string_sprintf("unable to send file body to socket (%s:%u)",
hostname, port),
sock);
- }
- #else
+ }
+#else
send_size = htonl(fsize_uint);
send_final_zeroblock = 0;
if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
@@ -1314,14 +1407,15 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
string_sprintf("unable to send file body to socket (%s)", hostname),
sock);
}
- #endif
+#endif
free(clamav_fbuf);
CLOSE_SOCKDATA;
- #undef CLOSE_SOCKDATA
-
- } else { /* use scan command */
+#undef CLOSE_SOCKDATA
+ }
+ 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 */
@@ -1348,17 +1442,17 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* Do not shut down the socket for writing; a user report noted that
* 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. */
/* Read the result */
memset(av_buffer, 0, sizeof(av_buffer));
- bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT);
+ bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
(void)close(sock);
sock = -1;
- if (!(bread > 0))
+ if (bread <= 0)
return m_errlog_defer(scanent,
string_sprintf("unable to read from socket (%s)", strerror(errno)));
@@ -1404,8 +1498,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
/* colon in returned output? */
if((p = Ustrchr(av_buffer,':')) == NULL)
- return m_errlog_defer(scanent,
- string_sprintf("ClamAV returned malformed result (missing colon): %s",
+ return m_errlog_defer(scanent, string_sprintf(
+ "ClamAV returned malformed result (missing colon): %s",
av_buffer));
/* strip filename */
@@ -1417,32 +1511,37 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
p = Ustrrchr(vname, ' ');
result_tag = p ? p+1 : vname;
- if (Ustrcmp(result_tag, "FOUND") == 0) {
+ if (Ustrcmp(result_tag, "FOUND") == 0)
+ {
/* p should still be the whitespace before the result_tag */
while (isspace(*p)) --p;
*++p = '\0';
/* Strip off the extended information too, which will be in parens
after the virus name, with no intervening whitespace. */
- if (*--p == ')') {
+ if (*--p == ')')
+ {
/* "(hash:size)", so previous '(' will do; if not found, we have
a curious virus name, but not an error. */
p = Ustrrchr(vname, '(');
if (p)
*p = '\0';
- }
+ }
malware_name = string_copy(vname);
DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name);
- } else if (Ustrcmp(result_tag, "ERROR") == 0)
+ }
+ else if (Ustrcmp(result_tag, "ERROR") == 0)
return m_errlog_defer(scanent,
string_sprintf("ClamAV returned: %s", av_buffer));
- else if (Ustrcmp(result_tag, "OK") == 0) {
+ else if (Ustrcmp(result_tag, "OK") == 0)
+ {
/* Everything should be OK */
malware_name = NULL;
DEBUG(D_acl) debug_printf("Malware not found\n");
- } else
+ }
+ else
return m_errlog_defer(scanent,
string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
@@ -1498,16 +1597,16 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
return m_errlog_defer(scanent, errstr);
/* Read the result */
- memset(av_buffer, 0, sizeof(av_buffer));
- bread = read(sock, av_buffer, sizeof(av_buffer));
+ bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
- if (!(bread > 0))
+ if (bread <= 0)
return m_errlog_defer_3(scanent,
string_sprintf("unable to read from socket (%s)", strerror(errno)),
sock);
if (bread == sizeof(av_buffer))
return m_errlog_defer_3(scanent, US"buffer too small", sock);
+ av_buffer[bread] = '\0';
linebuffer = string_copy(av_buffer);
/* try trigger match */
@@ -1524,10 +1623,10 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
{
char *mksd_options_end;
int mksd_maxproc = 1; /* default, if no option supplied */
- int sock;
int retval;
- if (scanner_options) {
+ if (scanner_options)
+ {
mksd_maxproc = (int)strtol(CS scanner_options, &mksd_options_end, 10);
if ( *scanner_options == '\0'
|| *mksd_options_end != '\0'
@@ -1536,7 +1635,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
)
return m_errlog_defer(scanent,
string_sprintf("invalid option '%s'", scanner_options));
- }
+ }
if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
return m_errlog_defer(scanent, errstr);
@@ -1545,10 +1644,11 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);
- if ((retval = mksd_scan_packed(scanent, sock, eml_filename)) != OK) {
+ if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK)
+ {
close (sock);
return retval;
- }
+ }
break;
}
case M_AVAST: /* "avast" scanner type ----------------------------------- */
@@ -1577,7 +1677,7 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
return malware_errlog_defer(errstr);
/* wait for result */
- for (avast_stage = AVA_HELO; recv_line(sock, buf, sizeof(buf)) > 0; )
+ for (avast_stage = AVA_HELO; recv_line(sock, buf, sizeof(buf), tmo) > 0; )
{
int slen = Ustrlen(buf);
if (slen >= 1)
@@ -1690,25 +1790,6 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking)
}
-/* simple wrapper for reading lines from sockets */
-int
-recv_line(int sock, uschar *buffer, int size)
-{
- uschar *p = buffer;
-
- memset(buffer,0,size);
- /* read until \n */
- while(recv(sock,p,1,0) > -1) {
- if ((p-buffer) > (size-2)) break;
- if (*p == '\n') break;
- if (*p != '\r') p++;
- }
- *p = '\0';
-
- return (p-buffer);
-}
-
-
/* ============= private routines for the "mksd" scanner type ============== */
#include <sys/uio.h>
@@ -1742,24 +1823,28 @@ mksd_writev (int sock, struct iovec *iov, int iovcnt)
}
static inline int
-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
{
int offset = 0;
int i;
- do {
- if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) {
+ do
+ {
+ i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+ if (i <= 0)
+ {
(void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
return -1;
- }
+ }
offset += i;
/* offset == av_buffer_size -> buffer full */
- if (offset == av_buffer_size) {
+ if (offset == av_buffer_size)
+ {
(void) malware_errlog_defer(US"malformed reply received from mksd");
return -1;
- }
- } while (av_buffer[offset-1] != '\n');
+ }
+ } while (av_buffer[offset-1] != '\n');
av_buffer[offset] = '\0';
return offset;
@@ -1797,7 +1882,7 @@ mksd_parse_line(struct scan * scanent, char *line)
}
static int
-mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename)
+mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename, int tmo)
{
struct iovec iov[3];
const char *cmd = "MSQ\n";
@@ -1813,7 +1898,7 @@ mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename)
if (mksd_writev (sock, iov, 3) < 0)
return DEFER;
- if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)
+ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0)
return DEFER;
return mksd_parse_line (scanent, CS av_buffer);