Added patches for remote clamd operation and improved drwebd error handling. Contribu...
[exim.git] / src / src / malware.c
index 8211155..b9a641c 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/malware.c,v 1.3 2004/12/17 14:52:44 ph10 Exp $ */
+/* $Cambridge: exim/src/src/malware.c,v 1.4 2005/01/05 13:33:58 tom Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -24,6 +24,11 @@ int mksd_scan_packed(int sock);
 #define DRWEBD_RETURN_VIRUSES       (1<<0)   /* ask daemon return to us viruses names from report */
 #define DRWEBD_IS_MAIL              (1<<19)  /* say to daemon that format is "archive MAIL" */
 
+#define DERR_READ_ERR               (1<<0)   /* read error */
+#define DERR_NOMEMORY               (1<<2)   /* no memory */
+#define DERR_TIMEOUT                (1<<9)   /* scan timeout has run out */
+#define DERR_BAD_CALL               (1<<15)  /* wrong command */
+
 /* Routine to check whether a system is big- or litte-endian. 
    Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html
    Needed for proper kavdaemon implementation. Sigh. */
@@ -197,6 +202,7 @@ int malware(uschar **listptr) {
                        /* calc file size */
                        drweb_fd = open(CS scanrequest, O_RDONLY);
                        if (drweb_fd == -1) {
+                               close(sock);
                                log_write(0, LOG_MAIN|LOG_PANIC,
                                        "malware acl condition: drweb: can't open spool file %s: %s", 
                                        scanrequest, strerror(errno));
@@ -204,6 +210,8 @@ int malware(uschar **listptr) {
                        }
                        fsize = lseek(drweb_fd, 0, SEEK_END);
                        if (fsize == -1) {
+                               close(sock);
+                               close(drweb_fd);
                                log_write(0, LOG_MAIN|LOG_PANIC,
                                        "malware acl condition: drweb: can't seek spool file %s: %s", 
                                        scanrequest, strerror(errno));
@@ -238,21 +246,23 @@ int malware(uschar **listptr) {
                        if (result == -1) {
                                close(sock);
                                close(drweb_fd);
+                               free(drweb_fbuf);
                                log_write(0, LOG_MAIN|LOG_PANIC,
                                        "malware acl condition: drweb: can't read spool file %s: %s",
                                        scanrequest, strerror(errno));
                                return DEFER; 
                        }
+                       close(drweb_fd);
                        
                        /* send file body to socket */
                        if (send(sock, drweb_fbuf, fsize, 0) < 0) {
                                close(sock);
+                               free(drweb_fbuf);
                                log_write(0, LOG_MAIN|LOG_PANIC,
                                        "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options);
                                return DEFER;
                        }
                        close(drweb_fd);
-                       free(drweb_fbuf);
                }
                else {
                        /* open the drwebd UNIX socket */
@@ -367,6 +377,22 @@ int malware(uschar **listptr) {
                        }
                }
                else {
+                       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) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s);
+                               close(sock);
+                               return DEFER;
+                       }
                        /* no virus found */
                        malware_name = NULL;
                };
@@ -956,6 +982,14 @@ int malware(uschar **listptr) {
       uschar hostname[256];
       struct hostent *he;
       struct in_addr in;
+      uschar *clamd_options2;
+      uschar clamd_options2_buffer[1024];
+      uschar clamd_options2_default[] = "";
+      uschar av_buffer2[1024];
+      uschar *clamav_fbuf;
+      uschar scanrequest[1024];
+      int sockData, clam_fd, result;
+      unsigned int fsize;
 
       if ((clamd_options = string_nextinlist(&av_scanner_work, &sep,
                                              clamd_options_buffer,
@@ -963,6 +997,11 @@ int malware(uschar **listptr) {
         /* 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;
+      }
     
       /* socket does not start with '/' -> network socket */
       if (*clamd_options != '/') {
@@ -998,6 +1037,126 @@ int malware(uschar **listptr) {
                     inet_ntoa(in), port, strerror(errno));
           return DEFER;
         }
+
+        if (strcmpic(clamd_options2,US"local") == 0) {
+
+      /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
+    
+          snprintf(CS file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
+    
+          if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
+            close(sock);
+            log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
+                  strerror(errno));
+            return DEFER;
+          }
+        } else {
+
+      /* Pass the string to ClamAV (7 = "STREAM\n") */
+
+          if (send(sock, "STREAM\n", 7, 0) < 0) {
+            close(sock);
+            log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
+                  strerror(errno));
+            return DEFER;
+          }
+          memset(av_buffer2, 0, sizeof(av_buffer2));
+          bread = read(sock, av_buffer2, sizeof(av_buffer2));
+
+          if (bread < 0) {
+            log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: clamd: unable to read PORT from socket (%s)",
+                  strerror(errno));
+            return DEFER;
+          }
+    
+          if (bread == sizeof(av_buffer)) {
+            log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: clamd: buffer too small");
+            return DEFER;
+          }
+    
+          if (!(*av_buffer2)) {
+            log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: clamd: ClamAV returned null");
+            return DEFER;
+          }
+    
+          av_buffer2[bread] = '\0';
+          if( sscanf(CS av_buffer2, "PORT %hu\n", &port) != 1 ) {
+            log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: clamd: Expected port information from clamd, got '%s'", av_buffer2);
+            return DEFER;
+          };
+    
+          if ( (sockData = 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;
+          }
+    
+          if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
+            close(sockData);
+            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;
+          }
+
+         snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml",
+           spool_directory, message_id, message_id);
+  
+         /* calc file size */
+         clam_fd = open(CS scanrequest, O_RDONLY);
+         if (clam_fd == -1) {
+           log_write(0, LOG_MAIN|LOG_PANIC,
+                   "malware acl condition: clamd: can't open spool file %s: %s", 
+                   scanrequest, strerror(errno));
+           return DEFER; 
+         }
+         fsize = lseek(clam_fd, 0, SEEK_END);
+         if (fsize == -1) {
+           log_write(0, LOG_MAIN|LOG_PANIC,
+                   "malware acl condition: clamd: can't seek spool file %s: %s", 
+                   scanrequest, strerror(errno));
+           return DEFER; 
+         }
+         lseek(clam_fd, 0, SEEK_SET);
+  
+         clamav_fbuf = (uschar *) malloc (fsize);
+         if (!clamav_fbuf) {
+           close(sockData);
+           close(clam_fd);
+           log_write(0, LOG_MAIN|LOG_PANIC,
+                   "malware acl condition: clamd: unable to allocate memory %u for file (%s)", 
+                   fsize, scanrequest);
+           return DEFER;
+         }
+  
+         result = read (clam_fd, clamav_fbuf, fsize);
+         if (result == -1) {
+           close(sockData);
+           close(clam_fd);
+           free(clamav_fbuf);
+           log_write(0, LOG_MAIN|LOG_PANIC,
+                   "malware acl condition: clamd: can't read spool file %s: %s",
+                   scanrequest, strerror(errno));
+           return DEFER; 
+         }
+         close(clam_fd);
+
+         /* send file body to socket */
+         if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
+           close(sockData);
+           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;
+         }
+         free(clamav_fbuf);
+          close(sockData);
+        }
       }
       else {
         /* open the local socket */