[exim-cvs] cvs commit: exim/exim-doc/doc-docbook spec.xfpt …

Top Page
Delete this message
Reply to this message
Author: Phil Pennock
Date:  
To: exim-cvs
Subject: [exim-cvs] cvs commit: exim/exim-doc/doc-docbook spec.xfpt exim/exim-doc/doc-txt ChangeLog NewStuff exim/exim-src/src EDITME config.h.defaults demime.c exim.c functions.h malware.c receive.c regex.c
pdp 2010/06/05 12:13:31 BST

  Modified files:
    exim-doc/doc-docbook spec.xfpt 
    exim-doc/doc-txt     ChangeLog NewStuff 
    exim-src/src         EDITME config.h.defaults demime.c exim.c 
                         functions.h malware.c receive.c regex.c 
                         spam.c spool_mbox.c 
  Log:
  ClamAV INSTREAM scanning by default, unless built with WITH_OLD_CLAMAV_STREAM.
  New command-line option, -bmalware (restricted to admin_user).
  Fixes: #926


  Revision  Changes    Path
  1.77      +31 -1     exim/exim-doc/doc-docbook/spec.xfpt
  1.621     +4 -0      exim/exim-doc/doc-txt/ChangeLog
  1.168     +17 -0     exim/exim-doc/doc-txt/NewStuff
  1.25      +9 -0      exim/exim-src/src/EDITME
  1.19      +1 -0      exim/exim-src/src/config.h.defaults
  1.10      +1 -1      exim/exim-src/src/demime.c
  1.66      +35 -2     exim/exim-src/src/exim.c
  1.49      +1 -1      exim/exim-src/src/functions.h
  1.19      +427 -177  exim/exim-src/src/malware.c
  1.55      +1 -1      exim/exim-src/src/receive.c
  1.8       +1 -1      exim/exim-src/src/regex.c
  1.18      +1 -1      exim/exim-src/src/spam.c
  1.15      +22 -10    exim/exim-src/src/spool_mbox.c


  Index: spec.xfpt
  ===================================================================
  RCS file: /home/cvs/exim/exim-doc/doc-docbook/spec.xfpt,v
  retrieving revision 1.76
  retrieving revision 1.77
  diff -u -r1.76 -r1.77
  --- spec.xfpt    5 Jun 2010 10:04:43 -0000    1.76
  +++ spec.xfpt    5 Jun 2010 11:13:29 -0000    1.77
  @@ -1,4 +1,4 @@
  -. $Cambridge: exim/exim-doc/doc-docbook/spec.xfpt,v 1.76 2010/06/05 10:04:43 pdp Exp $
  +. $Cambridge: exim/exim-doc/doc-docbook/spec.xfpt,v 1.77 2010/06/05 11:13:29 pdp Exp $
   .
   . /////////////////////////////////////////////////////////////////////////////
   . This is the primary source of the Exim Manual. It is an xfpt document that is
  @@ -3169,6 +3169,17 @@
   Exim behaves in exactly the same way as it does when receiving a message via
   the listening daemon.


+.vitem &%-bmalware%&&~<&'filename'&>
+.oindex "&%-bmalware%&"
+.cindex "testing", "malware"
+.cindex "malware scan test"
+This debugging option causes Exim to scan the given file,
+using the malware scanning framework. The option of av_scanner influences
+this option, so if av_scanner's value is dependent upon an expansion then
+the expansion should have defaults which apply to this invocation. Exim will
+have changed working directory before resolving the filename, so using fully
+qualified pathnames is advisable. This option requires admin privileges.
+
.vitem &%-bt%&
.oindex "&%-bt%&"
.cindex "testing" "addresses"
@@ -13952,6 +13963,14 @@
the generic transport option &%message_size_limit%&, which limits the size of
message that an individual transport can process.

+If you use a virus-scanner and set this option to to a value larger than the
+maximum size that your virus-scanner is configured to support, you may get
+failures triggered by large mails. The right size to configure for the
+virus-scanner depends upon what data is passed and the options in use but it's
+probably safest to just set it to a little larger than this value. Eg, with a
+default Exim message size of 50M and a default ClamAV StreamMaxLength of 10M,
+some problems may result.
+

.option move_frozen_messages main boolean false
.cindex "frozen messages" "moving"
@@ -27884,8 +27903,16 @@
number, and a port, separated by space, as in the second of these examples:
.code
av_scanner = clamd:/opt/clamd/socket
-av_scanner = clamd:192.168.2.100 1234
+av_scanner = clamd:192.0.2.3 1234
+av_scanner = clamd:192.0.2.3 1234:local
.endd
+If the value of av_scanner points to a UNIX socket file or contains the local
+keyword, then the ClamAV interface will pass a filename containing the data
+to be scanned, which will should normally result in less I/O happening and be
+more efficient. Normally in the TCP case, the data is streamed to ClamAV as
+Exim does not assume that there is a common filesystem with the remote host.
+There is an option WITH_OLD_CLAMAV_STREAM in &_src/EDITME_& available, should
+you be running a version of ClamAV prior to 0.95.
If the option is unset, the default is &_/tmp/clamd_&. Thanks to David Saez for
contributing the code for this scanner.

@@ -28025,6 +28052,9 @@
use the &%demime%& condition (see section &<<SECTdemimecond>>&) before the
&%malware%& condition.

+Beware the interaction of Exim's &%message_size_limit%& with any size limits
+imposed by your anti-virus scanner.
+
Here is a very simple scanning example:
.code
deny message = This message contains malware ($malware_name)

  Index: ChangeLog
  ===================================================================
  RCS file: /home/cvs/exim/exim-doc/doc-txt/ChangeLog,v
  retrieving revision 1.620
  retrieving revision 1.621
  diff -u -r1.620 -r1.621
  --- ChangeLog    5 Jun 2010 10:34:29 -0000    1.620
  +++ ChangeLog    5 Jun 2010 11:13:29 -0000    1.621
  @@ -1,4 +1,4 @@
  -$Cambridge: exim/exim-doc/doc-txt/ChangeLog,v 1.620 2010/06/05 10:34:29 pdp Exp $
  +$Cambridge: exim/exim-doc/doc-txt/ChangeLog,v 1.621 2010/06/05 11:13:29 pdp Exp $


   Change log file for Exim from version 4.21
   -------------------------------------------
  @@ -27,6 +27,10 @@
         an assumption that peers always have certificates.  Be a little more
         paranoid.  Problem reported by Martin Tscholak.


  +PP/08 Bugzilla 926: switch ClamAV to use the new zINSTREAM API for content
  +      filtering; old API available if built with WITH_OLD_CLAMAV_STREAM=yes
  +      NB: ClamAV planning to remove STREAM in "middle of 2010".
  +


Exim version 4.72
-----------------

  Index: NewStuff
  ===================================================================
  RCS file: /home/cvs/exim/exim-doc/doc-txt/NewStuff,v
  retrieving revision 1.167
  retrieving revision 1.168
  diff -u -r1.167 -r1.168
  --- NewStuff    5 Jun 2010 10:04:43 -0000    1.167
  +++ NewStuff    5 Jun 2010 11:13:29 -0000    1.168
  @@ -1,4 +1,4 @@
  -$Cambridge: exim/exim-doc/doc-txt/NewStuff,v 1.167 2010/06/05 10:04:43 pdp Exp $
  +$Cambridge: exim/exim-doc/doc-txt/NewStuff,v 1.168 2010/06/05 11:13:29 pdp Exp $


   New Features in Exim
   --------------------
  @@ -26,6 +26,23 @@
       so that safety mechanism would have to be overriden for this option to
       be able to take effect.


  + 3. ClamAV 0.95 is now required for ClamAV support in Exim, unless
  +    Local/Makefile sets: WITH_OLD_CLAMAV_STREAM=yes
  +    Note that this switches Exim to use a new API ("INSTREAM") and a future
  +    release of ClamAV will remove support for the old API ("STREAM").
  +
  +    The av_scanner option, when set to "clamd", now takes an optional third
  +    part, "local", which causes Exim to pass a filename to ClamAV instead of
  +    the file content.  This is the same behaviour as when clamd is pointed at
  +    a Unix-domain socket.  For example:
  +
  +      av_scanner = clamd:192.0.2.3 1234:local
  +
  + 4. There is now a -bmalware option, restricted to admin users.  This option
  +    takes one parameter, a filename, and scans that file with Exim's
  +    malware-scanning framework.  This is intended purely as a debugging aid
  +    to ensure that Exim's scanning is working, not to replace other tools.
  +


Version 4.72
------------

  Index: EDITME
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/EDITME,v
  retrieving revision 1.24
  retrieving revision 1.25
  diff -u -r1.24 -r1.25
  --- EDITME    3 Jun 2010 15:20:41 -0000    1.24
  +++ EDITME    5 Jun 2010 11:13:29 -0000    1.25
  @@ -1,4 +1,4 @@
  -# $Cambridge: exim/exim-src/src/EDITME,v 1.24 2010/06/03 15:20:41 jetmore Exp $
  +# $Cambridge: exim/exim-src/src/EDITME,v 1.25 2010/06/05 11:13:29 pdp Exp $


   ##################################################
   #          The Exim mail transport agent         #
  @@ -352,6 +352,15 @@


# WITH_OLD_DEMIME=yes

+# If you're using ClamAV and are backporting fixes to an old version, instead
+# of staying current (which is the more usual approach) then you may need to
+# use an older API which uses a STREAM command, now deprecated, instead of
+# zINSTREAM. If you need to set this, please let the Exim developers know, as
+# if nobody reports a need for it, we'll remove this option and clean up the
+# code. zINSTREAM was introduced with ClamAV 0.95.
+#
+# WITH_OLD_CLAMAV_STREAM=yes
+
#------------------------------------------------------------------------------
# By default Exim includes code to support DKIM (DomainKeys Identified
# Mail, RFC4871) signing and verification. Verification of signatures is

  Index: config.h.defaults
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/config.h.defaults,v
  retrieving revision 1.18
  retrieving revision 1.19
  diff -u -r1.18 -r1.19
  --- config.h.defaults    16 Nov 2009 19:50:36 -0000    1.18
  +++ config.h.defaults    5 Jun 2010 11:13:29 -0000    1.19
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/config.h.defaults,v 1.18 2009/11/16 19:50:36 nm4 Exp $ */
  +/* $Cambridge: exim/exim-src/src/config.h.defaults,v 1.19 2010/06/05 11:13:29 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -146,6 +146,7 @@


#define WITH_CONTENT_SCAN
#define WITH_OLD_DEMIME
+#define WITH_OLD_CLAMAV_STREAM

/* EXPERIMENTAL features */
#define EXPERIMENTAL_SPF

  Index: demime.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/demime.c,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- demime.c    22 Feb 2006 14:46:44 -0000    1.9
  +++ demime.c    5 Jun 2010 11:13:29 -0000    1.10
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/demime.c,v 1.9 2006/02/22 14:46:44 ph10 Exp $ */
  +/* $Cambridge: exim/exim-src/src/demime.c,v 1.10 2010/06/05 11:13:29 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -47,7 +47,7 @@
     };


     /* make sure the eml mbox file is spooled up */
  -  mbox_file = spool_mbox(&mbox_size);
  +  mbox_file = spool_mbox(&mbox_size, NULL);


     if (mbox_file == NULL) {
       /* error while spooling */


  Index: exim.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/exim.c,v
  retrieving revision 1.65
  retrieving revision 1.66
  diff -u -r1.65 -r1.66
  --- exim.c    16 Nov 2009 19:50:36 -0000    1.65
  +++ exim.c    5 Jun 2010 11:13:29 -0000    1.66
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/exim.c,v 1.65 2009/11/16 19:50:36 nm4 Exp $ */
  +/* $Cambridge: exim/exim-src/src/exim.c,v 1.66 2010/06/05 11:13:29 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -1355,6 +1355,7 @@
   uschar *ftest_localpart = NULL;
   uschar *ftest_prefix = NULL;
   uschar *ftest_suffix = NULL;
  +uschar *malware_test_file = NULL;
   uschar *real_sender_address;
   uschar *originator_home = US"/";
   void *reset_point;
  @@ -1821,6 +1822,14 @@


       else if (Ustrcmp(argrest, "m") == 0) receiving_message = TRUE;


  +    /* -bmalware: test the filename given for malware */
  +
  +    else if (Ustrcmp(argrest, "malware") == 0)
  +      {
  +      if (++i >= argc) { badarg = TRUE; break; }
  +      malware_test_file = argv[i];
  +      }
  +
       /* -bnq: For locally originating messages, do not qualify unqualified
       addresses. In the envelope, this causes errors; in header lines they
       just get left. */
  @@ -3592,12 +3601,13 @@
   user may request that a message be returned to its sender forthwith. Only an
   admin user may specify a debug level greater than D_v (because it might show
   passwords, etc. in lookup queries). Only an admin user may request a queue
  -count. */
  +count. Only an admin user can use the test interface to scan for email
  +(because Exim will be in the spool dir and able to look at mails). */


   if (!admin_user)
     {
     BOOL debugset = (debug_selector & ~D_v) != 0;
  -  if (deliver_give_up || daemon_listen ||
  +  if (deliver_give_up || daemon_listen || malware_test_file ||
        (count_queue && queue_list_requires_admin) ||
        (list_queue && queue_list_requires_admin) ||
        (queue_interval >= 0 && prod_requires_admin) ||
  @@ -3748,6 +3758,29 @@


else setgid(exim_gid);

  +/* Handle a request to scan a file for malware */
  +if (malware_test_file)
  +  {
  +  int result;
  +  set_process_info("scanning file for malware");
  +  result = malware_in_file(malware_test_file);
  +  if (result == FAIL)
  +    {
  +    printf("No malware found.\n");
  +    exit(EXIT_SUCCESS);
  +    }
  +  if (result != OK)
  +    {
  +    printf("Malware lookup returned non-okay/fail: %d\n", result);
  +    exit(EXIT_FAILURE);
  +    }
  +  if (malware_name)
  +    printf("Malware found: %s\n", malware_name);
  +  else
  +    printf("Malware scan detected malware of unknown name.\n");
  +  exit(EXIT_FAILURE);
  +  }
  +
   /* Handle a request to list the delivery queue */


if (list_queue)

  Index: functions.h
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/functions.h,v
  retrieving revision 1.48
  retrieving revision 1.49
  diff -u -r1.48 -r1.49
  --- functions.h    5 Jun 2010 09:10:10 -0000    1.48
  +++ functions.h    5 Jun 2010 11:13:30 -0000    1.49
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/functions.h,v 1.48 2010/06/05 09:10:10 pdp Exp $ */
  +/* $Cambridge: exim/exim-src/src/functions.h,v 1.49 2010/06/05 11:13:30 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -297,7 +297,7 @@
   extern int     smtp_write_command(smtp_outblock *, BOOL, char *, ...);
   #ifdef WITH_CONTENT_SCAN
   extern int     spam(uschar **);
  -extern FILE   *spool_mbox(unsigned long *);
  +extern FILE   *spool_mbox(unsigned long *, uschar *);
   #endif
   extern BOOL    spool_move_message(uschar *, uschar *, uschar *, uschar *);
   extern BOOL    spool_open_datafile(uschar *);


  Index: malware.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/malware.c,v
  retrieving revision 1.18
  retrieving revision 1.19
  diff -u -r1.18 -r1.19
  --- malware.c    11 Nov 2009 10:08:01 -0000    1.18
  +++ malware.c    5 Jun 2010 11:13:30 -0000    1.19
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/malware.c,v 1.18 2009/11/11 10:08:01 nm4 Exp $ */
  +/* $Cambridge: exim/exim-src/src/malware.c,v 1.19 2010/06/05 11:13:30 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -13,7 +13,8 @@
   #ifdef WITH_CONTENT_SCAN


/* declaration of private routines */
-int mksd_scan_packed(int sock);
+static int mksd_scan_packed(int sock, uschar *scan_filename);
+static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking);

/* SHUT_WR seems to be undefined on Unixware? */
#ifndef SHUT_WR
@@ -48,7 +49,104 @@
uschar malware_name_buffer[256];
int malware_ok = 0;

  +/* Gross hacks for the -bmalware option; perhaps we should just create
  +the scan directory normally for that case, but look into rigging up the
  +needed header variables if not already set on the command-line? */
  +extern int spool_mbox_ok;
  +extern uschar spooled_message_id[17];
  +
  +/*************************************************
  +*          Scan an email for malware             *
  +*************************************************/
  +
  +/* This is the normal interface for scanning an email, which doesn't need a
  +filename; it's a wrapper around the malware_file function.
  +
  +Arguments:
  +  listptr     the list of options to the "malware = ..." ACL condition
  +
  +Returns:      Exim message processing code (OK, FAIL, DEFER, ...)
  +              where true means malware was found (condition applies)
  +*/
   int malware(uschar **listptr) {
  +  uschar scan_filename[1024];
  +  BOOL fits;
  +
  +  fits = string_format(scan_filename, sizeof(scan_filename),
  +      CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
  +  if (!fits)
  +    {
  +    log_write(0, LOG_MAIN|LOG_PANIC,
  +        "malware filename does not fit in buffer [malware()]");
  +    return DEFER;
  +  }
  +
  +  return malware_internal(listptr, scan_filename, FALSE);
  +}
  +
  +
  +/*************************************************
  +*          Scan a file for malware               *
  +*************************************************/
  +
  +/* This is a test wrapper for scanning an email, which is not used in
  +normal processing.  Scan any file, using the Exim scanning interface.
  +This function tampers with various global variables so is unsafe to use
  +in any other context.
  +
  +Arguments:
  +  eml_filename  a file holding the message to be scanned
  +
  +Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
  +                where true means malware was found (condition applies)
  +*/
  +int malware_in_file(uschar *eml_filename) {
  +  uschar *scan_options[2];
  +  uschar message_id_buf[64];
  +  int ret;
  +
  +  scan_options[0] = "*";
  +  scan_options[1] = NULL;
  +
  +  /* spool_mbox() assumes various parameters exist, when creating
  +  the relevant directory and the email within */
  +  (void) string_format(message_id_buf, sizeof(message_id_buf),
  +      US"dummy-%d", pseudo_random_number(INT_MAX));
  +  message_id = message_id_buf;
  +  sender_address = "malware-sender@???";
  +  return_path = "";
  +  recipients_list = NULL;
  +  receive_add_recipient("malware-victim@???", -1);
  +  enable_dollar_recipients = TRUE;
  +
  +  ret = malware_internal(scan_options, eml_filename, TRUE);
  +
  +  strncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
  +  spool_mbox_ok = 1;
  +  /* don't set no_mbox_unspool; at present, there's no way for it to become
  +  set, but if that changes, then it should apply to these tests too */
  +  unspool_mbox();
  +
  +  return ret;
  +}
  +
  +
  +/*************************************************
  +*          Scan content for malware              *
  +*************************************************/
  +
  +/* This is an internal interface for scanning an email; the normal interface
  +is via malware(), or there's malware_in_file() used for testing/debugging.
  +
  +Arguments:
  +  listptr       the list of options to the "malware = ..." ACL condition
  +  eml_filename  the file holding the email to be scanned
  +  faking        whether or not we're faking this up for the -bmalware test
  +
  +Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
  +                where true means malware was found (condition applies)
  +*/
  +static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) {
     int sep = 0;
     uschar *list = *listptr;
     uschar *av_scanner_work = av_scanner;
  @@ -64,7 +162,7 @@
     const uschar *rerror;


     /* make sure the eml mbox file is spooled up */
  -  mbox_file = spool_mbox(&mbox_size);
  +  mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL);
     if (mbox_file == NULL) {
       /* error while spooling */
       log_write(0, LOG_MAIN|LOG_PANIC,
  @@ -202,8 +300,8 @@
         return DEFER;
       }


  -    (void)string_format(scanrequest, 1024, CS"GET %s/scan/%s/%s.eml",
  -          spool_directory, message_id, message_id);
  +    DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name);
  +    (void)string_format(scanrequest, 1024, CS"GET %s", eml_filename);


       while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep,
         fp_scan_option_buffer, sizeof(fp_scan_option_buffer))) != NULL) {
  @@ -257,7 +355,6 @@
       int sock, result, ovector[30];
       unsigned int port, 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;
  @@ -310,16 +407,14 @@
         /* prepare variables */
         drweb_cmd = htonl(DRWEBD_SCAN_CMD);
         drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
  -      (void)string_format(scanrequest, 1024,CS"%s/scan/%s/%s.eml",
  -            spool_directory, message_id, message_id);


         /* calc file size */
  -      drweb_fd = open(CS scanrequest, O_RDONLY);
  +      drweb_fd = open(CS eml_filename, O_RDONLY);
         if (drweb_fd == -1) {
           (void)close(sock);
           log_write(0, LOG_MAIN|LOG_PANIC,
             "malware acl condition: drweb: can't open spool file %s: %s",
  -          scanrequest, strerror(errno));
  +          eml_filename, strerror(errno));
           return DEFER;
         }
         fsize = lseek(drweb_fd, 0, SEEK_END);
  @@ -328,12 +423,15 @@
           (void)close(drweb_fd);
           log_write(0, LOG_MAIN|LOG_PANIC,
             "malware acl condition: drweb: can't seek spool file %s: %s",
  -          scanrequest, strerror(errno));
  +          eml_filename, strerror(errno));
           return DEFER;
         }
         drweb_slen = htonl(fsize);
         lseek(drweb_fd, 0, SEEK_SET);


  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s %u]\n",
  +          scanner_name, hostname, port);
  +
         /* send scan request */
         if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
             (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
  @@ -352,7 +450,7 @@
           (void)close(drweb_fd);
           log_write(0, LOG_MAIN|LOG_PANIC,
             "malware acl condition: drweb: unable to allocate memory %u for file (%s)",
  -          fsize, scanrequest);
  +          fsize, eml_filename);
           return DEFER;
         }


  @@ -363,7 +461,7 @@
           free(drweb_fbuf);
           log_write(0, LOG_MAIN|LOG_PANIC,
             "malware acl condition: drweb: can't read spool file %s: %s",
  -          scanrequest, strerror(errno));
  +          eml_filename, strerror(errno));
           return DEFER;
         }
         (void)close(drweb_fd);
  @@ -398,14 +496,16 @@
         /* prepare variables */
         drweb_cmd = htonl(DRWEBD_SCAN_CMD);
         drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
  -      (void)string_format(scanrequest, 1024,CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
  -      drweb_slen = htonl(Ustrlen(scanrequest));
  +      drweb_slen = htonl(Ustrlen(eml_filename));
  +
  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n",
  +          scanner_name, drweb_options);


         /* 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, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
             (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) {
           (void)close(sock);
           log_write(0, LOG_MAIN|LOG_PANIC,
  @@ -557,7 +657,9 @@
         };


         /* prepare our command */
  -      (void)string_format(buf, 32768, "SCAN bPQRSTUW %s/scan/%s/%s.eml\r\n", spool_directory, message_id, message_id);
  +      (void)string_format(buf, 32768, "SCAN bPQRSTUW %s\r\n", eml_filename);
  +
  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name);


         /* and send it */
         if (send(sock, buf, Ustrlen(buf), 0) < 0) {
  @@ -577,8 +679,8 @@
     } else if (buf[0] == '5') {
             /* aveserver is having problems */
             log_write(0, LOG_MAIN|LOG_PANIC,
  -             "malware acl condition: unable to scan file %s/scan/%s/%s.eml (Responded: %s).",
  -       spool_directory, message_id, message_id, buf);
  +             "malware acl condition: unable to scan file %s (Responded: %s).",
  +       eml_filename, buf);
             result = DEFER;
       break;
     } else if (Ustrncmp(buf,"322",3) == 0) {
  @@ -656,6 +758,9 @@
           return DEFER;
         }


  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
  +          scanner_name, fsecure_options);
  +
         /* pass options */
         memset(av_buffer, 0, sizeof(av_buffer));
         for (i=0; i != 4; i++) {
  @@ -682,7 +787,7 @@
         };


         /* pass the mailfile to fsecure */
  -      (void)string_format(file_name,1024,"SCAN\t%s/scan/%s/%s.eml\n", spool_directory, message_id, message_id);
  +      (void)string_format(file_name,1024,"SCAN\t%s\n", eml_filename);
         /* debug_printf("send scan %s",file_name); */
         if (write(sock, file_name, Ustrlen(file_name)) < 0) {
           (void)close(sock);
  @@ -745,6 +850,8 @@
         int kav_rc;
         unsigned long kav_reportlen, bread;
         pcre *kav_re;
  +      uschar *p;
  +      int fits;


         if ((kav_options = string_nextinlist(&av_scanner_work, &sep,
                                              kav_options_buffer,
  @@ -771,8 +878,24 @@


         /* 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));
  -      (void)string_format(scanrequest, 1024,CS tmpbuf, spool_directory, message_id);
  +      /* pdp note: before the eml_filename parameter, this scanned the
  +      directory; not finding documentation, so we'll strip off the directory.
  +      The side-effect is that the test framework scanning may end up in
  +      scanning more than was requested, but for the normal interface, this is
  +      fine. */
  +      strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s", localtime(&t));
  +      fits = string_format(scanrequest, 1024,CS tmpbuf, eml_filename);
  +      if (!fits) {
  +        (void)close(sock);
  +        log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware filename does not fit in buffer [malware_internal() kavdaemon]");
  +      }
  +      p = strrchr(scanrequest, '/');
  +      if (p)
  +        *p = '\0';
  +
  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
  +          scanner_name, kav_options);


         /* send scan request */
         if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) {
  @@ -917,6 +1040,8 @@
         int trigger = 0;
         int result;
         int ovector[30];
  +      uschar *p;
  +      BOOL fits;


         /* find scanner command line */
         if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep,
  @@ -964,12 +1089,36 @@
           return DEFER;
         };


  -      /* prepare scanner call */
  -      (void)string_format(file_name,1024,"%s/scan/%s", spool_directory, message_id);
  -      (void)string_format(commandline,1024, CS cmdline_scanner,file_name);
  +      /* prepare scanner call; despite the naming, file_name holds a directory
  +      name which is documented as the value given to %s. */
  +      if (Ustrlen(eml_filename) > sizeof(file_name) - 1)
  +        {
  +        log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware filename does not fit in buffer [malware_internal() cmdline]");
  +        return DEFER;
  +        }
  +      p = strrchr(eml_filename, '/');
  +      if (p)
  +        *p = '\0';
  +      fits = string_format(commandline, sizeof(commandline), CS cmdline_scanner, file_name);
  +      if (!fits)
  +        {
  +        log_write(0, LOG_MAIN|LOG_PANIC,
  +            "cmdline scanner command-line does not fit in buffer");
  +        return DEFER;
  +        }
  +
         /* redirect STDERR too */
  +      if (Ustrlen(commandline) + 5 > sizeof(commandline))
  +        {
  +        log_write(0, LOG_MAIN|LOG_PANIC,
  +            "cmdline scanner command-line does not fit in buffer (STDERR redirect)");
  +        return DEFER;
  +        }
         Ustrcat(commandline," 2>&1");


  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline);
  +
         /* store exims signal handlers */
         eximsigchld = signal(SIGCHLD,SIG_DFL);
         eximsigpipe = signal(SIGPIPE,SIG_DFL);
  @@ -1047,7 +1196,9 @@
         uschar sophie_options_default[] = "/var/run/sophie";
         int bread = 0;
         struct sockaddr_un server;
  -      int sock;
  +      int sock, len;
  +      uschar *p;
  +      BOOL fits;
         uschar file_name[1024];
         uschar av_buffer[1024];


  @@ -1075,7 +1226,22 @@
         }


         /* pass the scan directory to sophie */
  -      (void)string_format(file_name,1024,"%s/scan/%s", spool_directory, message_id);
  +      len = Ustrlen(eml_filename) + 1;
  +      if (len > sizeof(file_name))
  +        {
  +        (void)close(sock);
  +        log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware filename does not fit in buffer [malware_internal() sophie]");
  +        return DEFER;
  +        }
  +      memcpy(file_name, eml_filename, len);
  +      p = strrchr(file_name, '/');
  +      if (p)
  +        *p = '\0';
  +
  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
  +          scanner_name, sophie_options);
  +
         if (write(sock, file_name, Ustrlen(file_name)) < 0) {
           (void)close(sock);
           log_write(0, LOG_MAIN|LOG_PANIC,
  @@ -1116,7 +1282,17 @@



       /* "clamd" scanner type ------------------------------------------------- */
  -    /* This code was contributed by David Saez */
  +    /* This code was originally contributed by David Saez */
  +    /* There are three scanning methods available to us:
  +     *  (1) Use the SCAN command, pointing to a file in the filesystem
  +     *  (2) Use the STREAM command, send the data on a separate port
  +     *  (3) Use the zINSTREAM command, send the data inline
  +     * The zINSTREAM command was introduced with ClamAV 0.95, which marked
  +     * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095
  +     * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that
  +     * the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless
  +     * WITH_OLD_CLAMAV_STREAM is defined.
  +     * See Exim bug 926 for details.  */
       else if (strcmpic(scanner_name,US"clamd") == 0) {
         uschar *clamd_options;
         uschar clamd_options_buffer[1024];
  @@ -1133,11 +1309,16 @@
         uschar *clamd_options2;
         uschar clamd_options2_buffer[1024];
         uschar clamd_options2_default[] = "";
  -      uschar av_buffer2[1024];
         uschar *clamav_fbuf;
         uschar scanrequest[1024];
         int sockData, clam_fd, result;
         unsigned int fsize;
  +      BOOL use_scan_command, fits;
  +#ifdef WITH_OLD_CLAMAV_STREAM
  +      uschar av_buffer2[1024];
  +#else
  +      uint32_t send_size, send_final_zeroblock;
  +#endif


         if ((clamd_options = string_nextinlist(&av_scanner_work, &sep,
                                                clamd_options_buffer,
  @@ -1151,9 +1332,18 @@
           clamd_options2 = clamd_options2_default;
         }


  +      if ((*clamd_options == '/') || (strcmpic(clamd_options2,US"local") == 0))
  +        use_scan_command = TRUE;
  +      else
  +        use_scan_command = FALSE;
  +
         /* socket does not start with '/' -> network socket */
         if (*clamd_options != '/') {


  +        /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd
  +         * only supports AF_INET, but we should probably be looking to the
  +         * future and rewriting this to be protocol-independent anyway. */
  +
           /* extract host and port part */
           if( sscanf(CS clamd_options, "%s %u", hostname, &port) != 2 ) {
             log_write(0, LOG_MAIN|LOG_PANIC,
  @@ -1186,166 +1376,225 @@
             return DEFER;
           }


  -        if (strcmpic(clamd_options2,US"local") == 0) {
  -
  -      /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
  -
  -          (void)string_format(file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
  +      } 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;
  +        }


  -          if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
  -            (void)close(sock);
  -            log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
  -                  strerror(errno));
  -            return DEFER;
  -          }
  -        } else {
  +        server.sun_family = AF_UNIX;
  +        Ustrcpy(server.sun_path, clamd_options);


  -      /* Pass the string to ClamAV (7 = "STREAM\n") */
  +        if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
  +          (void)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;
  +        }
  +      }


  -          if (send(sock, "STREAM\n", 7, 0) < 0) {
  -            (void)close(sock);
  -            log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
  -                  strerror(errno));
  -            return DEFER;
  -          }
  -          memset(av_buffer2, 0, sizeof(av_buffer2));
  -          bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);
  +      /* have socket in variable "sock"; command to use is semi-independent of
  +       * the socket protocol.  We use SCAN if is local (either Unix/local
  +       * domain socket, or explicitly told local) else we stream the data.
  +       * How we stream the data depends upon how we were built.  */
  +
  +      if (!use_scan_command) {
  +
  +#ifdef WITH_OLD_CLAMAV_STREAM
  +        /* "STREAM\n" command, get back a "PORT <N>\n" response, send data to
  +         * that port on a second connection; then in the scan-method-neutral
  +         * part, read the response back on the original connection. */
  +
  +        DEBUG(D_acl) debug_printf("Malware scan: issuing %s old-style remote scan (PORT)\n",
  +            scanner_name);
  +
  +        /* Pass the string to ClamAV (7 = "STREAM\n") */
  +        if (send(sock, "STREAM\n", 7, 0) < 0) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
  +                strerror(errno));
  +          (void)close(sock);
  +          return DEFER;
  +        }
  +        memset(av_buffer2, 0, sizeof(av_buffer2));
  +        bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT);


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


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


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


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


  -          if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
  -            log_write(0, LOG_MAIN|LOG_PANIC,
  -                    "malware acl condition: clamd: unable to acquire socket (%s)",
  -                    strerror(errno));
  -            return DEFER;
  -          }
  +        if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +                  "malware acl condition: clamd: unable to acquire socket (%s)",
  +                  strerror(errno));
  +          (void)close(sock);
  +          return DEFER;
  +        }


  -          if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
  -            (void)close(sockData);
  -            log_write(0, LOG_MAIN|LOG_PANIC,
  -                    "malware acl condition: clamd: connection to %s, port %u failed (%s)",
  -                    inet_ntoa(in), port, strerror(errno));
  -            return DEFER;
  -          }
  +        if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +                  "malware acl condition: clamd: connection to %s, port %u failed (%s)",
  +                  inet_ntoa(in), port, strerror(errno));
  +          (void)close(sockData); (void)close(sock);
  +          return DEFER;
  +        }


  -      (void)string_format(scanrequest, 1024,CS"%s/scan/%s/%s.eml",
  -      spool_directory, message_id, message_id);
  +#define CLOSE_SOCKDATA (void)close(sockData)
  +#else /* WITH_OLD_CLAMAV_STREAM not defined */
  +        /* New protocol: "zINSTREAM\n" followed by a sequence of <length><data>
  +        chunks, <n> a 4-byte number (network order), terminated by a zero-length
  +        chunk. */


  -    /* calc file size */
  -    clam_fd = open(CS scanrequest, O_RDONLY);
  -    if (clam_fd == -1) {
  -      log_write(0, LOG_MAIN|LOG_PANIC,
  -        "malware acl condition: clamd: can't open spool file %s: %s",
  -        scanrequest, strerror(errno));
  -      return DEFER;
  -    }
  -    fsize = lseek(clam_fd, 0, SEEK_END);
  -    if (fsize == -1) {
  -      log_write(0, LOG_MAIN|LOG_PANIC,
  -        "malware acl condition: clamd: can't seek spool file %s: %s",
  -        scanrequest, strerror(errno));
  -      return DEFER;
  -    }
  -    lseek(clam_fd, 0, SEEK_SET);
  +        DEBUG(D_acl) debug_printf("Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
  +            scanner_name);


  -    clamav_fbuf = (uschar *) malloc (fsize);
  -    if (!clamav_fbuf) {
  -      (void)close(sockData);
  -      (void)close(clam_fd);
  -      log_write(0, LOG_MAIN|LOG_PANIC,
  -        "malware acl condition: clamd: unable to allocate memory %u for file (%s)",
  -        fsize, scanrequest);
  -      return DEFER;
  -    }
  +        /* Pass the string to ClamAV (10 = "zINSTREAM\0") */
  +        if (send(sock, "zINSTREAM", 10, 0) < 0) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +              "malware acl condition: clamd: unable to send zINSTREAM to socket (%s)",
  +              strerror(errno));
  +          (void)close(sock);
  +          return DEFER;
  +        }


  -    result = read (clam_fd, clamav_fbuf, fsize);
  -    if (result == -1) {
  -      (void)close(sockData);
  -      (void)close(clam_fd);
  -      free(clamav_fbuf);
  -      log_write(0, LOG_MAIN|LOG_PANIC,
  -        "malware acl condition: clamd: can't read spool file %s: %s",
  -        scanrequest, strerror(errno));
  -      return DEFER;
  -    }
  -    (void)close(clam_fd);
  +#define CLOSE_SOCKDATA /**/
  +#endif


  -    /* send file body to socket */
  -    if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
  -      (void)close(sockData);
  -      free(clamav_fbuf);
  -      log_write(0, LOG_MAIN|LOG_PANIC,
  -        "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
  -      return DEFER;
  -    }
  -    free(clamav_fbuf);
  -          (void)close(sockData);
  +        /* calc file size */
  +        clam_fd = open(CS eml_filename, O_RDONLY);
  +        if (clam_fd == -1) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware acl condition: clamd: can't open spool file %s: %s",
  +            eml_filename, strerror(errno));
  +          CLOSE_SOCKDATA; (void)close(sock);
  +          return DEFER;
           }
  -      }
  -      else {
  -        /* open the local socket */
  -        if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
  +        fsize = lseek(clam_fd, 0, SEEK_END);
  +        if (fsize == -1) {
             log_write(0, LOG_MAIN|LOG_PANIC,
  -                    "malware acl condition: clamd: unable to acquire socket (%s)",
  -                    strerror(errno));
  +            "malware acl condition: clamd: can't seek spool file %s: %s",
  +            eml_filename, strerror(errno));
  +          CLOSE_SOCKDATA; (void)close(sock);
             return DEFER;
           }
  +        lseek(clam_fd, 0, SEEK_SET);


  -        server.sun_family = AF_UNIX;
  -        Ustrcpy(server.sun_path, clamd_options);
  +        clamav_fbuf = (uschar *) malloc (fsize);
  +        if (!clamav_fbuf) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware acl condition: clamd: unable to allocate memory %u for file (%s)",
  +            fsize, eml_filename);
  +          CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
  +          return DEFER;
  +        }


  -        if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
  -          (void)close(sock);
  +        result = read (clam_fd, clamav_fbuf, fsize);
  +        if (result == -1) {
             log_write(0, LOG_MAIN|LOG_PANIC,
  -                    "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)",
  -                    clamd_options, strerror(errno) );
  +            "malware acl condition: clamd: can't read spool file %s: %s",
  +            eml_filename, strerror(errno));
  +          CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd);
  +          free(clamav_fbuf);
             return DEFER;
           }
  -      }
  +        (void)close(clam_fd);


  -      /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
  +        /* send file body to socket */
  +#ifdef WITH_OLD_CLAMAV_STREAM
  +        if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
  +          CLOSE_SOCKDATA; (void)close(sock);
  +          free(clamav_fbuf);
  +          return DEFER;
  +        }
  +#else
  +        send_size = htonl(fsize);
  +        send_final_zeroblock = 0;
  +        if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
  +            (send(sock, clamav_fbuf, fsize, 0) < 0) ||
  +            (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
  +          {
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +            "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
  +          (void)close(sock);
  +          free(clamav_fbuf);
  +          return DEFER;
  +          }
  +#endif


  -      (void)string_format(file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
  +        free(clamav_fbuf);


  -      if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
  -        (void)close(sock);
  -        log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
  -                  strerror(errno));
  -        return DEFER;
  -      }
  +        CLOSE_SOCKDATA;
  +#undef CLOSE_SOCKDATA


  -      /*
  -        We're done sending, close socket for writing.
  +      } else { /* use scan command */
  +        /* Send a SCAN command pointing to a filename; then in the then in the
  +         * scan-method-neutral part, read the response back */
  +
  +/* ================================================================= */
  +
  +        /* Prior to the reworking post-Exim-4.72, this scanned a directory,
  +        which dates to when ClamAV needed us to break apart the email into the
  +        MIME parts (eg, with the now deprecated demime condition coming first).
  +        Some time back, ClamAV gained the ability to deconstruct the emails, so
  +        doing this would actually have resulted in the mail attachments being
  +        scanned twice, in the broken out files and from the original .eml.
  +        Since ClamAV now handles emails (and has for quite some time) we can
  +        just use the email file itself. */
  +        /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
  +        fits = string_format(file_name, sizeof(file_name), "SCAN %s\n",
  +            eml_filename);
  +        if (!fits) {
  +          (void)close(sock);
  +          log_write(0, LOG_MAIN|LOG_PANIC,
  +              "malware filename does not fit in buffer [malware_internal() clamd]");
  +        }


  -        One user reported that clamd 0.70 does not like this any more ...
  +        DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n",
  +            scanner_name, clamd_options);


  -      */
  +        if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
  +          (void)close(sock);
  +          log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
  +                    strerror(errno));
  +          return DEFER;
  +        }


  -      /* shutdown(sock, SHUT_WR); */
  +        /* Do not shut down the socket for writing; a user report noted that
  +         * clamd 0.70 does not react well to this. */
  +      }
  +      /* Commands have been sent, no matter which scan method or connection
  +       * type we're using; now just read the result, independent of method. */


         /* Read the result */
         memset(av_buffer, 0, sizeof(av_buffer));
  @@ -1368,7 +1617,7 @@
         /* Check the result. ClamAV Returns
            infected: -> "<filename>: <virusname> FOUND"
            not-infected: -> "<filename>: OK"
  -    error: -> "<filename>: <errcode> ERROR */
  +         error: -> "<filename>: <errcode> ERROR */


         if (!(*av_buffer)) {
           log_write(0, LOG_MAIN|LOG_PANIC,
  @@ -1376,10 +1625,12 @@
           return DEFER;
         }


  -      /* strip newline at the end */
  +      /* strip newline at the end (won't be present for zINSTREAM) */
         p = av_buffer + Ustrlen(av_buffer) - 1;
         if( *p == '\n' ) *p = '\0';


  +      DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer);
  +
         /* colon in returned output? */
         if((p = Ustrrchr(av_buffer,':')) == NULL) {
           log_write(0, LOG_MAIN|LOG_PANIC,
  @@ -1398,6 +1649,7 @@
              for (;*vname==32;vname++);
              Ustrcpy(malware_name_buffer,vname);
              malware_name = malware_name_buffer;
  +           DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name);
         }
         else {
              if (Ustrstr(vname, "ERROR")!=NULL) {
  @@ -1413,6 +1665,7 @@
              else {
                 /* Everything should be OK */
                 malware_name = NULL;
  +              DEBUG(D_acl) debug_printf("Malware not found\n");
              }
         }
       }
  @@ -1459,7 +1712,9 @@


         malware_name = NULL;


  -      retval = mksd_scan_packed(sock);
  +      DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);
  +
  +      retval = mksd_scan_packed(sock, eml_filename);


         if (retval != OK)
           return retval;
  @@ -1481,6 +1736,7 @@
     /* match virus name against pattern (caseless ------->----------v) */
     if ( (malware_name != NULL) &&
          (regex_match_and_setup(re, malware_name, 0, -1)) ) {
  +    DEBUG(D_acl) debug_printf("Matched regex to malware [%s] [%s]\n", malware_regex, malware_name);
       return OK;
     }
     else {
  @@ -1510,7 +1766,7 @@


#include <sys/uio.h>

  -int mksd_writev (int sock, struct iovec *iov, int iovcnt)
  +static int mksd_writev (int sock, struct iovec *iov, int iovcnt)
   {
     int i;


  @@ -1539,7 +1795,7 @@
     }
   }


  -int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
  +static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
   {
     int offset = 0;
     int i;
  @@ -1566,7 +1822,7 @@
     return offset;
   }


  -int mksd_parse_line (char *line)
  +static int mksd_parse_line (char *line)
   {
     char *p;


  @@ -1600,26 +1856,20 @@
     }
   }


  -int mksd_scan_packed (int sock)
  +static int mksd_scan_packed(int sock, uschar *scan_filename)
   {
  -  struct iovec iov[7];
  -  char *cmd = "MSQ/scan/.eml\n";
  +  struct iovec iov[3];
  +  char *cmd = "MSQ\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[1].iov_base = CS scan_filename;
  +  iov[1].iov_len = Ustrlen(scan_filename);
     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;
  +  iov[2].iov_len = 1;


  -  if (mksd_writev (sock, iov, 7) < 0)
  +  if (mksd_writev (sock, iov, 3) < 0)
       return DEFER;


     if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)


  Index: receive.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/receive.c,v
  retrieving revision 1.54
  retrieving revision 1.55
  diff -u -r1.54 -r1.55
  --- receive.c    3 Jun 2010 05:40:27 -0000    1.54
  +++ receive.c    5 Jun 2010 11:13:30 -0000    1.55
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/receive.c,v 1.54 2010/06/03 05:40:27 pdp Exp $ */
  +/* $Cambridge: exim/exim-src/src/receive.c,v 1.55 2010/06/05 11:13:30 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -1094,7 +1094,7 @@


   DO_MIME_ACL:
   /* make sure the eml mbox file is spooled up */
  -mbox_file = spool_mbox(&mbox_size);
  +mbox_file = spool_mbox(&mbox_size, NULL);
   if (mbox_file == NULL) {
     /* error while spooling */
     log_write(0, LOG_MAIN|LOG_PANIC,


  Index: regex.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/regex.c,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- regex.c    1 Jul 2005 10:49:02 -0000    1.7
  +++ regex.c    5 Jun 2010 11:13:30 -0000    1.8
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/regex.c,v 1.7 2005/07/01 10:49:02 ph10 Exp $ */
  +/* $Cambridge: exim/exim-src/src/regex.c,v 1.8 2010/06/05 11:13:30 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -47,7 +47,7 @@


     if (mime_stream == NULL) {
       /* We are in the DATA ACL */
  -    mbox_file = spool_mbox(&mbox_size);
  +    mbox_file = spool_mbox(&mbox_size, NULL);
       if (mbox_file == NULL) {
         /* error while spooling */
         log_write(0, LOG_MAIN|LOG_PANIC,


  Index: spam.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/spam.c,v
  retrieving revision 1.17
  retrieving revision 1.18
  diff -u -r1.17 -r1.18
  --- spam.c    18 Jul 2008 17:55:42 -0000    1.17
  +++ spam.c    5 Jun 2010 11:13:30 -0000    1.18
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/spam.c,v 1.17 2008/07/18 17:55:42 fanf2 Exp $ */
  +/* $Cambridge: exim/exim-src/src/spam.c,v 1.18 2010/06/05 11:13:30 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -80,7 +80,7 @@
     };


     /* make sure the eml mbox file is spooled up */
  -  mbox_file = spool_mbox(&mbox_size);
  +  mbox_file = spool_mbox(&mbox_size, NULL);


     if (mbox_file == NULL) {
       /* error while spooling */


  Index: spool_mbox.c
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/spool_mbox.c,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- spool_mbox.c    16 Jan 2008 09:56:55 -0000    1.14
  +++ spool_mbox.c    5 Jun 2010 11:13:30 -0000    1.15
  @@ -1,4 +1,4 @@
  -/* $Cambridge: exim/exim-src/src/spool_mbox.c,v 1.14 2008/01/16 09:56:55 tom Exp $ */
  +/* $Cambridge: exim/exim-src/src/spool_mbox.c,v 1.15 2010/06/05 11:13:30 pdp Exp $ */


   /*************************************************
   *     Exim - an Internet mail transport agent    *
  @@ -25,9 +25,10 @@
   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 */
+/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
+ * normally, source_file_override is NULL */

  -FILE *spool_mbox(unsigned long *mbox_file_size) {
  +FILE *spool_mbox(unsigned long *mbox_file_size, uschar *source_file_override) {
     uschar message_subdir[2];
     uschar buffer[16384];
     uschar *temp_string;
  @@ -100,13 +101,17 @@
       (void)fwrite("\n", 1, 1, mbox_file);


       /* 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;
  -      temp_string = string_sprintf("%s/input/%s/%s-D", spool_directory,
  -        message_subdir, message_id);
  -      data_file = Ufopen(temp_string, "rb");
  -      if (data_file != NULL) break;
  +    if (source_file_override == NULL) {
  +      message_subdir[1] = '\0';
  +      for (i = 0; i < 2; i++) {
  +        message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0;
  +        temp_string = string_sprintf("%s/input/%s/%s-D", spool_directory,
  +          message_subdir, message_id);
  +        data_file = Ufopen(temp_string, "rb");
  +        if (data_file != NULL) break;
  +      };
  +    } else {
  +      data_file = Ufopen(source_file_override, "rb");
       };


       if (data_file == NULL) {
  @@ -125,7 +130,8 @@
        * explicitly, because the one in the file is parted of the locked area.
        */


  -    (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
  +    if (!source_file_override)
  +      (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);


       do {
         j = fread(buffer, 1, sizeof(buffer), data_file);
  @@ -188,6 +194,12 @@
       mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);


       tempdir = opendir(CS mbox_path);
  +    if (!tempdir) {
  +      debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
  +      /* Just in case we still can: */
  +      rmdir(CS mbox_path);
  +      return;
  +    }
       /* loop thru dir & delete entries */
       while((entry = readdir(tempdir)) != NULL) {
         uschar *name = US entry->d_name;