From cd45e2aa4bd0a1fa8eb39692721c7b8b3d6acdc9 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 2 Feb 2014 18:55:18 +0000 Subject: [PATCH] Use table for scanner name and default args --- src/src/malware.c | 2375 ++++++++++++++++++++++----------------------- 1 file changed, 1184 insertions(+), 1191 deletions(-) diff --git a/src/src/malware.c b/src/src/malware.c index ece46f2b2..9d4d16aba 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -223,7 +223,7 @@ clmd_errlog(const uschar * str) /*************************************************/ static int -m_unixsocket(uschar * path, uschar ** errstr) +m_unixsocket(const uschar * path, uschar ** errstr) { int sock; struct sockaddr_un server; @@ -262,6 +262,27 @@ return sock; * Scan content for malware * *************************************************/ +typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL, + M_SOPHIE, M_CLAMD, M_MKSD} scanner_t; +static struct scan +{ + scanner_t scancode; + const char * name; + const char * options_default; +} m_scans[] = +{ + { M_FPROTD, "f-protd", "localhost 10200-10204" }, + { M_DRWEB, "drweb", "/usr/local/drweb/run/drwebd.sock" }, + { M_AVES, "aveserver", "/var/run/aveserver" }, + { M_FSEC, "fsecure", "/var/run/.fsav" }, + { M_KAVD, "kavdaemon", "/var/run/AvpCtl" }, + { M_CMDL, "cmdline", NULL }, + { M_SOPHIE, "sophie", "/var/run/sophie" }, + { M_CLAMD, "clamd", "/tmp/clamd" }, + { M_MKSD, "mksd", NULL }, + { -1, NULL, NULL } /* end-marker */ +}; + /* This is an internal interface for scanning an email; the normal interface is via malware(), or there's malware_in_file() used for testing/debugging. @@ -288,6 +309,8 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) const pcre *re; const uschar *rerror; uschar * errstr; + struct scan * scanent; + const uschar * scanner_options; /* make sure the eml mbox file is spooled up */ mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL); @@ -347,1301 +370,1271 @@ malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) if (malware_ok == 0) { /* find the scanner type from the av_scanner option */ - if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) { - /* no scanner given */ + if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) return malware_errlog_defer("av_scanner configuration variable is empty"); - } - /* "f-protd" scanner type ----------------------------------------------- */ - if (strcmpic(scanner_name, US"f-protd") == 0) { - uschar *fp_options, *fp_scan_option; - uschar fp_options_default[] = "localhost 10200-10204"; - uschar hostname[256]; - unsigned int port, portlow, porthigh, connect_ok=0, detected=0, par_count = 0; - struct hostent *he; - struct in_addr in; - int sock; - uschar * scanrequest; - uschar buf[32768], *strhelper, *strhelper2; - - if (!(fp_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - fp_options = fp_options_default; - - /* extract host and port part */ - if ( sscanf(CS fp_options, "%255s %u-%u", hostname, &portlow, &porthigh) != 3 ) { - if ( sscanf(CS fp_options, "%s %u", hostname, &portlow) != 2 ) - return fprotd_errlog_defer( - string_sprintf("invalid socket '%s'", fp_options)); - porthigh = portlow; + for (scanent = m_scans; ; scanent++) { + if (!scanent->name) + return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", + scanner_name)); + if (strcmpic(scanner_name, US scanent->name) == 0) + break; } + if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) + scanner_options = scanent->options_default; - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) - return fprotd_errlog_defer( - string_sprintf("failed to lookup host '%s'", hostname)); - - in = *(struct in_addr *) he->h_addr_list[0]; - port = portlow; - + switch (scanent->scancode) { + case M_FPROTD: /* "f-protd" scanner type -------------------------------- */ + { + uschar *fp_scan_option; + uschar hostname[256]; + unsigned int port, portlow, porthigh, connect_ok=0, detected=0, par_count=0; + struct hostent *he; + struct in_addr in; + int sock; + uschar * scanrequest; + uschar buf[32768], *strhelper, *strhelper2; + + /* extract host and port part */ + if ( sscanf(CS scanner_options, "%255s %u-%u", hostname, &portlow, &porthigh) != 3 ) { + if ( sscanf(CS scanner_options, "%s %u", hostname, &portlow) != 2 ) + return fprotd_errlog_defer( + string_sprintf("invalid socket '%s'", scanner_options)); + porthigh = portlow; + } - /* Open the f-protd TCP socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) - return fprotd_errlog_defer( - string_sprintf("unable to acquire socket (%s)", strerror(errno))); + /* Lookup the host */ + if((he = gethostbyname(CS hostname)) == 0) + return fprotd_errlog_defer( + string_sprintf("failed to lookup host '%s'", hostname)); - /* Try to connect to all portslow-high until connection is established */ - for (port = portlow; !connect_ok && port < porthigh; port++) - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) >= 0) - connect_ok = 1; + in = *(struct in_addr *) he->h_addr_list[0]; + port = portlow; - if ( !connect_ok ) { - int err = errno; - (void)close(sock); - return fprotd_errlog_defer( - string_sprintf("connection to %s, port %u-%u failed (%s)", - inet_ntoa(in), portlow, porthigh, strerror(err))); - } - DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name); - scanrequest = string_sprintf("GET %s", eml_filename); + /* Open the f-protd TCP socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) + return fprotd_errlog_defer( + string_sprintf("unable to acquire socket (%s)", strerror(errno))); - while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep, - 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); - - /* send scan request */ - if (m_sock_send(sock, &scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) - return fprotd_errlog_defer(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, 32768) > 0) { - if ( Ustrstr(buf, US"")) ) { - if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { - *strhelper2 = '\0'; - malware_name_internal = string_copy(strhelper+6); - } - } else if ( Ustrstr(buf, US"") - ? malware_name_internal : NULL; - } - } - (void)close(sock); - } - /* "drweb" scanner type ----------------------------------------------- */ - /* v0.1 - added support for tcp sockets */ - /* v0.0 - initial release -- support for unix sockets */ - else if (strcmpic(scanner_name,US"drweb") == 0) { - uschar *drweb_options; - uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock"; - struct sockaddr_un server; - int sock, result, ovector[10*3]; - unsigned int port, fsize; - uschar * tmpbuf, *drweb_fbuf; - int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, - drweb_vnum, drweb_slen, drweb_fin = 0x0000; - unsigned long bread; - uschar hostname[256]; - struct hostent *he; - struct in_addr in; - pcre *drweb_re; - - if (!(drweb_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - drweb_options = drweb_options_default; - - if (*drweb_options != '/') { - - /* extract host and port part */ - if( sscanf(CS drweb_options, "%255s %u", hostname, &port) != 2 ) - return drweb_errlog_defer( - string_sprintf("invalid socket '%s'", drweb_options)); - - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) - return drweb_errlog_defer( - string_sprintf("failed to lookup host '%s'", hostname)); - - in = *(struct in_addr *) he->h_addr_list[0]; - - /* Open the drwebd TCP socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) - return drweb_errlog_defer( - string_sprintf("unable to acquire socket (%s)", strerror(errno))); - - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - int err = errno; - (void)close(sock); - return drweb_errlog_defer( - string_sprintf("connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(err))); - } + /* Try to connect to all portslow-high until connection is established */ + for (port = portlow; !connect_ok && port < porthigh; port++) + if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) >= 0) + connect_ok = 1; - /* prepare variables */ - drweb_cmd = htonl(DRWEBD_SCAN_CMD); - drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); - - /* calc file size */ - drweb_fd = open(CS eml_filename, O_RDONLY); - if (drweb_fd == -1) { - int err = errno; - (void)close(sock); - return drweb_errlog_defer( - string_sprintf("can't open spool file %s: %s", - eml_filename, strerror(err))); - } - fsize = lseek(drweb_fd, 0, SEEK_END); - if (fsize == -1) { - int err = errno; - (void)close(sock); - (void)close(drweb_fd); - return drweb_errlog_defer( - string_sprintf("can't seek spool file %s: %s", - eml_filename, strerror(err))); - } - drweb_slen = htonl(fsize); - lseek(drweb_fd, 0, SEEK_SET); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s %u]\n", - scanner_name, hostname, port); - - /* send scan request */ - 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)) { - (void)close(sock); - (void)close(drweb_fd); - return drweb_errlog_defer( - string_sprintf("unable to send commands to socket (%s)", drweb_options)); - } + if ( !connect_ok ) { + int err = errno; + (void)close(sock); + return fprotd_errlog_defer( + string_sprintf("connection to %s, port %u-%u failed (%s)", + inet_ntoa(in), portlow, porthigh, strerror(err))); + } - drweb_fbuf = (uschar *) malloc (fsize); - if (!drweb_fbuf) { - (void)close(sock); - (void)close(drweb_fd); - return drweb_errlog_defer( - string_sprintf("unable to allocate memory %u for file (%s)", - fsize, eml_filename)); - } + DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name); + scanrequest = string_sprintf("GET %s", eml_filename); - result = read (drweb_fd, drweb_fbuf, fsize); - if (result == -1) { - int err = errno; - (void)close(sock); - (void)close(drweb_fd); - free(drweb_fbuf); - return drweb_errlog_defer( - string_sprintf("can't read spool file %s: %s", - eml_filename, strerror(err))); - } - (void)close(drweb_fd); - - /* send file body to socket */ - if (send(sock, drweb_fbuf, fsize, 0) < 0) { - (void)close(sock); - free(drweb_fbuf); - return drweb_errlog_defer( - string_sprintf("unable to send file body to socket (%s)", drweb_options)); - } - (void)close(drweb_fd); - } - else { - if((sock = m_unixsocket(drweb_options, &errstr)) < 0) - return drweb_errlog_defer(errstr); - - /* prepare variables */ - drweb_cmd = htonl(DRWEBD_SCAN_CMD); - drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); - drweb_slen = htonl(Ustrlen(eml_filename)); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n", - scanner_name, drweb_options); - - /* send scan request */ - if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || - (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || - (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)) { - (void)close(sock); - return drweb_errlog_defer( - string_sprintf("unable to send commands to socket (%s)", drweb_options)); + while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep, + 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); + + /* send scan request */ + if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return fprotd_errlog_defer(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, 32768) > 0) { + if ( Ustrstr(buf, US"")) ) { + if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { + *strhelper2 = '\0'; + malware_name_internal = string_copy(strhelper+6); + } + } else if ( Ustrstr(buf, US"") + ? malware_name_internal : NULL; + } + } + (void)close(sock); + break; } - } - - /* wait for result */ - if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { - (void)close(sock); - return drweb_errlog_defer("unable to read return code"); - } - drweb_rc = ntohl(drweb_rc); - if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { - (void)close(sock); - return drweb_errlog_defer("unable to read the number of viruses"); - } - drweb_vnum = ntohl(drweb_vnum); - - /* "virus(es) found" if virus number is > 0 */ - if (drweb_vnum) - { - int i; + case M_DRWEB: /* "drweb" scanner type ----------------------------------- */ + /* v0.1 - added support for tcp sockets */ + /* v0.0 - initial release -- support for unix sockets */ + { + struct sockaddr_un server; + int sock, result, ovector[10*3]; + unsigned int port, fsize; + uschar * tmpbuf, *drweb_fbuf; + int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, + drweb_vnum, drweb_slen, drweb_fin = 0x0000; + unsigned long bread; + uschar hostname[256]; + struct hostent *he; + struct in_addr in; + pcre *drweb_re; + + if (*scanner_options != '/') { + + /* extract host and port part */ + if( sscanf(CS scanner_options, "%255s %u", hostname, &port) != 2 ) + return drweb_errlog_defer( + string_sprintf("invalid socket '%s'", scanner_options)); + + /* Lookup the host */ + if((he = gethostbyname(CS hostname)) == 0) + return drweb_errlog_defer( + string_sprintf("failed to lookup host '%s'", hostname)); + + in = *(struct in_addr *) he->h_addr_list[0]; + + /* Open the drwebd TCP socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) + return drweb_errlog_defer( + string_sprintf("unable to acquire socket (%s)", strerror(errno))); + + if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { + int err = errno; + (void)close(sock); + return drweb_errlog_defer( + string_sprintf("connection to %s, port %u failed (%s)", + inet_ntoa(in), port, strerror(err))); + } + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); + + /* calc file size */ + drweb_fd = open(CS eml_filename, O_RDONLY); + if (drweb_fd == -1) { + int err = errno; + (void)close(sock); + return drweb_errlog_defer( + string_sprintf("can't open spool file %s: %s", + eml_filename, strerror(err))); + } + fsize = lseek(drweb_fd, 0, SEEK_END); + if (fsize == -1) { + int err = errno; + (void)close(sock); + (void)close(drweb_fd); + return drweb_errlog_defer( + string_sprintf("can't seek spool file %s: %s", + eml_filename, strerror(err))); + } + drweb_slen = htonl(fsize); + lseek(drweb_fd, 0, SEEK_SET); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s %u]\n", + scanner_name, hostname, port); + + /* send scan request */ + 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)) { + (void)close(sock); + (void)close(drweb_fd); + return drweb_errlog_defer( + string_sprintf("unable to send commands to socket (%s)", scanner_options)); + } + + drweb_fbuf = (uschar *) malloc (fsize); + if (!drweb_fbuf) { + (void)close(sock); + (void)close(drweb_fd); + return drweb_errlog_defer( + string_sprintf("unable to allocate memory %u for file (%s)", + fsize, eml_filename)); + } + + result = read (drweb_fd, drweb_fbuf, fsize); + if (result == -1) { + int err = errno; + (void)close(sock); + (void)close(drweb_fd); + free(drweb_fbuf); + return drweb_errlog_defer( + string_sprintf("can't read spool file %s: %s", + eml_filename, strerror(err))); + } + (void)close(drweb_fd); + + /* send file body to socket */ + if (send(sock, drweb_fbuf, fsize, 0) < 0) { + (void)close(sock); + free(drweb_fbuf); + return drweb_errlog_defer( + string_sprintf("unable to send file body to socket (%s)", scanner_options)); + } + (void)close(drweb_fd); + } + else { + if((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return drweb_errlog_defer(errstr); + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); + drweb_slen = htonl(Ustrlen(eml_filename)); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n", + scanner_name, scanner_options); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (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)) { + (void)close(sock); + return drweb_errlog_defer( + string_sprintf("unable to send commands to socket (%s)", scanner_options)); + } + } - /* setup default virus name */ - malware_name_internal = "unknown"; - malware_name = malware_name_internal; + /* wait for result */ + if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { + (void)close(sock); + return drweb_errlog_defer("unable to read return code"); + } + drweb_rc = ntohl(drweb_rc); - /* set up match regex */ - drweb_re = pcre_compile( "infected\\swith\\s*(.+?)$", PCRE_COPT, - (const char **)&rerror, &roffset, NULL ); + if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { + (void)close(sock); + return drweb_errlog_defer("unable to read the number of viruses"); + } + drweb_vnum = ntohl(drweb_vnum); + + /* "virus(es) found" if virus number is > 0 */ + if (drweb_vnum) + { + int i; + + /* setup default virus name */ + malware_name_internal = "unknown"; + malware_name = malware_name_internal; + + /* set up match regex */ + drweb_re = pcre_compile( "infected\\swith\\s*(.+?)$", PCRE_COPT, + (const char **)&rerror, &roffset, NULL ); + + /* read and concatenate virus names into one string */ + for (i=0;i= 2) { + const char * pre_malware_nb; + + pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); + + /* the first name we just copy to malware_name */ + if (i==0) + malware_name_internal = string_append(NULL, &size, &off, + 1, pre_malware_nb); + else + /* concatenate each new virus name to previous */ + malware_name_internal = string_append(malware_name_internal, + &size, &off, 2, "/", pre_malware_nb); + + pcre_free_substring(pre_malware_nb); + } + } + } + else { + const char *drweb_s = NULL; + + if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; + if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; + if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout"; + if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command"; + /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED. + * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM, + * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR + * and others are ignored */ + if (drweb_s) { + (void)close(sock); + return drweb_errlog_defer( + string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s)); + } + /* no virus found */ + malware_name = NULL; + } + (void)close(sock); + break; + } - /* read and concatenate virus names into one string */ - for (i=0;i= 2) { - const char * pre_malware_nb; - - pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); - - /* the first name we just copy to malware_name */ - if (i==0) - malware_name_internal = string_append(NULL, &size, &off, - 1, pre_malware_nb); - else - /* concatenate each new virus name to previous */ - malware_name_internal = string_append(malware_name_internal, - &size, &off, 2, "/", pre_malware_nb); - - pcre_free_substring(pre_malware_nb); - } - } - } - else { - const char *drweb_s = NULL; - - if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; - if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; - if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout"; - if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command"; - /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED. - * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM, - * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR - * and others are ignored */ - if (drweb_s) { - (void)close(sock); - return drweb_errlog_defer( - string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s)); - } - /* no virus found */ - malware_name = NULL; - } - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - else if (strcmpic(scanner_name,US"aveserver") == 0) { - uschar *kav_options; - uschar kav_options_default[] = "/var/run/aveserver"; - uschar buf[32768]; - struct sockaddr_un server; - int sock; - int result; - - if (!(kav_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - kav_options = kav_options_default; - - if((sock = m_unixsocket(kav_options, &errstr)) < 0) - return aves_errlog_defer(errstr); - - /* read aveserver's greeting and see if it is ready (2xx greeting) */ - recv_line(sock, buf, sizeof(buf)); - - if (buf[0] != '2') { - /* aveserver is having problems */ - (void)close(sock); - return aves_errlog_defer( - string_sprintf("unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") )); - } + uschar buf[32768]; + struct sockaddr_un server; + int sock; + int result; + + if((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return aves_errlog_defer(errstr); + + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, sizeof(buf)); + + if (buf[0] != '2') { + /* aveserver is having problems */ + (void)close(sock); + return aves_errlog_defer( + string_sprintf("unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") )); + } - /* prepare our command */ - (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n", - eml_filename); + /* prepare our command */ + (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n", + eml_filename); - DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name); + DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name); - /* and send it */ - if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) - return aves_errlog_defer(errstr); + /* and send it */ + if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) + return aves_errlog_defer(errstr); - malware_name = NULL; - result = 0; - /* read response lines, find malware name and final response */ - while (recv_line(sock, buf, sizeof(buf)) > 0) { - debug_printf("aveserver: %s\n", buf); - if (buf[0] == '2') - break; - if (buf[0] == '5') { - /* aveserver is having problems */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to scan file %s (Responded: %s).", - eml_filename, buf); - result = DEFER; - break; - } else if (Ustrncmp(buf,"322",3) == 0) { - uschar *p = Ustrchr(&buf[4],' '); - *p = '\0'; - malware_name_internal = string_copy(&buf[4]); - malware_name = malware_name_internal; + malware_name = NULL; + result = 0; + /* read response lines, find malware name and final response */ + while (recv_line(sock, buf, sizeof(buf)) > 0) { + debug_printf("aveserver: %s\n", buf); + if (buf[0] == '2') + break; + if (buf[0] == '5') { + /* aveserver is having problems */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to scan file %s (Responded: %s).", + eml_filename, buf); + result = DEFER; + break; + } else if (Ustrncmp(buf,"322",3) == 0) { + uschar *p = Ustrchr(&buf[4],' '); + *p = '\0'; + malware_name_internal = string_copy(&buf[4]); + malware_name = malware_name_internal; + } } - } - /* prepare our command */ - (void)string_format(buf, sizeof(buf), "quit\r\n"); + /* prepare our command */ + (void)string_format(buf, sizeof(buf), "quit\r\n"); - /* and send it */ - if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) - return aves_errlog_defer(errstr); + /* and send it */ + if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) + return aves_errlog_defer(errstr); - /* read aveserver's greeting and see if it is ready (2xx greeting) */ - recv_line(sock, buf, sizeof(buf)); + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, sizeof(buf)); - if (buf[0] != '2') { - /* aveserver is having problems */ - (void)close(sock); - return aves_errlog_defer( - string_sprintf("unable to quit dialogue (Responded: %s).", - ((buf[0] != 0) ? buf : (uschar *)"nothing") )); - } + if (buf[0] != '2') { + /* aveserver is having problems */ + (void)close(sock); + return aves_errlog_defer( + string_sprintf("unable to quit dialogue (Responded: %s).", + ((buf[0] != 0) ? buf : (uschar *)"nothing") )); + } - (void)close(sock); + (void)close(sock); - if (result == DEFER) return DEFER; - } - /* "fsecure" scanner type ------------------------------------------------- */ - else if (strcmpic(scanner_name,US"fsecure") == 0) { - uschar *fsec_options; - uschar fsec_options_default[] = "/var/run/.fsav"; - struct sockaddr_un server; - int sock, i, j, bread = 0; - uschar * file_name; - uschar av_buffer[1024]; - pcre * fs_inf; - static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n", - US"CONFIGURE\tTIMEOUT\t0\n", - US"CONFIGURE\tMAXARCH\t5\n", - US"CONFIGURE\tMIME\t1\n" }; + if (result == DEFER) return DEFER; + break; + } - malware_name = NULL; - if (!(fsec_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - fsec_options = fsec_options_default; + case M_FSEC: /* "fsecure" scanner type ---------------------------------- */ + { + struct sockaddr_un server; + int sock, i, j, bread = 0; + uschar * file_name; + uschar av_buffer[1024]; + pcre * fs_inf; + static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n", + US"CONFIGURE\tTIMEOUT\t0\n", + US"CONFIGURE\tMAXARCH\t5\n", + US"CONFIGURE\tMIME\t1\n" }; - if((sock = m_unixsocket(fsec_options, &errstr)) < 0) - return fsec_errlog_defer(errstr); + malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, fsec_options); + if((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return fsec_errlog_defer(errstr); - /* pass options */ - memset(av_buffer, 0, sizeof(av_buffer)); - for (i=0; i != nelements(cmdopt); i++) { - /* debug_printf("send option \"%s\"",cmdopt[i]); */ + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); - if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) - return fsec_errlog_defer(errstr); + /* pass options */ + memset(av_buffer, 0, sizeof(av_buffer)); + for (i=0; i != nelements(cmdopt); i++) { + /* debug_printf("send option \"%s\"",cmdopt[i]); */ - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); - if (bread >0) av_buffer[bread]='\0'; - if (bread < 0) { - int err = errno; - (void)close(sock); - return fsec_errlog_defer( - string_sprintf("unable to read answer %d (%s)", i, strerror(errno))); - } - for (j=0;j0) av_buffer[bread]='\0'; + if (bread < 0) { int err = errno; - (void)close(sock); + (void)close(sock); return fsec_errlog_defer( - string_sprintf("unable to read result (%s)", strerror(err))); - } - i++; - } - while ((i < sizeof(av_buffer)-1 ) && (av_buffer[i-1] != '\n')); - av_buffer[i-1] = '\0'; - /* debug_printf("got line \"%s\"\n",av_buffer); */ - - /* Really search for virus again? */ - if (malware_name == NULL) { - /* try matcher on the line, grab substring */ - i = pcre_exec(fs_inf, NULL, CS av_buffer, Ustrlen(av_buffer), 0, 0, - ovector, nelements(ovector)); - if (i >= 2) { - /* Got it */ - pcre_get_substring(CS av_buffer, ovector, i, 1, - (const char **) &malware_name_internal); - malware_name = malware_name_internal; - } - } - } - while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL); - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - - /* "kavdaemon" scanner type ------------------------------------------------ */ - else if (strcmpic(scanner_name,US"kavdaemon") == 0) { - uschar *kav_options; - uschar kav_options_default[] = "/var/run/AvpCtl"; - struct sockaddr_un server; - int sock; - time_t t; - uschar tmpbuf[1024]; - uschar * scanrequest; - int kav_rc; - unsigned long kav_reportlen, bread; - pcre *kav_re; - uschar *p; - - if (!(kav_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - kav_options = kav_options_default; - - if((sock = m_unixsocket(kav_options, &errstr)) < 0) - return kavd_errlog_defer(errstr); - - /* get current date and time, build scan request */ - time(&t); - /* pdp note: before the eml_filename parameter, this scanned the - directory; not finding documentation, so we'll strip off the directory. - The side-effect is that the test framework scanning may end up in - scanning more than was requested, but for the normal interface, this is - fine. */ - strftime(CS tmpbuf, sizeof(tmpbuf), "%d %b %H:%M:%S", localtime(&t)); - scanrequest = string_sprintf("<0>%s:%s", CS tmpbuf, eml_filename); - p = Ustrrchr(scanrequest, '/'); - if (p) - *p = '\0'; - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, kav_options); - - /* send scan request */ - if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) - return kavd_errlog_defer(errstr); - - /* wait for result */ - if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { - (void)close(sock); - return kavd_errlog_defer("unable to read 2 bytes from socket."); - } + string_sprintf("unable to read answer %d (%s)", i, strerror(errno))); + } + for (j=0;j= 2) { + /* Got it */ + pcre_get_substring(CS av_buffer, ovector, i, 1, + (const char **) &malware_name_internal); + malware_name = malware_name_internal; + } + } + } + while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL); + (void)close(sock); + break; } - if (kav_rc == 7) { - (void)close(sock); - return kavd_errlog_defer("reported 'kavdaemon damaged' (code 7)."); - } + case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */ + { + struct sockaddr_un server; + int sock; + time_t t; + uschar tmpbuf[1024]; + uschar * scanrequest; + int kav_rc; + unsigned long kav_reportlen, bread; + pcre *kav_re; + uschar *p; + + if((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return kavd_errlog_defer(errstr); + + /* get current date and time, build scan request */ + time(&t); + /* pdp note: before the eml_filename parameter, this scanned the + directory; not finding documentation, so we'll strip off the directory. + The side-effect is that the test framework scanning may end up in + scanning more than was requested, but for the normal interface, this is + fine. */ + strftime(CS tmpbuf, sizeof(tmpbuf), "%d %b %H:%M:%S", localtime(&t)); + scanrequest = string_sprintf("<0>%s:%s", CS tmpbuf, eml_filename); + p = Ustrrchr(scanrequest, '/'); + if (p) + *p = '\0'; + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); + + /* send scan request */ + if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return kavd_errlog_defer(errstr); + + /* wait for result */ + if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { + (void)close(sock); + return kavd_errlog_defer("unable to read 2 bytes from socket."); + } - /* code 8 is not handled, since it is ambigous. It appears mostly on - bounces where part of a file has been cut off */ + /* get errorcode from one nibble */ + kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F; - /* "virus found" return codes (2-4) */ - if ((kav_rc > 1) && (kav_rc < 5)) { - int report_flag = 0; + /* improper kavdaemon configuration */ + if ( (kav_rc == 5) || (kav_rc == 6) ) { + (void)close(sock); + return kavd_errlog_defer("please reconfigure kavdaemon to NOT disinfect or remove infected files."); + } - /* setup default virus name */ - malware_name_internal = string_copy("unknown"); - malware_name = malware_name_internal; + if (kav_rc == 1) { + (void)close(sock); + return kavd_errlog_defer("reported 'scanning not completed' (code 1)."); + } - report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ]; + if (kav_rc == 7) { + (void)close(sock); + return kavd_errlog_defer("reported 'kavdaemon damaged' (code 7)."); + } - /* read the report, if available */ - if( report_flag == 1 ) { - /* read report size */ - if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { - (void)close(sock); - return kavd_errlog_defer("cannot read report size"); - } + /* code 8 is not handled, since it is ambigous. It appears mostly on + bounces where part of a file has been cut off */ + + /* "virus found" return codes (2-4) */ + if ((kav_rc > 1) && (kav_rc < 5)) { + int report_flag = 0; + + /* setup default virus name */ + malware_name_internal = string_copy("unknown"); + malware_name = malware_name_internal; + + report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ]; + + /* read the report, if available */ + if( report_flag == 1 ) { + /* read report size */ + if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { + (void)close(sock); + return kavd_errlog_defer("cannot read report size"); + } + + /* it's possible that avp returns av_buffer[1] == 1 but the + reportsize is 0 (!?) */ + if (kav_reportlen > 0) { + /* set up match regex, depends on retcode */ + kav_re = pcre_compile( kav_rc == 3 + ? "suspicion:\\s*(.+?)\\s*$" + : "infected:\\s*(.+?)\\s*$", + PCRE_COPT, + (const char **)&rerror, + &roffset, + NULL ); + + /* read report, linewise */ + while (kav_reportlen > 0) { + int result = 0; + int ovector[10*3]; + + bread = 0; + while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { + kav_reportlen--; + if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; + bread++; + } + bread++; + tmpbuf[bread] = '\0'; + + /* try matcher on the line, grab substring */ + result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, + ovector, nelements(ovector)); + if (result >= 2) { + pcre_get_substring(CS tmpbuf, ovector, result, 1, + (const char **) &malware_name_internal); + break; + } + } + } + } + } + else /* no virus found */ + malware_name = NULL; - /* it's possible that avp returns av_buffer[1] == 1 but the - reportsize is 0 (!?) */ - if (kav_reportlen > 0) { - /* set up match regex, depends on retcode */ - kav_re = pcre_compile( kav_rc == 3 - ? "suspicion:\\s*(.+?)\\s*$" - : "infected:\\s*(.+?)\\s*$", - PCRE_COPT, - (const char **)&rerror, - &roffset, - NULL ); - - /* read report, linewise */ - while (kav_reportlen > 0) { - int result = 0; - int ovector[10*3]; - - bread = 0; - while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { - kav_reportlen--; - if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; - bread++; - } - bread++; - tmpbuf[bread] = '\0'; - - /* try matcher on the line, grab substring */ - result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, - ovector, nelements(ovector)); - if (result >= 2) { - pcre_get_substring(CS tmpbuf, ovector, result, 1, - (const char **) &malware_name_internal); - break; - } - } - } - } + (void)close(sock); + break; } - else /* no virus found */ - malware_name = NULL; - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - - - /* "cmdline" scanner type ------------------------------------------------ */ - else if (strcmpic(scanner_name,US"cmdline") == 0) { - uschar *cmdline_scanner; - uschar *cmdline_trigger; - const pcre *cmdline_trigger_re; - uschar *cmdline_regex; - const pcre *cmdline_regex_re; - uschar * file_name; - uschar * commandline; - void (*eximsigchld)(int); - void (*eximsigpipe)(int); - FILE *scanner_out = NULL; - FILE *scanner_record = NULL; - uschar linebuffer[32767]; - int trigger = 0; - int result; - int ovector[10*3]; - uschar *p; - BOOL fits; - - /* find scanner command line */ - if (!(cmdline_scanner = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - return cmdl_errlog_defer("missing commandline specification"); - - /* find scanner output trigger */ - if (!(cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - return cmdl_errlog_defer("missing trigger specification"); - - /* precompile trigger regex */ - cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (cmdline_trigger_re == NULL) - return cmdl_errlog_defer( - string_sprintf("regular expression error in '%s': %s at offset %d", - cmdline_trigger, rerror, roffset)); - - /* find scanner name regex */ - if (!(cmdline_regex = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - return cmdl_errlog_defer("missing virus name regex specification"); - - /* precompile name regex */ - cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (cmdline_regex_re == NULL) - return cmdl_errlog_defer( - string_sprintf("regular expression error in '%s': %s at offset %d", - cmdline_regex, rerror, roffset)); - - /* prepare scanner call; despite the naming, file_name holds a directory - name which is documented as the value given to %s. */ - - file_name = string_copy(eml_filename); - p = Ustrrchr(file_name, '/'); - if (p) - *p = '\0'; - commandline = string_sprintf(CS cmdline_scanner, file_name); - - /* 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); - - /* store exims signal handlers */ - eximsigchld = signal(SIGCHLD,SIG_DFL); - eximsigpipe = signal(SIGPIPE,SIG_DFL); - - scanner_out = popen(CS commandline,"r"); - if (scanner_out == NULL) { - int err = errno; - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return cmdl_errlog_defer( - string_sprintf("call (%s) failed: %s.", commandline, strerror(err))); - } + case M_CMDL: /* "cmdline" scanner type ---------------------------------- */ + { + const uschar *cmdline_scanner = scanner_options; + uschar *cmdline_trigger; + const pcre *cmdline_trigger_re; + uschar *cmdline_regex; + const pcre *cmdline_regex_re; + uschar * file_name; + uschar * commandline; + void (*eximsigchld)(int); + void (*eximsigpipe)(int); + FILE *scanner_out = NULL; + FILE *scanner_record = NULL; + uschar linebuffer[32767]; + int trigger = 0; + int result; + int ovector[10*3]; + uschar *p; + BOOL fits; + + if (!cmdline_scanner) + return cmdl_errlog_defer("missing commandline specification"); + + /* find scanner output trigger */ + if (!(cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) + return cmdl_errlog_defer("missing trigger specification"); + + /* precompile trigger regex */ + cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (cmdline_trigger_re == NULL) + return cmdl_errlog_defer( + string_sprintf("regular expression error in '%s': %s at offset %d", + cmdline_trigger, rerror, roffset)); - 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) { - int err = errno; - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return cmdl_errlog_defer( - string_sprintf("opening scanner output file (%s) failed: %s.", - file_name, strerror(err))); - } + /* find scanner name regex */ + if (!(cmdline_regex = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) + return cmdl_errlog_defer("missing virus name regex specification"); - /* look for trigger while recording output */ - while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out) != NULL) { - if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { - /* short write */ - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); + /* precompile name regex */ + cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (cmdline_regex_re == NULL) return cmdl_errlog_defer( - string_sprintf("short write on scanner output file (%s).", file_name)); - } - /* try trigger match */ - if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) - trigger = 1; - } + string_sprintf("regular expression error in '%s': %s at offset %d", + cmdline_regex, rerror, roffset)); - (void)fclose(scanner_record); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - - if (trigger) { - /* 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,32767,scanner_record) != NULL) { - /* try match */ - result = pcre_exec(cmdline_regex_re, NULL, - CS linebuffer, Ustrlen(linebuffer), 0, 0, - ovector, nelements(ovector)); - if (result >= 2) - pcre_get_substring(CS linebuffer, ovector, result, 1, - (const char **) &malware_name_internal); - } - (void)fclose(scanner_record); - } - else /* no virus found */ - malware_name = NULL; - } - /* ----------------------------------------------------------------------- */ + /* prepare scanner call; despite the naming, file_name holds a directory + name which is documented as the value given to %s. */ + file_name = string_copy(eml_filename); + p = Ustrrchr(file_name, '/'); + if (p) + *p = '\0'; + commandline = string_sprintf(CS cmdline_scanner, file_name); - /* "sophie" scanner type ------------------------------------------------- */ - else if (strcmpic(scanner_name,US"sophie") == 0) { - uschar * soph_options; - uschar soph_default_options[] = "/var/run/sophie"; - int bread = 0; - struct sockaddr_un server; - int sock; - uschar *p; - uschar * file_name; - uschar av_buffer[1024]; + /* redirect STDERR too */ + commandline = string_sprintf("%s 2>&1", commandline); - if (!(soph_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - soph_options = soph_default_options; + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline); - if((sock = m_unixsocket(soph_options, &errstr)) < 0) - return soph_errlog_defer(errstr); + /* store exims signal handlers */ + eximsigchld = signal(SIGCHLD,SIG_DFL); + eximsigpipe = signal(SIGPIPE,SIG_DFL); - /* pass the scan directory to sophie */ - file_name = string_copy(eml_filename); - if ((p = Ustrrchr(file_name, '/'))) - *p = '\0'; - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, soph_options); + scanner_out = popen(CS commandline,"r"); + if (scanner_out == NULL) { + int err = errno; + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return cmdl_errlog_defer( + string_sprintf("call (%s) failed: %s.", commandline, strerror(err))); + } - if ( write(sock, file_name, Ustrlen(file_name)) < 0 - || write(sock, "\n", 1) != 1 - ) { - (void)close(sock); - return soph_errlog_defer( - string_sprintf("unable to write to UNIX socket (%s)", soph_options)); - } + file_name = string_sprintf("%s/scan/%s/%s_scanner_output", + spool_directory, message_id, message_id); + scanner_record = modefopen(file_name, "wb", SPOOL_MODE); - /* wait for result */ - memset(av_buffer, 0, sizeof(av_buffer)); - if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) { - (void)close(sock); - return soph_errlog_defer( - string_sprintf("unable to read from UNIX socket (%s)", soph_options)); - } + if (scanner_record == NULL) { + int err = errno; + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return cmdl_errlog_defer( + string_sprintf("opening scanner output file (%s) failed: %s.", + file_name, strerror(err))); + } - (void)close(sock); + /* look for trigger while recording output */ + while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out) != NULL) { + if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { + /* short write */ + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return cmdl_errlog_defer( + string_sprintf("short write on scanner output file (%s).", file_name)); + } + /* try trigger match */ + if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) + trigger = 1; + } - /* infected ? */ - if (av_buffer[0] == '1') { - if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; - malware_name_internal = string_copy(&av_buffer[2]); - malware_name = malware_name_internal; + (void)fclose(scanner_record); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + + if (trigger) { + /* 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,32767,scanner_record) != NULL) { + /* try match */ + result = pcre_exec(cmdline_regex_re, NULL, + CS linebuffer, Ustrlen(linebuffer), 0, 0, + ovector, nelements(ovector)); + if (result >= 2) + pcre_get_substring(CS linebuffer, ovector, result, 1, + (const char **) &malware_name_internal); + } + (void)fclose(scanner_record); + } + else /* no virus found */ + malware_name = NULL; + break; } - else if (!strncmp(CS av_buffer, "-1", 2)) - return soph_errlog_defer("scanner reported error"); - else /* all ok, no virus */ - malware_name = NULL; - } - /* ----------------------------------------------------------------------- */ - - - /* "clamd" scanner type ------------------------------------------------- */ - /* This code was originally contributed by David Saez */ - /* There are three scanning methods available to us: - * (1) Use the SCAN command, pointing to a file in the filesystem - * (2) Use the STREAM command, send the data on a separate port - * (3) Use the zINSTREAM command, send the data inline - * 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. - * See Exim bug 926 for details. */ - else if (strcmpic(scanner_name,US"clamd") == 0) { - uschar *clamd_options = NULL; - uschar clamd_options_default[] = "/tmp/clamd"; - uschar *p, *vname, *result_tag, *response_end; - struct sockaddr_un server; - int sock,bread=0; - unsigned int port; - uschar * file_name; - uschar av_buffer[1024]; - uschar *hostname = ""; - struct hostent *he; - struct in_addr in; - uschar *clamav_fbuf; - int clam_fd, result; - unsigned int fsize; - BOOL use_scan_command = FALSE; - clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; - int current_server; - int num_servers = 0; -#ifdef WITH_OLD_CLAMAV_STREAM - uschar av_buffer2[1024]; - int sockData; -#else - uint32_t send_size, send_final_zeroblock; -#endif - - if (!(clamd_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) - clamd_options = clamd_options_default; - - if (*clamd_options == '/') - /* 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 { - uschar *address = clamd_options; - uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20]; - - /* Go through the rest of the list of host/port and construct an array - * of servers to try. The first one is the bit we just passed from - * clamd_options so process that first and then scan the remainder of - * the address buffer */ - 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) { - use_scan_command = TRUE; - continue; - } - /* XXX: If unsuccessful we should free this memory */ - this_clamd = - (clamd_address_container *)store_get(sizeof(clamd_address_container)); + case M_SOPHIE: /* "sophie" scanner type --------------------------------- */ + { + int bread = 0; + struct sockaddr_un server; + int sock; + uschar *p; + uschar * file_name; + uschar av_buffer[1024]; + + if((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return soph_errlog_defer(errstr); + + /* pass the scan directory to sophie */ + file_name = string_copy(eml_filename); + if ((p = Ustrrchr(file_name, '/'))) + *p = '\0'; + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); + + if ( write(sock, file_name, Ustrlen(file_name)) < 0 + || write(sock, "\n", 1) != 1 + ) { + (void)close(sock); + return soph_errlog_defer( + string_sprintf("unable to write to UNIX socket (%s)", scanner_options)); + } - /* 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 ) { - clmd_errlog(string_sprintf("invalid address '%s'", address)); - continue; - } + /* wait for result */ + memset(av_buffer, 0, sizeof(av_buffer)); + if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) { + (void)close(sock); + return soph_errlog_defer( + string_sprintf("unable to read from UNIX socket (%s)", scanner_options)); + } - clamd_address_vector[num_servers] = this_clamd; - num_servers++; - if (num_servers >= MAX_CLAMD_SERVERS) { - clmd_errlog("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, - address_buffer, - sizeof(address_buffer))) != NULL); + (void)close(sock); - /* check if we have at least one server */ - if (!num_servers) - return clmd_errlog_defer("no useable server addresses in malware configuration option."); + /* infected ? */ + if (av_buffer[0] == '1') { + if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; + malware_name_internal = string_copy(&av_buffer[2]); + malware_name = malware_name_internal; + } + else if (!strncmp(CS av_buffer, "-1", 2)) + return soph_errlog_defer("scanner reported error"); + else /* all ok, no virus */ + malware_name = NULL; + break; } - /* 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 clmd_errlog_defer( - string_sprintf("local/SCAN mode incompatible with" \ - " : in path to email filename [%s]", eml_filename)); - - /* 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. */ - - while ( num_servers > 0 ) { - /* Randomly pick a server to start with */ - current_server = random_number( num_servers ); - - debug_printf("trying server name %s, port %u\n", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->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) */ - if((he = gethostbyname(CS clamd_address_vector[current_server]->tcp_addr)) - == 0) { - clmd_errlog(string_sprintf("failed to lookup host '%s'", - clamd_address_vector[current_server]->tcp_addr)); - goto try_next_server; - } - - in = *(struct in_addr *) he->h_addr_list[0]; + case M_CLAMD: /* "clamd" scanner type ----------------------------------- */ + { + /* This code was originally contributed by David Saez */ + /* There are three scanning methods available to us: + * (1) Use the SCAN command, pointing to a file in the filesystem + * (2) Use the STREAM command, send the data on a separate port + * (3) Use the zINSTREAM command, send the data inline + * 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. + * See Exim bug 926 for details. */ + + uschar *p, *vname, *result_tag, *response_end; + struct sockaddr_un server; + int sock,bread=0; + unsigned int port; + uschar * file_name; + uschar av_buffer[1024]; + uschar *hostname = ""; + struct hostent *he; + struct in_addr in; + uschar *clamav_fbuf; + int clam_fd, result; + unsigned int fsize; + BOOL use_scan_command = FALSE; + clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; + int current_server; + int num_servers = 0; + #ifdef WITH_OLD_CLAMAV_STREAM + uschar av_buffer2[1024]; + int sockData; + #else + uint32_t send_size, send_final_zeroblock; + #endif + + if (*scanner_options == '/') + /* 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 { + const uschar *address = scanner_options; + uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20]; + + /* Go through the rest of the list of host/port and construct an array + * 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 { + 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) { + use_scan_command = TRUE; + continue; + } + + /* XXX: If unsuccessful we should free this memory */ + this_clamd = + (clamd_address_container *)store_get(sizeof(clamd_address_container)); + + /* 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 ) { + clmd_errlog(string_sprintf("invalid address '%s'", address)); + continue; + } + + clamd_address_vector[num_servers] = this_clamd; + num_servers++; + if (num_servers >= MAX_CLAMD_SERVERS) { + clmd_errlog("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, + address_buffer, + sizeof(address_buffer))) != NULL); + + /* check if we have at least one server */ + if (!num_servers) + return clmd_errlog_defer("no useable server addresses in malware configuration option."); + } - /* Open the ClamAV Socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - clmd_errlog(string_sprintf("unable to acquire socket (%s)", - strerror(errno))); - goto try_next_server; - } + /* 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 clmd_errlog_defer( + string_sprintf("local/SCAN mode incompatible with" \ + " : in path to email filename [%s]", eml_filename)); + + /* 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. */ + + while ( num_servers > 0 ) { + /* Randomly pick a server to start with */ + current_server = random_number( num_servers ); + + debug_printf("trying server name %s, port %u\n", + clamd_address_vector[current_server]->tcp_addr, + clamd_address_vector[current_server]->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) */ + if((he = gethostbyname(CS clamd_address_vector[current_server]->tcp_addr)) + == 0) { + clmd_errlog(string_sprintf("failed to lookup host '%s'", + clamd_address_vector[current_server]->tcp_addr)); + goto try_next_server; + } + + in = *(struct in_addr *) he->h_addr_list[0]; + + /* Open the ClamAV Socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + clmd_errlog(string_sprintf("unable to acquire socket (%s)", + strerror(errno))); + goto try_next_server; + } + + if (ip_connect( sock, + AF_INET, + (uschar*)inet_ntoa(in), + clamd_address_vector[current_server]->tcp_port, + 5 ) > -1) { + /* Connection successfully established with a server */ + hostname = clamd_address_vector[current_server]->tcp_addr; + break; + } else { + clmd_errlog(string_sprintf( + "malware acl condition: clamd: connection to %s, port %u failed (%s)", + clamd_address_vector[current_server]->tcp_addr, + clamd_address_vector[current_server]->tcp_port, + strerror(errno))); + + (void)close(sock); + } + + try_next_server: + /* Remove the server from the list. XXX We should free the memory */ + num_servers--; + int i; + for( i = current_server; i < num_servers; i++ ) + clamd_address_vector[i] = clamd_address_vector[i+1]; + } + + if ( num_servers == 0 ) + return clmd_errlog_defer("all servers failed"); + + } else { + if((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return clmd_errlog_defer(errstr); + } - if (ip_connect( sock, - AF_INET, - (uschar*)inet_ntoa(in), - clamd_address_vector[current_server]->tcp_port, - 5 ) > -1) { - /* Connection successfully established with a server */ - hostname = clamd_address_vector[current_server]->tcp_addr; - break; - } else { - clmd_errlog(string_sprintf( - "malware acl condition: clamd: connection to %s, port %u failed (%s)", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port, - strerror(errno))); - - (void)close(sock); - } + /* 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. */ -try_next_server: - /* Remove the server from the list. XXX We should free the memory */ - num_servers--; - int i; - for( i = current_server; i < num_servers; i++ ) - clamd_address_vector[i] = clamd_address_vector[i+1]; - } + if (!use_scan_command) { - if ( num_servers == 0 ) - return clmd_errlog_defer("all servers failed"); + #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. */ - } else { - if((sock = m_unixsocket(clamd_options, &errstr)) < 0) - return clmd_errlog_defer(errstr); - } + DEBUG(D_acl) debug_printf("Malware scan: issuing %s old-style remote scan (PORT)\n", + scanner_name); - /* 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. */ + /* Pass the string to ClamAV (7 = "STREAM\n") */ + if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0) + return clmd_errlog_defer(errstr); - if (!use_scan_command) { + memset(av_buffer2, 0, sizeof(av_buffer2)); + bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT); -#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. */ + if (bread < 0) { + int err = errno; + (void)close(sock); + return clmd_errlog_defer( + string_sprintf("unable to read PORT from socket (%s)", + strerror(err))); + } + + if (bread == sizeof(av_buffer2)) { + (void)close(sock); + return clmd_errlog_defer("buffer too small"); + } + + if (!(*av_buffer2)) { + (void)close(sock); + return clmd_errlog_defer("ClamAV returned null"); + } + + av_buffer2[bread] = '\0'; + if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) { + (void)close(sock); + return clmd_errlog_defer( + string_sprintf("Expected port information from clamd, got '%s'", + av_buffer2)); + } + + if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + int err = errno; + (void)close(sock); + return clmd_errlog_defer( + string_sprintf("unable to acquire socket (%s)", strerror(err))); + } - DEBUG(D_acl) debug_printf("Malware scan: issuing %s old-style remote scan (PORT)\n", - scanner_name); + if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { + int err = errno; + (void)close(sockData); (void)close(sock); + return clmd_errlog_defer( + string_sprintf("connection to %s, port %u failed (%s)", + inet_ntoa(in), port, strerror(err))); + } + + #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. */ + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", + scanner_name); + + /* Pass the string to ClamAV (10 = "zINSTREAM\0") */ + if (send(sock, "zINSTREAM", 10, 0) < 0) { + int err = errno; + (void)close(sock); + return clmd_errlog_defer( + string_sprintf("unable to send zINSTREAM to socket (%s)", + strerror(err))); + } + + #define CLOSE_SOCKDATA /**/ + #endif + + /* calc file size */ + clam_fd = open(CS eml_filename, O_RDONLY); + if (clam_fd == -1) { + int err = errno; + CLOSE_SOCKDATA; (void)close(sock); + return clmd_errlog_defer( + string_sprintf("can't open spool file %s: %s", + eml_filename, strerror(err))); + } + fsize = lseek(clam_fd, 0, SEEK_END); + if (fsize == -1) { + int err = errno; + CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); + return clmd_errlog_defer( + string_sprintf("can't seek spool file %s: %s", + eml_filename, strerror(errno))); + } + lseek(clam_fd, 0, SEEK_SET); + + clamav_fbuf = (uschar *) malloc (fsize); + if (!clamav_fbuf) { + CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); + return clmd_errlog_defer( + string_sprintf("unable to allocate memory %u for file (%s)", + fsize, eml_filename)); + } + + result = read (clam_fd, clamav_fbuf, fsize); + if (result == -1) { + int err = errno; + CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); + free(clamav_fbuf); + return clmd_errlog_defer( + 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, 0) < 0) { + CLOSE_SOCKDATA; (void)close(sock); + free(clamav_fbuf); + return clmd_errlog_defer( + string_sprintf("unable to send file body to socket (%s:%u)", + hostname, port); + } + #else + send_size = htonl(fsize); + send_final_zeroblock = 0; + if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || + (send(sock, clamav_fbuf, fsize, 0) < 0) || + (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) + { + (void)close(sock); + free(clamav_fbuf); + return clmd_errlog_defer( + string_sprintf("unable to send file body to socket (%s:%u)", + hostname, port)); + } + #endif + + free(clamav_fbuf); + + CLOSE_SOCKDATA; + #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 */ + + /* ================================================================= */ + + /* Prior to the reworking post-Exim-4.72, this scanned a directory, + which dates to when ClamAV needed us to break apart the email into the + MIME parts (eg, with the now deprecated demime condition coming first). + Some time back, ClamAV gained the ability to deconstruct the emails, so + doing this would actually have resulted in the mail attachments being + scanned twice, in the broken out files and from the original .eml. + Since ClamAV now handles emails (and has for quite some time) we can + just use the email file itself. */ + /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ + file_name = string_sprintf("SCAN %s\n", eml_filename); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n", + scanner_name, scanner_options); + + if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { + int err = errno; + (void)close(sock); + return clmd_errlog_defer( + string_sprintf("unable to write to socket (%s)", strerror(err))); + } - /* Pass the string to ClamAV (7 = "STREAM\n") */ - if (m_sock_send(sock, "STREAM\n", 7, &errstr) < 0) - return clmd_errlog_defer(errstr); + /* 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. */ - memset(av_buffer2, 0, sizeof(av_buffer2)); - bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT); + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); + (void)close(sock); - if (bread < 0) { - int err = errno; - (void)close(sock); + if (!(bread > 0)) return clmd_errlog_defer( - string_sprintf("unable to read PORT from socket (%s)", - strerror(err))); - } + string_sprintf("unable to read from socket (%s)", strerror(errno))); - if (bread == sizeof(av_buffer2)) { - (void)close(sock); + if (bread == sizeof(av_buffer)) return clmd_errlog_defer("buffer too small"); - } - - if (!(*av_buffer2)) { - (void)close(sock); + /* We're now assured of a NULL at the end of av_buffer */ + + /* Check the result. ClamAV returns one of two result formats. + In the basic mode, the response is of the form: + infected: -> ": FOUND" + not-infected: -> ": OK" + error: -> ": ERROR + If the ExtendedDetectionInfo option has been turned on, then we get: + ": (:) FOUND" + for the infected case. Compare: + /tmp/eicar.com: Eicar-Test-Signature FOUND + /tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND + + In the streaming case, clamd uses the filename "stream" which you should + be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The + client app will replace "stream" with the original filename before returning + results to stdout, but the trace shows the data). + + We will assume that the pathname passed to clamd from Exim does not contain + a colon. We will have whined loudly above if the eml_filename does (and we're + passing a filename to clamd). */ + + if (!(*av_buffer)) return clmd_errlog_defer("ClamAV returned null"); - } - av_buffer2[bread] = '\0'; - if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) { - (void)close(sock); - return clmd_errlog_defer( - string_sprintf("Expected port information from clamd, got '%s'", - av_buffer2)); - } + /* 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 ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - int err = errno; - (void)close(sock); - return clmd_errlog_defer( - string_sprintf("unable to acquire socket (%s)", strerror(err))); - } + DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); - if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - int err = errno; - (void)close(sockData); (void)close(sock); - return clmd_errlog_defer( - string_sprintf("connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(err))); - } - -#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. */ - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", - scanner_name); - - /* Pass the string to ClamAV (10 = "zINSTREAM\0") */ - if (send(sock, "zINSTREAM", 10, 0) < 0) { - int err = errno; - (void)close(sock); - return clmd_errlog_defer( - string_sprintf("unable to send zINSTREAM to socket (%s)", - strerror(err))); - } - -#define CLOSE_SOCKDATA /**/ -#endif - - /* calc file size */ - clam_fd = open(CS eml_filename, O_RDONLY); - if (clam_fd == -1) { - int err = errno; - CLOSE_SOCKDATA; (void)close(sock); - return clmd_errlog_defer( - string_sprintf("can't open spool file %s: %s", - eml_filename, strerror(err))); - } - fsize = lseek(clam_fd, 0, SEEK_END); - if (fsize == -1) { - int err = errno; - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - return clmd_errlog_defer( - string_sprintf("can't seek spool file %s: %s", - eml_filename, strerror(errno))); - } - lseek(clam_fd, 0, SEEK_SET); - - clamav_fbuf = (uschar *) malloc (fsize); - if (!clamav_fbuf) { - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - return clmd_errlog_defer( - string_sprintf("unable to allocate memory %u for file (%s)", - fsize, eml_filename)); - } + while (isspace(*--p) && (p > av_buffer)) + *p = '\0'; + if (*p) ++p; + response_end = p; - result = read (clam_fd, clamav_fbuf, fsize); - if (result == -1) { - int err = errno; - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - free(clamav_fbuf); - return clmd_errlog_defer( - 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, 0) < 0) { - CLOSE_SOCKDATA; (void)close(sock); - free(clamav_fbuf); + /* colon in returned output? */ + if((p = Ustrchr(av_buffer,':')) == NULL) return clmd_errlog_defer( - string_sprintf("unable to send file body to socket (%s:%u)", - hostname, port); - } -#else - send_size = htonl(fsize); - send_final_zeroblock = 0; - if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || - (send(sock, clamav_fbuf, fsize, 0) < 0) || - (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) - { - (void)close(sock); - free(clamav_fbuf); + string_sprintf("ClamAV returned malformed result (missing colon): %s", + av_buffer)); + + /* strip filename */ + while (*p && isspace(*++p)) /**/; + vname = p; + + /* It would be bad to encounter a virus with "FOUND" in part of the name, + but we should at least be resistant to it. */ + p = Ustrrchr(vname, ' '); + result_tag = p ? p+1 : vname; + + 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 == ')') { + /* "(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_internal = string_copy(vname); + malware_name = malware_name_internal; + DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); + + } else if (Ustrcmp(result_tag, "ERROR") == 0) return clmd_errlog_defer( - string_sprintf("unable to send file body to socket (%s:%u)", - hostname, port)); - } -#endif - - free(clamav_fbuf); - - CLOSE_SOCKDATA; -#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 */ - -/* ================================================================= */ - - /* Prior to the reworking post-Exim-4.72, this scanned a directory, - which dates to when ClamAV needed us to break apart the email into the - MIME parts (eg, with the now deprecated demime condition coming first). - Some time back, ClamAV gained the ability to deconstruct the emails, so - doing this would actually have resulted in the mail attachments being - scanned twice, in the broken out files and from the original .eml. - Since ClamAV now handles emails (and has for quite some time) we can - just use the email file itself. */ - /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ - file_name = string_sprintf("SCAN %s\n", eml_filename); + string_sprintf("ClamAV returned: %s", av_buffer)); - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n", - scanner_name, clamd_options); + else if (Ustrcmp(result_tag, "OK") == 0) { + /* Everything should be OK */ + malware_name = NULL; + DEBUG(D_acl) debug_printf("Malware not found\n"); - if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { - int err = errno; - (void)close(sock); + } else return clmd_errlog_defer( - string_sprintf("unable to write to socket (%s)", strerror(err))); - } + string_sprintf("unparseable response from ClamAV: {%s}", av_buffer)); - /* 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); - (void)close(sock); - - if (!(bread > 0)) - return clmd_errlog_defer( - string_sprintf("unable to read from socket (%s)", strerror(errno))); - - if (bread == sizeof(av_buffer)) - return clmd_errlog_defer("buffer too small"); - /* We're now assured of a NULL at the end of av_buffer */ - - /* Check the result. ClamAV returns one of two result formats. - In the basic mode, the response is of the form: - infected: -> ": FOUND" - not-infected: -> ": OK" - error: -> ": ERROR - If the ExtendedDetectionInfo option has been turned on, then we get: - ": (:) FOUND" - for the infected case. Compare: -/tmp/eicar.com: Eicar-Test-Signature FOUND -/tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND - - In the streaming case, clamd uses the filename "stream" which you should - be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The - client app will replace "stream" with the original filename before returning - results to stdout, but the trace shows the data). - - We will assume that the pathname passed to clamd from Exim does not contain - a colon. We will have whined loudly above if the eml_filename does (and we're - passing a filename to clamd). */ - - if (!(*av_buffer)) - return clmd_errlog_defer("ClamAV returned null"); - - /* 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'; - - DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); - - while (isspace(*--p) && (p > av_buffer)) - *p = '\0'; - if (*p) ++p; - response_end = p; - - /* colon in returned output? */ - if((p = Ustrchr(av_buffer,':')) == NULL) - return clmd_errlog_defer( - string_sprintf("ClamAV returned malformed result (missing colon): %s", - av_buffer)); - - /* strip filename */ - while (*p && isspace(*++p)) /**/; - vname = p; - - /* It would be bad to encounter a virus with "FOUND" in part of the name, - but we should at least be resistant to it. */ - p = Ustrrchr(vname, ' '); - result_tag = p ? p+1 : vname; - - 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 == ')') { - /* "(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'; + break; + } /* clamd */ + + case M_MKSD: /* "mksd" scanner type ------------------------------------- */ + { + char *mksd_options_end; + int mksd_maxproc = 1; /* default, if no option supplied */ + struct sockaddr_un server; + int sock; + int retval; + + if (scanner_options) { + mksd_maxproc = (int)strtol(CS scanner_options, &mksd_options_end, 10); + if ( *scanner_options == '\0' + || *mksd_options_end != '\0' + || mksd_maxproc < 1 + || mksd_maxproc > 32 + ) + return mksd_errlog_defer( + string_sprintf("invalid option '%s'", scanner_options)); } - malware_name_internal = string_copy(vname); - malware_name = malware_name_internal; - DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); - } else if (Ustrcmp(result_tag, "ERROR") == 0) - return clmd_errlog_defer( - string_sprintf("ClamAV returned: %s", av_buffer)); + if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + return mksd_errlog_defer(errstr); - else if (Ustrcmp(result_tag, "OK") == 0) { - /* Everything should be OK */ malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware not found\n"); - - } else - return clmd_errlog_defer( - string_sprintf("unparseable response from ClamAV: {%s}", av_buffer)); - - } /* clamd */ - - /* ----------------------------------------------------------------------- */ - - - /* "mksd" scanner type --------------------------------------------------- */ - else if (strcmpic(scanner_name, US"mksd") == 0) { - uschar *mksd_options; - char *mksd_options_end; - int mksd_maxproc = 1; /* default, if no option supplied */ - struct sockaddr_un server; - int sock; - int retval; - - if ((mksd_options = string_nextinlist(&av_scanner_work, &sep, - NULL, 0))) { - mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10); - if ( *mksd_options == '\0' - || *mksd_options_end != '\0' - || mksd_maxproc < 1 - || mksd_maxproc > 32 - ) - return mksd_errlog_defer( - string_sprintf("invalid option '%s'", mksd_options)); - } - if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) - return mksd_errlog_defer(errstr); + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); - malware_name = NULL; - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); - - retval = mksd_scan_packed(sock, eml_filename); + retval = mksd_scan_packed(sock, eml_filename); - if (retval != OK) - return retval; + if (retval != OK) + return retval; + break; + } } - /* ----------------------------------------------------------------------- */ - - /* "unknown" scanner type ------------------------------------------------- */ - else - return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", - scanner_name)); - /* ----------------------------------------------------------------------- */ /* set "been here, done that" marker */ malware_ok = 1; -- 2.30.2