From: Jeremy Harris Date: Sat, 27 Dec 2014 20:47:19 +0000 (+0000) Subject: Apply timeout consistently to all malware scanner types X-Git-Tag: exim-4_91_RC1~148^2~42^2~42 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/9439f85e4de0ea4cd0ddedad9387e50c935073e2 Apply timeout consistently to all malware scanner types --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 65bd5f703..0b592e006 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -30343,10 +30343,12 @@ It supports a &"generic"& interface to scanners called via the shell, and specialized interfaces for &"daemon"& type virus scanners, which are resident in memory and thus are much faster. +A timeout of 2 minutes is applied to a scanner call; +if it expires then a defer action is taken. .oindex "&%av_scanner%&" -You can set the &%av_scanner%& option in first part of the Exim configuration -file to specify which scanner to use, together with any additional options that +You can set the &%av_scanner%& option in the main part of the configuration +to specify which scanner to use, together with any additional options that are needed. The basic syntax is as follows: .display &`av_scanner = <`&&'scanner-type'&&`>:<`&&'option1'&&`>:<`&&'option2'&&`>:[...]`& diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 28b53bc8c..7eaa074ba 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -35,6 +35,8 @@ JH/08 The EXPERIMENTAL_DSN compile option is no longer needed; all Delivery under the control of the dsn_advertise_hosts option, and routers may have a dsn_lasthop option. +JH/09 A timeout of 2 minutes is now applied to all malware scanner types. + Exim version 4.85 diff --git a/src/src/functions.h b/src/src/functions.h index 671a6ea96..e2e9befb3 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"= 0) + if (len > 0) + { + if (Ustrstr(buf, US"")) ) { - if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { + else if (detected && (strhelper = Ustrstr(buf, US""))) + { + if ((strhelper2 = Ustrstr(buf, US"")) != NULL) + { *strhelper2 = '\0'; malware_name_internal = string_copy(strhelper+6); + } } - } else if ( Ustrstr(buf, US"") + else if (Ustrstr(buf, US"") ? 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= 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 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" 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 chunks, 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 @@ -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);