#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);
* 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;
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;
/* 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. */
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) {