[exim-cvs] TLS: rework client-side use with an explicit cont…

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] TLS: rework client-side use with an explicit context rather than a global
Gitweb: https://git.exim.org/exim.git/commitdiff/74f1a42304ce056cf979d22fb970faae161e3ab2
Commit:     74f1a42304ce056cf979d22fb970faae161e3ab2
Parent:     b24eb9cd4cd3bacbb044090cba7140a012b3eabb
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Thu Jun 21 19:16:29 2018 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sun Jun 24 18:00:40 2018 +0100


    TLS: rework client-side use with an explicit context rather than a global
---
 doc/doc-txt/ChangeLog         |   5 +
 src/exim_monitor/em_globals.c |   2 +-
 src/src/daemon.c              |   2 +-
 src/src/deliver.c             |  16 +--
 src/src/dkim_transport.c      |   6 +-
 src/src/exim.c                |   6 +-
 src/src/functions.h           |  14 +--
 src/src/globals.c             |   9 +-
 src/src/globals.h             |   4 +-
 src/src/ip.c                  |  16 +--
 src/src/macros.h              |   2 +-
 src/src/malware.c             | 224 ++++++++++++++++++++-------------------
 src/src/receive.c             |   6 +-
 src/src/routers/iplookup.c    |  19 ++--
 src/src/smtp_in.c             |  34 +++---
 src/src/smtp_out.c            |  10 +-
 src/src/spam.c                |  40 +++----
 src/src/structs.h             |  14 ++-
 src/src/tls-gnu.c             | 102 ++++++++++--------
 src/src/tls-openssl.c         | 239 ++++++++++++++++++++++++------------------
 src/src/transport.c           |  10 +-
 src/src/transports/smtp.c     | 137 ++++++++++++++----------
 src/src/transports/smtp.h     |   5 +-
 src/src/verify.c              | 100 ++++++++++--------
 24 files changed, 566 insertions(+), 456 deletions(-)


diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 96508ff..db88049 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -68,6 +68,11 @@ JH/13 For receent Openssl versions (1.1 onward) use modern generic protocol

JH/14 Bug 2284: Fix DKIM signing for body lines starting with a pair of dots.

+JH/15 Rework TLS client-side context management.  Stop using a global, and
+      explicitly pass a context around.  This enables future use of TLS for
+      connections to service-daemons (eg. malware scanning) while a client smtp
+      connection is using TLS; with cutthrough connections this is quite likely.
+


 Exim version 4.91
 -----------------
diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index e0ed024..6ed57ef 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -217,7 +217,7 @@ int     string_datestamp_type  = -1;


 BOOL    timestamps_utc         = FALSE;
 tls_support tls_in = {
- -1,    /* tls_active */
+ {-1},    /* tls_active */
  0,    /* bits */
  FALSE,    /* tls_certificate_verified */
 #ifdef SUPPORT_DANE
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 4a89afe..a382a73 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -648,7 +648,7 @@ if (pid == 0)
         the data structures if necessary. */


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


         /* Reset SIGHUP and SIGCHLD in the child in both cases. */
diff --git a/src/src/deliver.c b/src/src/deliver.c
index c35f3fa..7127518 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -4478,7 +4478,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
   treat it as if it is a continued connection (apart from the counter used
   for the log line mark). */


-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
     DEBUG(D_deliver)
       debug_printf("lazy-callout-close: have conn still open from verification\n");
@@ -4985,12 +4985,13 @@ all pipes, so I do not see a reason to use non-blocking IO here
   release its TLS library context (if any) as responsibility was passed to
   the delivery child process. */


-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
 #ifdef SUPPORT_TLS
-    tls_close(FALSE, TLS_NO_SHUTDOWN);
+    if (cutthrough.is_tls)
+      tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
 #endif
-    (void) close(cutthrough.fd);
+    (void) close(cutthrough.cctx.sock);
     release_cutthrough_connection(US"passed to transport proc");
     }


@@ -8528,9 +8529,9 @@ delivery_re_exec(int exec_type)
{
uschar * where;

-if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
{
- int pfd[2], channel_fd = cutthrough.fd, pid;
+ int pfd[2], channel_fd = cutthrough.cctx.sock, pid;

   smtp_peer_options = cutthrough.peer_options;
   continue_sequence = 0;
@@ -8554,7 +8555,8 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
       {
       if (running_in_test_harness) millisleep(100); /* let parent debug out */
       /* does not return */
-      smtp_proxy_tls(big_buffer, big_buffer_size, pfd, 5*60);
+      smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+              pfd, 5*60);
       }


     DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c
index 0e9c381..db3db91 100644
--- a/src/src/dkim_transport.c
+++ b/src/src/dkim_transport.c
@@ -56,7 +56,7 @@ DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
    to the socket. However only if we don't use TLS,
    as then there's another layer of indirection
    before the data finally hits the socket. */
-if (tls_out.active != out_fd)
+if (tls_out.active.sock != out_fd)
   {
   ssize_t copied = 0;


@@ -84,8 +84,8 @@ else
     while (sread)
       {
 #ifdef SUPPORT_TLS
-      wwritten = tls_out.active == out_fd
-    ? tls_write(FALSE, p, sread, FALSE)
+      wwritten = tls_out.active.sock == out_fd
+    ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
     : write(out_fd, CS p, sread);
 #else
       wwritten = write(out_fd, CS p, sread);
diff --git a/src/src/exim.c b/src/src/exim.c
index 58c2dc9..2b4ecbc 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -550,9 +550,9 @@ close_unwanted(void)
 {
 if (smtp_input)
   {
-  #ifdef SUPPORT_TLS
-  tls_close(TRUE, TLS_NO_SHUTDOWN);      /* Shut down the TLS library */
-  #endif
+#ifdef SUPPORT_TLS
+  tls_close(NULL, TLS_NO_SHUTDOWN);      /* Shut down the TLS library */
+#endif
   (void)close(fileno(smtp_in));
   (void)close(fileno(smtp_out));
   smtp_in = NULL;
diff --git a/src/src/functions.h b/src/src/functions.h
index e333f4e..9b10577 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -44,13 +44,13 @@ extern uschar * tls_cert_fprt_md5(void *);
 extern uschar * tls_cert_fprt_sha1(void *);
 extern uschar * tls_cert_fprt_sha256(void *);


-extern int     tls_client_start(int, host_item *, address_item *,
+extern void *  tls_client_start(int, host_item *, address_item *,
          transport_instance *,
 # ifdef SUPPORT_DANE
         dns_answer *,
 # endif
-        uschar **);
-extern void    tls_close(BOOL, int);
+        tls_support *, uschar **);
+extern void    tls_close(void *, int);
 extern BOOL    tls_could_read(void);
 extern int     tls_export_cert(uschar *, size_t, void *);
 extern int     tls_feof(void);
@@ -60,11 +60,11 @@ extern int     tls_getc(unsigned);
 extern uschar *tls_getbuf(unsigned *);
 extern void    tls_get_cache(void);
 extern int     tls_import_cert(const uschar *, void **);
-extern int     tls_read(BOOL, uschar *, size_t);
+extern int     tls_read(void *, uschar *, size_t);
 extern int     tls_server_start(const uschar *, uschar **);
 extern BOOL    tls_smtp_buffered(void);
 extern int     tls_ungetc(int);
-extern int     tls_write(BOOL, const uschar *, size_t, BOOL);
+extern int     tls_write(void *, const uschar *, size_t, BOOL);
 extern uschar *tls_validate_require_cipher(void);
 extern void    tls_version_report(FILE *);
 # ifndef USE_GNUTLS
@@ -271,7 +271,7 @@ extern int     ip_connectedsocket(int, const uschar *, int, int,
                  int, host_item *, uschar **, const blob *);
 extern int     ip_get_address_family(int);
 extern void    ip_keepalive(int, const uschar *, BOOL);
-extern int     ip_recv(int, uschar *, int, int);
+extern int     ip_recv(client_conn_ctx *, uschar *, int, int);
 extern int     ip_socket(int, int);


 extern int     ip_tcpsocket(const uschar *, uschar **, int);
@@ -449,7 +449,7 @@ extern void    smtp_get_cache(void);
 extern int     smtp_handle_acl_fail(int, int, uschar *, uschar *);
 extern void    smtp_log_no_mail(void);
 extern void    smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
-extern void    smtp_proxy_tls(uschar *, size_t, int *, int);
+extern void    smtp_proxy_tls(void *, uschar *, size_t, int *, int);
 extern BOOL    smtp_read_response(smtp_inblock *, uschar *, int, int, int);
 extern void    smtp_reset(void *);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
diff --git a/src/src/globals.c b/src/src/globals.c
index 138a29e..3fa0e3e 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -98,10 +98,11 @@ BOOL    move_frozen_messages   = FALSE;


/* These variables are outside the #ifdef because it keeps the code less
cluttered in several places (e.g. during logging) if we can always refer to
-them. Also, the tls_ variables are now always visible. */
+them. Also, the tls_ variables are now always visible. Note that these are
+only used for smtp connections, not for service-daemon access. */

 tls_support tls_in = {
- .active =        -1,
+ .active =        {.sock = -1},
  .bits =        0,
  .certificate_verified = FALSE,
 #ifdef SUPPORT_DANE
@@ -118,7 +119,7 @@ tls_support tls_in = {
  .ocsp =        OCSP_NOT_REQ
 };
 tls_support tls_out = {
- .active =        -1,
+ .active =        {.sock = -1},
  .bits =        0,
  .certificate_verified = FALSE,
 #ifdef SUPPORT_DANE
@@ -559,7 +560,7 @@ cut_t   cutthrough = {
   .delivery =        FALSE,                /* when to attempt */
   .defer_pass =        FALSE,                /* on defer: spool locally */
   .is_tls =        FALSE,                /* not a TLS conn yet */
-  .fd =            -1,                /* open connection */
+  .cctx =        {.sock = -1},            /* open connection */
   .nrcpt =        0,                /* number of addresses */
 };


diff --git a/src/src/globals.h b/src/src/globals.h
index f9be8b8..ef5b3a5 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -80,7 +80,7 @@ cluttered in several places (e.g. during logging) if we can always refer to
them. Also, the tls_ variables are now always visible. */

 typedef struct {
-  int     active;             /* fd/socket when in a TLS session */
+  client_conn_ctx active;     /* fd/socket when in a TLS session, and ptr to TLS context */
   int     bits;               /* bits used in TLS session */
   BOOL    certificate_verified; /* Client certificate verified */
 #ifdef SUPPORT_DANE
@@ -314,7 +314,7 @@ typedef struct {
   unsigned     delivery:1;             /* When to attempt */
   unsigned     defer_pass:1;           /* Pass 4xx to caller rather than spooling */
   unsigned     is_tls:1;           /* Conn has TLS active */
-  int          fd;                     /* Open connection */
+  client_conn_ctx cctx;                /* Open connection */
   int          nrcpt;                  /* Count of addresses */
   uschar *     transport;           /* Name of transport */
   uschar *     interface;              /* (address of) */
diff --git a/src/src/ip.c b/src/src/ip.c
index 2e89685..555dc2d 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -591,7 +591,7 @@ getting interrupted, and the possibility of select() returning with a positive
 result but no ready descriptor. Is this in fact possible?


 Arguments:
-  sock        the socket
+  cctx        the connection context (socket fd, possibly TLS context)
   buffer      to read into
   bufsize     the buffer size
   timeout     the timeout
@@ -601,24 +601,24 @@ Returns:      > 0 => that much data read
 */


int
-ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout)
{
int rc;

-if (!fd_ready(sock, timeout))
+if (!fd_ready(cctx->sock, timeout))
return -1;

/* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
close down of the connection), set errno to zero; otherwise leave it alone. */

 #ifdef SUPPORT_TLS
-if (tls_out.active == sock)
-  rc = tls_read(FALSE, buffer, buffsize);
-else if (tls_in.active == sock)
-  rc = tls_read(TRUE, buffer, buffsize);
+if (cctx->tls_ctx)                    /* client TLS */
+  rc = tls_read(cctx->tls_ctx, buffer, buffsize);
+else if (tls_in.active.sock == cctx->sock)        /* server TLS */
+  rc = tls_read(NULL, buffer, buffsize);
 else
 #endif
-  rc = recv(sock, buffer, buffsize, 0);
+  rc = recv(cctx->sock, buffer, buffsize, 0);


if (rc > 0) return rc;
if (rc == 0) errno = 0;
diff --git a/src/src/macros.h b/src/src/macros.h
index 85ceb0a..f22fe8c 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -85,7 +85,7 @@ as unsigned. */
a no-op once an SSL session is in progress. */

#ifdef SUPPORT_TLS
-#define mac_smtp_fflush() if (tls_in.active < 0) fflush(smtp_out);
+#define mac_smtp_fflush() if (tls_in.active.sock < 0) fflush(smtp_out);
#else
#define mac_smtp_fflush() fflush(smtp_out);
#endif
diff --git a/src/src/malware.c b/src/src/malware.c
index a5de8a9..49456de 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -436,12 +436,13 @@ for (;;)
static inline int
mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
{
+client_conn_ctx cctx = {.sock = sock};
int offset = 0;
int i;

 do
   {
-  i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+  i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
   if (i <= 0)
     {
     (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
@@ -576,7 +577,7 @@ const pcre *re;
 uschar * errstr;
 struct scan * scanent;
 const uschar * scanner_options;
-int sock = -1;
+client_conn_ctx malware_daemon_ctx = {.sock = -1};
 time_t tmo;
 uschar * eml_filename, * eml_dir;


@@ -657,12 +658,16 @@ if (!malware_ok)
     DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
     switch(scanent->conn)
     {
-    case MC_TCP:  sock = ip_tcpsocket(scanner_options, &errstr, 5);    break;
-    case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr);    break;
-    case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
-    default: /* compiler quietening */ break;
+    case MC_TCP:
+      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5);    break;
+    case MC_UNIX:
+      malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr);    break;
+    case MC_STRM:
+      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5);    break;
+    default:
+      /* compiler quietening */ break;
     }
-    if (sock < 0)
+    if (malware_daemon_ctx.sock < 0)
       return m_panic_defer(scanent, CUS callout_address, errstr);
     break;
   }
@@ -693,10 +698,10 @@ if (!malware_ok)
     scanner_name, scanrequest);


       /* send scan request */
-      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


-      while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
+      while ((len = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) >= 0)
     if (len > 0)
       {
       if (Ustrstr(buf, US"<detected type=\"") != NULL)
@@ -718,7 +723,7 @@ if (!malware_ok)
       }
       if (len < -1)
     {
-    (void)close(sock);
+    (void)close(malware_daemon_ctx.sock);
     return DEFER;
     }
       break;
@@ -742,28 +747,28 @@ if (!malware_ok)
       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
         scanner_name, scanrequest);


-      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
         return m_panic_defer(scanent, CUS callout_address, errstr);


-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));


       if (bread <= 0)
         return m_panic_defer_3(scanent, CUS callout_address,
           string_sprintf("unable to read from socket (%s)", strerror(errno)),
-          sock);
+          malware_daemon_ctx.sock);


       if (bread == sizeof(av_buffer))
         return m_panic_defer_3(scanent, CUS callout_address,
-          US"buffer too small", sock);
+          US"buffer too small", malware_daemon_ctx.sock);


       av_buffer[bread] = '\0';
       linebuffer = string_copy(av_buffer);


-      m_sock_send(sock, US"QUIT\n", 5, 0);
+      m_sock_send(malware_daemon_ctx.sock, US"QUIT\n", 5, 0);


       if ((e = m_pcre_exec(fprot6d_re_error, linebuffer)))
         return m_panic_defer_3(scanent, CUS callout_address,
-          string_sprintf("scanner reported error (%s)", e), sock);
+          string_sprintf("scanner reported error (%s)", e), malware_daemon_ctx.sock);


       if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer)))
         malware_name = NULL;
@@ -795,7 +800,7 @@ if (!malware_ok)
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't open spool file %s: %s",
           eml_filename, strerror(errno)),
-        sock);
+        malware_daemon_ctx.sock);


     if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
       {
@@ -805,7 +810,7 @@ badseek:  err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't seek spool file %s: %s",
           eml_filename, strerror(err)),
-        sock);
+        malware_daemon_ctx.sock);
       }
     fsize_uint = (unsigned int) fsize;
     if ((off_t)fsize_uint != fsize)
@@ -814,7 +819,7 @@ badseek:  err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("seeking spool file %s, size overflow",
           eml_filename),
-        sock);
+        malware_daemon_ctx.sock);
       }
     drweb_slen = htonl(fsize);
     if (lseek(drweb_fd, 0, SEEK_SET) < 0)
@@ -824,15 +829,15 @@ badseek:  err = errno;
         scanner_name, scanner_options);


     /* send scan request */
-    if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
-        (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
-        (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
-        (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+    if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
       {
       (void)close(drweb_fd);
       return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
         "unable to send commands to socket (%s)", scanner_options),
-        sock);
+        malware_daemon_ctx.sock);
       }


     if (!(drweb_fbuf = US malloc(fsize_uint)))
@@ -841,7 +846,7 @@ badseek:  err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("unable to allocate memory %u for file (%s)",
           fsize_uint, eml_filename),
-        sock);
+        malware_daemon_ctx.sock);
       }


     if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1)
@@ -852,17 +857,17 @@ badseek:  err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't read spool file %s: %s",
           eml_filename, strerror(err)),
-        sock);
+        malware_daemon_ctx.sock);
       }
     (void)close(drweb_fd);


     /* send file body to socket */
-    if (send(sock, drweb_fbuf, fsize, 0) < 0)
+    if (send(malware_daemon_ctx.sock, drweb_fbuf, fsize, 0) < 0)
       {
       free(drweb_fbuf);
       return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
         "unable to send file body to socket (%s)", scanner_options),
-        sock);
+        malware_daemon_ctx.sock);
       }
     }
       else
@@ -873,25 +878,25 @@ badseek:  err = errno;
         scanner_name, scanner_options);


     /* send scan request */
-    if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
-        (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
-        (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
-        (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
-        (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
+    if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
       return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
         "unable to send commands to socket (%s)", scanner_options),
-        sock);
+        malware_daemon_ctx.sock);
     }


       /* wait for result */
-      if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_rc, sizeof(drweb_rc), tmo))
     return m_panic_defer_3(scanent, CUS callout_address,
-            US"unable to read return code", sock);
+            US"unable to read return code", malware_daemon_ctx.sock);
       drweb_rc = ntohl(drweb_rc);


-      if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
     return m_panic_defer_3(scanent, CUS callout_address,
-                US"unable to read the number of viruses", sock);
+                US"unable to read the number of viruses", malware_daemon_ctx.sock);
       drweb_vnum = ntohl(drweb_vnum);


       /* "virus(es) found" if virus number is > 0 */
@@ -913,16 +918,16 @@ badseek:  err = errno;
       int ovector[10*3];


       /* read the size of report */
-      if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo))
         return m_panic_defer_3(scanent, CUS callout_address,
-                  US"cannot read report size", sock);
+                  US"cannot read report size", malware_daemon_ctx.sock);
       drweb_slen = ntohl(drweb_slen);
       tmpbuf = store_get(drweb_slen);


       /* read report body */
-      if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
+      if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo))
         return m_panic_defer_3(scanent, CUS callout_address,
-                  US"cannot read report string", sock);
+                  US"cannot read report string", malware_daemon_ctx.sock);
       tmpbuf[drweb_slen] = '\0';


       /* try matcher on the line, grab substring */
@@ -961,7 +966,7 @@ badseek:  err = errno;
     if (drweb_s)
       return m_panic_defer_3(scanent, CUS callout_address,
         string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
-        sock);
+        malware_daemon_ctx.sock);


     /* no virus found */
     malware_name = NULL;
@@ -978,13 +983,13 @@ badseek:  err = errno;


       /* read aveserver's greeting and see if it is ready (2xx greeting) */
       buf[0] = 0;
-      recv_line(sock, buf, sizeof(buf), tmo);
+      recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);


       if (buf[0] != '2')        /* aveserver is having problems */
     return m_panic_defer_3(scanent, CUS callout_address,
       string_sprintf("unavailable (Responded: %s).",
               ((buf[0] != 0) ? buf : US "nothing") ),
-      sock);
+      malware_daemon_ctx.sock);


       /* prepare our command */
       (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n",
@@ -993,13 +998,13 @@ badseek:  err = errno;
       /* and send it */
       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n",
     scanner_name, buf);
-      if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, buf, Ustrlen(buf), &errstr) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


       malware_name = NULL;
       result = 0;
       /* read response lines, find malware name and final response */
-      while (recv_line(sock, buf, sizeof(buf), tmo) > 0)
+      while (recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo) > 0)
     {
     if (buf[0] == '2')
       break;
@@ -1018,22 +1023,22 @@ badseek:  err = errno;
       }
     }


-      if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, US"quit\r\n", 6, &errstr) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


       /* read aveserver's greeting and see if it is ready (2xx greeting) */
       buf[0] = 0;
-      recv_line(sock, buf, sizeof(buf), tmo);
+      recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);


       if (buf[0] != '2')        /* aveserver is having problems */
     return m_panic_defer_3(scanent, CUS callout_address,
       string_sprintf("unable to quit dialogue (Responded: %s).",
             ((buf[0] != 0) ? buf : US "nothing") ),
-      sock);
+      malware_daemon_ctx.sock);


       if (result == DEFER)
     {
-    (void)close(sock);
+    (void)close(malware_daemon_ctx.sock);
     return DEFER;
     }
       break;
@@ -1060,15 +1065,15 @@ badseek:  err = errno;
       for (i = 0; i != nelem(cmdopt); i++)
     {


-    if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+    if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
       return m_panic_defer(scanent, CUS callout_address, errstr);


-    bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+    bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
     if (bread > 0) av_buffer[bread]='\0';
     if (bread < 0)
       return m_panic_defer_3(scanent, CUS callout_address,
         string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
-        sock);
+        malware_daemon_ctx.sock);
     for (j = 0; j < bread; j++)
       if (av_buffer[j] == '\r' || av_buffer[j] == '\n')
         av_buffer[j] ='@';
@@ -1077,7 +1082,7 @@ badseek:  err = errno;
       /* pass the mailfile to fsecure */
       file_name = string_sprintf("SCAN\t%s\n", eml_filename);


-      if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, file_name, Ustrlen(file_name), &errstr) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


       /* set up match */
@@ -1095,10 +1100,10 @@ badseek:  err = errno;
       {
       errno = ETIMEDOUT;
       i =  av_buffer+sizeof(av_buffer)-p;
-      if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
+      if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0)
         return m_panic_defer_3(scanent, CUS callout_address,
           string_sprintf("unable to read result (%s)", strerror(errno)),
-          sock);
+          malware_daemon_ctx.sock);


       for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1)
         {
@@ -1155,13 +1160,13 @@ badseek:  err = errno;
       scanner_name, scanner_options);


       /* send scan request */
-      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


       /* wait for result */
-      if (!recv_len(sock, tmpbuf, 2, tmo))
+      if (!recv_len(malware_daemon_ctx.sock, tmpbuf, 2, tmo))
     return m_panic_defer_3(scanent, CUS callout_address,
-                US"unable to read 2 bytes from socket.", sock);
+                US"unable to read 2 bytes from socket.", malware_daemon_ctx.sock);


       /* get errorcode from one nibble */
       kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F;
@@ -1170,13 +1175,13 @@ badseek:  err = errno;
       case 5: case 6: /* improper kavdaemon configuration */
     return m_panic_defer_3(scanent, CUS callout_address,
         US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
-        sock);
+        malware_daemon_ctx.sock);
       case 1:
     return m_panic_defer_3(scanent, CUS callout_address,
-        US"reported 'scanning not completed' (code 1).", sock);
+        US"reported 'scanning not completed' (code 1).", malware_daemon_ctx.sock);
       case 7:
     return m_panic_defer_3(scanent, CUS callout_address,
-        US"reported 'kavdaemon damaged' (code 7).", sock);
+        US"reported 'kavdaemon damaged' (code 7).", malware_daemon_ctx.sock);
       }


       /* code 8 is not handled, since it is ambiguous. It appears mostly on
@@ -1196,9 +1201,9 @@ badseek:  err = errno;
     if (report_flag == 1)
       {
       /* read report size */
-      if (!recv_len(sock, &kav_reportlen, 4, tmo))
+      if (!recv_len(malware_daemon_ctx.sock, &kav_reportlen, 4, tmo))
         return m_panic_defer_3(scanent, CUS callout_address,
-          US"cannot read report size", sock);
+          US"cannot read report size", malware_daemon_ctx.sock);


       /* it's possible that avp returns av_buffer[1] == 1 but the
       reportsize is 0 (!?) */
@@ -1221,7 +1226,7 @@ badseek:  err = errno;
         /* coverity[tainted_data] */
         while (kav_reportlen > 0)
           {
-          if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
+          if ((bread = recv_line(malware_daemon_ctx.sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
         break;
           kav_reportlen -= bread+1;


@@ -1391,19 +1396,19 @@ badseek:  err = errno;
       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
       scanner_name, scanner_options);


-      if (  write(sock, file_name, Ustrlen(file_name)) < 0
-     || write(sock, "\n", 1) != 1
+      if (  write(malware_daemon_ctx.sock, file_name, Ustrlen(file_name)) < 0
+     || write(malware_daemon_ctx.sock, "\n", 1) != 1
      )
     return m_panic_defer_3(scanent, CUS callout_address,
       string_sprintf("unable to write to UNIX socket (%s)", scanner_options),
-      sock);
+      malware_daemon_ctx.sock);


       /* wait for result */
       memset(av_buffer, 0, sizeof(av_buffer));
-      if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
+      if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
     return m_panic_defer_3(scanent, CUS callout_address,
       string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
-      sock);
+      malware_daemon_ctx.sock);


       /* infected ? */
       if (av_buffer[0] == '1') {
@@ -1414,7 +1419,7 @@ badseek:  err = errno;
       }
       else if (!strncmp(CS av_buffer, "-1", 2))
     return m_panic_defer_3(scanent, CUS callout_address,
-        US"scanner reported error", sock);
+        US"scanner reported error", malware_daemon_ctx.sock);
       else /* all ok, no virus */
     malware_name = NULL;


@@ -1573,7 +1578,7 @@ badseek:  err = errno;
        * on both connections (as one host could resolve to multiple ips) */
       for (;;)
         {
-        if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
+        if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
                     &connhost, &errstr, &cmd_str)) >= 0)
           {
           /* Connection successfully established with a server */
@@ -1584,7 +1589,7 @@ badseek:  err = errno;
         if (cd->retry <= 0) break;
         while (cd->retry > 0) cd->retry = sleep(cd->retry);
         }
-      if (sock >= 0)
+      if (malware_daemon_ctx.sock >= 0)
         break;


       (void) m_panic_defer(scanent, CUS callout_address, errstr);
@@ -1601,7 +1606,7 @@ badseek:  err = errno;
       else
     for (;;)
       {
-      if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+      if ((malware_daemon_ctx.sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
         {
         hostname = cv[0]->hostspec;
         break;
@@ -1628,11 +1633,11 @@ badseek:  err = errno;


     /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
     if (cmd_str.len)
-      if (send(sock, cmd_str.data, cmd_str.len, 0) < 0)
+      if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
         return m_panic_defer_3(scanent, CUS hostname,
           string_sprintf("unable to send zINSTREAM to socket (%s)",
         strerror(errno)),
-          sock);
+          malware_daemon_ctx.sock);


     /* calc file size */
     if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
@@ -1641,7 +1646,7 @@ badseek:  err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't open spool file %s: %s",
           eml_filename, strerror(err)),
-        sock);
+        malware_daemon_ctx.sock);
       }
     if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
       {
@@ -1651,7 +1656,7 @@ b_seek:   err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't seek spool file %s: %s",
           eml_filename, strerror(err)),
-        sock);
+        malware_daemon_ctx.sock);
       }
     fsize_uint = (unsigned int) fsize;
     if ((off_t)fsize_uint != fsize)
@@ -1660,7 +1665,7 @@ b_seek:   err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("seeking spool file %s, size overflow",
           eml_filename),
-        sock);
+        malware_daemon_ctx.sock);
       }
     if (lseek(clam_fd, 0, SEEK_SET) < 0)
       goto b_seek;
@@ -1671,7 +1676,7 @@ b_seek:   err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("unable to allocate memory %u for file (%s)",
           fsize_uint, eml_filename),
-        sock);
+        malware_daemon_ctx.sock);
       }


     if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
@@ -1681,21 +1686,21 @@ b_seek:   err = errno;
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't read spool file %s: %s",
           eml_filename, strerror(err)),
-        sock);
+        malware_daemon_ctx.sock);
       }
     (void)close(clam_fd);


     /* send file body to socket */
     send_size = htonl(fsize_uint);
     send_final_zeroblock = 0;
-    if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
-        (send(sock, clamav_fbuf, fsize_uint, 0) < 0) ||
-        (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
+    if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) ||
+        (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) ||
+        (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
       {
       free(clamav_fbuf);
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("unable to send file body to socket (%s)", hostname),
-        sock);
+        malware_daemon_ctx.sock);
       }


     free(clamav_fbuf);
@@ -1722,10 +1727,10 @@ b_seek:   err = errno;
         scanner_name, scanner_options);


     if (cmd_str.len)
-      if (send(sock, cmd_str.data, cmd_str.len, 0) < 0)
+      if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
         return m_panic_defer_3(scanent, CUS callout_address,
           string_sprintf("unable to write to socket (%s)", strerror(errno)),
-          sock);
+          malware_daemon_ctx.sock);


     /* Do not shut down the socket for writing; a user report noted that
      * clamd 0.70 does not react well to this. */
@@ -1735,9 +1740,10 @@ b_seek:   err = errno;


       /* Read the result */
       memset(av_buffer, 0, sizeof(av_buffer));
-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
-      (void)close(sock);
-      sock = -1;
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      (void)close(malware_daemon_ctx.sock);
+      malware_daemon_ctx.sock = -1;
+      malware_daemon_ctx.tls_ctx = NULL;


       if (bread <= 0)
     return m_panic_defer(scanent, CUS callout_address,
@@ -1863,7 +1869,7 @@ b_seek:   err = errno;
     if (s++)
       if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
         return m_panic_defer_3(scanent, NULL,
-                  US"unsafe sock scanner call spec", sock);
+                  US"unsafe sock scanner call spec", malware_daemon_ctx.sock);
       }
       else
     sockline_scanner = sockline_scanner_default;
@@ -1874,13 +1880,13 @@ b_seek:   err = errno;
       sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                 "missing trigger specification", &errstr);
       if (!sockline_trig_re)
-    return m_panic_defer_3(scanent, NULL, errstr, sock);
+    return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);


       /* find virus name regex */
       sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep,
               "missing virus name regex specification", &errstr);
       if (!sockline_name_re)
-    return m_panic_defer_3(scanent, NULL, errstr, sock);
+    return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);


       /* prepare scanner call - security depends on expansions check above */
       commandline = string_sprintf( CS sockline_scanner, CS eml_filename);
@@ -1888,20 +1894,20 @@ b_seek:   err = errno;
     string_printing(commandline));


       /* Pass the command string to the socket */
-      if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0)
+      if (m_sock_send(malware_daemon_ctx.sock, commandline, Ustrlen(commandline), &errstr) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


       /* Read the result */
-      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));


       if (bread <= 0)
     return m_panic_defer_3(scanent, CUS callout_address,
       string_sprintf("unable to read from socket (%s)", strerror(errno)),
-      sock);
+      malware_daemon_ctx.sock);


       if (bread == sizeof(av_buffer))
     return m_panic_defer_3(scanent, CUS callout_address,
-        US"buffer too small", sock);
+        US"buffer too small", malware_daemon_ctx.sock);
       av_buffer[bread] = '\0';
       linebuffer = string_copy(av_buffer);
       DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ",
@@ -1940,16 +1946,16 @@ b_seek:   err = errno;
         string_sprintf("invalid option '%s'", scanner_options));
     }


-      if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
+      if((malware_daemon_ctx.sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
     return m_panic_defer(scanent, CUS callout_address, errstr);


       malware_name = NULL;


       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name);


-      if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK)
+      if ((retval = mksd_scan_packed(scanent, malware_daemon_ctx.sock, eml_filename, tmo)) != OK)
     {
-    close (sock);
+    close (malware_daemon_ctx.sock);
     return retval;
     }
       break;
@@ -2018,7 +2024,7 @@ b_seek:   err = errno;


       /* wait for result */
       for (avast_stage = AVA_HELO;
-       (nread = recv_line(sock, buf, sizeof(buf), tmo)) > 0;
+       (nread = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) > 0;
       )
     {
     int slen = Ustrlen(buf);
@@ -2072,12 +2078,12 @@ b_seek:   err = errno;


           /* send config-cmd or scan-request to socket */
           len = Ustrlen(scanrequest);
-          if (send(sock, scanrequest, len, 0) == -1)
+          if (send(malware_daemon_ctx.sock, scanrequest, len, 0) == -1)
         {
         scanrequest[len-1] = '\0';
         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
               "unable to send request '%s' to socket (%s): %s",
-              scanrequest, scanner_options, strerror(errno)), sock);
+              scanrequest, scanner_options, strerror(errno)), malware_daemon_ctx.sock);
         }
           break;
           }
@@ -2136,20 +2142,20 @@ b_seek:   err = errno;
       else if (buf[0] != '2') error_message = buf;


       DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n");
-      if (send(sock, "QUIT\n", 5, 0) == -1)
+      if (send(malware_daemon_ctx.sock, "QUIT\n", 5, 0) == -1)
         return m_panic_defer_3(scanent, CUS callout_address,
           string_sprintf("unable to send quit request to socket (%s): %s",
-            scanner_options, strerror(errno)), sock);
+            scanner_options, strerror(errno)), malware_daemon_ctx.sock);


       if (error_message)
-        return m_panic_defer_3(scanent, CUS callout_address, error_message, sock);
+        return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock);


       }
 #endif
   }    /* scanner type switch */


-  if (sock >= 0)
-    (void) close (sock);
+  if (malware_daemon_ctx.sock >= 0)
+    (void) close (malware_daemon_ctx.sock);
   malware_ok = TRUE;            /* set "been here, done that" marker */
   }


diff --git a/src/src/receive.c b/src/src/receive.c
index c378142..974756f 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1181,7 +1181,7 @@ switch(where)
   case ACL_WHERE_DKIM:
   case ACL_WHERE_MIME:
   case ACL_WHERE_DATA:
-    if (  cutthrough.fd >= 0 && cutthrough.delivery
+    if (  cutthrough.cctx.sock >= 0 && cutthrough.delivery
        && (acl_removed_headers || acl_added_headers))
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
@@ -2995,7 +2995,7 @@ 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.
 Having created it, send the headers to the destination. */


-if (cutthrough.fd >= 0 && cutthrough.delivery)
+if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
   {
   if (received_count > received_headers_max)
     {
@@ -4179,7 +4179,7 @@ for this message. */


    XXX We do not handle queue-only, freezing, or blackholes.
 */
-if(cutthrough.fd >= 0 && cutthrough.delivery)
+if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)
   {
   uschar * msg = cutthrough_finaldot();    /* Ask the target system to accept the message */
                     /* Logging was done in finaldot() */
diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c
index e5e25ae..bc58a1f 100644
--- a/src/src/routers/iplookup.c
+++ b/src/src/routers/iplookup.c
@@ -230,7 +230,8 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,


   for (h = host; h; h = h->next)
     {
-    int host_af, query_socket;
+    int host_af;
+    client_conn_ctx query_cctx = {0};


     /* Skip any hosts for which we have no address */


@@ -241,9 +242,9 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,

     host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;


-    query_socket = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
+    query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
       host_af);
-    if (query_socket < 0)
+    if (query_cctx.sock < 0)
       {
       if (ob->optional) return PASS;
       addr->message = string_sprintf("failed to create socket in %s router",
@@ -256,10 +257,10 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     router will timeout later on the read call). */
 /*XXX could take advantage of TFO */


-    if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout,
+    if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
         ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
       {
-      close(query_socket);
+      close(query_cctx.sock);
       DEBUG(D_route)
         debug_printf("connection to %s failed: %s\n", h->address,
           strerror(errno));
@@ -268,18 +269,18 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,


     /* Send the query. If it fails, just continue with the next address. */


-    if (send(query_socket, query, query_len, 0) < 0)
+    if (send(query_cctx.sock, query, query_len, 0) < 0)
       {
       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
-      (void)close(query_socket);
+      (void)close(query_cctx.sock);
       continue;
       }


     /* Read the response and close the socket. If the read fails, try the
     next IP address. */


-    count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
-    (void)close(query_socket);
+    count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout);
+    (void)close(query_cctx.sock);
     if (count <= 0)
       {
       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index e09d35c..0afb97c 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -340,7 +340,7 @@ fd_set fds;
 struct timeval tzero;


#ifdef SUPPORT_TLS
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
return !tls_could_read();
#endif

@@ -429,7 +429,7 @@ smtp_command_timeout_exit(void)
 {
 log_write(L_lost_incoming_connection,
       LOG_MAIN, "SMTP command timeout on%s connection from %s",
-      tls_in.active >= 0 ? " TLS" : "", host_and_ident(FALSE));
+      tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
 smtp_notquit_exit(US"command-timeout", US"421",
@@ -922,9 +922,9 @@ if (rcpt_in_progress)
 /* Now write the string */


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


@@ -3686,7 +3686,7 @@ switch(rc)

     received_protocol =
       (sender_host_address ? protocols : protocols_local)
-    [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+    [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
     *s = *ss = US"235 Authentication succeeded";
     authenticated_by = au;
     break;
@@ -3780,7 +3780,7 @@ else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);


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

log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
@@ -3891,7 +3891,7 @@ while (done <= 0)

 #ifdef AUTH_TLS
   /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
-  if (  tls_in.active >= 0
+  if (  tls_in.active.sock >= 0
      && tls_in.peercert
      && tls_in.certificate_verified
      && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
@@ -4108,7 +4108,7 @@ while (done <= 0)


       host_build_sender_fullhost();  /* Rebuild */
       set_process_info("handling%s incoming connection from %s",
-        (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
+        (tls_in.active.sock >= 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,
@@ -4364,7 +4364,7 @@ while (done <= 0)
       secure connection. */


 #ifdef SUPPORT_TLS
-      if (tls_in.active < 0 &&
+      if (tls_in.active.sock < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
         g = string_catn(g, smtp_code, 3);
@@ -4402,7 +4402,7 @@ while (done <= 0)
     has been seen. */


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


       {
@@ -4425,7 +4425,7 @@ while (done <= 0)
     [ (esmtp
       ? pextend + (sender_host_authenticated ? pauthed : 0)
       : pnormal)
-    + (tls_in.active >= 0 ? pcrpted : 0)
+    + (tls_in.active.sock >= 0 ? pcrpted : 0)
     ];
     cancel_cutthrough_connection(TRUE, US"sent EHLO response");
     smtp_reset(reset_point);
@@ -5213,7 +5213,7 @@ while (done <= 0)
       ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
       to get the DATA command sent. */


-      if (acl_smtp_predata == NULL && cutthrough.fd < 0)
+      if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
     rc = OK;
       else
     {
@@ -5367,7 +5367,7 @@ while (done <= 0)
       {
       DEBUG(D_any)
         debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
-      if (tls_in.active < 0)
+      if (tls_in.active.sock < 0)
         smtp_inend = smtp_inptr = smtp_inbuffer;
       /* and if TLS is already active, tls_server_start() should fail */
       }
@@ -5410,7 +5410,7 @@ while (done <= 0)
       [ (esmtp
         ? pextend + (sender_host_authenticated ? pauthed : 0)
         : pnormal)
-      + (tls_in.active >= 0 ? pcrpted : 0)
+      + (tls_in.active.sock >= 0 ? pcrpted : 0)
       ];


       sender_host_auth_pubname = sender_host_authenticated = NULL;
@@ -5470,7 +5470,7 @@ while (done <= 0)
     smtp_printf("554 Security failure\r\n", FALSE);
     break;
       }
-    tls_close(TRUE, TLS_SHUTDOWN_NOWAIT);
+    tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
     break;
     #endif


@@ -5512,7 +5512,7 @@ while (done <= 0)
       buffer[0] = 0;
       Ustrcat(buffer, " AUTH");
       #ifdef SUPPORT_TLS
-      if (tls_in.active < 0 &&
+      if (tls_in.active.sock < 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 c968f70..70aaef3 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -411,11 +411,11 @@ HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
   more ? " (more expected)" : "");


 #ifdef SUPPORT_TLS
-if (tls_out.active == outblock->sock)
-  rc = tls_write(FALSE, outblock->buffer, n, more);
+if (outblock->cctx->tls_ctx)
+  rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more);
 else
 #endif
-  rc = send(outblock->sock, outblock->buffer, n,
+  rc = send(outblock->cctx->sock, outblock->buffer, n,
 #ifdef MSG_MORE
         more ? MSG_MORE : 0
 #else
@@ -546,7 +546,7 @@ read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout)
 uschar *p = buffer;
 uschar *ptr = inblock->ptr;
 uschar *ptrend = inblock->ptrend;
-int sock = inblock->sock;
+client_conn_ctx * cctx = inblock->cctx;


/* Loop for reading multiple packets or reading another packet after emptying
a previously-read one. */
@@ -584,7 +584,7 @@ for (;;)

/* Need to read a new input packet. */

-  if((rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout)) <= 0)
+  if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0)
     {
     DEBUG(D_deliver|D_transport|D_acl)
       debug_printf_indent(errno ? "  SMTP(%s)<<\n" : "  SMTP(closed)<<\n",
diff --git a/src/src/spam.c b/src/src/spam.c
index 8e08a40..9384bfa 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -193,7 +193,7 @@ uschar *user_name;
 uschar user_name_buffer[128];
 unsigned long mbox_size;
 FILE *mbox_file;
-int spamd_sock = -1;
+client_conn_ctx spamd_cctx = {.sock = -1};
 uschar spamd_buffer[32600];
 int i, j, offset, result;
 uschar spamd_version[8];
@@ -344,14 +344,14 @@ start = time(NULL);
     for (;;)
       {
       /*XXX could potentially use TFO early-data here */
-      if (  (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
+      if (  (spamd_cctx.sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
          || sd->retry <= 0
      )
     break;
       DEBUG(D_acl) debug_printf_indent("spamd: server %s: retry conn\n", sd->hostspec);
       while (sd->retry > 0) sd->retry = sleep(sd->retry);
       }
-    if (spamd_sock >= 0)
+    if (spamd_cctx.sock >= 0)
       break;


     log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr);
@@ -367,8 +367,8 @@ start = time(NULL);
     }
   }


-(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
-/* now we are connected to spamd on spamd_sock */
+(void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK);
+/* now we are connected to spamd on spamd_cctx.sock */
 if (sd->is_rspamd)
   {
   gstring * req_str;
@@ -392,7 +392,7 @@ if (sd->is_rspamd)
   if ((s = expand_string(US"$authenticated_id")) && *s)
     req_str = string_append(req_str, 3, "User: ", s, "\r\n");
   req_str = string_catn(req_str, US"\r\n", 2);
-  wrote = send(spamd_sock, req_str->s, req_str->ptr, 0);
+  wrote = send(spamd_cctx.sock, req_str->s, req_str->ptr, 0);
   }
 else
   {                /* spamassassin variant */
@@ -402,12 +402,12 @@ else
       user_name,
       mbox_size);
   /* send our request */
-  wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
+  wrote = send(spamd_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
   }


 if (wrote == -1)
   {
-  (void)close(spamd_sock);
+  (void)close(spamd_cctx.sock);
   log_write(0, LOG_MAIN|LOG_PANIC,
        "%s spamd %s send failed: %s", loglabel, callout_address, strerror(errno));
   goto defer;
@@ -424,10 +424,10 @@ if (wrote == -1)
  *       broken in more recent versions (up to 10.4).
  */
 #ifndef NO_POLL_H
-pollfd.fd = spamd_sock;
+pollfd.fd = spamd_cctx.sock;
 pollfd.events = POLLOUT;
 #endif
-(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
+(void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK);
 do
   {
   read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
@@ -443,8 +443,8 @@ again:
     select_tv.tv_sec = 1;
     select_tv.tv_usec = 0;
     FD_ZERO(&select_fd);
-    FD_SET(spamd_sock, &select_fd);
-    result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
+    FD_SET(spamd_cctx.sock, &select_fd);
+    result = select(spamd_cctx.sock+1, NULL, &select_fd, NULL, &select_tv);
 #endif
 /* End Erik's patch */


@@ -462,16 +462,16 @@ again:
     log_write(0, LOG_MAIN|LOG_PANIC,
       "%s timed out writing spamd %s, socket", loglabel, callout_address);
     }
-      (void)close(spamd_sock);
+      (void)close(spamd_cctx.sock);
       goto defer;
       }


-    wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0);
+    wrote = send(spamd_cctx.sock,spamd_buffer + offset,read - offset,0);
     if (wrote == -1)
       {
       log_write(0, LOG_MAIN|LOG_PANIC,
       "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
-      (void)close(spamd_sock);
+      (void)close(spamd_cctx.sock);
       goto defer;
       }
     if (offset + wrote != read)
@@ -487,7 +487,7 @@ if (ferror(mbox_file))
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
     "%s error reading spool file: %s", loglabel, strerror(errno));
-  (void)close(spamd_sock);
+  (void)close(spamd_cctx.sock);
   goto defer;
   }


@@ -495,12 +495,12 @@ if (ferror(mbox_file))

/* we're done sending, close socket for writing */
if (!sd->is_rspamd)
- shutdown(spamd_sock,SHUT_WR);
+ shutdown(spamd_cctx.sock,SHUT_WR);

 /* read spamd response using what's left of the timeout.  */
 memset(spamd_buffer, 0, sizeof(spamd_buffer));
 offset = 0;
-while ((i = ip_recv(spamd_sock,
+while ((i = ip_recv(&spamd_cctx,
            spamd_buffer + offset,
            sizeof(spamd_buffer) - offset - 1,
            sd->timeout - time(NULL) + start)) > 0)
@@ -512,12 +512,12 @@ if (i <= 0 && errno != 0)
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
        "%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno));
-  (void)close(spamd_sock);
+  (void)close(spamd_cctx.sock);
   return DEFER;
   }


/* reading done */
-(void)close(spamd_sock);
+(void)close(spamd_cctx.sock);

 if (sd->is_rspamd)
   {                /* rspamd variant of reply */
diff --git a/src/src/structs.h b/src/src/structs.h
index 8384920..9ee3dba 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -780,15 +780,21 @@ md5;
 typedef struct sha1 {
   unsigned int H[5];
   unsigned int length;
-  }
-sha1;
+} sha1;
+
+/* A client-initiated connection. If TLS, the second element is non-NULL */
+typedef struct {
+  int    sock;
+  void * tls_ctx;
+} client_conn_ctx;
+


/* Structure used to hold incoming packets of SMTP responses for a specific
socket. The packets which may contain multiple lines (and in some cases,
multiple responses). */

 typedef struct smtp_inblock {
-  int     sock;                   /* the socket */
+  client_conn_ctx * cctx;      /* the connection */
   int     buffersize;             /* the size of the buffer */
   uschar *ptr;                    /* current position in the buffer */
   uschar *ptrend;                 /* end of data in the buffer */
@@ -800,7 +806,7 @@ specific socket. The packets which may contain multiple lines when pipelining
 is in use. */


 typedef struct smtp_outblock {
-  int     sock;                   /* the socket */
+  client_conn_ctx * cctx;      /* the connection */
   int     cmd_count;              /* count of buffered commands */
   int     buffersize;             /* the size of the buffer */
   BOOL    authenticating;         /* TRUE when authenticating */
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 08c1d93..12c9fdb 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -213,7 +213,7 @@ second connection.
 XXX But see gnutls_session_get_ptr()
 */


-static exim_gnutls_state_st state_server, state_client;
+static exim_gnutls_state_st state_server;

/* 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
@@ -448,7 +448,8 @@ gnutls_datum_t channel;
#endif
tls_support * tlsp = state->tlsp;

-tlsp->active = state->fd_out;
+tlsp->active.sock = state->fd_out;
+tlsp->active.tls_ctx = state;

 cipher = gnutls_cipher_get(state->session);
 /* returns size in "bytes" */
@@ -1262,6 +1263,7 @@ tls_init(
     const uschar *crl,
     const uschar *require_ciphers,
     exim_gnutls_state_st **caller_state,
+    tls_support * tlsp,
     uschar ** errstr)
 {
 exim_gnutls_state_st *state;
@@ -1310,9 +1312,15 @@ if (!exim_gnutls_base_init_done)


if (host)
{
- state = &state_client;
+ /* For client-side sessions we allocate a context. This lets us run
+ several in parallel. */
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ state = store_get(sizeof(exim_gnutls_state_st));
+ store_pool = old_pool;
+
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_out;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
rc = gnutls_init(&state->session, GNUTLS_CLIENT);
}
@@ -1320,7 +1328,7 @@ else
{
state = &state_server;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_in;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER);
}
@@ -1980,7 +1988,7 @@ int rc;
exim_gnutls_state_st * state = NULL;

/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
smtp_printf("554 Already in TLS\r\n", FALSE);
@@ -1994,7 +2002,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");

 if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
     NULL, tls_verify_certificates, tls_crl,
-    require_ciphers, &state, errstr)) != OK) return rc;
+    require_ciphers, &state, &tls_in, errstr)) != OK) return rc;


 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */
@@ -2241,26 +2249,27 @@ Arguments:
             Which implies cert must be requested and supplied, dane
             verify must pass, and cert verify irrelevant (incl.
             hostnames), and (caller handled) require_tls
+  tlsp            record details of channel configuration
   errstr        error string pointer


-Returns:            OK/DEFER/FAIL (because using common functions),
-                    but for a client, DEFER and FAIL have the same meaning
+Returns:            Pointer to TLS session context, or NULL on error
 */


-int
+void *
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
     transport_instance * tb,
 #ifdef SUPPORT_DANE
     dns_answer * tlsa_dnsa,
 #endif
-    uschar ** errstr)
+    tls_support * tlsp, uschar ** errstr)
 {
 smtp_transport_options_block *ob =
   (smtp_transport_options_block *)tb->options_block;
 int rc;
 exim_gnutls_state_st * state = NULL;
 uschar *cipher_list = NULL;
+
 #ifndef DISABLE_OCSP
 BOOL require_ocsp =
   verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
@@ -2276,7 +2285,7 @@ if (tlsa_dnsa && ob->dane_require_tls_ciphers)
   /* not using expand_check_tlsvar because not yet in state */
   if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
       &cipher_list, errstr))
-    return DEFER;
+    return NULL;
   cipher_list = cipher_list && *cipher_list
     ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
   }
@@ -2285,10 +2294,10 @@ if (tlsa_dnsa && ob->dane_require_tls_ciphers)
 if (!cipher_list)
   cipher_list = ob->tls_require_ciphers;


-if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
     ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
-    cipher_list, &state, errstr)) != OK)
-  return rc;
+    cipher_list, &state, tlsp, errstr) != OK)
+  return NULL;


   {
   int dh_min_bits = ob->tls_dh_min_bits;
@@ -2357,9 +2366,11 @@ if (request_ocsp)
   DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
             NULL, 0, NULL)) != OK)
-    return tls_error(US"cert-status-req",
-            gnutls_strerror(rc), state->host, errstr);
-  tls_out.ocsp = OCSP_NOT_RESP;
+    {
+    tls_error(US"cert-status-req", gnutls_strerror(rc), state->host, errstr);
+    return NULL;
+    }
+  tlsp->ocsp = OCSP_NOT_RESP;
   }
 #endif


@@ -2387,20 +2398,26 @@ while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
alarm(0);

 if (rc != GNUTLS_E_SUCCESS)
+  {
   if (sigalrm_seen)
     {
     gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
-    return tls_error(US"gnutls_handshake", "timed out", state->host, errstr);
+    tls_error(US"gnutls_handshake", "timed out", state->host, errstr);
     }
   else
-    return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr);
+    tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr);
+  return NULL;
+  }


DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");

/* Verify late */

if (!verify_certificate(state, errstr))
- return tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+ {
+ tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+ return NULL;
+ }

#ifndef DISABLE_OCSP
if (require_ocsp)
@@ -2425,24 +2442,25 @@ if (require_ocsp)

   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
     {
-    tls_out.ocsp = OCSP_FAILED;
-    return tls_error(US"certificate status check failed", NULL, state->host, errstr);
+    tlsp->ocsp = OCSP_FAILED;
+    tls_error(US"certificate status check failed", NULL, state->host, errstr);
+    return NULL;
     }
   DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
-  tls_out.ocsp = OCSP_VFIED;
+  tlsp->ocsp = OCSP_VFIED;
   }
 #endif


/* Figure out peer DN, and if authenticated, etc. */

-if ((rc = peer_status(state, errstr)) != OK)
- return rc;
+if (peer_status(state, errstr) != OK)
+ return NULL;

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

extract_exim_vars_from_tls_state(state);

-return OK;
+return state;
}


@@ -2457,6 +2475,7 @@ daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the TLS session in the parent process).

 Arguments:
+  ct_ctx    client context pointer, or NULL for the one global server context
   shutdown    1 if TLS close-alert is to be sent,
          2 if also response to be waited for


@@ -2464,11 +2483,11 @@ Returns:     nothing
 */


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

-if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */
+if (!state->tlsp || state->tlsp->active.sock < 0) return; /* TLS was not active */

if (shutdown)
{
@@ -2484,15 +2503,10 @@ gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred);


-state->tlsp->active = -1;
+state->tlsp->active.sock = -1;
+state->tlsp->active.tls_ctx = NULL;
if (state->xfer_buffer) store_free(state->xfer_buffer);
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
-
-if (!state_server.session && !state_client.session)
- {
- gnutls_global_deinit();
- exim_gnutls_base_init_done = FALSE;
- }
}


@@ -2549,7 +2563,8 @@ else if (inbytes == 0)
gnutls_certificate_free_credentials(state->x509_cred);

state->session = NULL;
- state->tlsp->active = -1;
+ state->tlsp->active.sock = -1;
+ state->tlsp->active.tls_ctx = NULL;
state->tlsp->bits = 0;
state->tlsp->certificate_verified = FALSE;
tls_channelbinding_b64 = NULL;
@@ -2658,6 +2673,7 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
then the caller must feed DKIM.

 Arguments:
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       size of buffer


@@ -2666,9 +2682,9 @@ Returns:    the number of bytes read
 */


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

if (len > INT_MAX)
@@ -2704,7 +2720,7 @@ return -1;

 /*
 Arguments:
-  is_server channel specifier
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       number of bytes
   more        more data expected soon
@@ -2714,11 +2730,11 @@ Returns:    the number of bytes after a successful write,
 */


int
-tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
{
ssize_t outbytes;
size_t left = len;
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
#ifdef SUPPORT_CORK
static BOOL corked = FALSE;

diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index e7bba02..adabc96 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -117,7 +117,9 @@ static const uschar *sid_ctx = US"exim";
Simple case: client, `client_ctx`
As a client, we can be doing a callout or cut-through delivery while receiving
a message. So we have a client context, which should have options initialised
- from the SMTP Transport.
+ from the SMTP Transport. We may also concurrently want to make TLS connections
+ to utility daemons, so client-contexts are allocated and passed around in call
+ args rather than using a gobal.

Server:
There are two cases: with and without ServerNameIndication from the client.
@@ -131,9 +133,12 @@ Server:
configuration.
*/

-static SSL_CTX *client_ctx = NULL;
+typedef struct {
+  SSL_CTX *    ctx;
+  SSL *        ssl;
+} exim_openssl_client_tls_ctx;
+
 static SSL_CTX *server_ctx = NULL;
-static SSL     *client_ssl = NULL;
 static SSL     *server_ssl = NULL;


#ifdef EXIM_HAVE_OPENSSL_TLSEXT
@@ -1975,7 +1980,7 @@ static uschar cipherbuf[256];

/* Check for previous activation */

-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
smtp_printf("554 Already in TLS\r\n", FALSE);
@@ -2130,7 +2135,8 @@ receive_feof = tls_feof;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;

-tls_in.active = fileno(smtp_out);
+tls_in.active.sock = fileno(smtp_out);
+tls_in.active.tls_ctx = NULL;    /* not using explicit ctx for server-side */
 return OK;
 }


@@ -2249,23 +2255,23 @@ Argument:
   addr             the first address
   tb               transport (always smtp)
   tlsa_dnsa        tlsa lookup, if DANE, else null
+  tlsp           record details of channel configuration
   errstr       error string pointer


-Returns:           OK on success
-                   FAIL otherwise - note that tls_error() will not give DEFER
-                     because this is not a server
+Returns:           Pointer to TLS session context, or NULL on error
 */


-int
+void *
tls_client_start(int fd, host_item *host, address_item *addr,
transport_instance * tb,
#ifdef SUPPORT_DANE
dns_answer * tlsa_dnsa,
#endif
- uschar ** errstr)
+ tls_support * tlsp, uschar ** errstr)
{
smtp_transport_options_block * ob =
(smtp_transport_options_block *)tb->options_block;
+exim_openssl_client_tls_ctx * exim_client_ctx;
static uschar peerdn[256];
uschar * expciphers;
int rc;
@@ -2276,8 +2282,13 @@ BOOL request_ocsp = FALSE;
BOOL require_ocsp = FALSE;
#endif

+rc = store_pool;
+store_pool = POOL_PERM;
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+store_pool = rc;
+
#ifdef SUPPORT_DANE
-tls_out.tlsa_usage = 0;
+tlsp->tlsa_usage = 0;
#endif

#ifndef DISABLE_OCSP
@@ -2308,15 +2319,15 @@ tls_out.tlsa_usage = 0;
}
#endif

-rc = tls_init(&client_ctx, host, NULL,
+rc = tls_init(&exim_client_ctx->ctx, host, NULL,
     ob->tls_certificate, ob->tls_privatekey,
 #ifndef DISABLE_OCSP
     (void *)(long)request_ocsp,
 #endif
     addr, &client_static_cbinfo, errstr);
-if (rc != OK) return rc;
+if (rc != OK) return NULL;


-tls_out.certificate_verified = FALSE;
+tlsp->certificate_verified = FALSE;
client_verify_callback_called = FALSE;

 expciphers = NULL;
@@ -2328,7 +2339,7 @@ if (tlsa_dnsa)
   if (ob->dane_require_tls_ciphers &&
       !expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
         &expciphers, errstr))
-    return FAIL;
+    return NULL;
   if (expciphers && *expciphers == '\0')
     expciphers = NULL;
   }
@@ -2336,7 +2347,7 @@ if (tlsa_dnsa)
 if (!expciphers &&
     !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
       &expciphers, errstr))
-  return FAIL;
+  return NULL;


 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
 are separated by underscores. So that I can use either form in my tests, and
@@ -2347,62 +2358,74 @@ if (expciphers)
   uschar *s = expciphers;
   while (*s) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
-  if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
-    return tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
+  if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
+    {
+    tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
+    return NULL;
+    }
   }


 #ifdef SUPPORT_DANE
 if (tlsa_dnsa)
   {
-  SSL_CTX_set_verify(client_ctx,
+  SSL_CTX_set_verify(exim_client_ctx->ctx,
     SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
     verify_callback_client_dane);


   if (!DANESSL_library_init())
-    return tls_error(US"library init", host, NULL, errstr);
-  if (DANESSL_CTX_init(client_ctx) <= 0)
-    return tls_error(US"context init", host, NULL, errstr);
+    {
+    tls_error(US"library init", host, NULL, errstr);
+    return NULL;
+    }
+  if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0)
+    {
+    tls_error(US"context init", host, NULL, errstr);
+    return NULL;
+    }
   }
 else


#endif

-  if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob,
-      client_static_cbinfo, errstr)) != OK)
-    return rc;
+  if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
+    client_static_cbinfo, errstr) != OK)
+    return NULL;


-if (!(client_ssl = SSL_new(client_ctx)))
- return tls_error(US"SSL_new", host, NULL, errstr);
-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 (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
+ {
+ tls_error(US"SSL_new", host, NULL, errstr);
+ return NULL;
+ }
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(exim_client_ctx->ssl, fd);
+SSL_set_connect_state(exim_client_ctx->ssl);

 if (ob->tls_sni)
   {
-  if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni, errstr))
-    return FAIL;
-  if (!tls_out.sni)
+  if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
+    return NULL;
+  if (!tlsp->sni)
     {
     DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
     }
-  else if (!Ustrlen(tls_out.sni))
-    tls_out.sni = NULL;
+  else if (!Ustrlen(tlsp->sni))
+    tlsp->sni = NULL;
   else
     {
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
-    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
-    SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+    SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
 #else
     log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
-          tls_out.sni);
+          tlsp->sni);
 #endif
     }
   }


 #ifdef SUPPORT_DANE
 if (tlsa_dnsa)
-  if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa, errstr)) != OK)
-    return rc;
+  if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK)
+    return NULL;
 #endif


#ifndef DISABLE_OCSP
@@ -2427,9 +2450,9 @@ if (request_ocsp)

if (request_ocsp)
{
- SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+ SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp);
client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
- tls_out.ocsp = OCSP_NOT_RESP;
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif

@@ -2442,33 +2465,36 @@ client_static_cbinfo->event_action = tb->event_action;
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
sigalrm_seen = FALSE;
alarm(ob->command_timeout);
-rc = SSL_connect(client_ssl);
+rc = SSL_connect(exim_client_ctx->ssl);
alarm(0);

#ifdef SUPPORT_DANE
if (tlsa_dnsa)
- DANESSL_cleanup(client_ssl);
+ DANESSL_cleanup(exim_client_ctx->ssl);
#endif

 if (rc <= 0)
-  return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL,
-    errstr);
+  {
+  tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
+  return NULL;
+  }


DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");

-peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn));
+peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));

-construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
-tls_out.cipher = cipherbuf;
+construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits);
+tlsp->cipher = cipherbuf;

/* Record the certificate we presented */
{
- X509 * crt = SSL_get_certificate(client_ssl);
- tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+ X509 * crt = SSL_get_certificate(exim_client_ctx->ssl);
+ tlsp->ourcert = crt ? X509_dup(crt) : NULL;
}

-tls_out.active = fd;
-return OK;
+tlsp->active.sock = fd;
+tlsp->active.tls_ctx = exim_client_ctx;
+return exim_client_ctx;
}


@@ -2503,53 +2529,55 @@ if (had_data_sigint)
closed down, not that the socket itself has been closed down. Revert to
non-SSL handling. */

-if (error == SSL_ERROR_ZERO_RETURN)
+switch(error)
   {
-  DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
+  case SSL_ERROR_NONE:
+    break;


-  receive_getc = smtp_getc;
-  receive_getbuf = smtp_getbuf;
-  receive_get_cache = smtp_get_cache;
-  receive_ungetc = smtp_ungetc;
-  receive_feof = smtp_feof;
-  receive_ferror = smtp_ferror;
-  receive_smtp_buffered = smtp_buffered;
+  case SSL_ERROR_ZERO_RETURN:
+    DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");


-  if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
-     SSL_shutdown(server_ssl);
+    receive_getc = smtp_getc;
+    receive_getbuf = smtp_getbuf;
+    receive_get_cache = smtp_get_cache;
+    receive_ungetc = smtp_ungetc;
+    receive_feof = smtp_feof;
+    receive_ferror = smtp_ferror;
+    receive_smtp_buffered = smtp_buffered;


-#ifndef DISABLE_OCSP
-  sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
-  server_static_cbinfo->verify_stack = NULL;
-#endif
-  SSL_free(server_ssl);
-  SSL_CTX_free(server_ctx);
-  server_ctx = NULL;
-  server_ssl = NULL;
-  tls_in.active = -1;
-  tls_in.bits = 0;
-  tls_in.cipher = NULL;
-  tls_in.peerdn = NULL;
-  tls_in.sni = NULL;
+    if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
+      SSL_shutdown(server_ssl);


-  return FALSE;
-  }
+#ifndef DISABLE_OCSP
+    sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+    server_static_cbinfo->verify_stack = NULL;
+#endif
+    SSL_free(server_ssl);
+    SSL_CTX_free(server_ctx);
+    server_ctx = NULL;
+    server_ssl = NULL;
+    tls_in.active.sock = -1;
+    tls_in.active.tls_ctx = NULL;
+    tls_in.bits = 0;
+    tls_in.cipher = NULL;
+    tls_in.peerdn = NULL;
+    tls_in.sni = NULL;


-/* Handle genuine errors */
+    return FALSE;


-else if (error == SSL_ERROR_SSL)
-  {
-  ERR_error_string(ERR_get_error(), ssl_errstring);
-  log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
-  ssl_xfer_error = TRUE;
-  return FALSE;
-  }
+  /* Handle genuine errors */
+  case SSL_ERROR_SSL:
+    ERR_error_string(ERR_get_error(), ssl_errstring);
+    log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
+    ssl_xfer_error = TRUE;
+    return FALSE;


-else if (error != SSL_ERROR_NONE)
-  {
-  DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
-  ssl_xfer_error = TRUE;
-  return FALSE;
+  default:
+    DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
+    DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL)
+      debug_printf(" - syscall %s\n", strerror(errno));
+    ssl_xfer_error = TRUE;
+    return FALSE;
   }


#ifndef DISABLE_DKIM
@@ -2633,6 +2661,7 @@ return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0;

 /*
 Arguments:
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       size of buffer


@@ -2643,9 +2672,9 @@ Only used by the client-side TLS.
*/

int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-SSL *ssl = is_server ? server_ssl : client_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
int inbytes;
int error;

@@ -2676,7 +2705,7 @@ return inbytes;

 /*
 Arguments:
-  is_server channel specifier
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       number of bytes
   more        further data expected soon
@@ -2688,10 +2717,10 @@ Used by both server-side and client-side TLS.
 */


int
-tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
+tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
{
int outbytes, error, left;
-SSL *ssl = is_server ? server_ssl : client_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
static gstring * corked = NULL;

DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
@@ -2702,7 +2731,7 @@ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
one stream does it, in one context (i.e. no store reset). Currently it is used
for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */

-if (is_server && (more || corked))
+if (!ct_ctx && (more || corked))
{
corked = string_catn(corked, buff, len);
if (more)
@@ -2714,7 +2743,7 @@ if (is_server && (more || corked))

for (left = len; left > 0;)
{
- DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
+ DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
@@ -2759,6 +2788,7 @@ daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the SSL session in the parent process).

 Arguments:
+  ct_ctx    client TLS context pointer, or NULL for the one global server context
   shutdown    1 if TLS close-alert is to be sent,
          2 if also response to be waited for


@@ -2768,11 +2798,12 @@ Used by both server-side and client-side TLS.
*/

 void
-tls_close(BOOL is_server, int shutdown)
+tls_close(void * ct_ctx, int shutdown)
 {
-SSL_CTX **ctxp = is_server ? &server_ctx : &client_ctx;
-SSL **sslp = is_server ? &server_ssl : &client_ssl;
-int *fdp = is_server ? &tls_in.active : &tls_out.active;
+exim_openssl_client_tls_ctx * o_ctx = ct_ctx;
+SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx;
+SSL **sslp =     o_ctx ? &o_ctx->ssl : &server_ssl;
+int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock;


if (*fdp < 0) return; /* TLS was not active */

@@ -2798,7 +2829,7 @@ if (shutdown)
}

 #ifndef DISABLE_OCSP
-if (is_server)
+if (!o_ctx)        /* server side */
   {
   sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
   server_static_cbinfo->verify_stack = NULL;
diff --git a/src/src/transport.c b/src/src/transport.c
index a2da321..d5ff738 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -242,7 +242,7 @@ for (i = 0; i < 100; i++)
     {
     rc =
 #ifdef SUPPORT_TLS
-    tls_out.active == fd ? tls_write(FALSE, block, len, more) :
+    tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
 #endif
 #ifdef MSG_MORE
     more && !(tctx->options & topt_not_socket)
@@ -260,7 +260,7 @@ for (i = 0; i < 100; i++)


     rc =
 #ifdef SUPPORT_TLS
-    tls_out.active == fd ? tls_write(FALSE, block, len, more) :
+    tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
 #endif
 #ifdef MSG_MORE
     more && !(tctx->options & topt_not_socket)
@@ -1075,7 +1075,7 @@ dkim signing,  when we had CHUNKING input.  */
 if (  spool_file_wireformat
    && !(tctx->options & (topt_no_body | topt_end_dot))
    && !nl_check_length
-   && tls_out.active != tctx->u.fd
+   && tls_out.active.sock != tctx->u.fd
    )
   {
   ssize_t copied = 0;
@@ -1877,12 +1877,12 @@ if (smtp_peer_options & OPTION_PIPE)        argv[i++] = US"-MCP";
 if (smtp_peer_options & OPTION_SIZE)        argv[i++] = US"-MCS";
 #ifdef SUPPORT_TLS
 if (smtp_peer_options & OPTION_TLS)
-  if (tls_out.active >= 0 || continue_proxy_cipher)
+  if (tls_out.active.sock >= 0 || continue_proxy_cipher)
     {
     argv[i++] = US"-MCt";
     argv[i++] = sending_ip_address;
     argv[i++] = string_sprintf("%d", sending_port);
-    argv[i++] = tls_out.active >= 0 ? tls_out.cipher : continue_proxy_cipher;
+    argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
     }
   else
     argv[i++] = US"-MCT";
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 6d70858..1f0256f 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1646,11 +1646,13 @@ if (!continue_hostname)


/* Make the TCP connection */

-  sx->inblock.sock = sx->outblock.sock =
+  sx->cctx.sock =
     smtp_connect(sx->host, sx->host_af, sx->interface,
           sx->ob->connect_timeout, sx->tblock);
+  sx->cctx.tls_ctx = NULL;
+  sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;


-  if (sx->inblock.sock < 0)
+  if (sx->cctx.sock < 0)
     {
     uschar * msg = NULL;
     if (sx->verify)
@@ -1703,7 +1705,7 @@ if (!continue_hostname)
     BOOL good_response;


 #ifdef TCP_QUICKACK
-    (void) setsockopt(sx->inblock.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
 #endif
     good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
       '2', sx->ob->command_timeout);
@@ -1882,16 +1884,18 @@ separate - we could match up by host ip+port as a bodge. */


 else
   {
-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
-    sx->inblock.sock = sx->outblock.sock = cutthrough.fd;
+    sx->cctx = cutthrough.cctx;
     sx->host->port = sx->port = cutthrough.host.port;
     }
   else
     {
-    sx->inblock.sock = sx->outblock.sock = 0;    /* stdin */
+    sx->cctx.sock = 0;                /* stdin */
+    sx->cctx.tls_ctx = NULL;
     smtp_port_for_connect(sx->host, sx->port);    /* Record the port that was used */
     }
+  sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
   sx->helo_data = NULL;        /* ensure we re-expand ob->helo_data */


@@ -1899,7 +1903,8 @@ else
held-open verify connection with TLS, nothing more to do. */

   if (  continue_proxy_cipher
-     || (cutthrough.fd >= 0 && cutthrough.callout_hold_only && cutthrough.is_tls)
+     || (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only
+         && cutthrough.is_tls)
      )
     {
     sx->peer_offered = smtp_peer_options;
@@ -1959,18 +1964,19 @@ if (  smtp_peer_options & OPTION_TLS
     {
     address_item * addr;
     uschar * errstr;
-    int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock,
+    sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->host,
+                sx->addrlist, sx->tblock,
 # ifdef SUPPORT_DANE
                  sx->dane ? &tlsa_dnsa : NULL,
 # endif
-                 &errstr);
+                 &tls_out, &errstr);


-    /* TLS negotiation failed; give an error. From outside, this function may
-    be called again to try in clear on a new connection, if the options permit
-    it for this host. */
-
-    if (rc != OK)
+    if (!sx->cctx.tls_ctx)
       {
+      /* TLS negotiation failed; give an error. From outside, this function may
+      be called again to try in clear on a new connection, if the options permit
+      it for this host. */
+
 # ifdef SUPPORT_DANE
       if (sx->dane)
         {
@@ -2013,7 +2019,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_out.active >= 0)
+if (tls_out.active.sock >= 0)
{
char *greeting_cmd;
BOOL good_response;
@@ -2091,7 +2097,7 @@ we skip this. */

 if (continue_hostname == NULL
 #ifdef SUPPORT_TLS
-    || tls_out.active >= 0
+    || tls_out.active.sock >= 0
 #endif
     )
   {
@@ -2270,7 +2276,11 @@ if (sx->send_quit)
   (void)smtp_write_command(&sx->outblock, SCMD_FLUSH, "QUIT\r\n");


#ifdef SUPPORT_TLS
-tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+if (sx->cctx.tls_ctx)
+ {
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx->cctx.tls_ctx = NULL;
+ }
#endif

 /* Close the socket, and return the appropriate value, first setting
@@ -2281,14 +2291,14 @@ remote_max_parallel is forced to 1 when delivering over an existing connection,
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx->send_quit)
   {
-  shutdown(sx->outblock.sock, SHUT_WR);
-  if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0)
-    for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
+  shutdown(sx->cctx.sock, SHUT_WR);
+  if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
       rc--;                /* drain socket */
   sx->send_quit = FALSE;
   }
-(void)close(sx->inblock.sock);
-sx->inblock.sock = sx->outblock.sock = -1;
+(void)close(sx->cctx.sock);
+sx->cctx.sock = -1;


#ifndef DISABLE_EVENT
(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
@@ -2624,6 +2634,7 @@ Do blocking full-size writes, and reads under a timeout. Once both input
channels are closed, exit the process.

 Arguments:
+  ct_ctx    tls context
   buf        space to use for buffering
   bufsiz    size of buffer
   pfd        pipe filedescriptor array; [0] is comms to proxied process
@@ -2631,10 +2642,11 @@ Arguments:
 */


void
-smtp_proxy_tls(uschar * buf, size_t bsize, int * pfd, int timeout)
+smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
+ int timeout)
{
fd_set rfds, efds;
-int max_fd = MAX(pfd[0], tls_out.active) + 1;
+int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
int rc, i, fd_bits, nbytes;

close(pfd[1]);
@@ -2647,7 +2659,7 @@ if ((rc = fork()))
if (running_in_test_harness) millisleep(100); /* let parent debug out */
set_process_info("proxying TLS connection for continued transport");
FD_ZERO(&rfds);
-FD_SET(tls_out.active, &rfds);
+FD_SET(tls_out.active.sock, &rfds);
FD_SET(pfd[0], &rfds);

 for (fd_bits = 3; fd_bits; )
@@ -2673,21 +2685,21 @@ for (fd_bits = 3; fd_bits; )
       goto done;
       }


-    if (FD_ISSET(tls_out.active, &efds) || FD_ISSET(pfd[0], &efds))
+    if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
       {
       DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
     FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
       goto done;
       }
     }
-  while (rc < 0 || !(FD_ISSET(tls_out.active, &rfds) || FD_ISSET(pfd[0], &rfds)));
+  while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));


   /* handle inbound data */
-  if (FD_ISSET(tls_out.active, &rfds))
-    if ((rc = tls_read(FALSE, buf, bsize)) <= 0)
+  if (FD_ISSET(tls_out.active.sock, &rfds))
+    if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
       {
       fd_bits &= ~1;
-      FD_CLR(tls_out.active, &rfds);
+      FD_CLR(tls_out.active.sock, &rfds);
       shutdown(pfd[0], SHUT_WR);
       timeout = 5;
       }
@@ -2697,19 +2709,19 @@ for (fd_bits = 3; fd_bits; )
     if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
       }
   else if (fd_bits & 1)
-    FD_SET(tls_out.active, &rfds);
+    FD_SET(tls_out.active.sock, &rfds);


   /* handle outbound data */
   if (FD_ISSET(pfd[0], &rfds))
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
       fd_bits = 0;
-      tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+      tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
       }
     else
       {
       for (nbytes = 0; rc - nbytes > 0; nbytes += i)
-    if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes, FALSE)) < 0)
+    if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
       goto done;
       }
   else if (fd_bits & 2)
@@ -2952,7 +2964,7 @@ if (!(sx.peer_offered & OPTION_CHUNKING) && !sx.ok)
 else
   {
   transport_ctx tctx = {
-    {sx.inblock.sock},
+    {sx.cctx.sock},        /*XXX will this need TLS info? */
     tblock,
     addrlist,
     US".", US"..",    /* Escaping strings */
@@ -3465,7 +3477,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
      || continue_more
      || (
 #ifdef SUPPORT_TLS
-       (  tls_out.active < 0  &&  !continue_proxy_cipher
+       (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
            || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
        )
         &&
@@ -3503,7 +3515,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
     if (sx.ok)
       {
       int pfd[2];
-      int socket_fd = sx.inblock.sock;
+      int socket_fd = sx.cctx.sock;



       if (sx.first_addr != NULL)         /* More addresses still to be sent */
@@ -3518,7 +3530,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
       the connection still open. */


 #ifdef SUPPORT_TLS
-      if (tls_out.active >= 0)
+      if (tls_out.active.sock >= 0)
     if (  continue_more
        || verify_check_given_host(&sx.ob->hosts_noproxy_tls, host) == OK)
       {
@@ -3528,7 +3540,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
       a new EHLO. If we don't get a good response, we don't attempt to pass
       the socket on. */


-      tls_close(FALSE, TLS_SHUTDOWN_WAIT);
+      tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
       smtp_peer_options = smtp_peer_options_wrap;
       sx.ok = !sx.smtps
         && smtp_write_command(&sx.outblock, SCMD_FLUSH,
@@ -3576,14 +3588,14 @@ propagate it from the initial
     get logging done asap.  Which way to place the work makes assumptions
     about post-fork prioritisation which may not hold on all platforms. */
 #ifdef SUPPORT_TLS
-    if (tls_out.active >= 0)
+    if (tls_out.active.sock >= 0)
       {
       int pid = fork();
       if (pid == 0)        /* child; fork again to disconnect totally */
         {
         if (running_in_test_harness) millisleep(100); /* let parent debug out */
         /* does not return */
-        smtp_proxy_tls(sx.buffer, sizeof(sx.buffer), pfd,
+        smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd,
                 sx.ob->command_timeout);
         }


@@ -3593,8 +3605,10 @@ propagate it from the initial
         close(pfd[0]);
         /* tidy the inter-proc to disconn the proxy proc */
         waitpid(pid, NULL, 0);
-        tls_close(FALSE, TLS_NO_SHUTDOWN);
-        (void)close(sx.inblock.sock);
+        tls_close(sx.cctx.tls_ctx, TLS_NO_SHUTDOWN);
+        sx.cctx.tls_ctx = NULL;
+        (void)close(sx.cctx.sock);
+        sx.cctx.sock = -1;
         continue_transport = NULL;
         continue_hostname = NULL;
         return yield;
@@ -3639,7 +3653,7 @@ if (sx.send_quit) (void)smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n")
 END_OFF:


#ifdef SUPPORT_TLS
-tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
#endif

 /* Close the socket, and return the appropriate value, first setting
@@ -3655,12 +3669,12 @@ case continue_more won't get set. */
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx.send_quit)
   {
-  shutdown(sx.outblock.sock, SHUT_WR);
-  if (fcntl(sx.inblock.sock, F_SETFL, O_NONBLOCK) == 0)
-    for (rc = 16; read(sx.inblock.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
+  shutdown(sx.cctx.sock, SHUT_WR);
+  if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
       rc--;                /* drain socket */
   }
-(void)close(sx.inblock.sock);
+(void)close(sx.cctx.sock);


#ifndef DISABLE_EVENT
(void) event_raise(tblock->event_action, US"tcp:close", NULL);
@@ -3696,19 +3710,24 @@ smtp_transport_closedown(transport_instance *tblock)
{
smtp_transport_options_block *ob =
(smtp_transport_options_block *)tblock->options_block;
+client_conn_ctx cctx;
smtp_inblock inblock;
smtp_outblock outblock;
uschar buffer[256];
uschar inbuffer[4096];
uschar outbuffer[16];

-inblock.sock = fileno(stdin);
+/*XXX really we need an active-smtp-client ctx, rather than assuming stdout */
+cctx.sock = fileno(stdin);
+cctx.tls_ctx = cctx.sock == tls_out.active.sock ? tls_out.active.tls_ctx : NULL;
+
+inblock.cctx = &cctx;
inblock.buffer = inbuffer;
inblock.buffersize = sizeof(inbuffer);
inblock.ptr = inbuffer;
inblock.ptrend = inbuffer;

-outblock.sock = inblock.sock;
+outblock.cctx = &cctx;
outblock.buffersize = sizeof(outbuffer);
outblock.buffer = outbuffer;
outblock.ptr = outbuffer;
@@ -3718,7 +3737,7 @@ outblock.authenticating = FALSE;
(void)smtp_write_command(&outblock, SCMD_FLUSH, "QUIT\r\n");
(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout);
-(void)close(inblock.sock);
+(void)close(cctx.sock);
}


@@ -3819,7 +3838,7 @@ DEBUG(D_transport)
   if (continue_hostname)
     debug_printf("already connected to %s [%s] (on fd %d)\n",
       continue_hostname, continue_host_address,
-      cutthrough.fd >= 0 ? cutthrough.fd : 0);
+      cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
   }


/* Set the flag requesting that these hosts be added to the waiting
@@ -4604,21 +4623,27 @@ retry_non_continued:

   if (continue_hostname && !continue_host_tried)
     {
-    int fd = cutthrough.fd >= 0 ? cutthrough.fd : 0;
+    int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0;


     DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
 #ifdef SUPPORT_TLS
-    if (tls_out.active == fd)
+    /* A TLS conn could be open for a cutthrough, but not for a plain continued-
+    transport */
+/*XXX doublecheck that! */
+
+    if (cutthrough.cctx.sock >= 0 && cutthrough.is_tls)
       {
-      (void) tls_write(FALSE, US"QUIT\r\n", 6, FALSE);
-      tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+      (void) tls_write(cutthrough.cctx.tls_ctx, US"QUIT\r\n", 6, FALSE);
+      tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+      cutthrough.cctx.tls_ctx = NULL;
+      cutthrough.is_tls = FALSE;
       }
     else
 #else
       (void) write(fd, US"QUIT\r\n", 6);
 #endif
     (void) close(fd);
-    cutthrough.fd = -1;
+    cutthrough.cctx.sock = -1;
     continue_hostname = NULL;
     goto retry_non_continued;
     }
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 7727c0c..a33ef43 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -146,8 +146,9 @@ typedef struct {
   address_item *    next_addr;
   address_item *    sync_addr;


-  smtp_inblock  inblock;
-  smtp_outblock outblock;
+  client_conn_ctx    cctx;
+  smtp_inblock        inblock;
+  smtp_outblock        outblock;
   uschar    buffer[DELIVER_BUFFER_SIZE];
   uschar    inbuffer[4096];
   uschar    outbuffer[4096];
diff --git a/src/src/verify.c b/src/src/verify.c
index 5d0551e..c7c769a 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -39,7 +39,7 @@ static tree_node *dnsbl_cache = NULL;
 #define MT_NOT 1
 #define MT_ALL 2


-static uschar cutthrough_response(int, char, uschar **, int);
+static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int);



@@ -410,10 +410,11 @@ if (addr->transport == cutthrough.addr.transport)

     /* Match!  Send the RCPT TO, set done from the response */
     done =
-      smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
-        transport_rcpt_address(addr,
-           addr->transport->rcpt_include_affixes)) >= 0 &&
-      cutthrough_response(cutthrough.fd, '2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2';
+         smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
+          transport_rcpt_address(addr,
+         addr->transport->rcpt_include_affixes)) >= 0
+      && cutthrough_response(&cutthrough.cctx, '2', &resp,
+          CUTTHROUGH_DATA_TIMEOUT) == '2';


     /* This would go horribly wrong if a callout fail was ignored by ACL.
     We punt by abandoning cutthrough on a reject, like the
@@ -612,7 +613,7 @@ that conn for verification purposes (and later delivery also).  Simplest
 coding means skipping this whole loop and doing the append separately.  */


   /* Can we re-use an open cutthrough connection? */
-  if (  cutthrough.fd >= 0
+  if (  cutthrough.cctx.sock >= 0
      && (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
     == vopt_callout_recipsender
      && !random_local_part
@@ -823,11 +824,11 @@ tls_retry_connection:
           debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
         random_local_part = NULL;
 #ifdef SUPPORT_TLS
-        tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+        tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
 #endif
         HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-        (void)close(sx.inblock.sock);
-        sx.inblock.sock = sx.outblock.sock = -1;
+        (void)close(sx.cctx.sock);
+        sx.cctx.sock = -1;
 #ifndef DISABLE_EVENT
         (void) event_raise(addr->transport->event_action,
                   US"tcp:close", NULL);
@@ -1059,7 +1060,7 @@ no_conn:
        == vopt_callout_recipsender
        && !random_local_part
        && !pm_mailfrom
-       && cutthrough.fd < 0
+       && cutthrough.cctx.sock < 0
        && !sx.lmtp
        )
       {
@@ -1068,8 +1069,9 @@ no_conn:
     ? "cutthrough delivery" : "potential further verifies and delivery");


       cutthrough.callout_hold_only = !cutthrough.delivery;
-      cutthrough.is_tls =    tls_out.active >= 0;
-      cutthrough.fd =    sx.outblock.sock;    /* We assume no buffer in use in the outblock */
+      cutthrough.is_tls =    tls_out.active.sock >= 0;
+      /* We assume no buffer in use in the outblock */
+      cutthrough.cctx =        sx.cctx;
       cutthrough.nrcpt =    1;
       cutthrough.transport =    addr->transport->name;
       cutthrough.interface =    interface;
@@ -1094,7 +1096,7 @@ no_conn:
       ctblock.buffersize = sizeof(ctbuffer);
       ctblock.ptr = ctbuffer;
       /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-      ctblock.sock = cutthrough.fd;
+      ctblock.cctx = &cutthrough.cctx;
       }
     else
       {
@@ -1110,14 +1112,18 @@ no_conn:
       '2', 1);
     }


-      if (sx.inblock.sock >= 0)
+      if (sx.cctx.sock >= 0)
     {
 #ifdef SUPPORT_TLS
-    tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+    if (sx.cctx.tls_ctx)
+      {
+      tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+      sx.cctx.tls_ctx = NULL;
+      }
 #endif
     HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-    (void)close(sx.inblock.sock);
-    sx.inblock.sock = sx.outblock.sock = -1;
+    (void)close(sx.cctx.sock);
+    sx.cctx.sock = -1;
 #ifndef DISABLE_EVENT
     (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
 #endif
@@ -1210,14 +1216,16 @@ return rc;
 static BOOL
 cutthrough_send(int n)
 {
-if(cutthrough.fd < 0)
+if(cutthrough.cctx.sock < 0)
   return TRUE;


 if(
 #ifdef SUPPORT_TLS
-   tls_out.active == cutthrough.fd ? tls_write(FALSE, ctblock.buffer, n, FALSE) :
+   cutthrough.is_tls
+   ? tls_write(cutthrough.cctx.tls_ctx, ctblock.buffer, n, FALSE)
+   :
 #endif
-   send(cutthrough.fd, ctblock.buffer, n, 0) > 0
+     send(cutthrough.cctx.sock, ctblock.buffer, n, 0) > 0
   )
 {
   transport_count += n;
@@ -1249,8 +1257,8 @@ return TRUE;
 static BOOL
 cutthrough_puts(uschar * cp, int n)
 {
-if (cutthrough.fd < 0)       return TRUE;
-if (_cutthrough_puts(cp, n)) return TRUE;
+if (cutthrough.cctx.sock < 0) return TRUE;
+if (_cutthrough_puts(cp, n))  return TRUE;
 cancel_cutthrough_connection(TRUE, US"transmit failed");
 return FALSE;
 }
@@ -1301,7 +1309,7 @@ cutthrough_data_puts(US"\r\n", 2);


/* Get and check response from cutthrough target */
static uschar
-cutthrough_response(int fd, char expect, uschar ** copy, int timeout)
+cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
{
smtp_inblock inblock;
uschar inbuffer[4096];
@@ -1311,8 +1319,7 @@ inblock.buffer = inbuffer;
inblock.buffersize = sizeof(inbuffer);
inblock.ptr = inbuffer;
inblock.ptrend = inbuffer;
-inblock.sock = fd;
-/* this relies on (inblock.sock == tls_out.active) */
+inblock.cctx = cctx;
if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout))
cancel_cutthrough_connection(TRUE, US"target timeout on read");

@@ -1334,7 +1341,7 @@ return responsebuffer[0];
BOOL
cutthrough_predata(void)
{
-if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
+if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
return FALSE;

HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> DATA\n");
@@ -1342,7 +1349,7 @@ cutthrough_puts(US"DATA\r\n", 6);
cutthrough_flush_send();

/* Assume nothing buffered. If it was it gets ignored. */
-return cutthrough_response(cutthrough.fd, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
+return cutthrough_response(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
}


@@ -1369,7 +1376,7 @@ cutthrough_headers_send(void)
{
transport_ctx tctx;

-if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
+if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
return FALSE;

/* We share a routine with the mainline transport to handle header add/remove/rewrites,
@@ -1377,7 +1384,7 @@ if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
*/
HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n");

-tctx.u.fd = cutthrough.fd;
+tctx.u.fd = cutthrough.cctx.sock;
 tctx.tblock = cutthrough.addr.transport;
 tctx.addr = &cutthrough.addr;
 tctx.check_string = US".";
@@ -1396,25 +1403,31 @@ return TRUE;
 static void
 close_cutthrough_connection(const uschar * why)
 {
-int fd = cutthrough.fd;
+int fd = cutthrough.cctx.sock;
 if(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.
   */
+  client_conn_ctx tmp_ctx = cutthrough.cctx;
   ctblock.ptr = ctbuffer;
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
   _cutthrough_puts(US"QUIT\r\n", 6);    /* avoid recursion */
   _cutthrough_flush_send();
-  cutthrough.fd = -1;            /* avoid recursion via read timeout */
+  cutthrough.cctx.sock = -1;        /* avoid recursion via read timeout */
   cutthrough.nrcpt = 0;            /* permit re-cutthrough on subsequent message */


/* Wait a short time for response, and discard it */
- cutthrough_response(fd, '2', NULL, 1);
+ cutthrough_response(&tmp_ctx, '2', NULL, 1);

 #ifdef SUPPORT_TLS
-  tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+  if (cutthrough.is_tls)
+    {
+    tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+    cutthrough.cctx.tls_ctx = NULL;
+    cutthrough.is_tls = FALSE;
+    }
 #endif
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
   (void)close(fd);
@@ -1435,9 +1448,10 @@ cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
 void
 release_cutthrough_connection(const uschar * why)
 {
-if (cutthrough.fd < 0) return;
+if (cutthrough.cctx.sock < 0) return;
 HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why);
-cutthrough.fd = -1;
+cutthrough.cctx.sock = -1;
+cutthrough.cctx.tls_ctx = NULL;
 cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
 }


@@ -1463,7 +1477,8 @@ if( !cutthrough_puts(US".", 1)
)
return cutthrough.addr.message;

-res = cutthrough_response(cutthrough.fd, '2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT);
+res = cutthrough_response(&cutthrough.cctx, '2', &cutthrough.addr.message,
+    CUTTHROUGH_DATA_TIMEOUT);
 for (addr = &cutthrough.addr; addr; addr = addr->next)
   {
   addr->message = cutthrough.addr.message;
@@ -2681,7 +2696,8 @@ Side effect: any received ident value is put in sender_ident (NULL otherwise)
 void
 verify_get_ident(int port)
 {
-int sock, host_af, qlen;
+client_conn_ctx ident_conn_ctx = {0};
+int host_af, qlen;
 int received_sender_port, received_interface_port, n;
 uschar *p;
 blob early_data;
@@ -2701,9 +2717,9 @@ to the incoming interface address. If the sender host address is an IPv6
 address, the incoming interface address will also be IPv6. */


host_af = Ustrchr(sender_host_address, ':') == NULL ? AF_INET : AF_INET6;
-if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;
+if ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;

-if (ip_bind(sock, host_af, interface_address, 0) < 0)
+if (ip_bind(ident_conn_ctx.sock, host_af, interface_address, 0) < 0)
   {
   DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n",
     strerror(errno));
@@ -2717,7 +2733,7 @@ qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n",
 early_data.data = buffer;
 early_data.len = qlen;


-if (ip_connect(sock, host_af, sender_host_address, port,
+if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
         rfc1413_query_timeout, &early_data) < 0)
   {
   if (errno == ETIMEDOUT && LOGGING(ident_timeout))
@@ -2741,7 +2757,7 @@ for (;;)
   int size = sizeof(buffer) - (p - buffer);


if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */
- count = ip_recv(sock, p, size, rfc1413_query_timeout);
+ count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout);
if (count <= 0) goto END_OFF; /* Read error or EOF */

/* Scan what we just read, to see if we have reached the terminating \r\n. Be
@@ -2806,7 +2822,7 @@ sender_ident = US string_printing(string_copyn(p, 127));
DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);

END_OFF:
-(void)close(sock);
+(void)close(ident_conn_ctx.sock);
return;
}