[exim-cvs] Multi-recipient cutthrough delivery. Bug 1542

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Multi-recipient cutthrough delivery. Bug 1542
Gitweb: http://git.exim.org/exim.git/commitdiff/5032d1cf500b102849d9a47867fbb7b34d871683
Commit:     5032d1cf500b102849d9a47867fbb7b34d871683
Parent:     7f3555b3d8e1fccfe8f5d450bdde367018a5e764
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Tue Jan 13 11:19:32 2015 +0000
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Tue Jan 20 13:39:22 2015 +0000


    Multi-recipient cutthrough delivery. Bug 1542


    Testing and fixes by: Heiko Schlittermann <hs@???>
---
 doc/doc-docbook/spec.xfpt         |   13 +-
 doc/doc-txt/ChangeLog             |    3 +
 src/src/acl.c                     |   36 ++--
 src/src/globals.c                 |    7 +-
 src/src/globals.h                 |   12 +-
 src/src/receive.c                 |   74 ++++----
 src/src/smtp_in.c                 |    2 +-
 src/src/verify.c                  |  261 ++++++++++++++++++++-------
 test/confs/5400                   |   17 ++-
 test/log/5400                     |   48 +++++-
 test/scripts/5400-cutthrough/5400 |  321 +++++++++++++++++++++++++++++++--
 test/stderr/5400                  |  235 ------------------------
 test/stdout/5400                  |  364 +++++++++++++++++++++++++++++++++++-
 13 files changed, 999 insertions(+), 394 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index d78797f..d5a16ca 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -28003,14 +28003,18 @@ is what is wanted for subsequent tests.
.cindex "cutthrough" "requesting"
This option requests delivery be attempted while the item is being received.

-The option usable in the RCPT ACL.
+The option is usable in the RCPT ACL.
If enabled for a message recieved via smtp and routed to an smtp transport,
-and the message has only one recipient,
+and only one transport, interface, destination host and port combination
+is used for all recipients of the message,
then the delivery connection is made while the receiving connection is open
and data is copied from one to the other.

+An attempt to set this option for any recipient but the first
+for a mail will be quietly ignored.
If a recipient-verify callout connection is subsequently
-requested in the same ACL it is held open and used for the data,
+requested in the same ACL it is held open and used for
+any subsequent receipients and the data,
otherwise one is made after the initial RCPT ACL completes.

Note that routers are used in verify mode,
@@ -28022,12 +28026,13 @@ Headers may be modified by routers (subject to the above) and transports.
Cutthrough delivery is not supported via transport-filters or when DKIM signing
of outgoing messages is done, because it sends data to the ultimate destination
before the entire message has been received from the source.
+It is not supported for messages recieved with the SMTP PRDR option in use.

Should the ultimate destination system positively accept or reject the mail,
a corresponding indication is given to the source system and nothing is queued.
If there is a temporary error the item is queued for later delivery in the
usual fashion. If the item is successfully delivered in cutthrough mode
-the log line is tagged with ">>" rather than "=>" and appears
+the delivery log lines are tagged with ">>" rather than "=>" and appear
before the acceptance "<=" line.

 Delivery in this mode avoids the generation of a bounce mail to a
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index eeefef7..57df782 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -44,6 +44,9 @@ JH/10 The smtp_receive_timeout main option is now expanded before use.
 JH/11 The incoming_interface log option now also enables logging of the
       local interface on delivery outgoing connections.


+JH/12 The cutthrough-routing facility now supports multi-recipient mails,
+      if the interface and destination host and port all match.
+



 Exim version 4.85
diff --git a/src/src/acl.c b/src/src/acl.c
index 8fdae03..06c1c49 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3354,19 +3354,27 @@ for (; cb != NULL; cb = cb->next)
       break;


       case CONTROL_CUTTHROUGH_DELIVERY:
-      if (deliver_freeze)
-        *log_msgptr = US"frozen";
-      else if (queue_only_policy)
-        *log_msgptr = US"queue-only";
-      else if (fake_response == FAIL)
-        *log_msgptr = US"fakereject";
+      if (prdr_requested)
+    /* Too hard to think about for now.  We might in future cutthrough
+    the case where both sides handle prdr and this-node prdr acl
+    is "accept" */
+        *log_msgptr = string_sprintf(US"PRDR on %s reception\n", arg);
       else
     {
-    cutthrough_delivery = TRUE;
-    break;
+    if (deliver_freeze)
+      *log_msgptr = US"frozen";
+    else if (queue_only_policy)
+      *log_msgptr = US"queue-only";
+    else if (fake_response == FAIL)
+      *log_msgptr = US"fakereject";
+    else
+      {
+      if (rcpt_count == 1) cutthrough.delivery = TRUE;
+      break;
+      }
+    *log_msgptr = string_sprintf("\"control=%s\" on %s item",
+                      arg, *log_msgptr);
     }
-      *log_msgptr = string_sprintf("\"control=%s\" on %s item",
-                    arg, *log_msgptr);
       return ERROR;
       }
     break;
@@ -4351,9 +4359,9 @@ ratelimiters_cmd = NULL;
 log_reject_target = LOG_MAIN|LOG_REJECT;


 #ifndef DISABLE_PRDR
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR )
+if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR)
 #else
-if (where == ACL_WHERE_RCPT )
+if (where == ACL_WHERE_RCPT)
 #endif
   {
   adb = address_defaults;
@@ -4397,9 +4405,7 @@ case ACL_WHERE_RCPT:
 #ifndef DISABLE_PRDR
 case ACL_WHERE_PRDR:
 #endif
-  if( rcpt_count > 1 )
-    cancel_cutthrough_connection("more than one recipient");
-  else if (rc == OK  &&  cutthrough_delivery  &&  cutthrough_fd < 0)
+  if (rc == OK  &&  cutthrough.delivery  && rcpt_count > cutthrough.nrcpt)
     open_cutthrough_connection(addr);
   break;


diff --git a/src/src/globals.c b/src/src/globals.c
index a8670e4..a066f35 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -506,8 +506,11 @@ int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;


 uschar *csa_status             = NULL;
-BOOL    cutthrough_delivery    = FALSE;
-int     cutthrough_fd          = -1;
+cut_t   cutthrough = {
+  FALSE,                /* delivery: when to attempt */
+  -1,                    /* fd: open connection */
+  0,                    /* nrcpt: number of addresses */
+};


 BOOL    daemon_listen          = FALSE;
 uschar *daemon_smtp_port       = US"smtp";
diff --git a/src/src/globals.h b/src/src/globals.h
index 5495f54..b5ea8a4 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -291,8 +291,16 @@ extern int     continue_sequence;      /* Sequence num for continued delivery */
 extern uschar *continue_transport;     /* Transport for continued delivery */


 extern uschar *csa_status;             /* Client SMTP Authorization result */
-extern BOOL    cutthrough_delivery;    /* Deliver in foreground */
-extern int     cutthrough_fd;          /* Connection for ditto */
+
+typedef struct {
+  BOOL         delivery;               /* When to attempt */
+  int          fd;                     /* Open connection */
+  int          nrcpt;                  /* Count of addresses */
+  uschar *     interface;              /* (address of) */
+  host_item    host;                   /* Host used */
+  address_item addr;                   /* (Chain of) addresses */
+} cut_t;
+extern cut_t cutthrough;               /* Deliver-concurrently */


 extern BOOL    daemon_listen;          /* True if listening required */
 extern uschar *daemon_smtp_port;       /* Can be a list of ports */
diff --git a/src/src/receive.c b/src/src/receive.c
index 8ab3166..977150c 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -997,7 +997,7 @@ switch(where)
   case ACL_WHERE_DKIM:
   case ACL_WHERE_MIME:
   case ACL_WHERE_DATA:
-    if (cutthrough_fd >= 0 && (acl_removed_headers || acl_added_headers))
+    if (cutthrough.fd >= 0 && (acl_removed_headers || acl_added_headers))
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
             " will not take effect on cutthrough deliveries");
@@ -2807,12 +2807,11 @@ if (filter_test != FTEST_NONE)
   }


 /* Cutthrough delivery:
-    We have to create the Received header now rather than at the end of reception,
-    so the timestamp behaviour is a change to the normal case.
-    XXX Ensure this gets documented XXX.
-    Having created it, send the headers to the destination.
-*/
-if (cutthrough_fd >= 0)
+We have to create the Received header now rather than at the end of reception,
+so the timestamp behaviour is a change to the normal case.
+XXX Ensure this gets documented XXX.
+Having created it, send the headers to the destination. */
+if (cutthrough.fd >= 0)
   {
   if (received_count > received_headers_max)
     {
@@ -3184,56 +3183,61 @@ else
           rc = OK;
           while ((item = string_nextinlist(&ptr, &sep,
                                            itembuf,
-                                           sizeof(itembuf))) != NULL)
+                                           sizeof(itembuf))))
             {
             /* Prevent running ACL for an empty item */
             if (!item || (item[0] == '\0')) continue;
-            /* Only run ACL once for each domain or identity, no matter how often it
-               appears in the expanded list. */
-            if (seen_items != NULL)
+
+            /* Only run ACL once for each domain or identity,
+        no matter how often it appears in the expanded list. */
+            if (seen_items)
               {
               uschar *seen_item = NULL;
               uschar seen_item_buf[256];
               uschar *seen_items_list = seen_items;
-              int seen_this_item = 0;
+              BOOL seen_this_item = FALSE;


               while ((seen_item = string_nextinlist(&seen_items_list, &sep,
                                                     seen_item_buf,
-                                                    sizeof(seen_item_buf))) != NULL)
-                {
-                  if (Ustrcmp(seen_item,item) == 0)
-                    {
-                      seen_this_item = 1;
-                      break;
-                    }
-                }
-
-              if (seen_this_item > 0)
+                                                    sizeof(seen_item_buf))))
+        if (Ustrcmp(seen_item,item) == 0)
+          {
+          seen_this_item = TRUE;
+          break;
+          }
+
+              if (seen_this_item)
                 {
                 DEBUG(D_receive)
-                  debug_printf("acl_smtp_dkim: skipping signer %s, already seen\n", item);
+                  debug_printf("acl_smtp_dkim: skipping signer %s, "
+            "already seen\n", item);
                 continue;
                 }


-              seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
+              seen_items = string_append(seen_items, &seen_items_size,
+        &seen_items_offset, 1, ":");
               }


-            seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,item);
+            seen_items = string_append(seen_items, &seen_items_size,
+          &seen_items_offset, 1, item);
             seen_items[seen_items_offset] = '\0';


             DEBUG(D_receive)
-              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n", item);
+              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
+        item);


             dkim_exim_acl_setup(item);
-            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, &user_msg, &log_msg);
+            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
+          &user_msg, &log_msg);


             if (rc != OK)
-              {
-                DEBUG(D_receive)
-                  debug_printf("acl_smtp_dkim: acl_check returned %d on %s, skipping remaining items\n", rc, item);
-            cancel_cutthrough_connection("dkim acl not ok");
-                break;
-              }
+          {
+          DEBUG(D_receive)
+        debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+          "skipping remaining items\n", rc, item);
+          cancel_cutthrough_connection("dkim acl not ok");
+          break;
+          }
             }
           add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
           if (rc == DISCARD)
@@ -3957,7 +3961,7 @@ for this message. */


    XXX We do not handle queue-only, freezing, or blackholes.
 */
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
   uschar * msg= cutthrough_finaldot();    /* Ask the target system to accept the messsage */
                     /* Logging was done in finaldot() */
@@ -4103,7 +4107,7 @@ if (smtp_input)
       case TMP_REJ: message_id[0] = 0;      /* Prevent a delivery from starting */
       default:break;
       }
-    cutthrough_delivery = FALSE;
+    cutthrough.delivery = FALSE;
     }


   /* For batched SMTP, generate an error message on failure, and do
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 4fc2cfd..4e4cdb8 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -4448,7 +4448,7 @@ while (done <= 0)
     ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
     to get the DATA command sent. */


-    if (acl_smtp_predata == NULL && cutthrough_fd < 0) rc = OK; else
+    if (acl_smtp_predata == NULL && cutthrough.fd < 0) rc = OK; else
       {
       uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept";
       enable_dollar_recipients = TRUE;
diff --git a/src/src/verify.c b/src/src/verify.c
index 96740f8..4474f2c 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -14,7 +14,6 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */


 #define CUTTHROUGH_CMD_TIMEOUT  30    /* timeout for cutthrough-routing calls */
 #define CUTTHROUGH_DATA_TIMEOUT 60    /* timeout for cutthrough-routing calls */
-address_item cutthrough_addr;
 static smtp_outblock ctblock;
 uschar ctbuffer[8192];


@@ -39,6 +38,7 @@ static tree_node *dnsbl_cache = NULL;
#define MT_NOT 1
#define MT_ALL 2

+static uschar cutthrough_response(char, uschar **);


/*************************************************
@@ -189,12 +189,12 @@ from_address = US"";

 if (is_recipient)
   {
-  if ((options & vopt_callout_recipsender) != 0)
+  if (options & vopt_callout_recipsender)
     {
     address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
     from_address = sender_address;
     }
-  else if ((options & vopt_callout_recippmaster) != 0)
+  else if (options & vopt_callout_recippmaster)
     {
     address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
       qualify_domain_sender);
@@ -410,6 +410,113 @@ else


if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();

+/* cutthrough-multi: if a nonfirst rcpt has the same routing as the first,
+and we are holding a cutthrough conn open, we can just append the rcpt to
+that conn for verification purposes (and later delivery also).  Simplest
+coding means skipping this whole loop and doing the append separately.
+
+We will need to remember it has been appended so that rcpt-acl tail code
+can do it there for the non-rcpt-verify case.  For this we keep an addresscount.
+*/
+
+  /* Can we re-use an open cutthrough connection? */
+  if (  cutthrough.fd >= 0
+     && (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
+    == vopt_callout_recipsender
+     && !random_local_part
+     && !pm_mailfrom
+     )
+    {
+    if (addr->transport == cutthrough.addr.transport)
+      for (host = host_list; host; host = host->next)
+    if (Ustrcmp(host->address, cutthrough.host.address) == 0)
+      {
+      int host_af;
+      uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
+      int port = 25;
+
+      deliver_host = host->name;
+      deliver_host_address = host->address;
+      deliver_host_port = host->port;
+      deliver_domain = addr->domain;
+      transport_name = addr->transport->name;
+
+      host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+
+      if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
+          US"callout") ||
+          !smtp_get_port(tf->port, addr, &port, US"callout"))
+        log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
+          addr->message);
+
+      if (  (  interface == cutthrough.interface
+            || (  interface
+               && cutthrough.interface
+           && Ustrcmp(interface, cutthrough.interface) == 0
+        )  )
+         && port == cutthrough.host.port
+         )
+        {
+        uschar * resp;
+
+        /* Match!  Send the RCPT TO, append the addr, set done */
+        done =
+          smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+        transport_rcpt_address(addr,
+          (addr->transport == NULL)? FALSE :
+           addr->transport->rcpt_include_affixes)) >= 0 &&
+          cutthrough_response('2', &resp) == '2';
+
+        /* This would go horribly wrong if a callout fail was ignored by ACL.
+        We punt by abandoning cutthrough on a reject, like the
+        first-rcpt does. */
+
+        if (done)
+          {
+          address_item * na = store_get(sizeof(address_item));
+          *na = cutthrough.addr;
+          cutthrough.addr = *addr;
+          cutthrough.addr.host_used = &cutthrough.host;
+          cutthrough.addr.next = na;
+
+          cutthrough.nrcpt++;
+          }
+        else
+          {
+          cancel_cutthrough_connection("recipient rejected");
+          if (errno == ETIMEDOUT)
+        {
+        HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+        }
+          else if (errno == 0)
+        {
+        if (*resp == 0)
+          Ustrcpy(resp, US"connection dropped");
+
+        addr->message =
+          string_sprintf("response to \"%s\" from %s [%s] was: %s",
+            big_buffer, host->name, host->address,
+            string_printing(resp));
+
+        addr->user_message =
+          string_sprintf("Callout verification failed:\n%s", resp);
+
+        /* Hard rejection ends the process */
+
+        if (resp[0] == '5')   /* Address rejected */
+          {
+          yield = FAIL;
+          done = TRUE;
+          }
+        }
+          }
+        }
+      break;
+      }
+    if (!done)
+      cancel_cutthrough_connection("incompatible connection");
+    }
+
   /* Now make connections to the hosts and do real callouts. The list of hosts
   is passed in as an argument. */


@@ -692,9 +799,9 @@ else

     ob->command_timeout = callout;
         rc = tls_client_start(inblock.sock, host, addr, addr->transport
-#ifdef EXPERIMENTAL_DANE
+# ifdef EXPERIMENTAL_DANE
                 , dane ? &tlsa_dnsa : NULL
-#endif
+# endif
                 );
     ob->command_timeout = oldtimeout;


@@ -709,10 +816,10 @@ else
          )
         {
         (void)close(inblock.sock);
-#ifdef EXPERIMENTAL_EVENT
+# ifdef EXPERIMENTAL_EVENT
         (void) event_raise(addr->transport->event_action,
                     US"tcp:close", NULL);
-#endif
+# endif
         log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
           "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
         suppress_tls = TRUE;
@@ -741,9 +848,9 @@ else
     /* If the host is required to use a secure channel, ensure that we have one. */
     if (tls_out.active < 0)
       if (
-#ifdef EXPERIMENTAL_DANE
+# ifdef EXPERIMENTAL_DANE
      dane ||
-#endif
+# endif
          verify_check_given_host(&ob->hosts_require_tls, host) == OK
      )
         {
@@ -757,7 +864,7 @@ else
         goto TLS_FAILED;
         }


-    #endif /*SUPPORT_TLS*/
+#endif /*SUPPORT_TLS*/


     done = TRUE; /* so far so good; have response to HELO */


@@ -765,17 +872,17 @@ else

     /* For now, transport_filter by cutthrough-delivery is not supported */
     /* Need proper integration with the proper transport mechanism. */
-    if (cutthrough_delivery)
+    if (cutthrough.delivery)
       {
       if (addr->transport->filter_command)
         {
-        cutthrough_delivery= FALSE;
+        cutthrough.delivery = FALSE;
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
         }
 #ifndef DISABLE_DKIM
       if (ob->dkim_domain)
         {
-        cutthrough_delivery= FALSE;
+        cutthrough.delivery = FALSE;
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
         }
 #endif
@@ -887,6 +994,8 @@ else


         else if (errno == 0)
           {
+      cancel_cutthrough_connection("random-recipient");
+
           if (randombuffer[0] == '5')
             new_domain_record.random_result = ccache_reject;


@@ -932,8 +1041,9 @@ else

         if (done && pm_mailfrom != NULL)
           {
-          /*XXX not suitable for cutthrough - sequencing problems */
-      cutthrough_delivery= FALSE;
+          /*XXX not suitable for cutthrough - we cannot afford to do an RSET
+      and lose the original mail-from */
+    cancel_cutthrough_connection("postmaster verify");
       HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");


           done =
@@ -1028,30 +1138,34 @@ else


     /* End the SMTP conversation and close the connection. */


-    /* Cutthrough - on a successfull connect and recipient-verify with use-sender
-    and we have no cutthrough conn so far
+    /* Cutthrough - on a successfull connect and recipient-verify with
+    use-sender and we are 1st rcpt and have no cutthrough conn so far
     here is where we want to leave the conn open */
-    if (  cutthrough_delivery
+    if (  cutthrough.delivery
+       && rcpt_count == 1
        && done
        && yield == OK
        && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
        && !random_local_part
        && !pm_mailfrom
-       && cutthrough_fd < 0
+       && cutthrough.fd < 0
        )
       {
-      cutthrough_fd= outblock.sock;    /* We assume no buffer in use in the outblock */
-      cutthrough_addr = *addr;        /* Save the address_item for later logging */
-      cutthrough_addr.next =      NULL;
-      cutthrough_addr.host_used = store_get(sizeof(host_item));
-      *(cutthrough_addr.host_used) = *host;
+      cutthrough.fd = outblock.sock;    /* We assume no buffer in use in the outblock */
+      cutthrough.nrcpt = 1;
+      cutthrough.interface = interface;
+      cutthrough.host = *host;
+      cutthrough.addr = *addr;        /* Save the address_item for later logging */
+      cutthrough.addr.next =      NULL;
+      cutthrough.addr.host_used = &cutthrough.host;
       if (addr->parent)
-        *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+        *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
+      *addr->parent;
       ctblock.buffer = ctbuffer;
       ctblock.buffersize = sizeof(ctbuffer);
       ctblock.ptr = ctbuffer;
       /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-      ctblock.sock = cutthrough_fd;
+      ctblock.sock = cutthrough.fd;
       }
     else
       {
@@ -1176,7 +1290,8 @@ address_item addr2;
 get rewritten. */


 addr2 = *addr;
-HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
+HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n",
+  rcpt_count > 1 ? "more" : "start");
 (void) verify_address(&addr2, NULL,
     vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
     CUTTHROUGH_CMD_TIMEOUT, -1, -1,
@@ -1191,14 +1306,14 @@ return;
 static BOOL
 cutthrough_send(int n)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return TRUE;


 if(
 #ifdef SUPPORT_TLS
-   (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+   (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
 #endif
-   send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+   send(cutthrough.fd, ctblock.buffer, n, 0) > 0
   )
 {
   transport_count += n;
@@ -1230,7 +1345,7 @@ return TRUE;
 BOOL
 cutthrough_puts(uschar * cp, int n)
 {
-if (cutthrough_fd < 0)       return TRUE;
+if (cutthrough.fd < 0)       return TRUE;
 if (_cutthrough_puts(cp, n)) return TRUE;
 cancel_cutthrough_connection("transmit failed");
 return FALSE;
@@ -1238,7 +1353,7 @@ return FALSE;



static BOOL
-_cutthrough_flush_send( void )
+_cutthrough_flush_send(void)
{
int n= ctblock.ptr-ctblock.buffer;

@@ -1251,7 +1366,7 @@ return TRUE;

/* Send out any bufferred output. Return boolean success. */
BOOL
-cutthrough_flush_send( void )
+cutthrough_flush_send(void)
{
if (_cutthrough_flush_send()) return TRUE;
cancel_cutthrough_connection("transmit failed");
@@ -1260,7 +1375,7 @@ return FALSE;


BOOL
-cutthrough_put_nl( void )
+cutthrough_put_nl(void)
{
return cutthrough_puts(US"\r\n", 2);
}
@@ -1278,7 +1393,7 @@ inblock.buffer = inbuffer;
inblock.buffersize = sizeof(inbuffer);
inblock.ptr = inbuffer;
inblock.ptrend = inbuffer;
-inblock.sock = cutthrough_fd;
+inblock.sock = cutthrough.fd;
/* this relies on (inblock.sock == tls_out.active) */
if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
cancel_cutthrough_connection("target timeout on read");
@@ -1286,7 +1401,7 @@ if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect,
if(copy != NULL)
{
uschar * cp;
- *copy= cp= string_copy(responsebuffer);
+ *copy = cp = string_copy(responsebuffer);
/* Trim the trailing end of line */
cp += Ustrlen(responsebuffer);
if(cp > *copy && cp[-1] == '\n') *--cp = '\0';
@@ -1299,9 +1414,9 @@ return responsebuffer[0];

/* Negotiate dataphase with the cutthrough target, returning success boolean */
BOOL
-cutthrough_predata( void )
+cutthrough_predata(void)
{
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
return FALSE;

 HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> DATA\n");
@@ -1332,9 +1447,9 @@ return TRUE;
 /* Expands newlines to wire format (CR,NL).           */
 /* Also sends header-terminating blank line.          */
 BOOL
-cutthrough_headers_send( void )
+cutthrough_headers_send(void)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return FALSE;


/* We share a routine with the mainline transport to handle header add/remove/rewrites,
@@ -1342,10 +1457,12 @@ if(cutthrough_fd < 0)
*/
HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");

-if (!transport_headers_send(&cutthrough_addr, cutthrough_fd,
-    cutthrough_addr.transport->add_headers, cutthrough_addr.transport->remove_headers,
+if (!transport_headers_send(&cutthrough.addr, cutthrough.fd,
+    cutthrough.addr.transport->add_headers,
+    cutthrough.addr.transport->remove_headers,
     &cutthrough_write_chunk, TRUE,
-    cutthrough_addr.transport->rewrite_rules, cutthrough_addr.transport->rewrite_existflags))
+    cutthrough.addr.transport->rewrite_rules,
+    cutthrough.addr.transport->rewrite_existflags))
   return FALSE;


HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
@@ -1354,9 +1471,9 @@ return TRUE;


 static void
-close_cutthrough_connection( const char * why )
+close_cutthrough_connection(const char * why)
 {
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
   /* We could be sending this after a bunch of data, but that is ok as
      the only way to cancel the transfer in dataphase is to drop the tcp
@@ -1371,18 +1488,18 @@ if(cutthrough_fd >= 0)
   #ifdef SUPPORT_TLS
   tls_close(FALSE, TRUE);
   #endif
-  (void)close(cutthrough_fd);
-  cutthrough_fd= -1;
+  (void)close(cutthrough.fd);
+  cutthrough.fd = -1;
   HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctblock.ptr = ctbuffer;
 }


void
-cancel_cutthrough_connection( const char * why )
+cancel_cutthrough_connection(const char * why)
{
close_cutthrough_connection(why);
-cutthrough_delivery= FALSE;
+cutthrough.delivery = FALSE;
}


@@ -1394,33 +1511,45 @@ cutthrough_delivery= FALSE;
    Return smtp response-class digit.
 */
 uschar *
-cutthrough_finaldot( void )
+cutthrough_finaldot(void)
 {
+uschar res;
+address_item * addr;
 HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> .\n");


/* Assume data finshed with new-line */
-if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
- return cutthrough_addr.message;
+if( !cutthrough_puts(US".", 1)
+ || !cutthrough_put_nl()
+ || !cutthrough_flush_send()
+ )
+ return cutthrough.addr.message;

-switch(cutthrough_response('2', &cutthrough_addr.message))
+res = cutthrough_response('2', &cutthrough.addr.message);
+for (addr = &cutthrough.addr; addr; addr = addr->next)
   {
-  case '2':
-    delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
-    close_cutthrough_connection("delivered");
-    break;
+  addr->message = cutthrough.addr.message;
+  switch(res)
+    {
+    case '2':
+      delivery_log(LOG_MAIN, addr, (int)'>', NULL);
+      close_cutthrough_connection("delivered");
+      break;


-  case '4':
-    delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
-    break;
+    case '4':
+      delivery_log(LOG_MAIN, addr, 0,
+    US"tmp-reject from cutthrough after DATA:");
+      break;


-  case '5':
-    delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
-    break;
+    case '5':
+      delivery_log(LOG_MAIN|LOG_REJECT, addr, 0,
+    US"rejected after DATA:");
+      break;


-  default:
-    break;
+    default:
+      break;
+    }
   }
-  return cutthrough_addr.message;
+return cutthrough.addr.message;
 }



diff --git a/test/confs/5400 b/test/confs/5400
index 09a0aaf..5948ce8 100644
--- a/test/confs/5400
+++ b/test/confs/5400
@@ -8,6 +8,8 @@ log_file_path = DIR/spool/log/%slog
gecos_pattern = ""
gecos_name = CALLER_NAME

+log_selector = +received_recipients
+
# ----- Main settings -----

domainlist local_domains = test.ex : *.test.ex
@@ -27,12 +29,18 @@ ar:

begin routers

+dns:
+  driver = dnslookup
+  domains = localhost.test.ex : thishost.test.ex
+  self = send
+  transport = smtp
+
 all:
   driver = manualroute
   domains = ! +local_domains
-  route_list = * 127.0.0.1
+  route_list = special.com HOSTIPV4 ; * 127.0.0.1
   self = send
-  transport = smtp
+  transport = ${if eq {special_tpt}{$local_part} {smtp2}{smtp}}
   headers_remove = X-hdr-rtr
   headers_add =    X-hdr-rtr-new: $h_X-hdr-rtr:+++
   no_more
@@ -48,5 +56,10 @@ smtp:
   port = PORT_S
   headers_add =  ${if def:h_X-hdr-rtr {X-hdr-tpt-new: new} {}}


+smtp2:
+ driver = smtp
+ interface = HOSTIPV4
+ port = PORT_S
+

# End
diff --git a/test/log/5400 b/test/log/5400
index 6b51348..c6f3662 100644
--- a/test/log/5400
+++ b/test/log/5400
@@ -1,18 +1,54 @@
1999-03-02 09:44:33 rcpt for userx@???
1999-03-02 09:44:33 10HmaX-0005vi-00 >> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@???
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
1999-03-02 09:44:33 rcpt for userz@???
1999-03-02 09:44:33 10HmaY-0005vi-00 >> userz@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userz@???
1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
1999-03-02 09:44:33 rcpt for usery@???
1999-03-02 09:44:33 rcpt for userx@???
-1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
-1999-03-02 09:44:33 10HmaZ-0005vi-00 => usery@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-1999-03-02 09:44:33 10HmaZ-0005vi-00 -> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 >> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 >> usery@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for usery@??? userx@???
1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
1999-03-02 09:44:33 rcpt for userx@???
1999-03-02 09:44:33 10HmbA-0005vi-00 >> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@???
1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 rcpt for no@???
+1999-03-02 09:44:33 rcpt for userx@???
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for no@??? userx@???
+1999-03-02 09:44:33 10HmbB-0005vi-00 => no@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbB-0005vi-00 -> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 rcpt for userx@???
+1999-03-02 09:44:33 rcpt for no@???
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@??? no@???
+1999-03-02 09:44:33 10HmbC-0005vi-00 => userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbC-0005vi-00 -> no@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 rcpt for userx@???
+1999-03-02 09:44:33 rcpt for special_tpt@???
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@??? special_tpt@???
+1999-03-02 09:44:33 10HmbD-0005vi-00 => userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbD-0005vi-00 => special_tpt@??? R=all T=smtp2 H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 rcpt for userx@???
+1999-03-02 09:44:33 rcpt for usery@???
+1999-03-02 09:44:33 10HmbE-0005vi-00 >> usery@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbE-0005vi-00 >> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@??? usery@???
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 rcpt for userx@???
+1999-03-02 09:44:33 rcpt for usery@???
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@??? usery@???
+1999-03-02 09:44:33 10HmbF-0005vi-00 => userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbF-0005vi-00 => usery@??? R=all T=smtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK"
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 rcpt for userx@???
+1999-03-02 09:44:33 rcpt for usery@???
+1999-03-02 09:44:33 10HmbG-0005vi-00 >> usery@??? R=dns T=smtp H=localhost.test.ex [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbG-0005vi-00 >> userx@??? R=dns T=smtp H=localhost.test.ex [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss for userx@??? usery@???
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
diff --git a/test/scripts/5400-cutthrough/5400 b/test/scripts/5400-cutthrough/5400
index 3e56b43..79072d7 100644
--- a/test/scripts/5400-cutthrough/5400
+++ b/test/scripts/5400-cutthrough/5400
@@ -16,7 +16,7 @@ DATA
QUIT
250 OK
****
-exim -d-all+acl+transport -bs
+exim -bs
EHLO myhost.test.ex
MAIL FROM:<CALLER@???>
RCPT TO:<userx@???>
@@ -43,7 +43,7 @@ DATA
QUIT
250 OK
****
-exim -d-all+acl+transport -bs
+exim -bs
EHLO myhost.test.ex
MAIL FROM:<CALLER@???>
RCPT TO:<userz@???>
@@ -52,8 +52,8 @@ DATA
.
QUIT
****
-# cutthrough cancelled by multiple recipients
-server PORT_S 2
+# cutthrough for 2 recipients in one domain
+server PORT_S
220 ESMTP
EHLO
250 OK
@@ -61,8 +61,32 @@ MAIL FROM:
250 Sender OK
RCPT TO:
250 Recipient OK
+RCPT TO:
+250 Recipient OK
+DATA
+354 Send data
+.
+250 OK
QUIT
-*eof
+250 OK
+****
+exim -bs
+EHLO myhost.test.ex
+MAIL FROM:<CALLER@???>
+RCPT TO:<usery@???>
+RCPT TO:<userx@???>
+DATA
+
+.
+QUIT
+****
+#
+#
+#
+#
+#
+# cutthrough_delivery basic operation, again
+server PORT_S
220 ESMTP
EHLO
250 OK
@@ -70,8 +94,53 @@ MAIL FROM:
250 Sender OK
RCPT TO:
250 Recipient OK
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+****
+exim -bs
+EHLO myhost.test.ex
+MAIL FROM:<CALLER@???>
+RCPT TO:<userx@???>
+DATA
+X-hdr-rtr: qqq
+X-hdr-tpt: zzz
+
+body
+.
+QUIT
+****
+#
+#
+#
+#
+# cutthrough for 2 recipients in one domain
+# first one denied
+# so we get a 2nd conn with the traditional delivery
+# (for both because it's not a real verify=recipient)
+server PORT_S 2
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
RCPT TO:
-250 Recipient OK
+550 Not that one
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-1
+RCPT TO:
+250 ok rcpt-2
DATA
354 Send data
.
@@ -79,23 +148,138 @@ DATA
QUIT
250 OK
****
-exim -d-all+acl+transport -bs
+exim -bs
EHLO myhost.test.ex
MAIL FROM:<CALLER@???>
-RCPT TO:<usery@???>
+RCPT TO:<no@???>
RCPT TO:<userx@???>
DATA

.
QUIT
****
-sleep 1
#
#
#
#
#
-# cutthrough_delivery basic operation, again
+# cutthrough for 2 recipients in one domain
+# second one denied
+# so we get a 2nd conn with the traditional delivery
+# (for both because it's not a real verify=recipient)
+server PORT_S 2
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 first recipient ok
+RCPT TO:
+550 Not that one
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-1
+RCPT TO:
+250 ok rcpt-2
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+****
+exim -bs
+EHLO myhost.test.ex
+MAIL FROM:<CALLER@???>
+RCPT TO:<userx@???>
+RCPT TO:<no@???>
+DATA
+
+.
+QUIT
+****
+#
+#
+#
+#
+#
+# cutthrough for 2 recipients in one domain
+# second one uses a different transport
+# so we get a 2nd conn with 2nd rcpt, doing the fake verify
+# then 3rd & 4th conns with the traditional deliveries on the different transports
+server PORT_S 4
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 first recipient ok
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 second recipient ok
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-1
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-2
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+****
+exim -bs
+EHLO myhost.test.ex
+MAIL FROM:<CALLER@???>
+RCPT TO:<userx@???>
+RCPT TO:<special_tpt@???>
+DATA
+
+.
+QUIT
+****
+#
+#
+#
+#
+#
+# cutthrough for 2 recipients in different domains, handled by the same tpt & host
server PORT_S
220 ESMTP
EHLO
@@ -103,7 +287,9 @@ EHLO
MAIL FROM:
250 Sender OK
RCPT TO:
-250 Recipient OK
+250 ok rcpt-1
+RCPT TO:
+250 ok rcpt-2
DATA
354 Send data
.
@@ -111,15 +297,120 @@ DATA
QUIT
250 OK
****
-exim -d-all+acl+transport -bs
+exim -bs
+EHLO myhost.test.ex
+MAIL FROM:<CALLER@???>
+RCPT TO:<userx@???>
+RCPT TO:<usery@???>
+DATA
+
+.
+QUIT
+****
+#
+#
+#
+#
+#
+# cutthrough for 2 recipients in different domains, handled by the same tpt but different hosts
+# so we get a 2nd conn with 2nd rcpt, doing the fake verify
+# then 3rd & 4th conns with the traditional deliveries on the different transports
+server PORT_S 4
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 first recipient ok
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 second recipient ok
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-1
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+*eof
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-2
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+****
+exim -bs
EHLO myhost.test.ex
MAIL FROM:<CALLER@???>
RCPT TO:<userx@???>
+RCPT TO:<usery@???>
+DATA
+
+.
+QUIT
+****
+#
+#
+#
+#
+#
+# cutthrough for 2 recipients in different domains, handled by the same tpt & host
+# but via a dnslookup router (all previous were manualroute)
+server PORT_S
+220 ESMTP
+EHLO
+250 OK
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 ok rcpt-1
+RCPT TO:
+250 ok rcpt-2
+DATA
+354 Send data
+.
+250 OK
+QUIT
+250 OK
+****
+exim -bs
+EHLO myhost.test.ex
+MAIL FROM:<CALLER@???>
+RCPT TO:<userx@???>
+RCPT TO:<usery@???>
DATA
-X-hdr-rtr: qqq
-X-hdr-tpt: zzz

-body
 .
 QUIT
 ****
+#
+#
+#
+#
+#
+sleep 1
diff --git a/test/stderr/5400 b/test/stderr/5400
deleted file mode 100644
index 9fa77d5..0000000
--- a/test/stderr/5400
+++ /dev/null
@@ -1,235 +0,0 @@
-Exim version x.yz ....
-configuration file is TESTSUITE/test-config
-admin user
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER
-using ACL "ar"
-processing "accept"
-check control = cutthrough_delivery
-check logwrite = rcpt for $local_part@$domain
-               = rcpt for userx@???
-LOG: MAIN
-  rcpt for userx@???
-created log directory TESTSUITE/spool/log
-accept: condition test succeeded in ACL "ar"
-end of ACL "ar": ACCEPT
------------ start cutthrough setup ------------
-Connecting to 127.0.0.1 [127.0.0.1]:1224 from ip4.ip4.ip4.ip4 ... connected
-  SMTP<< 220 ESMTP
-  SMTP>> EHLO myhost.test.ex
-  SMTP<< 250 OK
-  SMTP>> MAIL FROM:<CALLER@???>
-  SMTP<< 250 Sender OK
-  SMTP>> RCPT TO:<userx@???>
-  SMTP<< 250 Recipient OK
------------ end cutthrough setup ------------
-processing "accept"
-accept: condition test succeeded in inline ACL
-end of inline ACL: ACCEPT
-  SMTP>> DATA
-  SMTP<< 354 Send data
------------ start cutthrough headers send -----------
-added header line(s):
-X-hdr-rtr-new: +++
----
------------ done cutthrough headers send ------------
-  SMTP>> .
-  SMTP<< 250 OK
-LOG: MAIN
-  >> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-  SMTP>> QUIT
------------ cutthrough shutdown (delivered) ------------
-LOG: MAIN
-  <= CALLER@??? U=CALLER P=local-esmtp S=sss
-LOG: MAIN
-  Completed
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER closed by QUIT
->>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>>
-Exim version x.yz ....
-configuration file is TESTSUITE/test-config
-admin user
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER
-using ACL "ar"
-processing "accept"
-check control = cutthrough_delivery
-check logwrite = rcpt for $local_part@$domain
-               = rcpt for userz@???
-LOG: MAIN
-  rcpt for userz@???
-accept: condition test succeeded in ACL "ar"
-end of ACL "ar": ACCEPT
------------ start cutthrough setup ------------
-Connecting to 127.0.0.1 [127.0.0.1]:1224 from ip4.ip4.ip4.ip4 ... connected
-  SMTP<< 220 SMTP only spoken here
-  SMTP>> EHLO myhost.test.ex
-  SMTP<< 550 Not here, mate
-  SMTP>> HELO myhost.test.ex
-  SMTP<< 250 OK
-  SMTP>> MAIL FROM:<CALLER@???>
-  SMTP<< 250 Sender OK
-  SMTP>> RCPT TO:<userz@???>
-  SMTP<< 250 Recipient OK
------------ end cutthrough setup ------------
-processing "accept"
-accept: condition test succeeded in inline ACL
-end of inline ACL: ACCEPT
-  SMTP>> DATA
-  SMTP<< 354 Send data
------------ start cutthrough headers send -----------
-added header line(s):
-X-hdr-rtr-new: +++
----
------------ done cutthrough headers send ------------
-  SMTP>> .
-  SMTP<< 250 OK
-LOG: MAIN
-  >> userz@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-  SMTP>> QUIT
------------ cutthrough shutdown (delivered) ------------
-LOG: MAIN
-  <= CALLER@??? U=CALLER P=local-esmtp S=sss
-LOG: MAIN
-  Completed
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER closed by QUIT
->>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>>
-Exim version x.yz ....
-configuration file is TESTSUITE/test-config
-admin user
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER
-using ACL "ar"
-processing "accept"
-check control = cutthrough_delivery
-check logwrite = rcpt for $local_part@$domain
-               = rcpt for usery@???
-LOG: MAIN
-  rcpt for usery@???
-accept: condition test succeeded in ACL "ar"
-end of ACL "ar": ACCEPT
------------ start cutthrough setup ------------
-Connecting to 127.0.0.1 [127.0.0.1]:1224 from ip4.ip4.ip4.ip4 ... connected
-  SMTP<< 220 ESMTP
-  SMTP>> EHLO myhost.test.ex
-  SMTP<< 250 OK
-  SMTP>> MAIL FROM:<CALLER@???>
-  SMTP<< 250 Sender OK
-  SMTP>> RCPT TO:<usery@???>
-  SMTP<< 250 Recipient OK
------------ end cutthrough setup ------------
-using ACL "ar"
-processing "accept"
-check control = cutthrough_delivery
-check logwrite = rcpt for $local_part@$domain
-               = rcpt for userx@???
-LOG: MAIN
-  rcpt for userx@???
-accept: condition test succeeded in ACL "ar"
-end of ACL "ar": ACCEPT
-  SMTP>> QUIT
------------ cutthrough shutdown (more than one recipient) ------------
-LOG: MAIN
-  <= CALLER@??? U=CALLER P=local-esmtp S=sss
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER closed by QUIT
->>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>>
-Exim version x.yz ....
-configuration file is TESTSUITE/test-config
-trusted user
-admin user
->>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>
---------> usery@??? <--------
-smtp transport entered
-  usery@???
-  userx@???
-checking status of 127.0.0.1
-127.0.0.1 [127.0.0.1]:1111 status = usable
-delivering 10HmaZ-0005vi-00 to 127.0.0.1 [127.0.0.1] (usery@???, ...)
-Connecting to 127.0.0.1 [127.0.0.1]:1224 from ip4.ip4.ip4.ip4 ... connected
-  SMTP<< 220 ESMTP
-  SMTP>> EHLO myhost.test.ex
-  SMTP<< 250 OK
-not using PIPELINING
-use_dsn=0
-  SMTP>> MAIL FROM:<CALLER@???>
-  SMTP<< 250 Sender OK
-  SMTP>> RCPT TO:<usery@???>
-  SMTP<< 250 Recipient OK
-  SMTP>> RCPT TO:<userx@???>
-  SMTP<< 250 Recipient OK
-  SMTP>> DATA
-  SMTP<< 354 Send data
-  SMTP>> writing message and terminating "."
-added header line(s):
-X-hdr-rtr-new: +++
----
-writing data block fd=dddd size=sss timeout=300
-  SMTP<< 250 OK
-ok=1 send_quit=1 send_rset=0 continue_more=0 yield=0 first_address is NULL
-transport_check_waiting entered
-  sequence=1 local_max=500 global_max=-1
-no messages waiting for 127.0.0.1
-  SMTP>> QUIT
-Leaving smtp transport
-LOG: MAIN
-  => usery@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-LOG: MAIN
-  -> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-LOG: MAIN
-  Completed
->>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>>
-Exim version x.yz ....
-configuration file is TESTSUITE/test-config
-admin user
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER
-using ACL "ar"
-processing "accept"
-check control = cutthrough_delivery
-check logwrite = rcpt for $local_part@$domain
-               = rcpt for userx@???
-LOG: MAIN
-  rcpt for userx@???
-accept: condition test succeeded in ACL "ar"
-end of ACL "ar": ACCEPT
------------ start cutthrough setup ------------
-Connecting to 127.0.0.1 [127.0.0.1]:1224 from ip4.ip4.ip4.ip4 ... connected
-  SMTP<< 220 ESMTP
-  SMTP>> EHLO myhost.test.ex
-  SMTP<< 250 OK
-  SMTP>> MAIL FROM:<CALLER@???>
-  SMTP<< 250 Sender OK
-  SMTP>> RCPT TO:<userx@???>
-  SMTP<< 250 Recipient OK
------------ end cutthrough setup ------------
-processing "accept"
-accept: condition test succeeded in inline ACL
-end of inline ACL: ACCEPT
-  SMTP>> DATA
-  SMTP<< 354 Send data
------------ start cutthrough headers send -----------
-removed header line:
-X-hdr-rtr: qqq
----
-added header line(s):
-X-hdr-rtr-new: +++
----
-added header line:
-X-hdr-tpt-new: new
----
------------ done cutthrough headers send ------------
-  SMTP>> .
-  SMTP<< 250 OK
-LOG: MAIN
-  >> userx@??? R=all T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
-  SMTP>> QUIT
------------ cutthrough shutdown (delivered) ------------
-LOG: MAIN
-  <= CALLER@??? U=CALLER P=local-esmtp S=sss
-LOG: MAIN
-  Completed
-LOG: smtp_connection MAIN
-  SMTP connection from CALLER closed by QUIT
->>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>>
diff --git a/test/stdout/5400 b/test/stdout/5400
index 4895072..3452a9c 100644
--- a/test/stdout/5400
+++ b/test/stdout/5400
@@ -43,6 +43,78 @@
 354 Enter message, ending with "." on a line by itself
 250 OK id=10HmbA-0005vi-00
 221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at myhost.test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbB-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at myhost.test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbC-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at myhost.test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbD-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at myhost.test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbE-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at myhost.test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbF-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at myhost.test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbG-0005vi-00
+221 myhost.test.ex closing connection


 ******** SERVER ********
 Listening on port 1224 ... 
@@ -108,17 +180,6 @@ MAIL FROM:<CALLER@???>
 250 Sender OK
 RCPT TO:<usery@???>
 250 Recipient OK
-QUIT
-Expected EOF read from client
-Listening on port 1224 ... 
-Connection request from [ip4.ip4.ip4.ip4]
-220 ESMTP
-EHLO myhost.test.ex
-250 OK
-MAIL FROM:<CALLER@???>
-250 Sender OK
-RCPT TO:<usery@???>
-250 Recipient OK
 RCPT TO:<userx@???>
 250 Recipient OK
 DATA
@@ -166,3 +227,284 @@ body
 QUIT
 250 OK
 End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<no@???>
+550 Not that one
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<no@???>
+250 ok rcpt-1
+RCPT TO:<userx@???>
+250 ok rcpt-2
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbB-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbB-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 first recipient ok
+RCPT TO:<no@???>
+550 Not that one
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 ok rcpt-1
+RCPT TO:<no@???>
+250 ok rcpt-2
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbC-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbC-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 first recipient ok
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<special_tpt@???>
+250 second recipient ok
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 ok rcpt-1
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbD-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbD-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<special_tpt@???>
+250 ok rcpt-2
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbD-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbD-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 ok rcpt-1
+RCPT TO:<usery@???>
+250 ok rcpt-2
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbE-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbE-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 first recipient ok
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<usery@???>
+250 second recipient ok
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 ok rcpt-1
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbF-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbF-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<usery@???>
+250 ok rcpt-2
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbF-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbF-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-hdr-rtr-new: +++
+
+.
+250 OK
+QUIT
+250 OK
+End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+220 ESMTP
+EHLO myhost.test.ex
+250 OK
+MAIL FROM:<CALLER@???>
+250 Sender OK
+RCPT TO:<userx@???>
+250 ok rcpt-1
+RCPT TO:<usery@???>
+250 ok rcpt-2
+DATA
+354 Send data
+Received: from CALLER (helo=myhost.test.ex)
+    by myhost.test.ex with local-esmtp (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbG-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbG-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 OK
+QUIT
+250 OK
+End of script