GnuTLS: simplify cert hostname checking
[exim.git] / src / src / tls-gnu.c
index 9f166691a0221e56d4a497046effc87f2d472a63..ff8064babc1e36ad2ef90efdb8e2ce5607787910 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -66,8 +66,17 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
 # define SUPPORT_SRV_OCSP_STACK
 #endif
 #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
 # define SUPPORT_SRV_OCSP_STACK
 #endif
-#if GNUTLS_VERSION_NUMBER >= 0x030000 && defined(EXPERIMENTAL_DANE)
-# define SUPPORT_DANE
+
+#ifdef SUPPORT_DANE
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+#  define DANESSL_USAGE_DANE_TA 2
+#  define DANESSL_USAGE_DANE_EE 3
+# else
+#  error GnuTLS version too early for DANE
+# endif
+# if GNUTLS_VERSION_NUMBER < 0x999999
+#  define GNUTLS_BROKEN_DANE_VALIDATION
+# endif
 #endif
 
 #ifndef DISABLE_OCSP
 #endif
 
 #ifndef DISABLE_OCSP
@@ -115,8 +124,8 @@ typedef struct exim_gnutls_state {
   BOOL                 peer_dane_verified;
   BOOL                 trigger_sni_changes;
   BOOL                 have_set_peerdn;
   BOOL                 peer_dane_verified;
   BOOL                 trigger_sni_changes;
   BOOL                 have_set_peerdn;
-  const struct host_item *host;
-  gnutls_x509_crt_t    peercert;
+  const struct host_item *host;                /* NULL if server */
+  gnutls_x509_crt_t    peercert;
   uschar               *peerdn;
   uschar               *ciphersuite;
   uschar               *received_sni;
   uschar               *peerdn;
   uschar               *ciphersuite;
   uschar               *received_sni;
@@ -147,8 +156,8 @@ typedef struct exim_gnutls_state {
   uschar *xfer_buffer;
   int xfer_buffer_lwm;
   int xfer_buffer_hwm;
   uschar *xfer_buffer;
   int xfer_buffer_lwm;
   int xfer_buffer_hwm;
-  int xfer_eof;
-  int xfer_error;
+  BOOL xfer_eof;       /*XXX never gets set! */
+  BOOL xfer_error;
 } exim_gnutls_state_st;
 
 static const exim_gnutls_state_st exim_gnutls_state_init = {
 } exim_gnutls_state_st;
 
 static const exim_gnutls_state_st exim_gnutls_state_init = {
@@ -189,8 +198,8 @@ static const exim_gnutls_state_st exim_gnutls_state_init = {
   .xfer_buffer =       NULL,
   .xfer_buffer_lwm =   0,
   .xfer_buffer_hwm =   0,
   .xfer_buffer =       NULL,
   .xfer_buffer_lwm =   0,
   .xfer_buffer_hwm =   0,
-  .xfer_eof =          0,
-  .xfer_error =                0,
+  .xfer_eof =          FALSE,
+  .xfer_error =                FALSE,
 };
 
 /* Not only do we have our own APIs which don't pass around state, assuming
 };
 
 /* Not only do we have our own APIs which don't pass around state, assuming
@@ -204,7 +213,7 @@ second connection.
 XXX But see gnutls_session_get_ptr()
 */
 
 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
 
 /* 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
@@ -439,7 +448,8 @@ gnutls_datum_t channel;
 #endif
 tls_support * tlsp = state->tlsp;
 
 #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" */
 
 cipher = gnutls_cipher_get(state->session);
 /* returns size in "bytes" */
@@ -781,7 +791,7 @@ if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
   goto err;
 
 where = US"configuring cert";
   goto err;
 
 where = US"configuring cert";
-now = 0;
+now = 1;
 if (  (rc = gnutls_x509_crt_set_version(cert, 3))
    || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
    || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
 if (  (rc = gnutls_x509_crt_set_version(cert, 3))
    || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
    || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
@@ -1253,6 +1263,7 @@ tls_init(
     const uschar *crl,
     const uschar *require_ciphers,
     exim_gnutls_state_st **caller_state,
     const uschar *crl,
     const uschar *require_ciphers,
     exim_gnutls_state_st **caller_state,
+    tls_support * tlsp,
     uschar ** errstr)
 {
 exim_gnutls_state_st *state;
     uschar ** errstr)
 {
 exim_gnutls_state_st *state;
@@ -1301,9 +1312,15 @@ if (!exim_gnutls_base_init_done)
 
 if (host)
   {
 
 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));
   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);
   }
   DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
   rc = gnutls_init(&state->session, GNUTLS_CLIENT);
   }
@@ -1311,7 +1328,7 @@ else
   {
   state = &state_server;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
   {
   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);
   }
   DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
   rc = gnutls_init(&state->session, GNUTLS_SERVER);
   }
@@ -1573,7 +1590,7 @@ Returns:
 */
 
 static BOOL
 */
 
 static BOOL
-verify_certificate(exim_gnutls_state_st *state, uschar ** errstr)
+verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
 {
 int rc;
 uint verify;
 {
 int rc;
 uint verify;
@@ -1600,42 +1617,128 @@ else
 
     dane_state_t s;
     dane_query_t r;
 
     dane_state_t s;
     dane_query_t r;
-    const gnutls_datum_t * certlist;
     uint lsize;
     uint lsize;
+    const gnutls_datum_t * certlist =
+      gnutls_certificate_get_peers(state->session, &lsize);
+    int usage = tls_out.tlsa_usage;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* Split the TLSA records into two sets, TA and EE selectors.  Run the
+    dane-verification separately so that we know which selector verified;
+    then we know whether to do name-verification (needed for TA but not EE). */
+
+    if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
+      {                                                /* a mixed-usage bundle */
+      int i, j, nrec;
+      const char ** dd;
+      int * ddl;
+
+      for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+      nrec++;
+
+      dd = store_get(nrec * sizeof(uschar *));
+      ddl = store_get(nrec * sizeof(int));
+      nrec--;
+
+      if ((rc = dane_state_init(&s, 0)))
+       goto tlsa_prob;
+
+      for (usage = DANESSL_USAGE_DANE_EE;
+          usage >= DANESSL_USAGE_DANE_TA; usage--)
+       {                               /* take records with this usage */
+       for (j = i = 0; i < nrec; i++)
+         if (state->dane_data[i][0] == usage)
+           {
+           dd[j] = state->dane_data[i];
+           ddl[j++] = state->dane_data_len[i];
+           }
+       if (j)
+         {
+         dd[j] = NULL;
+         ddl[j] = 0;
+
+         if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0)))
+           goto tlsa_prob;
+
+         if ((rc = dane_verify_crt_raw(s, certlist, lsize,
+                           gnutls_certificate_type_get(state->session),
+                           r, 0,
+                           usage == DANESSL_USAGE_DANE_EE
+                           ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+                           &verify)))
+           {
+           DEBUG(D_tls)
+             debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
+           }
+         else if (verify == 0) /* verification passed */
+           {
+           usage = 1 << usage;
+           break;
+           }
+         }
+       }
 
 
-    certlist = gnutls_certificate_get_peers(state->session, &lsize);
-
-    if (  (rc = dane_state_init(&s, 0))
-       || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
-                             1, 0))
-       || (rc = dane_verify_crt_raw(s, certlist, lsize,
-                             gnutls_certificate_type_get(state->session),
-                             r, 0, 0, &verify))
-       )
-
+       if (rc) goto tlsa_prob;
+      }
+    else
+# endif
       {
       {
-      *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
-      goto badcert;
+      if (  (rc = dane_state_init(&s, 0))
+        || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+                       1, 0))
+        || (rc = dane_verify_crt_raw(s, certlist, lsize,
+                       gnutls_certificate_type_get(state->session),
+                       r, 0,
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+                       usage == (1 << DANESSL_USAGE_DANE_EE)
+                       ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+# else
+                       0,
+# endif
+                       &verify))
+        )
+       goto tlsa_prob;
       }
       }
-    if (verify != 0)
+
+    if (verify != 0)           /* verification failed */
       {
       gnutls_datum_t str;
       (void) dane_verification_status_print(verify, &str, 0);
       *errstr = US str.data;   /* don't bother to free */
       goto badcert;
       }
       {
       gnutls_datum_t str;
       (void) dane_verification_status_print(verify, &str, 0);
       *errstr = US str.data;   /* don't bother to free */
       goto badcert;
       }
-    state->peer_dane_verified = TRUE;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* If a TA-mode TLSA record was used for verification we must additionally
+    verify the cert name (but not the CA chain).  For EE-mode, skip it. */
+
+    if (usage & (1 << DANESSL_USAGE_DANE_EE))
+# endif
+      {
+      state->peer_dane_verified = state->peer_cert_verified = TRUE;
+      goto goodcert;
+      }
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* Assume that the name on the A-record is the one that should be matching
+    the cert.  An alternate view is that the domain part of the email address
+    is also permissible. */
+
+    if (gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+         CS state->host->name))
+      {
+      state->peer_dane_verified = state->peer_cert_verified = TRUE;
+      goto goodcert;
+      }
+# endif
     }
     }
-#endif
+#endif /*SUPPORT_DANE*/
 
   rc = gnutls_certificate_verify_peers2(state->session, &verify);
   }
 
 /* Handle the result of verification. INVALID is set if any others are. */
 
 
   rc = gnutls_certificate_verify_peers2(state->session, &verify);
   }
 
 /* Handle the result of verification. INVALID is set if any others are. */
 
-if (rc < 0 ||
-    verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
-   )
+if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
   {
   state->peer_cert_verified = FALSE;
   if (!*errstr)
   {
   state->peer_cert_verified = FALSE;
   if (!*errstr)
@@ -1654,30 +1757,37 @@ if (rc < 0 ||
 
 else
   {
 
 else
   {
-  if (state->exp_tls_verify_cert_hostnames)
+  /* Client side, check the server's certificate name versus the name on the
+  A-record for the connection we made.  What to do for server side - what name
+  to use for client?  We document that there is no such checking for server
+  side. */
+
+  if (  state->exp_tls_verify_cert_hostnames
+     && !gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+               CS state->exp_tls_verify_cert_hostnames)
+     )
     {
     {
-    int sep = 0;
-    const uschar * list = state->exp_tls_verify_cert_hostnames;
-    uschar * name;
-    while ((name = string_nextinlist(&list, &sep, NULL, 0)))
-      if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
-       break;
-    if (!name)
-      {
-      DEBUG(D_tls)
-       debug_printf("TLS certificate verification failed: cert name mismatch\n");
-      if (state->verify_requirement >= VERIFY_REQUIRED)
-       goto badcert;
-      return TRUE;
-      }
+    DEBUG(D_tls)
+      debug_printf("TLS certificate verification failed: cert name mismatch\n");
+    if (state->verify_requirement >= VERIFY_REQUIRED)
+      goto badcert;
+    return TRUE;
     }
     }
+
   state->peer_cert_verified = TRUE;
   DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
       state->peerdn ? state->peerdn : US"<unset>");
   }
 
   state->peer_cert_verified = TRUE;
   DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
       state->peerdn ? state->peerdn : US"<unset>");
   }
 
-state->tlsp->peerdn = state->peerdn;
-return TRUE;
+goodcert:
+  state->tlsp->peerdn = state->peerdn;
+  return TRUE;
+
+#ifdef SUPPORT_DANE
+tlsa_prob:
+  *errstr = string_sprintf("TLSA record problem: %s",
+    rc == DANE_E_REQUESTED_DATA_NOT_AVAILABLE ? "none usable" : dane_strerror(rc));
+#endif
 
 badcert:
   gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
 
 badcert:
   gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
@@ -1829,12 +1939,10 @@ int rc;
 uschar * yield;
 exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
 
 uschar * yield;
 exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
 
-cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
-if (cert_list)
+if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
   while (cert_list_size--)
   {
   while (cert_list_size--)
   {
-  rc = import_cert(&cert_list[cert_list_size], &crt);
-  if (rc != GNUTLS_E_SUCCESS)
+  if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
     {
     DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
       cert_list_size, gnutls_strerror(rc));
     {
     DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
       cert_list_size, gnutls_strerror(rc));
@@ -1891,7 +1999,7 @@ int rc;
 exim_gnutls_state_st * state = NULL;
 
 /* Check for previous activation */
 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);
   {
   tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
   smtp_printf("554 Already in TLS\r\n", FALSE);
@@ -1905,7 +2013,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,
 
 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. */
 
 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */
@@ -2050,7 +2158,7 @@ static void
 tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
   smtp_transport_options_block * ob)
 {
 tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
   smtp_transport_options_block * ob)
 {
-if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
   state->exp_tls_verify_cert_hostnames =
 #ifdef SUPPORT_I18N
   {
   state->exp_tls_verify_cert_hostnames =
 #ifdef SUPPORT_I18N
@@ -2075,7 +2183,7 @@ use in DANE verification.
 We point at the dnsa data not copy it, so it must remain valid until
 after verification is done.*/
 
 We point at the dnsa data not copy it, so it must remain valid until
 after verification is done.*/
 
-static void
+static BOOL
 dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
 {
 dns_record * rr;
 dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
 {
 dns_record * rr;
@@ -2098,17 +2206,39 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
     ) if (rr->type == T_TLSA)
   {
   const uschar * p = rr->data;
     ) if (rr->type == T_TLSA)
   {
   const uschar * p = rr->data;
-  uint8_t usage = *p;
+  uint8_t usage = p[0], sel = p[1], type = p[2];
+
+  DEBUG(D_tls)
+    debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
+
+  if (  (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
+     || (sel != 0 && sel != 1)
+     )
+    continue;
+  switch(type)
+    {
+    case 0:    /* Full: cannot check at present */
+               break;
+    case 1:    if (rr->size != 3 + 256/8) continue;    /* sha2-256 */
+               break;
+    case 2:    if (rr->size != 3 + 512/8) continue;    /* sha2-512 */
+               break;
+    default:   continue;
+    }
 
   tls_out.tlsa_usage |= 1<<usage;
   dane_data[i] = p;
   dane_data_len[i++] = rr->size;
   }
 
   tls_out.tlsa_usage |= 1<<usage;
   dane_data[i] = p;
   dane_data_len[i++] = rr->size;
   }
+
+if (!i) return FALSE;
+
 dane_data[i] = NULL;
 dane_data_len[i] = 0;
 
 state->dane_data = (char * const *)dane_data;
 state->dane_data_len = dane_data_len;
 dane_data[i] = NULL;
 dane_data_len[i] = 0;
 
 state->dane_data = (char * const *)dane_data;
 state->dane_data_len = dane_data_len;
+return TRUE;
 }
 #endif
 
 }
 #endif
 
@@ -2122,7 +2252,7 @@ state->dane_data_len = dane_data_len;
 
 Arguments:
   fd                the fd of the connection
 
 Arguments:
   fd                the fd of the connection
-  host              connected host (for messages)
+  host              connected host (for messages and option-tests)
   addr              the first address (not used)
   tb                transport (always smtp)
   tlsa_dnsa        non-NULL, either request or require dane for this host, and
   addr              the first address (not used)
   tb                transport (always smtp)
   tlsa_dnsa        non-NULL, either request or require dane for this host, and
@@ -2130,38 +2260,56 @@ Arguments:
                    Which implies cert must be requested and supplied, dane
                    verify must pass, and cert verify irrelevant (incl.
                    hostnames), and (caller handled) require_tls
                    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
 
   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,
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
     transport_instance * tb,
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
     dns_answer * tlsa_dnsa,
 #endif
     dns_answer * tlsa_dnsa,
 #endif
-    uschar ** errstr)
+    tls_support * tlsp, uschar ** errstr)
 {
 {
-smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)tb->options_block;
+smtp_transport_options_block *ob = tb
+  ? (smtp_transport_options_block *)tb->options_block
+  : &smtp_transport_option_defaults;
 int rc;
 exim_gnutls_state_st * state = NULL;
 int rc;
 exim_gnutls_state_st * state = NULL;
+uschar *cipher_list = NULL;
+
 #ifndef DISABLE_OCSP
 BOOL require_ocsp =
 #ifndef DISABLE_OCSP
 BOOL require_ocsp =
-  verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+  verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK;
 BOOL request_ocsp = require_ocsp ? TRUE
 BOOL request_ocsp = require_ocsp ? TRUE
-  : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+  : verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
 
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
 
-if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+#ifdef SUPPORT_DANE
+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 NULL;
+  cipher_list = cipher_list && *cipher_list
+    ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
+  }
+#endif
+
+if (!cipher_list)
+  cipher_list = ob->tls_require_ciphers;
+
+if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
     ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
     ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
-    ob->tls_require_ciphers, &state, errstr)) != OK)
-  return rc;
+    cipher_list, &state, tlsp, errstr) != OK)
+  return NULL;
 
   {
   int dh_min_bits = ob->tls_dh_min_bits;
 
   {
   int dh_min_bits = ob->tls_dh_min_bits;
@@ -2185,13 +2333,12 @@ set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
 the specified host patterns if one of them is defined */
 
 #ifdef SUPPORT_DANE
 the specified host patterns if one of them is defined */
 
 #ifdef SUPPORT_DANE
-if (tlsa_dnsa)
+if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa))
   {
   DEBUG(D_tls)
     debug_printf("TLS: server certificate DANE required.\n");
   state->verify_requirement = VERIFY_DANE;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
   {
   DEBUG(D_tls)
     debug_printf("TLS: server certificate DANE required.\n");
   state->verify_requirement = VERIFY_DANE;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
-  dane_tlsa_load(state, tlsa_dnsa);
   }
 else
 #endif
   }
 else
 #endif
@@ -2199,7 +2346,7 @@ else
          && !ob->tls_verify_hosts
          && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
          )
          && !ob->tls_verify_hosts
          && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
          )
-       || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
+       || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
        )
   {
   tls_client_setup_hostname_checks(host, state, ob);
        )
   {
   tls_client_setup_hostname_checks(host, state, ob);
@@ -2208,7 +2355,7 @@ else
   state->verify_requirement = VERIFY_REQUIRED;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
   }
   state->verify_requirement = VERIFY_REQUIRED;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
   }
-else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
   {
   tls_client_setup_hostname_checks(host, state, ob);
   DEBUG(D_tls)
   {
   tls_client_setup_hostname_checks(host, state, ob);
   DEBUG(D_tls)
@@ -2231,14 +2378,16 @@ 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)
   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
 
 #ifndef DISABLE_EVENT
   }
 #endif
 
 #ifndef DISABLE_EVENT
-if (tb->event_action)
+if (tb && tb->event_action)
   {
   state->event_action = tb->event_action;
   gnutls_session_set_ptr(state->session, state);
   {
   state->event_action = tb->event_action;
   gnutls_session_set_ptr(state->session, state);
@@ -2256,27 +2405,31 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
 sigalrm_seen = FALSE;
 alarm(ob->command_timeout);
 do
 sigalrm_seen = FALSE;
 alarm(ob->command_timeout);
 do
-  {
   rc = gnutls_handshake(state->session);
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) ||
-      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
+  {
   if (sigalrm_seen)
     {
     gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
   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
     }
   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))
 
 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)
 
 #ifndef DISABLE_OCSP
 if (require_ocsp)
@@ -2301,24 +2454,25 @@ if (require_ocsp)
 
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
     {
 
   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");
     }
   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. */
 
   }
 #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);
 
 
 /* Sets various Exim expansion variables; may need to adjust for ACL callouts */
 
 extract_exim_vars_from_tls_state(state);
 
-return OK;
+return state;
 }
 
 
 }
 
 
@@ -2332,35 +2486,39 @@ return OK;
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the TLS session in the parent process).
 
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the TLS session in the parent process).
 
-Arguments:   TRUE if gnutls_bye is to be called
+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
+
 Returns:     nothing
 */
 
 void
 Returns:     nothing
 */
 
 void
-tls_close(BOOL is_server, BOOL 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)
   {
 
 if (shutdown)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
-  gnutls_bye(state->session, GNUTLS_SHUT_WR);
+  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+    shutdown > 1 ? " (with response-wait)" : "");
+
+  alarm(2);
+  gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  alarm(0);
   }
 
 gnutls_deinit(state->session);
 gnutls_certificate_free_credentials(state->x509_cred);
 
 
   }
 
 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));
 memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
-
-if ((state_server.session == NULL) && (state_client.session == NULL))
-  {
-  gnutls_global_deinit();
-  exim_gnutls_base_init_done = FALSE;
-  }
 }
 
 
 }
 
 
@@ -2375,20 +2533,29 @@ ssize_t inbytes;
 DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
   state->session, state->xfer_buffer, ssl_xfer_buffer_size);
 
 DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
   state->session, state->xfer_buffer, ssl_xfer_buffer_size);
 
+sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
   MIN(ssl_xfer_buffer_size, lim));
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
   MIN(ssl_xfer_buffer_size, lim));
-alarm(0);
+if (smtp_receive_timeout > 0) alarm(0);
+
+if (had_command_timeout)               /* set by signal handler */
+  smtp_command_timeout_exit();         /* does not return */
+if (had_command_sigterm)
+  smtp_command_sigterm_exit();
+if (had_data_timeout)
+  smtp_data_timeout_exit();
+if (had_data_sigint)
+  smtp_data_sigint_exit();
 
 
-/* Timeouts do not get this far; see command_timeout_handler().
-   A zero-byte return appears to mean that the TLS session has been
-   closed down, not that the socket itself has been closed down. Revert to
-   non-TLS handling. */
+/* Timeouts do not get this far.  A zero-byte return appears to mean that the
+TLS session has been closed down, not that the socket itself has been closed
+down. Revert to non-TLS handling. */
 
 if (sigalrm_seen)
   {
   DEBUG(D_tls) debug_printf("Got tls read timeout\n");
 
 if (sigalrm_seen)
   {
   DEBUG(D_tls) debug_printf("Got tls read timeout\n");
-  state->xfer_error = 1;
+  state->xfer_error = TRUE;
   return FALSE;
   }
 
   return FALSE;
   }
 
@@ -2408,7 +2575,8 @@ else if (inbytes == 0)
   gnutls_certificate_free_credentials(state->x509_cred);
 
   state->session = NULL;
   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;
   state->tlsp->bits = 0;
   state->tlsp->certificate_verified = FALSE;
   tls_channelbinding_b64 = NULL;
@@ -2423,8 +2591,9 @@ else if (inbytes == 0)
 
 else if (inbytes < 0)
   {
 
 else if (inbytes < 0)
   {
+debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
   record_io_error(state, (int) inbytes, US"recv", NULL);
   record_io_error(state, (int) inbytes, US"recv", NULL);
-  state->xfer_error = 1;
+  state->xfer_error = TRUE;
   return FALSE;
   }
 #ifndef DISABLE_DKIM
   return FALSE;
   }
 #ifndef DISABLE_DKIM
@@ -2517,17 +2686,18 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
 then the caller must feed DKIM.
 
 Arguments:
 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
 
 Returns:    the number of bytes read
   buff      buffer of data
   len       size of buffer
 
 Returns:    the number of bytes read
-            -1 after a failed read
+            -1 after a failed read, including EOF
 */
 
 int
 */
 
 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)
 ssize_t inbytes;
 
 if (len > INT_MAX)
@@ -2549,7 +2719,11 @@ if (inbytes == 0)
   {
   DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
   }
   {
   DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
   }
-else record_io_error(state, (int)inbytes, US"recv", NULL);
+else
+{
+debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+record_io_error(state, (int)inbytes, US"recv", NULL);
+}
 
 return -1;
 }
 
 return -1;
 }
@@ -2563,7 +2737,7 @@ return -1;
 
 /*
 Arguments:
 
 /*
 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
   buff      buffer of data
   len       number of bytes
   more     more data expected soon
@@ -2573,11 +2747,11 @@ Returns:    the number of bytes after a successful write,
 */
 
 int
 */
 
 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;
 {
 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;
 
 #ifdef SUPPORT_CORK
 static BOOL corked = FALSE;
 
@@ -2596,6 +2770,7 @@ while (left > 0)
   DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
   if (outbytes < 0)
     {
   DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
   if (outbytes < 0)
     {
+debug_printf("%s: err from gnutls_record_send(\n", __FUNCTION__);
     record_io_error(state, outbytes, US"send", NULL);
     return -1;
     }
     record_io_error(state, outbytes, US"send", NULL);
     return -1;
     }