[exim] Patch: ClamAV server failover

Top Page

Reply to this message
Author: Adam Stephens
Date:  
To: exim-users
Subject: [exim] Patch: ClamAV server failover

We've had some problems with our ClamAV servers recently. Exim will only
try one of them, so when one of the servers is unavailable the messages
it would have scanned are deferred (or not scanned, if you have
/defer_ok set).

The attached patch makes Exim try multiple clamAV servers; it still only
accepts one hostname in the configuration file, but with this patch Exim
will try each of the IP addresses associated with that name until it
finds one that works, or it's tried them all.

We've been using this in production for about a month, with no problems.
As usual, however, if you do use it you do so at your own risk!

Adam.
--

--------------------------------
Adam Stephens
Network Specialist - Email & DNS
adam.stephens@???

--- malware.c.orig    Thu Jun 7 09:58:08 2007
+++ malware.c    Tue Jun 12 15:31:10 2007
@@ -1016,7 +1016,7 @@
       uschar clamd_options_default[] = "/tmp/clamd";
       uschar *p,*vname;
       struct sockaddr_un server;
-      int sock,bread=0;
+      int sock,count=0,bread=0;
       unsigned int port;
       uschar file_name[1024];
       uschar av_buffer[1024];
@@ -1061,8 +1061,8 @@
           return DEFER;
         }


-        in = *(struct in_addr *) he->h_addr_list[0];
-
+    /*try all returned hostnames until one works (or we run out)*/
+        while (he->h_addr_list[count]) {
         /* Open the ClamAV Socket */
         if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
           log_write(0, LOG_MAIN|LOG_PANIC,
@@ -1071,12 +1071,14 @@
           return DEFER;
         }


+    in = *(struct in_addr *) he->h_addr_list[count];
         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;
+     count++; 
+     continue;
         }


         if (strcmpic(clamd_options2,US"local") == 0) {
@@ -1089,7 +1091,8 @@
             (void)close(sock);
             log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
                   strerror(errno));
-            return DEFER;
+            count++;
+        continue;
           }
         } else {


@@ -1099,35 +1102,44 @@
             (void)close(sock);
             log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
                   strerror(errno));
-            return DEFER;
+           count++;
+       continue; 
           }
           memset(av_buffer2, 0, sizeof(av_buffer2));
           bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);


           if (bread < 0) {
+        (void)close(sock);
             log_write(0, LOG_MAIN|LOG_PANIC,
                   "malware acl condition: clamd: unable to read PORT from socket (%s)",
                   strerror(errno));
-            return DEFER;
+            count++;
+        continue;
           }


           if (bread == sizeof(av_buffer)) {
+        (void)close(sock);
             log_write(0, LOG_MAIN|LOG_PANIC,
                   "malware acl condition: clamd: buffer too small");
-            return DEFER;
+            count++;
+        continue;
           }


           if (!(*av_buffer2)) {
+        (void)close(sock);
             log_write(0, LOG_MAIN|LOG_PANIC,
                   "malware acl condition: clamd: ClamAV returned null");
-            return DEFER;
+        count++;
+        continue;
           }


           av_buffer2[bread] = '\0';
           if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) {
+        (void)close(sock);
             log_write(0, LOG_MAIN|LOG_PANIC,
                     "malware acl condition: clamd: Expected port information from clamd, got '%s'", av_buffer2);
-            return DEFER;
+            count++;
+        continue;
           };


           if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
@@ -1139,10 +1151,12 @@


           if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
             (void)close(sockData);
+        (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;
+            count++;
+        continue;
           }


       (void)string_format(scanrequest, 1024,CS"%s/scan/%s/%s.eml",
@@ -1190,15 +1204,27 @@
     /* send file body to socket */
     if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
       (void)close(sockData);
+      (void)close(sock);
       free(clamav_fbuf);
       log_write(0, LOG_MAIN|LOG_PANIC,
         "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
-      return DEFER;
+      count++;
+      continue;
     }
     free(clamav_fbuf);
           (void)close(sockData);
+    break;
         }
+     } /*while*/
+
+     /*defer if we didn't find a working clamAV server*/
+     if (!he->h_addr_list[count]) {
+      log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: clamd: connection to all clamd hosts failed!");
+      return DEFER;
       }
+
+
+}
       else {
         /* open the local socket */
         if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {