Merge branch 'CHUNKING'
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 6 Aug 2016 13:04:45 +0000 (14:04 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 6 Aug 2016 13:04:45 +0000 (14:04 +0100)
1  2 
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/deliver.c
src/src/dkim.c
src/src/functions.h
src/src/receive.c
src/src/transport.c
src/src/transports/smtp.c
test/stderr/5410
test/stderr/5420

index 5ff976e72d46811fadb2fb1e4cf5af775b413533,6302f5185581332356963b4e1a07dcc414fd9dbb..d18dd0ce1f2d5e88e1784336915556cd8fb6c3b4
@@@ -10090,21 -10090,6 +10090,21 @@@ Last:user@example.co
  user@example.com
  .endd
  
 +.new
 +.vitem &*${base32:*&<&'digits'&>&*}*&
 +.cindex "&%base32%& expansion item"
 +.cindex "expansion" "conversion to base 32"
 +The string must consist entirely of decimal digits. The number is converted to
 +base 32 and output as a (empty, for zero) string of characters.
 +Only lowercase letters are used.
 +
 +.vitem &*${base32d:*&<&'base-32&~digits'&>&*}*&
 +.cindex "&%base32d%& expansion item"
 +.cindex "expansion" "conversion to base 32"
 +The string must consist entirely of base-32 digits.
 +The number is converted to decimal and output as a string.
 +.wen
 +
  .vitem &*${base62:*&<&'digits'&>&*}*&
  .cindex "&%base62%& expansion item"
  .cindex "expansion" "conversion to base 62"
@@@ -13816,6 -13801,7 +13816,7 @@@ See also the &'Policy controls'& sectio
  .table2
  .row &%accept_8bitmime%&             "advertise 8BITMIME"
  .row &%auth_advertise_hosts%&        "advertise AUTH to these hosts"
+ .row &%chunking_advertise_hosts%&    "advertise CHUNKING to these hosts"
  .row &%dsn_advertise_hosts%&         "advertise DSN extensions to these hosts"
  .row &%ignore_fromline_hosts%&       "allow &""From ""& from these hosts"
  .row &%ignore_fromline_local%&       "allow &""From ""& from local SMTP"
@@@ -14373,6 -14359,15 +14374,15 @@@ For non-SMTP input and for batched SMT
  failure a message is written to stderr and Exim exits with a non-zero code, as
  it obviously cannot send an error message of any kind.
  
+ .new
+ .option chunking_advertise_hosts main "host list&!!" *
+ .cindex CHUNKING advertisement
+ .cindex "RFC 3030" "CHUNKING"
+ The CHUNKING extension (RFC3030) will be advertised in the EHLO message to
+ these hosts.
+ Hosts may use the BDAT command as an alternate to DATA.
+ .wen
  .option daemon_smtp_ports main string &`smtp`&
  .cindex "port" "for daemon"
  .cindex "TCP/IP" "setting listening ports"
@@@ -19888,17 -19883,12 +19898,17 @@@ list1:   :include:/opt/lists/list
  .endd
  .next
  .cindex "address redirection" "to black hole"
 -Sometimes you want to throw away mail to a particular local part. Making the
 -&%data%& option expand to an empty string does not work, because that causes
 -the router to decline. Instead, the alias item
 +.cindex "delivery" "discard"
 +.cindex "delivery" "blackhole"
  .cindex "black hole"
  .cindex "abandoning mail"
 -&':blackhole:'& can be used. It does what its name implies. No delivery is
 +Sometimes you want to throw away mail to a particular local part.  Making the
 +&%data%& option expand to an empty string does not work, because that causes
 +the router to decline. Instead, the alias item
 +.code
 +:blackhole:
 +.endd
 +can be used. It does what its name implies. No delivery is
  done, and no error message is generated. This has the same effect as specifying
  &_/dev/null_& as a destination, but it can be independently disabled.
  
@@@ -23891,6 -23881,16 +23901,16 @@@ connects. If authentication fails, Exi
  unauthenticated. See also &%hosts_require_auth%&, and chapter
  &<<CHAPSMTPAUTH>>& for details of authentication.
  
+ .new
+ .option hosts_try_chunking smtp "host list&!!" *
+ .cindex CHUNKING "enabling, in client"
+ .cindex BDAT "SMTP command"
+ .cindex "RFC 3030" "CHUNKING"
+ This option provides a list of server to which, provided they announce
+ CHUNKING support, Exim will attempt to use BDAT commands rather than DATA.
+ BDAT will not be used in conjuction with a transport filter.
+ .wen
  .option hosts_try_prdr smtp "host list&!!" *
  .cindex "PRDR" "enabling, optional in client"
  This option provides a list of servers to which, provided they announce
@@@ -27761,6 -27761,17 +27781,17 @@@ received, before the final response to 
  the ACL specified by &%acl_smtp_data%&, which is the second ACL that is
  associated with the DATA command.
  
+ .new
+ .cindex CHUNKING "BDAT command"
+ .cindex BDAT "SMTP command"
+ .cindex "RFC 3030" CHUNKING
+ If CHUNKING was advertised and a BDAT command sequence is received,
+ the &%acl_smtp_predata%& ACL is not run.
+ . XXX why not?  It should be possible, for the first BDAT.
+ The &%acl_smtp_data%& is run after the last BDAT command and all of
+ the data specified is received.
+ .wen
  For both of these ACLs, it is not possible to reject individual recipients. An
  error response rejects the entire message. Unfortunately, it is known that some
  MTAs do not treat hard (5&'xx'&) responses to the DATA command (either
@@@ -31544,7 -31555,7 +31575,7 @@@ condition defers
  
  Unix and TCP socket specifications may be mixed in any order.
  Each element of the list is a list itself, space-separated by default
 -and changeable in the usual way.
 +and changeable in the usual way; take care to not double the separator.
  
  For TCP socket specifications a host name or IP (v4 or v6, but
  subject to list-separator quoting rules) address can be used,
@@@ -31671,11 -31682,6 +31702,11 @@@ spam bar is 50 characters
  A multiline text table, containing the full SpamAssassin report for the
  message. Useful for inclusion in headers or reject messages.
  This variable is only usable in a DATA-time ACL.
 +.new
 +Beware that SpamAssassin may return non-ASCII characters, especially
 +when running in country-specific locales, which are not legal
 +unencoded in headers.
 +.wen
  
  .vitem &$spam_action$&
  For SpamAssassin either 'reject' or 'no action' depending on the
@@@ -35413,7 -35419,6 +35444,7 @@@ picked out by the distinctive two-chara
  timestamp. The flags are:
  .display
  &`<=`&     message arrival
 +&`(=`&     message fakereject
  &`=>`&     normal message delivery
  &`->`&     additional address in same delivery
  &`>>`&     cutthrough message delivery
@@@ -35646,10 -35651,12 +35677,12 @@@ the following table
  &`F   `&        sender address (on delivery lines)
  &`H   `&        host name and IP address
  &`I   `&        local interface used
+ &`K   `&        CHUNKING extension used
  &`id  `&        message id for incoming message
  &`P   `&        on &`<=`& lines: protocol used
  &`    `&        on &`=>`& and &`**`& lines: return path
- &`PRX `&        on &'<='& and&`=>`& lines: proxy address
+ &`PRDR`&        PRDR extension used
+ &`PRX `&        on &'<='& and &`=>`& lines: proxy address
  &`Q   `&        alternate queue name
  &`QT  `&        on &`=>`& lines: time spent on queue so far
  &`    `&        on &"Completed"& lines: time spent on queue
@@@ -38084,7 -38091,7 +38117,7 @@@ senders)
  .section "Signing outgoing messages" "SECDKIMSIGN"
  .cindex "DKIM" "signing"
  
 -Signing is implemented by setting private options on the SMTP transport.
 +Signing is enabled by setting private options on the SMTP transport.
  These options take (expandable) strings as arguments.
  
  .option dkim_domain smtp string&!! unset
diff --combined doc/doc-txt/NewStuff
index 587dc65084331b8370a9e7a75e97493a2b33f8b3,9e2be8372108b895f13570b67a598dd0fb5d0bea..719bedf3179f9e937de9e57eabfa86e77b847d98
@@@ -26,8 -26,11 +26,13 @@@ Version 4.8
      the queue to be used for a message.  A $queue_name variable gives
      visibility.
  
 - 6. The CHUNKING ESMTP extension from RFC 3030.  May give some slight
 + 6. New expansion operators base32/base32d.
 +
++ 7. The CHUNKING ESMTP extension from RFC 3030.  May give some slight
+     performance increase and network load decrease.  Main config option
+     chunking_advertise_hosts, and smtp transport option hosts_try_chunking
+     for control.
  
  Version 4.87
  ------------
diff --combined src/src/deliver.c
index 956a33250e8fd58d8c3b2083ee1f315a71c79e66,a8d740cde16b08c16ff788b9b607236943b1453f..eae675753c5c7152b08a6cb1b1588b1729621b02
@@@ -715,7 -715,7 +715,7 @@@ host_item * h = addr->host_used
  s = string_append(s, sp, pp, 2, US" H=", h->name);
  
  if (LOGGING(dnssec) && h->dnssec == DS_YES)
-   s = string_cat(s, sp, pp, US" DS");
+   s = string_catn(s, sp, pp, US" DS", 3);
  
  s = string_append(s, sp, pp, 3, US" [", h->address, US"]");
  
@@@ -836,184 -836,6 +836,184 @@@ router_name = transport_name = NULL
  
  
  
 +/******************************************************************************/
 +
 +
 +/*************************************************
 +*        Generate local prt for logging          *
 +*************************************************/
 +
 +/* This function is a subroutine for use in string_log_address() below.
 +
 +Arguments:
 +  addr        the address being logged
 +  yield       the current dynamic buffer pointer
 +  sizeptr     points to current size
 +  ptrptr      points to current insert pointer
 +
 +Returns:      the new value of the buffer pointer
 +*/
 +
 +static uschar *
 +string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
 +  int *ptrptr)
 +{
 +uschar * s;
 +
 +s = addr->prefix;
 +if (testflag(addr, af_include_affixes) && s)
 +  {
 +#ifdef SUPPORT_I18N
 +  if (testflag(addr, af_utf8_downcvt))
 +    s = string_localpart_utf8_to_alabel(s, NULL);
 +#endif
 +  yield = string_cat(yield, sizeptr, ptrptr, s);
 +  }
 +
 +s = addr->local_part;
 +#ifdef SUPPORT_I18N
 +if (testflag(addr, af_utf8_downcvt))
 +  s = string_localpart_utf8_to_alabel(s, NULL);
 +#endif
 +yield = string_cat(yield, sizeptr, ptrptr, s);
 +
 +s = addr->suffix;
 +if (testflag(addr, af_include_affixes) && s)
 +  {
 +#ifdef SUPPORT_I18N
 +  if (testflag(addr, af_utf8_downcvt))
 +    s = string_localpart_utf8_to_alabel(s, NULL);
 +#endif
 +  yield = string_cat(yield, sizeptr, ptrptr, s);
 +  }
 +
 +return yield;
 +}
 +
 +
 +/*************************************************
 +*          Generate log address list             *
 +*************************************************/
 +
 +/* This function generates a list consisting of an address and its parents, for
 +use in logging lines. For saved onetime aliased addresses, the onetime parent
 +field is used. If the address was delivered by a transport with rcpt_include_
 +affixes set, the af_include_affixes bit will be set in the address. In that
 +case, we include the affixes here too.
 +
 +Arguments:
 +  str           points to start of growing string, or NULL
 +  size          points to current allocation for string
 +  ptr           points to offset for append point; updated on exit
 +  addr          bottom (ultimate) address
 +  all_parents   if TRUE, include all parents
 +  success       TRUE for successful delivery
 +
 +Returns:        a growable string in dynamic store
 +*/
 +
 +static uschar *
 +string_log_address(uschar * str, int * size, int * ptr,
 +  address_item *addr, BOOL all_parents, BOOL success)
 +{
 +BOOL add_topaddr = TRUE;
 +address_item *topaddr;
 +
 +/* Find the ultimate parent */
 +
 +for (topaddr = addr; topaddr->parent; topaddr = topaddr->parent) ;
 +
 +/* We start with just the local part for pipe, file, and reply deliveries, and
 +for successful local deliveries from routers that have the log_as_local flag
 +set. File deliveries from filters can be specified as non-absolute paths in
 +cases where the transport is goin to complete the path. If there is an error
 +before this happens (expansion failure) the local part will not be updated, and
 +so won't necessarily look like a path. Add extra text for this case. */
 +
 +if (  testflag(addr, af_pfr)
 +   || (  success
 +      && addr->router && addr->router->log_as_local
 +      && addr->transport && addr->transport->info->local
 +   )  )
 +  {
 +  if (testflag(addr, af_file) && addr->local_part[0] != '/')
 +    str = string_catn(str, size, ptr, CUS"save ", 5);
 +  str = string_get_localpart(addr, str, size, ptr);
 +  }
 +
 +/* Other deliveries start with the full address. It we have split it into local
 +part and domain, use those fields. Some early failures can happen before the
 +splitting is done; in those cases use the original field. */
 +
 +else
 +  {
 +  uschar * cmp = str + *ptr;
 +
 +  if (addr->local_part)
 +    {
 +    const uschar * s;
 +    str = string_get_localpart(addr, str, size, ptr);
 +    str = string_catn(str, size, ptr, US"@", 1);
 +    s = addr->domain;
 +#ifdef SUPPORT_I18N
 +    if (testflag(addr, af_utf8_downcvt))
 +      s = string_localpart_utf8_to_alabel(s, NULL);
 +#endif
 +    str = string_cat(str, size, ptr, s);
 +    }
 +  else
 +    str = string_cat(str, size, ptr, addr->address);
 +
 +  /* If the address we are going to print is the same as the top address,
 +  and all parents are not being included, don't add on the top address. First
 +  of all, do a caseless comparison; if this succeeds, do a caseful comparison
 +  on the local parts. */
 +
 +  str[*ptr] = 0;
 +  if (  strcmpic(cmp, topaddr->address) == 0
 +     && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
 +     && !addr->onetime_parent
 +     && (!all_parents || !addr->parent || addr->parent == topaddr)
 +     )
 +    add_topaddr = FALSE;
 +  }
 +
 +/* If all parents are requested, or this is a local pipe/file/reply, and
 +there is at least one intermediate parent, show it in brackets, and continue
 +with all of them if all are wanted. */
 +
 +if (  (all_parents || testflag(addr, af_pfr))
 +   && addr->parent
 +   && addr->parent != topaddr)
 +  {
 +  uschar *s = US" (";
 +  address_item *addr2;
 +  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
 +    {
 +    str = string_catn(str, size, ptr, s, 2);
 +    str = string_cat (str, size, ptr, addr2->address);
 +    if (!all_parents) break;
 +    s = US", ";
 +    }
 +  str = string_catn(str, size, ptr, US")", 1);
 +  }
 +
 +/* Add the top address if it is required */
 +
 +if (add_topaddr)
 +  str = string_append(str, size, ptr, 3,
 +    US" <",
 +    addr->onetime_parent ? addr->onetime_parent : topaddr->address,
 +    US">");
 +
 +return str;
 +}
 +
 +
 +/******************************************************************************/
 +
 +
 +
  /* If msg is NULL this is a delivery log and logchar is used. Otherwise
  this is a nonstandard call; no two-character delivery flag is written
  but sender-host and sender are prefixed and "msg" is inserted in the log line.
@@@ -1024,10 -846,11 +1024,10 @@@ Arguments
  void
  delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
  {
 -uschar *log_address;
  int size = 256;         /* Used for a temporary, */
  int ptr = 0;            /* expanding buffer, for */
 -uschar *s;              /* building log lines;   */
 -void *reset_point;      /* released afterwards.  */
 +uschar * s;             /* building log lines;   */
 +void * reset_point;     /* released afterwards.  */
  
  /* Log the delivery on the main log. We use an extensible string to build up
  the log line, and reset the store afterwards. Remote deliveries should always
@@@ -1041,14 -864,14 +1041,14 @@@ pointer to a single host item in their 
  
  s = reset_point = store_get(size);
  
 -log_address = string_log_address(addr, LOGGING(all_parents), TRUE);
  if (msg)
 -  s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
 +  s = string_append(s, &size, &ptr, 2, host_and_ident(TRUE), US" ");
  else
    {
    s[ptr++] = logchar;
 -  s = string_append(s, &size, &ptr, 2, US"> ", log_address);
 +  s = string_catn(s, &size, &ptr, US"> ", 2);
    }
 +s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), TRUE);
  
  if (LOGGING(sender_on_delivery) || msg)
    s = string_append(s, &size, &ptr, 3, US" F=<",
@@@ -1139,8 -962,11 +1139,11 @@@ els
  
  #ifndef DISABLE_PRDR
    if (addr->flags & af_prdr_used)
-     s = string_append(s, &size, &ptr, 1, US" PRDR");
+     s = string_catn(s, &size, &ptr, US" PRDR", 5);
  #endif
+   if (addr->flags & af_chunking_used)
+     s = string_catn(s, &size, &ptr, US" K", 2);
    }
  
  /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
@@@ -1191,163 -1017,6 +1194,163 @@@ return
  
  
  
 +static void
 +deferral_log(address_item * addr, uschar * now,
 +  int logflags, uschar * driver_name, uschar * driver_kind)
 +{
 +int size = 256;         /* Used for a temporary, */
 +int ptr = 0;            /* expanding buffer, for */
 +uschar * s;             /* building log lines;   */
 +void * reset_point;     /* released afterwards.  */
 +
 +uschar ss[32];
 +
 +/* Build up the line that is used for both the message log and the main
 +log. */
 +
 +s = reset_point = store_get(size);
 +
 +/* Create the address string for logging. Must not do this earlier, because
 +an OK result may be changed to FAIL when a pipe returns text. */
 +
 +s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
 +
 +if (*queue_name)
 +  s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
 +
 +/* Either driver_name contains something and driver_kind contains
 +" router" or " transport" (note the leading space), or driver_name is
 +a null string and driver_kind contains "routing" without the leading
 +space, if all routing has been deferred. When a domain has been held,
 +so nothing has been done at all, both variables contain null strings. */
 +
 +if (driver_name)
 +  {
 +  if (driver_kind[1] == 't' && addr->router)
 +    s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
 +  Ustrcpy(ss, " ?=");
 +  ss[1] = toupper(driver_kind[1]);
 +  s = string_append(s, &size, &ptr, 2, ss, driver_name);
 +  }
 +else if (driver_kind)
 +  s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
 +
 +/*XXX need an s+s+p sprintf */
 +sprintf(CS ss, " defer (%d)", addr->basic_errno);
 +s = string_cat(s, &size, &ptr, ss);
 +
 +if (addr->basic_errno > 0)
 +  s = string_append(s, &size, &ptr, 2, US": ",
 +    US strerror(addr->basic_errno));
 +
 +if (addr->host_used)
 +  {
 +  s = string_append(s, &size, &ptr, 5,
 +                  US" H=", addr->host_used->name,
 +                  US" [",  addr->host_used->address, US"]");
 +  if (LOGGING(outgoing_port))
 +    {
 +    int port = addr->host_used->port;
 +    s = string_append(s, &size, &ptr, 2,
 +        US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
 +    }
 +  }
 +
 +if (addr->message)
 +  s = string_append(s, &size, &ptr, 2, US": ", addr->message);
 +
 +s[ptr] = 0;
 +
 +/* Log the deferment in the message log, but don't clutter it
 +up with retry-time defers after the first delivery attempt. */
 +
 +if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
 +  deliver_msglog("%s %s\n", now, s);
 +
 +/* Write the main log and reset the store.
 +For errors of the type "retry time not reached" (also remotes skipped
 +on queue run), logging is controlled by L_retry_defer. Note that this kind
 +of error number is negative, and all the retry ones are less than any
 +others. */
 +
 +
 +log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
 +  "== %s", s);
 +
 +store_reset(reset_point);
 +return;
 +}
 +
 +
 +
 +static void
 +failure_log(address_item * addr, uschar * driver_kind, uschar * now)
 +{
 +int size = 256;         /* Used for a temporary, */
 +int ptr = 0;            /* expanding buffer, for */
 +uschar * s;             /* building log lines;   */
 +void * reset_point;     /* released afterwards.  */
 +
 +/* Build up the log line for the message and main logs */
 +
 +s = reset_point = store_get(size);
 +
 +/* Create the address string for logging. Must not do this earlier, because
 +an OK result may be changed to FAIL when a pipe returns text. */
 +
 +s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
 +
 +if (LOGGING(sender_on_delivery))
 +  s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
 +
 +if (*queue_name)
 +  s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
 +
 +/* Return path may not be set if no delivery actually happened */
 +
 +if (used_return_path && LOGGING(return_path_on_delivery))
 +  s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
 +
 +if (addr->router)
 +  s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
 +if (addr->transport)
 +  s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
 +
 +if (addr->host_used)
 +  s = d_hostlog(s, &size, &ptr, addr);
 +
 +#ifdef SUPPORT_TLS
 +s = d_tlslog(s, &size, &ptr, addr);
 +#endif
 +
 +if (addr->basic_errno > 0)
 +  s = string_append(s, &size, &ptr, 2, US": ", US strerror(addr->basic_errno));
 +
 +if (addr->message)
 +  s = string_append(s, &size, &ptr, 2, US": ", addr->message);
 +
 +s[ptr] = 0;
 +
 +/* Do the logging. For the message log, "routing failed" for those cases,
 +just to make it clearer. */
 +
 +if (driver_kind)
 +  deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
 +else
 +  deliver_msglog("%s %s\n", now, s);
 +
 +log_write(0, LOG_MAIN, "** %s", s);
 +
 +#ifndef DISABLE_EVENT
 +msg_event_raise(US"msg:fail:delivery", addr);
 +#endif
 +
 +store_reset(reset_point);
 +return;
 +}
 +
 +
 +
  /*************************************************
  *    Actions at the end of handling an address   *
  *************************************************/
@@@ -1373,6 -1042,12 +1376,6 @@@ post_process_one(address_item *addr, in
  uschar *now = tod_stamp(tod_log);
  uschar *driver_kind = NULL;
  uschar *driver_name = NULL;
 -uschar *log_address;
 -
 -int size = 256;         /* Used for a temporary, */
 -int ptr = 0;            /* expanding buffer, for */
 -uschar *s;              /* building log lines;   */
 -void *reset_point;      /* released afterwards.  */
  
  DEBUG(D_deliver) debug_printf("post-process %s (%d)\n", addr->address, result);
  
@@@ -1578,7 -1253,85 +1581,7 @@@ else if (result == DEFER || result == P
    log or the main log for SMTP defers. */
  
    if (!queue_2stage || addr->basic_errno != 0)
 -    {
 -    uschar ss[32];
 -
 -    /* For errors of the type "retry time not reached" (also remotes skipped
 -    on queue run), logging is controlled by L_retry_defer. Note that this kind
 -    of error number is negative, and all the retry ones are less than any
 -    others. */
 -
 -    unsigned int use_log_selector = addr->basic_errno <= ERRNO_RETRY_BASE
 -      ? L_retry_defer : 0;
 -
 -    /* Build up the line that is used for both the message log and the main
 -    log. */
 -
 -    s = reset_point = store_get(size);
 -
 -    /* Create the address string for logging. Must not do this earlier, because
 -    an OK result may be changed to FAIL when a pipe returns text. */
 -
 -    log_address = string_log_address(addr, LOGGING(all_parents), result == OK);
 -
 -    s = string_cat(s, &size, &ptr, log_address);
 -
 -    if (*queue_name)
 -      s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
 -
 -    /* Either driver_name contains something and driver_kind contains
 -    " router" or " transport" (note the leading space), or driver_name is
 -    a null string and driver_kind contains "routing" without the leading
 -    space, if all routing has been deferred. When a domain has been held,
 -    so nothing has been done at all, both variables contain null strings. */
 -
 -    if (driver_name)
 -      {
 -      if (driver_kind[1] == 't' && addr->router)
 -        s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
 -      Ustrcpy(ss, " ?=");
 -      ss[1] = toupper(driver_kind[1]);
 -      s = string_append(s, &size, &ptr, 2, ss, driver_name);
 -      }
 -    else if (driver_kind)
 -      s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
 -
 -    sprintf(CS ss, " defer (%d)", addr->basic_errno);
 -    s = string_cat(s, &size, &ptr, ss);
 -
 -    if (addr->basic_errno > 0)
 -      s = string_append(s, &size, &ptr, 2, US": ",
 -        US strerror(addr->basic_errno));
 -
 -    if (addr->host_used)
 -      {
 -      s = string_append(s, &size, &ptr, 5,
 -                      US" H=", addr->host_used->name,
 -                      US" [",  addr->host_used->address, US"]");
 -      if (LOGGING(outgoing_port))
 -      {
 -      int port = addr->host_used->port;
 -      s = string_append(s, &size, &ptr, 2,
 -            US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
 -      }
 -      }
 -
 -    if (addr->message)
 -      s = string_append(s, &size, &ptr, 2, US": ", addr->message);
 -
 -    s[ptr] = 0;
 -
 -    /* Log the deferment in the message log, but don't clutter it
 -    up with retry-time defers after the first delivery attempt. */
 -
 -    if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
 -      deliver_msglog("%s %s\n", now, s);
 -
 -    /* Write the main log and reset the store */
 -
 -    log_write(use_log_selector, logflags, "== %s", s);
 -    store_reset(reset_point);
 -    }
 +    deferral_log(addr, now, logflags, driver_name, driver_kind);
    }
  
  
@@@ -1633,7 -1386,64 +1636,7 @@@ els
      addr_failed = addr;
      }
  
 -  /* Build up the log line for the message and main logs */
 -
 -  s = reset_point = store_get(size);
 -
 -  /* Create the address string for logging. Must not do this earlier, because
 -  an OK result may be changed to FAIL when a pipe returns text. */
 -
 -  log_address = string_log_address(addr, LOGGING(all_parents), result == OK);
 -
 -  s = string_cat(s, &size, &ptr, log_address);
 -
 -  if (LOGGING(sender_on_delivery))
 -    s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
 -
 -  if (*queue_name)
 -    s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
 -
 -  /* Return path may not be set if no delivery actually happened */
 -
 -  if (used_return_path && LOGGING(return_path_on_delivery))
 -    s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
 -
 -  if (addr->router)
 -    s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
 -  if (addr->transport)
 -    s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
 -
 -  if (addr->host_used)
 -    s = d_hostlog(s, &size, &ptr, addr);
 -
 -#ifdef SUPPORT_TLS
 -  s = d_tlslog(s, &size, &ptr, addr);
 -#endif
 -
 -  if (addr->basic_errno > 0)
 -    s = string_append(s, &size, &ptr, 2, US": ",
 -      US strerror(addr->basic_errno));
 -
 -  if (addr->message)
 -    s = string_append(s, &size, &ptr, 2, US": ", addr->message);
 -
 -  s[ptr] = 0;
 -
 -  /* Do the logging. For the message log, "routing failed" for those cases,
 -  just to make it clearer. */
 -
 -  if (driver_name)
 -    deliver_msglog("%s %s\n", now, s);
 -  else
 -    deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
 -
 -  log_write(0, LOG_MAIN, "** %s", s);
 -
 -#ifndef DISABLE_EVENT
 -  msg_event_raise(US"msg:fail:delivery", addr);
 -#endif
 -
 -  store_reset(reset_point);
 +  failure_log(addr, driver_name ? NULL : driver_kind, now);
    }
  
  /* Ensure logging is turned on again in all cases */
@@@ -3482,6 -3292,10 +3485,10 @@@ while (!done
      break;
  #endif
  
+     case 'K':
+     addr->flags |= af_chunking_used;
+     break;
      case 'D':
      if (!addr) goto ADDR_MISMATCH;
      memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
@@@ -4720,6 -4534,9 +4727,9 @@@ for (delivery_count = 0; addr_remote; d
        rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
  #endif
  
+       if (addr->flags & af_chunking_used)
+       rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
        memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
        rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
        DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware);
@@@ -7100,7 -6917,7 +7110,7 @@@ if (addr_senddsn
      FILE *f = fdopen(fd, "wb");
      /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
      uschar * bound;
-     transport_ctx tctx;
+     transport_ctx tctx = {0};
  
      DEBUG(D_deliver)
        debug_printf("sending error message to: %s\n", sender_address);
  
      /* Write the original email out */
  
-     bzero(&tctx, sizeof(tctx));
      tctx.options = topt_add_return_path | topt_no_body;
      transport_write_message(fileno(f), &tctx, 0);
      fflush(f);
@@@ -7638,16 -7454,14 +7647,14 @@@ wording. *
        transport_filter_argv = NULL;   /* Just in case */
        return_path = sender_address;   /* In case not previously set */
        {                             /* Dummy transport for headers add */
-       transport_ctx * tctx =
-         store_get(sizeof(*tctx) + sizeof(transport_instance));
-       transport_instance * tb = (transport_instance *)(tctx+1);
+       transport_ctx tctx = {0};
+       transport_instance tb = {0};
  
-       bzero(tctx, sizeof(*tctx)+sizeof(*tb));
-       tctx->tblock = tb;
-       tctx->options = topt;
-       tb->add_headers = dsnnotifyhdr;
+       tctx.tblock = &tb;
+       tctx.options = topt;
+       tb.add_headers = dsnnotifyhdr;
  
-       transport_write_message(fileno(f), tctx, 0);
+       transport_write_message(fileno(f), &tctx, 0);
        }
        fflush(f);
  
@@@ -7964,9 -7778,7 +7971,7 @@@ else if (addr_defer != (address_item *)
          FILE *wmf = NULL;
          FILE *f = fdopen(fd, "wb");
        uschar * bound;
-       transport_ctx tctx;
-       bzero(&tctx, sizeof(tctx));
+       transport_ctx tctx = {0};
  
          if (warn_message_file)
            if (!(wmf = Ufopen(warn_message_file, "rb")))
@@@ -8282,6 -8094,9 +8287,9 @@@ if (!regex_STARTTLS) regex_STARTTLS 
    regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
  #endif
  
+ if (!regex_CHUNKING) regex_CHUNKING =
+   regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE);
  #ifndef DISABLE_PRDR
  if (!regex_PRDR) regex_PRDR =
    regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
diff --combined src/src/dkim.c
index e027a23b3213d227d3c12cf4659e0844eb14e31e,a09ec7ecae62d60c9a35dc4eb1d5427aa619d9a0..f09d438d5e304dd115b6d7490db86a9f135380e3
@@@ -28,7 -28,7 +28,7 @@@ dns_record *rr
  
  lookup_dnssec_authenticated = NULL;
  if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
 -  return PDKIM_FAIL;
 +  return PDKIM_FAIL;  /*XXX better error detail?  logging? */
  
  /* Search for TXT record */
  
@@@ -51,12 -51,12 +51,12 @@@ for (rr = dns_next_rr(&dnsa, &dnss, RES
        rr_offset += len;
        answer_offset += len;
        if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN)
 -      return PDKIM_FAIL;
 +      return PDKIM_FAIL;      /*XXX better error detail?  logging? */
        }
      return PDKIM_OK;
      }
  
 -return PDKIM_FAIL;
 +return PDKIM_FAIL;    /*XXX better error detail?  logging? */
  }
  
  
@@@ -88,6 -88,9 +88,9 @@@ if (dkim_verify_ctx
  dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt);
  dkim_collect_input = !!dkim_verify_ctx;
  
+ /* Start feed up with any cached data */
+ receive_get_cache();
  store_pool = dkim_verify_oldpool;
  }
  
  void
  dkim_exim_verify_feed(uschar * data, int len)
  {
 +int rc;
 +
  store_pool = POOL_PERM;
  if (  dkim_collect_input
 -   && pdkim_feed(dkim_verify_ctx, (char *)data, len) != PDKIM_OK)
 +   && (rc = pdkim_feed(dkim_verify_ctx, (char *)data, len)) != PDKIM_OK)
 +  {
 +  log_write(0, LOG_MAIN,
 +           "DKIM: validation error: %.100s", pdkim_errstr(rc));
    dkim_collect_input = FALSE;
 +  }
  store_pool = dkim_verify_oldpool;
  }
  
@@@ -116,7 -113,6 +119,7 @@@ pdkim_signature *sig = NULL
  int dkim_signers_size = 0;
  int dkim_signers_ptr = 0;
  dkim_signers = NULL;
 +int rc;
  
  store_pool = POOL_PERM;
  
@@@ -141,12 -137,8 +144,12 @@@ dkim_collect_input = FALSE
  
  /* Finish DKIM operation and fetch link to signatures chain */
  
 -if (pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures) != PDKIM_OK)
 +if ((rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures)) != PDKIM_OK)
 +  {
 +  log_write(0, LOG_MAIN,
 +           "DKIM: validation error: %.100s", pdkim_errstr(rc));
    goto out;
 +  }
  
  for (sig = dkim_signatures; sig; sig = sig->next)
    {
@@@ -489,7 -481,8 +492,7 @@@ if (!(dkim_domain = expand_cstring(dkim
    /* expansion error, do not send message. */
    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
             "dkim_domain: %s", expand_string_message);
 -  rc = NULL;
 -  goto CLEANUP;
 +  goto bad;
    }
  
  /* Set $dkim_domain expansion variable to each unique domain in list. */
@@@ -526,7 -519,8 +529,7 @@@ while ((dkim_signing_domain = string_ne
      {
      log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
               "dkim_selector: %s", expand_string_message);
 -    rc = NULL;
 -    goto CLEANUP;
 +    goto bad;
      }
  
    /* Get canonicalization to use */
      /* expansion error, do not send message. */
      log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
               "dkim_canon: %s", expand_string_message);
 -    rc = NULL;
 -    goto CLEANUP;
 +    goto bad;
      }
  
    if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
        {
        log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
                 "dkim_sign_headers: %s", expand_string_message);
 -      rc = NULL;
 -      goto CLEANUP;
 +      goto bad;
        }
                        /* else pass NULL, which means default header list */
  
      {
      log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
               "dkim_private_key: %s", expand_string_message);
 -    rc = NULL;
 -    goto CLEANUP;
 +    goto bad;
      }
  
    if (  Ustrlen(dkim_private_key_expanded) == 0
        log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
                 "private key file for reading: %s",
                 dkim_private_key_expanded);
 -      rc = NULL;
 -      goto CLEANUP;
 +      goto bad;
        }
  
      if (read(privkey_fd, big_buffer, big_buffer_size - 2) < 0)
        {
        log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
                 dkim_private_key_expanded);
 -      rc = NULL;
 -      goto CLEANUP;
 +      goto bad;
        }
  
      (void) close(privkey_fd);
      dkim_private_key_expanded = big_buffer;
      }
  
 -  ctx = pdkim_init_sign( (char *) dkim_signing_domain,
 -                       (char *) dkim_signing_selector,
 -                       (char *) dkim_private_key_expanded,
 +  ctx = pdkim_init_sign( CS dkim_signing_domain,
 +                       CS dkim_signing_selector,
 +                       CS dkim_private_key_expanded,
                         PDKIM_ALGO_RSA_SHA256);
    pdkim_set_optional(ctx,
                      (char *) dkim_sign_headers_expanded,
    lseek(dkim_fd, 0, SEEK_SET);
  
    while ((sread = read(dkim_fd, &buf, 4096)) > 0)
 -    if (pdkim_feed(ctx, buf, sread) != PDKIM_OK)
 -      {
 -      rc = NULL;
 -      goto CLEANUP;
 -      }
 +    if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
 +      goto pk_bad;
  
    /* Handle failed read above. */
    if (sread == -1)
      {
      debug_printf("DKIM: Error reading -K file.\n");
      save_errno = errno;
 -    rc = NULL;
 -    goto CLEANUP;
 +    goto bad;
      }
  
    if ((pdkim_rc = pdkim_feed_finish(ctx, &signature)) != PDKIM_OK)
 -    {
 -    log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
 -    rc = NULL;
 -    goto CLEANUP;
 -    }
 +    goto pk_bad;
  
    sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
                          US signature->signature_header, US"\r\n");
@@@ -647,18 -654,11 +650,18 @@@ els
    rc = US"";
  
  CLEANUP:
 -if (ctx)
 -  pdkim_free_ctx(ctx);
 -store_pool = old_pool;
 -errno = save_errno;
 -return rc;
 +  if (ctx)
 +    pdkim_free_ctx(ctx);
 +  store_pool = old_pool;
 +  errno = save_errno;
 +  return rc;
 +
 +pk_bad:
 +  log_write(0, LOG_MAIN|LOG_PANIC,
 +              "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
 +bad:
 +  rc = NULL;
 +  goto CLEANUP;
  }
  
  #endif
diff --combined src/src/functions.h
index 2c141636ed410a74ef10f156e6d1b645f9c481c3,8d47a0da185ef677dd9795be097be8e600d57d30..8745a399cb2311a9f901f6a256993bfbeb04e75c
@@@ -56,6 -56,7 +56,7 @@@ extern int     tls_feof(void)
  extern int     tls_ferror(void);
  extern void    tls_free_cert(void **);
  extern int     tls_getc(void);
+ extern void    tls_get_cache(void);
  extern int     tls_import_cert(const uschar *, void **);
  extern int     tls_read(BOOL, uschar *, size_t);
  extern int     tls_server_start(const uschar *);
@@@ -100,6 -101,7 +101,7 @@@ extern int     auth_xtextdecode(uschar 
  
  extern uschar *b64encode(uschar *, int);
  extern int     b64decode(uschar *, uschar **);
+ extern int     bdat_getc(void);
  extern void    bits_clear(unsigned int *, size_t, int *);
  extern void    bits_set(unsigned int *, size_t, int *);
  
@@@ -389,6 -391,7 +391,7 @@@ extern BOOL    smtp_get_interface(uscha
                   uschar **, uschar *);
  extern BOOL    smtp_get_port(uschar *, address_item *, int *, uschar *);
  extern int     smtp_getc(void);
+ extern void    smtp_get_cache(void);
  extern int     smtp_handle_acl_fail(int, int, uschar *, uschar *);
  extern void    smtp_log_no_mail(void);
  extern void    smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
@@@ -435,6 -438,7 +438,6 @@@ extern int     string_is_ip_address(con
  #ifdef SUPPORT_I18N
  extern BOOL    string_is_utf8(const uschar *);
  #endif
 -extern uschar *string_log_address(address_item *, BOOL, BOOL);
  extern uschar *string_nextinlist(const uschar **, int *, uschar *, int);
  extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3);
  extern const uschar *string_printing2(const uschar *, BOOL);
@@@ -466,9 -470,8 +469,8 @@@ extern BOOL    transport_set_up_command
  extern void    transport_update_waiting(host_item *, uschar *);
  extern BOOL    transport_write_block(int, uschar *, int);
  extern BOOL    transport_write_string(int, const char *, ...);
- extern BOOL    transport_headers_send(address_item *, int, transport_instance *,
-                  BOOL (*)(int, uschar *, int, BOOL),
-                BOOL);
+ extern BOOL    transport_headers_send(int, transport_ctx *,
+                  BOOL (*)(int, transport_ctx *, uschar *, int));
  extern BOOL    transport_write_message(int, transport_ctx *, int);
  extern void    tree_add_duplicate(uschar *, address_item *);
  extern void    tree_add_nonrecipient(uschar *);
diff --combined src/src/receive.c
index 32154792e97ffb9cabb2c394d2ed457423ad7425,3b048252d30e0754b58a0f99e625b54397670d5b..6a8ce884152a90924766e1ad2cb566f7760376f8
@@@ -777,7 -777,7 +777,7 @@@ read_message_data_smtp(FILE *fout
  {
  int ch_state = 0;
  int ch;
register int linelength = 0;
+ int linelength = 0;
  
  while ((ch = (receive_getc)()) != EOF)
    {
  
    message_size++;
    linelength++;
-   if (fout != NULL)
+   if (fout)
      {
      if (fputc(ch, fout) == EOF) return END_WERROR;
      if (message_size > thismessage_size_limit) return END_SIZE;
      (void) cutthrough_put_nl();
    else
      {
-     uschar c= ch;
+     uschar c = ch;
      (void) cutthrough_puts(&c, 1);
      }
    }
@@@ -890,6 -890,63 +890,63 @@@ return END_EOF
  
  
  
+ /* Variant of the above read_message_data_smtp() specialised for RFC 3030
+ CHUNKING.  We assume that the incoming has proper CRLF, so only have to scan
+ for and strip CR.  On the downside there are more protocol reasons to stop.
+ Arguments:
+   fout      a FILE to which to write the message; NULL if skipping
+ Returns:    One of the END_xxx values indicating why it stopped reading
+ */
+ static int
+ read_message_bdat_smtp(FILE *fout)
+ {
+ int ch;
+ int linelength = 0;
+ for (;;) switch (ch = bdat_getc())
+   {
+   case EOF: return END_EOF;
+   case EOD: return END_DOT;
+   case ERR: return END_PROTOCOL;
+   case '\r':
+     body_linecount++;
+     if (linelength > max_received_linelength)
+       max_received_linelength = linelength;
+     linelength = -1;
+     break;
+   case 0:
+     body_zerocount++;
+     /*FALLTHROUGH*/
+   default:
+     message_size++;
+     linelength++;
+     if (fout)
+       {
+       if (fputc(ch, fout) == EOF) return END_WERROR;
+       if (message_size > thismessage_size_limit) return END_SIZE;
+       }
+ #ifdef notyet
+     if(ch == '\n')
+       (void) cutthrough_put_nl();
+     else
+       {
+       uschar c = ch;
+       (void) cutthrough_puts(&c, 1);
+       }
+ #endif
+     break;
+   }
+ /*NOTREACHED*/
+ }
  /*************************************************
  *             Swallow SMTP message               *
  *************************************************/
@@@ -906,6 -963,7 +963,7 @@@ Returns:     nothin
  void
  receive_swallow_smtp(void)
  {
+ /*XXX CHUNKING: not enough.  read chunks until RSET? */
  if (message_ended >= END_NOTENDED)
    message_ended = read_message_data_smtp(NULL);
  }
@@@ -2832,6 -2890,14 +2890,14 @@@ if (filter_test != FTEST_NONE
    return message_ended == END_DOT;
    }
  
+ /*XXX CHUNKING: need to cancel cutthrough under BDAT, for now.  In future,
+ think more if it could be handled.  Cannot do onward CHUNKING unless
+ inbound is, but inbound chunking ought to be ok with outbound plain.
+ Could we do onward CHUNKING given inbound CHUNKING?
+ */
+ if (chunking_state > CHUNKING_OFFERED)
+   cancel_cutthrough_connection("chunking active");
  /* 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.
@@@ -2929,7 -2995,9 +2995,9 @@@ if (!ferror(data_file) && !(receive_feo
    {
    if (smtp_input)
      {
-     message_ended = read_message_data_smtp(data_file);
+     message_ended = chunking_state > CHUNKING_OFFERED
+       ? read_message_bdat_smtp(data_file)
+       : read_message_data_smtp(data_file);
      receive_linecount++;                /* The terminating "." line */
      }
    else message_ended = read_message_data(data_file);
    receive_linecount += body_linecount;  /* For BSMTP errors mainly */
    message_linecount += body_linecount;
  
-   /* Handle premature termination of SMTP */
-   if (smtp_input && message_ended == END_EOF)
+   switch (message_ended)
      {
-     Uunlink(spool_name);                     /* Lose data file when closed */
-     cancel_cutthrough_connection("sender closed connection");
-     message_id[0] = 0;                       /* Indicate no message accepted */
-     smtp_reply = handle_lost_connection(US"");
-     smtp_yield = FALSE;
-     goto TIDYUP;                             /* Skip to end of function */
-     }
+     /* Handle premature termination of SMTP */
  
-   /* Handle message that is too big. Don't use host_or_ident() in the log
-   message; we want to see the ident value even for non-remote messages. */
+     case END_EOF:
+       if (smtp_input)
+       {
+       Uunlink(spool_name);                 /* Lose data file when closed */
+       cancel_cutthrough_connection("sender closed connection");
+       message_id[0] = 0;                   /* Indicate no message accepted */
+       smtp_reply = handle_lost_connection(US"");
+       smtp_yield = FALSE;
+       goto TIDYUP;                         /* Skip to end of function */
+       }
+       break;
  
-   if (message_ended == END_SIZE)
-     {
-     Uunlink(spool_name);                /* Lose the data file when closed */
-     cancel_cutthrough_connection("mail too big");
-     if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
+     /* Handle message that is too big. Don't use host_or_ident() in the log
+     message; we want to see the ident value even for non-remote messages. */
  
-     log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
-       "message too big: read=%d max=%d",
-       sender_address,
-       (sender_fullhost == NULL)? "" : " H=",
-       (sender_fullhost == NULL)? US"" : sender_fullhost,
-       (sender_ident == NULL)? "" : " U=",
-       (sender_ident == NULL)? US"" : sender_ident,
-       message_size,
-       thismessage_size_limit);
+     case END_SIZE:
+       Uunlink(spool_name);                /* Lose the data file when closed */
+       cancel_cutthrough_connection("mail too big");
+       if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
  
-     if (smtp_input)
-       {
-       smtp_reply = US"552 Message size exceeds maximum permitted";
-       message_id[0] = 0;               /* Indicate no message accepted */
-       goto TIDYUP;                     /* Skip to end of function */
-       }
-     else
-       {
-       fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-       give_local_error(ERRMESS_TOOBIG,
-         string_sprintf("message too big (max=%d)", thismessage_size_limit),
-         US"message rejected: ", error_rc, data_file, header_list);
-       /* Does not return */
-       }
+       log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+       "message too big: read=%d max=%d",
+       sender_address,
+       (sender_fullhost == NULL)? "" : " H=",
+       (sender_fullhost == NULL)? US"" : sender_fullhost,
+       (sender_ident == NULL)? "" : " U=",
+       (sender_ident == NULL)? US"" : sender_ident,
+       message_size,
+       thismessage_size_limit);
+       if (smtp_input)
+       {
+       smtp_reply = US"552 Message size exceeds maximum permitted";
+       message_id[0] = 0;               /* Indicate no message accepted */
+       goto TIDYUP;                     /* Skip to end of function */
+       }
+       else
+       {
+       fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+       give_local_error(ERRMESS_TOOBIG,
+         string_sprintf("message too big (max=%d)", thismessage_size_limit),
+         US"message rejected: ", error_rc, data_file, header_list);
+       /* Does not return */
+       }
+       break;
+     /* Handle bad BDAT protocol sequence */
+     case END_PROTOCOL:
+       Uunlink(spool_name);            /* Lose the data file when closed */
+       cancel_cutthrough_connection("sender protocol error");
+       smtp_reply = US"";              /* Response already sent */
+       message_id[0] = 0;              /* Indicate no message accepted */
+       goto TIDYUP;                    /* Skip to end of function */
      }
    }
  
@@@ -3164,9 -3245,8 +3245,8 @@@ user_msg = NULL
  enable_dollar_recipients = TRUE;
  
  if (recipients_count == 0)
-   {
-   blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL";
-   }
+   blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
  else
    {
    /* Handle interactive SMTP messages */
        dkim_exim_verify_finish();
  
        /* Check if we must run the DKIM ACL */
-       if ((acl_smtp_dkim != NULL) &&
-           (dkim_verify_signers != NULL) &&
-           (dkim_verify_signers[0] != '\0'))
+       if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
          {
          uschar *dkim_verify_signers_expanded =
            expand_string(dkim_verify_signers);
-         if (dkim_verify_signers_expanded == NULL)
-           {
+         if (!dkim_verify_signers_expanded)
            log_write(0, LOG_MAIN|LOG_PANIC,
              "expansion of dkim_verify_signers option failed: %s",
              expand_string_message);
-           }
          else
            {
            int sep = 0;
            uschar *seen_items = NULL;
            int     seen_items_size = 0;
            int     seen_items_offset = 0;
-           uschar itembuf[256];
            /* Default to OK when no items are present */
            rc = OK;
-           while ((item = string_nextinlist(&ptr, &sep,
-                                            itembuf,
-                                            sizeof(itembuf))))
+           while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
              {
              /* Prevent running ACL for an empty item */
-             if (!item || (item[0] == '\0')) continue;
+             if (!item || !*item) continue;
  
              /* 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];
                const uschar *seen_items_list = seen_items;
                BOOL seen_this_item = FALSE;
  
                while ((seen_item = string_nextinlist(&seen_items_list, &sep,
-                                                     seen_item_buf,
-                                                     sizeof(seen_item_buf))))
+                                                     NULL, 0)))
                if (Ustrcmp(seen_item,item) == 0)
                  {
                  seen_this_item = TRUE;
@@@ -3748,10 -3820,9 +3820,10 @@@ size = 256
  sptr = 0;
  s = store_get(size);
  
 -s = string_append(s, &size, &sptr, 2, US"<= ",
 -  (sender_address[0] == 0)? US"<>" : sender_address);
 -if (message_reference != NULL)
 +s = string_append(s, &size, &sptr, 2,
 +  fake_response == FAIL ? US"(= " : US"<= ",
 +  sender_address[0] == 0 ? US"<>" : sender_address);
 +if (message_reference)
    s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
  
  s = add_host_info_for_log(s, &size, &sptr);
@@@ -3761,7 -3832,7 +3833,7 @@@ if (LOGGING(tls_cipher) && tls_in.ciphe
    s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
  if (LOGGING(tls_certificate_verified) && tls_in.cipher)
    s = string_append(s, &size, &sptr, 2, US" CV=",
 -    tls_in.certificate_verified? "yes":"no");
 +    tls_in.certificate_verified ? "yes":"no");
  if (LOGGING(tls_peerdn) && tls_in.peerdn)
    s = string_append(s, &size, &sptr, 3, US" DN=\"",
      string_printing(tls_in.peerdn), US"\"");
@@@ -3773,17 -3844,17 +3845,17 @@@ if (LOGGING(tls_sni) && tls_in.sni
  if (sender_host_authenticated)
    {
    s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
 -  if (authenticated_id != NULL)
 +  if (authenticated_id)
      {
      s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
 -    if (LOGGING(smtp_mailauth) && authenticated_sender != NULL)
 +    if (LOGGING(smtp_mailauth) && authenticated_sender)
        s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
      }
    }
  
  #ifndef DISABLE_PRDR
  if (prdr_requested)
-   s = string_append(s, &size, &sptr, 1, US" PRDR");
+   s = string_catn(s, &size, &sptr, US" PRDR", 5);
  #endif
  
  #ifdef SUPPORT_PROXY
@@@ -3791,6 -3862,9 +3863,9 @@@ if (proxy_session && LOGGING(proxy)
    s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
  #endif
  
+ if (chunking_state > CHUNKING_OFFERED)
+   s = string_catn(s, &size, &sptr, US" K", 2);
  sprintf(CS big_buffer, "%d", msg_size);
  s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
  
@@@ -3812,7 -3886,7 +3887,7 @@@ any characters except " \ and CR and s
  Therefore, make sure we use a printing-characters only version for the log.
  Also, allow for domain literals in the message id. */
  
 -if (msgid_header != NULL)
 +if (msgid_header)
    {
    uschar *old_id;
    BOOL save_allow_domain_literals = allow_domain_literals;
@@@ -3895,9 -3969,7 +3970,9 @@@ if (message_logs && blackholed_by == NU
        if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
          frozen_by);
        if (queue_only_policy) fprintf(message_log,
 -        "%s no immediate delivery: queued by %s\n", now, queued_by);
 +        "%s no immediate delivery: queued%s%s by %s\n", now,
 +        *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
 +      queued_by);
        (void)fclose(message_log);
        }
      }
@@@ -4017,9 -4089,7 +4092,9 @@@ if(!smtp_reply
  
    if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
    if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
 -    "no immediate delivery: queued by %s", queued_by);
 +    "no immediate delivery: queued%s%s by %s",
 +    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",       
 +    queued_by);
    }
  receive_call_bombout = FALSE;
  
@@@ -4075,15 -4145,15 +4150,15 @@@ if (smtp_input
  
    if (!smtp_batched_input)
      {
 -    if (smtp_reply == NULL)
 +    if (!smtp_reply)
        {
        if (fake_response != OK)
 -        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
 -          fake_response_text);
 +        smtp_respond(fake_response == DEFER ? US"450" : US"550",
 +        3, TRUE, fake_response_text);
  
        /* An OK response is required; use "message" text if present. */
  
 -      else if (user_msg != NULL)
 +      else if (user_msg)
          {
          uschar *code = US"250";
          int len = 3;
  
        /* Default OK response */
  
+       else if (chunking_state > CHUNKING_OFFERED)
+       {
+         smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+           chunking_datasize, message_size+message_linecount, message_id);
+       chunking_state = CHUNKING_OFFERED;
+       }
        else
          smtp_printf("250 OK id=%s\r\n", message_id);
        if (host_checking)
          fprintf(stdout,
            "\n**** SMTP testing: that is not a real message id!\n\n");
    nothing on success. The function moan_smtp_batch() does not return -
    it exits from the program with a non-zero return code. */
  
 -  else if (smtp_reply != NULL) moan_smtp_batch(NULL, "%s", smtp_reply);
 +  else if (smtp_reply)
 +    moan_smtp_batch(NULL, "%s", smtp_reply);
    }
  
  
@@@ -4140,7 -4216,7 +4222,7 @@@ file has already been unlinked, and th
  We must now indicate that nothing was received, to prevent a delivery from
  starting. */
  
 -if (blackholed_by != NULL)
 +if (blackholed_by)
    {
    const uschar *detail = local_scan_data
      ? string_printing(local_scan_data)
diff --combined src/src/transport.c
index 8e0a802400c0ae6268cb80793e883ae91522e8e7,88d925e39904fd2c98a71345e397f0648ae24fde..a68c22f298bb82df888c5ecbd24f0b104c3d31a4
@@@ -236,10 -236,12 +236,12 @@@ for (i = 0; i < 100; i++
    else
      {
      alarm(local_timeout);
-     #ifdef SUPPORT_TLS
-     if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
-     #endif
-     rc = write(fd, block, len);
+ #ifdef SUPPORT_TLS
+     if (tls_out.active == fd)
+       rc = tls_write(FALSE, block, len);
+     else
+ #endif
+       rc = write(fd, block, len);
      save_errno = errno;
      local_timeout = alarm(0);
      if (sigalrm_seen)
@@@ -357,7 -359,7 +359,7 @@@ Arguments
    fd         file descript to write to
    chunk      pointer to data to write
    len        length of data to write
-   usr_crlf   TRUE if CR LF is wanted at the end of each line
+   tctx       transport context - processing to be done during output
  
  In addition, the static nl_xxx variables must be set as required.
  
@@@ -365,7 -367,7 +367,7 @@@ Returns:     TRUE on success, FALSE on 
  */
  
  static BOOL
- write_chunk(int fd, uschar *chunk, int len, BOOL use_crlf)
+ write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
  {
  uschar *start = chunk;
  uschar *end = chunk + len;
@@@ -408,16 -410,22 +410,22 @@@ possible. *
  
  for (ptr = start; ptr < end; ptr++)
    {
-   int ch;
+   int ch, len;
  
    /* Flush the buffer if it has reached the threshold - we want to leave enough
    room for the next uschar, plus a possible extra CR for an LF, plus the escape
    string. */
  
-   if (chunk_ptr - deliver_out_buffer > mlen)
+   if ((len = chunk_ptr - deliver_out_buffer) > mlen)
      {
-     if (!transport_write_block(fd, deliver_out_buffer,
-           chunk_ptr - deliver_out_buffer))
+     /* If CHUNKING, prefix with BDAT (size) NON-LAST.  Also, reap responses
+     from previous SMTP commands. */
+     if (tctx &&  tctx->options & topt_use_bdat  &&  tctx->chunk_cb)
+       if (tctx->chunk_cb(fd, tctx, (unsigned)len, tc_reap_prev|tc_reap_one) != OK)
+       return FALSE;
+     if (!transport_write_block(fd, deliver_out_buffer, len))
        return FALSE;
      chunk_ptr = deliver_out_buffer;
      }
  
      /* Insert CR before NL if required */
  
-     if (use_crlf) *chunk_ptr++ = '\r';
+     if (tctx  &&  tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
      *chunk_ptr++ = '\n';
      transport_newlines++;
  
@@@ -545,14 -553,14 +553,14 @@@ Arguments
    pdlist    address of anchor of the list of processed addresses
    first     TRUE if this is the first address; set it FALSE afterwards
    fd        the file descriptor to write to
-   use_crlf  to be passed on to write_chunk()
+   tctx      transport context - processing to be done during output
  
  Returns:    FALSE if writing failed
  */
  
  static BOOL
  write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
-   BOOL *first, int fd, BOOL use_crlf)
+   BOOL *first, int fd, transport_ctx * tctx)
  {
  address_item *pp;
  struct aci *ppp;
@@@ -574,7 -582,7 +582,7 @@@ for (pp = p;; pp = pp->parent
    address_item *dup;
    for (dup = addr_duplicate; dup; dup = dup->next)
      if (dup->dupof == pp)   /* a dup of our address */
-       if (!write_env_to(dup, pplist, pdlist, first, fd, use_crlf))
+       if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
        return FALSE;
    if (!pp->parent) break;
    }
@@@ -591,9 -599,9 +599,9 @@@ ppp->next = *pplist
  *pplist = ppp;
  ppp->ptr = pp;
  
- if (!(*first) && !write_chunk(fd, US",\n ", 3, use_crlf)) return FALSE;
+ if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
  *first = FALSE;
- return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf);
+ return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
  }
  
  
@@@ -608,20 -616,24 +616,19 @@@ Arguments
    addr                  (chain of) addresses (for extra headers), or NULL;
                            only the first address is used
    fd                    file descriptor to write the message to
-   sendfn              function for output
-   use_crlf            turn NL into CR LF
-   rewrite_rules         chain of header rewriting rules
-   rewrite_existflags    flags for the rewriting rules
++  tctx                  transport context
+   sendfn              function for output (transport or verify)
 -  wck_flags
 -    use_crlf          turn NL into CR LF
 -    use_bdat          callback before chunk flush
 -  rewrite_rules         chain of header rewriting rules
 -  rewrite_existflags    flags for the rewriting rules
 -  chunk_cb            transport callback function for data-chunk commands
  
  Returns:                TRUE on success; FALSE on failure.
  */
  BOOL
- transport_headers_send(address_item *addr, int fd, transport_instance * tblock,
-   BOOL (*sendfn)(int fd, uschar * s, int len, BOOL use_crlf),
-   BOOL use_crlf)
+ transport_headers_send(int fd, transport_ctx * tctx,
+   BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len))
  {
  header_line *h;
  const uschar *list;
+ transport_instance * tblock = tctx ? tctx->tblock : NULL;
+ address_item * addr = tctx ? tctx->addr : NULL;
  
  /* Then the message's headers. Don't write any that are flagged as "old";
  that means they were rewritten, or are a record of envelope rewriting, or
@@@ -676,7 -688,7 +683,7 @@@ for (h = header_list; h; h = h->next) i
        if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
                  tblock->rewrite_existflags, FALSE)))
        {
-       if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE;
+       if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
        store_reset(reset_point);
        continue;     /* With the next header line */
        }
  
      /* Either no rewriting rules, or it didn't get rewritten */
  
-     if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+     if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
      }
  
    /* Header removed */
@@@ -719,7 -731,7 +726,7 @@@ if (addr
        hprev = h;
        if (i == 1)
        {
-       if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+       if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
        DEBUG(D_transport)
          debug_printf("added header line(s):\n%s---\n", h->text);
        }
@@@ -744,8 -756,8 +751,8 @@@ if (tblock && (list = CUS tblock->add_h
        int len = Ustrlen(s);
        if (len > 0)
        {
-       if (!sendfn(fd, s, len, use_crlf)) return FALSE;
-       if (s[len-1] != '\n' && !sendfn(fd, US"\n", 1, use_crlf))
+       if (!sendfn(fd, tctx, s, len)) return FALSE;
+       if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
          return FALSE;
        DEBUG(D_transport)
          {
  
  /* Separate headers from body with a blank line */
  
- return sendfn(fd, US"\n", 1, use_crlf);
+ return sendfn(fd, tctx, US"\n", 1);
  }
  
  
@@@ -814,12 -826,12 +821,12 @@@ Arguments
        end_dot               if TRUE, send a terminating "." line at the end
        no_headers            if TRUE, omit the headers
        no_body               if TRUE, omit the body
-     size_limit            if > 0, this is a limit to the size of message written;
+     check_string          a string to check for at the start of lines, or NULL
+     escape_string         a string to insert in front of any check string
+   size_limit              if > 0, this is a limit to the size of message written;
                              it is used when returning messages to their senders,
                              and is approximate rather than exact, owing to chunk
                              buffering
-     check_string          a string to check for at the start of lines, or NULL
-     escape_string         a string to insert in front of any check string
  
  Returns:                TRUE on success; FALSE (with errno) on failure.
                          In addition, the global variable transport_count
  static BOOL
  internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
  {
- int written = 0;
  int len;
- BOOL use_crlf = (tctx->options & topt_use_crlf) != 0;
  
  /* Initialize pointer in output buffer. */
  
@@@ -869,7 -879,7 +874,7 @@@ if (!(tctx->options & topt_no_headers)
      uschar buffer[ADDRESS_MAXLENGTH + 20];
      int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
        return_path);
-     if (!write_chunk(fd, buffer, n, use_crlf)) return FALSE;
+     if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
      }
  
    /* Add envelope-to: if requested */
      struct aci *dlist = NULL;
      void *reset_point = store_get(0);
  
-     if (!write_chunk(fd, US"Envelope-to: ", 13, use_crlf)) return FALSE;
+     if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
  
      /* Pick up from all the addresses. The plist and dlist variables are
      anchors for lists of addresses already handled; they have to be defined at
      this level becuase write_env_to() calls itself recursively. */
  
      for (p = tctx->addr; p; p = p->next)
-       if (!write_env_to(p, &plist, &dlist, &first, fd, use_crlf))
+       if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
        return FALSE;
  
      /* Add a final newline and reset the store used for tracking duplicates */
  
-     if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE;
+     if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
      store_reset(reset_point);
      }
  
      {
      uschar buffer[100];
      int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
-     if (!write_chunk(fd, buffer, n, use_crlf)) return FALSE;
+     if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
      }
  
    /* Then the message's headers. Don't write any that are flagged as "old";
    match any entries therein. Then check addr->prop.remove_headers too, provided that
    addr is not NULL. */
  
-   if (!transport_headers_send(tctx->addr, fd, tctx->tblock, &write_chunk, use_crlf))
+   if (!transport_headers_send(fd, tctx, &write_chunk))
+     return FALSE;
+   }
+ /* When doing RFC3030 CHUNKING output, work out how much data will be in the
+ last BDAT, consisting of the current write_chunk() output buffer fill
+ (optimally, all of the headers - but it does not matter if we already had to
+ flush that buffer with non-last BDAT prependix) plus the amount of body data
+ (as expanded for CRLF lines).  Then create and write the BDAT, and ensure
+ that further use of write_chunk() will not prepend BDATs.
+ The first BDAT written will also first flush any outstanding MAIL and RCPT
+ commands which were buffered thans to PIPELINING.
+ Commands go out (using a send()) from a different buffer to data (using a
+ write()).  They might not end up in the same TCP segment, which is
+ suboptimal. */
+ if (tctx->options & topt_use_bdat)
+   {
+   off_t fsize;
+   int hsize, size;
+   if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
+     hsize = 0;
+   if (!(tctx->options & topt_no_body))
+     {
+     if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE;
+     fsize -= SPOOL_DATA_START_OFFSET;
+     if (size_limit > 0  &&  fsize > size_limit)
+       fsize = size_limit;
+     size = hsize + fsize;
+     if (tctx->options & topt_use_crlf)
+       size += body_linecount; /* account for CRLF-expansion */
+     }
+   /* If the message is large, emit first a non-LAST chunk with just the
+   headers, and reap the command responses.  This lets us error out early
+   on RCPT rejects rather than sending megabytes of data.  Include headers
+   on the assumption they are cheap enough and some clever implementations
+   might errorcheck them too, on-the-fly, and reject that chunk. */
+   if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
+     {
+     if (  tctx->chunk_cb(fd, tctx, hsize, 0) != OK
+        || !transport_write_block(fd, deliver_out_buffer, hsize)
+        || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+        )
+       return FALSE;
+     chunk_ptr = deliver_out_buffer;
+     size -= hsize;
+     }
+   /* Emit a LAST datachunk command. */
+   if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK)
      return FALSE;
+   tctx->options &= ~topt_use_bdat;
    }
  
  /* If the body is required, ensure that the data for check strings (formerly
@@@ -925,23 -990,18 +985,18 @@@ it, applying the size limit if required
  
  if (!(tctx->options & topt_no_body))
    {
+   int size = size_limit;
    nl_check_length = abs(nl_check_length);
    nl_partial_match = 0;
    if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
      return FALSE;
-   while ((len = read(deliver_datafile, deliver_in_buffer,
-            DELIVER_IN_BUFFER_SIZE)) > 0)
+   while (  (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+       && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
      {
-     if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) return FALSE;
-     if (size_limit > 0)
-       {
-       written += len;
-       if (written > size_limit)
-         {
-         len = 0;    /* Pretend EOF */
-         break;
-         }
-       }
+     if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+       return FALSE;
+     size -= len;
      }
  
    /* A read error on the body will have left len == -1 and errno set. */
@@@ -955,7 -1015,7 +1010,7 @@@ nl_check_length = nl_escape_length = 0
  
  /* If requested, add a terminating "." line (SMTP output). */
  
- if (tctx->options & topt_end_dot && !write_chunk(fd, US".\n", 2, use_crlf))
+ if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
    return FALSE;
  
  /* Write out any remaining data in the buffer before returning. */
@@@ -997,7 -1057,9 +1052,9 @@@ uschar * dkim_spool_name
  int sread = 0;
  int wwritten = 0;
  uschar *dkim_signature = NULL;
+ int siglen = 0;
  off_t k_file_size;
+ int options;
  
  /* If we can't sign, just call the original function. */
  
@@@ -1017,7 -1079,10 +1074,10 @@@ if ((dkim_fd = Uopen(dkim_spool_name, O
  
  /* Call original function to write the -K file; does the CRLF expansion */
  
+ options = tctx->options;
+ tctx->options &= ~topt_use_bdat;
  rc = transport_write_message(dkim_fd, tctx, 0);
+ tctx->options = options;
  
  /* Save error state. We must clean up before returning. */
  if (!rc)
    goto CLEANUP;
    }
  
- if (dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+ /* Rewind file and feed it to the goats^W DKIM lib */
+ lseek(dkim_fd, 0, SEEK_SET);
+ dkim_signature = dkim_exim_sign(dkim_fd,
+                               dkim->dkim_private_key,
+                               dkim->dkim_domain,
+                               dkim->dkim_selector,
+                               dkim->dkim_canon,
+                               dkim->dkim_sign_headers);
+ if (dkim_signature)
+   siglen = Ustrlen(dkim_signature);
+ else if (dkim->dkim_strict)
    {
-   /* Rewind file and feed it to the goats^W DKIM lib */
-   lseek(dkim_fd, 0, SEEK_SET);
-   dkim_signature = dkim_exim_sign(dkim_fd,
-                                 dkim->dkim_private_key,
-                                 dkim->dkim_domain,
-                                 dkim->dkim_selector,
-                                 dkim->dkim_canon,
-                                 dkim->dkim_sign_headers);
-   if (!dkim_signature)
-     {
-     if (dkim->dkim_strict)
+   uschar *dkim_strict_result = expand_string(dkim->dkim_strict);
+   if (dkim_strict_result)
+     if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
+        (strcmpic(dkim->dkim_strict,US"true") == 0) )
        {
-       uschar *dkim_strict_result = expand_string(dkim->dkim_strict);
-       if (dkim_strict_result)
-       if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
-            (strcmpic(dkim->dkim_strict,US"true") == 0) )
-         {
-         /* Set errno to something halfway meaningful */
-         save_errno = EACCES;
-         log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
-           " and dkim_strict is set. Deferring message delivery.");
-         rc = FALSE;
-         goto CLEANUP;
-         }
+       /* Set errno to something halfway meaningful */
+       save_errno = EACCES;
+       log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+       " and dkim_strict is set. Deferring message delivery.");
+       rc = FALSE;
+       goto CLEANUP;
        }
-     }
+   }
  
-   if (dkim_signature)
-     {
-     int siglen = Ustrlen(dkim_signature);
-     while(siglen > 0)
-       {
- #ifdef SUPPORT_TLS
-       wwritten = tls_out.active == out_fd
-         ? tls_write(FALSE, dkim_signature, siglen)
-         : write(out_fd, dkim_signature, siglen);
- #else
-       wwritten = write(out_fd, dkim_signature, siglen);
+ #ifndef HAVE_LINUX_SENDFILE
+ if (options & topt_use_bdat)
  #endif
-       if (wwritten == -1)
-       {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
-       siglen -= wwritten;
-       dkim_signature += wwritten;
-       }
+   k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
+ if (options & topt_use_bdat)
+   {
+   /* On big messages output a precursor chunk to get any pipelined
+   MAIL & RCPT commands flushed, then reap the responses so we can
+   error out on RCPT rejects before sending megabytes. */
+   if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
+     {
+     if (  tctx->chunk_cb(out_fd, tctx, siglen, 0) != OK
+        || !transport_write_block(out_fd, dkim_signature, siglen)
+        || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK
+        )
+       goto err;
+     siglen = 0;
      }
+   if (tctx->chunk_cb(out_fd, tctx, siglen + k_file_size, tc_chunk_last) != OK)
+     goto err;
    }
  
+ if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen))
+   goto err;
  #ifdef HAVE_LINUX_SENDFILE
  /* We can use sendfile() to shove the file contents
     to the socket. However only if we don't use TLS,
@@@ -1090,18 -1156,13 +1151,13 @@@ if (tls_out.active != out_fd
    ssize_t copied = 0;
    off_t offset = 0;
  
-   k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
    /* Rewind file */
    lseek(dkim_fd, 0, SEEK_SET);
  
    while(copied >= 0 && offset < k_file_size)
      copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
    if (copied < 0)
-     {
-     save_errno = errno;
-     rc = FALSE;
-     }
+     goto err;
    }
  else
  
        wwritten = write(out_fd, p, sread);
  #endif
        if (wwritten == -1)
-       {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
+       goto err;
        p += wwritten;
        sread -= wwritten;
        }
    }
  
  CLEANUP:
- /* unlink -K file */
- (void)close(dkim_fd);
- Uunlink(dkim_spool_name);
- errno = save_errno;
- return rc;
+   /* unlink -K file */
+   (void)close(dkim_fd);
+   Uunlink(dkim_spool_name);
+   errno = save_errno;
+   return rc;
+ err:
+   save_errno = errno;
+   rc = FALSE;
+   goto CLEANUP;
  }
  
  #endif
@@@ -1167,7 -1228,6 +1223,7 @@@ set up a filtering process, fork anothe
  to write to the filter, and in this process just suck from the filter and write
  down the given fd. At the end, tidy up the pipes and the processes.
  
 +XXX
  Arguments:     as for internal_transport_write_message() above
  
  Returns:       TRUE on success; FALSE (with errno) for any failure
  BOOL
  transport_write_message(int fd, transport_ctx * tctx, int size_limit)
  {
BOOL use_crlf;
unsigned wck_flags;
  BOOL last_filter_was_NL = TRUE;
  int rc, len, yield, fd_read, fd_write, save_errno;
  int pfd[2] = {-1, -1};
  pid_t filter_pid, write_pid;
- static transport_ctx dummy_tctx = { NULL, NULL, NULL, NULL, 0 };
+ static transport_ctx dummy_tctx = {0};
  
  if (!tctx) tctx = &dummy_tctx;
  
@@@ -1201,7 -1261,7 +1257,7 @@@ if (  !transport_filter_arg
  before being written to the incoming fd. First set up the special processing to
  be done during the copying. */
  
use_crlf = (tctx->options & topt_use_crlf) != 0;
wck_flags = tctx->options & topt_use_crlf;
  nl_partial_match = -1;
  
  if (tctx->check_string && tctx->escape_string)
@@@ -1248,7 -1308,7 +1304,7 @@@ if ((write_pid = fork()) == 0
    nl_check_length = nl_escape_length = 0;
  
    tctx->check_string = tctx->escape_string = NULL;
-   tctx->options &= ~(topt_use_crlf | topt_end_dot);
+   tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
  
    rc = internal_transport_write_message(fd_write, tctx, size_limit);
  
@@@ -1317,7 -1377,7 +1373,7 @@@ for (;;
  
    if (len > 0)
      {
-     if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) goto TIDY_UP;
+     if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
      last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
      }
  
@@@ -1399,8 -1459,8 +1455,8 @@@ if (yield
    nl_check_length = nl_escape_length = 0;
    if (  tctx->options & topt_end_dot
       && ( last_filter_was_NL
-         ? !write_chunk(fd, US".\n", 2, use_crlf)
-       : !write_chunk(fd, US"\n.\n", 3, use_crlf)
+         ? !write_chunk(fd, tctx, US".\n", 2)
+       : !write_chunk(fd, tctx, US"\n.\n", 3)
       )  )
      yield = FALSE;
  
@@@ -1883,7 -1943,7 +1939,7 @@@ DEBUG(D_transport) debug_printf("transp
  
  if ((pid = fork()) == 0)
    {
-   int i = 16;
+   int i = 17;
    const uschar **argv;
  
    /* Disconnect entirely from the parent process. If we are running in the
  
    argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
  
-   if (smtp_use_dsn) argv[i++] = US"-MCD";
    if (smtp_authenticated) argv[i++] = US"-MCA";
  
-   #ifdef SUPPORT_TLS
-   if (tls_offered) argv[i++] = US"-MCT";
-   #endif
-   if (smtp_use_size) argv[i++] = US"-MCS";
-   if (smtp_use_pipelining) argv[i++] = US"-MCP";
+   if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK";
+   if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD";
+   if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP";
+   if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS";
+ #ifdef SUPPORT_TLS
+   if (smtp_peer_options & PEER_OFFERED_TLS) argv[i++] = US"-MCT";
+ #endif
  
    if (queue_run_pid != (pid_t)0)
      {
index bbfef0632a61181905de5113a62e4770aa67ebe7,25e493433e54c347b55eeb2cf876e4738d2500ff..52b2b913f4fde5eb99e835ceb27c17189145b727
@@@ -12,6 -12,8 +12,8 @@@
  #define PENDING_DEFER   (PENDING + DEFER)
  #define PENDING_OK      (PENDING + OK)
  
+ #define DELIVER_BUFFER_SIZE 4096
  
  /* Options specific to the smtp transport. This transport also supports LMTP
  over TCP/IP. The options must be in alphabetic order (note that "_" comes
@@@ -116,6 -118,8 +118,8 @@@ optionlist smtp_transport_options[] = 
  #endif
    { "hosts_try_auth",       opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+   { "hosts_try_chunking",   opt_stringptr,
+       (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
  #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
    { "hosts_try_dane",       opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
@@@ -200,12 -204,13 +204,13 @@@ smtp_transport_options_block smtp_trans
    NULL,                /* serialize_hosts */
    NULL,                /* hosts_try_auth */
    NULL,                /* hosts_require_auth */
+   US"*",               /* hosts_try_chunking */
  #ifdef EXPERIMENTAL_DANE
    NULL,                /* hosts_try_dane */
    NULL,                /* hosts_require_dane */
  #endif
  #ifndef DISABLE_PRDR
-   US"*",                /* hosts_try_prdr */
+   US"*",               /* hosts_try_prdr */
  #endif
  #ifndef DISABLE_OCSP
    US"*",               /* hosts_request_ocsp (except under DANE; tls_client_start()) */
@@@ -778,6 -783,7 +783,7 @@@ if (pending_MAIL
    count--;
    if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
      {
+     DEBUG(D_transport) debug_printf("bad response for MAIL\n");
      Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
      if (errno == 0 && buffer[0] != 0)
        {
@@@ -1217,8 -1223,7 +1223,8 @@@ switch (dns_lookup(dnsa, buffer, T_TLSA
    case DNS_AGAIN:
      return DEFER; /* just defer this TLS'd conn */
  
 -  case DNS_NOMATCH:
 +  case DNS_NODATA:    /* no TLSA RR for this lookup */
 +  case DNS_NOMATCH:   /* no records at all for this lookup */
      return dane_required ? FAIL : FAIL_FORCED;
  
    default:
@@@ -1321,6 -1326,10 +1327,10 @@@ if (  checks & PEER_OFFERED_IGN
                PCRE_EOPT, NULL, 0) < 0)
    checks &= ~PEER_OFFERED_IGNQ;
  
+ if (  checks & PEER_OFFERED_CHUNKING
+    && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   checks &= ~PEER_OFFERED_CHUNKING;
  #ifndef DISABLE_PRDR
  if (  checks & PEER_OFFERED_PRDR
     && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
@@@ -1350,6 -1359,99 +1360,99 @@@ return checks
  }
  
  
+ /* Callback for emitting a BDAT data chunk header.
+ If given a nonzero size, first flush any buffered SMTP commands
+ then emit the command.
+ Reap previous SMTP command responses if requested.
+ Reap one SMTP command response if requested.
+ Returns:      OK or ERROR
+ */
+ static int
+ smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
+   unsigned chunk_size, unsigned flags)
+ {
+ smtp_transport_options_block * ob =
+   (smtp_transport_options_block *)(tctx->tblock->options_block);
+ int cmd_count = 0;
+ int prev_cmd_count;
+ uschar * buffer = tctx->buffer;
+ /* Write SMTP chunk header command */
+ if (chunk_size > 0)
+   if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
+                             chunk_size,
+                             flags & tc_chunk_last ? " LAST" : "")
+      ) < 0) return ERROR;
+ prev_cmd_count = cmd_count += tctx->cmd_count;
+ /* Reap responses for any previous, but not one we just emitted */
+ if (chunk_size > 0)
+   prev_cmd_count--;
+ if (tctx->pending_BDAT)
+   prev_cmd_count--;
+ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
+   {
+   switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes,
+         tctx->sync_addr, tctx->host, prev_cmd_count,
+         ob->address_retry_include_sender,
+         tctx->pending_MAIL, 0,
+         tctx->inblock,
+         ob->command_timeout,
+         buffer, DELIVER_BUFFER_SIZE))
+     {
+     case 1:                           /* 2xx (only) => OK */
+     case 3: tctx->good_RCPT = TRUE;   /* 2xx & 5xx => OK & progress made */
+     case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */
+     case 0: break;                    /* No 2xx or 5xx, but no probs */
+     case -1:                          /* Timeout on RCPT */
+     default: return ERROR;            /* I/O error, or any MAIL/DATA error */
+     }
+   cmd_count = 1;
+   if (!tctx->pending_BDAT)
+     pipelining_active = FALSE;
+   }
+ /* Reap response for the cmd we just emitted, or an outstanding BDAT */
+ if (flags & tc_reap_one  ||  tctx->pending_BDAT)
+   {
+   if (!smtp_read_response(tctx->inblock, buffer, DELIVER_BUFFER_SIZE, '2',
+        ob->command_timeout))
+     {
+     if (errno == 0 && buffer[0] == '4')
+       {
+       errno = ERRNO_DATA4XX;  /*XXX does this actually get used? */
+       tctx->first_addr->more_errno |=
+       ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+       }
+     return ERROR;
+     }
+   cmd_count--;
+   tctx->pending_BDAT = FALSE;
+   pipelining_active = FALSE;
+   }
+ else if (chunk_size > 0)
+   tctx->pending_BDAT = TRUE;
+ tctx->cmd_count = cmd_count;
+ return OK;
+ }
  /*************************************************
  *       Deliver address list to given host       *
  *************************************************/
@@@ -1419,7 -1521,7 +1522,7 @@@ BOOL completed_address = FALSE
  BOOL esmtp = TRUE;
  BOOL pending_MAIL;
  BOOL pass_message = FALSE;
- uschar peer_offered = 0;      /*XXX should this be handed on cf. tls_offered, smtp_use_dsn ? */
+ uschar peer_offered = 0;
  #ifndef DISABLE_PRDR
  BOOL prdr_active;
  #endif
@@@ -1446,7 -1548,7 +1549,7 @@@ uschar *helo_data = NULL
  uschar *message = NULL;
  uschar new_message_id[MESSAGE_ID_LENGTH + 1];
  uschar *p;
- uschar buffer[4096];
+ uschar buffer[DELIVER_BUFFER_SIZE];
  uschar inbuffer[4096];
  uschar outbuffer[4096];
  
@@@ -1654,7 -1756,7 +1757,7 @@@ goto SEND_QUIT
  #ifdef SUPPORT_TLS
    if (smtps)
      {
-     tls_offered = TRUE;
+     smtp_peer_options |= PEER_OFFERED_TLS;
      suppress_tls = FALSE;
      ob->tls_tempfail_tryclear = FALSE;
      smtp_command = US"SSL-on-connect";
      if (!good_response) goto RESPONSE_FAILED;
      }
  
+   peer_offered = smtp_peer_options = 0;
    if (esmtp || lmtp)
+     {
      peer_offered = ehlo_response(buffer, Ustrlen(buffer),
        PEER_OFFERED_TLS        /* others checked later */
        );
    /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
  
  #ifdef SUPPORT_TLS
-   tls_offered = !!(peer_offered & PEER_OFFERED_TLS);
+     smtp_peer_options |= peer_offered & PEER_OFFERED_TLS;
  #endif
+     }
    }
  
  /* For continuing deliveries down the same channel, the socket is the standard
  input, and we don't need to redo EHLO here (but may need to do so for TLS - see
  below). Set up the pointer to where subsequent commands will be left, for
- error messages. Note that smtp_use_size and smtp_use_pipelining will have been
+ error messages. Note that smtp_peer_options will have been
  set from the command line if they were set in the process that passed the
  connection on. */
  
@@@ -1743,7 -1849,7 +1850,7 @@@ the client not be required to use TLS. 
  for error analysis. */
  
  #ifdef SUPPORT_TLS
- if (  tls_offered
+ if (  smtp_peer_options & PEER_OFFERED_TLS
     && !suppress_tls
     && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK)
    {
  
      /* TLS session is set up */
  
+     smtp_peer_options_wrap = smtp_peer_options;
      for (addr = addrlist; addr; addr = addr->next)
        if (addr->transport_return == PENDING_DEFER)
          {
@@@ -1874,6 -1981,7 +1982,7 @@@ if (tls_out.active >= 0
    helo_response = string_copy(buffer);
  #endif
    if (!good_response) goto RESPONSE_FAILED;
+   smtp_peer_options = 0;
    }
  
  /* If the host is required to use a secure channel, ensure that we
@@@ -1888,8 -1996,8 +1997,8 @@@ else if (  smtp
    {
    save_errno = ERRNO_TLSREQUIRED;
    message = string_sprintf("a TLS session is required, but %s",
-     tls_offered ? "an attempt to start TLS failed"
-               : "the server did not offer TLS support");
+     smtp_peer_options & PEER_OFFERED_TLS
+     ? "an attempt to start TLS failed" : "the server did not offer TLS support");
    goto TLS_FAILED;
    }
  #endif        /*SUPPORT_TLS*/
@@@ -1906,13 -2014,15 +2015,15 @@@ if (continue_hostname == NUL
      )
    {
    if (esmtp || lmtp)
+     {
      peer_offered = ehlo_response(buffer, Ustrlen(buffer),
        0 /* no TLS */
        | (lmtp && ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
+       | PEER_OFFERED_CHUNKING
        | PEER_OFFERED_PRDR
  #ifdef SUPPORT_I18N
        | (addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0)
-               /*XXX if we hand peercaps on to continued-conn processes,
+       /*XXX if we hand peercaps on to continued-conn processes,
              must not depend on this addr */
  #endif
        | PEER_OFFERED_DSN
        | (ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
        );
  
-   /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
-   lmtp_ignore_quota option was set. */
+     /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+     lmtp_ignore_quota option was set. */
+     igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
  
-   igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
+     /* If the response to EHLO specified support for the SIZE parameter, note
+     this, provided size_addition is non-negative. */
  
-   /* If the response to EHLO specified support for the SIZE parameter, note
-   this, provided size_addition is non-negative. */
+     smtp_peer_options |= peer_offered & PEER_OFFERED_SIZE;
  
-   smtp_use_size = !!(peer_offered & PEER_OFFERED_SIZE);
+     /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
+     the current host, esmtp will be false, so PIPELINING can never be used. If
+     the current host matches hosts_avoid_pipelining, don't do it. */
  
-   /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
-   the current host, esmtp will be false, so PIPELINING can never be used. If
-   the current host matches hosts_avoid_pipelining, don't do it. */
+     if (  peer_offered & PEER_OFFERED_PIPE
+        && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK)
+       smtp_peer_options |= PEER_OFFERED_PIPE;
  
-   smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE
-     && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK;
+     DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
+       smtp_peer_options & PEER_OFFERED_PIPE ? "" : "not ");
  
-   DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
-     smtp_use_pipelining ? "" : "not ");
+     if (  peer_offered & PEER_OFFERED_CHUNKING
+        && verify_check_given_host(&ob->hosts_try_chunking, host) != OK)
+       peer_offered &= ~PEER_OFFERED_CHUNKING;
+     if (peer_offered & PEER_OFFERED_CHUNKING)
+       {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
  
  #ifndef DISABLE_PRDR
-   if (  peer_offered & PEER_OFFERED_PRDR
-      && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
-     peer_offered &= ~PEER_OFFERED_PRDR;
+     if (  peer_offered & PEER_OFFERED_PRDR
+        && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
+       peer_offered &= ~PEER_OFFERED_PRDR;
  
-   if (peer_offered & PEER_OFFERED_PRDR)
-     {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+     if (peer_offered & PEER_OFFERED_PRDR)
+       {DEBUG(D_transport) debug_printf("PRDR usable\n");}
  #endif
  
-   /* Note if the server supports DSN */
-   smtp_use_dsn = !!(peer_offered & PEER_OFFERED_DSN);
-   DEBUG(D_transport) debug_printf("%susing DSN\n", smtp_use_dsn ? "" : "not ");
+     /* Note if the server supports DSN */
+     smtp_peer_options |= peer_offered & PEER_OFFERED_DSN;
+     DEBUG(D_transport) debug_printf("%susing DSN\n",
+                       peer_offered & PEER_OFFERED_DSN ? "" : "not ");
  
-   /* Note if the response to EHLO specifies support for the AUTH extension.
-   If it has, check that this host is one we want to authenticate to, and do
-   the business. The host name and address must be available when the
-   authenticator's client driver is running. */
+     /* Note if the response to EHLO specifies support for the AUTH extension.
+     If it has, check that this host is one we want to authenticate to, and do
+     the business. The host name and address must be available when the
+     authenticator's client driver is running. */
  
-   switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
-                           ob, esmtp, &inblock, &outblock))
-     {
-     default:          goto SEND_QUIT;
-     case OK:          break;
-     case FAIL_SEND:   goto SEND_FAILED;
-     case FAIL:                goto RESPONSE_FAILED;
+     switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+                             ob, esmtp, &inblock, &outblock))
+       {
+       default:                goto SEND_QUIT;
+       case OK:                break;
+       case FAIL_SEND: goto SEND_FAILED;
+       case FAIL:      goto RESPONSE_FAILED;
+       }
      }
    }
- pipelining_active = smtp_use_pipelining;
+ pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
  
  /* The setting up of the SMTP call is now complete. Any subsequent errors are
  message-specific. */
@@@ -2013,6 -2133,16 +2134,16 @@@ if (tblock->filter_command != NULL
      yield = ERROR;
      goto SEND_QUIT;
      }
+   if (  transport_filter_argv
+      && *transport_filter_argv
+      && **transport_filter_argv
+      && peer_offered & PEER_OFFERED_CHUNKING
+      )
+     {
+     peer_offered &= ~PEER_OFFERED_CHUNKING;
+     DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
+     }
    }
  
  
@@@ -2042,7 -2172,7 +2173,7 @@@ included in the count.) *
  p = buffer;
  *p = 0;
  
- if (smtp_use_size)
+ if (peer_offered & PEER_OFFERED_SIZE)
    {
    sprintf(CS p, " SIZE=%d", message_size+message_linecount+ob->size_addition);
    while (*p) p++;
@@@ -2086,17 -2216,14 +2217,14 @@@ for (dsn_all_lasthop = TRUE, addr = fir
  
  /* Add any DSN flags to the mail command */
  
- if (smtp_use_dsn && !dsn_all_lasthop)
+ if (peer_offered & PEER_OFFERED_DSN && !dsn_all_lasthop)
    {
    if (dsn_ret == dsn_ret_hdrs)
-     {
-     Ustrcpy(p, " RET=HDRS"); p += 9;
-     }
+     { Ustrcpy(p, " RET=HDRS"); p += 9; }
    else if (dsn_ret == dsn_ret_full)
-     {
-     Ustrcpy(p, " RET=FULL"); p += 9;
-     }
-   if (dsn_envid != NULL)
+     { Ustrcpy(p, " RET=FULL"); p += 9; }
+   if (dsn_envid)
      {
      string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid);
      while (*p) p++;
@@@ -2147,7 -2274,7 +2275,7 @@@ pending_MAIL = TRUE;     /* The block s
      }
  #endif
  
-   rc = smtp_write_command(&outblock, smtp_use_pipelining,
+   rc = smtp_write_command(&outblock, pipelining_active,
          "MAIL FROM:<%s>%s\r\n", s, buffer);
    }
  
@@@ -2194,21 -2321,22 +2322,22 @@@ for (addr = first_addr
    BOOL no_flush;
    uschar * rcpt_addr;
  
-   addr->dsn_aware = smtp_use_dsn ? dsn_support_yes : dsn_support_no;
+   addr->dsn_aware = peer_offered & PEER_OFFERED_DSN
+     ? dsn_support_yes : dsn_support_no;
  
    if (addr->transport_return != PENDING_DEFER) continue;
  
    address_count++;
-   no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next);
+   no_flush = pipelining_active && (!mua_wrapper || addr->next);
  
    /* Add any DSN flags to the rcpt command and add to the sent string */
  
    p = buffer;
    *p = 0;
  
-   if (smtp_use_dsn && !(addr->dsn_flags & rf_dsnlasthop))
+   if (peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
      {
-     if ((addr->dsn_flags & rf_dsnflags) != 0)
+     if (addr->dsn_flags & rf_dsnflags)
        {
        int i;
        BOOL first = TRUE;
@@@ -2302,15 -2430,19 +2431,19 @@@ if (mua_wrapper
  send DATA, but if it is FALSE (in the normal, non-wrapper case), we may still
  have a good recipient buffered up if we are pipelining. We don't want to waste
  time sending DATA needlessly, so we only send it if either ok is TRUE or if we
- are pipelining. The responses are all handled by sync_responses(). */
+ are pipelining. The responses are all handled by sync_responses().
+ If using CHUNKING, do not send a BDAT until we know how big a chunk we want
+ to send is. */
  
- if (ok || (smtp_use_pipelining && !mua_wrapper))
+ if (  !(peer_offered & PEER_OFFERED_CHUNKING)
+    && (ok || (pipelining_active && !mua_wrapper)))
    {
    int count = smtp_write_command(&outblock, FALSE, "DATA\r\n");
    if (count < 0) goto SEND_FAILED;
    switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
             host, count, ob->address_retry_include_sender, pending_MAIL,
-            ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer)))
+            ok ? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer)))
      {
      case 3: ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
      case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
    pipelining_active = FALSE;
    }
  
- /* Save the first address of the next batch. */
- first_addr = addr;
  /* If there were no good recipients (but otherwise there have been no
  problems), just set ok TRUE, since we have handled address-specific errors
  already. Otherwise, it's OK to send the message. Use the check/escape mechanism
@@@ -2337,27 -2465,68 +2466,68 @@@ for handling the SMTP dot-handling prot
  well as body. Set the appropriate timeout value to be used for each chunk.
  (Haven't been able to make it work using select() for writing yet.) */
  
- if (!ok)
+ if (!(peer_offered & PEER_OFFERED_CHUNKING) && !ok)
+   {
+   /* Save the first address of the next batch. */
+   first_addr = addr;
    ok = TRUE;
+   }
  else
    {
    transport_ctx tctx = {
      tblock,
      addrlist,
      US".", US"..",    /* Escaping strings */
-     topt_use_crlf | topt_end_dot | topt_escape_headers
+     topt_use_crlf | topt_escape_headers
      | (tblock->body_only      ? topt_no_headers : 0)
      | (tblock->headers_only   ? topt_no_body : 0)
      | (tblock->return_path_add        ? topt_add_return_path : 0)
      | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
 -    | (tblock->envelope_to_add        ? topt_add_envelope_to : 0),
 +    | (tblock->envelope_to_add        ? topt_add_envelope_to : 0)
    };
  
+   /* If using CHUNKING we need a callback from the generic transport
+   support to us, for the sending of BDAT smtp commands and the reaping
+   of responses.  The callback needs a whole bunch of state so set up
+   a transport-context structure to be passed around. */
+   if (peer_offered & PEER_OFFERED_CHUNKING)
+     {
+     tctx.check_string = tctx.escape_string = NULL;
+     tctx.options |= topt_use_bdat;
+     tctx.chunk_cb = smtp_chunk_cmd_callback;
+     tctx.inblock = &inblock;
+     tctx.outblock = &outblock;
+     tctx.host = host;
+     tctx.first_addr = first_addr;
+     tctx.sync_addr = &sync_addr;
+     tctx.pending_MAIL = pending_MAIL;
+     tctx.pending_BDAT = FALSE;
+     tctx.good_RCPT = ok;
+     tctx.completed_address = &completed_address;
+     tctx.cmd_count = 0;
+     tctx.buffer = buffer;
+     }
+   else
+     tctx.options |= topt_end_dot;
+   /* Save the first address of the next batch. */
+   first_addr = addr;
+   /* Responses from CHUNKING commands go in buffer.  Otherwise,
+   there has not been a response. */
+   buffer[0] = 0;
    sigalrm_seen = FALSE;
    transport_write_timeout = ob->data_timeout;
    smtp_command = US"sending data block";   /* For error messages */
    DEBUG(D_transport|D_v)
-     debug_printf("  SMTP>> writing message and terminating \".\"\n");
+     if (peer_offered & PEER_OFFERED_CHUNKING)
+       debug_printf("         will write message using CHUNKING\n");
+     else
+       debug_printf("  SMTP>> writing message and terminating \".\"\n");
    transport_count = 0;
  
  #ifndef DISABLE_DKIM
    transport_write_timeout = 0;   /* for subsequent transports */
  
    /* Failure can either be some kind of I/O disaster (including timeout),
-   or the failure of a transport filter or the expansion of added headers. */
+   or the failure of a transport filter or the expansion of added headers.
+   Or, when CHUNKING, it can be a protocol-detected failure. */
  
    if (!ok)
-     {
-     buffer[0] = 0;              /* There hasn't been a response */
      goto RESPONSE_FAILED;
-     }
  
    /* We used to send the terminating "." explicitly here, but because of
    buffering effects at both ends of TCP/IP connections, you don't gain
  
    smtp_command = US"end of data";
  
+   if (peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1)
+     {
+     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
+     switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
+            host, tctx.cmd_count-1, ob->address_retry_include_sender,
+            pending_MAIL, 0,
+            &inblock, ob->command_timeout, buffer, sizeof(buffer)))
+       {
+       case 3: ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
+       case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
+       break;
+       case 1: ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
+       if (!lmtp) completed_address = TRUE; /* can't tell about progress yet */
+       case 0: break;                       /* No 2xx or 5xx, but no probs */
+       case -1: goto END_OFF;               /* Timeout on RCPT */
+       default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
+       }
+     }
  #ifndef DISABLE_PRDR
    /* For PRDR we optionally get a partial-responses warning
     * followed by the individual responses, before going on with
      {
      ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
        ob->final_timeout);
-     if (!ok && errno == 0)
-       switch(buffer[0])
-         {
-       case '2': prdr_active = FALSE;
-                 ok = TRUE;
-                 break;
-       case '4': errno = ERRNO_DATA4XX;
-                   addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
-                 break;
-         }
+     if (!ok && errno == 0) switch(buffer[0])
+       {
+       case '2': prdr_active = FALSE;
+               ok = TRUE;
+               break;
+       case '4': errno = ERRNO_DATA4XX;
+               addrlist->more_errno |=
+                 ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+               break;
+       }
      }
    else
  #endif
  #ifndef DISABLE_PRDR
        if (prdr_active) addr->flags |= af_prdr_used;
  #endif
+       if (peer_offered & PEER_OFFERED_CHUNKING) addr->flags |= af_chunking_used;
        flag = '-';
  
  #ifndef DISABLE_PRDR
@@@ -2853,6 -3042,7 +3043,7 @@@ if (completed_address && ok && send_qui
        if (tls_out.active >= 0)
          {
          tls_close(FALSE, TRUE);
+       smtp_peer_options = smtp_peer_options_wrap;
          if (smtps)
            ok = FALSE;
          else
diff --combined test/stderr/5410
index 837941d171f1a35ad8e13a20dd6cceae2c36f0cf,f455075d1fbe2e0a24fed23449eaec76fcd834d0..fe3ed4429cf82a23c0705138df9d0957d8f45f10
@@@ -9,6 -9,7 +9,7 @@@ considering: $smtp_active_hostname ESMT
       result: myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
   in dsn_advertise_hosts? no (option unset)
   in pipelining_advertise_hosts? yes (matched "*")
+  in chunking_advertise_hosts? no (end of list)
   in tls_advertise_hosts? yes (matched "*")
  considering: ${if eq {SERVER}{server}{queue}{cutthrough}}
  considering: SERVER}{server}{queue}{cutthrough}}
@@@ -17,8 -18,8 +18,8 @@@
  considering: server}{queue}{cutthrough}}
    expanding: server
       result: server
 -condition: eq {SERVER}{server}
 -   result: false
 +  condition: eq {SERVER}{server}
 +     result: false
     scanning: queue}{cutthrough}}
    expanding: queue
       result: queue
@@@ -68,8 -69,8 +69,8 @@@ considering: $address_data}{usery}{*}{:
  considering: usery}{*}{:}}
    expanding: usery
       result: usery
 -condition: eq {$address_data}{usery}
 -   result: false
 +  condition: eq {$address_data}{usery}
 +     result: false
     scanning: *}{:}}
    expanding: *
       result: *
@@@ -87,8 -88,8 +88,8 @@@ considering: $address_data}{userz}{*}{:
  considering: userz}{*}{:}}
    expanding: userz
       result: userz
 -condition: eq {$address_data}{userz}
 -   result: false
 +  condition: eq {$address_data}{userz}
 +     result: false
     scanning: *}{:}}
    expanding: *
       result: *
@@@ -133,8 -134,8 +134,8 @@@ considering: Received: ${if def:sender_
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_rcvhost
 -   result: false
 +  condition: def:sender_rcvhost
 +     result: false
     scanning: from $sender_rcvhost
        }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
@@@ -153,8 -154,8 +154,8 @@@ considering: ${if def:sender_ident {fro
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_ident
 -   result: true
 +  condition: def:sender_ident
 +     result: true
  considering: from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
@@@ -171,8 -172,8 +172,8 @@@ considering: $sender_ident} }}${if def:
       result: CALLER
    expanding: from ${quote_local_part:$sender_ident} 
       result: from CALLER 
 -condition: def:sender_helo_name
 -   result: true
 +  condition: def:sender_helo_name
 +     result: true
  considering: (helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        }}
       result: from CALLER (helo=myhost.test.ex)
        
 -condition: def:received_protocol
 -   result: true
 +  condition: def:received_protocol
 +     result: true
  considering: with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        ${if def:sender_address {(envelope-from <$sender_address>)
        for $received_for}}
    expanding: with $received_protocol
       result: with local-esmtp
 -condition: def:sender_address
 -   result: true
 +  condition: def:tls_cipher
 +     result: false
 +   scanning: ($tls_cipher)
 +      }}(Exim $version_number)
 +      ${if def:sender_address {(envelope-from <$sender_address>)
 +      }}id $message_exim_id${if def:received_for {
 +      for $received_for}}
 +  expanding: ($tls_cipher)
 +      
 +     result: ()
 +      
 +   skipping: result is not used
 +  condition: def:sender_address
 +     result: true
  considering: (envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
        
       result: (envelope-from <CALLER@myhost.test.ex>)
        
 -condition: def:received_for
 -   result: true
 +  condition: def:received_for
 +     result: true
  considering: 
        for $received_for}}
    expanding: 
@@@ -266,6 -255,7 +267,7 @@@ considering: $smtp_active_hostname ESMT
       result: myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
   in dsn_advertise_hosts? no (option unset)
   in pipelining_advertise_hosts? yes (matched "*")
+  in chunking_advertise_hosts? no (end of list)
   in tls_advertise_hosts? yes (matched "*")
  considering: ${if eq {SERVER}{server}{queue}{cutthrough}}
  considering: SERVER}{server}{queue}{cutthrough}}
  considering: server}{queue}{cutthrough}}
    expanding: server
       result: server
 -condition: eq {SERVER}{server}
 -   result: false
 +  condition: eq {SERVER}{server}
 +     result: false
     scanning: queue}{cutthrough}}
    expanding: queue
       result: queue
@@@ -325,8 -315,8 +327,8 @@@ considering: $address_data}{usery}{*}{:
  considering: usery}{*}{:}}
    expanding: usery
       result: usery
 -condition: eq {$address_data}{usery}
 -   result: true
 +  condition: eq {$address_data}{usery}
 +     result: true
  considering: *}{:}}
    expanding: *
       result: *
@@@ -359,8 -349,8 +361,8 @@@ considering: Received: ${if def:sender_
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_rcvhost
 -   result: false
 +  condition: def:sender_rcvhost
 +     result: false
     scanning: from $sender_rcvhost
        }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
@@@ -379,8 -369,8 +381,8 @@@ considering: ${if def:sender_ident {fro
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_ident
 -   result: true
 +  condition: def:sender_ident
 +     result: true
  considering: from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
@@@ -397,8 -387,8 +399,8 @@@ considering: $sender_ident} }}${if def:
       result: CALLER
    expanding: from ${quote_local_part:$sender_ident} 
       result: from CALLER 
 -condition: def:sender_helo_name
 -   result: true
 +  condition: def:sender_helo_name
 +     result: true
  considering: (helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        }}
       result: from CALLER (helo=myhost.test.ex)
        
 -condition: def:received_protocol
 -   result: true
 +  condition: def:received_protocol
 +     result: true
  considering: with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        ${if def:sender_address {(envelope-from <$sender_address>)
        for $received_for}}
    expanding: with $received_protocol
       result: with local-esmtp
 -condition: def:sender_address
 -   result: true
 +  condition: def:tls_cipher
 +     result: false
 +   scanning: ($tls_cipher)
 +      }}(Exim $version_number)
 +      ${if def:sender_address {(envelope-from <$sender_address>)
 +      }}id $message_exim_id${if def:received_for {
 +      for $received_for}}
 +  expanding: ($tls_cipher)
 +      
 +     result: ()
 +      
 +   skipping: result is not used
 +  condition: def:sender_address
 +     result: true
  considering: (envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
        
       result: (envelope-from <CALLER@myhost.test.ex>)
        
 -condition: def:received_for
 -   result: true
 +  condition: def:received_for
 +     result: true
  considering: 
        for $received_for}}
    expanding: 
@@@ -492,6 -470,7 +494,7 @@@ considering: $smtp_active_hostname ESMT
       result: myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
   in dsn_advertise_hosts? no (option unset)
   in pipelining_advertise_hosts? yes (matched "*")
+  in chunking_advertise_hosts? no (end of list)
   in tls_advertise_hosts? yes (matched "*")
  considering: ${if eq {SERVER}{server}{queue}{cutthrough}}
  considering: SERVER}{server}{queue}{cutthrough}}
  considering: server}{queue}{cutthrough}}
    expanding: server
       result: server
 -condition: eq {SERVER}{server}
 -   result: false
 +  condition: eq {SERVER}{server}
 +     result: false
     scanning: queue}{cutthrough}}
    expanding: queue
       result: queue
@@@ -551,8 -530,8 +554,8 @@@ considering: $address_data}{usery}{*}{:
  considering: usery}{*}{:}}
    expanding: usery
       result: usery
 -condition: eq {$address_data}{usery}
 -   result: true
 +  condition: eq {$address_data}{usery}
 +     result: true
  considering: *}{:}}
    expanding: *
       result: *
@@@ -585,8 -564,8 +588,8 @@@ considering: Received: ${if def:sender_
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_rcvhost
 -   result: false
 +  condition: def:sender_rcvhost
 +     result: false
     scanning: from $sender_rcvhost
        }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
@@@ -605,8 -584,8 +608,8 @@@ considering: ${if def:sender_ident {fro
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_ident
 -   result: true
 +  condition: def:sender_ident
 +     result: true
  considering: from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
@@@ -623,8 -602,8 +626,8 @@@ considering: $sender_ident} }}${if def:
       result: CALLER
    expanding: from ${quote_local_part:$sender_ident} 
       result: from CALLER 
 -condition: def:sender_helo_name
 -   result: true
 +  condition: def:sender_helo_name
 +     result: true
  considering: (helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        }}
       result: from CALLER (helo=myhost.test.ex)
        
 -condition: def:received_protocol
 -   result: true
 +  condition: def:received_protocol
 +     result: true
  considering: with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        ${if def:sender_address {(envelope-from <$sender_address>)
        for $received_for}}
    expanding: with $received_protocol
       result: with local-esmtp
 -condition: def:sender_address
 -   result: true
 +  condition: def:tls_cipher
 +     result: false
 +   scanning: ($tls_cipher)
 +      }}(Exim $version_number)
 +      ${if def:sender_address {(envelope-from <$sender_address>)
 +      }}id $message_exim_id${if def:received_for {
 +      for $received_for}}
 +  expanding: ($tls_cipher)
 +      
 +     result: ()
 +      
 +   skipping: result is not used
 +  condition: def:sender_address
 +     result: true
  considering: (envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
        
       result: (envelope-from <CALLER@myhost.test.ex>)
        
 -condition: def:received_for
 -   result: true
 +  condition: def:received_for
 +     result: true
  considering: 
        for $received_for}}
    expanding: 
diff --combined test/stderr/5420
index a1d4d32678a8e8e57b599d8a53531164a2b2d449,9117875ef0c31b378fee140a88597248755cd711..623b99864b1596737db5be3aad6204098beb1665
@@@ -9,6 -9,7 +9,7 @@@ considering: $smtp_active_hostname ESMT
       result: myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
   in dsn_advertise_hosts? no (option unset)
   in pipelining_advertise_hosts? yes (matched "*")
+  in chunking_advertise_hosts? no (end of list)
   in tls_advertise_hosts? yes (matched "*")
  considering: ${if eq {SERVER}{server}{queue}{cutthrough}}
  considering: SERVER}{server}{queue}{cutthrough}}
@@@ -17,8 -18,8 +18,8 @@@
  considering: server}{queue}{cutthrough}}
    expanding: server
       result: server
 -condition: eq {SERVER}{server}
 -   result: false
 +  condition: eq {SERVER}{server}
 +     result: false
     scanning: queue}{cutthrough}}
    expanding: queue
       result: queue
@@@ -68,8 -69,8 +69,8 @@@ considering: $address_data}{usery}{*}{:
  considering: usery}{*}{:}}
    expanding: usery
       result: usery
 -condition: eq {$address_data}{usery}
 -   result: false
 +  condition: eq {$address_data}{usery}
 +     result: false
     scanning: *}{:}}
    expanding: *
       result: *
@@@ -87,8 -88,8 +88,8 @@@ considering: $address_data}{userz}{*}{:
  considering: userz}{*}{:}}
    expanding: userz
       result: userz
 -condition: eq {$address_data}{userz}
 -   result: false
 +  condition: eq {$address_data}{userz}
 +     result: false
     scanning: *}{:}}
    expanding: *
       result: *
@@@ -132,8 -133,8 +133,8 @@@ considering: Received: ${if def:sender_
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_rcvhost
 -   result: false
 +  condition: def:sender_rcvhost
 +     result: false
     scanning: from $sender_rcvhost
        }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
@@@ -152,8 -153,8 +153,8 @@@ considering: ${if def:sender_ident {fro
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_ident
 -   result: true
 +  condition: def:sender_ident
 +     result: true
  considering: from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
@@@ -170,8 -171,8 +171,8 @@@ considering: $sender_ident} }}${if def:
       result: CALLER
    expanding: from ${quote_local_part:$sender_ident} 
       result: from CALLER 
 -condition: def:sender_helo_name
 -   result: true
 +  condition: def:sender_helo_name
 +     result: true
  considering: (helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        }}
       result: from CALLER (helo=myhost.test.ex)
        
 -condition: def:received_protocol
 -   result: true
 +  condition: def:received_protocol
 +     result: true
  considering: with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        ${if def:sender_address {(envelope-from <$sender_address>)
        for $received_for}}
    expanding: with $received_protocol
       result: with local-esmtp
 -condition: def:sender_address
 -   result: true
 +  condition: def:tls_cipher
 +     result: false
 +   scanning: ($tls_cipher)
 +      }}(Exim $version_number)
 +      ${if def:sender_address {(envelope-from <$sender_address>)
 +      }}id $message_exim_id${if def:received_for {
 +      for $received_for}}
 +  expanding: ($tls_cipher)
 +      
 +     result: ()
 +      
 +   skipping: result is not used
 +  condition: def:sender_address
 +     result: true
  considering: (envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
        
       result: (envelope-from <CALLER@myhost.test.ex>)
        
 -condition: def:received_for
 -   result: true
 +  condition: def:received_for
 +     result: true
  considering: 
        for $received_for}}
    expanding: 
@@@ -265,6 -254,7 +266,7 @@@ considering: $smtp_active_hostname ESMT
       result: myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
   in dsn_advertise_hosts? no (option unset)
   in pipelining_advertise_hosts? yes (matched "*")
+  in chunking_advertise_hosts? no (end of list)
   in tls_advertise_hosts? yes (matched "*")
  considering: ${if eq {SERVER}{server}{queue}{cutthrough}}
  considering: SERVER}{server}{queue}{cutthrough}}
  considering: server}{queue}{cutthrough}}
    expanding: server
       result: server
 -condition: eq {SERVER}{server}
 -   result: false
 +  condition: eq {SERVER}{server}
 +     result: false
     scanning: queue}{cutthrough}}
    expanding: queue
       result: queue
@@@ -324,8 -314,8 +326,8 @@@ considering: $address_data}{usery}{*}{:
  considering: usery}{*}{:}}
    expanding: usery
       result: usery
 -condition: eq {$address_data}{usery}
 -   result: true
 +  condition: eq {$address_data}{usery}
 +     result: true
  considering: *}{:}}
    expanding: *
       result: *
@@@ -358,8 -348,8 +360,8 @@@ considering: Received: ${if def:sender_
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_rcvhost
 -   result: false
 +  condition: def:sender_rcvhost
 +     result: false
     scanning: from $sender_rcvhost
        }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
@@@ -378,8 -368,8 +380,8 @@@ considering: ${if def:sender_ident {fro
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_ident
 -   result: true
 +  condition: def:sender_ident
 +     result: true
  considering: from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
@@@ -396,8 -386,8 +398,8 @@@ considering: $sender_ident} }}${if def:
       result: CALLER
    expanding: from ${quote_local_part:$sender_ident} 
       result: from CALLER 
 -condition: def:sender_helo_name
 -   result: true
 +  condition: def:sender_helo_name
 +     result: true
  considering: (helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        }}
       result: from CALLER (helo=myhost.test.ex)
        
 -condition: def:received_protocol
 -   result: true
 +  condition: def:received_protocol
 +     result: true
  considering: with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        ${if def:sender_address {(envelope-from <$sender_address>)
        for $received_for}}
    expanding: with $received_protocol
       result: with local-esmtp
 -condition: def:sender_address
 -   result: true
 +  condition: def:tls_cipher
 +     result: false
 +   scanning: ($tls_cipher)
 +      }}(Exim $version_number)
 +      ${if def:sender_address {(envelope-from <$sender_address>)
 +      }}id $message_exim_id${if def:received_for {
 +      for $received_for}}
 +  expanding: ($tls_cipher)
 +      
 +     result: ()
 +      
 +   skipping: result is not used
 +  condition: def:sender_address
 +     result: true
  considering: (envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
        
       result: (envelope-from <CALLER@myhost.test.ex>)
        
 -condition: def:received_for
 -   result: true
 +  condition: def:received_for
 +     result: true
  considering: 
        for $received_for}}
    expanding: 
@@@ -491,6 -469,7 +493,7 @@@ considering: $smtp_active_hostname ESMT
       result: myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
   in dsn_advertise_hosts? no (option unset)
   in pipelining_advertise_hosts? yes (matched "*")
+  in chunking_advertise_hosts? no (end of list)
   in tls_advertise_hosts? yes (matched "*")
  considering: ${if eq {SERVER}{server}{queue}{cutthrough}}
  considering: SERVER}{server}{queue}{cutthrough}}
  considering: server}{queue}{cutthrough}}
    expanding: server
       result: server
 -condition: eq {SERVER}{server}
 -   result: false
 +  condition: eq {SERVER}{server}
 +     result: false
     scanning: queue}{cutthrough}}
    expanding: queue
       result: queue
@@@ -550,8 -529,8 +553,8 @@@ considering: $address_data}{usery}{*}{:
  considering: usery}{*}{:}}
    expanding: usery
       result: usery
 -condition: eq {$address_data}{usery}
 -   result: true
 +  condition: eq {$address_data}{usery}
 +     result: true
  considering: *}{:}}
    expanding: *
       result: *
@@@ -584,8 -563,8 +587,8 @@@ considering: Received: ${if def:sender_
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_rcvhost
 -   result: false
 +  condition: def:sender_rcvhost
 +     result: false
     scanning: from $sender_rcvhost
        }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
@@@ -604,8 -583,8 +607,8 @@@ considering: ${if def:sender_ident {fro
        ${if def:sender_address {(envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
 -condition: def:sender_ident
 -   result: true
 +  condition: def:sender_ident
 +     result: true
  considering: from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
@@@ -622,8 -601,8 +625,8 @@@ considering: $sender_ident} }}${if def:
       result: CALLER
    expanding: from ${quote_local_part:$sender_ident} 
       result: from CALLER 
 -condition: def:sender_helo_name
 -   result: true
 +  condition: def:sender_helo_name
 +     result: true
  considering: (helo=$sender_helo_name)
        }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        }}
       result: from CALLER (helo=myhost.test.ex)
        
 -condition: def:received_protocol
 -   result: true
 +  condition: def:received_protocol
 +     result: true
  considering: with $received_protocol}} ${if def:tls_cipher {($tls_cipher)
        }}(Exim $version_number)
        ${if def:sender_address {(envelope-from <$sender_address>)
        for $received_for}}
    expanding: with $received_protocol
       result: with local-esmtp
 -condition: def:sender_address
 -   result: true
 +  condition: def:tls_cipher
 +     result: false
 +   scanning: ($tls_cipher)
 +      }}(Exim $version_number)
 +      ${if def:sender_address {(envelope-from <$sender_address>)
 +      }}id $message_exim_id${if def:received_for {
 +      for $received_for}}
 +  expanding: ($tls_cipher)
 +      
 +     result: ()
 +      
 +   skipping: result is not used
 +  condition: def:sender_address
 +     result: true
  considering: (envelope-from <$sender_address>)
        }}id $message_exim_id${if def:received_for {
        for $received_for}}
        
       result: (envelope-from <CALLER@myhost.test.ex>)
        
 -condition: def:received_for
 -   result: true
 +  condition: def:received_for
 +     result: true
  considering: 
        for $received_for}}
    expanding: