Merge branch 'master' into transp_logging_1031
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 30 Jun 2013 16:02:05 +0000 (17:02 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 30 Jun 2013 16:04:35 +0000 (17:04 +0100)
Conflicts:
doc/doc-txt/experimental-spec.txt
src/src/EDITME
src/src/deliver.c
src/src/exim.c

1  2 
doc/doc-txt/experimental-spec.txt
src/src/EDITME
src/src/config.h.defaults
src/src/deliver.c
src/src/exim.c
src/src/expand.c
src/src/globals.c
src/src/globals.h
src/src/readconf.c
src/src/transports/smtp.c
src/src/transports/smtp.h

index 047440611d0047c1436009393cc77b46f28f9533,7fd2bd8ecf015d1383b2bb8362f4dfd76f24688c..565c01851a5bc80acbc663117b70c03cf8bfc1c1
@@@ -2,14 -2,42 +2,42 @@@ From time to time, experimental feature
  While a feature  is experimental, there  will be a  build-time
  option whose name starts  "EXPERIMENTAL_" that must be  set in
  order to include the  feature. This file contains  information
- about experimenatal  features, all  of which  are unstable and
- liable to incompatibile change.
+ about experimental  features, all  of which  are unstable and
+ liable to incompatible change.
+ PRDR support
+ --------------------------------------------------------------
+ Per-Recipient Data Reponse is an SMTP extension proposed by Eric Hall
+ in a (now-expired) IETF draft from 2007.  It's not hit mainstream
+ use, but has apparently been implemented in the META1 MTA.
+ There is mention at http://mail.aegee.org/intern/sendmail.html
+ of a patch to sendmail "to make it PRDR capable".
+  ref: http://www.eric-a-hall.com/specs/draft-hall-prdr-00.txt
+ If Exim is built with EXPERIMENTAL_PRDR there is a new config
+ boolean "prdr_enable" which controls whether PRDR is advertised
+ as part of an EHLO response, a new "acl_data_smtp_prdr" ACL
+ (called for each recipient, after data arrives but before the
+ data ACL), and a new smtp transport option "hosts_try_prdr".
+ PRDR may be used to support per-user content filtering.  Without it
+ one must defer any recipient after the first that has a different
+ content-filter configuration.  With PRDR, the RCPT-time check
+ for this can be disabled when the MAIL-time $smtp_command included
+ "PRDR".  Any required difference in behaviour of the main DATA-time
+ ACL should however depend on the PRDR-time ACL having run, as Exim
+ will avoid doing so in some situations (eg.  single-recipient mails).
  
  
  OCSP Stapling support
  --------------------------------------------------------------
  
- X509 PKI certificates expire and can be revoked; to handle this, the
+ X.509 PKI certificates expire and can be revoked; to handle this, the
  clients need some way to determine if a particular certificate, from a
  particular Certificate Authority (CA), is still valid.  There are three
  main ways to do so.
@@@ -41,7 -69,7 +69,7 @@@ starts retrying to fetch an OCSP proof 
  proof expires.  The downside is that it requires server support.
  
  If Exim is built with EXPERIMENTAL_OCSP and it was built with OpenSSL,
- then it gains one new option: "tls_ocsp_file".
+ then it gains a new global option: "tls_ocsp_file".
  
  The file specified therein is expected to be in DER format, and contain
  an OCSP proof.  Exim will serve it as part of the TLS handshake.  This
@@@ -58,10 -86,30 +86,30 @@@ next connection
  Exim will check for a valid next update timestamp in the OCSP proof;
  if not present, or if the proof has expired, it will be ignored.
  
+ Also, given EXPERIMENTAL_OCSP and OpenSSL, the smtp transport gains
+ a "hosts_require_ocsp" option; a host-list for which an OCSP Stapling
+ is requested and required for the connection to proceed.  The host(s)
+ should also be in "hosts_require_tls", and "tls_verify_certificates"
+ configured for the transport.
+ For the client to be able to verify the stapled OCSP the server must
+ also supply, in its stapled information, any intermediate
+ certificates for the chain leading to the OCSP proof from the signer
+ of the server certificate.  There may be zero or one such. These
+ intermediate certificates should be added to the server OCSP stapling
+ file (named by tls_ocsp_file).
  At this point in time, we're gathering feedback on use, to determine if
  it's worth adding complexity to the Exim daemon to periodically re-fetch
- OCSP files and somehow handling multiple files.  There is no client support
- for OCSP in Exim, this is feature expected to be used by mail clients.
+ OCSP files and somehow handling multiple files.
+   A helper script "ocsp_fetch.pl" for fetching a proof from a CA
+   OCSP server is supplied.  The server URL may be included in the
+   server certificate, if the CA is helpful.
+   One fail mode seen was the OCSP Signer cert expiring before the end
+   of vailidity of the OCSP proof. The checking done by Exim/OpenSSL
+   noted this as invalid overall, but the re-fetch script did not.
  
  
  
@@@ -380,7 -428,7 +428,7 @@@ their default locations
  
  You can now run SPF checks in incoming SMTP by using the "spf"
  ACL condition  in either  the MAIL,  RCPT or  DATA ACLs.  When
- using it in the RCPT ACL, you can make the checks dependend on
+ using it in the RCPT ACL, you can make the checks dependent on
  the RCPT  address (or  domain), so  you can  check SPF records
  only  for   certain  target   domains.  This   gives  you  the
  possibility  to opt-out  certain customers  that do  not want
@@@ -491,7 -539,7 +539,7 @@@ reject message
  When the spf_guess condition has run, it sets up the same expansion
  variables as when spf condition is run, described above.
  
- Additionally, since Best-guess is not standarized, you may redefine
+ Additionally, since Best-guess is not standardized, you may redefine
  what "Best-guess" means to you by redefining spf_guess variable in
  global config.  For example, the following:
  
@@@ -546,7 -594,7 +594,7 @@@ In the DATA ACL you can use the new con
  
  After that "$dcc_header" contains the X-DCC-Header.
  
- Returnvalues are:
+ Return values are:
    fail    for overall "R", "G" from dccifd
    defer   for overall "T" from dccifd
    accept  for overall "A", "S" from dccifd
@@@ -570,89 -618,232 +618,311 @@@ through to eg. SpamAssassin
  If you want to pass even more headers in the middle of the
  DATA stage you can set
    $acl_m_dcc_add_header
- to tell the DCC routines add more information; eg, you might set
+ to tell the DCC routines to add more information; eg, you might set
  this to some results from ClamAV.  Be careful.  Header syntax is
  not checked and is added "as is".
  
+ In case you've troubles with sites sending the same queue items from several
+ hosts and fail to get through greylisting you can use
+ $acl_m_dcc_override_client_ip
+ Setting $acl_m_dcc_override_client_ip to an IP address overrides the default
+ of $sender_host_address. eg. use the following ACL in DATA stage:
+   warn    set acl_m_dcc_override_client_ip = \
+     ${lookup{$sender_helo_name}nwildlsearch{/etc/mail/multipleip_sites}{$value}{}}
+           condition = ${if def:acl_m_dcc_override_client_ip}
+           log_message = dbg: acl_m_dcc_override_client_ip set to \
+                         $acl_m_dcc_override_client_ip
+ Then set something like
+ # cat /etc/mail/multipleip_sites
+ mout-xforward.gmx.net           82.165.159.12
+ mout.gmx.net                    212.227.15.16
+ Use a reasonable IP. eg. one the sending cluster acutally uses.
+ DMARC Support
+ --------------------------------------------------------------
+ DMARC combines feedback from SPF, DKIM, and header From: in order
+ to attempt to provide better indicators of the authenticity of an
+ email.  This document does not explain the fundamentals, you
+ should read and understand how it works by visiting the website at
+ http://www.dmarc.org/.
+ DMARC support is added via the libopendmarc library.  Visit:
+   http://sourceforge.net/projects/opendmarc/
+ to obtain a copy, or find it in your favorite rpm package
+ repository.  If building from source, this description assumes
+ that headers will be in /usr/local/include, and that the libraries
+ are in /usr/local/lib.
+ 1. To compile Exim with DMARC support, you must first enable SPF.
+ Please read the above section on enabling the EXPERIMENTAL_SPF
+ feature.  You must also have DKIM support, so you cannot set the
+ DISABLE_DKIM feature.  Once both of those conditions have been met
+ you can enable DMARC in Local/Makefile:
+ EXPERIMENTAL_DMARC=yes
+ LDFLAGS += -lopendmarc
+ # CFLAGS += -I/usr/local/include
+ # LDFLAGS += -L/usr/local/lib
+ The first line sets the feature to include the correct code, and
+ the second line says to link the libopendmarc libraries into the
+ exim binary.  The commented out lines should be uncommented if you
+ built opendmarc from source and installed in the default location.
+ Adjust the paths if you installed them elsewhere, but you do not
+ need to uncomment them if an rpm (or you) installed them in the
+ package controlled locations (/usr/include and /usr/lib).
+ 2. Use the following global settings to configure DMARC:
+ Required:
+ dmarc_tld_file      Defines the location of a text file of valid
+                     top level domains the opendmarc library uses
+                     during domain parsing. Maintained by Mozilla,
+                     the most current version can be downloaded
+                     from a link at http://publicsuffix.org/list/.
+ Optional:
+ dmarc_history_file  Defines the location of a file to log results
+                     of dmarc verification on inbound emails. The
+                     contents are importable by the opendmarc tools
+                     which will manage the data, send out DMARC
+                     reports, and expire the data. Make sure the
+                     directory of this file is writable by the user
+                     exim runs as.
+ dmarc_forensic_sender The email address to use when sending a
+                     forensic report detailing alignment failures
+                     if a sender domain's dmarc record specifies it
+                     and you have configured Exim to send them.
+                     Default: do-not-reply@$default_hostname
+ 3. By default, the DMARC processing will run for any remote,
+ non-authenticated user.  It makes sense to only verify DMARC
+ status of messages coming from remote, untrusted sources.  You can
+ use standard conditions such as hosts, senders, etc, to decide that
+ DMARC verification should *not* be performed for them and disable
+ DMARC with a control setting:
+   control = dmarc_verify_disable
+ A DMARC record can also specify a "forensic address", which gives
+ exim an email address to submit reports about failed alignment.
+ Exim does not do this by default because in certain conditions it
+ results in unintended information leakage (what lists a user might
+ be subscribed to, etc).  You must configure exim to submit forensic
+ reports to the owner of the domain.  If the DMARC record contains a
+ forensic address and you specify the control statement below, then
+ exim will send these forensic emails.  It's also advised that you
+ configure a dmarc_forensic_sender because the default sender address
+ construction might be inadequate.
+   control = dmarc_forensic_enable
+ (AGAIN: You can choose not to send these forensic reports by simply
+ not putting the dmarc_forensic_enable control line at any point in
+ your exim config.  If you don't tell it to send them, it will not
+ send them.)
+ There are no options to either control.  Both must appear before
+ the DATA acl.
+ 4. You can now run DMARC checks in incoming SMTP by using the
+ "dmarc_status" ACL condition in the DATA ACL.  You are required to
+ call the spf condition first in the ACLs, then the "dmarc_status"
+ condition.  Putting this condition in the ACLs is required in order
+ for a DMARC check to actually occur.  All of the variables are set
+ up before the DATA ACL, but there is no actual DMARC check that
+ occurs until a "dmarc_status" condition is encountered in the ACLs.
+ The dmarc_status condition takes a list of strings on its
+ right-hand side.  These strings describe recommended action based
+ on the DMARC check.  To understand what the policy recommendations
+ mean, refer to the DMARC website above.  Valid strings are:
+   o accept      The DMARC check passed and the library recommends
+                 accepting the email.
+   o reject      The DMARC check failed and the library recommends
+                 rejecting the email.
+   o quarantine  The DMARC check failed and the library recommends
+                 keeping it for further inspection.
+   o norecord    No policy section in the DMARC record for this
+                 sender domain.
+   o nofrom      Unable to determine the domain of the sender.
+   o none        There is no DMARC record for this sender domain.
+   o error       Library error or dns error.
+ You can prefix each string with an exclamation mark to invert its
+ meaning, for example "!accept" will match all results but
+ "accept".  The string list is evaluated left-to-right in a
+ short-circuit fashion.  When a string matches the outcome of the
+ DMARC check, the condition succeeds.  If none of the listed
+ strings matches the outcome of the DMARC check, the condition
+ fails.
+ Of course, you can also use any other lookup method that Exim
+ supports, including LDAP, Postgres, MySQL, etc, as long as the
+ result is a list of colon-separated strings;
+ Several expansion variables are set before the DATA ACL is
+ processed, and you can use them in this ACL.  The following
+ expansion variables are available:
+   o $dmarc_status
+     This is a one word status indicating what the DMARC library
+     thinks of the email.
+   o $dmarc_status_text
+     This is a slightly longer, human readable status.
+   o $dmarc_used_domain
+     This is the domain which DMARC used to look up the DMARC
+     policy record.
+   o $dmarc_ar_header
+     This is the entire Authentication-Results header which you can
+     add using an add_header modifier.
+ 5. How to enable DMARC advanced operation:
+ By default, Exim's DMARC configuration is intended to be
+ non-intrusive and conservative.  To facilitate this, Exim will not
+ create any type of logging files without explicit configuration by
+ you, the admin.  Nor will Exim send out any emails/reports about
+ DMARC issues without explicit configuration by you, the admin (other
+ than typical bounce messages that may come about due to ACL
+ processing or failure delivery issues).
+ In order to log statistics suitable to be imported by the opendmarc
+ tools, you need to:
+ a. Configure the global setting dmarc_history_file.
+ b. Configure cron jobs to call the appropriate opendmarc history
+    import scripts and truncating the dmarc_history_file.
+ In order to send forensic reports, you need to:
+ a. Configure the global setting dmarc_forensic_sender.
+ b. Configure, somewhere before the DATA ACL, the control option to
+    enable sending DMARC forensic reports.
+ 6. Example usage:
+ (RCPT ACL)
+   warn    domains        = +local_domains
+           hosts          = +local_hosts
+           control        = dmarc_verify_disable
+   warn    !domains       = +screwed_up_dmarc_records
+           control        = dmarc_enable_forensic
+ (DATA ACL)
+   warn    dmarc_status   = accept : none : off
+           !authenticated = *
+           log_message    = DMARC DEBUG: $dmarc_status $dmarc_used_domain
+           add_header     = $dmarc_ar_header
+   warn    dmarc_status   = !accept
+           !authenticated = *
+           log_message    = DMARC DEBUG: '$dmarc_status' for $dmarc_used_domain
+   warn    dmarc_status   = quarantine
+           !authenticated = *
+           set $acl_m_quarantine = 1
+           # Do something in a transport with this flag variable
+   deny    dmarc_status   = reject
+           !authenticated = *
+           message        = Message from $domain_used_domain failed sender's DMARC policy, REJECT
 +DBL (Database Logging)
 +--------------------------------------------------------------
 +
 +This feature allows to write exim internal log information
 +(not available otherwise) into a database. 
 +Initially implemented is logging of details about successfully
 +completed remote deliveries, which are needed for reputation
 +systems, and deferrals caused by a host error.
 +
 +In order to use DBL, you must set
 +
 +EXPERIMENTAL_DBL=yes
 +
 +in your Local/Makefile
 +
 +and define the database queries in the runtime config file, to
 +be executed at end of delivery.
 +
 +Additionally, there are 8 more variables, available at end of
 +delivery:
 +
 +dbl_delivery_ip             IP of host, which has accepted delivery
 +dbl_delivery_port           Port of remote host which has accepted delivery
 +dbl_delivery_fqdn           FQDN of host, which has accepted delivery
 +dbl_delivery_local_part     local part of address being delivered
 +dbl_delivery_domain         domain part of address being delivered
 +dbl_delivery_confirmation   SMTP confirmation message
 +
 +In case of a deferral caused by a host-error:
 +dbl_defer_errno             Error number
 +dbl_defer_errstr            Error string possibly containing more details
 +
 +
 +To log successful deliveries, set the following option in the main
 +option part of runtime config.
 +
 +dbl_delivery_query
 +
 +An example might look like:
 +
 +dbl_delivery_query = \
 +${lookup pgsql {SELECT * FROM record_Delivery( \
 +    '${quote_pgsql:$sender_address_domain}',\
 +    '${quote_pgsql:${lc:$sender_address_local_part}}', \
 +    '${quote_pgsql:$dbl_delivery_domain}', \
 +    '${quote_pgsql:${lc:$dbl_delivery_local_part}}', \
 +    '${quote_pgsql:$dbl_delivery_ip}', \
 +    '${quote_pgsql:${lc:$dbl_delivery_fqdn}}', \
 +    '${quote_pgsql:$message_exim_id}')}}
 +
 +
 +In order to log host deferrals, add the following option to an SMTP
 +transport:
 +
 +dbl_host_defer_query
 +
 +This is a private option of the SMTP transport. It is intended to
 +log failures of remote hosts. It is executed only when exim has
 +attempted to deliver a message to a remote host and failed due to
 +an error which doesn't seem to be related to the individual
 +message, sender, or recipient address.
 +See section 45.2 of the exim documentation for more details on how
 +this is determined.
 +
 +Example:
 +
 +dbl_host_defer_query = \
 +${lookup mysql {insert into delivlog set \
 +    msgid = '${quote_mysql:$message_exim_id}', \
 +    senderlp = '${quote_mysql:${lc:$sender_address_local_part}}', \
 +    senderdom = '${quote_mysql:$sender_address_domain}', \
 +    delivlp = '${quote_mysql:${lc:$dbl_delivery_local_part}}', \
 +    delivdom = '${quote_mysql:$dbl_delivery_domain}', \
 +    delivip = '${quote_mysql:$dbl_delivery_ip}', \
 +    delivport = '${quote_mysql:$dbl_delivery_port}', \
 +    delivfqdn = '${quote_mysql:$dbl_delivery_fqdn}', \
 +    deliverrno = '${quote_mysql:$dbl_defer_errno}', \
 +    deliverrstr = '${quote_mysql:$dbl_defer_errstr}' \
 +    }}
  
  --------------------------------------------------------------
  End of file
diff --combined src/src/EDITME
index 1387b980aa2ea278538da5a112f7312af3fa9030,e29c1eb25e11ffe47f1af6750af24998d588e4d5..637256c7ddf518b330ce0cf2ab812c86c1f97852
@@@ -411,6 -411,14 +411,14 @@@ EXIM_MONITOR=eximon.bi
  # DISABLE_DKIM=yes
  
  
+ #------------------------------------------------------------------------------
+ # By default, Exim has support for checking the AD bit in a DNS response, to
+ # determine if DNSSEC validation was successful.  If your system libraries
+ # do not support that bit, then set DISABLE_DNSSEC to "yes"
+ # DISABLE_DNSSEC=yes
  #------------------------------------------------------------------------------
  # Compiling Exim with experimental features. These are documented in
  # experimental-spec.txt. "Experimental" means that the way these features are
  
  # EXPERIMENTAL_OCSP=yes
  
+ # Uncomment the following line to add DMARC checking capability, implemented
+ # using libopendmarc libraries.
+ # EXPERIMENTAL_DMARC=yes
+ # CFLAGS += -I/usr/local/include
+ # LDFLAGS += -lopendmarc
+ # Uncomment the following line to add Per-Recipient-Data-Response support.
+ # EXPERIMENTAL_PRDR=yes
 +# This feature allows to write log information about a completed
 +#  delivery into a database
 +# EXPERIMENTAL_DBL=yes
 +
  ###############################################################################
  #                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
  ###############################################################################
index 1652c784254178a8ae7f46a9a730412f542563b1,1594f6858d95978a1deab0fdcb70af000b07dd5f..bcbc70b389d9fc8d9a6aa4fee876bc29de8a5f13
@@@ -41,6 -41,7 +41,7 @@@ it's a default value. *
  #define DELIVER_IN_BUFFER_SIZE     8192
  #define DELIVER_OUT_BUFFER_SIZE    8192
  #define DISABLE_DKIM
+ #define DISABLE_DNSSEC
  #define DISABLE_D_OPTION
  
  #define ENABLE_DISABLE_FSYNC
@@@ -49,7 -50,8 +50,8 @@@
  #define EXIMDB_LOCK_TIMEOUT          60
  #define EXIMDB_LOCKFILE_MODE       0640
  #define EXIMDB_MODE                0640
- #define EXIM_CLIENT_DH_MIN_BITS
+ #define EXIM_CLIENT_DH_MIN_MIN_BITS         512
+ #define EXIM_CLIENT_DH_DEFAULT_MIN_BITS    1024
  #define EXIM_GNUTLS_LIBRARY_LOG_LEVEL
  #define EXIM_SERVER_DH_BITS_PRE2_12
  #define EXIM_PERL
  /* EXPERIMENTAL features */
  #define EXPERIMENTAL_BRIGHTMAIL
  #define EXPERIMENTAL_DCC
+ #define EXPERIMENTAL_DMARC
  #define EXPERIMENTAL_OCSP
+ #define EXPERIMENTAL_PRDR
  #define EXPERIMENTAL_SPF
  #define EXPERIMENTAL_SRS
 +#define EXPERIMENTAL_DBL
  
  /* For developers */
  #define WANT_DEEPER_PRINTF_CHECKS
@@@ -179,18 -182,13 +183,13 @@@ just in case. *
  #define ROOT_UID                      0
  #define ROOT_GID                      0
  
- /* Sizes for integer arithmetic.  Go for 64bit; can be overridden in OS/os.h-FOO */
- #ifndef int_eximarith_t
-  #define int_eximarith_t int64_t
- #endif
- #ifndef PR_EXIM_ARITH
-  #define PR_EXIM_ARITH "%" PRId64             /* C99 standard, printf %lld */
- #endif
- #ifndef SC_EXIM_ARITH
-  #define SC_EXIM_ARITH "%" SCNi64             /* scanf incl. 0x prefix */
- #endif
- #ifndef SC_EXIM_DEC
-  #define SC_EXIM_DEC   "%" SCNd64             /* scanf decimal */
- #endif
+ /* Sizes for integer arithmetic.
+ Go for 64bit; can be overridden in OS/Makefile-FOO
+ If you make it a different number of bits, provide a definition
+ for EXIM_64B_MAX and _MIN in OS/oh.h-FOO */
+ #define int_eximarith_t int64_t
+ #define PR_EXIM_ARITH "%" PRId64              /* C99 standard, printf %lld */
+ #define SC_EXIM_ARITH "%" SCNi64              /* scanf incl. 0x prefix */
+ #define SC_EXIM_DEC   "%" SCNd64              /* scanf decimal */
  
  /* End of config.h.defaults */
diff --combined src/src/deliver.c
index 1c3c4bafd392b002789fa57d625e0d2c8c4845e5,23e63d553d188426538ad0e826a0b72d090812db..7ff25f22aa33231a1f7351bb0dc218e509001a84
@@@ -673,6 -673,166 +673,177 @@@ while (addr->parent != NULL
  
  
  
+ /* If msg is NULL this is a delivery log and logchar is used. Otherwise
+ this is a nonstandard call; no two-characher delivery flag is written
+ but sender-host and sender are prefixed and "msg" is inserted in the log line.
+ Arguments:
+   flags               passed to log_write()
+ */
+ 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.  */
+ /* 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
+ have a pointer to the host item that succeeded; local deliveries can have a
+ pointer to a single host item in their host list, for use by the transport. */
+ s = reset_point = store_get(size);
+ log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
+ if (msg)
+   s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
+ else
+   {
+   s[ptr++] = logchar;
+   s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+   }
+ if ((log_extra_selector & LX_sender_on_delivery) != 0  ||  msg)
+   s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+ #ifdef EXPERIMENTAL_SRS
+ if(addr->p.srs_sender)
+   s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
+ #endif
++#ifdef EXPERIMENTAL_DBL
++  dbl_delivery_ip = NULL;     /* presume no successful remote delivery */
++#endif
+ /* You might think that the return path must always be set for a successful
+ delivery; indeed, I did for some time, until this statement crashed. The case
+ when it is not set is for a delivery to /dev/null which is optimised by not
+ being run at all. */
+ if (used_return_path != NULL &&
+       (log_extra_selector & LX_return_path_on_delivery) != 0)
+   s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+ if (msg)
+   s = string_append(s, &size, &ptr, 2, US" ", msg);
+ /* For a delivery from a system filter, there may not be a router */
+ if (addr->router != NULL)
+   s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+ s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+ if ((log_extra_selector & LX_delivery_size) != 0)
+   s = string_append(s, &size, &ptr, 2, US" S=",
+     string_sprintf("%d", transport_count));
+ /* Local delivery */
+ if (addr->transport->info->local)
+   {
+   if (addr->host_list != NULL)
+     s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+   if (addr->shadow_message != NULL)
+     s = string_cat(s, &size, &ptr, addr->shadow_message,
+       Ustrlen(addr->shadow_message));
+   }
+ /* Remote delivery */
+ else
+   {
+   if (addr->host_used != NULL)
+     {
+     s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name,
+       US" [", addr->host_used->address, US"]");
+     if ((log_extra_selector & LX_outgoing_port) != 0)
+       s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d",
+         addr->host_used->port));
+     if (continue_sequence > 1)
+       s = string_cat(s, &size, &ptr, US"*", 1);
+     }
+   #ifdef SUPPORT_TLS
+   if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL)
+     s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher);
+   if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
+        addr->cipher != NULL)
+     s = string_append(s, &size, &ptr, 2, US" CV=",
+       testflag(addr, af_cert_verified)? "yes":"no");
+   if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
+     s = string_append(s, &size, &ptr, 3, US" DN=\"",
+       string_printing(addr->peerdn), US"\"");
+   #endif
+   if (addr->authenticator)
+     {
+     s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+     if (addr->auth_id)
+       {
+       s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
+       if (log_extra_selector & LX_smtp_mailauth  &&  addr->auth_sndr)
+         s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+       }
+     }
+   #ifdef EXPERIMENTAL_PRDR
+   if (addr->flags & af_prdr_used)
+     s = string_append(s, &size, &ptr, 1, US" PRDR");
+   #endif
+   if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
+       addr->message != NULL)
+     {
+     int i;
+     uschar *p = big_buffer;
+     uschar *ss = addr->message;
+     *p++ = '\"';
+     for (i = 0; i < 100 && ss[i] != 0; i++)
+       {
+       if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
+       *p++ = ss[i];
+       }
+     *p++ = '\"';
+     *p = 0;
+     s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+     }
+   }
+ /* Time on queue and actual time taken to deliver */
+ if ((log_extra_selector & LX_queue_time) != 0)
+   {
+   s = string_append(s, &size, &ptr, 2, US" QT=",
+     readconf_printtime(time(NULL) - received_time));
+   }
+ if ((log_extra_selector & LX_deliver_time) != 0)
+   {
+   s = string_append(s, &size, &ptr, 2, US" DT=",
+     readconf_printtime(addr->more_errno));
+   }
+ /* string_cat() always leaves room for the terminator. Release the
+ store we used to build the line after writing it. */
+ s[ptr] = 0;
+ log_write(0, flags, "%s", s);
++#ifdef EXPERIMENTAL_DBL
++DEBUG(D_deliver)
++  {
++  debug_printf("  DBL(Delivery): dbl_delivery_query=|%s| dbl_delivery_IP=%s\n", dbl_delivery_query, dbl_delivery_ip);
++  }
++if (dbl_delivery_ip != NULL && dbl_delivery_query != NULL)
++  expand_string(dbl_delivery_query);
++#endif
+ store_reset(reset_point);
+ return;
+ }
  /*************************************************
  *    Actions at the end of handling an address   *
  *************************************************/
@@@ -835,12 -995,6 +1006,6 @@@ if (addr->return_file >= 0 && addr->ret
    (void)close(addr->return_file);
    }
  
- /* 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,
-   (log_write_selector & L_all_parents) != 0, result == OK);
  /* The sucess case happens only after delivery by a transport. */
  
  if (result == OK)
      child_done(addr, now);
      }
  
-   /* 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
-   have a pointer to the host item that succeeded; local deliveries can have a
-   pointer to a single host item in their host list, for use by the transport. */
-   s = reset_point = store_get(size);
-   s[ptr++] = logchar;
-   s = string_append(s, &size, &ptr, 2, US"> ", log_address);
-   if ((log_extra_selector & LX_sender_on_delivery) != 0)
-     s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
-   #ifdef EXPERIMENTAL_SRS
-   if(addr->p.srs_sender)
-     s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
-   #endif
-   #ifdef EXPERIMENTAL_DBL
-   dbl_delivery_ip = NULL;     /* presume no successful remote delivery */
-   #endif
-   /* You might think that the return path must always be set for a successful
-   delivery; indeed, I did for some time, until this statement crashed. The case
-   when it is not set is for a delivery to /dev/null which is optimised by not
-   being run at all. */
-   if (used_return_path != NULL &&
-         (log_extra_selector & LX_return_path_on_delivery) != 0)
-     s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
-   /* For a delivery from a system filter, there may not be a router */
-   if (addr->router != NULL)
-     s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
-   s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
-   if ((log_extra_selector & LX_delivery_size) != 0)
-     s = string_append(s, &size, &ptr, 2, US" S=",
-       string_sprintf("%d", transport_count));
-   /* Local delivery */
-   if (addr->transport->info->local)
-     {
-     if (addr->host_list != NULL)
-       s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
-     if (addr->shadow_message != NULL)
-       s = string_cat(s, &size, &ptr, addr->shadow_message,
-         Ustrlen(addr->shadow_message));
-     }
-   /* Remote delivery */
-   else
-     {
-     if (addr->host_used != NULL)
-       {
-       s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name,
-         US" [", addr->host_used->address, US"]");
-       if ((log_extra_selector & LX_outgoing_port) != 0)
-         s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d",
-           addr->host_used->port));
-       if (continue_sequence > 1)
-         s = string_cat(s, &size, &ptr, US"*", 1);
-       #ifdef EXPERIMENTAL_DBL
-       dbl_delivery_ip = string_copy(addr->host_used->address);
-       dbl_delivery_port = addr->host_used->port;
-       dbl_delivery_fqdn = string_copy(addr->host_used->name);
-       dbl_delivery_local_part = string_copy(addr->local_part);
-       dbl_delivery_domain = string_copy(addr->domain);
-       dbl_delivery_confirmation = string_copy(addr->message);
-       #endif
-       }
-     #ifdef SUPPORT_TLS
-     if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL)
-       s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher);
-     if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-          addr->cipher != NULL)
-       s = string_append(s, &size, &ptr, 2, US" CV=",
-         testflag(addr, af_cert_verified)? "yes":"no");
-     if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
-       s = string_append(s, &size, &ptr, 3, US" DN=\"",
-         string_printing(addr->peerdn), US"\"");
-     #endif
-     if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
-         addr->message != NULL)
-       {
-       int i;
-       uschar *p = big_buffer;
-       uschar *ss = addr->message;
-       *p++ = '\"';
-       for (i = 0; i < 100 && ss[i] != 0; i++)
-         {
-         if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
-         *p++ = ss[i];
-         }
-       *p++ = '\"';
-       *p = 0;
-       s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
-       }
-     }
-   /* Time on queue and actual time taken to deliver */
-   if ((log_extra_selector & LX_queue_time) != 0)
-     {
-     s = string_append(s, &size, &ptr, 2, US" QT=",
-       readconf_printtime(time(NULL) - received_time));
-     }
-   if ((log_extra_selector & LX_deliver_time) != 0)
-     {
-     s = string_append(s, &size, &ptr, 2, US" DT=",
-       readconf_printtime(addr->more_errno));
-     }
-   /* string_cat() always leaves room for the terminator. Release the
-   store we used to build the line after writing it. */
-   s[ptr] = 0;
-   log_write(0, LOG_MAIN, "%s", s);
-   #ifdef EXPERIMENTAL_DBL
-   DEBUG(D_deliver)
-     {
-     debug_printf("  DBL(Delivery): dbl_delivery_query=|%s| dbl_delivery_IP=%s\n", dbl_delivery_query, dbl_delivery_ip);
-       }
-   if (dbl_delivery_ip != NULL && dbl_delivery_query != NULL)
-       expand_string(dbl_delivery_query);
-   #endif
-   store_reset(reset_point);
+   delivery_log(LOG_MAIN, addr, logchar, NULL);
    }
  
  
@@@ -1047,6 -1070,13 +1081,13 @@@ else if (result == DEFER || result == P
      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,
+       (log_write_selector & L_all_parents) != 0, result == OK);
      s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
  
      /* Either driver_name contains something and driver_kind contains
    /* 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,
+     (log_write_selector & L_all_parents) != 0, result == OK);
    s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
  
    if ((log_extra_selector & LX_sender_on_delivery) != 0)
@@@ -1839,6 -1876,9 +1887,9 @@@ if ((pid = fork()) == 0
      set_process_info("delivering %s to %s using %s", message_id,
       addr->local_part, addr->transport->name);
  
+     /* Setting this global in the subprocess means we need never clear it */
+     transport_name = addr->transport->name;
      /* If a transport filter has been specified, set up its argument list.
      Any errors will get put into the address, and FALSE yielded. */
  
      int i;
      int local_part_length = Ustrlen(addr2->local_part);
      uschar *s;
+     int ret;
  
-     (void)write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int));
-     (void)write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count));
-     (void)write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags));
-     (void)write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int));
-     (void)write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int));
-     (void)write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int));
-     (void)write(pfd[pipe_write], (void *)&(addr2->transport),
-       sizeof(transport_instance *));
+     if(  (ret = write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int))) != sizeof(int)
+       || (ret = write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count))) != sizeof(transport_count)
+       || (ret = write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags))) != sizeof(addr2->flags)
+       || (ret = write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int))) != sizeof(int)
+       || (ret = write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int))) != sizeof(int)
+       || (ret = write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int))) != sizeof(int)
+       || (ret = write(pfd[pipe_write], (void *)&(addr2->transport),
+         sizeof(transport_instance *))) != sizeof(transport_instance *)
  
      /* For a file delivery, pass back the local part, in case the original
      was only part of the final delivery path. This gives more complete
      logging. */
  
-     if (testflag(addr2, af_file))
-       {
-       (void)write(pfd[pipe_write], (void *)&local_part_length, sizeof(int));
-       (void)write(pfd[pipe_write], addr2->local_part, local_part_length);
-       }
+       || (testflag(addr2, af_file)
+           && (  (ret = write(pfd[pipe_write], (void *)&local_part_length, sizeof(int))) != sizeof(int)
+              || (ret = write(pfd[pipe_write], addr2->local_part, local_part_length)) != local_part_length
+            )
+        )
+       )
+       log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+       ret == -1 ? strerror(errno) : "short write");
  
      /* Now any messages */
  
      for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
        {
        int message_length = (s == NULL)? 0 : Ustrlen(s) + 1;
-       (void)write(pfd[pipe_write], (void *)&message_length, sizeof(int));
-       if (message_length > 0) (void)write(pfd[pipe_write], s, message_length);
+       if(  (ret = write(pfd[pipe_write], (void *)&message_length, sizeof(int))) != sizeof(int)
+         || (message_length > 0  && (ret = write(pfd[pipe_write], s, message_length)) != message_length)
+       )
+         log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+         ret == -1 ? strerror(errno) : "short write");
        }
      }
  
@@@ -2347,45 -2394,8 +2405,8 @@@ while (addr_local != NULL
            to do, which is for the ultimate address timeout. */
  
            if (!ok)
-             {
-             retry_config *retry =
-               retry_find_config(retry_key+2, addr2->domain,
-                 retry_record->basic_errno,
-                 retry_record->more_errno);
-             DEBUG(D_deliver|D_retry)
-               {
-               debug_printf("retry time not reached for %s: "
-                 "checking ultimate address timeout\n", addr2->address);
-               debug_printf("  now=%d first_failed=%d next_try=%d expired=%d\n",
-                 (int)now, (int)retry_record->first_failed,
-                 (int)retry_record->next_try, retry_record->expired);
-               }
-             if (retry != NULL && retry->rules != NULL)
-               {
-               retry_rule *last_rule;
-               for (last_rule = retry->rules;
-                    last_rule->next != NULL;
-                    last_rule = last_rule->next);
-               DEBUG(D_deliver|D_retry)
-                 debug_printf("  received_time=%d diff=%d timeout=%d\n",
-                   received_time, (int)now - received_time, last_rule->timeout);
-               if (now - received_time > last_rule->timeout) ok = TRUE;
-               }
-             else
-               {
-               DEBUG(D_deliver|D_retry)
-                 debug_printf("no retry rule found: assume timed out\n");
-               ok = TRUE;    /* No rule => timed out */
-               }
-             DEBUG(D_deliver|D_retry)
-               {
-               if (ok) debug_printf("on queue longer than maximum retry for "
-                 "address - allowing delivery\n");
-               }
-             }
+             ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
+                 retry_record, now);
            }
          }
        else DEBUG(D_retry) debug_printf("no retry record exists\n");
@@@ -2892,6 -2902,27 +2913,27 @@@ while (!done
      break;
      #endif
  
+     case 'C': /* client authenticator information */
+     switch (*ptr++)
+     {
+     case '1':
+       addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
+       break;
+     case '2':
+       addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
+       break;
+     case '3':
+       addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
+       break;
+     }
+     while (*ptr++);
+     break;
+ #ifdef EXPERIMENTAL_PRDR
+     case 'P':
+       addr->flags |= af_prdr_used; break;
+ #endif
      case 'A':
      if (addr == NULL)
        {
@@@ -3401,6 -3432,15 +3443,15 @@@ while (parcount > max
  
  
  
+ static void
+ rmt_dlv_checked_write(int fd, void * buf, int size)
+ {
+ int ret = write(fd, buf, size);
+ if(ret != size)
+   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
+     ret == -1 ? strerror(errno) : "short write");
+ }
  /*************************************************
  *           Do remote deliveries                 *
  *************************************************/
@@@ -3633,6 -3673,9 +3684,9 @@@ for (delivery_count = 0; addr_remote !
  
    deliver_set_expansions(addr);
  
+   /* Ensure any transport-set auth info is fresh */
+   addr->authenticator = addr->auth_id = addr->auth_sndr = NULL;
    /* Compute the return path, expanding a new one if required. The old one
    must be set first, as it might be referred to in the expansion. */
  
      int fd = pfd[pipe_write];
      host_item *h;
  
-     /* There are weird circumstances in which logging is disabled */
+     /* Setting this global in the subprocess means we need never clear it */
+     transport_name = tp->name;
  
+     /* There are weird circumstances in which logging is disabled */
      disable_logging = tp->disable_logging;
  
      /* Show pids on debug output if parallelism possible */
        {
        if (h->address == NULL || h->status < hstatus_unusable) continue;
        sprintf(CS big_buffer, "H%c%c%s", h->status, h->why, h->address);
-       (void)write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
+       rmt_dlv_checked_write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
        }
  
      /* The number of bytes written. This is the same for each address. Even
  
      big_buffer[0] = 'S';
      memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
-     (void)write(fd, big_buffer, sizeof(transport_count) + 1);
+     rmt_dlv_checked_write(fd, big_buffer, sizeof(transport_count) + 1);
  
-     /* Information about what happened to each address. Three item types are
-     used: an optional 'X' item first, for TLS information, followed by 'R'
-     items for any retry settings, and finally an 'A' item for the remaining
-     data. */
+     /* Information about what happened to each address. Four item types are
+     used: an optional 'X' item first, for TLS information, then an optional "C"
+     item for any client-auth info followed by 'R' items for any retry settings,
+     and finally an 'A' item for the remaining data. */
  
      for(; addr != NULL; addr = addr->next)
        {
  
        /* The certificate verification status goes into the flags */
  
-       if (tls_certificate_verified) setflag(addr, af_cert_verified);
+       if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
  
        /* Use an X item only if there's something to send */
  
        if (addr->cipher != NULL)
          {
          ptr = big_buffer;
-         *ptr++ = 'X';
-         sprintf(CS ptr, "%.128s", addr->cipher);
+         sprintf(CS ptr, "X%.128s", addr->cipher);
          while(*ptr++);
          if (addr->peerdn == NULL) *ptr++ = 0; else
            {
            sprintf(CS ptr, "%.512s", addr->peerdn);
            while(*ptr++);
            }
-         (void)write(fd, big_buffer, ptr - big_buffer);
+         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
          }
        #endif
  
+       if (client_authenticator)
+         {
+         ptr = big_buffer;
+       sprintf(CS big_buffer, "C1%.64s", client_authenticator);
+         while(*ptr++);
+         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+       if (client_authenticated_id)
+         {
+         ptr = big_buffer;
+       sprintf(CS big_buffer, "C2%.64s", client_authenticated_id);
+         while(*ptr++);
+         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+       if (client_authenticated_sender)
+         {
+         ptr = big_buffer;
+       sprintf(CS big_buffer, "C3%.64s", client_authenticated_sender);
+         while(*ptr++);
+         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+       #ifdef EXPERIMENTAL_PRDR
+       if (addr->flags & af_prdr_used) rmt_dlv_checked_write(fd, "P", 1);
+       #endif
        /* Retry information: for most success cases this will be null. */
  
        for (r = addr->retries; r != NULL; r = r->next)
            sprintf(CS ptr, "%.512s", r->message);
            while(*ptr++);
            }
-         (void)write(fd, big_buffer, ptr - big_buffer);
+         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
          }
  
        /* The rest of the information goes in an 'A' item. */
          memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
          ptr += sizeof(addr->host_used->port);
          }
-       (void)write(fd, big_buffer, ptr - big_buffer);
+       rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
        }
  
      /* Add termination flag, close the pipe, and that's it. The character
  
      big_buffer[0] = 'Z';
      big_buffer[1] = (continue_transport == NULL)? '0' : '1';
-     (void)write(fd, big_buffer, 2);
+     rmt_dlv_checked_write(fd, big_buffer, 2);
      (void)close(fd);
      exit(EXIT_SUCCESS);
      }
@@@ -4494,6 -4564,7 +4575,7 @@@ FILE *jread
  int process_recipients = RECIP_ACCEPT;
  open_db dbblock;
  open_db *dbm_file;
+ extern int acl_where;
  
  uschar *info = (queue_run_pid == (pid_t)0)?
    string_sprintf("delivering %s", id) :
@@@ -4544,6 -4615,9 +4626,9 @@@ message_size = 0
  update_spool = FALSE;
  remove_journal = TRUE;
  
+ /* Set a known context for any ACLs we call via expansions */
+ acl_where = ACL_WHERE_DELIVERY;
  /* Reset the random number generator, so that if several delivery processes are
  started from a queue runner that has already used random numbers (for sorting),
  they don't all get the same sequence. */
@@@ -5564,7 -5638,18 +5649,18 @@@ while (addr_new != NULL)           /* L
      will be far too many attempts for an address that gets a 4xx error. In
      fact, after such an error, we should not get here because, the host should
      not be remembered as one this message needs. However, there was a bug that
-     used to cause this to  happen, so it is best to be on the safe side. */
+     used to cause this to  happen, so it is best to be on the safe side.
+     Even if we haven't reached the retry time in the hints, there is one more
+     check to do, which is for the ultimate address timeout. We only do this
+     check if there is an address retry record and there is not a domain retry
+     record; this implies that previous attempts to handle the address had the
+     retry_use_local_parts option turned on. We use this as an approximation
+     for the destination being like a local delivery, for example delivery over
+     LMTP to an IMAP message store. In this situation users are liable to bump
+     into their quota and thereby have intermittently successful deliveries,
+     which keep the retry record fresh, which can lead to us perpetually
+     deferring messages. */
  
      else if (((queue_running && !deliver_force) || continue_hostname != NULL)
              &&
              ||
              (address_retry_record != NULL &&
                now < address_retry_record->next_try))
-             )
+             &&
+           (domain_retry_record != NULL ||
+            address_retry_record == NULL ||
+            !retry_ultimate_address_timeout(addr->address_retry_key,
+              addr->domain, address_retry_record, now)))
        {
        addr->message = US"retry time not reached";
        addr->basic_errno = ERRNO_RRETRY;
@@@ -5946,12 -6035,23 +6046,23 @@@ if (addr_local != NULL || addr_remote !
    that the mode is correct - the group setting doesn't always seem to get
    set automatically. */
  
-   (void)fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC);
-   (void)fchown(journal_fd, exim_uid, exim_gid);
-   (void)fchmod(journal_fd, SPOOL_MODE);
+   if(  fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
+     || fchown(journal_fd, exim_uid, exim_gid)
+     || fchmod(journal_fd, SPOOL_MODE)
+     )
+     {
+     int ret = Uunlink(spoolname);
+     log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
+       spoolname, strerror(errno));
+     if(ret  &&  errno != ENOENT)
+       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+         spoolname, strerror(errno));
+     return DELIVER_NOT_ATTEMPTED;
+     }
    }
  
  
  /* Now we can get down to the business of actually doing deliveries. Local
  deliveries are done first, then remote ones. If ever the problems of how to
  handle fallback transports are figured out, this section can be put into a loop
@@@ -6015,6 -6115,11 +6126,11 @@@ if (addr_remote != NULL
      regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
    #endif
  
+   #ifdef EXPERIMENTAL_PRDR
+   if (regex_PRDR == NULL) regex_PRDR =
+     regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+   #endif
    /* Now sort the addresses if required, and do the deliveries. The yield of
    do_remote_deliveries is FALSE when mua_wrapper is set and all addresses
    cannot be delivered in one transaction. */
@@@ -7013,6 -7118,7 +7129,7 @@@ expand_check_condition) to do a lookup
  released. */
  
  search_tidyup();
+ acl_where = ACL_WHERE_UNKNOWN;
  return final_yield;
  }
  
diff --combined src/src/exim.c
index 587e136856693f36012e6a0246f114765194b9a9,a27e391d1b1e40cf98e8706a6c41090c2e845090..1e4d6d64611fde419c9045503c34bed52576bafe
@@@ -52,6 -52,16 +52,16 @@@ store_free(block)
  
  
  
+ /*************************************************
+ *         Enums for cmdline interface            *
+ *************************************************/
+ enum commandline_info { CMDINFO_NONE=0,
+   CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP };
  /*************************************************
  *  Compile regular expression and panic on fail  *
  *************************************************/
@@@ -212,7 -222,7 +222,7 @@@ to disrupt whatever is going on outsid
  
  if (fd < 0) return;
  
- (void)write(fd, process_info, process_info_len);
+ {int dummy = write(fd, process_info, process_info_len); dummy = dummy; }
  (void)close(fd);
  }
  
@@@ -516,7 -526,7 +526,7 @@@ close_unwanted(void
  if (smtp_input)
    {
    #ifdef SUPPORT_TLS
-   tls_close(FALSE);      /* Shut down the TLS library */
+   tls_close(FALSE, FALSE);      /* Shut down the TLS library */
    #endif
    (void)close(fileno(smtp_in));
    (void)close(fileno(smtp_out));
@@@ -806,9 -816,15 +816,18 @@@ fprintf(f, "Support for:")
  #ifdef EXPERIMENTAL_DCC
    fprintf(f, " Experimental_DCC");
  #endif
+ #ifdef EXPERIMENTAL_DMARC
+   fprintf(f, " Experimental_DMARC");
+ #endif
+ #ifdef EXPERIMENTAL_OCSP
+   fprintf(f, " Experimental_OCSP");
+ #endif
+ #ifdef EXPERIMENTAL_PRDR
+   fprintf(f, " Experimental_PRDR");
+ #endif
 +#ifdef EXPERIMENTAL_DBL
 +  fprintf(f, " EXPERIMENTAL_DBL");
 +#endif
  fprintf(f, "\n");
  
  fprintf(f, "Lookups (built-in):");
@@@ -1017,6 -1033,39 +1036,39 @@@ DEBUG(D_any) do 
  }
  
  
+ /*************************************************
+ *     Show auxiliary information about Exim      *
+ *************************************************/
+ static void
+ show_exim_information(enum commandline_info request, FILE *stream)
+ {
+ const uschar **pp;
+ switch(request)
+   {
+   case CMDINFO_NONE:
+     fprintf(stream, "Oops, something went wrong.\n");
+     return;
+   case CMDINFO_HELP:
+     fprintf(stream,
+ "The -bI: flag takes a string indicating which information to provide.\n"
+ "If the string is not recognised, you'll get this help (on stderr).\n"
+ "\n"
+ "  exim -bI:help    this information\n"
+ "  exim -bI:dscp    dscp value keywords known\n"
+ "  exim -bI:sieve   list of supported sieve extensions, one per line.\n"
+ );
+     return;
+   case CMDINFO_SIEVE:
+     for (pp = exim_sieve_extension_list; *pp; ++pp)
+       fprintf(stream, "%s\n", *pp);
+     return;
+   case CMDINFO_DSCP:
+     dscp_list_to_stream(stream);
+     return;
+   }
+ }
  
  
  /*************************************************
@@@ -1394,6 -1443,8 +1446,8 @@@ BOOL checking = FALSE
  BOOL count_queue = FALSE;
  BOOL expansion_test = FALSE;
  BOOL extract_recipients = FALSE;
+ BOOL flag_G = FALSE;
+ BOOL flag_n = FALSE;
  BOOL forced_delivery = FALSE;
  BOOL f_end_dot = FALSE;
  BOOL deliver_give_up = FALSE;
@@@ -1414,6 -1465,7 +1468,7 @@@ BOOL verify_as_sender = FALSE
  BOOL version_printed = FALSE;
  uschar *alias_arg = NULL;
  uschar *called_as = US"";
+ uschar *cmdline_syslog_name = NULL;
  uschar *start_queue_run_id = NULL;
  uschar *stop_queue_run_id = NULL;
  uschar *expansion_test_message = NULL;
@@@ -1424,6 -1476,7 +1479,7 @@@ uschar *ftest_suffix = NULL
  uschar *malware_test_file = NULL;
  uschar *real_sender_address;
  uschar *originator_home = US"/";
+ size_t sz;
  void *reset_point;
  
  struct passwd *pw;
@@@ -1432,6 -1485,10 +1488,10 @@@ pid_t passed_qr_pid = (pid_t)0
  int passed_qr_pipe = -1;
  gid_t group_list[NGROUPS_MAX];
  
+ /* For the -bI: flag */
+ enum commandline_info info_flag = CMDINFO_NONE;
+ BOOL info_stdout = FALSE;
  /* Possible options for -R and -S */
  
  static uschar *rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
@@@ -1830,6 -1887,26 +1890,26 @@@ for (i = 1; i < argc; i++
  
    switch(switchchar)
      {
+     /* sendmail uses -Ac and -Am to control which .cf file is used;
+     we ignore them. */
+     case 'A':
+     if (*argrest == '\0') { badarg = TRUE; break; }
+     else
+       {
+       BOOL ignore = FALSE;
+       switch (*argrest)
+         {
+         case 'c':
+         case 'm':
+           if (*(argrest + 1) == '\0')
+             ignore = TRUE;
+           break;
+         }
+       if (!ignore) { badarg = TRUE; break; }
+       }
+     break;
      /* -Btype is a sendmail option for 7bit/8bit setting. Exim is 8-bit clean
      so has no need of it. */
  
  
      else if (Ustrcmp(argrest, "i") == 0) bi_option = TRUE;
  
+     /* -bI: provide information, of the type to follow after a colon.
+     This is an Exim flag. */
+     else if (argrest[0] == 'I' && Ustrlen(argrest) >= 2 && argrest[1] == ':')
+       {
+       uschar *p = &argrest[2];
+       info_flag = CMDINFO_HELP;
+       if (Ustrlen(p))
+         {
+         if (strcmpic(p, CUS"sieve") == 0)
+           {
+           info_flag = CMDINFO_SIEVE;
+           info_stdout = TRUE;
+           }
+         else if (strcmpic(p, CUS"dscp") == 0)
+           {
+           info_flag = CMDINFO_DSCP;
+           info_stdout = TRUE;
+           }
+         else if (strcmpic(p, CUS"help") == 0)
+           {
+           info_stdout = TRUE;
+           }
+         }
+       }
      /* -bm: Accept and deliver message - the default option. Reinstate
      receiving_message, which got turned off for all -b options. */
  
        }
      break;
  
-     /* This is some Sendmail thing which can be ignored */
+     /* -G: sendmail invocation to specify that it's a gateway submission and
+     sendmail may complain about problems instead of fixing them.
+     We make it equivalent to an ACL "control = suppress_local_fixups" and do
+     not at this time complain about problems. */
  
      case 'G':
+     flag_G = TRUE;
      break;
  
      /* -h: Set the hop count for an incoming message. Exim does not currently
      break;
  
  
+     /* -L: set the identifier used for syslog; equivalent to setting
+     syslog_processname in the config file, but needs to be an admin option. */
+     case 'L':
+     if (*argrest == '\0')
+       {
+       if(++i < argc) argrest = argv[i]; else
+         { badarg = TRUE; break; }
+       }
+     sz = Ustrlen(argrest);
+     if (sz > 32)
+       {
+       fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
+       return EXIT_FAILURE;
+       }
+     if (sz < 1)
+       {
+       fprintf(stderr, "exim: the -L syslog name is too short\n");
+       return EXIT_FAILURE;
+       }
+     cmdline_syslog_name = argrest;
+     break;
      case 'M':
      receiving_message = FALSE;
  
      break;
  
  
-     /* -n: This means "don't alias" in sendmail, apparently. Just ignore
-     it. */
+     /* -n: This means "don't alias" in sendmail, apparently.
+     For normal invocations, it has no effect.
+     It may affect some other options. */
  
      case 'n':
+     flag_n = TRUE;
      break;
  
      /* -O: Just ignore it. In sendmail, apparently -O option=value means set
      /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
  
      #ifdef SUPPORT_TLS
-     else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_on_connect = TRUE;
+     else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_in.on_connect = TRUE;
      #endif
  
      else badarg = TRUE;
      if (*argrest != 0) badarg = TRUE;
      break;
  
+     /* -X: in sendmail: takes one parameter, logfile, and sends debugging
+     logs to that file.  We swallow the parameter and otherwise ignore it. */
+     case 'X':
+     if (*argrest == '\0')
+       {
+       if (++i >= argc)
+         {
+         fprintf(stderr, "exim: string expected after -X\n");
+         exit(EXIT_FAILURE);
+         }
+       }
+     break;
      /* All other initial characters are errors */
  
      default:
@@@ -3498,6 -3644,66 +3647,66 @@@ configuration data for delivery can be 
  
  readconf_main();
  
+ /* If an action on specific messages is requested, or if a daemon or queue
+ runner is being started, we need to know if Exim was called by an admin user.
+ This is the case if the real user is root or exim, or if the real group is
+ exim, or if one of the supplementary groups is exim or a group listed in
+ admin_groups. We don't fail all message actions immediately if not admin_user,
+ since some actions can be performed by non-admin users. Instead, set admin_user
+ for later interrogation. */
+ if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
+   admin_user = TRUE;
+ else
+   {
+   int i, j;
+   for (i = 0; i < group_count; i++)
+     {
+     if (group_list[i] == exim_gid) admin_user = TRUE;
+     else if (admin_groups != NULL)
+       {
+       for (j = 1; j <= (int)(admin_groups[0]); j++)
+         if (admin_groups[j] == group_list[i])
+           { admin_user = TRUE; break; }
+       }
+     if (admin_user) break;
+     }
+   }
+ /* Another group of privileged users are the trusted users. These are root,
+ exim, and any caller matching trusted_users or trusted_groups. Trusted callers
+ are permitted to specify sender_addresses with -f on the command line, and
+ other message parameters as well. */
+ if (real_uid == root_uid || real_uid == exim_uid)
+   trusted_caller = TRUE;
+ else
+   {
+   int i, j;
+   if (trusted_users != NULL)
+     {
+     for (i = 1; i <= (int)(trusted_users[0]); i++)
+       if (trusted_users[i] == real_uid)
+         { trusted_caller = TRUE; break; }
+     }
+   if (!trusted_caller && trusted_groups != NULL)
+     {
+     for (i = 1; i <= (int)(trusted_groups[0]); i++)
+       {
+       if (trusted_groups[i] == real_gid)
+         trusted_caller = TRUE;
+       else for (j = 0; j < group_count; j++)
+         {
+         if (trusted_groups[i] == group_list[j])
+           { trusted_caller = TRUE; break; }
+         }
+       if (trusted_caller) break;
+       }
+     }
+   }
  /* Handle the decoding of logging options. */
  
  decode_bits(&log_write_selector, &log_extra_selector, 0, 0,
@@@ -3529,6 -3735,24 +3738,24 @@@ if (sender_address != NULL
      }
    }
  
+ /* See if an admin user overrode our logging. */
+ if (cmdline_syslog_name != NULL)
+   {
+   if (admin_user)
+     {
+     syslog_processname = cmdline_syslog_name;
+     log_file_path = string_copy(CUS"syslog");
+     }
+   else
+     {
+     /* not a panic, non-privileged users should not be able to spam paniclog */
+     fprintf(stderr,
+         "exim: you lack sufficient privilege to specify syslog process name\n");
+     return EXIT_FAILURE;
+     }
+   }
  /* Paranoia check of maximum lengths of certain strings. There is a check
  on the length of the log file path in log.c, which will come into effect
  if there are any calls to write the log earlier than this. However, if we
@@@ -3690,8 -3914,9 +3917,9 @@@ if (((debug_selector & D_any) != 0 || (
    {
    int i;
    uschar *p = big_buffer;
-   Ustrcpy(p, "cwd=");
-   (void)getcwd(CS p+4, big_buffer_size - 4);
+   char * dummy;
+   Ustrcpy(p, "cwd= (failed)");
+   dummy = /* quieten compiler */ getcwd(CS p+4, big_buffer_size - 4);
    while (*p) p++;
    (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
    while (*p) p++;
@@@ -3734,8 -3959,9 +3962,9 @@@ privilege by now. Before the chdir, we 
  
  if (Uchdir(spool_directory) != 0)
    {
+   int dummy;
    (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
-   (void)Uchdir(spool_directory);
+   dummy = /* quieten compiler */ Uchdir(spool_directory);
    }
  
  /* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
@@@ -3772,65 -3998,9 +4001,9 @@@ if (bi_option
      }
    }
  
- /* If an action on specific messages is requested, or if a daemon or queue
- runner is being started, we need to know if Exim was called by an admin user.
- This is the case if the real user is root or exim, or if the real group is
- exim, or if one of the supplementary groups is exim or a group listed in
- admin_groups. We don't fail all message actions immediately if not admin_user,
- since some actions can be performed by non-admin users. Instead, set admin_user
- for later interrogation. */
- if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
-   admin_user = TRUE;
- else
-   {
-   int i, j;
-   for (i = 0; i < group_count; i++)
-     {
-     if (group_list[i] == exim_gid) admin_user = TRUE;
-     else if (admin_groups != NULL)
-       {
-       for (j = 1; j <= (int)(admin_groups[0]); j++)
-         if (admin_groups[j] == group_list[i])
-           { admin_user = TRUE; break; }
-       }
-     if (admin_user) break;
-     }
-   }
- /* Another group of privileged users are the trusted users. These are root,
- exim, and any caller matching trusted_users or trusted_groups. Trusted callers
- are permitted to specify sender_addresses with -f on the command line, and
- other message parameters as well. */
- if (real_uid == root_uid || real_uid == exim_uid)
-   trusted_caller = TRUE;
- else
-   {
-   int i, j;
-   if (trusted_users != NULL)
-     {
-     for (i = 1; i <= (int)(trusted_users[0]); i++)
-       if (trusted_users[i] == real_uid)
-         { trusted_caller = TRUE; break; }
-     }
-   if (!trusted_caller && trusted_groups != NULL)
-     {
-     for (i = 1; i <= (int)(trusted_groups[0]); i++)
-       {
-       if (trusted_groups[i] == real_gid)
-         trusted_caller = TRUE;
-       else for (j = 0; j < group_count; j++)
-         {
-         if (trusted_groups[i] == group_list[j])
-           { trusted_caller = TRUE; break; }
-         }
-       if (trusted_caller) break;
-       }
-     }
-   }
+ /* We moved the admin/trusted check to be immediately after reading the
+ configuration file.  We leave these prints here to ensure that syslog setup,
+ logfile setup, and so on has already happened. */
  
  if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
  if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
      interface_port = check_port(interface_address);
    }
  
+ /* If the caller is trusted, then they can use -G to suppress_local_fixups. */
+ if (flag_G)
+   {
+   if (trusted_caller)
+     {
+     suppress_local_fixups = suppress_local_fixups_default = TRUE;
+     DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
+     }
+   else
+     {
+     fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
+     return EXIT_FAILURE;
+     }
+   }
  /* If an SMTP message is being received check to see if the standard input is a
  TCP/IP socket. If it is, we assume that Exim was called from inetd if the
  caller is root or the Exim user, or if the port is a privileged one. Otherwise,
@@@ -3919,7 -4104,7 +4107,7 @@@ if (smtp_input
          interface_address = host_ntoa(-1, &interface_sock, NULL,
            &interface_port);
  
-       if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE;
+       if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE;
  
        if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
          {
@@@ -4226,11 -4411,12 +4414,12 @@@ if (test_retry_arg >= 0
    }
  
  /* Handle a request to list one or more configuration options */
+ /* If -n was set, we suppress some information */
  
  if (list_options)
    {
    set_process_info("listing variables");
-   if (recipients_arg >= argc) readconf_print(US"all", NULL);
+   if (recipients_arg >= argc) readconf_print(US"all", NULL, flag_n);
      else for (i = recipients_arg; i < argc; i++)
        {
        if (i < argc - 1 &&
             Ustrcmp(argv[i], "authenticator") == 0 ||
             Ustrcmp(argv[i], "macro") == 0))
          {
-         readconf_print(argv[i+1], argv[i]);
+         readconf_print(argv[i+1], argv[i], flag_n);
          i++;
          }
-       else readconf_print(argv[i], NULL);
+       else readconf_print(argv[i], NULL, flag_n);
        }
    exim_exit(EXIT_SUCCESS);
    }
@@@ -4769,7 -4955,8 +4958,8 @@@ if (host_checking
  
  /* Arrange for message reception if recipients or SMTP were specified;
  otherwise complain unless a version print (-bV) happened or this is a filter
- verification test. In the former case, show the configuration file name. */
+ verification test or info dump.
+ In the former case, show the configuration file name. */
  
  if (recipients_arg >= argc && !extract_recipients && !smtp_input)
    {
      return EXIT_SUCCESS;
      }
  
+   if (info_flag != CMDINFO_NONE)
+     {
+     show_exim_information(info_flag, info_stdout ? stdout : stderr);
+     return info_stdout ? EXIT_SUCCESS : EXIT_FAILURE;
+     }
    if (filter_test == FTEST_NONE)
      exim_usage(called_as);
    }
@@@ -5216,7 -5409,11 +5412,11 @@@ while (more
      if (ftest_prefix != NULL) printf("Prefix    = %s\n", ftest_prefix);
      if (ftest_suffix != NULL) printf("Suffix    = %s\n", ftest_suffix);
  
-     (void)chdir("/");   /* Get away from wherever the user is running this from */
+     if (chdir("/"))   /* Get away from wherever the user is running this from */
+       {
+       DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
+       exim_exit(EXIT_FAILURE);
+       }
  
      /* Now we run either a system filter test, or a user filter test, or both.
      In the latter case, headers added by the system filter will persist and be
diff --combined src/src/expand.c
index 31d51d79ea9a59de7627861a157ef69c7b84977e,1da2225637850b5ae8eda30854c96d81858c6499..d808d1bf3a90636930123f86586c917b28d0ce06
@@@ -102,6 -102,7 +102,7 @@@ bcrypt ({CRYPT}$2a$)
  alphabetical order. */
  
  static uschar *item_table[] = {
+   US"acl",
    US"dlfunc",
    US"extract",
    US"filter",
    US"tr" };
  
  enum {
+   EITEM_ACL,
    EITEM_DLFUNC,
    EITEM_EXTRACT,
    EITEM_FILTER,
@@@ -179,9 -181,12 +181,12 @@@ static uschar *op_table_main[] = 
    US"h",
    US"hash",
    US"hex2b64",
+   US"hexquote",
    US"l",
    US"lc",
    US"length",
+   US"listcount",
+   US"listnamed",
    US"mask",
    US"md5",
    US"nh",
@@@ -212,9 -217,12 +217,12 @@@ enum 
    EOP_H,
    EOP_HASH,
    EOP_HEX2B64,
+   EOP_HEXQUOTE,
    EOP_L,
    EOP_LC,
    EOP_LENGTH,
+   EOP_LISTCOUNT,
+   EOP_LISTNAMED,
    EOP_MASK,
    EOP_MD5,
    EOP_NH,
@@@ -243,6 -251,7 +251,7 @@@ static uschar *cond_table[] = 
    US"==",     /* Backward compatibility */
    US">",
    US">=",
+   US"acl",
    US"and",
    US"bool",
    US"bool_lax",
@@@ -288,6 -297,7 +297,7 @@@ enum 
    ECOND_NUM_EE,
    ECOND_NUM_G,
    ECOND_NUM_GE,
+   ECOND_ACL,
    ECOND_AND,
    ECOND_BOOL,
    ECOND_BOOL_LAX,
@@@ -351,6 -361,7 +361,7 @@@ enum 
    vtype_ino,            /* value is address of ino_t (not always an int) */
    vtype_uid,            /* value is address of uid_t (not always an int) */
    vtype_gid,            /* value is address of gid_t (not always an int) */
+   vtype_bool,           /* value is address of bool */
    vtype_stringptr,      /* value is address of pointer to string */
    vtype_msgbody,        /* as stringptr, but read when first required */
    vtype_msgbody_end,    /* ditto, the end of the message */
    vtype_msgheaders_raw, /* the message's headers, unprocessed */
    vtype_localpart,      /* extract local part from string */
    vtype_domain,         /* extract domain from string */
-   vtype_recipients,     /* extract recipients from recipients list */
-                         /* (available only in system filters, ACLs, and */
-                         /* local_scan()) */
+   vtype_string_func,  /* value is string returned by given function */
    vtype_todbsdin,       /* value not used; generate BSD inbox tod */
    vtype_tode,           /* value not used; generate tod in epoch format */
    vtype_todel,          /* value not used; generate tod in epoch/usec format */
    #endif
    };
  
+ static uschar * fn_recipients(void);
  /* This table must be kept in alphabetical order. */
  
  static var_entry var_table[] = {
    /* WARNING: Do not invent variables whose names start acl_c or acl_m because
       they will be confused with user-creatable ACL variables. */
+   { "acl_arg1",            vtype_stringptr,   &acl_arg[0] },
+   { "acl_arg2",            vtype_stringptr,   &acl_arg[1] },
+   { "acl_arg3",            vtype_stringptr,   &acl_arg[2] },
+   { "acl_arg4",            vtype_stringptr,   &acl_arg[3] },
+   { "acl_arg5",            vtype_stringptr,   &acl_arg[4] },
+   { "acl_arg6",            vtype_stringptr,   &acl_arg[5] },
+   { "acl_arg7",            vtype_stringptr,   &acl_arg[6] },
+   { "acl_arg8",            vtype_stringptr,   &acl_arg[7] },
+   { "acl_arg9",            vtype_stringptr,   &acl_arg[8] },
+   { "acl_narg",            vtype_int,         &acl_narg },
    { "acl_verify_message",  vtype_stringptr,   &acl_verify_message },
    { "address_data",        vtype_stringptr,   &deliver_address_data },
    { "address_file",        vtype_stringptr,   &address_file },
    { "compile_date",        vtype_stringptr,   &version_date },
    { "compile_number",      vtype_stringptr,   &version_cnumber },
    { "csa_status",          vtype_stringptr,   &csa_status },
 +#ifdef EXPERIMENTAL_DBL
 +  { "dbl_defer_errno",     vtype_int,         &dbl_defer_errno },
 +  { "dbl_defer_errstr",    vtype_stringptr,   &dbl_defer_errstr },
 +  { "dbl_delivery_confirmation", vtype_stringptr,   &dbl_delivery_confirmation },
 +  { "dbl_delivery_domain", vtype_stringptr,   &dbl_delivery_domain },
 +  { "dbl_delivery_fqdn",   vtype_stringptr,   &dbl_delivery_fqdn },
 +  { "dbl_delivery_ip",     vtype_stringptr,   &dbl_delivery_ip },
 +  { "dbl_delivery_local_part",vtype_stringptr,&dbl_delivery_local_part },
 +  { "dbl_delivery_port",   vtype_int,         &dbl_delivery_port },
 +#endif
  #ifdef EXPERIMENTAL_DCC
    { "dcc_header",          vtype_stringptr,   &dcc_header },
    { "dcc_result",          vtype_stringptr,   &dcc_result },
    { "dkim_signers",        vtype_stringptr,   &dkim_signers },
    { "dkim_verify_reason",  vtype_dkim,        (void *)DKIM_VERIFY_REASON },
    { "dkim_verify_status",  vtype_dkim,        (void *)DKIM_VERIFY_STATUS},
+ #endif
+ #ifdef EXPERIMENTAL_DMARC
+   { "dmarc_ar_header",     vtype_stringptr,   &dmarc_ar_header },
+   { "dmarc_status",        vtype_stringptr,   &dmarc_status },
+   { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
+   { "dmarc_used_domain",   vtype_stringptr,   &dmarc_used_domain },
  #endif
    { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
    { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
  #ifdef WITH_OLD_DEMIME
    { "found_extension",     vtype_stringptr,   &found_extension },
  #endif
+   { "headers_added",       vtype_string_func, &fn_hdrs_added },
    { "home",                vtype_stringptr,   &deliver_home },
    { "host",                vtype_stringptr,   &deliver_host },
    { "host_address",        vtype_stringptr,   &deliver_host_address },
    { "received_time",       vtype_int,         &received_time },
    { "recipient_data",      vtype_stringptr,   &recipient_data },
    { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
-   { "recipients",          vtype_recipients,  NULL },
+   { "recipients",          vtype_string_func, &fn_recipients },
    { "recipients_count",    vtype_int,         &recipients_count },
  #ifdef WITH_CONTENT_SCAN
    { "regex_match_string",  vtype_stringptr,   &regex_match_string },
    { "reply_address",       vtype_reply,       NULL },
    { "return_path",         vtype_stringptr,   &return_path },
    { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
+   { "router_name",         vtype_stringptr,   &router_name },
    { "runrc",               vtype_int,         &runrc },
    { "self_hostname",       vtype_stringptr,   &self_hostname },
    { "sender_address",      vtype_stringptr,   &sender_address },
    { "sender_helo_name",    vtype_stringptr,   &sender_helo_name },
    { "sender_host_address", vtype_stringptr,   &sender_host_address },
    { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
+   { "sender_host_dnssec",  vtype_bool,        &sender_host_dnssec },
    { "sender_host_name",    vtype_host_lookup, NULL },
    { "sender_host_port",    vtype_int,         &sender_host_port },
    { "sender_ident",        vtype_stringptr,   &sender_ident },
    { "srs_status",          vtype_stringptr,   &srs_status },
  #endif
    { "thisaddress",         vtype_stringptr,   &filter_thisaddress },
-   { "tls_bits",            vtype_int,         &tls_bits },
-   { "tls_certificate_verified", vtype_int,    &tls_certificate_verified },
-   { "tls_cipher",          vtype_stringptr,   &tls_cipher },
-   { "tls_peerdn",          vtype_stringptr,   &tls_peerdn },
- #ifdef SUPPORT_TLS
-   { "tls_sni",             vtype_stringptr,   &tls_sni },
+   /* The non-(in,out) variables are now deprecated */
+   { "tls_bits",            vtype_int,         &tls_in.bits },
+   { "tls_certificate_verified", vtype_int,    &tls_in.certificate_verified },
+   { "tls_cipher",          vtype_stringptr,   &tls_in.cipher },
+   { "tls_in_bits",         vtype_int,         &tls_in.bits },
+   { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
+   { "tls_in_cipher",       vtype_stringptr,   &tls_in.cipher },
+   { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
  #endif
+   { "tls_out_bits",        vtype_int,         &tls_out.bits },
+   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
+   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
+   { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
+ #endif
+   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },       /* mind the alphabetical order! */
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+   { "tls_sni",             vtype_stringptr,   &tls_in.sni },  /* mind the alphabetical order! */
+ #endif
    { "tod_bsdinbox",        vtype_todbsdin,    NULL },
    { "tod_epoch",           vtype_tode,        NULL },
    { "tod_epoch_l",         vtype_todel,       NULL },
    { "tod_logfile",         vtype_todlf,       NULL },
    { "tod_zone",            vtype_todzone,     NULL },
    { "tod_zulu",            vtype_todzulu,     NULL },
+   { "transport_name",      vtype_stringptr,   &transport_name },
    { "value",               vtype_stringptr,   &lookup_value },
    { "version_number",      vtype_stringptr,   &version_string },
    { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
@@@ -754,8 -794,11 +804,11 @@@ return -1
  
  /* This function is called to expand a string, and test the result for a "true"
  or "false" value. Failure of the expansion yields FALSE; logged unless it was a
- forced fail or lookup defer. All store used by the function can be released on
- exit.
+ forced fail or lookup defer.
+ We used to release all store used, but this is not not safe due
+ to ${dlfunc } and ${acl }.  In any case expand_string_internal()
+ is reasonably careful to release what it can.
  
  The actual false-value tests should be replicated for ECOND_BOOL_LAX.
  
@@@ -771,7 -814,6 +824,6 @@@ BOO
  expand_check_condition(uschar *condition, uschar *m1, uschar *m2)
  {
  int rc;
- void *reset_point = store_get(0);
  uschar *ss = expand_string(condition);
  if (ss == NULL)
    {
    }
  rc = ss[0] != 0 && Ustrcmp(ss, "0") != 0 && strcmpic(ss, US"no") != 0 &&
    strcmpic(ss, US"false") != 0;
- store_reset(reset_point);
  return rc;
  }
  
@@@ -1417,6 -1458,34 +1468,34 @@@ return yield
  
  
  
+ /*************************************************
+ *               Return list of recipients        *
+ *************************************************/
+ /* A recipients list is available only during system message filtering,
+ during ACL processing after DATA, and while expanding pipe commands
+ generated from a system filter, but not elsewhere. */
+ static uschar *
+ fn_recipients(void)
+ {
+ if (!enable_dollar_recipients) return NULL; else
+   {
+   int size = 128;
+   int ptr = 0;
+   int i;
+   uschar * s = store_get(size);
+   for (i = 0; i < recipients_count; i++)
+     {
+     if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
+     s = string_cat(s, &size, &ptr, recipients_list[i].address,
+       Ustrlen(recipients_list[i].address));
+     }
+   s[ptr] = 0;     /* string_cat() leaves room */
+   return s;
+   }
+ }
  /*************************************************
  *               Find value of a variable         *
  *************************************************/
@@@ -1513,6 -1582,10 +1592,10 @@@ while (last > first
      sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */
      return var_buffer;
  
+     case vtype_bool:
+     sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */
+     return var_buffer;
      case vtype_stringptr:                      /* Pointer to string */
      s = *((uschar **)(var_table[middle].value));
      return (s == NULL)? US"" : s;
        }
      return (s == NULL)? US"" : s;
  
-     /* A recipients list is available only during system message filtering,
-     during ACL processing after DATA, and while expanding pipe commands
-     generated from a system filter, but not elsewhere. */
-     case vtype_recipients:
-     if (!enable_dollar_recipients) return NULL; else
+     case vtype_string_func:
        {
-       int size = 128;
-       int ptr = 0;
-       int i;
-       s = store_get(size);
-       for (i = 0; i < recipients_count; i++)
-         {
-         if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
-         s = string_cat(s, &size, &ptr, recipients_list[i].address,
-           Ustrlen(recipients_list[i].address));
-         }
-       s[ptr] = 0;     /* string_cat() leaves room */
+       uschar * (*fn)() = var_table[middle].value;
+       return fn();
        }
-     return s;
  
      case vtype_pspace:
        {
@@@ -1689,6 -1747,31 +1757,31 @@@ return NULL;          /* Unknown variab
  
  
  
+ void
+ modify_variable(uschar *name, void * value)
+ {
+ int first = 0;
+ int last = var_table_size;
+ while (last > first)
+   {
+   int middle = (first + last)/2;
+   int c = Ustrcmp(name, var_table[middle].name);
+   if (c > 0) { first = middle + 1; continue; }
+   if (c < 0) { last = middle; continue; }
+   /* Found an existing variable; change the item it refers to */
+   var_table[middle].value = value;
+   return;
+   }
+ return;          /* Unknown variable name, fail silently */
+ }
  /*************************************************
  *           Read and expand substrings           *
  *************************************************/
@@@ -1778,6 -1861,58 +1871,58 @@@ if (Ustrncmp(name, "acl_", 4) == 0
  
  
  
+ /*
+ Load args from sub array to globals, and call acl_check().
+ Sub array will be corrupted on return.
+ Returns:       OK         access is granted by an ACCEPT verb
+                DISCARD    access is granted by a DISCARD verb
+              FAIL       access is denied
+              FAIL_DROP  access is denied; drop the connection
+              DEFER      can't tell at the moment
+              ERROR      disaster
+ */
+ static int
+ eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
+ {
+ int i;
+ uschar *tmp;
+ int sav_narg = acl_narg;
+ int ret;
+ extern int acl_where;
+ if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+ for (i = 0; i < nsub && sub[i+1]; i++)
+   {
+   tmp = acl_arg[i];
+   acl_arg[i] = sub[i+1];      /* place callers args in the globals */
+   sub[i+1] = tmp;             /* stash the old args using our caller's storage */
+   }
+ acl_narg = i;
+ while (i < nsub)
+   {
+   sub[i+1] = acl_arg[i];
+   acl_arg[i++] = NULL;
+   }
+ DEBUG(D_expand)
+   debug_printf("expanding: acl: %s  arg: %s%s\n",
+     sub[0],
+     acl_narg>0 ? sub[1]   : US"<none>",
+     acl_narg>1 ? " +more" : "");
+ ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
+ for (i = 0; i < nsub; i++)
+   acl_arg[i] = sub[i+1];      /* restore old args */
+ acl_narg = sav_narg;
+ return ret;
+ }
  /*************************************************
  *        Read and evaluate a condition           *
  *************************************************/
@@@ -1805,7 -1940,7 +1950,7 @@@ int i, rc, cond_type, roffset
  int_eximarith_t num[2];
  struct stat statbuf;
  uschar name[256];
- uschar *sub[4];
+ uschar *sub[10];
  
  const pcre *re;
  const uschar *rerror;
@@@ -1872,6 -2007,7 +2017,7 @@@ switch(cond_type
        Ustrncmp(name, "bheader_", 8) == 0)
      {
      s = read_header_name(name, 256, s);
+     /* {-for-text-editors */
      if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
      if (yield != NULL) *yield =
        (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
    case ECOND_PWCHECK:
  
    while (isspace(*s)) s++;
-   if (*s != '{') goto COND_FAILED_CURLY_START;
+   if (*s != '{') goto COND_FAILED_CURLY_START;                /* }-for-text-editors */
  
    sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
    if (sub[0] == NULL) return NULL;
+   /* {-for-text-editors */
    if (*s++ != '}') goto COND_FAILED_CURLY_END;
  
    if (yield == NULL) return s;   /* No need to run the test if skipping */
    return s;
  
  
+   /* call ACL (in a conditional context).  Accept true, deny false.
+   Defer is a forced-fail.  Anything set by message= goes to $value.
+   Up to ten parameters are used; we use the braces round the name+args
+   like the saslauthd condition does, to permit a variable number of args.
+   See also the expansion-item version EITEM_ACL and the traditional
+   acl modifier ACLC_ACL.
+   */
+   case ECOND_ACL:
+     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
+     {
+     uschar *user_msg;
+     BOOL cond = FALSE;
+     int size = 0;
+     int ptr = 0;
+     while (isspace(*s)) s++;
+     if (*s++ != '{') goto COND_FAILED_CURLY_START;    /*}*/
+     switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+       &s, yield == NULL, TRUE, US"acl"))
+       {
+       case 1: expand_string_message = US"too few arguments or bracketing "
+         "error for acl";
+       case 2:
+       case 3: return NULL;
+       }
+     if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+       {
+       case OK:
+         cond = TRUE;
+       case FAIL:
+           lookup_value = NULL;
+         if (user_msg)
+           {
+             lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+             lookup_value[ptr] = '\0';
+           }
+         *yield = cond == testfor;
+         break;
+       case DEFER:
+           expand_string_forcedfail = TRUE;
+       default:
+           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+         return NULL;
+       }
+     return s;
+     }
    /* saslauthd: does Cyrus saslauthd authentication. Four parameters are used:
  
       ${if saslauthd {{username}{password}{service}{realm}}  {yes}[no}}
  
    However, the last two are optional. That is why the whole set is enclosed
-   in their own set or braces. */
+   in their own set of braces. */
  
    case ECOND_SASLAUTHD:
    #ifndef CYRUS_SASLAUTHD_SOCKET
    goto COND_FAILED_NOT_COMPILED;
    #else
    while (isspace(*s)) s++;
-   if (*s++ != '{') goto COND_FAILED_CURLY_START;
+   if (*s++ != '{') goto COND_FAILED_CURLY_START;      /* }-for-text-editors */
    switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd"))
      {
      case 1: expand_string_message = US"too few arguments or bracketing "
      {
      case ECOND_NUM_E:
      case ECOND_NUM_EE:
-     *yield = (num[0] == num[1]) == testfor;
+     tempcond = (num[0] == num[1]);
      break;
  
      case ECOND_NUM_G:
-     *yield = (num[0] > num[1]) == testfor;
+     tempcond = (num[0] > num[1]);
      break;
  
      case ECOND_NUM_GE:
-     *yield = (num[0] >= num[1]) == testfor;
+     tempcond = (num[0] >= num[1]);
      break;
  
      case ECOND_NUM_L:
-     *yield = (num[0] < num[1]) == testfor;
+     tempcond = (num[0] < num[1]);
      break;
  
      case ECOND_NUM_LE:
-     *yield = (num[0] <= num[1]) == testfor;
+     tempcond = (num[0] <= num[1]);
      break;
  
      case ECOND_STR_LT:
-     *yield = (Ustrcmp(sub[0], sub[1]) < 0) == testfor;
+     tempcond = (Ustrcmp(sub[0], sub[1]) < 0);
      break;
  
      case ECOND_STR_LTI:
-     *yield = (strcmpic(sub[0], sub[1]) < 0) == testfor;
+     tempcond = (strcmpic(sub[0], sub[1]) < 0);
      break;
  
      case ECOND_STR_LE:
-     *yield = (Ustrcmp(sub[0], sub[1]) <= 0) == testfor;
+     tempcond = (Ustrcmp(sub[0], sub[1]) <= 0);
      break;
  
      case ECOND_STR_LEI:
-     *yield = (strcmpic(sub[0], sub[1]) <= 0) == testfor;
+     tempcond = (strcmpic(sub[0], sub[1]) <= 0);
      break;
  
      case ECOND_STR_EQ:
-     *yield = (Ustrcmp(sub[0], sub[1]) == 0) == testfor;
+     tempcond = (Ustrcmp(sub[0], sub[1]) == 0);
      break;
  
      case ECOND_STR_EQI:
-     *yield = (strcmpic(sub[0], sub[1]) == 0) == testfor;
+     tempcond = (strcmpic(sub[0], sub[1]) == 0);
      break;
  
      case ECOND_STR_GT:
-     *yield = (Ustrcmp(sub[0], sub[1]) > 0) == testfor;
+     tempcond = (Ustrcmp(sub[0], sub[1]) > 0);
      break;
  
      case ECOND_STR_GTI:
-     *yield = (strcmpic(sub[0], sub[1]) > 0) == testfor;
+     tempcond = (strcmpic(sub[0], sub[1]) > 0);
      break;
  
      case ECOND_STR_GE:
-     *yield = (Ustrcmp(sub[0], sub[1]) >= 0) == testfor;
+     tempcond = (Ustrcmp(sub[0], sub[1]) >= 0);
      break;
  
      case ECOND_STR_GEI:
-     *yield = (strcmpic(sub[0], sub[1]) >= 0) == testfor;
+     tempcond = (strcmpic(sub[0], sub[1]) >= 0);
      break;
  
      case ECOND_MATCH:   /* Regular expression match */
          "\"%s\": %s at offset %d", sub[1], rerror, roffset);
        return NULL;
        }
-     *yield = regex_match_and_setup(re, sub[0], 0, -1) == testfor;
+     tempcond = regex_match_and_setup(re, sub[0], 0, -1);
      break;
  
      case ECOND_MATCH_ADDRESS:  /* Match in an address list */
      switch(rc)
        {
        case OK:
-       *yield = testfor;
+       tempcond = TRUE;
        break;
  
        case FAIL:
-       *yield = !testfor;
+       tempcond = FALSE;
        break;
  
        case DEFER:
      /* Various "encrypted" comparisons. If the second string starts with
      "{" then an encryption type is given. Default to crypt() or crypt16()
      (build-time choice). */
+     /* }-for-text-editors */
  
      case ECOND_CRYPTEQ:
      #ifndef SUPPORT_CRYPTEQ
          uschar *coded = auth_b64encode((uschar *)digest, 16);
          DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
            "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-         *yield = (Ustrcmp(coded, sub[1]+5) == 0) == testfor;
+         tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
          }
        else if (sublen == 32)
          {
          coded[32] = 0;
          DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
            "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-         *yield = (strcmpic(coded, sub[1]+5) == 0) == testfor;
+         tempcond = (strcmpic(coded, sub[1]+5) == 0);
          }
        else
          {
          DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
            "fail\n  crypted=%s\n", sub[1]+5);
-         *yield = !testfor;
+         tempcond = FALSE;
          }
        }
  
          uschar *coded = auth_b64encode((uschar *)digest, 20);
          DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
            "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-         *yield = (Ustrcmp(coded, sub[1]+6) == 0) == testfor;
+         tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
          }
        else if (sublen == 40)
          {
          coded[40] = 0;
          DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
            "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-         *yield = (strcmpic(coded, sub[1]+6) == 0) == testfor;
+         tempcond = (strcmpic(coded, sub[1]+6) == 0);
          }
        else
          {
          DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
            "fail\n  crypted=%s\n", sub[1]+6);
-         *yield = !testfor;
+       tempcond = FALSE;
          }
        }
  
          sub[1] += 9;
          which = 2;
          }
-       else if (sub[1][0] == '{')
+       else if (sub[1][0] == '{')              /* }-for-text-editors */
          {
          expand_string_message = string_sprintf("unknown encryption mechanism "
            "in \"%s\"", sub[1]);
        salt), force failure. Otherwise we get false positives: with an empty
        string the yield of crypt() is an empty string! */
  
-       *yield = (Ustrlen(sub[1]) < 2)? !testfor :
-         (Ustrcmp(coded, sub[1]) == 0) == testfor;
+       tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
+         (Ustrcmp(coded, sub[1]) == 0);
        }
      break;
      #endif  /* SUPPORT_CRYPTEQ */
      case ECOND_INLISTI:
        {
        int sep = 0;
-       BOOL found = FALSE;
        uschar *save_iterate_item = iterate_item;
        int (*compare)(const uschar *, const uschar *);
  
+       tempcond = FALSE;
        if (cond_type == ECOND_INLISTI)
          compare = strcmpic;
        else
        while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
          if (compare(sub[0], iterate_item) == 0)
            {
-           found = TRUE;
+           tempcond = TRUE;
            break;
            }
        iterate_item = save_iterate_item;
-       *yield = found;
        }
  
      }   /* Switch for comparison conditions */
  
+   *yield = tempcond == testfor;
    return s;    /* End of comparison conditions */
  
  
    combined_cond = (cond_type == ECOND_AND);
  
    while (isspace(*s)) s++;
-   if (*s++ != '{') goto COND_FAILED_CURLY_START;
+   if (*s++ != '{') goto COND_FAILED_CURLY_START;      /* }-for-text-editors */
  
    for (;;)
      {
      while (isspace(*s)) s++;
+     /* {-for-text-editors */
      if (*s == '}') break;
-     if (*s != '{')
+     if (*s != '{')                                    /* }-for-text-editors */
        {
        expand_string_message = string_sprintf("each subcondition "
          "inside an \"%s{...}\" condition must be in its own {}", name);
        }
      while (isspace(*s)) s++;
  
+     /* {-for-text-editors */
      if (*s++ != '}')
        {
+       /* {-for-text-editors */
        expand_string_message = string_sprintf("missing } at end of condition "
          "inside \"%s\" group", name);
        return NULL;
      uschar *save_iterate_item = iterate_item;
  
      while (isspace(*s)) s++;
-     if (*s++ != '{') goto COND_FAILED_CURLY_START;
+     if (*s++ != '{') goto COND_FAILED_CURLY_START;    /* }-for-text-editors */
      sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
      if (sub[0] == NULL) return NULL;
+     /* {-for-text-editors */
      if (*s++ != '}') goto COND_FAILED_CURLY_END;
  
      while (isspace(*s)) s++;
-     if (*s++ != '{') goto COND_FAILED_CURLY_START;
+     if (*s++ != '{') goto COND_FAILED_CURLY_START;    /* }-for-text-editors */
  
      sub[1] = s;
  
        }
      while (isspace(*s)) s++;
  
+     /* {-for-text-editors */
      if (*s++ != '}')
        {
+       /* {-for-text-editors */
        expand_string_message = string_sprintf("missing } at end of condition "
          "inside \"%s\"", name);
        return NULL;
      size_t len;
      BOOL boolvalue = FALSE;
      while (isspace(*s)) s++;
-     if (*s != '{') goto COND_FAILED_CURLY_START;
+     if (*s != '{') goto COND_FAILED_CURLY_START;      /* }-for-text-editors */
      ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
      switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
        {
@@@ -3203,12 -3399,12 +3409,12 @@@ if (*error == NULL
       * can just let the other invalid results occur otherwise, as they have
       * until now.  For this one case, we can coerce.
       */
-     if (y == -1 && x == LLONG_MIN && op != '*')
+     if (y == -1 && x == EXIM_ARITH_MIN && op != '*')
        {
        DEBUG(D_expand)
          debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
-             LLONG_MIN, op, LLONG_MAX);
-       x = LLONG_MAX;
+             EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
+       x = EXIM_ARITH_MAX;
        continue;
        }
      if (op == '*')
@@@ -3380,8 -3576,8 +3586,8 @@@ $message_headers which can get very lon
  There's a problem if a ${dlfunc item has side-effects that cause allocation,
  since resetting the store at the end of the expansion will free store that was
  allocated by the plugin code as well as the slop after the expanded string. So
- we skip any resets if ${dlfunc has been used. This is an unfortunate
- consequence of string expansion becoming too powerful.
+ we skip any resets if ${dlfunc has been used. The same applies for ${acl. This
is an unfortunate consequence of string expansion becoming too powerful.
  
  Arguments:
    string         the string to be expanded
@@@ -3597,6 -3793,46 +3803,46 @@@ while (*s != 0
  
    switch(item_type)
      {
+     /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
+     If the ACL returns accept or reject we return content set by "message ="
+     There is currently no limit on recursion; this would have us call
+     acl_check_internal() directly and get a current level from somewhere.
+     See also the acl expansion condition ECOND_ACL and the traditional
+     acl modifier ACLC_ACL.
+     Assume that the function has side-effects on the store that must be preserved.
+     */
+     case EITEM_ACL:
+       /* ${acl {name} {arg1}{arg2}...} */
+       {
+       uschar *sub[10];        /* name + arg1-arg9 (which must match number of acl_arg[]) */
+       uschar *user_msg;
+       switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl"))
+         {
+         case 1: goto EXPAND_FAILED_CURLY;
+         case 2:
+         case 3: goto EXPAND_FAILED;
+         }
+       if (skipping) continue;
+       resetok = FALSE;
+       switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+       {
+       case OK:
+       case FAIL:
+         if (user_msg)
+             yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+         continue;
+       case DEFER:
+           expand_string_forcedfail = TRUE;
+       default:
+           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+         goto EXPAND_FAILED;
+       }
+       }
      /* Handle conditionals - preserve the values of the numerical expansion
      variables in case they get changed by a regular expression match in the
      condition. If not, they retain their external settings. At the end
          continue;
          }
  
+       /* Convert octets outside 0x21..0x7E to \xXX form */
+       case EOP_HEXQUOTE:
+       {
+         uschar *t = sub - 1;
+         while (*(++t) != 0)
+           {
+           if (*t < 0x21 || 0x7E < *t)
+             yield = string_cat(yield, &size, &ptr,
+             string_sprintf("\\x%02x", *t), 4);
+         else
+           yield = string_cat(yield, &size, &ptr, t, 1);
+           }
+       continue;
+       }
+       /* count the number of list elements */
+       case EOP_LISTCOUNT:
+         {
+       int cnt = 0;
+       int sep = 0;
+       uschar * cp;
+       uschar buffer[256];
+       while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+       cp = string_sprintf("%d", cnt);
+         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         continue;
+         }
+       /* expand a named list given the name */
+       /* handles nested named lists; requotes as colon-sep list */
+       case EOP_LISTNAMED:
+       {
+       tree_node *t = NULL;
+       uschar * list;
+       int sep = 0;
+       uschar * item;
+       uschar * suffix = US"";
+       BOOL needsep = FALSE;
+       uschar buffer[256];
+       if (*sub == '+') sub++;
+       if (arg == NULL)        /* no-argument version */
+         {
+         if (!(t = tree_search(addresslist_anchor, sub)) &&
+             !(t = tree_search(domainlist_anchor,  sub)) &&
+             !(t = tree_search(hostlist_anchor,    sub)))
+           t = tree_search(localpartlist_anchor, sub);
+         }
+       else switch(*arg)       /* specific list-type version */
+         {
+         case 'a': t = tree_search(addresslist_anchor,   sub); suffix = US"_a"; break;
+         case 'd': t = tree_search(domainlist_anchor,    sub); suffix = US"_d"; break;
+         case 'h': t = tree_search(hostlist_anchor,      sub); suffix = US"_h"; break;
+         case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
+         default:
+             expand_string_message = string_sprintf("bad suffix on \"list\" operator");
+           goto EXPAND_FAILED;
+         }
+       if(!t)
+         {
+           expand_string_message = string_sprintf("\"%s\" is not a %snamed list",
+             sub, !arg?""
+             : *arg=='a'?"address "
+             : *arg=='d'?"domain "
+             : *arg=='h'?"host "
+             : *arg=='l'?"localpart "
+             : 0);
+         goto EXPAND_FAILED;
+         }
+       list = ((namedlist_block *)(t->data.ptr))->string;
+       while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+         {
+         uschar * buf = US" : ";
+         if (needsep)
+           yield = string_cat(yield, &size, &ptr, buf, 3);
+         else
+           needsep = TRUE;
+         if (*item == '+')     /* list item is itself a named list */
+           {
+           uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item);
+           item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE);
+           }
+         else if (sep != ':')  /* item from non-colon-sep list, re-quote for colon list-separator */
+           {
+           char * cp;
+           char tok[3];
+           tok[0] = sep; tok[1] = ':'; tok[2] = 0;
+           while ((cp= strpbrk((const char *)item, tok)))
+             {
+               yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+             if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
+               {
+                 yield = string_cat(yield, &size, &ptr, US"::", 2);
+               item = (uschar *)cp;
+               }
+             else              /* sep in item; should already be doubled; emit once */
+               {
+                 yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+               if (*cp == sep) cp++;
+               item = (uschar *)cp;
+               }
+             }
+           }
+           yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+         }
+         continue;
+       }
        /* mask applies a mask to an IP address; for example the result of
        ${mask:131.111.10.206/28} is 131.111.10.192/28. */
  
@@@ -6183,18 -6535,25 +6545,25 @@@ else if (value < 0 && isplus
    }
  else
    {
-   if (tolower(*endptr) == 'k')
+   switch (tolower(*endptr))
      {
-     if (value > LLONG_MAX/1024 || value < LLONG_MIN/1024) errno = ERANGE;
+     default:
+       break;
+     case 'k':
+       if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE;
        else value *= 1024;
-     endptr++;
-     }
-     else if (tolower(*endptr) == 'm')
-     {
-     if (value > LLONG_MAX/(1024*1024) || value < LLONG_MIN/(1024*1024))
-       errno = ERANGE;
-     else value *= 1024*1024;
-     endptr++;
+       endptr++;
+       break;
+     case 'm':
+       if (value > EXIM_ARITH_MAX/(1024*1024) || value < EXIM_ARITH_MIN/(1024*1024)) errno = ERANGE;
+       else value *= 1024*1024;
+       endptr++;
+       break;
+     case 'g':
+       if (value > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE;
+       else value *= 1024*1024*1024;
+       endptr++;
+       break;
      }
    if (errno == ERANGE)
      msg = US"absolute value of integer \"%s\" is too large (overflow)";
diff --combined src/src/globals.c
index 47f78e9dc00f87364087b20cc6e95f03c2df7d0e,74b6edb016132ddd00d90e2e4d040e7800fa579e..e9f62666527a129e27794228a9d1198bcc6c061f
@@@ -17,6 -17,8 +17,8 @@@ data blocks and hence have the opt_publ
  optionlist optionlist_auths[] = {
    { "client_condition", opt_stringptr | opt_public,
                   (void *)(offsetof(auth_instance, client_condition)) },
+   { "client_set_id", opt_stringptr | opt_public,
+                  (void *)(offsetof(auth_instance, set_client_id)) },
    { "driver",        opt_stringptr | opt_public,
                   (void *)(offsetof(auth_instance, driver_name)) },
    { "public_name",   opt_stringptr | opt_public,
@@@ -93,16 -95,31 +95,31 @@@ BOOL    move_frozen_messages   = FALSE
  cluttered in several places (e.g. during logging) if we can always refer to
  them. Also, the tls_ variables are now always visible. */
  
- BOOL    tls_active             = -1;
- int     tls_bits               = 0;
- BOOL    tls_certificate_verified = FALSE;
- uschar *tls_cipher             = NULL;
- BOOL    tls_on_connect         = FALSE;
- uschar *tls_on_connect_ports   = NULL;
- uschar *tls_peerdn             = NULL;
+ tls_support tls_in = {
+  -1,   /* tls_active */
+  0,    /* tls_bits */
+  FALSE,/* tls_certificate_verified */
+  NULL, /* tls_cipher */
+  FALSE,/* tls_on_connect */
+  NULL, /* tls_on_connect_ports */
+  NULL, /* tls_peerdn */
+  NULL  /* tls_sni */
+ };
+ tls_support tls_out = {
+  -1,   /* tls_active */
+  0,    /* tls_bits */
+  FALSE,/* tls_certificate_verified */
+  NULL, /* tls_cipher */
+  FALSE,/* tls_on_connect */
+  NULL, /* tls_on_connect_ports */
+  NULL, /* tls_peerdn */
+  NULL  /* tls_sni */
+ };
  
  #ifdef SUPPORT_TLS
  BOOL    gnutls_compat_mode     = FALSE;
+ BOOL    gnutls_enable_pkcs11   = FALSE;
  uschar *gnutls_require_mac     = NULL;
  uschar *gnutls_require_kx      = NULL;
  uschar *gnutls_require_proto   = NULL;
@@@ -123,12 -140,17 +140,17 @@@ BOOL    tls_offered            = FALSE
  uschar *tls_privatekey         = NULL;
  BOOL    tls_remember_esmtp     = FALSE;
  uschar *tls_require_ciphers    = NULL;
- uschar *tls_sni                = NULL;
  uschar *tls_try_verify_hosts   = NULL;
  uschar *tls_verify_certificates= NULL;
  uschar *tls_verify_hosts       = NULL;
  #endif
  
+ #ifdef EXPERIMENTAL_PRDR
+ /* Per Recipient Data Response variables */
+ BOOL    prdr_enable            = FALSE;
+ BOOL    prdr_requested         = FALSE;
+ const pcre *regex_PRDR         = NULL;
+ #endif
  
  /* Input-reading functions for messages, so we can use special ones for
  incoming TCP/IP. The defaults use stdin. We never need these for any
@@@ -173,16 -195,22 +195,22 @@@ int address_expansions_count = sizeof(a
  
  header_line *acl_added_headers = NULL;
  tree_node *acl_anchor          = NULL;
+ uschar *acl_arg[9]             = {NULL, NULL, NULL, NULL, NULL,
+                                   NULL, NULL, NULL, NULL};
+ int     acl_narg               = 0;
  
  uschar *acl_not_smtp           = NULL;
  #ifdef WITH_CONTENT_SCAN
  uschar *acl_not_smtp_mime      = NULL;
  #endif
  uschar *acl_not_smtp_start     = NULL;
+ uschar *acl_removed_headers    = NULL;
  uschar *acl_smtp_auth          = NULL;
  uschar *acl_smtp_connect       = NULL;
  uschar *acl_smtp_data          = NULL;
+ #ifdef EXPERIMENTAL_PRDR
+ uschar *acl_smtp_data_prdr     = NULL;
+ #endif
  #ifndef DISABLE_DKIM
  uschar *acl_smtp_dkim          = NULL;
  #endif
@@@ -216,6 -244,9 +244,9 @@@ uschar *acl_wherenames[]       = { US"R
                                     US"MIME",
                                     US"DKIM",
                                     US"DATA",
+ #ifdef EXPERIMENTAL_PRDR
+                                    US"PRDR",
+ #endif
                                     US"non-SMTP",
                                     US"AUTH",
                                     US"connection",
                                     US"NOTQUIT",
                                     US"QUIT",
                                     US"STARTTLS",
-                                    US"VRFY"
+                                    US"VRFY",
+                                  US"delivery",
+                                  US"unknown"
                                   };
  
  uschar *acl_wherecodes[]       = { US"550",     /* RCPT */
                                     US"550",     /* MIME */
                                     US"550",     /* DKIM */
                                     US"550",     /* DATA */
+ #ifdef EXPERIMENTAL_PRDR
+                                    US"550",    /* RCPT PRDR */
+ #endif
                                     US"0",       /* not SMTP; not relevant */
                                     US"503",     /* AUTH */
                                     US"550",     /* connect */
                                     US"0",       /* NOTQUIT; not relevant */
                                     US"0",       /* QUIT; not relevant */
                                     US"550",     /* STARTTLS */
-                                    US"252"      /* VRFY */
+                                    US"252",     /* VRFY */
+                                  US"0",       /* delivery; not relevant */
+                                  US"0"        /* unknown; not relevant */
                                   };
  
  BOOL    active_local_from_check = FALSE;
  BOOL    active_local_sender_retain = FALSE;
+ int     body_8bitmime = 0;
  BOOL    accept_8bitmime        = TRUE; /* deliberately not RFC compliant */
  address_item  *addr_duplicate  = NULL;
  
@@@ -291,6 -330,9 +330,9 @@@ address_item address_defaults = 
    NULL,                 /* cipher */
    NULL,                 /* peerdn */
    #endif
+   NULL,                       /* authenticator */
+   NULL,                       /* auth_id */
+   NULL,                       /* auth_sndr */
    (uid_t)(-1),          /* uid */
    (gid_t)(-1),          /* gid */
    0,                    /* flags */
@@@ -344,6 -386,7 +386,7 @@@ auth_instance auth_defaults    = 
      NULL,                      /* client_condition */
      NULL,                      /* public_name */
      NULL,                      /* set_id */
+     NULL,                      /* set_client_id */
      NULL,                      /* server_mail_auth_condition */
      NULL,                      /* server_debug_string */
      NULL,                      /* server_condition */
@@@ -404,6 -447,9 +447,9 @@@ int     check_log_space        = 0
  BOOL    check_rfc2047_length   = TRUE;
  int     check_spool_inodes     = 0;
  int     check_spool_space      = 0;
+ uschar        *client_authenticator  = NULL;
+ uschar        *client_authenticated_id = NULL;
+ uschar        *client_authenticated_sender = NULL;
  int     clmacro_count          = 0;
  uschar *clmacros[MAX_CLMACROS];
  BOOL    config_changed         = FALSE;
@@@ -429,24 -475,14 +475,26 @@@ int     continue_sequence      = 1
  uschar *continue_transport     = NULL;
  
  uschar *csa_status             = NULL;
+ BOOL    cutthrough_delivery    = FALSE;
+ int     cutthrough_fd          = -1;
  
  BOOL    daemon_listen          = FALSE;
  uschar *daemon_smtp_port       = US"smtp";
  int     daemon_startup_retries = 9;
  int     daemon_startup_sleep   = 30;
  
 +#ifdef EXPERIMENTAL_DBL
 +int     dbl_defer_errno        = 0;
 +uschar *dbl_defer_errstr       = NULL;
 +uschar *dbl_delivery_query     = NULL;
 +uschar *dbl_delivery_ip        = NULL;
 +int     dbl_delivery_port      = 0;
 +uschar *dbl_delivery_fqdn      = NULL;
 +uschar *dbl_delivery_local_part= NULL;
 +uschar *dbl_delivery_domain    = NULL;
 +uschar *dbl_delivery_confirmation = NULL;
 +#endif
 +
  #ifdef EXPERIMENTAL_DCC
  BOOL    dcc_direct_add_header  = FALSE;
  uschar *dcc_header             = NULL;
@@@ -554,6 -590,18 +602,18 @@@ uschar *dkim_verify_signers      = US"$
  BOOL    dkim_collect_input       = FALSE;
  BOOL    dkim_disable_verify      = FALSE;
  #endif
+ #ifdef EXPERIMENTAL_DMARC
+ BOOL    dmarc_has_been_checked  = FALSE;
+ uschar *dmarc_ar_header         = NULL;
+ uschar *dmarc_forensic_sender   = NULL;
+ uschar *dmarc_history_file      = NULL;
+ uschar *dmarc_status            = NULL;
+ uschar *dmarc_status_text       = NULL;
+ uschar *dmarc_tld_file          = NULL;
+ uschar *dmarc_used_domain       = NULL;
+ BOOL    dmarc_disable_verify    = FALSE;
+ BOOL    dmarc_enable_forensic   = FALSE;
+ #endif
  
  uschar *dns_again_means_nonexist = NULL;
  int     dns_csa_search_limit   = 5;
@@@ -561,6 -609,7 +621,7 @@@ BOOL    dns_csa_use_reverse    = TRUE
  uschar *dns_ipv4_lookup        = NULL;
  int     dns_retrans            = 0;
  int     dns_retry              = 0;
+ int     dns_dnssec_ok          = -1; /* <0 = not coerced */
  int     dns_use_edns0          = -1; /* <0 = not coerced */
  uschar *dnslist_domain         = NULL;
  uschar *dnslist_matched        = NULL;
@@@ -724,6 -773,7 +785,7 @@@ selectors was getting close to filling 
  /* Note that this list must be in alphabetical order. */
  
  bit_table log_options[]        = {
+   { US"8bitmime",                     LX_8bitmime },
    { US"acl_warn_skipped",             LX_acl_warn_skipped },
    { US"address_rewrite",              L_address_rewrite },
    { US"all",                          L_all },
    { US"smtp_confirmation",            LX_smtp_confirmation },
    { US"smtp_connection",              L_smtp_connection },
    { US"smtp_incomplete_transaction",  L_smtp_incomplete_transaction },
+   { US"smtp_mailauth",                LX_smtp_mailauth },
    { US"smtp_no_mail",                 LX_smtp_no_mail },
    { US"smtp_protocol_error",          L_smtp_protocol_error },
    { US"smtp_syntax_error",            L_smtp_syntax_error },
@@@ -1047,6 -1098,8 +1110,8 @@@ router_instance  router_defaults = 
      NULL                       /* redirect_router */
  };
  
+ uschar *router_name            = NULL;
  ip_address_item *running_interfaces = NULL;
  BOOL    running_in_test_harness = FALSE;
  
@@@ -1078,6 -1131,7 +1143,7 @@@ uschar **sender_host_aliases   = &no_al
  uschar *sender_host_address    = NULL;
  uschar *sender_host_authenticated = NULL;
  unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32];
+ BOOL    sender_host_dnssec     = FALSE;
  uschar *sender_host_name       = NULL;
  int     sender_host_port       = 0;
  BOOL    sender_host_notsocket  = FALSE;
@@@ -1197,6 -1251,7 +1263,7 @@@ uschar *submission_domain      = NULL
  BOOL    submission_mode        = FALSE;
  uschar *submission_name        = NULL;
  BOOL    suppress_local_fixups  = FALSE;
+ BOOL    suppress_local_fixups_default = FALSE;
  BOOL    synchronous_delivery   = FALSE;
  BOOL    syslog_duplication     = TRUE;
  int     syslog_facility        = LOG_MAIL;
@@@ -1279,6 -1334,7 +1346,7 @@@ transport_instance  transport_defaults 
  };
  
  int     transport_count;
+ uschar *transport_name          = NULL;
  int     transport_newlines;
  uschar **transport_filter_argv  = NULL;
  int     transport_filter_timeout;
diff --combined src/src/globals.h
index 2e1ed46184a74e794e476fa90d4a1b1f24567870,db436c06d18285dd6787ff9120f189ee88ea6d7b..4f22832969e09f669bc77e24373200b2fad3378d
@@@ -74,16 -74,22 +74,22 @@@ extern BOOL    move_frozen_messages;   
  cluttered in several places (e.g. during logging) if we can always refer to
  them. Also, the tls_ variables are now always visible. */
  
- extern int     tls_active;             /* fd/socket when in a TLS session */
- extern int     tls_bits;               /* bits used in TLS session */
- extern BOOL    tls_certificate_verified; /* Client certificate verified */
- extern uschar *tls_cipher;             /* Cipher used */
- extern BOOL    tls_on_connect;         /* For older MTAs that don't STARTTLS */
- extern uschar *tls_on_connect_ports;   /* Ports always tls-on-connect */
- extern uschar *tls_peerdn;             /* DN from peer */
+ typedef struct {
+   int     active;             /* fd/socket when in a TLS session */
+   int     bits;               /* bits used in TLS session */
+   BOOL    certificate_verified; /* Client certificate verified */
+   uschar *cipher;             /* Cipher used */
+   BOOL    on_connect;         /* For older MTAs that don't STARTTLS */
+   uschar *on_connect_ports;   /* Ports always tls-on-connect */
+   uschar *peerdn;             /* DN from peer */
+   uschar *sni;                /* Server Name Indication */
+ } tls_support;
+ extern tls_support tls_in;
+ extern tls_support tls_out;
  
  #ifdef SUPPORT_TLS
  extern BOOL    gnutls_compat_mode;     /* Less security, more compatibility */
+ extern BOOL    gnutls_enable_pkcs11;   /* Let GnuTLS autoload PKCS11 modules */
  extern uschar *gnutls_require_mac;     /* So some can be avoided */
  extern uschar *gnutls_require_kx;      /* So some can be avoided */
  extern uschar *gnutls_require_proto;   /* So some can be avoided */
@@@ -102,7 -108,6 +108,6 @@@ extern BOOL    tls_offered;            
  extern uschar *tls_privatekey;         /* Private key file */
  extern BOOL    tls_remember_esmtp;     /* For YAEB */
  extern uschar *tls_require_ciphers;    /* So some can be avoided */
- extern uschar *tls_sni;                /* Server Name Indication */
  extern uschar *tls_try_verify_hosts;   /* Optional client verification */
  extern uschar *tls_verify_certificates;/* Path for certificates to check */
  extern uschar *tls_verify_hosts;       /* Mandatory client verification */
@@@ -128,16 -133,24 +133,24 @@@ extern uschar **address_expansions[ADDR
  /* General global variables */
  
  extern BOOL    accept_8bitmime;        /* Allow *BITMIME incoming */
+ extern int     body_8bitmime;          /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
  extern header_line *acl_added_headers; /* Headers added by an ACL */
  extern tree_node *acl_anchor;          /* Tree of named ACLs */
+ extern uschar *acl_arg[9];             /* Argument to ACL call */
+ extern int     acl_narg;               /* Number of arguments to ACL call */
  extern uschar *acl_not_smtp;           /* ACL run for non-SMTP messages */
  #ifdef WITH_CONTENT_SCAN
  extern uschar *acl_not_smtp_mime;      /* For MIME parts of ditto */
  #endif
  extern uschar *acl_not_smtp_start;     /* ACL run at the beginning of a non-SMTP session */
+ extern uschar *acl_removed_headers;    /* Headers deleted by an ACL */
  extern uschar *acl_smtp_auth;          /* ACL run for AUTH */
  extern uschar *acl_smtp_connect;       /* ACL run on SMTP connection */
  extern uschar *acl_smtp_data;          /* ACL run after DATA received */
+ #ifdef EXPERIMENTAL_PRDR
+ extern uschar *acl_smtp_data_prdr;     /* ACL run after DATA received if in PRDR mode*/
+ const extern pcre *regex_PRDR;         /* For recognizing PRDR settings */
+ #endif
  #ifndef DISABLE_DKIM
  extern uschar *acl_smtp_dkim;          /* ACL run for DKIM signatures / domains */
  #endif
@@@ -229,6 -242,9 +242,9 @@@ extern int     check_log_space;        
  extern BOOL    check_rfc2047_length;   /* Check RFC 2047 encoded string length */
  extern int     check_spool_inodes;     /* Minimum for message acceptance */
  extern int     check_spool_space;      /* Minimum for message acceptance */
+ extern uschar *client_authenticator;        /* Authenticator name used for smtp delivery */
+ extern uschar *client_authenticated_id;     /* "login" name used for SMTP AUTH */
+ extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */
  extern int     clmacro_count;          /* Number of command line macros */
  extern uschar *clmacros[];             /* Copy of them, for re-exec */
  extern int     connection_max_messages;/* Max down one SMTP connection */
@@@ -251,6 -267,8 +267,8 @@@ extern int     continue_sequence;      
  extern uschar *continue_transport;     /* Transport for continued delivery */
  
  extern uschar *csa_status;             /* Client SMTP Authorization result */
+ extern BOOL    cutthrough_delivery;    /* Deliver in foreground */
+ extern int     cutthrough_fd;          /* Connection for ditto */
  
  extern BOOL    daemon_listen;          /* True if listening required */
  extern uschar *daemon_smtp_port;       /* Can be a list of ports */
@@@ -265,18 -283,6 +283,18 @@@ extern uschar *dccifd_address;         
  extern uschar *dccifd_options;         /* options for the dccifd daemon */
  #endif
  
 +#ifdef EXPERIMENTAL_DBL
 +extern int     dbl_defer_errno;        /* error number set when a remote delivery is deferred with a host error */
 +extern uschar *dbl_defer_errstr;       /* error string set when a remote delivery is deferred with a host error */
 +extern uschar *dbl_delivery_query;     /* query string to log delivery info in DB */
 +extern uschar *dbl_delivery_ip;        /* IP of host, which has accepted delivery */
 +extern int     dbl_delivery_port;       /* port of host, which has accepted delivery */
 +extern uschar *dbl_delivery_fqdn;      /* FQDN of host, which has accepted delivery */
 +extern uschar *dbl_delivery_local_part;/* local part of address being delivered */
 +extern uschar *dbl_delivery_domain;    /* domain part of address being delivered */
 +extern uschar *dbl_delivery_confirmation; /* SMTP confirmation message */
 +#endif
 +
  extern BOOL    debug_daemon;           /* Debug the daemon process only */
  extern int     debug_fd;               /* The fd for debug_file */
  extern FILE   *debug_file;             /* Where to write debugging info */
@@@ -340,6 -346,18 +358,18 @@@ extern uschar *dkim_verify_signers;    
  extern BOOL    dkim_collect_input;     /* Runtime flag that tracks wether SMTP input is fed to DKIM validation */
  extern BOOL    dkim_disable_verify;    /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
  #endif
+ #ifdef EXPERIMENTAL_DMARC
+ extern BOOL    dmarc_has_been_checked; /* Global variable to check if test has been called yet */
+ extern uschar *dmarc_ar_header;        /* Expansion variable, suggested header for dmarc auth results */
+ extern uschar *dmarc_forensic_sender;  /* Set sender address for forensic reports */
+ extern uschar *dmarc_history_file;     /* Expansion variable, file to store dmarc results */
+ extern uschar *dmarc_status;           /* Expansion variable, one word value */
+ extern uschar *dmarc_status_text;      /* Expansion variable, human readable value */
+ extern uschar *dmarc_tld_file;         /* Mozilla TLDs text file */
+ extern uschar *dmarc_used_domain;      /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
+ extern BOOL    dmarc_disable_verify;   /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */
+ extern BOOL    dmarc_enable_forensic;  /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */
+ #endif
  
  extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
  extern int     dns_csa_search_limit;   /* How deep to search for CSA SRV records */
@@@ -347,6 -365,7 +377,7 @@@ extern BOOL    dns_csa_use_reverse;    
  extern uschar *dns_ipv4_lookup;        /* For these domains, don't look for AAAA (or A6) */
  extern int     dns_retrans;            /* Retransmission time setting */
  extern int     dns_retry;              /* Number of retries */
+ extern int     dns_dnssec_ok;          /* When constructing DNS query, set DO flag */
  extern int     dns_use_edns0;          /* Coerce EDNS0 support on/off in resolver. */
  extern uschar *dnslist_domain;         /* DNS (black) list domain */
  extern uschar *dnslist_matched;        /* DNS (black) list matched key */
@@@ -372,6 -391,7 +403,7 @@@ extern int     errors_sender_rc;       
  extern gid_t   exim_gid;               /* To be used with exim_uid */
  extern BOOL    exim_gid_set;           /* TRUE if exim_gid set */
  extern uschar *exim_path;              /* Path to exec exim */
+ extern const uschar *exim_sieve_extension_list[]; /* list of sieve extensions */
  extern uid_t   exim_uid;               /* Non-root uid for exim */
  extern BOOL    exim_uid_set;           /* TRUE if exim_uid set */
  extern int     expand_forbid;          /* RDO flags for forbidding things */
@@@ -556,6 -576,10 +588,10 @@@ extern uschar *percent_hack_domains;   
  extern uschar *pid_file_path;          /* For writing daemon pids */
  extern uschar *pipelining_advertise_hosts; /* As it says */
  extern BOOL    pipelining_enable;      /* As it says */
+ #ifdef EXPERIMENTAL_PRDR
+ extern BOOL    prdr_enable;            /* As it says */
+ extern BOOL    prdr_requested;         /* Connecting mail server wants PRDR */
+ #endif
  extern BOOL    preserve_message_logs;  /* Save msglog files */
  extern uschar *primary_hostname;       /* Primary name of this computer */
  extern BOOL    print_topbitchars;      /* Topbit chars are printing chars */
@@@ -653,6 -677,7 +689,7 @@@ extern uid_t   root_uid;               
  extern router_info routers_available[];/* Vector of available routers */
  extern router_instance *routers;       /* Chain of instantiated routers */
  extern router_instance router_defaults;/* Default values */
+ extern uschar *router_name;            /* Name of router last started */
  extern BOOL    running_in_test_harness; /*TRUE when running_status is patched */
  extern ip_address_item *running_interfaces; /* Host's running interfaces */
  extern uschar *running_status;         /* Flag string for testing */
@@@ -671,6 -696,7 +708,7 @@@ extern uschar *sender_fullhost;        
  extern uschar *sender_helo_name;       /* Host name from HELO/EHLO */
  extern uschar **sender_host_aliases;   /* Points to list of alias names */
  extern unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for incoming host */
+ extern BOOL    sender_host_dnssec;     /* true if sender_host_name verified in DNSSEC */
  extern BOOL    sender_host_notsocket;  /* Set for -bs and -bS */
  extern BOOL    sender_host_unknown;    /* TRUE for -bs and -bS except inetd */
  extern uschar *sender_ident;           /* Sender identity via RFC 1413 */
@@@ -780,6 -806,7 +818,7 @@@ extern uschar *submission_domain;      
  extern BOOL    submission_mode;        /* Can be forced from ACL */
  extern uschar *submission_name;        /* User name set from ACL */
  extern BOOL    suppress_local_fixups;  /* Can be forced from ACL */
+ extern BOOL    suppress_local_fixups_default; /* former is reset to this; override with -G */
  extern BOOL    synchronous_delivery;   /* TRUE if -odi is set */
  extern BOOL    syslog_duplication;     /* FALSE => no duplicate logging */
  extern int     syslog_facility;        /* As defined by Syslog.h */
@@@ -806,6 -833,7 +845,7 @@@ extern int     test_harness_load_avg;  
  extern int     thismessage_size_limit; /* Limit for this message */
  extern int     timeout_frozen_after;   /* Max time to keep frozen messages */
  extern BOOL    timestamps_utc;         /* Use UTC for all times */
+ extern uschar *transport_name;         /* Name of transport last started */
  extern int     transport_count;        /* Count of bytes transported */
  extern int     transport_newlines;     /* Accurate count of number of newline chars transported */
  extern uschar **transport_filter_argv; /* For on-the-fly filtering */
diff --combined src/src/readconf.c
index a0c5ca0cdd0d63b2898c9a684679a385f8881774,7f42bb7a97179814cdbbe02713459c52b721557d..d0608d7dee8d94f0f44f306b8f4c95c977dcea96
@@@ -140,6 -140,9 +140,9 @@@ static optionlist optionlist_config[] 
    { "acl_smtp_auth",            opt_stringptr,   &acl_smtp_auth },
    { "acl_smtp_connect",         opt_stringptr,   &acl_smtp_connect },
    { "acl_smtp_data",            opt_stringptr,   &acl_smtp_data },
+ #ifdef EXPERIMENTAL_PRDR
+   { "acl_smtp_data_prdr",       opt_stringptr,   &acl_smtp_data_prdr },
+ #endif
  #ifndef DISABLE_DKIM
    { "acl_smtp_dkim",            opt_stringptr,   &acl_smtp_dkim },
  #endif
    { "daemon_smtp_ports",        opt_stringptr,   &daemon_smtp_port },
    { "daemon_startup_retries",   opt_int,         &daemon_startup_retries },
    { "daemon_startup_sleep",     opt_time,        &daemon_startup_sleep },
 +#ifdef EXPERIMENTAL_DBL
 +  { "dbl_delivery_query",       opt_stringptr,   &dbl_delivery_query },
 +#endif
  #ifdef EXPERIMENTAL_DCC
    { "dcc_direct_add_header",    opt_bool,        &dcc_direct_add_header },
    { "dccifd_address",           opt_stringptr,   &dccifd_address },
    { "disable_ipv6",             opt_bool,        &disable_ipv6 },
  #ifndef DISABLE_DKIM
    { "dkim_verify_signers",      opt_stringptr,   &dkim_verify_signers },
+ #endif
+ #ifdef EXPERIMENTAL_DMARC
+   { "dmarc_forensic_sender",    opt_stringptr,   &dmarc_forensic_sender },
+   { "dmarc_history_file",       opt_stringptr,   &dmarc_history_file },
+   { "dmarc_tld_file",           opt_stringptr,   &dmarc_tld_file },
  #endif
    { "dns_again_means_nonexist", opt_stringptr,   &dns_again_means_nonexist },
    { "dns_check_names_pattern",  opt_stringptr,   &check_dns_names_pattern },
    { "dns_csa_search_limit",     opt_int,         &dns_csa_search_limit },
    { "dns_csa_use_reverse",      opt_bool,        &dns_csa_use_reverse },
+   { "dns_dnssec_ok",            opt_int,         &dns_dnssec_ok },
    { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
    { "dns_retrans",              opt_time,        &dns_retrans },
    { "dns_retry",                opt_int,         &dns_retry },
    { "gecos_pattern",            opt_stringptr,   &gecos_pattern },
  #ifdef SUPPORT_TLS
    { "gnutls_compat_mode",       opt_bool,        &gnutls_compat_mode },
+   { "gnutls_enable_pkcs11",     opt_bool,        &gnutls_enable_pkcs11 },
    /* These three gnutls_require_* options stopped working in Exim 4.80 */
    { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
    { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
  #endif
    { "pid_file_path",            opt_stringptr,   &pid_file_path },
    { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+ #ifdef EXPERIMENTAL_PRDR
+   { "prdr_enable",              opt_bool,        &prdr_enable },
+ #endif
    { "preserve_message_logs",    opt_bool,        &preserve_message_logs },
    { "primary_hostname",         opt_stringptr,   &primary_hostname },
    { "print_topbitchars",        opt_bool,        &print_topbitchars },
    { "tls_crl",                  opt_stringptr,   &tls_crl },
    { "tls_dh_max_bits",          opt_int,         &tls_dh_max_bits },
    { "tls_dhparam",              opt_stringptr,   &tls_dhparam },
- #if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
+ # if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
    { "tls_ocsp_file",            opt_stringptr,   &tls_ocsp_file },
- #endif
-   { "tls_on_connect_ports",     opt_stringptr,   &tls_on_connect_ports },
+ # endif
+   { "tls_on_connect_ports",     opt_stringptr,   &tls_in.on_connect_ports },
    { "tls_privatekey",           opt_stringptr,   &tls_privatekey },
    { "tls_remember_esmtp",       opt_bool,        &tls_remember_esmtp },
    { "tls_require_ciphers",      opt_stringptr,   &tls_require_ciphers },
@@@ -1375,7 -1385,6 +1388,6 @@@ uid_t uid
  gid_t gid;
  BOOL boolvalue = TRUE;
  BOOL freesptr = TRUE;
- BOOL extra_condition = FALSE;
  optionlist *ol, *ol2;
  struct passwd *pw;
  void *reset_point;
@@@ -1438,16 -1447,9 +1450,9 @@@ if (ol == NULL
    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name);
    }
  
- if ((ol->type & opt_set) != 0)
-   {
-   uschar *mname = name;
-   if (Ustrncmp(mname, "no_", 3) == 0) mname += 3;
-   if (Ustrcmp(mname, "condition") == 0)
-     extra_condition = TRUE;
-   else
-     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-       "\"%s\" option set for the second time", mname);
-   }
+ if ((ol->type & opt_set)  && !(ol->type & (opt_rep_con | opt_rep_str)))
+   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+     "\"%s\" option set for the second time", name);
  
  ol->type |= opt_set | issecure;
  type = ol->type & opt_mask;
@@@ -1531,7 -1533,7 +1536,7 @@@ switch (type
        str_target = (uschar **)(ol->value);
      else
        str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
-     if (extra_condition)
+     if (ol->type & opt_rep_con)
        {
        /* We already have a condition, we're conducting a crude hack to let
        multiple condition rules be chained together, despite storing them in
        strtemp = string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
            saved_condition, sptr);
        *str_target = string_copy_malloc(strtemp);
-       /* TODO(pdp): there is a memory leak here when we set 3 or more
-       conditions; I still don't understand the store mechanism enough
-       to know what's the safe way to free content from an earlier store.
+       /* TODO(pdp): there is a memory leak here and just below
+       when we set 3 or more conditions; I still don't
+       understand the store mechanism enough to know
+       what's the safe way to free content from an earlier store.
        AFAICT, stores stack, so freeing an early stored item also stores
        all data alloc'd after it.  If we knew conditions were adjacent,
        we could survive that, but we don't.  So I *think* we need to take
        Because we only do this once, near process start-up, I'm prepared to
        let this slide for the time being, even though it rankles.  */
        }
+     else if (*str_target && (ol->type & opt_rep_str))
+      {
+       uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
+       saved_condition = *str_target;
+       strtemp = saved_condition + Ustrlen(saved_condition)-1;
+       if (*strtemp == sep) *strtemp = 0;      /* eliminate trailing list-sep */
+       strtemp = string_sprintf("%s%c%s", saved_condition, sep, sptr);
+       *str_target = string_copy_malloc(strtemp);
+      }
      else
        {
        *str_target = sptr;
@@@ -2153,13 -2165,14 +2168,14 @@@ Arguments
                     resides.
    oltop          points to the option list in which ol exists
    last           one more than the offset of the last entry in optop
+   no_labels      do not show "foo = " at the start.
  
  Returns:         nothing
  */
  
  static void
  print_ol(optionlist *ol, uschar *name, void *options_block,
-   optionlist *oltop, int last)
+   optionlist *oltop, int last, BOOL no_labels)
  {
  struct passwd *pw;
  struct group *gr;
@@@ -2181,7 -2194,11 +2197,11 @@@ if (ol == NULL
  
  if (!admin_user && (ol->type & opt_secure) != 0)
    {
-   printf("%s = <value not displayable>\n", name);
+   const char * const hidden = "<value not displayable>";
+   if (no_labels)
+     printf("%s\n", hidden);
+   else
+     printf("%s = %s\n", name, hidden);
    return;
    }
  
@@@ -2200,11 -2217,13 +2220,13 @@@ switch(ol->type & opt_mask
    case opt_stringptr:
    case opt_rewrite:        /* Show the text value */
    s = *((uschar **)value);
-   printf("%s = %s\n", name, (s == NULL)? US"" : string_printing2(s, FALSE));
+   if (!no_labels) printf("%s = ", name);
+   printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE));
    break;
  
    case opt_int:
-   printf("%s = %d\n", name, *((int *)value));
+   if (!no_labels) printf("%s = ", name);
+   printf("%d\n", *((int *)value));
    break;
  
    case opt_mkint:
          c = 'M';
          x >>= 10;
          }
-       printf("%s = %d%c\n", name, x, c);
+       if (!no_labels) printf("%s = ", name);
+       printf("%d%c\n", x, c);
+       }
+     else
+       {
+       if (!no_labels) printf("%s = ", name);
+       printf("%d\n", x);
        }
-     else printf("%s = %d\n", name, x);
      }
    break;
  
    case opt_Kint:
      {
      int x = *((int *)value);
-     if (x == 0) printf("%s = 0\n", name);
-       else if ((x & 1023) == 0) printf("%s = %dM\n", name, x >> 10);
-         else printf("%s = %dK\n", name, x);
+     if (!no_labels) printf("%s = ", name);
+     if (x == 0) printf("0\n");
+       else if ((x & 1023) == 0) printf("%dM\n", x >> 10);
+         else printf("%dK\n", x);
      }
    break;
  
    case opt_octint:
-   printf("%s = %#o\n", name, *((int *)value));
+   if (!no_labels) printf("%s = ", name);
+   printf("%#o\n", *((int *)value));
    break;
  
    /* Can be negative only when "unset", in which case integer */
      int d = 100;
      if (x < 0) printf("%s =\n", name); else
        {
-       printf("%s = %d.", name, x/1000);
+       if (!no_labels) printf("%s = ", name);
+       printf("%d.", x/1000);
        do
          {
          printf("%d", f/d);
        if (options_block != NULL)
          value2 = (void *)((uschar *)options_block + (long int)value2);
        s = *((uschar **)value2);
-       printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s));
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", (s == NULL)? US"" : string_printing(s));
        break;
        }
      }
    /* Else fall through */
  
    case opt_uid:
+   if (!no_labels) printf("%s = ", name);
    if (! *get_set_flag(name, oltop, last, options_block))
-     printf("%s =\n", name);
+     printf("\n");
    else
      {
      pw = getpwuid(*((uid_t *)value));
      if (pw == NULL)
-       printf("%s = %ld\n", name, (long int)(*((uid_t *)value)));
-     else printf("%s = %s\n", name, pw->pw_name);
+       printf("%ld\n", (long int)(*((uid_t *)value)));
+     else printf("%s\n", pw->pw_name);
      }
    break;
  
        if (options_block != NULL)
          value2 = (void *)((uschar *)options_block + (long int)value2);
        s = *((uschar **)value2);
-       printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s));
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", (s == NULL)? US"" : string_printing(s));
        break;
        }
      }
    /* Else fall through */
  
    case opt_gid:
+   if (!no_labels) printf("%s = ", name);
    if (! *get_set_flag(name, oltop, last, options_block))
-     printf("%s =\n", name);
+     printf("\n");
    else
      {
      gr = getgrgid(*((int *)value));
      if (gr == NULL)
-        printf("%s = %ld\n", name, (long int)(*((int *)value)));
-     else printf("%s = %s\n", name, gr->gr_name);
+        printf("%ld\n", (long int)(*((int *)value)));
+     else printf("%s\n", gr->gr_name);
      }
    break;
  
    case opt_uidlist:
    uidlist = *((uid_t **)value);
-   printf("%s =", name);
+   if (!no_labels) printf("%s =", name);
    if (uidlist != NULL)
      {
      int i;
      uschar sep = ' ';
+     if (no_labels) sep = '\0';
      for (i = 1; i <= (int)(uidlist[0]); i++)
        {
        uschar *name = NULL;
        pw = getpwuid(uidlist[i]);
        if (pw != NULL) name = US pw->pw_name;
-       if (name != NULL) printf("%c%s", sep, name);
-         else printf("%c%ld", sep, (long int)(uidlist[i]));
+       if (sep != '\0') printf("%c", sep);
+       if (name != NULL) printf("%s", name);
+         else printf("%ld", (long int)(uidlist[i]));
        sep = ':';
        }
      }
  
    case opt_gidlist:
    gidlist = *((gid_t **)value);
-   printf("%s =", name);
+   if (!no_labels) printf("%s =", name);
    if (gidlist != NULL)
      {
      int i;
      uschar sep = ' ';
+     if (no_labels) sep = '\0';
      for (i = 1; i <= (int)(gidlist[0]); i++)
        {
        uschar *name = NULL;
        gr = getgrgid(gidlist[i]);
        if (gr != NULL) name = US gr->gr_name;
-       if (name != NULL) printf("%c%s", sep, name);
-         else printf("%c%ld", sep, (long int)(gidlist[i]));
+       if (sep != '\0') printf("%c", sep);
+       if (name != NULL) printf("%s", name);
+         else printf("%ld", (long int)(gidlist[i]));
        sep = ':';
        }
      }
    break;
  
    case opt_time:
-   printf("%s = %s\n", name, readconf_printtime(*((int *)value)));
+   if (!no_labels) printf("%s = ", name);
+   printf("%s\n", readconf_printtime(*((int *)value)));
    break;
  
    case opt_timelist:
      {
      int i;
      int *list = (int *)value;
-     printf("%s = ", name);
+     if (!no_labels) printf("%s = ", name);
      for (i = 0; i < list[1]; i++)
        printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
      printf("\n");
      s = *((uschar **)value2);
      if (s != NULL)
        {
-       printf("%s = %s\n", name, string_printing(s));
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", string_printing(s));
        break;
        }
      /* s == NULL => string not set; fall through */
@@@ -2441,12 -2478,13 +2481,13 @@@ driver whose options are to be printed
  Arguments:
    name        option name if type == NULL; else driver name
    type        NULL or driver type name, as described above
+   no_labels   avoid the "foo = " at the start of an item
  
  Returns:      nothing
  */
  
  void
- readconf_print(uschar *name, uschar *type)
+ readconf_print(uschar *name, uschar *type, BOOL no_labels)
  {
  BOOL names_only = FALSE;
  optionlist *ol;
@@@ -2473,8 -2511,11 +2514,11 @@@ if (type == NULL
        if (t != NULL)
          {
          found = TRUE;
-         printf("%slist %s = %s\n", types[i], name+1,
-           ((namedlist_block *)(t->data.ptr))->string);
+         if (no_labels)
+           printf("%s\n", ((namedlist_block *)(t->data.ptr))->string);
+         else
+           printf("%slist %s = %s\n", types[i], name+1,
+             ((namedlist_block *)(t->data.ptr))->string);
          }
        }
  
           ol < optionlist_config + optionlist_config_size; ol++)
        {
        if ((ol->type & opt_hidden) == 0)
-         print_ol(ol, US ol->name, NULL, optionlist_config, optionlist_config_size);
+         print_ol(ol, US ol->name, NULL,
+             optionlist_config, optionlist_config_size,
+             no_labels);
        }
      return;
      }
           ol < local_scan_options + local_scan_options_count; ol++)
        {
        print_ol(ol, US ol->name, NULL, local_scan_options,
-         local_scan_options_count);
+         local_scan_options_count, no_labels);
        }
      #endif
      return;
    else
      {
      print_ol(find_option(name, optionlist_config, optionlist_config_size),
-       name, NULL, optionlist_config, optionlist_config_size);
+       name, NULL, optionlist_config, optionlist_config_size, no_labels);
      return;
      }
    }
@@@ -2644,14 -2687,14 +2690,14 @@@ for (; d != NULL; d = d->next
    for (ol = ol2; ol < ol2 + size; ol++)
      {
      if ((ol->type & opt_hidden) == 0)
-       print_ol(ol, US ol->name, d, ol2, size);
+       print_ol(ol, US ol->name, d, ol2, size, no_labels);
      }
  
    for (ol = d->info->options;
         ol < d->info->options + *(d->info->options_count); ol++)
      {
      if ((ol->type & opt_hidden) == 0)
-       print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count));
+       print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count), no_labels);
      }
    if (name != NULL) return;
    }
@@@ -3786,7 -3829,7 +3832,7 @@@ while ((p = get_config_line()) != NULL
    pp = p;
    while (mac_isgraph(*p)) p++;
    if (p - pp <= 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-     "missing error type");
+     "missing error type in retry rule");
  
    /* Test error names for things we understand. */
  
index 814a63085b3ce5c50fbc6fc959671f7c8ebd4245,25cc5490a8273ef394849da4b8435327b4894728..04bee7fab3d26ccdbfdcb2319d065aca81351379
@@@ -35,10 -35,6 +35,10 @@@ optionlist smtp_transport_options[] = 
        (void *)offsetof(transport_instance, connection_max_messages) },
    { "data_timeout",         opt_time,
        (void *)offsetof(smtp_transport_options_block, data_timeout) },
 +#ifdef EXPERIMENTAL_DBL
 +  { "dbl_host_defer_query",      opt_stringptr,
 +        (void *)offsetof(smtp_transport_options_block, dbl_host_defer_query) },
 +#endif
    { "delay_after_cutoff", opt_bool,
        (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
  #ifndef DISABLE_DKIM
@@@ -59,6 -55,8 +59,8 @@@
        (void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
    { "dns_search_parents",   opt_bool,
        (void *)offsetof(smtp_transport_options_block, dns_search_parents) },
+   { "dscp",                 opt_stringptr,
+       (void *)offsetof(smtp_transport_options_block, dscp) },
    { "fallback_hosts",       opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
    { "final_timeout",        opt_time,
    { "hosts_require_auth",   opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
  #ifdef SUPPORT_TLS
+ # if defined EXPERIMENTAL_OCSP
+   { "hosts_require_ocsp",   opt_stringptr,
+       (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
+ # endif
    { "hosts_require_tls",    opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
  #endif
    { "hosts_try_auth",       opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+ #ifdef EXPERIMENTAL_PRDR
+   { "hosts_try_prdr",       opt_stringptr,
+       (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
+ #endif
+ #ifdef SUPPORT_TLS
+   { "hosts_verify_avoid_tls", opt_stringptr,
+       (void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
+ #endif
    { "interface",            opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, interface) },
    { "keepalive",            opt_bool,
        (void *)offsetof(smtp_transport_options_block, tls_certificate) },
    { "tls_crl",              opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, tls_crl) },
+   { "tls_dh_min_bits",      opt_int,
+       (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
    { "tls_privatekey",       opt_stringptr,
        (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
    { "tls_require_ciphers",  opt_stringptr,
@@@ -164,11 -176,19 +180,19 @@@ smtp_transport_options_block smtp_trans
    NULL,                /* interface */
    NULL,                /* port */
    US"smtp",            /* protocol */
+   NULL,                /* DSCP */
    NULL,                /* serialize_hosts */
    NULL,                /* hosts_try_auth */
    NULL,                /* hosts_require_auth */
+ #ifdef EXPERIMENTAL_PRDR
+   NULL,                /* hosts_try_prdr */
+ #endif
+ #ifdef EXPERIMENTAL_OCSP
+   NULL,                /* hosts_require_ocsp */
+ #endif
    NULL,                /* hosts_require_tls */
    NULL,                /* hosts_avoid_tls */
+   US"*",               /* hosts_verify_avoid_tls */
    NULL,                /* hosts_avoid_pipelining */
    NULL,                /* hosts_avoid_esmtp */
    NULL,                /* hosts_nopass_tls */
    NULL,                /* gnutls_require_kx */
    NULL,                /* gnutls_require_mac */
    NULL,                /* gnutls_require_proto */
+   NULL,                /* tls_sni */
    NULL,                /* tls_verify_certificates */
-   TRUE,                /* tls_tempfail_tryclear */
-   NULL                 /* tls_sni */
+   EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+                        /* tls_dh_min_bits */
+   TRUE                 /* tls_tempfail_tryclear */
  #endif
  #ifndef DISABLE_DKIM
   ,NULL,                /* dkim_canon */
    NULL,                /* dkim_sign_headers */
    NULL                 /* dkim_strict */
  #endif
 +#ifdef EXPERIMENTAL_DBL
 + ,NULL                 /* dbl_host_defer_query */
 +#endif
  };
  
  
@@@ -558,52 -577,6 +584,52 @@@ els
  
  
  
 +#ifdef EXPERIMENTAL_DBL
 +/*************************************************
 +*          Write error message to database log   *
 +*************************************************/
 +
 +/* This writes to the database log
 +
 +Arguments:
 +  dbl_host_defer_query  dbl_host_defer_query from the transport options block
 +  addr                  the address item containing error information
 +  host                  the current host
 +
 +Returns:   nothing
 +*/
 +
 +static void
 +dbl_write_defer_log(uschar *dbl_host_defer_query, address_item *addr, host_item *host)
 +{
 +if (dbl_host_defer_query == NULL)
 +      return;
 +
 +dbl_delivery_ip = string_copy(host->address);
 +dbl_delivery_port = (host->port == PORT_NONE)? 25 : host->port;
 +dbl_delivery_fqdn = string_copy(host->name);
 +dbl_delivery_local_part = string_copy(addr->local_part);
 +dbl_delivery_domain = string_copy(addr->domain);
 +dbl_defer_errno = addr->basic_errno;
 +
 +dbl_defer_errstr = NULL;
 +if (addr->message != NULL)
 +  if (addr->basic_errno > 0)
 +    dbl_defer_errstr = string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno));
 +  else
 +      dbl_defer_errstr = string_copy(addr->message);
 +else if (addr->basic_errno > 0)
 +  dbl_defer_errstr = string_copy(strerror(addr->basic_errno));
 +
 +DEBUG(D_transport) {
 +      debug_printf("  DBL(host defer): dbl_host_defer_query=|%s| dbl_delivery_IP=%s\n", dbl_host_defer_query, dbl_delivery_ip);
 +}
 +expand_string(dbl_host_defer_query);
 +}
 +#endif
 +
 +
 +
  /*************************************************
  *           Synchronize SMTP responses           *
  *************************************************/
@@@ -841,6 -814,229 +867,229 @@@ return yield
  
  
  
+ /* Do the client side of smtp-level authentication */
+ /*
+ Arguments:
+   buffer      EHLO response from server (gets overwritten)
+   addrlist      chain of potential addresses to deliver
+   host          host to deliver to
+   ob          transport options
+   ibp, obp    comms channel control blocks
+ Returns:
+   OK                  Success, or failed (but not required): global "smtp_authenticated" set
+   DEFER                       Failed authentication (and was required)
+   ERROR                       Internal problem
+   FAIL_SEND           Failed communications - transmit
+   FAIL                        - response
+ */
+ int
+ smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
+     smtp_transport_options_block *ob, BOOL is_esmtp,
+     smtp_inblock *ibp, smtp_outblock *obp)
+ {
+   int require_auth;
+   uschar *fail_reason = US"server did not advertise AUTH support";
+   smtp_authenticated = FALSE;
+   client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+   require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
+     host->name, host->address, NULL);
+   if (is_esmtp && !regex_AUTH) regex_AUTH =
+       regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+             FALSE, TRUE);
+   if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+     {
+     uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+     expand_nmax = -1;                          /* reset */
+     /* Must not do this check until after we have saved the result of the
+     regex match above. */
+     if (require_auth == OK ||
+         verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
+           host->address, NULL) == OK)
+       {
+       auth_instance *au;
+       fail_reason = US"no common mechanisms were found";
+       DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+       /* Scan the configured authenticators looking for one which is configured
+       for use as a client, which is not suppressed by client_condition, and
+       whose name matches an authentication mechanism supported by the server.
+       If one is found, attempt to authenticate by calling its client function.
+       */
+       for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+         {
+         uschar *p = names;
+         if (!au->client ||
+             (au->client_condition != NULL &&
+              !expand_check_condition(au->client_condition, au->name,
+                US"client authenticator")))
+           {
+           DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+             au->name,
+             (au->client)? "client_condition is false" :
+                           "not configured as a client");
+           continue;
+           }
+         /* Loop to scan supported server mechanisms */
+         while (*p != 0)
+           {
+           int rc;
+           int len = Ustrlen(au->public_name);
+           while (isspace(*p)) p++;
+           if (strncmpic(au->public_name, p, len) != 0 ||
+               (p[len] != 0 && !isspace(p[len])))
+             {
+             while (*p != 0 && !isspace(*p)) p++;
+             continue;
+             }
+           /* Found data for a listed mechanism. Call its client entry. Set
+           a flag in the outblock so that data is overwritten after sending so
+           that reflections don't show it. */
+           fail_reason = US"authentication attempt(s) failed";
+           obp->authenticating = TRUE;
+           rc = (au->info->clientcode)(au, ibp, obp,
+             ob->command_timeout, buffer, bufsize);
+           obp->authenticating = FALSE;
+           DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
+             au->name, rc);
+           /* A temporary authentication failure must hold up delivery to
+           this host. After a permanent authentication failure, we carry on
+           to try other authentication methods. If all fail hard, try to
+           deliver the message unauthenticated unless require_auth was set. */
+           switch(rc)
+             {
+             case OK:
+             smtp_authenticated = TRUE;   /* stops the outer loop */
+           client_authenticator = au->name;
+           if (au->set_client_id != NULL)
+             client_authenticated_id = expand_string(au->set_client_id);
+             break;
+             /* Failure after writing a command */
+             case FAIL_SEND:
+             return FAIL_SEND;
+             /* Failure after reading a response */
+             case FAIL:
+             if (errno != 0 || buffer[0] != '5') return FAIL;
+             log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+               au->name, host->name, host->address, buffer);
+             break;
+             /* Failure by some other means. In effect, the authenticator
+             decided it wasn't prepared to handle this case. Typically this
+             is the result of "fail" in an expansion string. Do we need to
+             log anything here? Feb 2006: a message is now put in the buffer
+             if logging is required. */
+             case CANCELLED:
+             if (*buffer != 0)
+               log_write(0, LOG_MAIN, "%s authenticator cancelled "
+                 "authentication H=%s [%s] %s", au->name, host->name,
+                 host->address, buffer);
+             break;
+             /* Internal problem, message in buffer. */
+             case ERROR:
+             set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
+             return ERROR;
+             }
+           break;  /* If not authenticated, try next authenticator */
+           }       /* Loop for scanning supported server mechanisms */
+         }         /* Loop for further authenticators */
+       }
+     }
+   /* If we haven't authenticated, but are required to, give up. */
+   if (require_auth == OK && !smtp_authenticated)
+     {
+     set_errno(addrlist, ERRNO_AUTHFAIL,
+       string_sprintf("authentication required but %s", fail_reason), DEFER,
+       FALSE);
+     return DEFER;
+     }
+   
+   return OK;
+ }
+ /* Construct AUTH appendix string for MAIL TO */
+ /*
+ Arguments
+   buffer      to build string
+   addrlist      chain of potential addresses to deliver
+   ob          transport options
+ Globals               smtp_authenticated
+               client_authenticated_sender
+ Return        True on error, otherwise buffer has (possibly empty) terminated string
+ */
+ BOOL
+ smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
+                   smtp_transport_options_block *ob)
+ {
+ uschar *local_authenticated_sender = authenticated_sender;
+ #ifdef notdef
+   debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+ #endif
+ if (ob->authenticated_sender != NULL)
+   {
+   uschar *new = expand_string(ob->authenticated_sender);
+   if (new == NULL)
+     {
+     if (!expand_string_forcedfail)
+       {
+       uschar *message = string_sprintf("failed to expand "
+         "authenticated_sender: %s", expand_string_message);
+       set_errno(addrlist, 0, message, DEFER, FALSE);
+       return TRUE;
+       }
+     }
+   else if (new[0] != 0) local_authenticated_sender = new;
+   }
+ /* Add the authenticated sender address if present */
+ if ((smtp_authenticated || ob->authenticated_sender_force) &&
+     local_authenticated_sender != NULL)
+   {
+   string_format(buffer, bufsize, " AUTH=%s",
+     auth_xtextencode(local_authenticated_sender,
+     Ustrlen(local_authenticated_sender)));
+   client_authenticated_sender = string_copy(local_authenticated_sender);
+   }
+ else
+   *buffer= 0;
+ return FALSE;
+ }
  /*************************************************
  *       Deliver address list to given host       *
  *************************************************/
@@@ -912,11 -1108,14 +1161,14 @@@ BOOL completed_address = FALSE
  BOOL esmtp = TRUE;
  BOOL pending_MAIL;
  BOOL pass_message = FALSE;
+ #ifdef EXPERIMENTAL_PRDR
+ BOOL prdr_offered = FALSE;
+ BOOL prdr_active;
+ #endif
  smtp_inblock inblock;
  smtp_outblock outblock;
  int max_rcpt = tblock->max_addresses;
  uschar *igquotstr = US"";
- uschar *local_authenticated_sender = authenticated_sender;
  uschar *helo_data = NULL;
  uschar *message = NULL;
  uschar new_message_id[MESSAGE_ID_LENGTH + 1];
@@@ -948,35 -1147,19 +1200,19 @@@ outblock.authenticating = FALSE
  
  /* Reset the parameters of a TLS session. */
  
- tls_bits = 0;
- tls_cipher = NULL;
- tls_peerdn = NULL;
+ tls_in.bits = 0;
+ tls_in.cipher = NULL; /* for back-compatible behaviour */
+ tls_in.peerdn = NULL;
  #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
- tls_sni = NULL;
+ tls_in.sni = NULL;
  #endif
  
- /* If an authenticated_sender override has been specified for this transport
- instance, expand it. If the expansion is forced to fail, and there was already
- an authenticated_sender for this message, the original value will be used.
- Other expansion failures are serious. An empty result is ignored, but there is
- otherwise no check - this feature is expected to be used with LMTP and other
- cases where non-standard addresses (e.g. without domains) might be required. */
- if (ob->authenticated_sender != NULL)
-   {
-   uschar *new = expand_string(ob->authenticated_sender);
-   if (new == NULL)
-     {
-     if (!expand_string_forcedfail)
-       {
-       uschar *message = string_sprintf("failed to expand "
-         "authenticated_sender: %s", expand_string_message);
-       set_errno(addrlist, 0, message, DEFER, FALSE);
-       return ERROR;
-       }
-     }
-   else if (new[0] != 0) local_authenticated_sender = new;
-   }
+ tls_out.bits = 0;
+ tls_out.cipher = NULL;        /* the one we may use for this transport */
+ tls_out.peerdn = NULL;
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ tls_out.sni = NULL;
+ #endif
  
  #ifndef SUPPORT_TLS
  if (smtps)
@@@ -994,7 -1177,7 +1230,7 @@@ if (continue_hostname == NULL
    {
    inblock.sock = outblock.sock =
      smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-       ob->keepalive);   /* This puts port into host->port */
+       ob->keepalive, ob->dscp);   /* This puts port into host->port */
  
    if (inblock.sock < 0)
      {
@@@ -1123,6 -1306,17 +1359,17 @@@ goto SEND_QUIT
      pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
        PCRE_EOPT, NULL, 0) >= 0;
    #endif
+   #ifdef EXPERIMENTAL_PRDR
+   prdr_offered = esmtp &&
+     (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
+       PCRE_EOPT, NULL, 0) >= 0) &&
+     (verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
+       host->address, NULL) == OK);
+   if (prdr_offered)
+     {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+   #endif
    }
  
  /* For continuing deliveries down the same channel, the socket is the standard
@@@ -1182,13 -1376,16 +1429,16 @@@ if (tls_offered && !suppress_tls &
      int rc = tls_client_start(inblock.sock,
        host,
        addrlist,
-       NULL,                    /* No DH param */
        ob->tls_certificate,
        ob->tls_privatekey,
        ob->tls_sni,
        ob->tls_verify_certificates,
        ob->tls_crl,
        ob->tls_require_ciphers,
+ #ifdef EXPERIMENTAL_OCSP
+       ob->hosts_require_ocsp,
+ #endif
+       ob->tls_dh_min_bits,
        ob->command_timeout);
  
      /* TLS negotiation failed; give an error. From outside, this function may
        {
        if (addr->transport_return == PENDING_DEFER)
          {
-         addr->cipher = tls_cipher;
-         addr->peerdn = tls_peerdn;
+         addr->cipher = tls_out.cipher;
+         addr->peerdn = tls_out.peerdn;
          }
        }
      }
@@@ -1226,7 -1423,7 +1476,7 @@@ another process, and so we won't have e
  expand it here. $sending_ip_address and $sending_port are set up right at the
  start of the Exim process (in exim.c). */
  
- if (tls_active >= 0)
+ if (tls_out.active >= 0)
    {
    char *greeting_cmd;
    if (helo_data == NULL)
@@@ -1288,13 -1485,10 +1538,10 @@@ we skip this. *
  
  if (continue_hostname == NULL
      #ifdef SUPPORT_TLS
-     || tls_active >= 0
+     || tls_out.active >= 0
      #endif
      )
    {
-   int require_auth;
-   uschar *fail_reason = US"server did not advertise AUTH support";
    /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
    lmtp_ignore_quota option was set. */
  
    DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
      smtp_use_pipelining? "" : "not ");
  
+ #ifdef EXPERIMENTAL_PRDR
+   prdr_offered = esmtp &&
+     pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
+       PCRE_EOPT, NULL, 0) >= 0 &&
+     verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
+       host->address, NULL) == OK;
+   if (prdr_offered)
+     {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+ #endif
    /* 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. */
  
-   smtp_authenticated = FALSE;
-   require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
-     host->name, host->address, NULL);
-   if (esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+   switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+                           ob, esmtp, &inblock, &outblock))
      {
-     uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
-     expand_nmax = -1;                          /* reset */
-     /* Must not do this check until after we have saved the result of the
-     regex match above. */
-     if (require_auth == OK ||
-         verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
-           host->address, NULL) == OK)
-       {
-       auth_instance *au;
-       fail_reason = US"no common mechanisms were found";
-       DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
-       /* Scan the configured authenticators looking for one which is configured
-       for use as a client, which is not suppressed by client_condition, and
-       whose name matches an authentication mechanism supported by the server.
-       If one is found, attempt to authenticate by calling its client function.
-       */
-       for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
-         {
-         uschar *p = names;
-         if (!au->client ||
-             (au->client_condition != NULL &&
-              !expand_check_condition(au->client_condition, au->name,
-                US"client authenticator")))
-           {
-           DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
-             au->name,
-             (au->client)? "client_condition is false" :
-                           "not configured as a client");
-           continue;
-           }
-         /* Loop to scan supported server mechanisms */
-         while (*p != 0)
-           {
-           int rc;
-           int len = Ustrlen(au->public_name);
-           while (isspace(*p)) p++;
-           if (strncmpic(au->public_name, p, len) != 0 ||
-               (p[len] != 0 && !isspace(p[len])))
-             {
-             while (*p != 0 && !isspace(*p)) p++;
-             continue;
-             }
-           /* Found data for a listed mechanism. Call its client entry. Set
-           a flag in the outblock so that data is overwritten after sending so
-           that reflections don't show it. */
-           fail_reason = US"authentication attempt(s) failed";
-           outblock.authenticating = TRUE;
-           rc = (au->info->clientcode)(au, &inblock, &outblock,
-             ob->command_timeout, buffer, sizeof(buffer));
-           outblock.authenticating = FALSE;
-           DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
-             au->name, rc);
-           /* A temporary authentication failure must hold up delivery to
-           this host. After a permanent authentication failure, we carry on
-           to try other authentication methods. If all fail hard, try to
-           deliver the message unauthenticated unless require_auth was set. */
-           switch(rc)
-             {
-             case OK:
-             smtp_authenticated = TRUE;   /* stops the outer loop */
-             break;
-             /* Failure after writing a command */
-             case FAIL_SEND:
-             goto SEND_FAILED;
-             /* Failure after reading a response */
-             case FAIL:
-             if (errno != 0 || buffer[0] != '5') goto RESPONSE_FAILED;
-             log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
-               au->name, host->name, host->address, buffer);
-             break;
-             /* Failure by some other means. In effect, the authenticator
-             decided it wasn't prepared to handle this case. Typically this
-             is the result of "fail" in an expansion string. Do we need to
-             log anything here? Feb 2006: a message is now put in the buffer
-             if logging is required. */
-             case CANCELLED:
-             if (*buffer != 0)
-               log_write(0, LOG_MAIN, "%s authenticator cancelled "
-                 "authentication H=%s [%s] %s", au->name, host->name,
-                 host->address, buffer);
-             break;
-             /* Internal problem, message in buffer. */
-             case ERROR:
-             yield = ERROR;
-             set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
-             goto SEND_QUIT;
-             }
-           break;  /* If not authenticated, try next authenticator */
-           }       /* Loop for scanning supported server mechanisms */
-         }         /* Loop for further authenticators */
-       }
-     }
-   /* If we haven't authenticated, but are required to, give up. */
-   if (require_auth == OK && !smtp_authenticated)
-     {
-     yield = DEFER;
-     set_errno(addrlist, ERRNO_AUTHFAIL,
-       string_sprintf("authentication required but %s", fail_reason), DEFER,
-       FALSE);
-     goto SEND_QUIT;
+     default:          goto SEND_QUIT;
+     case OK:          break;
+     case FAIL_SEND:   goto SEND_FAILED;
+     case FAIL:                goto RESPONSE_FAILED;
      }
    }
  
@@@ -1521,15 -1604,35 +1657,35 @@@ if (smtp_use_size
    while (*p) p++;
    }
  
- /* Add the authenticated sender address if present */
- if ((smtp_authenticated || ob->authenticated_sender_force) &&
-     local_authenticated_sender != NULL)
+ #ifdef EXPERIMENTAL_PRDR
+ prdr_active = FALSE;
+ if (prdr_offered)
    {
-   string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
-     auth_xtextencode(local_authenticated_sender,
-     Ustrlen(local_authenticated_sender)));
+   for (addr = first_addr; addr; addr = addr->next)
+     if (addr->transport_return == PENDING_DEFER)
+       {
+       for (addr = addr->next; addr; addr = addr->next)
+         if (addr->transport_return == PENDING_DEFER)
+         {                     /* at least two recipients to send */
+         prdr_active = TRUE;
+         sprintf(CS p, " PRDR"); p += 5;
+         goto prdr_is_active;
+         }
+       break;
+       }
    }
+ prdr_is_active:
+ #endif
+ /* If an authenticated_sender override has been specified for this transport
+ instance, expand it. If the expansion is forced to fail, and there was already
+ an authenticated_sender for this message, the original value will be used.
+ Other expansion failures are serious. An empty result is ignored, but there is
+ otherwise no check - this feature is expected to be used with LMTP and other
+ cases where non-standard addresses (e.g. without domains) might be required. */
+ if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
+     return ERROR;
  
  /* From here until we send the DATA command, we can make use of PIPELINING
  if the server host supports it. The code has to be able to check the responses
@@@ -1737,8 -1840,31 +1893,31 @@@ if (!ok) ok = TRUE; els
  
    smtp_command = US"end of data";
  
-   /* For SMTP, we now read a single response that applies to the whole message.
-   If it is OK, then all the addresses have been delivered. */
+ #ifdef EXPERIMENTAL_PRDR
+   /* For PRDR we optionally get a partial-responses warning
+    * followed by the individual responses, before going on with
+    * the overall response.  If we don't get the warning then deal
+    * with per non-PRDR. */
+   if(prdr_active)
+     {
+     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;
+         }
+     }
+   else
+ #endif
+   /* For non-PRDR SMTP, we now read a single response that applies to the
+   whole message.  If it is OK, then all the addresses have been delivered. */
  
    if (!lmtp)
      {
  
      /* Set up confirmation if needed - applies only to SMTP */
  
 -    if ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp)
 +    if (
 +              #ifndef EXPERIMENTAL_DBL
 +                      (log_extra_selector & LX_smtp_confirmation) != 0 &&
 +              #endif
 +                      !lmtp
 +       )
        {
        uschar *s = string_printing(buffer);
        conf = (s == buffer)? (uschar *)string_copy(s) : s;
        }
  
-     /* Process all transported addresses - for LMTP, read a status for
+     /* Process all transported addresses - for LMTP or PRDR, read a status for
      each one. */
  
      for (addr = addrlist; addr != first_addr; addr = addr->next)
        address. For temporary errors, add a retry item for the address so that
        it doesn't get tried again too soon. */
  
+ #ifdef EXPERIMENTAL_PRDR
+       if (lmtp || prdr_active)
+ #else
        if (lmtp)
+ #endif
          {
          if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
              ob->final_timeout))
            {
            if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
-           addr->message = string_sprintf("LMTP error after %s: %s",
+           addr->message = string_sprintf(
+ #ifdef EXPERIMENTAL_PRDR
+           "%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
+ #else
+           "LMTP error after %s: %s",
+ #endif
              big_buffer, string_printing(buffer));
            setflag(addr, af_pass_message);   /* Allow message to go to user */
            if (buffer[0] == '5')
              errno = ERRNO_DATA4XX;
              addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
              addr->transport_return = DEFER;
-             retry_add_item(addr, addr->address_retry_key, 0);
+ #ifdef EXPERIMENTAL_PRDR
+             if (!prdr_active)
+ #endif
+               retry_add_item(addr, addr->address_retry_key, 0);
              }
            continue;
            }
        addr->host_used = thost;
        addr->special_action = flag;
        addr->message = conf;
+ #ifdef EXPERIMENTAL_PRDR
+       if (prdr_active) addr->flags |= af_prdr_used;
+ #endif
        flag = '-';
  
-       /* Update the journal. For homonymic addresses, use the base address plus
-       the transport name. See lots of comments in deliver.c about the reasons
-       for the complications when homonyms are involved. Just carry on after
-       write error, as it may prove possible to update the spool file later. */
-       if (testflag(addr, af_homonym))
-         sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
-       else
-         sprintf(CS buffer, "%.500s\n", addr->unique);
-       DEBUG(D_deliver) debug_printf("journalling %s", buffer);
-       len = Ustrlen(CS buffer);
-       if (write(journal_fd, buffer, len) != len)
-         log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
-           "%s: %s", buffer, strerror(errno));
+ #ifdef EXPERIMENTAL_PRDR
+       if (!prdr_active)
+ #endif
+         {
+         /* Update the journal. For homonymic addresses, use the base address plus
+         the transport name. See lots of comments in deliver.c about the reasons
+         for the complications when homonyms are involved. Just carry on after
+         write error, as it may prove possible to update the spool file later. */
+   
+         if (testflag(addr, af_homonym))
+           sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+         else
+           sprintf(CS buffer, "%.500s\n", addr->unique);
+   
+         DEBUG(D_deliver) debug_printf("journalling %s", buffer);
+         len = Ustrlen(CS buffer);
+         if (write(journal_fd, buffer, len) != len)
+           log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+             "%s: %s", buffer, strerror(errno));
+         }
        }
  
+ #ifdef EXPERIMENTAL_PRDR
+       if (prdr_active)
+         {
+       /* PRDR - get the final, overall response.  For any non-success
+       upgrade all the address statuses. */
+         ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+           ob->final_timeout);
+         if (!ok)
+         {
+         if(errno == 0 && buffer[0] == '4')
+             {
+             errno = ERRNO_DATA4XX;
+             addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+             }
+         for (addr = addrlist; addr != first_addr; addr = addr->next)
+             if (buffer[0] == '5' || addr->transport_return == OK)
+               addr->transport_return = PENDING_OK; /* allow set_errno action */
+         goto RESPONSE_FAILED;
+         }
+       /* Update the journal, or setup retry. */
+         for (addr = addrlist; addr != first_addr; addr = addr->next)
+         if (addr->transport_return == OK)
+         {
+           if (testflag(addr, af_homonym))
+             sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+           else
+             sprintf(CS buffer, "%.500s\n", addr->unique);
+   
+           DEBUG(D_deliver) debug_printf("journalling(PRDR) %s", buffer);
+           len = Ustrlen(CS buffer);
+           if (write(journal_fd, buffer, len) != len)
+             log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+               "%s: %s", buffer, strerror(errno));
+         }
+       else if (addr->transport_return == DEFER)
+           retry_add_item(addr, addr->address_retry_key, -2);
+       }
+ #endif
      /* Ensure the journal file is pushed out to disk. */
  
      if (EXIMfsync(journal_fd) < 0)
@@@ -2053,7 -2234,7 +2292,7 @@@ if (completed_address && ok && send_qui
    BOOL more;
    if (first_addr != NULL || continue_more ||
          (
-            (tls_active < 0 ||
+            (tls_out.active < 0 ||
             verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
               host->address, NULL) != OK)
          &&
        don't get a good response, we don't attempt to pass the socket on. */
  
        #ifdef SUPPORT_TLS
-       if (tls_active >= 0)
+       if (tls_out.active >= 0)
          {
-         tls_close(TRUE);
+         tls_close(FALSE, TRUE);
          if (smtps)
            ok = FALSE;
          else
@@@ -2154,7 -2335,7 +2393,7 @@@ if (send_quit) (void)smtp_write_command
  END_OFF:
  
  #ifdef SUPPORT_TLS
- tls_close(TRUE);
+ tls_close(FALSE, TRUE);
  #endif
  
  /* Close the socket, and return the appropriate value, first setting
@@@ -2870,11 -3051,6 +3109,11 @@@ for (cutoff_retry = 0; expired &
                           first_addr->basic_errno != ERRNO_TLSFAILURE)
          write_logs(first_addr, host);
  
 +      #ifdef EXPERIMENTAL_DBL
 +      if (rc == DEFER)
 +        dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host);
 +      #endif
 +
        /* If STARTTLS was accepted, but there was a failure in setting up the
        TLS session (usually a certificate screwup), and the host is not in
        hosts_require_tls, and tls_tempfail_tryclear is true, try again, with
            expanded_hosts != NULL, &message_defer, TRUE);
          if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
            write_logs(first_addr, host);
 +        #ifdef EXPERIMENTAL_DBL
 +        if (rc == DEFER)
 +          dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host);
 +        #endif
          }
        #endif
        }
@@@ -3161,9 -3333,12 +3400,12 @@@ for (addr = addrlist; addr != NULL; add
  /* Update the database which keeps information about which messages are waiting
  for which hosts to become available. For some message-specific errors, the
  update_waiting flag is turned off because we don't want follow-on deliveries in
- those cases. */
+ those cases.  If this transport instance is explicitly limited to one message
+ per connection then follow-on deliveries are not possible and there's no need
+ to create/update the per-transport wait-<transport_name> database. */
  
- if (update_waiting) transport_update_waiting(hostlist, tblock->name);
+ if (update_waiting && tblock->connection_max_messages != 1)
+   transport_update_waiting(hostlist, tblock->name);
  
  END_TRANSPORT:
  
index 1f23d39c8abcaa549688622b0c1b559a500a0a1e,8e85294bc337d155234c1fd45d9cb7382fa14164..1768585256bf556b657fc086bdebf7d888121c33
@@@ -17,11 -17,19 +17,19 @@@ typedef struct 
    uschar *interface;
    uschar *port;
    uschar *protocol;
+   uschar *dscp;
    uschar *serialize_hosts;
    uschar *hosts_try_auth;
    uschar *hosts_require_auth;
+ #ifdef EXPERIMENTAL_PRDR
+   uschar *hosts_try_prdr;
+ #endif
+ #ifdef EXPERIMENTAL_OCSP
+   uschar *hosts_require_ocsp;
+ #endif
    uschar *hosts_require_tls;
    uschar *hosts_avoid_tls;
+   uschar *hosts_verify_avoid_tls;
    uschar *hosts_avoid_pipelining;
    uschar *hosts_avoid_esmtp;
    uschar *hosts_nopass_tls;
    uschar *gnutls_require_kx;
    uschar *gnutls_require_mac;
    uschar *gnutls_require_proto;
+   uschar *tls_sni;
    uschar *tls_verify_certificates;
+   int     tls_dh_min_bits;
    BOOL    tls_tempfail_tryclear;
-   uschar *tls_sni;
    #endif
    #ifndef DISABLE_DKIM
    uschar *dkim_domain;
@@@ -64,9 -73,6 +73,9 @@@
    uschar *dkim_sign_headers;
    uschar *dkim_strict;
    #endif
 +  #ifdef EXPERIMENTAL_DBL
 +  uschar *dbl_host_defer_query;
 +  #endif
  } smtp_transport_options_block;
  
  /* Data for reading the private options. */
@@@ -84,4 -90,12 +93,12 @@@ extern BOOL smtp_transport_entry(transp
  extern void smtp_transport_init(transport_instance *);
  extern void smtp_transport_closedown(transport_instance *);
  
+ extern int     smtp_auth(uschar *, unsigned, address_item *, host_item *,
+                smtp_transport_options_block *, BOOL,
+                smtp_inblock *, smtp_outblock *);
+ extern BOOL    smtp_mail_auth_str(uschar *, unsigned,
+                address_item *, smtp_transport_options_block *);
  /* End of transports/smtp.h */