Extra exiscan source files - first batch
authorTom Kistner <tom@duncanthrax.net>
Fri, 26 Nov 2004 09:13:34 +0000 (09:13 +0000)
committerTom Kistner <tom@duncanthrax.net>
Fri, 26 Nov 2004 09:13:34 +0000 (09:13 +0000)
src/src/malware.c [new file with mode: 0644]
src/src/mime.c [new file with mode: 0644]
src/src/mime.h [new file with mode: 0644]
src/src/spam.c [new file with mode: 0644]
src/src/spam.h [new file with mode: 0644]
src/src/spool_mbox.c [new file with mode: 0644]

diff --git a/src/src/malware.c b/src/src/malware.c
new file mode 100644 (file)
index 0000000..ec5b1fb
--- /dev/null
@@ -0,0 +1,1417 @@
+/* $Cambridge: exim/src/src/malware.c,v 1.1.2.1 2004/11/26 09:13:34 tom Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* This file is part of the exiscan-acl content scanner
+patch. It is NOT part of the standard exim distribution. */
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
+/* License: GPL */
+
+/* Code for calling virus (malware) scanners. Called from acl.c. */
+
+#include "exim.h"
+
+/* declaration of private routines */
+int mksd_scan_packed(int sock);
+int mksd_scan_unpacked(int sock, int maxproc);
+
+/* SHUT_WR seems to be undefined on Unixware ? */
+#ifndef SHUT_WR
+#define SHUT_WR 1
+#endif
+
+#define DRWEBD_SCAN_CMD             (1)     /* scan file, buffer or diskfile */
+#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" */
+
+/* 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. */
+#define BIG_MY_ENDIAN      0
+#define LITTLE_MY_ENDIAN   1
+int test_byte_order(void);
+int test_byte_order() {
+      short int word = 0x0001;
+      char *byte = (char *) &word;
+      return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
+}
+
+uschar malware_name_buffer[256];
+int malware_ok = 0;
+
+int malware(uschar **listptr) {
+  int sep = 0;
+  uschar *list = *listptr;
+  uschar *av_scanner_work = av_scanner;
+  uschar *scanner_name;
+  uschar scanner_name_buffer[16];
+  uschar *malware_regex;
+  uschar malware_regex_buffer[64];
+  uschar malware_regex_default[] = ".+";
+  unsigned long long mbox_size;
+  FILE *mbox_file;
+  int roffset;
+  const pcre *re;
+  const uschar *rerror;
+  
+  /* make sure the eml mbox file is spooled up */
+  mbox_file = spool_mbox(&mbox_size);
+  if (mbox_file == NULL) {
+    /* error while spooling */
+    log_write(0, LOG_MAIN|LOG_PANIC,
+           "malware acl condition: error while creating mbox spool file");
+    return DEFER;  
+  };
+  /* none of our current scanners need the mbox
+     file as a stream, so we can close it right away */
+  fclose(mbox_file);
+  
+  /* extract the malware regex to match against from the option list */
+  if ((malware_regex = string_nextinlist(&list, &sep,
+                                         malware_regex_buffer,
+                                         sizeof(malware_regex_buffer))) != NULL) {
+    
+    /* parse 1st option */
+    if ( (strcmpic(malware_regex,US"false") == 0) || 
+         (Ustrcmp(malware_regex,"0") == 0) ) {
+      /* explicitly no matching */
+      return FAIL;
+    };
+    
+    /* special cases (match anything except empty) */
+    if ( (strcmpic(malware_regex,US"true") == 0) || 
+         (Ustrcmp(malware_regex,"*") == 0) || 
+         (Ustrcmp(malware_regex,"1") == 0) ) {
+      malware_regex = malware_regex_default;
+    };
+  }
+  else {
+    /* empty means "don't match anything" */
+    return FAIL;
+  };
+
+  /* compile the regex, see if it works */
+  re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
+  if (re == NULL) {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset);
+    return DEFER;
+  };
+
+  /* if av_scanner starts with a dollar, expand it first */
+  if (*av_scanner == '$') {
+    av_scanner_work = expand_string(av_scanner);
+    if (av_scanner_work == NULL) {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+           "malware acl condition: av_scanner starts with $, but expansion failed: %s", expand_string_message);
+      return DEFER;
+    }
+    else {
+      debug_printf("Expanded av_scanner global: %s\n", av_scanner_work);
+      /* disable result caching in this case */
+      malware_name = NULL;
+      malware_ok = 0;
+    };
+  }
+
+  /* Do not scan twice. */
+  if (malware_ok == 0) {
+
+    /* find the scanner type from the av_scanner option */
+    if ((scanner_name = string_nextinlist(&av_scanner_work, &sep,
+                                          scanner_name_buffer,
+                                          sizeof(scanner_name_buffer))) == NULL) {
+      /* no scanner given */
+      log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: av_scanner configuration variable is empty");
+      return DEFER;
+    };
+    
+       /* "drweb" scanner type ----------------------------------------------- */
+       /* v0.1 - added support for tcp sockets                                 */
+       /* v0.0 - initial release -- support for unix sockets                   */
+       if (strcmpic(scanner_name,US"drweb") == 0) {
+               uschar *drweb_options;
+               uschar drweb_options_buffer[1024];
+               uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock";
+               struct sockaddr_un server;
+               int sock, port, result, ovector[30];
+               unsigned int fsize;
+               uschar tmpbuf[1024], *drweb_fbuf;
+               uschar scanrequest[1024];
+               uschar drweb_match_string[128];
+               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,
+                       drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) {
+                       /* no options supplied, use default options */
+                       drweb_options = drweb_options_default;
+               };
+  
+               if (*drweb_options != '/') {
+      
+                       /* extract host and port part */
+                       if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: invalid socket '%s'", drweb_options);
+                               return DEFER;
+                       }
+      
+                       /* Lookup the host */
+                       if((he = gethostbyname(CS hostname)) == 0) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: failed to lookup host '%s'", hostname);
+                               return DEFER;
+                       }
+      
+                       in = *(struct in_addr *) he->h_addr_list[0];
+      
+                       /* Open the drwebd TCP socket */
+                       if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: unable to acquire socket (%s)",
+                                       strerror(errno));
+                               return DEFER;
+                       }
+      
+                       if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
+                               close(sock); 
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: connection to %s, port %u failed (%s)",
+                                       inet_ntoa(in), port, strerror(errno));
+                               return DEFER;
+                       }
+  
+                       /* prepare variables */
+                       drweb_cmd = htonl(DRWEBD_SCAN_CMD);
+                       drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
+                       snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", 
+                                   spool_directory, message_id, message_id);
+  
+                       /* calc file size */
+                       drweb_fd = open(CS scanrequest, O_RDONLY);
+                       if (drweb_fd == -1) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: can't open spool file %s: %s", 
+                                       scanrequest, strerror(errno));
+                               return DEFER; 
+                       }
+                       fsize = lseek(drweb_fd, 0, SEEK_END);
+                       if (fsize == -1) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: can't seek spool file %s: %s", 
+                                       scanrequest, strerror(errno));
+                               return DEFER; 
+                       }
+                       drweb_slen = htonl(fsize);
+                       lseek(drweb_fd, 0, SEEK_SET);
+  
+                       /* 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)) {
+                               close(sock); 
+                               close(drweb_fd);
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options);
+                               return DEFER;
+                       }
+  
+                       drweb_fbuf = (uschar *) malloc (fsize);
+                       if (!drweb_fbuf) {
+                               close(sock);
+                               close(drweb_fd);
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: unable to allocate memory %u for file (%s)", 
+                                       fsize, scanrequest);
+                               return DEFER;
+                       }
+  
+                       result = read (drweb_fd, drweb_fbuf, fsize);
+                       if (result == -1) {
+                               close(sock);
+                               close(drweb_fd);
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: can't read spool file %s: %s",
+                                       scanrequest, strerror(errno));
+                               return DEFER; 
+                       }
+                       
+                       /* send file body to socket */
+                       if (send(sock, drweb_fbuf, fsize, 0) < 0) {
+                               close(sock);
+                               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 */
+                       sock = socket(AF_UNIX, SOCK_STREAM, 0);
+                       if (sock < 0) {
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: can't open UNIX socket");
+                               return DEFER; 
+                       }
+                       server.sun_family = AF_UNIX;
+                       Ustrcpy(server.sun_path, drweb_options);
+                       if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+                               close(sock);
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno);
+                               return DEFER;
+                       }
+        
+                       /* prepare variables */
+                       drweb_cmd = htonl(DRWEBD_SCAN_CMD);
+                       drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
+                       snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
+                       drweb_slen = htonl(Ustrlen(scanrequest));
+  
+                       /* 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, scanrequest, Ustrlen(scanrequest), 0) < 0) ||
+                           (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) {
+                               close(sock);
+                               log_write(0, LOG_MAIN|LOG_PANIC,
+                                       "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options);
+                               return DEFER;
+                       }
+               }
+  
+               /* wait for result */
+               if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) {
+                       close(sock);
+                       log_write(0, LOG_MAIN|LOG_PANIC,
+                               "malware acl condition: drweb: unable to read return code");
+                       return DEFER;
+               }
+               drweb_rc = ntohl(drweb_rc);
+      
+               if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) {
+                       close(sock);
+                       log_write(0, LOG_MAIN|LOG_PANIC,
+                               "malware acl condition: drweb: unable to read the number of viruses");
+                       return DEFER;
+               }
+               drweb_vnum = ntohl(drweb_vnum);
+               
+               /* "virus(es) found" if virus number is > 0 */
+               if (drweb_vnum)
+               {
+                       int i;
+                       uschar pre_malware_nb[256];
+                       
+                       malware_name = malware_name_buffer;
+                       
+                       /* setup default virus name */
+                       Ustrcpy(malware_name_buffer,"unknown");
+                       
+                       /* read and concatenate virus names into one string */
+                       for (i=0;i<drweb_vnum;i++)
+                       {
+                               /* read the size of report */
+                               if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen))) {
+                                       close(sock);
+                                       log_write(0, LOG_MAIN|LOG_PANIC,
+                                               "malware acl condition: drweb: cannot read report size");
+                                       return DEFER;
+                               };
+                               drweb_slen = ntohl(drweb_slen);
+                       
+                               /* read report body */
+                               if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen) {
+                                       close(sock);
+                                       log_write(0, LOG_MAIN|LOG_PANIC,
+                                               "malware acl condition: drweb: cannot read report string");
+                                       return DEFER;
+                               };
+                               tmpbuf[drweb_slen] = '\0';
+  
+                               /* set up match regex, depends on retcode */
+                               Ustrcpy(drweb_match_string, "infected\\swith\\s*(.+?)$");
+  
+                               drweb_re = pcre_compile( CS drweb_match_string,
+                                       PCRE_COPT,
+                                       (const char **)&rerror,
+                                       &roffset,
+                                       NULL );
+              
+                               /* try matcher on the line, grab substring */
+                               result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30);
+                               if (result >= 2) {
+                                       pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255);
+                               }
+                               /* the first name we just copy to malware_name */
+                               if (i==0)
+                                       Ustrcpy(CS malware_name_buffer, CS pre_malware_nb);
+                               else {
+                                       /* concatenate each new virus name to previous */
+                                       int slen = Ustrlen(malware_name_buffer);
+                                       if (slen < (slen+Ustrlen(pre_malware_nb))) {
+                                               Ustrcat(malware_name_buffer, "/");
+                                               Ustrcat(malware_name_buffer, pre_malware_nb);
+                                       }
+                               }
+                       }
+               }
+               else {
+                       /* no virus found */
+                       malware_name = NULL;
+               };
+               close(sock);
+       }
+       /* ----------------------------------------------------------------------- */
+    else if (strcmpic(scanner_name,US"aveserver") == 0) {
+      uschar *kav_options;
+      uschar kav_options_buffer[1024];
+      uschar kav_options_default[] = "/var/run/aveserver";
+      uschar buf[32768];
+      uschar *p;
+      struct sockaddr_un server;
+      int sock;
+    
+      if ((kav_options = string_nextinlist(&av_scanner_work, &sep,
+                                           kav_options_buffer,
+                                           sizeof(kav_options_buffer))) == NULL) {
+        /* no options supplied, use default options */
+        kav_options = kav_options_default;
+      };
+    
+      /* open the aveserver socket */
+      sock = socket(AF_UNIX, SOCK_STREAM, 0);
+      if (sock < 0) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: can't open UNIX socket.");
+        return DEFER; 
+      }
+      server.sun_family = AF_UNIX;
+      Ustrcpy(server.sun_path, kav_options);
+      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to connect to aveserver UNIX socket (%s). errno=%d", kav_options, errno);
+        return DEFER;
+      }
+    
+      /* read aveserver's greeting and see if it is ready (2xx greeting) */
+      recv_line(sock, buf, 32768);
+
+      if (buf[0] != '2') {
+        /* aveserver is having problems */
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: aveserver is unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") );
+        return DEFER;
+      };
+      
+      /* prepare our command */
+      snprintf(CS buf, 32768, "SCAN bPQRSTUW %s/scan/%s/%s.eml\r\n", spool_directory, message_id, message_id);
+      
+      /* and send it */
+      if (send(sock, buf, Ustrlen(buf), 0) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options);
+        return DEFER;
+      }
+      
+      malware_name = NULL;
+      /* read response lines, find malware name and final response */
+      while (recv_line(sock, buf, 32768) > 0) {
+        debug_printf("aveserver: %s\n", buf);
+        if (buf[0] == '2') break;
+        if (Ustrncmp(buf,"322",3) == 0) {
+          uschar *p = Ustrchr(&buf[4],' ');
+          *p = '\0';
+          Ustrcpy(malware_name_buffer,&buf[4]);
+          malware_name = malware_name_buffer;
+        };
+      }
+
+      close(sock);
+    }
+    /* "fsecure" scanner type ------------------------------------------------- */
+    else if (strcmpic(scanner_name,US"fsecure") == 0) {
+      uschar *fsecure_options;
+      uschar fsecure_options_buffer[1024];
+      uschar fsecure_options_default[] = "/var/run/.fsav";
+      struct sockaddr_un server;
+      int sock, i, j, bread = 0;
+      uschar file_name[1024];
+      uschar av_buffer[1024];
+      pcre *fs_inf;
+      static uschar *cmdoptions[] = { "CONFIGURE\tARCHIVE\t1\n","CONFIGURE\tTIMEOUT\t0\n","CONFIGURE\tMAXARCH\t5\n","CONFIGURE\tMIME\t1\n" };
+      
+      malware_name = NULL;
+      if ((fsecure_options = string_nextinlist(&av_scanner_work, &sep,
+                                               fsecure_options_buffer,
+                                               sizeof(fsecure_options_buffer))) == NULL) { 
+         /* no options supplied, use default options */
+         fsecure_options = fsecure_options_default;
+      };
+     
+      /* open the fsecure socket */
+      sock = socket(AF_UNIX, SOCK_STREAM, 0);
+      if (sock < 0) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: unable to open fsecure socket %s (%s)",
+                  fsecure_options, strerror(errno));
+        return DEFER;
+      }
+      server.sun_family = AF_UNIX;
+      Ustrcpy(server.sun_path, fsecure_options);
+      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: unable to connect to fsecure socket %s (%s)",
+                  fsecure_options, strerror(errno));
+        return DEFER;
+      }
+       
+      /* pass options */
+      memset(av_buffer, 0, sizeof(av_buffer));
+      for (i=0; i != 4; i++) {
+        /* debug_printf("send option \"%s\"",cmdoptions[i]); */
+        if (write(sock, cmdoptions[i], Ustrlen(cmdoptions[i])) < 0) {
+          close(sock);
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: unable to write fsecure option %d to %s (%s)",
+                    i, fsecure_options, strerror(errno));
+          return DEFER; 
+        };
+       
+        bread = read(sock, av_buffer, sizeof(av_buffer));
+        if (bread >0) av_buffer[bread]='\0';
+        if (bread < 0) {
+          close(sock);
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: unable to read fsecure answer %d (%s)", i, strerror(errno));
+          return DEFER;
+        };
+        for (j=0;j<bread;j++) if((av_buffer[j]=='\r')||(av_buffer[j]=='\n')) av_buffer[j] ='@';
+        /* debug_printf("read answer %d read=%d \"%s\"\n", i, bread, av_buffer ); */
+        /* while (Ustrstr(av_buffer, "OK\tServer configured.@") == NULL); */
+      };
+      /* pass the mailfile to fsecure */
+      snprintf(CS file_name,1024,"SCAN\t%s/scan/%s/%s.eml\n", spool_directory, message_id, message_id);
+      /* debug_printf("send scan %s",file_name); */
+      if (write(sock, file_name, Ustrlen(file_name)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: unable to write fsecure scan to %s (%s)",
+                  fsecure_options, strerror(errno));
+        return DEFER;
+      };
+       
+      /* set up match */
+      /* todo also SUSPICION\t */
+      fs_inf = pcre_compile("\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", PCRE_COPT, (const char **)&rerror, &roffset, NULL);
+      /* read report, linewise */
+      do {
+        int ovector[30];
+        i = 0;
+        memset(av_buffer, 0, sizeof(av_buffer));
+        do {
+          bread=read(sock, &av_buffer[i], 1);
+          if (bread < 0) {
+            close(sock);
+            log_write(0, LOG_MAIN|LOG_PANIC,
+                      "malware acl condition: unable to read fsecure result (%s)", strerror(errno));
+            return DEFER;
+          };
+          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, 30);
+          if (i >= 2) {
+            /* Got it */
+            pcre_copy_substring(CS av_buffer, ovector, i, 1, CS malware_name_buffer, 255);
+            malware_name = malware_name_buffer;
+          };
+        };
+      }
+      while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL);
+      close(sock);      
+    }
+    /* ----------------------------------------------------------------------- */
+
+    /* "kavdaemon" scanner type ------------------------------------------------ */
+    else if (strcmpic(scanner_name,US"kavdaemon") == 0) {
+      uschar *kav_options;
+      uschar kav_options_buffer[1024];
+      uschar kav_options_default[] = "/var/run/AvpCtl";
+      struct sockaddr_un server;
+      int sock;
+      time_t t;
+      uschar tmpbuf[1024];
+      uschar scanrequest[1024];
+      uschar kav_match_string[128];
+      int kav_rc;
+      unsigned long kav_reportlen, bread;
+      pcre *kav_re;
+    
+      if ((kav_options = string_nextinlist(&av_scanner_work, &sep,
+                                           kav_options_buffer,
+                                           sizeof(kav_options_buffer))) == NULL) {
+        /* no options supplied, use default options */
+        kav_options = kav_options_default;
+      };
+    
+      /* open the kavdaemon socket */
+      sock = socket(AF_UNIX, SOCK_STREAM, 0);
+      if (sock < 0) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: can't open UNIX socket.");
+        return DEFER; 
+      }
+      server.sun_family = AF_UNIX;
+      Ustrcpy(server.sun_path, kav_options);
+      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno);
+        return DEFER;
+      }
+      
+      /* get current date and time, build scan request */
+      time(&t);
+      strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s/scan/%%s", localtime(&t));
+      snprintf(CS scanrequest, 1024,CS tmpbuf, spool_directory, message_id);
+      
+      /* send scan request */
+      if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options);
+        return DEFER;
+      }
+      
+      /* wait for result */
+      if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to read 2 bytes from kavdaemon socket.");
+        return DEFER;
+      }
+    
+      /* get errorcode from one nibble */
+      if (test_byte_order() == LITTLE_MY_ENDIAN) {
+        kav_rc = tmpbuf[0] & 0x0F;
+      }
+      else {
+        kav_rc = tmpbuf[1] & 0x0F;
+      };
+    
+      /* improper kavdaemon configuration */
+      if ( (kav_rc == 5) || (kav_rc == 6) ) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files.");
+        return DEFER;
+      };
+      
+      if (kav_rc == 1) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: kavdaemon reported 'scanning not completed' (code 1).");
+        return DEFER;
+      };
+    
+      if (kav_rc == 7) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7).");
+        return DEFER;
+      };
+    
+      /* 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 */
+        Ustrcpy(malware_name_buffer,"unknown");
+        malware_name = malware_name_buffer;
+      
+        if (test_byte_order() == LITTLE_MY_ENDIAN) {
+          report_flag = tmpbuf[1];
+        }
+        else {
+          report_flag = tmpbuf[0];
+        };
+      
+        /* read the report, if available */
+        if( report_flag == 1 ) {
+          /* read report size */
+          if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) {
+            close(sock);
+            log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: cannot read report size from kavdaemon");
+            return DEFER;
+          };
+  
+          /* 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 */
+            if( kav_rc == 3 )
+              Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$");
+            else
+              Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$");
+  
+            kav_re = pcre_compile( CS kav_match_string,
+                                   PCRE_COPT,
+                                   (const char **)&rerror,
+                                   &roffset,
+                                   NULL );
+            
+            /* read report, linewise */  
+            while (kav_reportlen > 0) {
+              int result = 0;
+              int ovector[30];
+              
+              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, 30);
+              if (result >= 2) {
+                pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255);
+                break;
+              };
+            };
+          };
+        };
+      }
+      else {
+        /* no virus found */
+        malware_name = NULL;
+      };
+    
+      close(sock);
+    }
+    /* ----------------------------------------------------------------------- */
+    
+    
+    /* "cmdline" scanner type ------------------------------------------------ */
+    else if (strcmpic(scanner_name,US"cmdline") == 0) {
+      uschar *cmdline_scanner;
+      uschar cmdline_scanner_buffer[1024];
+      uschar *cmdline_trigger;
+      uschar cmdline_trigger_buffer[1024];
+      const pcre *cmdline_trigger_re;
+      uschar *cmdline_regex;
+      uschar cmdline_regex_buffer[1024];
+      const pcre *cmdline_regex_re;
+      uschar file_name[1024];
+      uschar commandline[1024];
+      void (*eximsigchld)(int);
+      void (*eximsigpipe)(int);
+      FILE *scanner_out = NULL;
+      FILE *scanner_record = NULL;
+      uschar linebuffer[32767];
+      int trigger = 0;
+      int result;
+      int ovector[30];
+      
+      /* find scanner command line */
+      if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep,
+                                          cmdline_scanner_buffer,
+                                          sizeof(cmdline_scanner_buffer))) == NULL) {
+        /* no command line supplied */
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: missing commandline specification for cmdline scanner type.");
+        return DEFER; 
+      };
+    
+      /* find scanner output trigger */
+      if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep,
+                                          cmdline_trigger_buffer,
+                                          sizeof(cmdline_trigger_buffer))) == NULL) {
+        /* no trigger regex supplied */
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: missing trigger specification for cmdline scanner type.");
+        return DEFER; 
+      };
+    
+      /* precompile trigger regex */
+      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);
+        return DEFER;
+      };
+    
+      /* find scanner name regex */
+      if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep,
+                                             cmdline_regex_buffer,
+                                             sizeof(cmdline_regex_buffer))) == NULL) {
+        /* no name regex supplied */
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: missing virus name regex specification for cmdline scanner type.");
+        return DEFER; 
+      };
+    
+      /* precompile name regex */
+      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);
+        return DEFER;
+      };
+    
+      /* prepare scanner call */
+      snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id);
+      snprintf(CS commandline,1024, CS cmdline_scanner,file_name);
+      /* redirect STDERR too */
+      Ustrcat(commandline," 2>&1");
+      
+      /* store exims signal handlers */
+      eximsigchld = signal(SIGCHLD,SIG_DFL);
+      eximsigpipe = signal(SIGPIPE,SIG_DFL);
+      
+      scanner_out = popen(CS commandline,"r");
+      if (scanner_out == NULL) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                 "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno));
+        signal(SIGCHLD,eximsigchld);
+        signal(SIGPIPE,eximsigpipe);
+        return DEFER;
+      };
+      
+      snprintf(CS file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id);
+      scanner_record = fopen(CS file_name,"w");
+      
+      if (scanner_record == NULL) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                 "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno));
+        pclose(scanner_out);
+        signal(SIGCHLD,eximsigchld);
+        signal(SIGPIPE,eximsigpipe);
+        return DEFER;
+      };
+      
+      /* look for trigger while recording output */
+      while(fgets(CS linebuffer,32767,scanner_out) != NULL) {
+        if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) {
+          /* short write */
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                 "malware acl condition: short write on scanner output file (%s).", file_name);
+          pclose(scanner_out);
+          signal(SIGCHLD,eximsigchld);
+          signal(SIGPIPE,eximsigpipe);
+          return DEFER;
+        };
+        /* try trigger match */
+        if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1))
+          trigger = 1;
+      };
+      
+      fclose(scanner_record);
+      pclose(scanner_out);
+      signal(SIGCHLD,eximsigchld);
+      signal(SIGPIPE,eximsigpipe);
+      
+      if (trigger) {
+        /* setup default virus name */
+        Ustrcpy(malware_name_buffer,"unknown");
+        malware_name = malware_name_buffer;
+        
+        /* re-open the scanner output file, look for name match */
+        scanner_record = fopen(CS file_name,"r");
+        while(fgets(CS linebuffer,32767,scanner_record) != NULL) {
+          /* try match */
+          result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30);
+          if (result >= 2) {
+            pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255);
+          };
+        };
+        fclose(scanner_record);
+      }
+      else {
+        /* no virus found */
+        malware_name = NULL;
+      };
+    }
+    /* ----------------------------------------------------------------------- */
+    
+    
+    /* "sophie" scanner type ------------------------------------------------- */
+    else if (strcmpic(scanner_name,US"sophie") == 0) {
+      uschar *sophie_options;
+      uschar sophie_options_buffer[1024];
+      uschar sophie_options_default[] = "/var/run/sophie";
+      int bread = 0;
+      struct sockaddr_un server;
+      int sock;
+      uschar file_name[1024];
+      uschar av_buffer[1024];
+      
+      if ((sophie_options = string_nextinlist(&av_scanner_work, &sep,
+                                          sophie_options_buffer,
+                                          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);
+      if (sock < 0) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: can't open UNIX socket.");
+        return DEFER; 
+      }
+      server.sun_family = AF_UNIX;
+      Ustrcpy(server.sun_path, sophie_options);
+      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno);
+        return DEFER;
+      }
+      
+      /* pass the scan directory to sophie */
+      snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id);
+      if (write(sock, file_name, Ustrlen(file_name)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options);
+        return DEFER; 
+      };
+      
+      write(sock, "\n", 1);
+      
+      /* wait for result */
+      memset(av_buffer, 0, sizeof(av_buffer));
+      if ((!(bread = read(sock, av_buffer, sizeof(av_buffer))) > 0)) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options);
+        return DEFER;
+      };
+    
+      close(sock);
+    
+      /* infected ? */
+      if (av_buffer[0] == '1') {
+        if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0';
+        Ustrcpy(malware_name_buffer,&av_buffer[2]);
+        malware_name = malware_name_buffer;
+      }
+      else if (!strncmp(CS av_buffer, "-1", 2)) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: malware acl condition: sophie reported error");
+        return DEFER;
+      }
+      else {
+        /* all ok, no virus */
+        malware_name = NULL;
+      };
+    }
+    /* ----------------------------------------------------------------------- */
+    
+
+    /* "clamd" scanner type ------------------------------------------------- */
+    /* This code was contributed by David Saez <david@ols.es> */
+    else if (strcmpic(scanner_name,US"clamd") == 0) {
+      uschar *clamd_options;
+      uschar clamd_options_buffer[1024];
+      uschar clamd_options_default[] = "/tmp/clamd";
+      uschar *p,*vname;
+      struct sockaddr_un server;
+      int sock,port,bread=0;
+      uschar file_name[1024];
+      uschar av_buffer[1024];
+      uschar hostname[256];
+      struct hostent *he;
+      struct in_addr in;
+
+      if ((clamd_options = string_nextinlist(&av_scanner_work, &sep,
+                                             clamd_options_buffer,
+                                             sizeof(clamd_options_buffer))) == NULL) {
+        /* no options supplied, use default options */
+        clamd_options = clamd_options_default;
+      }
+    
+      /* socket does not start with '/' -> network socket */
+      if (*clamd_options != '/') {
+    
+        /* 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;
+        };
+    
+        /* 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;
+        }
+    
+        in = *(struct in_addr *) he->h_addr_list[0];
+    
+        /* 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;
+        }
+    
+        if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
+          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;
+        }
+      }
+      else {
+        /* open the local socket */
+        if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: clamd: unable to acquire socket (%s)",
+                    strerror(errno));
+          return DEFER;
+        }
+    
+        server.sun_family = AF_UNIX;
+        Ustrcpy(server.sun_path, clamd_options);
+    
+        if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+          close(sock);
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)",
+                    clamd_options, strerror(errno) );
+          return DEFER;
+        }
+      }
+    
+      /* 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;
+      }
+    
+      /* 
+        We're done sending, close socket for writing.
+        
+        One user reported that clamd 0.70 does not like this any more ...
+        
+      */
+      
+      /* shutdown(sock, SHUT_WR); */
+    
+      /* Read the result */
+      memset(av_buffer, 0, sizeof(av_buffer));
+      bread = read(sock, av_buffer, sizeof(av_buffer));
+      close(sock);
+
+      if (!(bread  > 0)) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: clamd: unable to read 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;
+      }
+    
+      /* Check the result. ClamAV Returns
+         infected: -> "<filename>: <virusname> FOUND"
+         not-infected: -> "<filename>: OK"
+               error: -> "<filename>: <errcode> ERROR */
+         
+      if (!(*av_buffer)) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: clamd: ClamAV returned null");
+        return DEFER;
+      }
+    
+      /* colon in returned output? */
+      if((p = Ustrrchr(av_buffer,':')) == NULL) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+                  "malware acl condition: clamd: ClamAV returned malformed result: %s",
+                  av_buffer);  
+        return DEFER;
+      }
+    
+      /* strip filename strip CR at the end */
+      ++p;
+      while (*p == ' ') ++p;
+      vname = p;
+      p = vname + Ustrlen(vname) - 1;
+      if( *p == '\n' ) *p = '\0';
+
+      if ((p = Ustrstr(vname, "FOUND"))!=NULL) {
+           *p=0;
+           for (--p;p>vname && *p<=32;p--) *p=0;
+           for (;*vname==32;vname++);
+           Ustrcpy(malware_name_buffer,vname);
+           malware_name = malware_name_buffer;
+      }
+      else {
+           if (Ustrstr(vname, "ERROR")!=NULL) {
+              /* ClamAV reports ERROR
+              Find line start */
+              for (;*vname!='\n' && vname>av_buffer; vname--);
+              if (*vname=='\n') vname++;
+
+              log_write(0, LOG_MAIN|LOG_PANIC,
+                     "malware acl condition: clamd: ClamAV returned %s",vname);
+              return DEFER;
+           }
+           else {
+              /* Everything should be OK */
+              malware_name = NULL;
+           }
+      }
+    }
+    /* ----------------------------------------------------------------------- */
+    
+    
+    /* "mksd" scanner type --------------------------------------------------- */
+    else if (strcmpic(scanner_name,US"mksd") == 0) {
+      uschar *mksd_options;
+      char *mksd_options_end;
+      uschar mksd_options_buffer[32];
+      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,
+                                            mksd_options_buffer,
+                                            sizeof(mksd_options_buffer))) != NULL) {
+        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)) {
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: mksd: invalid option '%s'", mksd_options);
+          return DEFER;
+        }
+      }
+      
+      /* open the mksd socket */
+      sock = socket(AF_UNIX, SOCK_STREAM, 0);
+      if (sock < 0) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: can't open UNIX socket.");
+        return DEFER; 
+      }
+      server.sun_family = AF_UNIX;
+      Ustrcpy(server.sun_path, "/var/run/mksd/socket");
+      if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+        close(sock);
+        log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno);
+        return DEFER;
+      }
+      
+      malware_name = NULL;
+      
+      /* choose the appropriate scan routine */
+      retval = demime_ok ?
+               mksd_scan_unpacked(sock, mksd_maxproc) :
+               mksd_scan_packed(sock);
+      
+      if (retval != OK)
+        return retval;
+    }
+    /* ----------------------------------------------------------------------- */
+
+    /* "unknown" scanner type ------------------------------------------------- */
+    else {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+             "malware condition: unknown scanner type '%s'", scanner_name);
+      return DEFER;
+    };
+    /* ----------------------------------------------------------------------- */
+  
+    /* set "been here, done that" marker */
+    malware_ok = 1;
+  };
+
+  /* match virus name against pattern (caseless ------->----------v) */
+  if ( (malware_name != NULL) &&
+       (regex_match_and_setup(re, malware_name, 0, -1)) ) {
+    return OK;
+  }
+  else {
+    return FAIL;
+  };
+}
+
+
+/* simple wrapper for reading lines from sockets */
+int recv_line(int sock, uschar *buffer, int size) {
+  uschar *p = buffer;
+
+  memset(buffer,0,size);
+  /* read until \n */
+  while(recv(sock,p,1,0) > -1) {
+    if ((p-buffer) > (size-2)) break;
+    if (*p == '\n') break;
+    if (*p != '\r') p++;
+  };
+  *p = '\0';
+
+  return (p-buffer);
+}
+
+
+/* ============= private routines for the "mksd" scanner type ============== */
+
+#include <sys/uio.h>
+
+int mksd_writev (int sock, struct iovec *iov, int iovcnt)
+{
+  int i;
+  
+  for (;;) {
+    do
+      i = writev (sock, iov, iovcnt);
+    while ((i < 0) && (errno == EINTR));
+    if (i <= 0) {
+      close (sock);
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)");
+      return -1;
+    }
+    
+    for (;;)
+      if (i >= iov->iov_len) {
+        if (--iovcnt == 0)
+          return 0;
+        i -= iov->iov_len;
+        iov++;
+      } else {
+        iov->iov_len -= i;
+        iov->iov_base = CS iov->iov_base + i;
+        break;
+      }
+  }
+}
+
+int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
+{
+  int offset = 0;
+  int i;
+  
+  do {
+    if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) {
+      close (sock);
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+      return -1;
+    }
+    
+    offset += i;
+    /* offset == av_buffer_size -> buffer full */
+    if (offset == av_buffer_size) {
+      close (sock);
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: malformed reply received from mksd");
+      return -1;
+    }
+  } while (av_buffer[offset-1] != '\n');
+  
+  av_buffer[offset] = '\0';
+  return offset;
+}
+
+int mksd_parse_line (char *line)
+{
+  char *p;
+  
+  switch (*line) {
+    case 'O':
+      /* OK */
+      return OK;
+    case 'E':
+    case 'A':
+      /* ERR */
+      if ((p = strchr (line, '\n')) != NULL)
+        (*p) = '\0';
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: mksd scanner failed: %s", line);
+      return DEFER;
+    default:
+      /* VIR */
+      if ((p = strchr (line, '\n')) != NULL) {
+        (*p) = '\0';
+        if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' '))
+          if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) {
+            (*p) = '\0';
+            Ustrcpy (malware_name_buffer, line+4);
+           malware_name = malware_name_buffer;
+            return OK;
+          }
+      }
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: malformed reply received from mksd: %s", line);
+      return DEFER;
+  }
+}
+
+int mksd_scan_packed (int sock)
+{
+  struct iovec iov[7];
+  char *cmd = "MSQ/scan/.eml\n";
+  uschar av_buffer[1024];
+  
+  iov[0].iov_base = cmd;
+  iov[0].iov_len = 3;
+  iov[1].iov_base = CS spool_directory;
+  iov[1].iov_len = Ustrlen (spool_directory);
+  iov[2].iov_base = cmd + 3;
+  iov[2].iov_len = 6;
+  iov[3].iov_base = iov[5].iov_base = CS message_id;
+  iov[3].iov_len = iov[5].iov_len = Ustrlen (message_id);
+  iov[4].iov_base = cmd + 3;
+  iov[4].iov_len = 1;
+  iov[6].iov_base = cmd + 9;
+  iov[6].iov_len = 5;
+  
+  if (mksd_writev (sock, iov, 7) < 0)
+    return DEFER;
+  
+  if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)
+    return DEFER;
+  
+  close (sock);
+  
+  return mksd_parse_line (CS av_buffer);
+}
+
+int mksd_scan_unpacked (int sock, int maxproc)
+{
+  struct iovec iov[5];
+  char *cmd = "\nSQ/";
+  DIR *unpdir;
+  struct dirent *entry;
+  int pending = 0;
+  uschar *line;
+  int i, offset;
+  uschar mbox_name[1024];
+  uschar unpackdir[1024];
+  uschar av_buffer[16384];
+  
+  snprintf (CS mbox_name, sizeof (mbox_name), "%s.eml", CS message_id);
+  snprintf (CS unpackdir, sizeof (unpackdir), "%s/scan/%s", CS spool_directory, CS message_id);
+  
+  if ((unpdir = opendir (CS unpackdir)) == NULL) {
+    close (sock);
+    log_write(0, LOG_MAIN|LOG_PANIC,
+              "malware acl condition: unable to scan spool directory");
+    return DEFER;
+  }
+  
+  iov[0].iov_base = cmd;
+  iov[0].iov_len = 3;
+  iov[1].iov_base = CS unpackdir;
+  iov[1].iov_len = Ustrlen (unpackdir);
+  iov[2].iov_base = cmd + 3;
+  iov[2].iov_len = 1;
+  iov[4].iov_base = cmd;
+  iov[4].iov_len = 1;
+  
+  /* main loop */
+  while ((unpdir != NULL) || (pending > 0)) {
+  
+    /* write loop */
+    while ((pending < maxproc) && (unpdir != NULL)) {
+      if ((entry = readdir (unpdir)) != NULL) {
+        if ((Ustrcmp (entry->d_name, ".") != 0) &&
+            (Ustrcmp (entry->d_name, "..") != 0) &&
+            (Ustrcmp (entry->d_name, mbox_name) != 0)) {
+          iov[3].iov_base = entry->d_name;
+          iov[3].iov_len = strlen (entry->d_name);
+          if (mksd_writev (sock, iov, 5) < 0) {
+            closedir (unpdir);
+            return DEFER;
+          }
+          iov[0].iov_base = cmd + 1;
+          iov[0].iov_len = 2;
+          pending++;
+        }
+      } else {
+        closedir (unpdir);
+        unpdir = NULL;
+      }
+    }
+    
+    /* read and parse */
+    if (pending > 0) {
+      if ((offset = mksd_read_lines (sock, av_buffer, sizeof (av_buffer))) < 0) {
+        if (unpdir != NULL)
+          closedir (unpdir);
+        return DEFER;
+      }
+      line = av_buffer;
+      do {
+        if (((i = mksd_parse_line (CS line)) != OK) || (malware_name != NULL)) {
+          close (sock);
+          if (unpdir != NULL)
+            closedir (unpdir);
+          return i;
+        }
+        pending--;
+        if ((line = Ustrchr (line, '\n')) == NULL) {
+          close (sock);
+          if (unpdir != NULL)
+            closedir (unpdir);
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                    "malware acl condition: unterminated line received from mksd");
+          return DEFER;
+        }
+      } while (++line != (av_buffer + offset));
+      offset = 0;
+    }
+  }
+  
+  close (sock);
+  return OK;
+}
+
diff --git a/src/src/mime.c b/src/src/mime.c
new file mode 100644 (file)
index 0000000..8104204
--- /dev/null
@@ -0,0 +1,714 @@
+/* $Cambridge: exim/src/src/mime.c,v 1.1.2.1 2004/11/26 09:13:34 tom Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* This file is part of the exiscan-acl content scanner
+patch. It is NOT part of the standard exim distribution. */
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */
+/* License: GPL */
+
+#include "exim.h"
+#include "mime.h"
+#include <sys/stat.h>
+
+FILE *mime_stream = NULL;
+uschar *mime_current_boundary = NULL;
+
+
+/*************************************************
+* decode quoted-printable chars                  *
+*************************************************/
+
+/* gets called when we hit a =
+   returns: new pointer position
+   result code in c:
+          -2 - decode error
+          -1 - soft line break, no char
+           0-255 - char to write
+*/
+
+unsigned int mime_qp_hstr_i(uschar *cptr) {
+  unsigned int i, j = 0;
+  while (cptr && *cptr && isxdigit(*cptr)) {
+    i = *cptr++ - '0';
+    if (9 < i) i -= 7;
+    j <<= 4;
+    j |= (i & 0x0f);
+  }
+  return(j);
+}
+
+uschar *mime_decode_qp_char(uschar *qp_p,int *c) {
+  uschar hex[] = {0,0,0};
+  int nan = 0;
+  uschar *initial_pos = qp_p;
+  
+  /* advance one char */
+  qp_p++;
+  
+  REPEAT_FIRST:
+  if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') )  {
+    /* tab or whitespace may follow
+       just ignore it, but remember
+       that this is not a valid hex
+       encoding any more */
+    nan = 1;
+    qp_p++;
+    goto REPEAT_FIRST;
+  }
+  else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F'))  || (('a' <= *qp_p) && (*qp_p <= 'f')) ) {
+    /* this is a valid hex char, if nan is unset */
+    if (nan) {
+      /* this is illegal */
+      *c = -2;
+      return initial_pos;
+    }
+    else {
+      hex[0] = *qp_p;
+      qp_p++;
+    };
+  }
+  else if (*qp_p == '\n') {    
+    /* hit soft line break already, continue */
+    *c = -1;
+    return qp_p;
+  }
+  else {
+    /* illegal char here */
+    *c = -2;
+    return initial_pos;
+  };
+  
+  if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) {
+    if (hex[0] > 0) {
+      hex[1] = *qp_p;
+      /* do hex conversion */
+      *c = mime_qp_hstr_i(hex);
+      qp_p++;
+      return qp_p;
+    }
+    else {
+      /* huh ? */
+      *c = -2;
+      return initial_pos;  
+    };
+  }
+  else {
+    /* illegal char */
+    *c = -2;
+    return initial_pos;  
+  };
+}
+
+
+uschar *mime_parse_line(uschar *buffer, uschar *encoding, int *num_decoded) {
+  uschar *data = NULL;
+
+  data = (uschar *)malloc(Ustrlen(buffer)+2);
+
+  if (encoding == NULL) {
+    /* no encoding type at all */
+    NO_DECODING:
+    memcpy(data, buffer, Ustrlen(buffer));
+    data[(Ustrlen(buffer))] = 0;
+    *num_decoded = Ustrlen(data);
+    return data;
+  }
+  else if (Ustrcmp(encoding,"base64") == 0) {
+    uschar *p = buffer;
+    int offset = 0;
+    
+    /* ----- BASE64 ---------------------------------------------------- */
+    /* NULL out '\r' and '\n' chars */
+    while (Ustrrchr(p,'\r') != NULL) {
+      *(Ustrrchr(p,'\r')) = '\0';
+    };
+    while (Ustrrchr(p,'\n') != NULL) {
+      *(Ustrrchr(p,'\n')) = '\0';
+    };
+
+    while (*(p+offset) != '\0') {
+      /* hit illegal char ? */
+      if (mime_b64[*(p+offset)] == 128) {
+        offset++;
+      }
+      else {
+        *p = mime_b64[*(p+offset)];
+        p++;
+      };
+    };
+    *p = 255;
+   
+    /* line is translated, start bit shifting */
+    p = buffer;
+    *num_decoded = 0;  
+    while(*p != 255) {
+      uschar tmp_c;
+      
+      /* byte 0 ---------------------- */
+      if (*(p+1) == 255) {
+        break;
+      }
+      data[(*num_decoded)] = *p;
+      data[(*num_decoded)] <<= 2;
+      tmp_c = *(p+1);
+      tmp_c >>= 4;
+      data[(*num_decoded)] |= tmp_c;
+      (*num_decoded)++;
+      p++;
+      /* byte 1 ---------------------- */
+      if (*(p+1) == 255) {
+        break;
+      }
+      data[(*num_decoded)] = *p;
+      data[(*num_decoded)] <<= 4;
+      tmp_c = *(p+1);
+      tmp_c >>= 2;
+      data[(*num_decoded)] |= tmp_c;
+      (*num_decoded)++;
+      p++;
+      /* byte 2 ---------------------- */
+      if (*(p+1) == 255) {
+        break;
+      }
+      data[(*num_decoded)] = *p;
+      data[(*num_decoded)] <<= 6;
+      data[(*num_decoded)] |= *(p+1); 
+      (*num_decoded)++;
+      p+=2;
+      
+    };
+    return data;
+    /* ----------------------------------------------------------------- */
+  }
+  else if (Ustrcmp(encoding,"quoted-printable") == 0) {
+    uschar *p = buffer;
+
+    /* ----- QP -------------------------------------------------------- */
+    *num_decoded = 0;
+    while (*p != 0) {
+      if (*p == '=') {
+        int decode_qp_result;
+        
+        p = mime_decode_qp_char(p,&decode_qp_result);
+              
+        if (decode_qp_result == -2) {
+          /* Error from decoder. p is unchanged. */
+          data[(*num_decoded)] = '=';
+          (*num_decoded)++;
+          p++;
+        }
+        else if (decode_qp_result == -1) {
+          break;
+        }
+        else if (decode_qp_result >= 0) {
+          data[(*num_decoded)] = decode_qp_result;
+          (*num_decoded)++;
+        };
+      }
+      else {
+        data[(*num_decoded)] = *p;
+        (*num_decoded)++;
+        p++;
+      };
+    };
+    return data;
+    /* ----------------------------------------------------------------- */
+  }
+  /* unknown encoding type, just dump as-is */
+  else goto NO_DECODING;
+}
+
+
+FILE *mime_get_decode_file(uschar *pname, uschar *fname) {
+  FILE *f;
+  uschar *filename;
+  
+  filename = (uschar *)malloc(2048);
+  
+  if ((pname != NULL) && (fname != NULL)) {
+    snprintf(CS filename, 2048, "%s/%s", pname, fname);
+    f = fopen(CS filename,"w+");
+  }
+  else if (pname == NULL) {
+    f = fopen(CS fname,"w+");
+  }
+  else if (fname == NULL) {
+    int file_nr = 0;
+    int result = 0;
+
+    /* must find first free sequential filename */
+    do {
+      struct stat mystat;
+      snprintf(CS filename,2048,"%s/%s-%05u", pname, message_id, file_nr);
+      file_nr++;
+      /* security break */
+      if (file_nr >= 1024)
+        break;
+      result = stat(CS filename,&mystat);
+    }
+    while(result != -1);
+    f = fopen(CS filename,"w+");
+  };
+  
+  /* set expansion variable */
+  mime_decoded_filename = filename;
+  
+  return f;
+}
+
+
+int mime_decode(uschar **listptr) {
+  int sep = 0;
+  uschar *list = *listptr;
+  uschar *option;
+  uschar option_buffer[1024];
+  uschar decode_path[1024];
+  FILE *decode_file = NULL;
+  uschar *buffer = NULL;
+  long f_pos = 0;
+  unsigned int size_counter = 0;
+
+  if (mime_stream == NULL)
+    return FAIL;
+  
+  f_pos = ftell(mime_stream);
+  
+  /* build default decode path (will exist since MBOX must be spooled up) */
+  snprintf(CS decode_path,1024,"%s/scan/%s",spool_directory,message_id);
+  
+  /* reserve a line buffer to work in */
+  buffer = (uschar *)malloc(MIME_MAX_LINE_LENGTH+1);
+  if (buffer == NULL) {
+    log_write(0, LOG_PANIC,
+                 "decode ACL condition: can't allocate %d bytes of memory.", MIME_MAX_LINE_LENGTH+1);
+    return DEFER;
+  };
+  
+  /* try to find 1st option */
+  if ((option = string_nextinlist(&list, &sep,
+                                  option_buffer,
+                                  sizeof(option_buffer))) != NULL) {
+    
+    /* parse 1st option */
+    if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) {
+      /* explicitly no decoding */
+      return FAIL;
+    };
+    
+    if (Ustrcmp(option,"default") == 0) {
+      /* explicit default path + file names */
+      goto DEFAULT_PATH;
+    };
+    
+    if (option[0] == '/') {
+      struct stat statbuf;
+
+      memset(&statbuf,0,sizeof(statbuf));
+      
+      /* assume either path or path+file name */
+      if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) )
+        /* is directory, use it as decode_path */
+        decode_file = mime_get_decode_file(option, NULL);
+      else
+        /* does not exist or is a file, use as full file name */
+        decode_file = mime_get_decode_file(NULL, option);
+    }
+    else
+      /* assume file name only, use default path */
+      decode_file = mime_get_decode_file(decode_path, option);
+  }
+  else
+    /* no option? patch default path */
+    DEFAULT_PATH: decode_file = mime_get_decode_file(decode_path, NULL);
+  
+  if (decode_file == NULL)
+    return DEFER;
+  
+  /* read data linewise and dump it to the file,
+     while looking for the current boundary */
+  while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) {
+    uschar *decoded_line = NULL;
+    int decoded_line_length = 0;
+    
+    if (mime_current_boundary != NULL) {
+      /* boundary line must start with 2 dashes */
+      if (Ustrncmp(buffer,"--",2) == 0) {
+        if (Ustrncmp((buffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0)
+          break;
+      };
+    };
+  
+    decoded_line = mime_parse_line(buffer, mime_content_transfer_encoding, &decoded_line_length);
+    /* write line to decode file */
+    if (fwrite(decoded_line, 1, decoded_line_length, decode_file) < decoded_line_length) {
+      /* error/short write */
+      clearerr(mime_stream);
+      fseek(mime_stream,f_pos,SEEK_SET);
+      return DEFER;
+    };
+    size_counter += decoded_line_length;
+    
+    if (size_counter > 1023) { 
+      if ((mime_content_size + (size_counter / 1024)) < 65535)
+        mime_content_size += (size_counter / 1024);
+      else 
+        mime_content_size = 65535;
+      size_counter = (size_counter % 1024);
+    };
+    
+    free(decoded_line);
+  }
+  
+  fclose(decode_file);
+  
+  clearerr(mime_stream);
+  fseek(mime_stream,f_pos,SEEK_SET);
+  
+  /* round up remaining size bytes to one k */
+  if (size_counter) {
+    mime_content_size++;
+  };
+  
+  return OK;
+}
+
+int mime_get_header(FILE *f, uschar *header) {
+  int c = EOF;
+  int done = 0;
+  int header_value_mode = 0;
+  int header_open_brackets = 0;
+  int num_copied = 0;
+  
+  while(!done) {
+    
+    c = fgetc(f);
+    if (c == EOF) break;
+   
+    /* always skip CRs */
+    if (c == '\r') continue;
+    
+    if (c == '\n') {
+      if (num_copied > 0) {
+        /* look if next char is '\t' or ' ' */
+        c = fgetc(f);
+        if (c == EOF) break;
+        if ( (c == '\t') || (c == ' ') ) continue;
+        ungetc(c,f);
+      };
+      /* end of the header, terminate with ';' */
+      c = ';';
+      done = 1;
+    };
+  
+    /* skip control characters */
+    if (c < 32) continue;
+
+    if (header_value_mode) {
+      /* --------- value mode ----------- */
+      /* skip leading whitespace */
+      if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) )
+        continue;
+      
+      /* we have hit a non-whitespace char, start copying value data */
+      header_value_mode = 2;
+      
+      /* skip quotes */
+      if (c == '"') continue;
+      
+      /* leave value mode on ';' */
+      if (c == ';') {
+        header_value_mode = 0;
+      };
+      /* -------------------------------- */
+    }
+    else {
+      /* -------- non-value mode -------- */
+      /* skip whitespace + tabs */
+      if ( (c == ' ') || (c == '\t') )
+        continue;
+      if (c == '\\') {
+        /* quote next char. can be used
+        to escape brackets. */
+        c = fgetc(f);
+        if (c == EOF) break;
+      }
+      else if (c == '(') {
+        header_open_brackets++;
+        continue;
+      }
+      else if ((c == ')') && header_open_brackets) {
+        header_open_brackets--;
+        continue;
+      }
+      else if ( (c == '=') && !header_open_brackets ) {
+        /* enter value mode */
+        header_value_mode = 1;
+      };
+      
+      /* skip chars while we are in a comment */
+      if (header_open_brackets > 0)
+        continue;
+      /* -------------------------------- */
+    };
+    
+    /* copy the char to the buffer */
+    header[num_copied] = (uschar)c;
+    /* raise counter */
+    num_copied++;
+    
+    /* break if header buffer is full */
+    if (num_copied > MIME_MAX_HEADER_SIZE-1) {
+      done = 1;
+    };
+  };
+
+  if (header[num_copied-1] != ';') {
+    header[num_copied-1] = ';';
+  };
+
+  /* 0-terminate */
+  header[num_copied] = '\0';
+  
+  /* return 0 for EOF or empty line */
+  if ((c == EOF) || (num_copied == 1))
+    return 0;
+  else
+    return 1;
+}
+
+
+int mime_acl_check(FILE *f, struct mime_boundary_context *context, uschar 
+                   **user_msgptr, uschar **log_msgptr) {
+  int rc = OK;
+  uschar *header = NULL;
+  struct mime_boundary_context nested_context;
+
+  /* reserve a line buffer to work in */
+  header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1);
+  if (header == NULL) {
+    log_write(0, LOG_PANIC,
+                 "acl_smtp_mime: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1);
+    return DEFER;
+  };
+
+  /* Not actually used at the moment, but will be vital to fixing
+   * some RFC 2046 nonconformance later... */
+  nested_context.parent = context;
+
+  /* loop through parts */
+  while(1) {
+  
+    /* reset all per-part mime variables */
+    mime_anomaly_level     = NULL;
+    mime_anomaly_text      = NULL;
+    mime_boundary          = NULL;
+    mime_charset           = NULL;
+    mime_decoded_filename  = NULL;
+    mime_filename          = NULL;
+    mime_content_description = NULL;
+    mime_content_disposition = NULL;
+    mime_content_id        = NULL;
+    mime_content_transfer_encoding = NULL;
+    mime_content_type      = NULL;
+    mime_is_multipart      = 0;
+    mime_content_size      = 0;
+  
+    /*
+    If boundary is null, we assume that *f is positioned on the start of headers (for example,
+    at the very beginning of a message.
+    If a boundary is given, we must first advance to it to reach the start of the next header
+    block.
+    */
+    
+    /* NOTE -- there's an error here -- RFC2046 specifically says to
+     * check for outer boundaries.  This code doesn't do that, and
+     * I haven't fixed this.
+     *
+     * (I have moved partway towards adding support, however, by adding 
+     * a "parent" field to my new boundary-context structure.)
+     */
+    if (context != NULL) {
+      while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) {
+        /* boundary line must start with 2 dashes */
+        if (Ustrncmp(header,"--",2) == 0) {
+          if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0) {
+            /* found boundary */
+            if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0) {
+              /* END boundary found */
+              debug_printf("End boundary found %s\n", context->boundary);
+              return rc;
+            }
+            else {
+              debug_printf("Next part with boundary %s\n", context->boundary);
+            };
+            /* can't use break here */
+            goto DECODE_HEADERS;
+          }
+        };
+      }
+      /* Hit EOF or read error. Ugh. */
+      debug_printf("Hit EOF ...\n");
+      return rc;
+    };
+  
+    DECODE_HEADERS:
+    /* parse headers, set up expansion variables */
+    while(mime_get_header(f,header)) {
+      int i;
+      /* loop through header list */
+      for (i = 0; i < mime_header_list_size; i++) {
+        uschar *header_value = NULL;
+        int header_value_len = 0;
+        
+        /* found an interesting header? */
+        if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) {
+          uschar *p = header + mime_header_list[i].namelen;
+          /* yes, grab the value (normalize to lower case)
+             and copy to its corresponding expansion variable */
+          while(*p != ';') {
+            *p = tolower(*p);
+            p++;
+          };
+          header_value_len = (p - (header + mime_header_list[i].namelen));
+          header_value = (uschar *)malloc(header_value_len+1);
+          memset(header_value,0,header_value_len+1);
+          p = header + mime_header_list[i].namelen;
+          Ustrncpy(header_value, p, header_value_len);
+          debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value);
+          *((uschar **)(mime_header_list[i].value)) = header_value;
+          
+          /* make p point to the next character after the closing ';' */
+          p += (header_value_len+1);
+          
+          /* grab all param=value tags on the remaining line, check if they are interesting */
+          NEXT_PARAM_SEARCH: while (*p != 0) {
+            int j;
+            for (j = 0; j < mime_parameter_list_size; j++) {
+              uschar *param_value = NULL;
+              int param_value_len = 0;
+              
+              /* found an interesting parameter? */
+              if (strncmpic(mime_parameter_list[j].name,p,mime_parameter_list[j].namelen) == 0) {
+                uschar *q = p + mime_parameter_list[j].namelen;
+                /* yes, grab the value and copy to its corresponding expansion variable */
+                while(*q != ';') q++;
+                param_value_len = (q - (p + mime_parameter_list[j].namelen));
+                param_value = (uschar *)malloc(param_value_len+1);
+                memset(param_value,0,param_value_len+1);
+                q = p + mime_parameter_list[j].namelen;
+                Ustrncpy(param_value, q, param_value_len);
+                param_value = rfc2047_decode(param_value, TRUE, NULL, 32, &param_value_len, &q);
+                debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mime_parameter_list[j].name, mime_header_list[i].name, param_value);
+                *((uschar **)(mime_parameter_list[j].value)) = param_value;
+                p += (mime_parameter_list[j].namelen + param_value_len + 1);
+                goto NEXT_PARAM_SEARCH;
+              };
+            }
+            /* There is something, but not one of our interesting parameters.
+               Advance to the next semicolon */
+            while(*p != ';') p++;
+            p++;
+          };
+        };
+      };
+    };
+    
+    /* set additional flag variables (easier access) */
+    if ( (mime_content_type != NULL) &&
+         (Ustrncmp(mime_content_type,"multipart",9) == 0) )
+      mime_is_multipart = 1;
+    
+    /* Make a copy of the boundary pointer.
+       Required since mime_boundary is global
+       and can be overwritten further down in recursion */
+    nested_context.boundary = mime_boundary;
+    
+    /* raise global counter */
+    mime_part_count++;
+    
+    /* copy current file handle to global variable */
+    mime_stream = f;
+    mime_current_boundary = context ? context->boundary : 0;
+
+    /* Note the context */
+    mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT);
+    
+    /* call ACL handling function */
+    rc = acl_check(ACL_WHERE_MIME, NULL, acl_smtp_mime, user_msgptr, log_msgptr);
+    
+    mime_stream = NULL;
+    mime_current_boundary = NULL;
+    
+    if (rc != OK) break;
+    
+    /* If we have a multipart entity and a boundary, go recursive */
+    if ( (mime_content_type != NULL) &&
+         (nested_context.boundary != NULL) &&
+         (Ustrncmp(mime_content_type,"multipart",9) == 0) ) {
+      debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary);
+
+      if (context && context->context == MBC_ATTACHMENT)
+        nested_context.context = MBC_ATTACHMENT;
+      else if (!Ustrcmp(mime_content_type,"multipart/alternative")
+            || !Ustrcmp(mime_content_type,"multipart/related"))
+        nested_context.context = MBC_COVERLETTER_ALL;
+      else
+        nested_context.context = MBC_COVERLETTER_ONESHOT;
+
+      rc = mime_acl_check(f, &nested_context, user_msgptr, log_msgptr);
+      if (rc != OK) break;
+    }
+    else if ( (mime_content_type != NULL) &&
+            (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) {
+      uschar *rfc822name = NULL;
+      uschar filename[2048];
+      int file_nr = 0;
+      int result = 0;
+      
+      /* must find first free sequential filename */
+      do {
+        struct stat mystat;
+        snprintf(CS filename,2048,"%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr);
+        file_nr++;
+        /* security break */
+        if (file_nr >= 128)
+          goto NO_RFC822;
+        result = stat(CS filename,&mystat);
+      }
+      while(result != -1);
+      
+      rfc822name = filename;
+      
+      /* decode RFC822 attachment */
+      mime_decoded_filename = NULL;
+      mime_stream = f;
+      mime_current_boundary = context ? context->boundary : NULL;
+      mime_decode(&rfc822name);
+      mime_stream = NULL;
+      mime_current_boundary = NULL;
+      if (mime_decoded_filename == NULL) {
+        /* decoding failed */
+        log_write(0, LOG_MAIN,
+             "mime_regex acl condition warning - could not decode RFC822 MIME part to file.");
+        return DEFER;
+      };
+      mime_decoded_filename = NULL;
+    };
+    
+    NO_RFC822:
+    /* If the boundary of this instance is NULL, we are finished here */
+    if (context == NULL) break;
+
+    if (context->context == MBC_COVERLETTER_ONESHOT)
+      context->context = MBC_ATTACHMENT;
+  
+  };
+
+  return rc;
+}
+
+
diff --git a/src/src/mime.h b/src/src/mime.h
new file mode 100644 (file)
index 0000000..f9d67c3
--- /dev/null
@@ -0,0 +1,79 @@
+/* $Cambridge: exim/src/src/mime.h,v 1.1.2.1 2004/11/26 09:13:34 tom Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* This file is part of the exiscan-acl content scanner
+patch. It is NOT part of the standard exim distribution. */
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */
+/* License: GPL */
+
+
+#define MIME_MAX_HEADER_SIZE 8192
+#define MIME_MAX_LINE_LENGTH 32768
+
+#define MBC_ATTACHMENT            0
+#define MBC_COVERLETTER_ONESHOT   1
+#define MBC_COVERLETTER_ALL       2
+
+struct mime_boundary_context
+{
+  struct mime_boundary_context *parent;
+  unsigned char *boundary;
+  int context;
+};
+
+typedef struct mime_header {
+  uschar *name;
+  int    namelen;
+  void   *value;
+} mime_header;
+
+static mime_header mime_header_list[] = {
+  { US"content-type:", 13, &mime_content_type },
+  { US"content-disposition:", 20, &mime_content_disposition },
+  { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding },
+  { US"content-id:", 11, &mime_content_id },
+  { US"content-description:", 20 , &mime_content_description }
+};
+
+static int mime_header_list_size = sizeof(mime_header_list)/sizeof(mime_header);
+
+
+
+typedef struct mime_parameter {
+  uschar *name;
+  int    namelen;
+  void   *value;
+} mime_parameter;
+
+static mime_parameter mime_parameter_list[] = {
+  { US"name=", 5, &mime_filename },
+  { US"filename=", 9, &mime_filename },
+  { US"charset=", 8, &mime_charset },
+  { US"boundary=", 9, &mime_boundary }
+};
+
+static int mime_parameter_list_size = sizeof(mime_parameter_list)/sizeof(mime_parameter);
+
+/* BASE64 decoder matrix */
+static unsigned char mime_b64[256]={
+/*   0 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/*  16 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128, 
+/*  32 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,   62,  128,  128,  128,   63,
+/*  48 */   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,  128,  128,  128,  255,  128,  128,
+/*  64 */  128,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
+/*  80 */   15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,  128,  128,  128,  128,  128,
+/*  96 */  128,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
+/* 112 */   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,  128,  128,  128,  128,  128,
+/* 128 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 144 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 160 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 176 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 192 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 208 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 224 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 240 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128 
+};
diff --git a/src/src/spam.c b/src/src/spam.c
new file mode 100644 (file)
index 0000000..8772435
--- /dev/null
@@ -0,0 +1,340 @@
+/* $Cambridge: exim/src/src/spam.c,v 1.1.2.1 2004/11/26 09:13:34 tom Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* This file is part of the exiscan-acl content scanner
+patch. It is NOT part of the standard exim distribution. */
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
+/* License: GPL */
+
+/* Code for calling spamassassin's spamd. Called from acl.c. */
+
+#include "exim.h"
+#include "spam.h"
+
+uschar spam_score_buffer[16];
+uschar spam_score_int_buffer[16];
+uschar spam_bar_buffer[128];
+uschar spam_report_buffer[32600];
+uschar prev_user_name[128] = "";
+int spam_ok = 0;
+int spam_rc = 0;
+
+int spam(uschar **listptr) {
+  int sep = 0;
+  uschar *list = *listptr;
+  uschar *user_name;
+  uschar user_name_buffer[128];
+  unsigned long long mbox_size;
+  FILE *mbox_file;
+  int spamd_sock;
+  uschar spamd_buffer[32600];
+  int i, j, offset;
+  uschar spamd_version[8];
+  uschar spamd_score_char;
+  double spamd_threshold, spamd_score;
+  int spamd_report_offset;
+  uschar *p,*q;
+  int override = 0;
+  struct sockaddr_un server;
+
+  /* find the username from the option list */
+  if ((user_name = string_nextinlist(&list, &sep,
+                                     user_name_buffer,
+                                     sizeof(user_name_buffer))) == NULL) {
+    /* no username given, this means no scanning should be done */
+    return FAIL;
+  };
+
+  /* if username is "0" or "false", do not scan */
+  if ( (Ustrcmp(user_name,"0") == 0) ||
+       (strcmpic(user_name,US"false") == 0) ) {
+    return FAIL;
+  };
+
+  /* if there is an additional option, check if it is "true" */
+  if (strcmpic(list,US"true") == 0) {
+    /* in that case, always return true later */
+    override = 1;
+  };
+
+  /* if we scanned for this username last time, just return */ 
+  if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) ) {
+    if (override)
+      return OK;
+    else
+      return spam_rc;
+  };
+  
+  /* make sure the eml mbox file is spooled up */
+  mbox_file = spool_mbox(&mbox_size);
+  
+  if (mbox_file == NULL) {
+    /* error while spooling */
+    log_write(0, LOG_MAIN|LOG_PANIC,
+           "spam acl condition: error while creating mbox spool file");
+    return DEFER;
+  };
+
+  /* socket does not start with '/' -> network socket */
+  if (*spamd_address != '/') {
+    time_t now = time(NULL);
+    int num_servers = 0;
+    int current_server = 0;
+    int start_server = 0;
+    uschar *address = NULL;
+    uschar *spamd_address_list_ptr = spamd_address;
+    uschar address_buffer[256];
+    spamd_address_container * spamd_address_vector[32];
+
+    /* Check how many spamd servers we have
+       and register their addresses */
+    while ((address = string_nextinlist(&spamd_address_list_ptr, &sep,
+                                        address_buffer,
+                                        sizeof(address_buffer))) != NULL) {
+      
+      spamd_address_container *this_spamd =
+        (spamd_address_container *)store_get(sizeof(spamd_address_container));
+      
+      /* grok spamd address and port */
+      if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) {
+        log_write(0, LOG_MAIN,
+          "spam acl condition: warning - invalid spamd address: '%s'", address);
+        continue;
+      };
+      
+      spamd_address_vector[num_servers] = this_spamd;
+      num_servers++;
+      if (num_servers > 31)
+        break;
+    };
+    
+    /* check if we have at least one server */
+    if (!num_servers) {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+         "spam acl condition: no useable spamd server addresses in spamd_address configuration option.");
+      fclose(mbox_file);
+      return DEFER;
+    };
+
+    current_server = start_server = (int)now % num_servers;
+
+    while (1) {
+      
+      debug_printf("trying server %s, port %u\n",
+                   spamd_address_vector[current_server]->tcp_addr,
+                   spamd_address_vector[current_server]->tcp_port);
+      
+      /* contact a spamd */
+      if ( (spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+           "spam acl condition: error creating IP socket for spamd");
+        fclose(mbox_file);
+        return DEFER; 
+      };
+      
+      if (ip_connect( spamd_sock,
+                      AF_INET,
+                      spamd_address_vector[current_server]->tcp_addr,
+                      spamd_address_vector[current_server]->tcp_port,
+                      5 ) > -1) {
+        /* connection OK */
+        break;
+      };
+      
+      log_write(0, LOG_MAIN|LOG_PANIC,
+         "spam acl condition: warning - spamd connection to %s, port %u failed: %s",
+         spamd_address_vector[current_server]->tcp_addr,
+         spamd_address_vector[current_server]->tcp_port,
+         strerror(errno));
+      current_server++;
+      if (current_server >= num_servers)
+        current_server = 0;
+      if (current_server == start_server) {
+        log_write(0, LOG_MAIN|LOG_PANIC, "spam acl condition: all spamd servers failed");
+        fclose(mbox_file);
+        close(spamd_sock);
+        return DEFER;
+      };
+    };
+
+  }
+  else {
+    /* open the local socket */
+
+    if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: spamd: unable to acquire socket (%s)",
+                strerror(errno));
+      fclose(mbox_file);
+      return DEFER;
+    }
+
+    server.sun_family = AF_UNIX;
+    Ustrcpy(server.sun_path, spamd_address);
+
+    if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)",
+                spamd_address, strerror(errno) );
+      fclose(mbox_file);
+      close(spamd_sock);
+      return DEFER;
+    }
+
+  }
+
+  /* now we are connected to spamd on spamd_sock */
+  snprintf(CS spamd_buffer,
+           sizeof(spamd_buffer),
+           "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %lld\r\n\r\n",
+           user_name,
+           mbox_size);
+
+  /* send our request */
+  if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) {
+    close(spamd_sock);
+    log_write(0, LOG_MAIN|LOG_PANIC,
+         "spam acl condition: spamd send failed: %s", strerror(errno));
+    fclose(mbox_file);
+    close(spamd_sock);
+    return DEFER;
+  };
+
+  /* now send the file */
+  do {
+    j = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
+    if (j > 0) {
+      i = send(spamd_sock,spamd_buffer,j,0);
+      if (i != j) {
+        log_write(0, LOG_MAIN|LOG_PANIC,
+          "spam acl condition: error/short send to spamd");
+        close(spamd_sock);
+        fclose(mbox_file);
+        return DEFER;
+      };
+    };
+  }
+  while (j > 0);
+
+  fclose(mbox_file);
+
+  /* we're done sending, close socket for writing */
+  shutdown(spamd_sock,SHUT_WR);
+  
+  /* read spamd response */
+  memset(spamd_buffer, 0, sizeof(spamd_buffer));
+  offset = 0;
+  while((i = ip_recv(spamd_sock,
+                     spamd_buffer + offset,
+                     sizeof(spamd_buffer) - offset - 1,
+                     SPAMD_READ_TIMEOUT)) > 0 ) {
+    offset += i;
+  }
+
+  /* error handling */
+  if((i <= 0) && (errno != 0)) {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+         "spam acl condition: error reading from spamd socket: %s", strerror(errno));
+    close(spamd_sock);
+    return DEFER;
+  }
+
+  /* reading done */
+  close(spamd_sock);
+
+  /* dig in the spamd output and put the report in a multiline header, if requested */
+  if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
+             spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
+              
+    /* try to fall back to pre-2.50 spamd output */
+    if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
+               spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+         "spam acl condition: cannot parse spamd output");
+      return DEFER;
+    };
+  };
+
+  /* Create report. Since this is a multiline string,
+  we must hack it into shape first */
+  p = &spamd_buffer[spamd_report_offset];
+  q = spam_report_buffer;
+  while (*p != '\0') {
+    /* skip \r */
+    if (*p == '\r') {
+      p++;
+      continue;
+    };
+    *q = *p;
+    q++;
+    if (*p == '\n') {
+      *q = '\t';
+      q++;
+      /* eat whitespace */
+      while( (*p <= ' ') && (*p != '\0') ) {
+        p++;
+      };
+      p--;
+    };
+    p++;
+  };
+  /* NULL-terminate */
+  *q = '\0';
+  q--;
+  /* cut off trailing leftovers */
+  while (*q <= ' ') {
+    *q = '\0';
+    q--;
+  };
+  spam_report = spam_report_buffer;
+
+  /* create spam bar */
+  spamd_score_char = spamd_score > 0 ? '+' : '-';
+  j = abs((int)(spamd_score));
+  i = 0;
+  if( j != 0 ) {
+    while((i < j) && (i <= MAX_SPAM_BAR_CHARS))
+       spam_bar_buffer[i++] = spamd_score_char;
+  }
+  else{
+    spam_bar_buffer[0] = '/';
+    i = 1;
+  }
+  spam_bar_buffer[i] = '\0';
+  spam_bar = spam_bar_buffer;
+
+  /* create "float" spam score */
+  snprintf(CS spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score);
+  spam_score = spam_score_buffer;
+
+  /* create "int" spam score */
+  j = (int)((spamd_score + 0.001)*10);
+  snprintf(CS spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j);
+  spam_score_int = spam_score_int_buffer;
+
+  /* compare threshold against score */
+  if (spamd_score >= spamd_threshold) {
+    /* spam as determined by user's threshold */
+    spam_rc = OK;
+  }
+  else {
+    /* not spam */
+    spam_rc = FAIL;
+  };
+  
+  /* remember user name and "been here" for it */
+  Ustrcpy(prev_user_name, user_name);
+  spam_ok = 1;
+  
+  if (override) {
+    /* always return OK, no matter what the score */
+    return OK;
+  }
+  else {
+    return spam_rc;
+  };
+}
diff --git a/src/src/spam.h b/src/src/spam.h
new file mode 100644 (file)
index 0000000..4d72a4c
--- /dev/null
@@ -0,0 +1,32 @@
+/* $Cambridge: exim/src/src/spam.h,v 1.1.2.1 2004/11/26 09:13:34 tom Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* This file is part of the exiscan-acl content scanner
+patch. It is NOT part of the standard exim distribution. */
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
+/* License: GPL */
+
+/* spam defines */
+
+/* timeout for reading from spamd */
+#define SPAMD_READ_TIMEOUT 3600
+
+/* maximum length of the spam bar */
+#define MAX_SPAM_BAR_CHARS 50
+
+/* SHUT_WR seems to be undefined on Unixware ? */
+#ifndef SHUT_WR
+#define SHUT_WR 1
+#endif
+
+typedef struct spamd_address_container {
+  uschar tcp_addr[24];
+  unsigned int tcp_port;
+} spamd_address_container;
+
+
+
diff --git a/src/src/spool_mbox.c b/src/src/spool_mbox.c
new file mode 100644 (file)
index 0000000..946a072
--- /dev/null
@@ -0,0 +1,198 @@
+/* $Cambridge: exim/src/src/spool_mbox.c,v 1.1.2.1 2004/11/26 09:13:34 tom Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* This file is part of the exiscan-acl content scanner
+patch. It is NOT part of the standard exim distribution. */
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
+/* License: GPL */
+
+/* Code for setting up a MBOX style spool file inside a /scan/<msgid>
+sub directory of exim's spool directory. */
+
+#include "exim.h"
+
+/* externals, we must reset them on unspooling */
+extern int demime_ok;
+extern int malware_ok;
+extern int spam_ok;
+extern struct file_extension *file_extensions;
+
+int spool_mbox_ok = 0;
+uschar spooled_message_id[17];
+
+/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size */
+
+FILE *spool_mbox(unsigned long long *mbox_file_size) {
+  uschar mbox_path[1024];
+  uschar message_subdir[2];
+  uschar data_buffer[65535];
+  FILE *mbox_file;
+  FILE *data_file = NULL;
+  header_line *my_headerlist;
+  struct stat statbuf;
+  int i,j;
+  
+  /*
+  uschar *received;
+  uschar *timestamp;
+  */
+  
+  if (!spool_mbox_ok) {
+    /* create scan directory, if not present */
+    if (!directory_make(spool_directory, US "scan", 0750, FALSE)) {
+      debug_printf("unable to create directory: %s/scan\n", spool_directory);
+      return NULL;
+    };
+    
+    /* create temp directory inside scan dir */
+    snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, message_id);
+    if (!directory_make(NULL, mbox_path, 0750, FALSE)) {
+      debug_printf("unable to create directory: %s/scan/%s\n", spool_directory, message_id);
+      return NULL;
+    };
+    
+    /* open [message_id].eml file for writing */
+    snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
+    mbox_file = Ufopen(mbox_path,"w");
+    
+    if (mbox_file == NULL) {
+      debug_printf("unable to open file for writing: %s\n", mbox_path);
+      return NULL;
+    };
+    
+    /* Generate a preliminary Received: header and put it in the file.
+       We need to do this so SA can do DNS list checks */
+       
+    /* removed for 4.34
+    
+    timestamp = expand_string(US"${tod_full}");
+    received = expand_string(received_header_text);
+    if (received != NULL) {
+      uschar *my_received;
+      if (received[0] == 0) {
+        my_received = string_sprintf("Received: ; %s\n", timestamp);
+      }
+      else {
+        my_received = string_sprintf("%s; %s\n", received, timestamp);
+      }
+      i = fwrite(my_received, 1, Ustrlen(my_received), mbox_file);
+      if (i != Ustrlen(my_received)) {
+        debug_printf("error/short write on writing in: %s", mbox_path);
+        fclose(mbox_file);
+        return NULL;
+      };
+    };
+    
+    */
+    
+    /* write all header lines to mbox file */
+    my_headerlist = header_list;
+    while (my_headerlist != NULL) {
+      
+      /* skip deleted headers */
+      if (my_headerlist->type == '*') {
+        my_headerlist = my_headerlist->next;
+        continue;
+      };
+  
+      i = fwrite(my_headerlist->text, 1, my_headerlist->slen, mbox_file);
+      if (i != my_headerlist->slen) {
+        debug_printf("error/short write on writing in: %s", mbox_path);
+        fclose(mbox_file);
+        return NULL;
+      };
+      
+      my_headerlist = my_headerlist->next;
+    };
+  
+    /* copy body file */
+    message_subdir[1] = '\0';
+    for (i = 0; i < 2; i++) {
+      message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0;
+      sprintf(CS mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id);
+      data_file = Ufopen(mbox_path,"r");
+      if (data_file != NULL)
+        break;
+    };
+
+    fread(data_buffer, 1, 18, data_file);
+    
+    do {
+      j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
+      if (j > 0) {
+        i = fwrite(data_buffer, 1, j, mbox_file);
+        if (i != j) {
+          debug_printf("error/short write on writing in: %s", mbox_path);
+          fclose(mbox_file);
+          fclose(data_file);
+          return NULL;
+        };
+      };
+    } while (j > 0);
+    
+    fclose(data_file);
+    fclose(mbox_file);
+    Ustrcpy(spooled_message_id, message_id);
+    spool_mbox_ok = 1;
+  };
+
+  snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
+
+  /* get the size of the mbox message */
+  stat(CS mbox_path, &statbuf);
+  *mbox_file_size = statbuf.st_size;
+
+  /* open [message_id].eml file for reading */
+  mbox_file = Ufopen(mbox_path,"r");
+  
+  return mbox_file;
+}
+
+/* remove mbox spool file, demimed files and temp directory */
+void unspool_mbox(void) {
+
+  /* reset all exiscan state variables */
+  demime_ok = 0;
+  demime_errorlevel = 0;
+  demime_reason = NULL;
+  file_extensions = NULL;
+  spam_ok = 0;
+  malware_ok = 0;
+  
+  if (spool_mbox_ok) {
+
+    spool_mbox_ok = 0;
+    
+    if (!no_mbox_unspool) {
+      uschar mbox_path[1024];
+      uschar file_path[1024];
+      int n;
+      struct dirent *entry;
+      DIR *tempdir;
+      
+      snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, spooled_message_id);
+       
+       tempdir = opendir(CS mbox_path);
+       /* loop thru dir & delete entries */
+       n = 0;
+       do {
+         entry = readdir(tempdir);
+         if (entry == NULL) break;
+         snprintf(CS file_path, 1024,"%s/scan/%s/%s", spool_directory, spooled_message_id, entry->d_name);
+         if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) ) {
+           debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
+              n = unlink(CS file_path);
+            }; 
+       } while (n > -1);
+       
+       closedir(tempdir);
+       
+       /* remove directory */
+       n = rmdir(CS mbox_path);
+    };
+  };
+}