ClamAV INSTREAM scanning by default, unless built with WITH_OLD_CLAMAV_STREAM.
authorPhil Pennock <pdp@exim.org>
Sat, 5 Jun 2010 11:13:29 +0000 (11:13 +0000)
committerPhil Pennock <pdp@exim.org>
Sat, 5 Jun 2010 11:13:29 +0000 (11:13 +0000)
New command-line option, -bmalware (restricted to admin_user).
Fixes: #926
13 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/src/EDITME
src/src/config.h.defaults
src/src/demime.c
src/src/exim.c
src/src/functions.h
src/src/malware.c
src/src/receive.c
src/src/regex.c
src/src/spam.c
src/src/spool_mbox.c

index 2a69fcf59f9e195f31e6ed2fac58b053ed135a0f..5cd8f1c0d527e3be766130b159eb4e47f357de1f 100644 (file)
@@ -1,4 +1,4 @@
-. $Cambridge: exim/doc/doc-docbook/spec.xfpt,v 1.76 2010/06/05 10:04:43 pdp Exp $
+. $Cambridge: 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
 .
 . /////////////////////////////////////////////////////////////////////////////
 . This is the primary source of the Exim Manual. It is an xfpt document that is
@@ -3169,6 +3169,17 @@ above concerning senders and qualification do not apply. In this situation,
 Exim behaves in exactly the same way as it does when receiving a message via
 the listening daemon.
 
 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"
 .vitem &%-bt%&
 .oindex "&%-bt%&"
 .cindex "testing" "addresses"
@@ -13952,6 +13963,14 @@ an oversized message is logged in both the main and the reject logs. See also
 the generic transport option &%message_size_limit%&, which limits the size of
 message that an individual transport can process.
 
 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"
 
 .option move_frozen_messages main boolean false
 .cindex "frozen messages" "moving"
@@ -27884,8 +27903,16 @@ required: either the path and name of a UNIX socket file, or a hostname or IP
 number, and a port, separated by space, as in the second of these examples:
 .code
 av_scanner = clamd:/opt/clamd/socket
 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
-.endd
+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.
 
 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 @@ If your virus scanner cannot unpack MIME and TNEF containers itself, you should
 use the &%demime%& condition (see section &<<SECTdemimecond>>&) before the
 &%malware%& condition.
 
 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)
 Here is a very simple scanning example:
 .code
 deny message = This message contains malware ($malware_name)
index 828e72fb239d18a253b95a5c7cf2e244adc424d4..421997b01a63398db97992a8d40e4a30d1eafb39 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.620 2010/06/05 10:34:29 pdp Exp $
+$Cambridge: 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
 -------------------------------------------
 
 Change log file for Exim from version 4.21
 -------------------------------------------
@@ -27,6 +27,10 @@ PP/07 If TLS negotiated an anonymous cipher, we could end up with SSL but
       an assumption that peers always have certificates.  Be a little more
       paranoid.  Problem reported by Martin Tscholak.
 
       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
 -----------------
 
 Exim version 4.72
 -----------------
index c2c49379f6a68a1d9da72d954eb2c76aa62cfd8d..c4d38fd4f8b638bb977e8a641abe8d9a988e38f3 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/NewStuff,v 1.167 2010/06/05 10:04:43 pdp Exp $
+$Cambridge: exim/doc/doc-txt/NewStuff,v 1.168 2010/06/05 11:13:29 pdp Exp $
 
 New Features in Exim
 --------------------
 
 New Features in Exim
 --------------------
@@ -26,6 +26,23 @@ Version 4.73
     so that safety mechanism would have to be overriden for this option to
     be able to take effect.
 
     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
 ------------
 
 Version 4.72
 ------------
index 02f2fead072862d253682929118c3b3fbaf1be4f..85922f8aaa975f88a96b0cf6e59b807bb126b2da 100644 (file)
@@ -1,4 +1,4 @@
-# $Cambridge: exim/src/src/EDITME,v 1.24 2010/06/03 15:20:41 jetmore Exp $
+# $Cambridge: exim/src/src/EDITME,v 1.25 2010/06/05 11:13:29 pdp Exp $
 
 ##################################################
 #          The Exim mail transport agent         #
 
 ##################################################
 #          The Exim mail transport agent         #
@@ -352,6 +352,15 @@ EXIM_MONITOR=eximon.bin
 
 # WITH_OLD_DEMIME=yes
 
 
 # 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
 #------------------------------------------------------------------------------
 # By default Exim includes code to support DKIM (DomainKeys Identified
 # Mail, RFC4871) signing and verification.  Verification of signatures is
index 8bae17bf60857a490d2e15158579d34debf1f4df..e114c6bc09b27aa6f60886313c88dab615de1787 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/config.h.defaults,v 1.18 2009/11/16 19:50:36 nm4 Exp $ */
+/* $Cambridge: exim/src/src/config.h.defaults,v 1.19 2010/06/05 11:13:29 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -146,6 +146,7 @@ it's a default value. */
 
 #define WITH_CONTENT_SCAN
 #define WITH_OLD_DEMIME
 
 #define WITH_CONTENT_SCAN
 #define WITH_OLD_DEMIME
+#define WITH_OLD_CLAMAV_STREAM
 
 /* EXPERIMENTAL features */
 #define EXPERIMENTAL_SPF
 
 /* EXPERIMENTAL features */
 #define EXPERIMENTAL_SPF
index 2eac6cde6ce37b87b7b62d470d3694774dc38c62..c64c397346b28051dbb17cdcbf728b0cba42dcb2 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/demime.c,v 1.9 2006/02/22 14:46:44 ph10 Exp $ */
+/* $Cambridge: exim/src/src/demime.c,v 1.10 2010/06/05 11:13:29 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -47,7 +47,7 @@ int demime(uschar **listptr) {
   };
 
   /* make sure the eml mbox file is spooled up */
   };
 
   /* 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 */
 
   if (mbox_file == NULL) {
     /* error while spooling */
index 12916e82430aa254a2917454da8c8ff0bc5e9fda..c54470bd6e56062503db41ab92a3a5131b75f8ce 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.65 2009/11/16 19:50:36 nm4 Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.66 2010/06/05 11:13:29 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -1355,6 +1355,7 @@ uschar *ftest_domain = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
 uschar *ftest_suffix = NULL;
 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;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
 void *reset_point;
@@ -1821,6 +1822,14 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "m") == 0) receiving_message = TRUE;
 
 
     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. */
     /* -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 @@ configuration, but the queue run restriction can be relaxed. Only an admin
 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
 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 (!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) ||
      (count_queue && queue_list_requires_admin) ||
      (list_queue && queue_list_requires_admin) ||
      (queue_interval >= 0 && prod_requires_admin) ||
@@ -3748,6 +3758,29 @@ if (!unprivileged &&                      /* originally had root AND */
 
 else setgid(exim_gid);
 
 
 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)
 /* Handle a request to list the delivery queue */
 
 if (list_queue)
index 612d1e70817d6f5ad91212f7a68f557afa755907..436038735d9c7c7cd188dff9cd98ee6f022024bb 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/functions.h,v 1.48 2010/06/05 09:10:10 pdp Exp $ */
+/* $Cambridge: exim/src/src/functions.h,v 1.49 2010/06/05 11:13:30 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -297,7 +297,7 @@ extern BOOL    smtp_verify_helo(void);
 extern int     smtp_write_command(smtp_outblock *, BOOL, char *, ...);
 #ifdef WITH_CONTENT_SCAN
 extern int     spam(uschar **);
 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 *);
 #endif
 extern BOOL    spool_move_message(uschar *, uschar *, uschar *, uschar *);
 extern BOOL    spool_open_datafile(uschar *);
index bbd2939975e5fde59e4aa8f81febd04fcc29de05..5ee52eb84c9696edc9ccdeebd77c46f1986e0228 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/malware.c,v 1.18 2009/11/11 10:08:01 nm4 Exp $ */
+/* $Cambridge: exim/src/src/malware.c,v 1.19 2010/06/05 11:13:30 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -13,7 +13,8 @@
 #ifdef WITH_CONTENT_SCAN
 
 /* declaration of private routines */
 #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
 
 /* SHUT_WR seems to be undefined on Unixware? */
 #ifndef SHUT_WR
@@ -48,7 +49,104 @@ int test_byte_order() {
 uschar malware_name_buffer[256];
 int malware_ok = 0;
 
 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) {
 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@example.net";
+  return_path = "";
+  recipients_list = NULL;
+  receive_add_recipient("malware-victim@example.net", -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;
   int sep = 0;
   uschar *list = *listptr;
   uschar *av_scanner_work = av_scanner;
@@ -64,7 +162,7 @@ int malware(uschar **listptr) {
   const uschar *rerror;
 
   /* make sure the eml mbox file is spooled up */
   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,
   if (mbox_file == NULL) {
     /* error while spooling */
     log_write(0, LOG_MAIN|LOG_PANIC,
@@ -202,8 +300,8 @@ int malware(uschar **listptr) {
       return DEFER;
     }
 
       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) {
 
     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 malware(uschar **listptr) {
     int sock, result, ovector[30];
     unsigned int port, fsize;
     uschar tmpbuf[1024], *drweb_fbuf;
     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;
     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 @@ int malware(uschar **listptr) {
       /* prepare variables */
       drweb_cmd = htonl(DRWEBD_SCAN_CMD);
       drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
       /* 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 */
 
       /* 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",
       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);
         return DEFER;
       }
       fsize = lseek(drweb_fd, 0, SEEK_END);
@@ -328,12 +423,15 @@ int malware(uschar **listptr) {
         (void)close(drweb_fd);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "malware acl condition: drweb: can't seek spool file %s: %s",
         (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);
 
         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) ||
       /* 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 @@ int malware(uschar **listptr) {
         (void)close(drweb_fd);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "malware acl condition: drweb: unable to allocate memory %u for file (%s)",
         (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;
       }
 
         return DEFER;
       }
 
@@ -363,7 +461,7 @@ int malware(uschar **listptr) {
         free(drweb_fbuf);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "malware acl condition: drweb: can't read spool file %s: %s",
         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);
         return DEFER;
       }
       (void)close(drweb_fd);
@@ -398,14 +496,16 @@ int malware(uschar **listptr) {
       /* prepare variables */
       drweb_cmd = htonl(DRWEBD_SCAN_CMD);
       drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL);
       /* 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 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,
           (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) {
         (void)close(sock);
         log_write(0, LOG_MAIN|LOG_PANIC,
@@ -557,7 +657,9 @@ int malware(uschar **listptr) {
       };
 
       /* prepare our command */
       };
 
       /* 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) {
 
       /* and send it */
       if (send(sock, buf, Ustrlen(buf), 0) < 0) {
@@ -577,8 +679,8 @@ int malware(uschar **listptr) {
   } else if (buf[0] == '5') {
           /* aveserver is having problems */
           log_write(0, LOG_MAIN|LOG_PANIC,
   } 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) {
           result = DEFER;
     break;
   } else if (Ustrncmp(buf,"322",3) == 0) {
@@ -656,6 +758,9 @@ int malware(uschar **listptr) {
         return DEFER;
       }
 
         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++) {
       /* pass options */
       memset(av_buffer, 0, sizeof(av_buffer));
       for (i=0; i != 4; i++) {
@@ -682,7 +787,7 @@ int malware(uschar **listptr) {
       };
 
       /* pass the mailfile to fsecure */
       };
 
       /* 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);
       /* debug_printf("send scan %s",file_name); */
       if (write(sock, file_name, Ustrlen(file_name)) < 0) {
         (void)close(sock);
@@ -745,6 +850,8 @@ int malware(uschar **listptr) {
       int kav_rc;
       unsigned long kav_reportlen, bread;
       pcre *kav_re;
       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,
 
       if ((kav_options = string_nextinlist(&av_scanner_work, &sep,
                                            kav_options_buffer,
@@ -771,8 +878,24 @@ int malware(uschar **listptr) {
 
       /* get current date and time, build scan request */
       time(&t);
 
       /* 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) {
 
       /* send scan request */
       if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) {
@@ -917,6 +1040,8 @@ int malware(uschar **listptr) {
       int trigger = 0;
       int result;
       int ovector[30];
       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,
 
       /* find scanner command line */
       if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep,
@@ -964,12 +1089,36 @@ int malware(uschar **listptr) {
         return DEFER;
       };
 
         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 */
       /* 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");
 
       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);
       /* store exims signal handlers */
       eximsigchld = signal(SIGCHLD,SIG_DFL);
       eximsigpipe = signal(SIGPIPE,SIG_DFL);
@@ -1047,7 +1196,9 @@ int malware(uschar **listptr) {
       uschar sophie_options_default[] = "/var/run/sophie";
       int bread = 0;
       struct sockaddr_un server;
       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];
 
       uschar file_name[1024];
       uschar av_buffer[1024];
 
@@ -1075,7 +1226,22 @@ int malware(uschar **listptr) {
       }
 
       /* pass the scan directory to sophie */
       }
 
       /* 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,
       if (write(sock, file_name, Ustrlen(file_name)) < 0) {
         (void)close(sock);
         log_write(0, LOG_MAIN|LOG_PANIC,
@@ -1116,7 +1282,17 @@ int malware(uschar **listptr) {
 
 
     /* "clamd" scanner type ------------------------------------------------- */
 
 
     /* "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];
     else if (strcmpic(scanner_name,US"clamd") == 0) {
       uschar *clamd_options;
       uschar clamd_options_buffer[1024];
@@ -1133,11 +1309,16 @@ int malware(uschar **listptr) {
       uschar *clamd_options2;
       uschar clamd_options2_buffer[1024];
       uschar clamd_options2_default[] = "";
       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;
       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,
 
       if ((clamd_options = string_nextinlist(&av_scanner_work, &sep,
                                              clamd_options_buffer,
@@ -1151,9 +1332,18 @@ int malware(uschar **listptr) {
         clamd_options2 = clamd_options2_default;
       }
 
         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 != '/') {
 
       /* 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,
         /* 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 @@ int malware(uschar **listptr) {
           return DEFER;
         }
 
           return DEFER;
         }
 
-        if (strcmpic(clamd_options2,US"local") == 0) {
+      } 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;
+        }
 
 
-      /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
+        server.sun_family = AF_UNIX;
+        Ustrcpy(server.sun_path, clamd_options);
 
 
-          (void)string_format(file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
+        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, 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 {
+      /* 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.  */
 
 
-      /* Pass the string to ClamAV (7 = "STREAM\n") */
+      if (!use_scan_command) {
 
 
-          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);
+#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. */
 
 
-          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;
-          }
+        DEBUG(D_acl) debug_printf("Malware scan: issuing %s old-style remote scan (PORT)\n",
+            scanner_name);
 
 
-          if (bread == sizeof(av_buffer)) {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: buffer too small");
-            return DEFER;
-          }
+        /* 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 (!(*av_buffer2)) {
-            log_write(0, LOG_MAIN|LOG_PANIC,
-                  "malware acl condition: clamd: ClamAV returned null");
-            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;
+        }
 
 
-          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;
-          };
+        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 ( (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 (!(*av_buffer2)) {
+          log_write(0, LOG_MAIN|LOG_PANIC,
+                "malware acl condition: clamd: ClamAV returned null");
+          (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;
-          }
+        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;
+        };
 
 
-      (void)string_format(scanrequest, 1024,CS"%s/scan/%s/%s.eml",
-      spool_directory, message_id, message_id);
+        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;
+        }
 
 
-    /* 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);
+        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;
+        }
 
 
-    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;
-    }
+#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. */
 
 
-    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);
+        DEBUG(D_acl) debug_printf("Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
+            scanner_name);
 
 
-    /* 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);
-        }
-      }
-      else {
-        /* open the local socket */
-        if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        /* Pass the string to ClamAV (10 = "zINSTREAM\0") */
+        if (send(sock, "zINSTREAM", 10, 0) < 0) {
           log_write(0, LOG_MAIN|LOG_PANIC,
           log_write(0, LOG_MAIN|LOG_PANIC,
-                    "malware acl condition: clamd: unable to acquire socket (%s)",
-                    strerror(errno));
+              "malware acl condition: clamd: unable to send zINSTREAM to socket (%s)",
+              strerror(errno));
+          (void)close(sock);
           return DEFER;
         }
 
           return DEFER;
         }
 
-        server.sun_family = AF_UNIX;
-        Ustrcpy(server.sun_path, clamd_options);
+#define CLOSE_SOCKDATA /**/
+#endif
 
 
-        if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-          (void)close(sock);
+        /* calc file size */
+        clam_fd = open(CS eml_filename, O_RDONLY);
+        if (clam_fd == -1) {
           log_write(0, LOG_MAIN|LOG_PANIC,
           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 open spool file %s: %s",
+            eml_filename, strerror(errno));
+          CLOSE_SOCKDATA; (void)close(sock);
           return DEFER;
         }
           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",
+            eml_filename, strerror(errno));
+          CLOSE_SOCKDATA; (void)close(sock);
+          return DEFER;
+        }
+        lseek(clam_fd, 0, SEEK_SET);
 
 
-      /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
+        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;
+        }
 
 
-      (void)string_format(file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
+        result = read (clam_fd, clamav_fbuf, fsize);
+        if (result == -1) {
+          log_write(0, LOG_MAIN|LOG_PANIC,
+            "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);
 
 
-      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;
-      }
+        /* 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
 
 
-      /*
-        We're done sending, close socket for writing.
+        free(clamav_fbuf);
+
+        CLOSE_SOCKDATA;
+#undef CLOSE_SOCKDATA
+
+      } 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));
 
       /* Read the result */
       memset(av_buffer, 0, sizeof(av_buffer));
@@ -1368,7 +1617,7 @@ int malware(uschar **listptr) {
       /* Check the result. ClamAV Returns
          infected: -> "<filename>: <virusname> FOUND"
          not-infected: -> "<filename>: OK"
       /* 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,
 
       if (!(*av_buffer)) {
         log_write(0, LOG_MAIN|LOG_PANIC,
@@ -1376,10 +1625,12 @@ int malware(uschar **listptr) {
         return DEFER;
       }
 
         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';
 
       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,
       /* colon in returned output? */
       if((p = Ustrrchr(av_buffer,':')) == NULL) {
         log_write(0, LOG_MAIN|LOG_PANIC,
@@ -1398,6 +1649,7 @@ int malware(uschar **listptr) {
            for (;*vname==32;vname++);
            Ustrcpy(malware_name_buffer,vname);
            malware_name = malware_name_buffer;
            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) {
       }
       else {
            if (Ustrstr(vname, "ERROR")!=NULL) {
@@ -1413,6 +1665,7 @@ int malware(uschar **listptr) {
            else {
               /* Everything should be OK */
               malware_name = NULL;
            else {
               /* Everything should be OK */
               malware_name = NULL;
+              DEBUG(D_acl) debug_printf("Malware not found\n");
            }
       }
     }
            }
       }
     }
@@ -1459,7 +1712,9 @@ int malware(uschar **listptr) {
 
       malware_name = NULL;
 
 
       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;
 
       if (retval != OK)
         return retval;
@@ -1481,6 +1736,7 @@ int malware(uschar **listptr) {
   /* match virus name against pattern (caseless ------->----------v) */
   if ( (malware_name != NULL) &&
        (regex_match_and_setup(re, malware_name, 0, -1)) ) {
   /* 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 {
     return OK;
   }
   else {
@@ -1510,7 +1766,7 @@ int recv_line(int sock, uschar *buffer, int size) {
 
 #include <sys/uio.h>
 
 
 #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;
 
 {
   int i;
 
@@ -1539,7 +1795,7 @@ int mksd_writev (int sock, struct iovec *iov, int iovcnt)
   }
 }
 
   }
 }
 
-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;
 {
   int offset = 0;
   int i;
@@ -1566,7 +1822,7 @@ int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size)
   return offset;
 }
 
   return offset;
 }
 
-int mksd_parse_line (char *line)
+static int mksd_parse_line (char *line)
 {
   char *p;
 
 {
   char *p;
 
@@ -1600,26 +1856,20 @@ int mksd_parse_line (char *line)
   }
 }
 
   }
 }
 
-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;
   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_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)
+  iov[2].iov_len = 1;
+
+  if (mksd_writev (sock, iov, 3) < 0)
     return DEFER;
 
   if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)
     return DEFER;
 
   if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)
index 54575fe6243a24fb1825b3c78872aa9bb810a570..c4fb31ea79bb39c3c0cdee0b897782d9256d376f 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/receive.c,v 1.54 2010/06/03 05:40:27 pdp Exp $ */
+/* $Cambridge: exim/src/src/receive.c,v 1.55 2010/06/05 11:13:30 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -1094,7 +1094,7 @@ return TRUE;
 
 DO_MIME_ACL:
 /* make sure the eml mbox file is spooled up */
 
 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,
 if (mbox_file == NULL) {
   /* error while spooling */
   log_write(0, LOG_MAIN|LOG_PANIC,
index 63c1c2c1085694a12d4d41bf7013fed06caa01d5..d502eca13afdaf0dfc0c3498df479168946d39dd 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/regex.c,v 1.7 2005/07/01 10:49:02 ph10 Exp $ */
+/* $Cambridge: exim/src/src/regex.c,v 1.8 2010/06/05 11:13:30 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -47,7 +47,7 @@ int regex(uschar **listptr) {
 
   if (mime_stream == NULL) {
     /* We are in the DATA ACL */
 
   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,
     if (mbox_file == NULL) {
       /* error while spooling */
       log_write(0, LOG_MAIN|LOG_PANIC,
index beec82363a16f7852b2d3a1db5614d0365ef63ab..f2ca92712cf0372141eff8dab21413a678310dec 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/spam.c,v 1.17 2008/07/18 17:55:42 fanf2 Exp $ */
+/* $Cambridge: exim/src/src/spam.c,v 1.18 2010/06/05 11:13:30 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -80,7 +80,7 @@ int spam(uschar **listptr) {
   };
 
   /* make sure the eml mbox file is spooled up */
   };
 
   /* 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 */
 
   if (mbox_file == NULL) {
     /* error while spooling */
index 42a2fe17176666eb3c96323992e8d6a3d3e04a25..3364b57517ba56a1e969e00bb38ee11bcf6ac226 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/spool_mbox.c,v 1.14 2008/01/16 09:56:55 tom Exp $ */
+/* $Cambridge: exim/src/src/spool_mbox.c,v 1.15 2010/06/05 11:13:30 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -25,9 +25,10 @@ extern int spam_ok;
 int spool_mbox_ok = 0;
 uschar spooled_message_id[17];
 
 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;
   uschar message_subdir[2];
   uschar buffer[16384];
   uschar *temp_string;
@@ -100,13 +101,17 @@ FILE *spool_mbox(unsigned long *mbox_file_size) {
     (void)fwrite("\n", 1, 1, mbox_file);
 
     /* copy body file */
     (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) {
     };
 
     if (data_file == NULL) {
@@ -125,7 +130,8 @@ FILE *spool_mbox(unsigned long *mbox_file_size) {
      * explicitly, because the one in the file is parted of the locked area.
      */
 
      * 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);
 
     do {
       j = fread(buffer, 1, sizeof(buffer), data_file);
@@ -188,6 +194,12 @@ void unspool_mbox(void) {
     mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
 
     tempdir = opendir(CS mbox_path);
     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;
     /* loop thru dir & delete entries */
     while((entry = readdir(tempdir)) != NULL) {
       uschar *name = US entry->d_name;