[exim-cvs] Dual-tls - split management of TLS into in- and o…

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Dual-tls - split management of TLS into in- and out-bound connection-handling.
Gitweb: http://git.exim.org/exim.git/commitdiff/817d9f576cdfbc27cf0536be348645baf27d7836
Commit:     817d9f576cdfbc27cf0536be348645baf27d7836
Parent:     528fde2aff25fbe284332ad2ec4b8cb5fc8c7859
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sun Apr 29 21:02:27 2012 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Mon Jun 4 14:57:02 2012 +0100


    Dual-tls - split management of TLS into in- and out-bound connection-handling.


    Enables concurrent use from a single process, and thereby use for cutthrough delivery.
    As a side-effect EHLO and TLS use for verify callouts introduced.


    This was a manual import from elsewhere and is known to fail the test-suite.
---
 src/exim_monitor/em_globals.c |   15 +-
 src/src/acl.c                 |   29 +--
 src/src/auths/cyrus_sasl.c    |    8 +-
 src/src/auths/dovecot.c       |    6 +-
 src/src/daemon.c              |    4 +-
 src/src/deliver.c             |   30 ++-
 src/src/exim.c                |    6 +-
 src/src/expand.c              |   21 ++-
 src/src/functions.h           |    8 +-
 src/src/globals.c             |   29 ++-
 src/src/globals.h             |   22 ++-
 src/src/host.c                |    4 +-
 src/src/ip.c                  |    6 +-
 src/src/macros.h              |    2 +-
 src/src/readconf.c            |    6 +-
 src/src/receive.c             |  165 +++++++---------
 src/src/smtp_in.c             |   54 +++---
 src/src/smtp_out.c            |    4 +-
 src/src/spool_in.c            |   16 +-
 src/src/spool_out.c           |    8 +-
 src/src/tls-gnu.c             |   89 +++++-----
 src/src/tls-openssl.c         |  241 +++++++++++++----------
 src/src/transport.c           |   10 +-
 src/src/transports/smtp.c     |   31 ++--
 src/src/verify.c              |  430 +++++++++++++++++++++++++++++++----------
 25 files changed, 763 insertions(+), 481 deletions(-)


diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index 816d42d..366af7e 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -211,12 +211,15 @@ int     string_datestamp_length= 0;
 int     string_datestamp_type  = -1;


 BOOL    timestamps_utc         = FALSE;
-BOOL    tls_certificate_verified = FALSE;
-uschar *tls_cipher             = NULL;
-uschar *tls_peerdn             = NULL;
-#ifdef SUPPORT_TLS
-uschar *tls_sni                = NULL;
-#endif
+tls_support tls_in = {
+ -1,    /* tls_active */
+ FALSE,    /* tls_certificate_verified */
+ NULL,    /* tls_cipher */
+ FALSE,    /* tls_on_connect */
+ NULL,    /* tls_on_connect_ports */
+ NULL,    /* tls_peerdn */
+ NULL    /* tls_sni */
+};


 tree_node *tree_duplicates     = NULL;
 tree_node *tree_nonrecipients  = NULL;
diff --git a/src/src/acl.c b/src/src/acl.c
index d0688d1..3177ac8 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1615,7 +1615,7 @@ switch(vp->value)
     test whether it was successful or not. (This is for optional verification; for
     mandatory verification, the connection doesn't last this long.) */


-      if (tls_certificate_verified) return OK;
+      if (tls_in.certificate_verified) return OK;
       *user_msgptr = US"no verified certificate";
       return FAIL;


@@ -3166,11 +3166,11 @@ for (; cb != NULL; cb = cb->next)
     writing is poorly documented. */


     case ACLC_ENCRYPTED:
-    if (tls_cipher == NULL) rc = FAIL; else
+    if (tls_in.cipher == NULL) rc = FAIL; else
       {
       uschar *endcipher = NULL;
-      uschar *cipher = Ustrchr(tls_cipher, ':');
-      if (cipher == NULL) cipher = tls_cipher; else
+      uschar *cipher = Ustrchr(tls_in.cipher, ':');
+      if (cipher == NULL) cipher = tls_in.cipher; else
         {
         endcipher = Ustrchr(++cipher, ':');
         if (endcipher != NULL) *endcipher = 0;
@@ -3915,21 +3915,14 @@ if (where == ACL_WHERE_RCPT)


rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);

-/*XXX cutthrough - if requested,
-and WHERE_RCPT and not yet opened conn as reult of verify,
-and rc==OK
+/* Cutthrough - if requested,
+and WHERE_RCPT and not yet opened conn as result of recipient-verify,
+and rcpt acl returned accept,
+and first recipient (cancel on any subsequents)
open one now and run it up to RCPT acceptance.
-Query: what to do with xple rcpts? Avoid for now by only doing on 1st, and
-cancelling on any subsequents.
A failed verify should cancel cutthrough request.
-For now, ensure we only accept requests to cutthrough pre-data. Maybe relax that later.

-On a pre-data acl, if not accept and a cutthrough conn is open, close it. If accept and
-a cutthrough conn is open, send DATA command and setup byte-by-byte copy mode and
-cancel spoolfile-write mode.
-NB this means no DATA acl, no content checking - might want an option for that?.
-
-Initial implementation: dual-write to spool (do the no-spool later).
+Initial implementation: dual-write to spool.
Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection.

Cease cutthrough copy on rxd final dot; do not send one.
@@ -3940,10 +3933,6 @@ On data acl accept, terminate the dataphase on an open cutthrough conn. If acce
perm-rejected, reflect that to the original sender - and dump the spooled copy.
If temp-reject, close the conn (and keep the spooled copy).
If conn-failure, no action (and keep the spooled copy).
-
-
-XXX What about TLS? Callouts never seem to do it atm. but we ought to support it eventually.
-XXX What about pipelining? Callouts don't, and we probably don't care too much.
*/
switch (where)
{
diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c
index 7922363..ca58928 100644
--- a/src/src/auths/cyrus_sasl.c
+++ b/src/src/auths/cyrus_sasl.c
@@ -256,19 +256,19 @@ if( rc != SASL_OK )
return DEFER;
}

-if (tls_cipher)
+if (tls_in.cipher)
   {
-  rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_bits);
+  rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
   if (rc != SASL_OK)
     {
     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
-        tls_bits, sasl_errstring(rc, NULL, NULL));
+        tls_in.bits, sasl_errstring(rc, NULL, NULL));
     auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
     sasl_done();
     return DEFER;
     }
   else
-    HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_bits);
+    HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
   }
 else
   HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c
index ba0b894..0824240 100644
--- a/src/src/auths/dovecot.c
+++ b/src/src/auths/dovecot.c
@@ -241,10 +241,10 @@ int auth_dovecot_server(auth_instance *ablock, uschar *data)
        /* Added by PH: extra fields when TLS is in use or if the TCP/IP
        connection is local. */


-       if (tls_cipher != NULL)
+       if (tls_in.cipher != NULL)
                auth_extra_data = string_sprintf("secured\t%s%s",
-                   tls_certificate_verified? "valid-client-cert" : "",
-                   tls_certificate_verified? "\t" : "");
+                   tls_in.certificate_verified? "valid-client-cert" : "",
+                   tls_in.certificate_verified? "\t" : "");
        else if (interface_address != NULL &&
                 Ustrcmp(sender_host_address, interface_address) == 0)
                auth_extra_data = US"secured\t";
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 9385a91..3467f14 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -382,7 +382,7 @@ if (pid == 0)


/* Check for a tls-on-connect 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;

   /* Expand smtp_active_hostname if required. We do not do this any earlier,
   because it may depend on the local interface address (indeed, that is most
@@ -639,7 +639,7 @@ if (pid == 0)
         the data structures if necessary. */


         #ifdef SUPPORT_TLS
-        tls_close(FALSE);
+        tls_close(FALSE, FALSE);
         #endif


         /* Reset SIGHUP and SIGCHLD in the child in both cases. */
diff --git a/src/src/deliver.c b/src/src/deliver.c
index b4d0251..55a27b0 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -673,8 +673,15 @@ 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(address_item * addr, int logchar)
+delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
 {
 uschar *log_address;
 int size = 256;         /* Used for a temporary, */
@@ -689,12 +696,17 @@ 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;

log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
-s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+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)
+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
@@ -711,8 +723,10 @@ 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 (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);

@@ -796,7 +810,7 @@ if ((log_extra_selector & LX_deliver_time) != 0)
store we used to build the line after writing it. */

 s[ptr] = 0;
-log_write(0, LOG_MAIN, "%s", s);
+log_write(0, flags, "%s", s);
 store_reset(reset_point);
 return;
 }
@@ -992,7 +1006,7 @@ if (result == OK)
     child_done(addr, now);
     }


- delivery_log(addr, logchar);
+ delivery_log(LOG_MAIN, addr, logchar, NULL);
}


@@ -3948,7 +3962,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)

       /* 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 */


diff --git a/src/src/exim.c b/src/src/exim.c
index f50cc08..a59cfea 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -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));
@@ -3276,7 +3276,7 @@ for (i = 1; i < argc; i++)
     /* -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;
@@ -4093,7 +4093,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)
         {
diff --git a/src/src/expand.c b/src/src/expand.c
index 84167b6..3fb431c 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -612,13 +612,22 @@ static var_entry var_table[] = {
   { "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 },
+
+  { "tls_bits",            vtype_int,         &tls_in.bits },
+  { "tls_certificate_verified", vtype_int,    &tls_in.certificate_verified },
+  { "tls_cipher",          vtype_stringptr,   &tls_in.cipher },
+  { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+  { "tls_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
+
   { "tod_bsdinbox",        vtype_todbsdin,    NULL },
   { "tod_epoch",           vtype_tode,        NULL },
   { "tod_epoch_l",         vtype_todel,       NULL },
diff --git a/src/src/functions.h b/src/src/functions.h
index aa3d184..2500431 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -28,15 +28,15 @@ extern const char *
 extern int     tls_client_start(int, host_item *, address_item *, uschar *,
                  uschar *, uschar *, uschar *, uschar *, uschar *, uschar *,
                  int, int);
-extern void    tls_close(BOOL);
+extern void    tls_close(BOOL, BOOL);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
 extern int     tls_getc(void);
-extern int     tls_read(uschar *, size_t);
+extern int     tls_read(BOOL, uschar *, size_t);
 extern int     tls_server_start(const uschar *);
 extern BOOL    tls_smtp_buffered(void);
 extern int     tls_ungetc(int);
-extern int     tls_write(const uschar *, size_t);
+extern int     tls_write(BOOL, int, const uschar *, size_t);
 extern uschar *tls_validate_require_cipher(void);
 extern void    tls_version_report(FILE *);
 #ifndef USE_GNUTLS
@@ -93,7 +93,7 @@ extern void    debug_vprintf(const char *, va_list);
 extern void    decode_bits(unsigned int *, unsigned int *,
                   int, int, uschar *, bit_table *, int, uschar *, int);
 extern address_item *deliver_make_addr(uschar *, BOOL);
-extern void    delivery_log(address_item *, int);
+extern void    delivery_log(int, address_item *, int, uschar *);
 extern int     deliver_message(uschar *, BOOL, BOOL);
 extern void    deliver_msglog(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    deliver_set_expansions(address_item *);
diff --git a/src/src/globals.c b/src/src/globals.c
index 60b5a27..97c7166 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -93,13 +93,27 @@ 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;
@@ -123,7 +137,6 @@ 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;
diff --git a/src/src/globals.h b/src/src/globals.h
index dd28b91..7ed9d5a 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -74,13 +74,20 @@ extern BOOL    move_frozen_messages;   /* Get them out of the normal directory *
 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 */
+#ifndef USE_GNUTLS
+  uschar *sni;                /* Server Name Indication */
+#endif
+} tls_support;
+extern tls_support tls_in;
+extern tls_support tls_out;


 #ifdef SUPPORT_TLS
 extern BOOL    gnutls_compat_mode;     /* Less security, more compatibility */
@@ -102,7 +109,6 @@ extern BOOL    tls_offered;            /* Server offered TLS */
 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 */
diff --git a/src/src/host.c b/src/src/host.c
index 03d9443..785eea4 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -1177,10 +1177,10 @@ host_is_tls_on_connect_port(int port)
 {
 int sep = 0;
 uschar buffer[32];
-uschar *list = tls_on_connect_ports;
+uschar *list = tls_in.on_connect_ports;
 uschar *s;


-if (tls_on_connect) return TRUE;
+if (tls_in.on_connect) return TRUE;

while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
{
diff --git a/src/src/ip.c b/src/src/ip.c
index 11f0fd8..b0c9887 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -347,8 +347,10 @@ for (;;)
close down of the connection), set errno to zero; otherwise leave it alone. */

#ifdef SUPPORT_TLS
-if (tls_active == sock)
- rc = tls_read(buffer, buffsize);
+if (tls_out.active == sock)
+ rc = tls_read(FALSE, buffer, buffsize);
+else if (tls_in.active == sock)
+ rc = tls_read(TRUE, buffer, buffsize);
else
#endif
rc = recv(sock, buffer, buffsize, 0);
diff --git a/src/src/macros.h b/src/src/macros.h
index f7a22b6..b17a80e 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -81,7 +81,7 @@ as unsigned. */
a no-op once an SSL session is in progress. */

 #ifdef SUPPORT_TLS
-#define mac_smtp_fflush() if (tls_active < 0) fflush(smtp_out);
+#define mac_smtp_fflush() if (tls_in.active < 0) fflush(smtp_out);
 #else
 #define mac_smtp_fflush() fflush(smtp_out);
 #endif
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 553f2e4..750e0d3 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -418,10 +418,10 @@ static optionlist optionlist_config[] = {
   { "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 },
diff --git a/src/src/receive.c b/src/src/receive.c
index 108e8d4..d0fc0c2 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -716,9 +716,6 @@ Arguments:


 Returns:    One of the END_xxx values indicating why it stopped reading
 */
-/*XXX cutthrough - need to copy to destination, not including the
-    terminating dot, canonicalizing newlines.
-*/


static int
read_message_data_smtp(FILE *fout)
@@ -1372,6 +1369,7 @@ BOOL resents_exist = FALSE;
uschar *resent_prefix = US"";
uschar *blackholed_by = NULL;
uschar *blackhole_log_msg = US"";
+int cutthrough_done;

flock_t lock_data;
error_block *bad_addresses = NULL;
@@ -1415,8 +1413,7 @@ search_tidyup();

/* Extracting the recipient list from an input file is incompatible with
cutthrough delivery with the no-spool option. It shouldn't be possible
- to set up the combination, but just in case kill any ongoing connection. */
-/*XXX add no-spool */
+to set up the combination, but just in case kill any ongoing connection. */
if (extract_recip || !smtp_input)
cancel_cutthrough_connection();

@@ -2038,7 +2035,6 @@ for (h = header_list->next; h != NULL; h = h->next)

     case htype_received:
     h->type = htype_received;
-/*XXX cutthrough delivery - need to error on excessive number here */
     received_count++;
     break;


@@ -2710,22 +2706,34 @@ if (filter_test != FTEST_NONE)
return message_ended == END_DOT;
}

-/*XXX cutthrough deliver:
+/* Cutthrough delivery:
     We have to create the Received header now rather than at the end of reception,
     so the timestamp behaviour is a change to the normal case.
     XXX Ensure this gets documented XXX.
+    Having created it, send the headers to the destination.
 */
 if (cutthrough_fd >= 0)
   {
+  if (received_count > received_headers_max)
+    {
+    cancel_cutthrough_connection();
+    if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
+    log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+      "Too many \"Received\" headers",
+      sender_address,
+      (sender_fullhost == NULL)? "" : " H=",
+      (sender_fullhost == NULL)? US"" : sender_fullhost,
+      (sender_ident == NULL)? "" : " U=",
+      (sender_ident == NULL)? US"" : sender_ident);
+    message_id[0] = 0;                       /* Indicate no message accepted */
+    smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
+    goto TIDYUP;                             /* Skip to end of function */
+    }
   received_header_gen();
   add_acl_headers(US"MAIL or RCPT");
   (void) cutthrough_headers_send();
   }
-
-
-/*XXX cutthrough deliver:
-  Here's where we open the data spoolfile.  Want to optionally avoid.
-*/
+ 


 /* Open a new spool file for the data portion of the message. We need
 to access it both via a file descriptor and a stream. Try to make the
@@ -2783,7 +2791,6 @@ if (next != NULL)
   {
   uschar *s = next->text;
   int len = next->slen;
-  /*XXX cutthrough - writing the data spool file here.  Want to optionally avoid. */
   (void)fwrite(s, 1, len, data_file);
   body_linecount++;                 /* Assumes only 1 line */
   }
@@ -2792,13 +2799,10 @@ if (next != NULL)
 (indicated by '.'), or might have encountered an error while writing the
 message id or "next" line. */


-/* XXX cutthrough - no-spool option....... */
 if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
-    /*XXX cutthrough - writing the data spool file here.  Want to optionally avoid. */
-    /*    Would suffice to leave data_file arg NULL */
     message_ended = read_message_data_smtp(data_file);
     receive_linecount++;                /* The terminating "." line */
     }
@@ -2869,7 +2873,6 @@ we can then give up. Note that for SMTP input we must swallow the remainder of
 the input in cases of output errors, since the far end doesn't expect to see
 anything until the terminating dot line is sent. */


-/* XXX cutthrough - no-spool option....... */
 if (fflush(data_file) == EOF || ferror(data_file) ||
     EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
   {
@@ -2909,7 +2912,6 @@ if (fflush(data_file) == EOF || ferror(data_file) ||


/* No I/O errors were encountered while writing the data file. */

-/*XXX cutthrough - avoid message if no-spool option */
DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);


@@ -2924,9 +2926,6 @@ recipients or stderr error writing, throw the data file away afterwards, and
exit. (This can't be SMTP, which always ensures there's at least one
syntactically good recipient address.) */

-/*XXX cutthrough - can't if no-spool option.  extract_recip is a fn arg.
-  Make incompat with no-spool at fn start. */
-
 if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
   {
   DEBUG(D_receive)
@@ -3022,13 +3021,9 @@ if (received_header->text == NULL)    /* Non-cutthrough case */


   add_acl_headers(US"MAIL or RCPT");
   }
-else if (data_fd >= 0)
+else
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
     statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
-else
-  /*XXX cutthrough - XXX how to get the body size? */
-  /*    perhaps a header-size to subtract from message_size? */
-  message_body_size = message_size - 1;


 /* If an ACL is specified for checking things at this stage of reception of a
 message, run it, unless all the recipients were removed by "discard" in earlier
@@ -3056,7 +3051,6 @@ else
 #ifndef DISABLE_DKIM
     if (!dkim_disable_verify)
       {
-/* XXX cutthrough - no-spool option....... */
       /* Finish verification, this will log individual signature results to
          the mainlog */
       dkim_exim_verify_finish();
@@ -3161,7 +3155,6 @@ else
 #endif /* DISABLE_DKIM */


 #ifdef WITH_CONTENT_SCAN
-/* XXX cutthrough - no-spool option....... */
     if (recipients_count > 0 &&
         acl_smtp_mime != NULL &&
         !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
@@ -3171,9 +3164,6 @@ else
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */


-/* XXX cutthrough - no-spool option must document that data-acl has no file access */
-/*    but can peek at headers */
-
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
@@ -3300,7 +3290,6 @@ local_scan_data = NULL;


os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
if (local_scan_timeout > 0) alarm(local_scan_timeout);
-/* XXX cutthrough - no-spool option..... */
rc = local_scan(data_fd, &local_scan_data);
alarm(0);
os_non_restarting_signal(SIGALRM, sigalrm_handler);
@@ -3456,7 +3445,6 @@ deliver_firsttime = TRUE;
#ifdef EXPERIMENTAL_BRIGHTMAIL
if (bmi_run == 1) {
/* rewind data file */
- /* XXX cutthrough - no-spool option..... */
lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
bmi_verdicts = bmi_process_message(header_list, data_fd);
};
@@ -3498,9 +3486,6 @@ if (host_checking || blackholed_by != NULL)

 else
   {
-  /*XXX cutthrough -
-     Optionally want to avoid writing spool files (when no data-time filtering needed) */
-
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
     log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
@@ -3567,18 +3552,18 @@ if (message_reference != NULL)
 s = add_host_info_for_log(s, &size, &sptr);


 #ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
-  s = string_append(s, &size, &sptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+  s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
 if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-     tls_cipher != NULL)
+     tls_in.cipher != NULL)
   s = string_append(s, &size, &sptr, 2, US" CV=",
-    tls_certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+    tls_in.certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
   s = string_append(s, &size, &sptr, 3, US" DN=\"",
-    string_printing(tls_peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+    string_printing(tls_in.peerdn), US"\"");
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
   s = string_append(s, &size, &sptr, 3, US" SNI=\"",
-    string_printing(tls_sni), US"\"");
+    string_printing(tls_in.sni), US"\"");
 #endif


if (sender_host_authenticated != NULL)
@@ -3759,63 +3744,41 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
/* The connection has not gone away; we really are going to take responsibility
for this message. */

-/*XXX cutthrough - had sender last-dot; assume we've sent or bufferred all
+/* Cutthrough - had sender last-dot; assume we've sent (or bufferred) all
    data onward by now.


-   Send dot onward.  If accepted, can the spooled files, log as delivered and accept
+   Send dot onward.  If accepted, wipe the spooled files, log as delivered and accept
    the sender's dot (below).
-   If not accepted: copy response to sender, can the spooled files, log approriately.
+   If rejected: copy response to sender, wipe the spooled files, log approriately.
+   If temp-reject: accept to sender, keep the spooled files.


    Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
+
+   XXX We do not handle queue-only, freezing, or blackholes.
 */
 if(cutthrough_fd >= 0)
   {
   uschar * msg= cutthrough_finaldot();    /* Ask the target system to accept the messsage */
+                    /* Logging was done in finaldot() */
   switch(msg[0])
-  {
-  case '2':    /* Accept. Do the same to the source; dump any spoolfiles.   */
-    /* logging was done in finaldot() */
-    if(data_file != NULL)
-      {
-      sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-      sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-      sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-      }
-    break;
-
-  default:    /* Unknown response, or error.  Treat as temp-reject.         */
-  case '4':    /* Temp-reject. If we wrote spoolfiles, keep them and accept. */
-          /* If not, temp-reject the source.                            */
-    /*XXX could we mark the spoolfile queue-only or already-tried? */
-    log_write(0, LOG_MAIN, "cutthrough target temp-reject: %s", msg);
-    if(data_file == NULL)
-    smtp_reply= msg;    /* Pass on the exact error */
-    break;
-
-  case '5':    /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
-    log_write(0, LOG_MAIN, "cutthrough target perm-reject: %s", msg);
-    smtp_reply= msg;        /* Pass on the exact error */
-    if(data_file != NULL)
-      {
-      sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-      sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-      sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-      }
-    break;
-  }
+    {
+    case '2':    /* Accept. Do the same to the source; dump any spoolfiles.   */
+      cutthrough_done = 3;
+      break;                    /* message_id needed for SMTP accept below */
+  
+    default:    /* Unknown response, or error.  Treat as temp-reject.         */
+    case '4':    /* Temp-reject. Keep spoolfiles and accept. */
+      cutthrough_done = 1;            /* Avoid the usual immediate delivery attempt */
+      break;                    /* message_id needed for SMTP accept below */
+  
+    case '5':    /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
+      smtp_reply= msg;        /* Pass on the exact error */
+      cutthrough_done = 2;
+      break;
+    }
   }
+else
+  cutthrough_done = 0;


if(smtp_reply == NULL)
{
@@ -3884,7 +3847,6 @@ if (smtp_input)

   if (!smtp_batched_input)
     {
-/*XXX cutthrough - here's where the originating sender gets given the data-acceptance */
     if (smtp_reply == NULL)
       {
       if (fake_response != OK)
@@ -3921,11 +3883,24 @@ if (smtp_input)
         smtp_printf("%.1024s\r\n", smtp_reply);
       }


-    if (cutthrough_delivery)
-      {
-      log_write(0, LOG_MAIN, "Completed");
-      message_id[0] = 0;            /* Prevent a delivery from starting */
+    switch (cutthrough_done)
+      {
+      case 3: log_write(0, LOG_MAIN, "Completed");    /* Delivery was done */
+      case 2: {                        /* Delete spool files */
+          sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
+            message_subdir, message_id);
+          Uunlink(spool_name);
+          sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
+            message_subdir, message_id);
+          Uunlink(spool_name);
+          sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
+            message_subdir, message_id);
+          Uunlink(spool_name);
+          }
+      case 1: message_id[0] = 0;            /* Prevent a delivery from starting */
+      default:break;
       }
+    cutthrough_delivery = FALSE;
     }


/* For batched SMTP, generate an error message on failure, and do
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 9652a06..4789fba 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -455,9 +455,10 @@ if (rcpt_in_progress)
/* Now write the string */

 #ifdef SUPPORT_TLS
-if (tls_active >= 0)
+if (tls_in.active >= 0)
   {
-  if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1;
+  if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+    smtp_write_error = -1;
   }
 else
 #endif
@@ -483,7 +484,7 @@ Returns:    0 for no error; -1 after an error
 int
 smtp_fflush(void)
 {
-if (tls_active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
 return smtp_write_error;
 }


@@ -506,7 +507,7 @@ command_timeout_handler(int sig)
 sig = sig;    /* Keep picky compilers happy */
 log_write(L_lost_incoming_connection,
           LOG_MAIN, "SMTP command timeout on%s connection from %s",
-          (tls_active >= 0)? " TLS" : "",
+          (tls_in.active >= 0)? " TLS" : "",
           host_and_ident(FALSE));
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SMTP command timeout");  /* Does not return */
@@ -707,7 +708,7 @@ fd_set fds;
 struct timeval tzero;


 if (!smtp_enforce_sync || sender_host_address == NULL ||
-    sender_host_notsocket || tls_active >= 0)
+    sender_host_notsocket || tls_in.active >= 0)
   return TRUE;


fd = fileno(smtp_in);
@@ -850,18 +851,18 @@ if (sender_host_authenticated != NULL)
}

 #ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
-  s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+  s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
 if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-     tls_cipher != NULL)
+     tls_in.cipher != NULL)
   s = string_append(s, &size, &ptr, 2, US" CV=",
-    tls_certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+    tls_in.certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
   s = string_append(s, &size, &ptr, 3, US" DN=\"",
-    string_printing(tls_peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+    string_printing(tls_in.peerdn), US"\"");
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
   s = string_append(s, &size, &ptr, 3, US" SNI=\"",
-    string_printing(tls_sni), US"\"");
+    string_printing(tls_in.sni), US"\"");
 #endif


sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
@@ -1404,7 +1405,7 @@ if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
authenticated_by = NULL;

#ifdef SUPPORT_TLS
-tls_cipher = tls_peerdn = NULL;
+tls_in.cipher = tls_in.peerdn = NULL;
tls_advertised = FALSE;
#endif

@@ -1692,8 +1693,7 @@ if (!sender_host_unknown)
smtps port for use with older style SSL MTAs. */

   #ifdef SUPPORT_TLS
-  if (tls_on_connect &&
-      tls_server_start(tls_require_ciphers) != OK)
+  if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
     return FALSE;
   #endif


@@ -2808,7 +2808,7 @@ while (done <= 0)
         sender_host_authenticated = au->name;
         authentication_failed = FALSE;
         received_protocol =
-          protocols[pextend + pauthed + ((tls_active >= 0)? pcrpted:0)] +
+          protocols[pextend + pauthed + ((tls_in.active >= 0)? pcrpted:0)] +
             ((sender_host_address != NULL)? pnlocal : 0);
         s = ss = US"235 Authentication succeeded";
         authenticated_by = au;
@@ -2942,7 +2942,7 @@ while (done <= 0)


       host_build_sender_fullhost();  /* Rebuild */
       set_process_info("handling%s incoming connection from %s",
-        (tls_active >= 0)? " TLS" : "", host_and_ident(FALSE));
+        (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));


       /* Verify if configured. This doesn't give much security, but it does
       make some people happy to be able to do it. If helo_required is set,
@@ -3167,7 +3167,7 @@ while (done <= 0)
       secure connection. */


       #ifdef SUPPORT_TLS
-      if (tls_active < 0 &&
+      if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
         s = string_cat(s, &size, &ptr, smtp_code, 3);
@@ -3188,7 +3188,7 @@ while (done <= 0)
     s[ptr] = 0;


     #ifdef SUPPORT_TLS
-    if (tls_active >= 0) (void)tls_write(s, ptr); else
+    if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
     #endif


     (void)fwrite(s, 1, ptr, smtp_out);
@@ -3206,9 +3206,9 @@ while (done <= 0)
     received_protocol = (esmtp?
       protocols[pextend +
         ((sender_host_authenticated != NULL)? pauthed : 0) +
-        ((tls_active >= 0)? pcrpted : 0)]
+        ((tls_in.active >= 0)? pcrpted : 0)]
       :
-      protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)])
+      protocols[pnormal + ((tls_in.active >= 0)? pcrpted : 0)])
       +
       ((sender_host_address != NULL)? pnlocal : 0);


@@ -3914,7 +3914,7 @@ while (done <= 0)
       {
       DEBUG(D_any)
         debug_printf("Non-empty input buffer after STARTTLS; naive attack?");
-      if (tls_active < 0)
+      if (tls_in.active < 0)
         smtp_inend = smtp_inptr = smtp_inbuffer;
       /* and if TLS is already active, tls_server_start() should fail */
       }
@@ -3975,7 +3975,7 @@ while (done <= 0)
       }


     /* Hard failure. Reject everything except QUIT or closed connection. One
-    cause for failure is a nested STARTTLS, in which case tls_active remains
+    cause for failure is a nested STARTTLS, in which case tls_in.active remains
     set, but we must still reject all incoming commands. */


     DEBUG(D_tls) debug_printf("TLS failed to start\n");
@@ -4019,7 +4019,7 @@ while (done <= 0)
         break;
         }
       }
-    tls_close(TRUE);
+    tls_close(TRUE, TRUE);
     break;
     #endif


@@ -4044,7 +4044,7 @@ while (done <= 0)
       smtp_respond(US"221", 3, TRUE, user_msg);


     #ifdef SUPPORT_TLS
-    tls_close(TRUE);
+    tls_close(TRUE, TRUE);
     #endif


     done = 2;
@@ -4082,7 +4082,7 @@ while (done <= 0)
       buffer[0] = 0;
       Ustrcat(buffer, " AUTH");
       #ifdef SUPPORT_TLS
-      if (tls_active < 0 &&
+      if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         Ustrcat(buffer, " STARTTLS");
       #endif
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 42a889a..0fa4ccd 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -300,8 +300,8 @@ flush_buffer(smtp_outblock *outblock)
 int rc;


#ifdef SUPPORT_TLS
-if (tls_active == outblock->sock)
- rc = tls_write(outblock->buffer, outblock->ptr - outblock->buffer);
+if (tls_out.active == outblock->sock)
+ rc = tls_write(FALSE, outblock->buffer, outblock->ptr - outblock->buffer);
else
#endif

diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 3c611a5..6747804 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -283,10 +283,10 @@ dkim_collect_input = FALSE;
#endif

#ifdef SUPPORT_TLS
-tls_certificate_verified = FALSE;
-tls_cipher = NULL;
-tls_peerdn = NULL;
-tls_sni = NULL;
+tls_in.certificate_verified = FALSE;
+tls_in.cipher = NULL;
+tls_in.peerdn = NULL;
+tls_in.sni = NULL;
#endif

 #ifdef WITH_CONTENT_SCAN
@@ -545,13 +545,13 @@ for (;;)
     #ifdef SUPPORT_TLS
     case 't':
     if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
-      tls_certificate_verified = TRUE;
+      tls_in.certificate_verified = TRUE;
     else if (Ustrncmp(p, "ls_cipher", 9) == 0)
-      tls_cipher = string_copy(big_buffer + 12);
+      tls_in.cipher = string_copy(big_buffer + 12);
     else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
-      tls_peerdn = string_unprinting(string_copy(big_buffer + 12));
+      tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
     else if (Ustrncmp(p, "ls_sni", 6) == 0)
-      tls_sni = string_unprinting(string_copy(big_buffer + 9));
+      tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
     break;
     #endif


diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index 1a380dd..5df2a41 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -226,10 +226,10 @@ if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
#endif

 #ifdef SUPPORT_TLS
-if (tls_certificate_verified) fprintf(f, "-tls_certificate_verified\n");
-if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher);
-if (tls_peerdn != NULL) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_peerdn));
-if (tls_sni != NULL) fprintf(f, "-tls_sni %s\n", string_printing(tls_sni));
+if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n");
+if (tls_in.cipher != NULL)       fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
+if (tls_in.peerdn != NULL)       fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
+if (tls_in.sni != NULL)         fprintf(f, "-tls_sni %s\n",    string_printing(tls_in.sni));
 #endif


/* To complete the envelope, write out the tree of non-recipients, followed by
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index cf315b6..8a133c5 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -63,8 +63,7 @@ Some of these correspond to variables in globals.c; those variables will
be set to point to content in one of these instances, as appropriate for
the stage of the process lifetime.

-Not handled here: globals tls_active, tls_bits, tls_cipher, tls_peerdn,
-tls_certificate_verified, tls_channelbinding_b64, tls_sni.
+Not handled here: global tls_channelbinding_b64.    /*XXX JGH */
 */


typedef struct exim_gnutls_state {
@@ -95,6 +94,8 @@ typedef struct exim_gnutls_state {
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;

+ tls_support *tlsp;
+
uschar *xfer_buffer;
int xfer_buffer_lwm;
int xfer_buffer_hwm;
@@ -107,6 +108,7 @@ static const exim_gnutls_state_st exim_gnutls_state_init = {
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
NULL, 0, 0, 0, 0,
};

@@ -120,7 +122,6 @@ there's no way for heart-beats to be responded to, for the duration of the
second connection. */

static exim_gnutls_state_st state_server, state_client;
-static exim_gnutls_state_st *current_global_tls_state;

 /* dh_params are initialised once within the lifetime of a process using TLS;
 if we used TLS in a long-lived daemon, we'd have to reconsider this.  But we
@@ -286,15 +287,13 @@ Sets:
   tls_cipher                a string
   tls_peerdn                a string
   tls_sni                   a (UTF-8) string
-Also:
-  current_global_tls_state  for API limitations


 Argument:
   state      the relevant exim_gnutls_state_st *
 */


static void
-extract_exim_vars_from_tls_state(exim_gnutls_state_st *state)
+extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server)
{
gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
@@ -303,19 +302,17 @@ int rc;
gnutls_datum_t channel;
#endif

-current_global_tls_state = state;
-
-tls_active = state->fd_out;
+state->tlsp->active = state->fd_out;

cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
-tls_bits = gnutls_cipher_get_key_size(cipher) * 8;
+state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;

-tls_cipher = state->ciphersuite;
+state->tlsp->cipher = state->ciphersuite;

-DEBUG(D_tls) debug_printf("cipher: %s\n", tls_cipher);
+DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);

-tls_certificate_verified = state->peer_cert_verified;
+state->tlsp->certificate_verified = state->peer_cert_verified;

/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
@@ -336,9 +333,8 @@ if (rc) {
}
#endif

-tls_peerdn = state->peerdn;
-
-tls_sni = state->received_sni;
+state->tlsp->peerdn = state->peerdn;
+state->tlsp->sni =    state->received_sni;
 }



@@ -884,6 +880,7 @@ Arguments:
   cas             CA certs file
   crl             CRL file
   require_ciphers tls_require_ciphers setting
+  caller_state    returned state-info structure


 Returns:          OK/DEFER/FAIL
 */
@@ -929,6 +926,7 @@ if (host)
   {
   state = &state_client;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+  state->tlsp = &tls_out;
   DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
   rc = gnutls_init(&state->session, GNUTLS_CLIENT);
   }
@@ -936,6 +934,7 @@ else
   {
   state = &state_server;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+  state->tlsp = &tls_in;
   DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
   rc = gnutls_init(&state->session, GNUTLS_SERVER);
   }
@@ -967,7 +966,7 @@ if (rc != OK) return rc;
 /* set SNI in client, only */
 if (host)
   {
-  if (!expand_check_tlsvar(tls_sni))
+  if (!expand_check_tlsvar(state->tlsp->sni))
     return DEFER;
   if (state->exp_tls_sni && *state->exp_tls_sni)
     {
@@ -1038,8 +1037,6 @@ if (gnutls_compat_mode)
   }


*caller_state = state;
-/* needs to happen before callbacks during handshake */
-current_global_tls_state = state;
return OK;
}

@@ -1118,7 +1115,7 @@ old_pool = store_pool;
store_pool = POOL_PERM;
state->ciphersuite = string_copy(cipherbuf);
store_pool = old_pool;
-tls_cipher = state->ciphersuite;
+state->tlsp->cipher = state->ciphersuite;

 /* tls_peerdn */
 cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
@@ -1240,7 +1237,7 @@ else
       state->peerdn ? state->peerdn : US"<unset>");
   }


-tls_peerdn = state->peerdn;
+state->tlsp->peerdn = state->peerdn;

return TRUE;
}
@@ -1284,6 +1281,7 @@ handshake.".

For inability to get SNI information, we return 0.
We only return non-zero if re-setup failed.
+Only used for server-side TLS.
*/

static int
@@ -1291,7 +1289,7 @@ exim_sni_handling_cb(gnutls_session_t session)
{
char sni_name[MAX_HOST_LEN];
size_t data_len = MAX_HOST_LEN;
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = &state_server;
unsigned int sni_type;
int rc, old_pool;

@@ -1321,7 +1319,7 @@ state->received_sni = string_copyn(US sni_name, data_len);
store_pool = old_pool;

/* We set this one now so that variable expansions below will work */
-tls_sni = state->received_sni;
+state->tlsp->sni = state->received_sni;

 DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", sni_name,
     state->trigger_sni_changes ? "" : " (unused for certificate selection)");
@@ -1377,9 +1375,7 @@ const char *error;
 exim_gnutls_state_st *state = NULL;


/* Check for previous activation */
-/* nb: this will not be TLS callout safe, needs reworking as part of that. */
-
-if (tls_active >= 0)
+if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", "", NULL);
smtp_printf("554 Already in TLS\r\n");
@@ -1430,10 +1426,10 @@ make them disconnect. We need to have an explicit fflush() here, to force out
the response. Other smtp_printf() calls do not need it, because in non-TLS
mode, the fflush() happens when smtp_getc() is called. */

-if (!tls_on_connect)
+if (!state->tlsp->on_connect)
   {
   smtp_printf("220 TLS go ahead\r\n");
-  fflush(smtp_out);
+  fflush(smtp_out);        /*XXX JGH */
   }


/* Now negotiate the TLS session. We put our own timer on it, since it seems
@@ -1500,7 +1496,7 @@ if (rc != OK) return rc;

/* Sets various Exim expansion variables; always safe within server */

-extract_exim_vars_from_tls_state(state);
+extract_exim_vars_from_tls_state(state, TRUE);

/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize appropriately. */
@@ -1620,7 +1616,7 @@ if (rc != OK) return rc;

/* Sets various Exim expansion variables; may need to adjust for ACL callouts */

-extract_exim_vars_from_tls_state(state);
+extract_exim_vars_from_tls_state(state, FALSE);

 return OK;
 }
@@ -1641,11 +1637,11 @@ Returns:     nothing
 */


void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
{
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;

-if (tls_active < 0) return; /* TLS was not active */
+if (state->tlsp->active < 0) return; /* TLS was not active */

if (shutdown)
{
@@ -1663,7 +1659,7 @@ if ((state_server.session == NULL) && (state_client.session == NULL))
exim_gnutls_base_init_done = FALSE;
}

-tls_active = -1;
+state->tlsp->active = -1;
}


@@ -1675,6 +1671,7 @@ tls_active = -1;

/* This gets the next byte from the TLS input buffer. If the buffer is empty,
it refills the buffer via the GnuTLS reading function.
+Only used by the server-side TLS.

This feeds DKIM and should be used for all message-body reads.

@@ -1685,7 +1682,7 @@ Returns:    the next character or EOF
 int
 tls_getc(void)
 {
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = &state_server;
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
   {
   ssize_t inbytes;
@@ -1714,12 +1711,12 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)


     gnutls_deinit(state->session);
     state->session = NULL;
-    tls_active = -1;
-    tls_bits = 0;
-    tls_certificate_verified = FALSE;
-    tls_channelbinding_b64 = NULL;
-    tls_cipher = NULL;
-    tls_peerdn = NULL;
+    state->tlsp->active = -1;
+    state->tlsp->bits = 0;
+    state->tlsp->certificate_verified = FALSE;
+    tls_channelbinding_b64 = NULL;    /*XXX JGH */
+    state->tlsp->cipher = NULL;
+    state->tlsp->peerdn = NULL;


     return smtp_getc();
     }
@@ -1753,6 +1750,7 @@ return state->xfer_buffer[state->xfer_buffer_lwm++];


 /* This does not feed DKIM, so if the caller uses this for reading message body,
 then the caller must feed DKIM.
+
 Arguments:
   buff      buffer of data
   len       size of buffer
@@ -1762,9 +1760,9 @@ Returns:    the number of bytes read
 */


int
-tls_read(uschar *buff, size_t len)
+tls_read(BOOL is_server, uschar *buff, size_t len)
{
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
ssize_t inbytes;

if (len > INT_MAX)
@@ -1800,6 +1798,7 @@ return -1;

 /*
 Arguments:
+  is_server channel specifier
   buff      buffer of data
   len       number of bytes


@@ -1808,11 +1807,11 @@ Returns:    the number of bytes after a successful write,
 */


int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
{
ssize_t outbytes;
size_t left = len;
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;

DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
while (left > 0)
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index fdcb95e..3140560 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -45,11 +45,14 @@ typedef struct randstuff {
static BOOL verify_callback_called = FALSE;
static const uschar *sid_ctx = US"exim";

-static SSL_CTX *ctx = NULL;
+static SSL_CTX *client_ctx = NULL;
+static SSL_CTX *server_ctx = NULL;
+static SSL     *client_ssl = NULL;
+static SSL     *server_ssl = NULL;
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
-static SSL_CTX *ctx_sni = NULL;
+static SSL_CTX *client_sni = NULL;
+static SSL_CTX *server_sni = NULL;
 #endif
-static SSL *ssl = NULL;


static char ssl_errstring[256];

@@ -77,7 +80,8 @@ typedef struct tls_ext_ctx_cb {
/* should figure out a cleanup of API to handle state preserved per
implementation, for various reasons, which can be void * in the APIs.
For now, we hack around it. */
-tls_ext_ctx_cb *static_cbinfo = NULL;
+tls_ext_ctx_cb *client_static_cbinfo = NULL;
+tls_ext_ctx_cb *server_static_cbinfo = NULL;

 static int
 setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
@@ -214,7 +218,7 @@ if (state == 0)
     x509ctx->error_depth,
     X509_verify_cert_error_string(x509ctx->error),
     txt);
-  tls_certificate_verified = FALSE;
+  tls_in.certificate_verified = FALSE;
   verify_callback_called = TRUE;
   if (!verify_optional) return 0;    /* reject */
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
@@ -231,10 +235,10 @@ else
   {
   DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
     verify_callback_called? "" : " authenticated", txt);
-  tls_peerdn = txt;
+  tls_in.peerdn = txt;
   }


-if (!verify_callback_called) tls_certificate_verified = TRUE;
+if (!verify_callback_called) tls_in.certificate_verified = TRUE;
verify_callback_called = TRUE;

 return 1;   /* accept */
@@ -282,7 +286,11 @@ Returns:    TRUE if OK (nothing to set up, or setup worked)
 */


static BOOL
+<<<<<<< HEAD
init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
+=======
+init_dh(SSL_CTX *ctx, uschar *dhparam, host_item *host)
+>>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling.
{
BIO *bio;
DH *dh;
@@ -599,7 +607,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername,

/* Make the extension value available for expansion */
store_pool = POOL_PERM;
-tls_sni = string_copy(US servername);
+tls_in.sni = string_copy(US servername);
store_pool = old_pool;

if (!reexpand_tls_files_for_sni)
@@ -609,8 +617,8 @@ if (!reexpand_tls_files_for_sni)
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */

-ctx_sni = SSL_CTX_new(SSLv23_server_method());
-if (!ctx_sni)
+server_sni = SSL_CTX_new(SSLv23_server_method());
+if (!server_sni)
{
ERR_error_string(ERR_get_error(), ssl_errstring);
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
@@ -620,35 +628,35 @@ if (!ctx_sni)
/* Not sure how many of these are actually needed, since SSL object
already exists. Might even need this selfsame callback, for reneg? */

-SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx));
-SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx));
-SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx));
-SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx));
-SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo);
+SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx));
+SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx));
+SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
+SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
+SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
+SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
if (cbinfo->server_cipher_list)
- SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list);
+ SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
#ifdef EXPERIMENTAL_OCSP
if (cbinfo->ocsp_file)
{
- SSL_CTX_set_tlsext_status_cb(ctx_sni, tls_stapling_cb);
+ SSL_CTX_set_tlsext_status_cb(server_sni, tls_stapling_cb);
SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
#endif

-rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;

/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-rc = tls_expand_session_files(ctx_sni, cbinfo);
+rc = tls_expand_session_files(server_sni, cbinfo);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;

rc = init_dh(ctx_sni, cbinfo->dhparam, NULL);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;

DEBUG(D_tls) debug_printf("Switching SSL context.\n");
-SSL_set_SSL_CTX(s, ctx_sni);
+SSL_set_SSL_CTX(s, server_sni);

 return SSL_TLSEXT_ERR_OK;
 }
@@ -714,12 +722,12 @@ Returns:          OK/DEFER/FAIL
 */


static int
-tls_init(host_item *host, uschar *dhparam, uschar *certificate,
+tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifdef EXPERIMENTAL_OCSP
uschar *ocsp_file,
#endif
- address_item *addr)
+ address_item *addr, tls_ext_ctx_cb ** cbp)
{
long init_options;
int rc;
@@ -752,10 +760,10 @@ when OpenSSL is built without SSLv2 support.
By disabling with openssl_options, we can let admins re-enable with the
existing knob. */

-ctx = SSL_CTX_new((host == NULL)?
+*ctxp = SSL_CTX_new((host == NULL)?
SSLv23_server_method() : SSLv23_client_method());

-if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);

/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
@@ -783,10 +791,10 @@ if (!RAND_status())
/* Set up the information callback, which outputs if debugging is at a suitable
level. */

-SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);

/* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);

 /* Apply administrator-supplied work-arounds.
 Historically we applied just one requested option,
@@ -804,7 +812,7 @@ if (!okay)
 if (init_options)
   {
   DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
-  if (!(SSL_CTX_set_options(ctx, init_options)))
+  if (!(SSL_CTX_set_options(*ctxp, init_options)))
     return tls_error(string_sprintf(
           "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
   }
@@ -813,11 +821,15 @@ else


/* Initialize with DH parameters if supplied */

+<<<<<<< HEAD
if (!init_dh(ctx, dhparam, host)) return DEFER;
+=======
+if (!init_dh(*ctxp, dhparam, host)) return DEFER;
+>>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling.

/* Set up certificate and key (and perhaps OCSP info) */

-rc = tls_expand_session_files(ctx, cbinfo);
+rc = tls_expand_session_files(*ctxp, cbinfo);
if (rc != OK) return rc;

/* If we need to handle SNI, do so */
@@ -837,21 +849,21 @@ if (host == NULL)
#endif
/* We always do this, so that $tls_sni is available even if not used in
tls_certificate */
- SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
- SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
+ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
}
#endif

/* Set up the RSA callback */

-SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);

/* Finally, set the timeout, and we are done */

-SSL_CTX_set_timeout(ctx, ssl_session_timeout);
+SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
DEBUG(D_tls) debug_printf("Initialized TLS\n");

-static_cbinfo = cbinfo;
+*cbp = cbinfo;

 return OK;
 }
@@ -863,17 +875,17 @@ return OK;
 *           Get name of cipher in use            *
 *************************************************/


-/* The answer is left in a static buffer, and tls_cipher is set to point
-to it.
-
+/*
 Argument:   pointer to an SSL structure for the connection
+            buffer to use for answer
+            size of buffer
+        pointer to number of bits for cipher
 Returns:    nothing
 */


static void
-construct_cipher_name(SSL *ssl)
+construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
{
-static uschar cipherbuf[256];
/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
@@ -911,11 +923,10 @@ switch (ssl->session->ssl_version)
}

c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
-SSL_CIPHER_get_bits(c, &tls_bits);
+SSL_CIPHER_get_bits(c, bits);

-string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
- SSL_CIPHER_get_name(c), tls_bits);
-tls_cipher = cipherbuf;
+string_format(cipherbuf, bsize, "%s:%s:%u", ver,
+ SSL_CIPHER_get_name(c), *bits);

DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
}
@@ -1072,10 +1083,11 @@ tls_server_start(const uschar *require_ciphers)
int rc;
uschar *expciphers;
tls_ext_ctx_cb *cbinfo;
+static uschar cipherbuf[256];

/* Check for previous activation */

-if (tls_active >= 0)
+if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"");
smtp_printf("554 Already in TLS\r\n");
@@ -1085,13 +1097,13 @@ if (tls_active >= 0)
/* Initialize the SSL library. If it fails, it will already have logged
the error. */

-rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey,
+rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
 #ifdef EXPERIMENTAL_OCSP
     tls_ocsp_file,
 #endif
-    NULL);
+    NULL, &server_static_cbinfo);
 if (rc != OK) return rc;
-cbinfo = static_cbinfo;
+cbinfo = server_static_cbinfo;


 if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
   return FAIL;
@@ -1106,7 +1118,7 @@ if (expciphers != NULL)
   uschar *s = expciphers;
   while (*s != 0) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
-  if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+  if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
     return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
   cbinfo->server_cipher_list = expciphers;
   }
@@ -1114,25 +1126,25 @@ if (expciphers != NULL)
 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */


-tls_certificate_verified = FALSE;
+tls_in.certificate_verified = FALSE;
verify_callback_called = FALSE;

if (verify_check_host(&tls_verify_hosts) == OK)
{
- rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
if (rc != OK) return rc;
verify_optional = FALSE;
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
- rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
if (rc != OK) return rc;
verify_optional = TRUE;
}

/* Prepare for new connection */

-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
+if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);

/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
@@ -1153,8 +1165,8 @@ make them disconnect. We need to have an explicit fflush() here, to force out
the response. Other smtp_printf() calls do not need it, because in non-TLS
mode, the fflush() happens when smtp_getc() is called. */

-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-if (!tls_on_connect)
+SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
+if (!tls_in.on_connect)
{
smtp_printf("220 TLS go ahead\r\n");
fflush(smtp_out);
@@ -1163,15 +1175,15 @@ if (!tls_on_connect)
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the OpenSSL library doesn't. */

-SSL_set_wfd(ssl, fileno(smtp_out));
-SSL_set_rfd(ssl, fileno(smtp_in));
-SSL_set_accept_state(ssl);
+SSL_set_wfd(server_ssl, fileno(smtp_out));
+SSL_set_rfd(server_ssl, fileno(smtp_in));
+SSL_set_accept_state(server_ssl);

DEBUG(D_tls) debug_printf("Calling SSL_accept\n");

sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-rc = SSL_accept(ssl);
+rc = SSL_accept(server_ssl);
alarm(0);

if (rc <= 0)
@@ -1188,16 +1200,22 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */

-construct_cipher_name(ssl);
+construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
+tls_in.cipher = cipherbuf;

 DEBUG(D_tls)
   {
   uschar buf[2048];
-  if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)) != NULL)
+  if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
     debug_printf("Shared ciphers: %s\n", buf);
   }



+/* Only used by the server-side tls (tls_in), including tls_getc.
+ Client-side (tls_out) reads (seem to?) go via
+ smtp_read_response()/ip_recv().
+ Hence no need to duplicate for _in and _out.
+ */
ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
ssl_xfer_eof = ssl_xfer_error = 0;
@@ -1208,7 +1226,7 @@ receive_feof = tls_feof;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;

-tls_active = fileno(smtp_out);
+tls_in.active = fileno(smtp_out);
return OK;
}

@@ -1252,15 +1270,16 @@ static uschar txt[256];
uschar *expciphers;
X509* server_cert;
int rc;
+static uschar cipherbuf[256];

-rc = tls_init(host, dhparam, certificate, privatekey,
+rc = tls_init(&client_ctx, host, dhparam, certificate, privatekey,
 #ifdef EXPERIMENTAL_OCSP
     NULL,
 #endif
-    addr);
+    addr, &client_static_cbinfo);
 if (rc != OK) return rc;


-tls_certificate_verified = FALSE;
+tls_out.certificate_verified = FALSE;
verify_callback_called = FALSE;

 if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
@@ -1275,29 +1294,29 @@ if (expciphers != NULL)
   uschar *s = expciphers;
   while (*s != 0) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
-  if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+  if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
     return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
   }


-rc = setup_certs(ctx, verify_certs, crl, host, FALSE);
+rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE);
if (rc != OK) return rc;

-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(ssl, fd);
-SSL_set_connect_state(ssl);
+if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
+SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(client_ssl, fd);
+SSL_set_connect_state(client_ssl);

 if (sni)
   {
-  if (!expand_check(sni, US"tls_sni", &tls_sni))
+  if (!expand_check(sni, US"tls_sni", &tls_out.sni))
     return FAIL;
-  if (!Ustrlen(tls_sni))
-    tls_sni = NULL;
+  if (!Ustrlen(tls_out.sni))
+    tls_out.sni = NULL;
   else
     {
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
-    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni);
-    SSL_set_tlsext_host_name(ssl, tls_sni);
+    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
+    SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
 #else
     DEBUG(D_tls)
       debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
@@ -1311,7 +1330,7 @@ if (sni)
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
 sigalrm_seen = FALSE;
 alarm(timeout);
-rc = SSL_connect(ssl);
+rc = SSL_connect(client_ssl);
 alarm(0);


if (rc <= 0)
@@ -1320,19 +1339,20 @@ if (rc <= 0)
DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");

 /* Beware anonymous ciphers which lead to server_cert being NULL */
-server_cert = SSL_get_peer_certificate (ssl);
+server_cert = SSL_get_peer_certificate (client_ssl);
 if (server_cert)
   {
-  tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
+  tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
     CS txt, sizeof(txt));
-  tls_peerdn = txt;
+  tls_out.peerdn = txt;
   }
 else
-  tls_peerdn = NULL;
+  tls_out.peerdn = NULL;


-construct_cipher_name(ssl); /* Sets tls_cipher */
+construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
+tls_out.cipher = cipherbuf;

-tls_active = fd;
+tls_out.active = fd;
return OK;
}

@@ -1349,6 +1369,8 @@ it refills the buffer via the SSL reading function.

 Arguments:  none
 Returns:    the next character or EOF
+
+Only used by the server-side TLS.
 */


int
@@ -1359,12 +1381,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
int error;
int inbytes;

-  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
     ssl_xfer_buffer, ssl_xfer_buffer_size);


if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
- error = SSL_get_error(ssl, inbytes);
+ inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
+ error = SSL_get_error(server_ssl, inbytes);
alarm(0);

   /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
@@ -1381,13 +1403,13 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     receive_ferror = smtp_ferror;
     receive_smtp_buffered = smtp_buffered;


-    SSL_free(ssl);
-    ssl = NULL;
-    tls_active = -1;
-    tls_bits = 0;
-    tls_cipher = NULL;
-    tls_peerdn = NULL;
-    tls_sni = NULL;
+    SSL_free(server_ssl);
+    server_ssl = NULL;
+    tls_in.active = -1;
+    tls_in.bits = 0;
+    tls_in.cipher = NULL;
+    tls_in.peerdn = NULL;
+    tls_in.sni = NULL;


     return smtp_getc();
     }
@@ -1434,6 +1456,8 @@ Arguments:


 Returns:    the number of bytes read
             -1 after a failed read
+
+Only used by the client-side TLS.
 */


int
@@ -1442,11 +1466,11 @@ tls_read(uschar *buff, size_t len)
int inbytes;
int error;

-DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", client_ssl,
buff, (unsigned int)len);

-inbytes = SSL_read(ssl, CS buff, len);
-error = SSL_get_error(ssl, inbytes);
+inbytes = SSL_read(client_ssl, CS buff, len);
+error = SSL_get_error(client_ssl, inbytes);

if (error == SSL_ERROR_ZERO_RETURN)
{
@@ -1471,19 +1495,23 @@ return inbytes;

 /*
 Arguments:
+  is_server channel specifier
   buff      buffer of data
   len       number of bytes


 Returns:    the number of bytes after a successful write,
             -1 after a failed write
+
+Used by both server-side and client-side TLS.
 */


int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
{
int outbytes;
int error;
int left = len;
+SSL *ssl = is_server ? server_ssl : client_ssl;

 DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
 while (left > 0)
@@ -1508,6 +1536,11 @@ while (left > 0)
     log_write(0, LOG_MAIN, "SSL channel closed on write");
     return -1;


+    case SSL_ERROR_SYSCALL:
+    log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+      sender_fullhost ? sender_fullhost : US"<unknown>",
+      strerror(errno));
+
     default:
     log_write(0, LOG_MAIN, "SSL_write error %d", error);
     return -1;
@@ -1528,23 +1561,27 @@ would tamper with the SSL session in the parent process).


 Arguments:   TRUE if SSL_shutdown is to be called
 Returns:     nothing
+
+Used by both server-side and client-side TLS.
 */


void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
{
-if (tls_active < 0) return; /* TLS was not active */
+SSL **sslp = is_server ? &server_ssl : &client_ssl;
+
+if (*fdp < 0) return; /* TLS was not active */

if (shutdown)
{
DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
- SSL_shutdown(ssl);
+ SSL_shutdown(*sslp);
}

-SSL_free(ssl);
-ssl = NULL;
+SSL_free(*sslp);
+*sslp = NULL;

-tls_active = -1;
+*fdp = -1;
}


diff --git a/src/src/transport.c b/src/src/transport.c
index 6894e96..9e533f6 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -219,7 +219,7 @@ for (i = 0; i < 100; i++)
   if (transport_write_timeout <= 0)   /* No timeout wanted */
     {
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) rc = tls_write(block, len); else
+    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
     #endif
     rc = write(fd, block, len);
     save_errno = errno;
@@ -231,7 +231,7 @@ for (i = 0; i < 100; i++)
     {
     alarm(local_timeout);
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) rc = tls_write(block, len); else
+    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
     #endif
     rc = write(fd, block, len);
     save_errno = errno;
@@ -1046,7 +1046,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options,
       int siglen = Ustrlen(dkim_signature);
       while(siglen > 0) {
         #ifdef SUPPORT_TLS
-        if (tls_active == fd) wwritten = tls_write(dkim_signature, siglen); else
+        if (tls_out.active == fd) wwritten = tls_write(FALSE, dkim_signature, siglen); else
         #endif
         wwritten = write(fd,dkim_signature,siglen);
         if (wwritten == -1) {
@@ -1072,7 +1072,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options,
      to the socket. However only if we don't use TLS,
      in which case theres another layer of indirection
      before the data finally hits the socket. */
-  if (tls_active != fd)
+  if (tls_out.active != fd)
     {
     ssize_t copied = 0;
     off_t offset = 0;
@@ -1096,7 +1096,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options,
     /* write the chunk */
     DKIM_WRITE:
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) wwritten = tls_write(US p, sread); else
+    if (tls_out.active == fd) wwritten = tls_write(FALSE, US p, sread); else
     #endif
     wwritten = write(fd,p,sread);
     if (wwritten == -1)
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 5322cc5..a63f48f 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -902,11 +902,18 @@ 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
+
+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


 /* If an authenticated_sender override has been specified for this transport
@@ -1164,8 +1171,8 @@ if (tls_offered && !suppress_tls &&
       {
       if (addr->transport_return == PENDING_DEFER)
         {
-        addr->cipher = tls_cipher;
-        addr->peerdn = tls_peerdn;
+        addr->cipher = tls_out.cipher;
+        addr->peerdn = tls_out.peerdn;
         }
       }
     }
@@ -1181,7 +1188,7 @@ another process, and so we won't have expanded helo_data above. We have to
 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)
@@ -1243,7 +1250,7 @@ we skip this. */

 if (continue_hostname == NULL
     #ifdef SUPPORT_TLS
-    || tls_active >= 0
+    || tls_out.active >= 0
     #endif
     )
   {
@@ -2003,7 +2010,7 @@ if (completed_address && ok && send_quit)
   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)
         &&
@@ -2052,9 +2059,9 @@ if (completed_address && ok && send_quit)
       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
@@ -2104,7 +2111,7 @@ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 END_OFF:


#ifdef SUPPORT_TLS
-tls_close(TRUE);
+tls_close(FALSE, TRUE);
#endif

/* Close the socket, and return the appropriate value, first setting
diff --git a/src/src/verify.c b/src/src/verify.c
index ff18847..964bdf7 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -10,10 +10,14 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */


#include "exim.h"
+#include "transports/smtp.h"

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


/* Structure for caching DNSBL lookups */

@@ -150,6 +154,7 @@ do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
int callout, int callout_overall, int callout_connect, int options,
uschar *se_mailfrom, uschar *pm_mailfrom)
{
+smtp_transport_options_block *ob = (smtp_transport_options_block *)(addr->transport->options_block);
BOOL is_recipient = (options & vopt_is_recipient) != 0;
BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0;
BOOL callout_random = (options & vopt_callout_random) != 0;
@@ -394,6 +399,13 @@ optimization. */

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

+/* Precompile some regex that are used to recognize parameters in response
+to an EHLO command, if they aren't already compiled. */
+#ifdef SUPPORT_TLS
+if (regex_STARTTLS == NULL) regex_STARTTLS =
+ regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
/* Now make connections to the hosts and do real callouts. The list of hosts
is passed in as an argument. */

@@ -405,7 +417,10 @@ for (host = host_list; host != NULL && !done; host = host->next)
   int port = 25;
   BOOL send_quit = TRUE;
   uschar *active_hostname = smtp_active_hostname;
-  uschar *helo = US"HELO";
+  BOOL lmtp;
+  BOOL smtps;
+  BOOL esmtp;
+  BOOL suppress_tls = FALSE;
   uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
   uschar inbuffer[4096];
   uschar outbuffer[1024];
@@ -452,8 +467,9 @@ for (host = host_list; host != NULL && !done; host = host->next)
       addr->message);


/* Set HELO string according to the protocol */
+ lmtp= Ustrcmp(tf->protocol, "lmtp") == 0;
+ smtps= Ustrcmp(tf->protocol, "smtps") == 0;

- if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO";

HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);

@@ -472,9 +488,14 @@ for (host = host_list; host != NULL && !done; host = host->next)
outblock.cmd_count = 0;
outblock.authenticating = FALSE;

+ /* Reset the parameters of a TLS session */
+ tls_out.cipher = tls_out.peerdn = NULL;
+
/* Connect to the host; on failure, just loop for the next one, but we
set the error for the last one. Use the callout_connect timeout. */

+  tls_retry_connection:
+
   inblock.sock = outblock.sock =
     smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
   /* reconsider DSCP here */
@@ -508,13 +529,173 @@ for (host = host_list; host != NULL && !done; host = host->next)


Ustrcpy(big_buffer, "initial connection");

-  done =
-    smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-      '2', callout) &&
-    smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo,
-      active_hostname) >= 0 &&
-    smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-      '2', callout);
+  /* Unless ssl-on-connect, wait for the initial greeting */
+  smtps_redo_greeting:
+
+  #ifdef SUPPORT_TLS
+  if (!smtps || (smtps && tls_out.active >= 0))
+  #endif
+    if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
+      goto RESPONSE_FAILED;
+  
+  /* Not worth checking greeting line for ESMTP support */
+  if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
+    host->name, host->address, NULL) != OK))
+    DEBUG(D_transport)
+      debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+
+  tls_redo_helo:
+
+  #ifdef SUPPORT_TLS
+  if (smtps  &&  tls_out.active < 0)    /* ssl-on-connect, first pass */
+    {
+    tls_offered = TRUE;
+    ob->tls_tempfail_tryclear = FALSE;
+    }
+    else                /* all other cases */
+  #endif
+
+    { esmtp_retry:
+
+    if (!(done= smtp_write_command(&outblock, FALSE, "%s %s\r\n",
+      !esmtp? "HELO" : lmtp? "LHLO" : "EHLO", active_hostname) >= 0))
+      goto SEND_FAILED;
+    if (!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))
+      {
+      if (errno != 0 || responsebuffer[0] == 0 || lmtp || !esmtp || tls_out.active >= 0)
+    {
+    done= FALSE;
+        goto RESPONSE_FAILED;
+    }
+      #ifdef SUPPORT_TLS
+      tls_offered = FALSE;
+      #endif
+      esmtp = FALSE;
+      goto esmtp_retry;            /* fallback to HELO */
+      }
+
+    /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
+    #ifdef SUPPORT_TLS
+    tls_offered = esmtp && !suppress_tls &&  tls_out.active < 0  &&
+      pcre_exec(regex_STARTTLS, NULL, CS responsebuffer, Ustrlen(responsebuffer), 0,
+        PCRE_EOPT, NULL, 0) >= 0;
+    #endif
+    }
+
+  /* If TLS is available on this connection attempt to
+  start up a TLS session, unless the host is in hosts_avoid_tls. If successful,
+  send another EHLO - the server may give a different answer in secure mode. We
+  use a separate buffer for reading the response to STARTTLS so that if it is
+  negative, the original EHLO data is available for subsequent analysis, should
+  the client not be required to use TLS. If the response is bad, copy the buffer
+  for error analysis. */
+
+  #ifdef SUPPORT_TLS
+  if (tls_offered &&
+      verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
+      host->address, NULL) != OK)
+    {
+    uschar buffer2[4096];
+    if (  !smtps
+       && !(done= smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") >= 0))
+      goto SEND_FAILED;
+
+    /* If there is an I/O error, transmission of this message is deferred. If
+    there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
+    false, we also defer. However, if there is a temporary rejection of STARTTLS
+    and tls_tempfail_tryclear is true, or if there is an outright rejection of
+    STARTTLS, we carry on. This means we will try to send the message in clear,
+    unless the host is in hosts_require_tls (tested below). */
+
+    if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
+            ob->command_timeout))
+      {
+      if (errno != 0 || buffer2[0] == 0 ||
+          (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+    {
+    Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
+    done= FALSE;
+    goto RESPONSE_FAILED;
+    }
+      }
+
+     /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
+    else
+      {
+      int rc = tls_client_start(inblock.sock, host, addr,
+     NULL,                    /* No DH param */
+     ob->tls_certificate, ob->tls_privatekey,
+     ob->tls_sni,
+     ob->tls_verify_certificates, ob->tls_crl,
+     ob->tls_require_ciphers,
+     ob->gnutls_require_mac, ob->gnutls_require_kx, ob->gnutls_require_proto,
+     callout);
+
+      /* TLS negotiation failed; give an error.  Try in clear on a new connection,
+         if the options permit it for this host. */
+      if (rc != OK)
+        {
+    if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
+       verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+         host->address, NULL) != OK)
+      {
+          (void)close(inblock.sock);
+      log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
+        "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+      suppress_tls = TRUE;
+      goto tls_retry_connection;
+      }
+    /*save_errno = ERRNO_TLSFAILURE;*/
+    /*message = US"failure while setting up TLS session";*/
+    send_quit = FALSE;
+    done= FALSE;
+    goto TLS_FAILED;
+    }
+
+      /* TLS session is set up.  Copy info for logging. */
+      addr->cipher = tls_out.cipher;
+      addr->peerdn = tls_out.peerdn;
+
+      /* For SMTPS we need to wait for the initial OK response, then do HELO. */
+      if (smtps)
+     goto smtps_redo_greeting;
+
+      /* For STARTTLS we need to redo EHLO */
+      goto tls_redo_helo;
+      }
+    }
+
+  /* If the host is required to use a secure channel, ensure that we have one. */
+  if (tls_out.active < 0)
+    if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+        host->address, NULL) == OK)
+      {
+      /*save_errno = ERRNO_TLSREQUIRED;*/
+      log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
+        host->name, host->address,
+    tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
+      done= FALSE;
+      goto TLS_FAILED;
+      }
+
+  #endif /*SUPPORT_TLS*/
+
+  done = TRUE; /* so far so good; have response to HELO */
+
+  /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING, AUTH */
+  /* If we haven't authenticated, but are required to, give up. */
+
+  /*XXX "filter command specified for this transport" ??? */
+  /* for now, transport_filter by cutthrough-delivery is not supported */
+  /* Need proper integration with the proper transport mechanism. */
+
+
+  SEND_FAILED:
+  RESPONSE_FAILED:
+  TLS_FAILED:
+  ;
+  /* Clear down of the TLS, SMTP and TCP layers on error is handled below.  */
+


   /* Failure to accept HELO is cached; this blocks the whole domain for all
   senders. I/O errors and defer responses are not cached. */
@@ -646,6 +827,7 @@ for (host = host_list; host != NULL && !done; host = host->next)
         {
         /*XXX not suitable for cutthrough - sequencing problems */
     cutthrough_delivery= FALSE;
+    HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");


         done =
           smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
@@ -739,29 +921,41 @@ for (host = host_list; host != NULL && !done; host = host->next)


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

-  /*XXX cutthrough - if "done"
-  and "yeild" is OK
+  /* Cutthrough - on a successfull connect and recipient-verify with use-sender
   and we have no cutthrough conn so far
   here is where we want to leave the conn open */
-  /* and leave some form of marker for it */
-  /*XXX in fact for simplicity we should abandon cutthrough as soon as more than one address
-  comes into play */
-/*XXX what about TLS? */
   if (  cutthrough_delivery
      && done
      && yield == OK
-     && cutthrough_fd < 0
      && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
      && !random_local_part
      && !pm_mailfrom
+     && cutthrough_fd < 0
      )
     {
     cutthrough_fd= outblock.sock;    /* We assume no buffer in use in the outblock */
-    cutthrough_addr= *addr;        /* Save the address_item for later logging */
+    cutthrough_addr = *addr;        /* Save the address_item for later logging */
+    cutthrough_addr.host_used = store_get(sizeof(host_item));
+    cutthrough_addr.host_used->name =    host->name;
+    cutthrough_addr.host_used->address = host->address;
+    cutthrough_addr.host_used->port =    port;
+    if (addr->parent)
+      *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+    ctblock.buffer = ctbuffer;
+    ctblock.buffersize = sizeof(ctbuffer);
+    ctblock.ptr = ctbuffer;
+    /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
+    ctblock.sock = cutthrough_fd;
     }
   else
     {
+    if (options & vopt_callout_recipsender)
+      cancel_cutthrough_connection();    /* Ensure no cutthrough on multiple address verifies */
     if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+
+    #ifdef SUPPORT_TLS
+    tls_close(FALSE, TRUE);
+    #endif
     (void)close(inblock.sock);
     }


@@ -857,6 +1051,9 @@ return yield;



+/* Called after recipient-acl to get a cutthrough connection open when
+ one was requested and a recipient-verify wasn't subsequently done.
+*/
void
open_cutthrough_connection( address_item * addr )
{
@@ -877,74 +1074,74 @@ return;
}


-static smtp_outblock ctblock;
-uschar ctbuffer[8192];
-

-void
-cancel_cutthrough_connection( void )
+/* Send given number of bytes from the buffer */
+static BOOL
+cutthrough_send(int n)
 {
-ctblock.ptr = ctbuffer;
-cutthrough_delivery= FALSE;
-if(cutthrough_fd >= 0)    /*XXX get that initialised, also at RSET */
-  {
-  int rc;
+if(cutthrough_fd < 0)
+  return TRUE;


-  /* We could be sending this after a bunch of data, but that is ok as
-     the only way to cancel the transfer in dataphase is to drop the tcp
-     conn before the final dot.
-  */
-  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> QUIT\n");
-  rc= send(cutthrough_fd, "QUIT\r\n", 6, 0);
-  /*XXX error handling?   TLS?   See flush_buffer() in smtp_out.c */
+if(
+#ifdef SUPPORT_TLS
+   (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+#endif
+   send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+  )
+{
+  transport_count += n;
+  ctblock.ptr= ctblock.buffer;
+  return TRUE;
+}


- (void)close(cutthrough_fd);
- cutthrough_fd= -1;
- HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
- }
+HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno));
+return FALSE;
}



-/* Buffered output counted data block.   Return boolean success */
+static BOOL
+_cutthrough_puts(uschar * cp, int n)
+{
+while(n--)
+ {
+ if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
+   if(!cutthrough_send(ctblock.buffersize))
+     return FALSE;
+
+ *ctblock.ptr++ = *cp++;
+ }
+return TRUE;
+}
+
+/* Buffered output of counted data block.   Return boolean success */
 BOOL
 cutthrough_puts(uschar * cp, int n)
 {
-if(cutthrough_fd >= 0)
-  while(n--)
-  {
-  /*XXX TLS?   See flush_buffer() in smtp_out.c */
+if (cutthrough_fd < 0)       return TRUE;
+if (_cutthrough_puts(cp, n)) return TRUE;
+cancel_cutthrough_connection();
+return FALSE;
+}


-  if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
-    {
-    if(send(cutthrough_fd, ctblock.buffer, ctblock.buffersize, 0) < 0)
-    goto bad;
-    transport_count += ctblock.buffersize;
-    ctblock.ptr= ctblock.buffer;
-    }


- *ctblock.ptr++ = *cp++;
- }
-return TRUE;
+static BOOL
+_cutthrough_flush_send( void )
+{
+int n= ctblock.ptr-ctblock.buffer;

-bad:
-cancel_cutthrough_connection();
-return FALSE;
+if(n>0)
+  if(!cutthrough_send(n))
+    return FALSE;
+return TRUE;
 }


+
+/* Send out any bufferred output.  Return boolean success. */
 BOOL
 cutthrough_flush_send( void )
 {
-if(cutthrough_fd >= 0)
-  {
-  if(send(cutthrough_fd, ctblock.buffer, ctblock.ptr-ctblock.buffer, 0) < 0)
-    goto bad;
-  transport_count += ctblock.ptr-ctblock.buffer;
-  ctblock.ptr= ctblock.buffer;
-  }
-return TRUE;
-
-bad:
+if (_cutthrough_flush_send()) return TRUE;
 cancel_cutthrough_connection();
 return FALSE;
 }
@@ -970,6 +1167,7 @@ inblock.buffersize = sizeof(inbuffer);
 inblock.ptr = inbuffer;
 inblock.ptrend = inbuffer;
 inblock.sock = cutthrough_fd;
+/* this relies on (inblock.sock == tls_out.active) */
 if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
   cancel_cutthrough_connection();


@@ -991,20 +1189,12 @@ return responsebuffer[0];
BOOL
cutthrough_predata( void )
{
-int rc;
-
if(cutthrough_fd < 0)
return FALSE;

HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n");
-rc= send(cutthrough_fd, "DATA\r\n", 6, 0);
-if (rc <= 0)
- {
- HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno));
- cancel_cutthrough_connection();
- return FALSE;
- }
-/*XXX error handling? TLS? See flush_buffer() in smtp_out.c */
+cutthrough_puts(US"DATA\r\n", 6);
+cutthrough_flush_send();

/* Assume nothing buffered. If it was it gets ignored. */
return cutthrough_response('3', NULL) == '3';
@@ -1012,36 +1202,68 @@ return cutthrough_response('3', NULL) == '3';


 /* Buffered send of headers.  Return success boolean. */
+/* Expands newlines to wire format (CR,NL).           */
 /* Also sends header-terminating blank line.          */
-/* Sets up the "ctblock" buffer as a side-effect.     */
 BOOL
 cutthrough_headers_send( void )
 {
 header_line * h;
+uschar * cp1, * cp2;


if(cutthrough_fd < 0)
return FALSE;

-ctblock.buffer = ctbuffer;
-ctblock.buffersize = sizeof(ctbuffer);
-ctblock.ptr = ctbuffer;
-/* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-ctblock.sock = cutthrough_fd;
-
 for(h= header_list; h != NULL; h= h->next)
   if(h->type != htype_old  &&  h->text != NULL)
-    if(!cutthrough_puts(h->text, h->slen))
-      return FALSE;
+    for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1)
+      if(  !cutthrough_puts(cp1, cp2-cp1)
+        || !cutthrough_put_nl())
+        return FALSE;


-if(!cutthrough_put_nl())
- return TRUE;
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n");
+return cutthrough_put_nl();
}


+static void
+close_cutthrough_connection( void )
+{
+if(cutthrough_fd >= 0)
+  {
+  /* We could be sending this after a bunch of data, but that is ok as
+     the only way to cancel the transfer in dataphase is to drop the tcp
+     conn before the final dot.
+  */
+  ctblock.ptr = ctbuffer;
+  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> QUIT\n");
+  _cutthrough_puts(US"QUIT\r\n", 6);    /* avoid recursion */
+  _cutthrough_flush_send();
+  /* No wait for response */
+
+  #ifdef SUPPORT_TLS
+  tls_close(FALSE, TRUE);
+  #endif
+  (void)close(cutthrough_fd);
+  cutthrough_fd= -1;
+  HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
+  }
+ctblock.ptr = ctbuffer;
+}
+
+void
+cancel_cutthrough_connection( void )
+{
+close_cutthrough_connection();
+cutthrough_delivery= FALSE;
+}
+
+
+
+
 /* Have senders final-dot.  Send one to cutthrough target, and grab the response.
    Log an OK response as a transmission.
+   Close the connection.
    Return smtp response-class digit.
-   XXX where do fail responses from target get logged?
 */
 uschar *
 cutthrough_finaldot( void )
@@ -1049,26 +1271,32 @@ cutthrough_finaldot( void )
 HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> .\n");


/* Assume data finshed with new-line */
-if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl()
- || !cutthrough_flush_send()
- || cutthrough_response('2', &cutthrough_addr.message) != '2')
+if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
return cutthrough_addr.message;

-(void)close(cutthrough_fd);
-cutthrough_fd= -1;
-HDEBUG(D_acl) debug_printf("----------- cutthrough close ------------\n");
+switch(cutthrough_response('2', &cutthrough_addr.message))
+  {
+  case '2':
+    delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
+    close_cutthrough_connection();
+    break;
+
+  case '4':
+    delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
+    break;


-delivery_log(&cutthrough_addr, (int)'>');
-/* C= ok */
-/* QT ok */
-/* DT always 0? */
-/* delivery S= zero!  (transport_count) */
-/* not TLS yet hence no X, CV, DN */
+  case '5':
+    delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
+    break;


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



+
 /*************************************************
 *           Copy error to toplevel address       *
 *************************************************/