X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/94a18f283972b276150cdf72fef47e56512c11d7..5428a9463ae1080029a84a1b33e4a8a6915c5f28:/src/src/malware.c diff --git a/src/src/malware.c b/src/src/malware.c index eb0c33cea..3660476d2 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -1,5 +1,3 @@ -/* $Cambridge: exim/src/src/malware.c,v 1.21 2010/06/07 00:12:42 pdp Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ @@ -12,6 +10,18 @@ #include "exim.h" #ifdef WITH_CONTENT_SCAN +/* The maximum number of clamd servers that are supported in the configuration */ +#define MAX_CLAMD_SERVERS 32 +#define MAX_CLAMD_SERVERS_S "32" +/* Maximum length of the hostname that can be specified in the clamd address list */ +#define MAX_CLAMD_ADDRESS_LENGTH 64 +#define MAX_CLAMD_ADDRESS_LENGTH_S "64" + +typedef struct clamd_address_container { + uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH]; + unsigned int tcp_port; +} clamd_address_container; + /* declaration of private routines */ static int mksd_scan_packed(int sock, uschar *scan_filename); static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking); @@ -71,17 +81,22 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) int malware(uschar **listptr) { uschar scan_filename[1024]; BOOL fits; + int ret; fits = string_format(scan_filename, sizeof(scan_filename), CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); if (!fits) { + av_failed = TRUE; log_write(0, LOG_MAIN|LOG_PANIC, "malware filename does not fit in buffer [malware()]"); return DEFER; } - return malware_internal(listptr, scan_filename, FALSE); + ret = malware_internal(listptr, scan_filename, FALSE); + if (ret == DEFER) av_failed = TRUE; + + return ret; } @@ -112,7 +127,7 @@ malware_in_file(uschar *eml_filename) { /* spool_mbox() assumes various parameters exist, when creating the relevant directory and the email within */ (void) string_format(message_id_buf, sizeof(message_id_buf), - "dummy-%d", pseudo_random_number(INT_MAX)); + "dummy-%d", vaguely_random_number(INT_MAX)); message_id = message_id_buf; sender_address = US"malware-sender@example.net"; return_path = US""; @@ -128,6 +143,9 @@ malware_in_file(uschar *eml_filename) { set, but if that changes, then it should apply to these tests too */ unspool_mbox(); + /* silence static analysis tools */ + message_id = NULL; + return ret; } @@ -592,7 +610,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) } } else { - char *drweb_s = NULL; + const char *drweb_s = NULL; if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; @@ -1068,7 +1086,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); if (cmdline_trigger_re == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger_re, rerror, roffset); + "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger, rerror, roffset); return DEFER; }; @@ -1086,7 +1104,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); if (cmdline_regex_re == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex_re, rerror, roffset); + "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex, rerror, roffset); return DEFER; }; @@ -1208,7 +1226,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) sizeof(sophie_options_buffer))) == NULL) { /* no options supplied, use default options */ sophie_options = sophie_options_default; - }; + } /* open the sophie socket */ sock = socket(AF_UNIX, SOCK_STREAM, 0); @@ -1243,14 +1261,14 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, sophie_options); - if (write(sock, file_name, Ustrlen(file_name)) < 0) { + if ( write(sock, file_name, Ustrlen(file_name)) < 0 + || write(sock, "\n", 1) != 1 + ) { (void)close(sock); log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options); return DEFER; - }; - - (void)write(sock, "\n", 1); + } /* wait for result */ memset(av_buffer, 0, sizeof(av_buffer)); @@ -1259,7 +1277,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options); return DEFER; - }; + } (void)close(sock); @@ -1277,7 +1295,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) else { /* all ok, no virus */ malware_name = NULL; - }; + } } /* ----------------------------------------------------------------------- */ @@ -1295,7 +1313,7 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) * WITH_OLD_CLAMAV_STREAM is defined. * See Exim bug 926 for details. */ else if (strcmpic(scanner_name,US"clamd") == 0) { - uschar *clamd_options; + uschar *clamd_options = NULL; uschar clamd_options_buffer[1024]; uschar clamd_options_default[] = "/tmp/clamd"; uschar *p, *vname, *result_tag, *response_end; @@ -1304,16 +1322,16 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) unsigned int port; uschar file_name[1024]; uschar av_buffer[1024]; - uschar hostname[256]; + uschar *hostname = ""; struct hostent *he; struct in_addr in; - uschar *clamd_options2; - uschar clamd_options2_buffer[1024]; - uschar clamd_options2_default[] = ""; uschar *clamav_fbuf; int clam_fd, result; unsigned int fsize; - BOOL use_scan_command, fits; + BOOL use_scan_command = FALSE, fits; + 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; @@ -1327,16 +1345,60 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) /* no options supplied, use default options */ clamd_options = clamd_options_default; } - if ((clamd_options2 = string_nextinlist(&av_scanner_work, &sep, - clamd_options2_buffer, - sizeof(clamd_options2_buffer))) == NULL) { - clamd_options2 = clamd_options2_default; - } - if ((*clamd_options == '/') || (strcmpic(clamd_options2,US"local") == 0)) + 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 - use_scan_command = FALSE; + 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)); + + /* 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 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: invalid address '%s'", address); + continue; + } + + clamd_address_vector[num_servers] = this_clamd; + num_servers++; + if (num_servers >= MAX_CLAMD_SERVERS) { + log_write(0, LOG_MAIN|LOG_PANIC, + "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) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: no useable clamd server addresses in malware configuration option."); + return DEFER; + } + } /* See the discussion of response formats below to see why we really don't like colons in filenames when passing filenames to ClamAV. */ @@ -1347,45 +1409,72 @@ static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) return DEFER; } - /* socket does not start with '/' -> network socket */ - if (*clamd_options != '/') { + /* 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. */ - /* extract host and port part */ - if( sscanf(CS clamd_options, "%s %u", hostname, &port) != 2 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: invalid socket '%s'", clamd_options); - return DEFER; - }; + while ( num_servers > 0 ) { + /* Randomly pick a server to start with */ + current_server = random_number( num_servers ); - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: failed to lookup host '%s'", hostname); - return DEFER; - } + debug_printf("trying server name %s, port %u\n", + clamd_address_vector[current_server]->tcp_addr, + clamd_address_vector[current_server]->tcp_port); - in = *(struct in_addr *) he->h_addr_list[0]; + /* 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) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: failed to lookup host '%s'", + clamd_address_vector[current_server]->tcp_addr + ); + goto try_next_server; + } - /* Open the ClamAV Socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } + in = *(struct in_addr *) he->h_addr_list[0]; - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(errno)); - return DEFER; + /* Open the ClamAV Socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: 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 { + log_write(0, LOG_MAIN|LOG_PANIC, + "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 ) { + log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: all clamd servers failed"); + return DEFER; + } } else { /* open the local socket */ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { @@ -1910,14 +1999,14 @@ static int mksd_parse_line (char *line) static int mksd_scan_packed(int sock, uschar *scan_filename) { struct iovec iov[3]; - char *cmd = "MSQ\n"; + const char *cmd = "MSQ\n"; uschar av_buffer[1024]; - iov[0].iov_base = cmd; + iov[0].iov_base = (void *) cmd; iov[0].iov_len = 3; iov[1].iov_base = CS scan_filename; iov[1].iov_len = Ustrlen(scan_filename); - iov[2].iov_base = cmd + 3; + iov[2].iov_base = (void *) (cmd + 3); iov[2].iov_len = 1; if (mksd_writev (sock, iov, 3) < 0)