Silence some compiler warnings. Bug 3076
[exim.git] / src / src / tls-gnu.c
index 1affba3668f6c1928e6aa527a013b8bda6414731..3e8ec6d847b8f0d88353231c795def00cdc29cc5 100644 (file)
@@ -2,11 +2,11 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
-/* 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. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* This file provides TLS/SSL support for Exim using the GnuTLS library,
 one of the available supported implementations.  This file is #included into
@@ -90,9 +90,6 @@ 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 >= 0x030600
-# define GNUTLS_AUTO_DHPARAMS
-#endif
 #if GNUTLS_VERSION_NUMBER >= 0x030603
 # define EXIM_HAVE_TLS1_3
 # define SUPPORT_GNUTLS_EXT_RAW_PARSE
@@ -120,7 +117,13 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #endif
 
 #if GNUTLS_VERSION_NUMBER >= 0x030200
-# define EXIM_HAVE_ALPN
+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE
+#  define EXIM_HAVE_ALPN
+# endif
+#endif
+
+#if GNUTLS_VERSION_NUMBER >= 0x030702
+# define HAVE_GNUTLS_EXPORTER
 #endif
 
 #ifndef DISABLE_OCSP
@@ -152,6 +155,9 @@ builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 builtin_macro_create(US"_HAVE_TLS_CA_CACHE");
 # endif
+# ifdef EXIM_HAVE_ALPN
+builtin_macro_create(US"_HAVE_TLS_ALPN");
+# endif
 }
 #else
 
@@ -261,13 +267,11 @@ static exim_gnutls_state_st state_server = {
   .fd_out =            -1,
 };
 
-#ifndef GNUTLS_AUTO_DHPARAMS
 /* 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
 don't want to repeat this. */
 
 static gnutls_dh_params_t dh_server_params = NULL;
-#endif
 
 static int ssl_session_timeout = 7200; /* Two hours */
 
@@ -283,7 +287,7 @@ static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
 #endif
 
 #ifdef EXIM_HAVE_ALPN
-static BOOL server_seen_alpn = FALSE;
+static int server_seen_alpn = -1;      /* count of names */
 #endif
 #ifdef EXIM_HAVE_TLS_RESUME
 static gnutls_datum_t server_sessticket_key;
@@ -334,6 +338,9 @@ before, for now. */
 # endif /* AVOID_GNUTLS_PKCS11 */
 #endif
 
+#if GNUTLS_VERSION_NUMBER >= 0x030404
+# define HAVE_GNUTLS_PRF_RFC5705
+#endif
 
 
 
@@ -372,7 +379,7 @@ Argument:
             the connected host if setting up a client
   errstr    pointer to returned error string
 
-Returns:    OK/DEFER/FAIL
+Returns:    DEFER/FAIL
 */
 
 static int
@@ -385,11 +392,18 @@ return host ? FAIL : DEFER;
 }
 
 
+/* Returns:    DEFER/FAIL */
 static int
-tls_error_gnu(const uschar *prefix, int err, const host_item *host,
+tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
   uschar ** errstr)
 {
-return tls_error(prefix, US gnutls_strerror(err), host, errstr);
+return tls_error(prefix,
+  state && err == GNUTLS_E_FATAL_ALERT_RECEIVED
+  ? string_sprintf("rxd alert: %s",
+                 US gnutls_alert_get_name(gnutls_alert_get(state->session)))
+  : US gnutls_strerror(err),
+  state ? state->host : NULL,
+  errstr);
 }
 
 static int
@@ -449,12 +463,12 @@ To prevent this, we init PKCS11 first, which is the documented approach. */
 
 if (!gnutls_allow_auto_pkcs11)
   if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL)))
-    return tls_error_gnu(US"gnutls_pkcs11_init", rc, NULL, errstr);
+    return tls_error_gnu(NULL, US"gnutls_pkcs11_init", rc, errstr);
 #endif
 
 #ifndef GNUTLS_AUTO_GLOBAL_INIT
 if ((rc = gnutls_global_init()))
-  return tls_error_gnu(US"gnutls_global_init", rc, NULL, errstr);
+  return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr);
 #endif
 
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
@@ -620,11 +634,6 @@ Argument:
 static void
 extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
 {
-#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-int old_pool;
-int rc;
-gnutls_datum_t channel;
-#endif
 tls_support * tlsp = state->tlsp;
 
 tlsp->active.sock = state->fd_out;
@@ -642,21 +651,46 @@ only available for use for authenticators while this TLS session is running. */
 
 tlsp->channelbinding = NULL;
 #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-channel.data = NULL;
-channel.size = 0;
-if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel)))
-  { DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); }
-else
   {
-  /* Declare the taintedness of the binding info.  On server, untainted; on
-  client, tainted - being the Finish msg from the server. */
+  gnutls_datum_t channel = {.data = NULL, .size = 0};
+  int rc;
 
-  old_pool = store_pool;
-  store_pool = POOL_PERM;
-  tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
-                                         !!state->host);
-  store_pool = old_pool;
-  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+# ifdef HAVE_GNUTLS_EXPORTER
+  if (gnutls_protocol_get_version(state->session) >= GNUTLS_TLS1_3)
+    {
+    rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_EXPORTER, &channel);
+    tlsp->channelbind_exporter = TRUE;
+    }
+  else
+# elif defined(HAVE_GNUTLS_PRF_RFC5705)
+  /* Older libraries may not have GNUTLS_TLS1_3 defined! */
+  if (gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2)
+    {
+    uschar * buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED);
+    rc = gnutls_prf_rfc5705(state->session,
+                               (size_t)24,  "EXPORTER-Channel-Binding", (size_t)0, "",
+                               32, CS buf);
+    channel.data = buf;
+    channel.size = 32;
+    }
+  else
+# endif
+    rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
+
+  if (rc)
+    { DEBUG(D_tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); }
+  else
+    {
+    int old_pool = store_pool;
+    /* Declare the taintedness of the binding info.  On server, untainted; on
+    client, tainted if we used the Finish msg from the server. */
+
+    store_pool = POOL_PERM;
+    tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
+               !tlsp->channelbind_exporter && state->host ? GET_TAINTED : GET_UNTAINTED);
+    store_pool = old_pool;
+    DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+    }
   }
 #endif
 
@@ -679,7 +713,6 @@ if (!state->host)
 
 
 
-#ifndef GNUTLS_AUTO_DHPARAMS
 /*************************************************
 *            Setup up DH parameters              *
 *************************************************/
@@ -694,7 +727,7 @@ file is never present. If two processes both compute some new parameters, you
 waste a bit of effort, but it doesn't seem worth messing around with locking to
 prevent this.
 
-Returns:     OK/DEFER/FAIL
+Returns:     OK/DEFER (expansion issue)/FAIL (requested none)
 */
 
 static int
@@ -702,7 +735,7 @@ init_server_dh(uschar ** errstr)
 {
 int fd, rc;
 unsigned int dh_bits;
-gnutls_datum_t m = {.data = NULL, .size = 0};
+gnutls_datum_t m;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
@@ -713,7 +746,10 @@ host_item *host = NULL; /* dummy for macros */
 DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n");
 
 if ((rc = gnutls_dh_params_init(&dh_server_params)))
-  return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr);
+  return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr);
+
+m.data = NULL;
+m.size = 0;
 
 if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
   return DEFER;
@@ -729,7 +765,7 @@ else if (Ustrcmp(exp_tls_dhparam, "historic") == 0)
 else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
   {
   DEBUG(D_tls) debug_printf("Requested no DH parameters\n");
-  return OK;
+  return FAIL;
   }
 else if (exp_tls_dhparam[0] != '/')
   {
@@ -743,7 +779,7 @@ else
 if (m.data)
   {
   if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM)))
-    return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
   DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
   return OK;
   }
@@ -827,7 +863,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
   store_free(m.data);
   if (rc)
-    return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
   DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
   }
 
@@ -864,12 +900,14 @@ if (rc < 0)
     return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr);
   (void)exim_chown(temp_fn, exim_uid, exim_gid);   /* Probably not necessary */
 
-  /* GnuTLS overshoots!  If we ask for 2236, we might get 2237 or more.  But
-  there's no way to ask GnuTLS how many bits there really are.  We can ask
-  how many bits were used in a TLS session, but that's it!  The prime itself
-  is hidden behind too much abstraction.  So we ask for less, and proceed on
-  a wing and a prayer.  First attempt, subtracted 3 for 2233 and got 2240.  */
-
+  /* GnuTLS overshoots!
+   * If we ask for 2236, we might get 2237 or more.
+   * But there's no way to ask GnuTLS how many bits there really are.
+   * We can ask how many bits were used in a TLS session, but that's it!
+   * The prime itself is hidden behind too much abstraction.
+   * So we ask for less, and proceed on a wing and a prayer.
+   * First attempt, subtracted 3 for 2233 and got 2240.
+   */
   if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10)
     {
     dh_bits_gen = dh_bits - 10;
@@ -882,7 +920,7 @@ if (rc < 0)
     debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
         dh_bits_gen);
   if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen)))
-    return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_generate2", rc, errstr);
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
   and I confirmed that a NULL call to get the size first is how the GnuTLS
@@ -893,8 +931,8 @@ if (rc < 0)
   if (  (rc = gnutls_dh_params_export_pkcs3(dh_server_params,
                GNUTLS_X509_FMT_PEM, m.data, &sz))
      && rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
-    return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing",
-             rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3(NULL) sizing",
+             rc, errstr);
   m.size = sz;
   if (!(m.data = store_malloc(m.size)))
     return tls_error_sys(US"memory allocation failed", errno, NULL, errstr);
@@ -904,7 +942,7 @@ if (rc < 0)
       m.data, &sz)))
     {
     store_free(m.data);
-    return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3() real", rc, errstr);
     }
   m.size = sz; /* shrink by 1, probably */
 
@@ -932,7 +970,6 @@ if (rc < 0)
 DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
 return OK;
 }
-#endif
 
 
 
@@ -979,7 +1016,7 @@ 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)))
-   || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60))      /* 2 hour */
+   || (rc = gnutls_x509_crt_set_expiration_time(cert, now + (long)2 * 60 * 60))        /* 2 hour */
    || (rc = gnutls_x509_crt_set_key(cert, pkey))
 
    || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
@@ -1009,7 +1046,7 @@ out:
   return rc;
 
 err:
-  rc = tls_error_gnu(where, rc, NULL, errstr);
+  rc = tls_error_gnu(state, where, rc, errstr);
   goto out;
 }
 
@@ -1030,9 +1067,9 @@ tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
 int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred,
     CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM);
 if (rc < 0)
-  return tls_error_gnu(
+  return tls_error_gnu(state,
     string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
-    rc, host, errstr);
+    rc, errstr);
 return -rc;
 }
 
@@ -1067,20 +1104,43 @@ return 0;
 /* Make a note that we saw a status-request */
 static int
 tls_server_clienthello_ext(void * ctx, unsigned tls_id,
-  const unsigned char *data, unsigned size)
+  const uschar * data, unsigned size)
 {
-/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
+/* The values for tls_id are documented here:
+https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
 switch (tls_id)
   {
-  case 5:      /* status_request */
+  case 5:      /* Status Request */
     DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
     tls_in.ocsp = OCSP_NOT_RESP;
     break;
 #ifdef EXIM_HAVE_ALPN
   case 16:     /* Application Layer Protocol Notification */
-    DEBUG(D_tls) debug_printf("Seen ALPN extension from client\n");
-    server_seen_alpn = TRUE;
+    /* The format of "data" here doesn't seem to be documented, but appears
+    to be a 2-byte field with a (redundant, given the "size" arg) total length
+    then a sequence of one-byte size then string (not nul-term) names.  The
+    latter is as described in OpenSSL documentation.
+    Note that we do not get called for a match_fail, making it hard to log
+    a single bad ALPN being offered (the common case). */
+    {
+    gstring * g = NULL;
+
+    DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
+    for (const uschar * s = data+2; s-data < size-1; s += *s + 1)
+      {
+      server_seen_alpn++;
+      g = string_append_listele_n(g, ':', s+1, *s);
+      DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
+      }
+    DEBUG(D_tls) debug_printf("\n");
+    if (server_seen_alpn > 1)
+      {
+      log_write(0, LOG_MAIN, "TLS ALPN (%Y) rejected", g);
+      DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
+      return GNUTLS_E_NO_APPLICATION_PROTOCOL;
+      }
     break;
+    }
 #endif
   }
 return 0;
@@ -1092,8 +1152,9 @@ tls_server_clienthello_cb(gnutls_session_t session, unsigned int htype,
   unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
 {
 /* Call fn for each extension seen.  3.6.3 onwards */
-return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg,
+int rc = gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg,
                           GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO);
+return rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE ? 0 : rc;
 }
 
 
@@ -1124,6 +1185,8 @@ tls_server_servercerts_cb(gnutls_session_t session, unsigned int htype,
 # ifdef notdef_crashes
                                /*XXX crashes */
 return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0);
+# else
+return GNUTLS_E_SUCCESS;
 # endif
 }
 #endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/
@@ -1142,12 +1205,12 @@ tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype,
   unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
 {
 DEBUG(D_tls) debug_printf("Sending certificate-status\n");             /*XXX we get this for tls1.2 but not for 1.3 */
-#ifdef SUPPORT_SRV_OCSP_STACK
+# ifdef SUPPORT_SRV_OCSP_STACK
 tls_in.ocsp = exim_testharness_disable_ocsp_validity_check
   ? OCSP_VFY_NOT_TRIED : OCSP_VFIED;   /* We know that GnuTLS verifies responses */
-#else
+# else
 tls_in.ocsp = OCSP_VFY_NOT_TRIED;
-#endif
+# endif
 return 0;
 }
 
@@ -1172,7 +1235,7 @@ switch (htype)
     return tls_server_ticket_cb(sess, htype, when, incoming, msg);
 # endif
   default:
-    return 0;
+    return GNUTLS_E_SUCCESS;
   }
 }
 #endif
@@ -1218,6 +1281,7 @@ DEBUG(D_tls)
   debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client");
 }
 
+/* Returns OK/DEFER/FAIL */
 static int
 creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert,
   const uschar * pkey, const uschar * ocsp, uschar ** errstr)
@@ -1241,7 +1305,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
 
   if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
     return tls_error(US"cert/key setup: out of keys", NULL, NULL, errstr);
-  else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > 0)
+  else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > OK)
     return rc;
   else
     {
@@ -1277,9 +1341,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
        if  ((rc = gnutls_certificate_set_ocsp_status_request_file2(
                  state->lib_state.x509_cred, CCS ofile, gnutls_cert_index,
                  ocsp_fmt)) < 0)
-         return tls_error_gnu(
+         return tls_error_gnu(state,
                  US"gnutls_certificate_set_ocsp_status_request_file2",
-                 rc, NULL, errstr);
+                 rc, errstr);
        DEBUG(D_tls)
          debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":"");
 
@@ -1297,9 +1361,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
        if ((rc = gnutls_certificate_set_ocsp_status_request_function2(
                     state->lib_state.x509_cred, gnutls_cert_index,
                     server_ocsp_stapling_cb, ofile)))
-           return tls_error_gnu(
+           return tls_error_gnu(state,
                  US"gnutls_certificate_set_ocsp_status_request_function2",
-                 rc, NULL, errstr);
+                 rc, errstr);
        else
 #  endif
          {
@@ -1319,7 +1383,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
       }
 #endif /* DISABLE_OCSP */
     }
-return 0;
+return OK;
 }
 
 static int
@@ -1329,7 +1393,7 @@ creds_load_client_certs(exim_gnutls_state_st * state, const host_item * host,
 int rc = tls_add_certfile(state, host, cert, pkey, errstr);
 if (rc > 0) return rc;
 DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
-return 0;
+return OK;
 }
 
 static int
@@ -1401,7 +1465,7 @@ else
   }
 
 if (cert_count < 0)
-  return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr);
+  return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr);
 DEBUG(D_tls)
   debug_printf("Added %d certificate authorities\n", cert_count);
 
@@ -1416,8 +1480,8 @@ int cert_count;
 DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl);
 if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred,
     CS crl, GNUTLS_X509_FMT_PEM)) < 0)
-  return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file",
-           cert_count, state->host, errstr);
+  return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file",
+           cert_count, errstr);
 
 DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count);
 return OK;
@@ -1501,9 +1565,14 @@ else if (  !tls_certificate && !tls_privatekey
 else
   DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
 
-/* If tls_verify_certificates is non-empty and has no $, load CAs */
+/* If tls_verify_certificates is non-empty and has no $, load CAs.
+If none was configured and we can't handle "system", treat as empty. */
 
-if (opt_set_and_noexpand(tls_verify_certificates))
+if (  opt_set_and_noexpand(tls_verify_certificates)
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+   && Ustrcmp(tls_verify_certificates, "system") != 0
+#endif
+   )
   {
   if (tls_set_watch(tls_verify_certificates, FALSE))
     {
@@ -1555,6 +1624,9 @@ return lifetime;
 /* Preload whatever creds are static, onto a transport.  The client can then
 just copy the pointer as it starts up. */
 
+/*XXX this is not called for a cmdline send. But one needing to use >1 conn would benefit,
+and there seems little downside. */
+
 static void
 tls_client_creds_init(transport_instance * t, BOOL watch)
 {
@@ -1607,7 +1679,14 @@ else
   DEBUG(D_tls)
     debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name);
 
-if (opt_set_and_noexpand(ob->tls_verify_certificates))
+/* If tls_verify_certificates is non-empty and has no $, load CAs.
+If none was configured and we can't handle "system", treat as empty. */
+
+if (  opt_set_and_noexpand(ob->tls_verify_certificates)
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+   && Ustrcmp(ob->tls_verify_certificates, "system") != 0
+#endif
+   )
   {
   if (!watch || tls_set_watch(ob->tls_verify_certificates, FALSE))
     {
@@ -1728,8 +1807,8 @@ if (!state->lib_state.x509_cred)
   {
   if ((rc = gnutls_certificate_allocate_credentials(
        (gnutls_certificate_credentials_t *) &state->lib_state.x509_cred)))
-    return tls_error_gnu(US"gnutls_certificate_allocate_credentials",
-           rc, host, errstr);
+    return tls_error_gnu(state, US"gnutls_certificate_allocate_credentials",
+           rc, errstr);
   creds_basic_init(state->lib_state.x509_cred, !host);
   }
 
@@ -1743,8 +1822,13 @@ D-H generation. */
 
 if (!state->lib_state.conn_certs)
   {
-  if (!Expand_check_tlsvar(tls_certificate, errstr))
+  if (  !Expand_check_tlsvar(tls_certificate, errstr)
+     || f.expand_string_forcedfail)
+    {
+    if (f.expand_string_forcedfail)
+      *errstr = US"expansion of tls_certificate failed";
     return DEFER;
+    }
 
   /* certificate is mandatory in server, optional in client */
 
@@ -1756,8 +1840,14 @@ if (!state->lib_state.conn_certs)
     else
       DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
 
-  if (state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr))
+  if (  state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)
+     || f.expand_string_forcedfail
+     )
+    {
+    if (f.expand_string_forcedfail)
+      *errstr = US"expansion of tls_privatekey failed";
     return DEFER;
+    }
 
   /* tls_privatekey is optional, defaulting to same file as certificate */
 
@@ -1799,7 +1889,11 @@ if (!state->lib_state.conn_certs)
                              tls_ocsp_file,
 #endif
                              errstr)
-       )  ) return rc;
+       )  )
+      {
+      DEBUG(D_tls) debug_printf("load-cert: '%s'\n", *errstr);
+      return rc;
+      }
     }
   }
 else
@@ -1823,7 +1917,8 @@ else
 provided. Experiment shows that, if the certificate file is empty, an unhelpful
 error message is provided. However, if we just refrain from setting anything up
 in that case, certificate verification fails, which seems to be the correct
-behaviour. */
+behaviour.
+If none was configured and we can't handle "system", treat as empty. */
 
 if (!state->lib_state.cabundle)
   {
@@ -1909,12 +2004,11 @@ Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr)
+tls_set_remaining_x509(exim_gnutls_state_st * state, uschar ** errstr)
 {
-int rc;
-const host_item *host = state->host;  /* macro should be reconsidered? */
+int rc = OK;
+const host_item * host = state->host;  /* macro should be reconsidered? */
 
-#ifndef GNUTLS_AUTO_DHPARAMS
 /* Create D-H parameters, or read them from the cache file. This function does
 its own SMTP error messaging. This only happens for the server, TLS D-H ignores
 client-side params. */
@@ -1922,18 +2016,20 @@ client-side params. */
 if (!state->host)
   {
   if (!dh_server_params)
-    if ((rc = init_server_dh(errstr)) != OK) return rc;
+    if ((rc = init_server_dh(errstr)) == DEFER) return rc;
 
-  /* Unnecessary & discouraged with 3.6.0 or later */
-  gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params);
+  /* Unnecessary & discouraged with 3.6.0 or later, according to docs.  But without it,
+  no DHE- ciphers are advertised. */
+
+  if (rc == OK)
+    gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params);
   }
-#endif
 
 /* Link the credentials to the session. */
 
 if ((rc = gnutls_credentials_set(state->session,
            GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred)))
-  return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr);
+  return tls_error_gnu(state, US"gnutls_credentials_set", rc, errstr);
 
 return OK;
 }
@@ -1980,7 +2076,7 @@ if (host)
 
   int old_pool = store_pool;
   store_pool = POOL_PERM;
-  state = store_get(sizeof(exim_gnutls_state_st), FALSE);
+  state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED);
   store_pool = old_pool;
 
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
@@ -2013,7 +2109,7 @@ else
   state->tls_crl =             tls_crl;
   }
 if (rc)
-  return tls_error_gnu(US"gnutls_init", rc, host, errstr);
+  return tls_error_gnu(state, US"gnutls_init", rc, errstr);
 
 state->tls_require_ciphers =   require_ciphers;
 state->host = host;
@@ -2042,7 +2138,7 @@ if (host)
     sz = Ustrlen(state->tlsp->sni);
     if ((rc = gnutls_server_name_set(state->session,
          GNUTLS_NAME_DNS, state->tlsp->sni, sz)))
-      return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr);
+      return tls_error_gnu(state, US"gnutls_server_name_set", rc, errstr);
     }
   }
 else if (state->tls_sni)
@@ -2072,10 +2168,10 @@ if (!state->lib_state.pri_string)
     }
 
   if ((rc = creds_load_pristring(state, p, &errpos)))
-    return tls_error_gnu(string_sprintf(
+    return tls_error_gnu(state, string_sprintf(
                        "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
-                       p, errpos - CS p, errpos),
-                   rc, host, errstr);
+                       p, (long)(errpos - CS p), errpos),
+                   rc, errstr);
   }
 else
   {
@@ -2085,7 +2181,7 @@ else
 
 
 if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache)))
-  return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr);
+  return tls_error_gnu(state, US"gnutls_priority_set", rc, errstr);
 
 /* This also sets the server ticket expiration time to the same, and
 the STEK rotation time to 3x. */
@@ -2210,7 +2306,7 @@ old_pool = store_pool;
 
     for (s++; (c = *s) && c != ')'; s++) g = string_catn(g, s, 1);
 
-    tlsp->ver = string_copyn(g->s, g->ptr);
+    tlsp->ver = string_copy_from_gstring(g);
     for (uschar * p = US tlsp->ver; *p; p++)
       if (*p == '-') { *p = '\0'; break; }     /* TLS1.0-PKIX -> TLS1.0 */
 
@@ -2283,7 +2379,7 @@ if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
       DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
        (Label), gnutls_strerror(rc)); \
       if (state->verify_requirement >= VERIFY_REQUIRED) \
-       return tls_error_gnu((Label), rc, state->host, errstr); \
+       return tls_error_gnu(state, (Label), rc, errstr); \
       return OK; \
       } \
     } while (0)
@@ -2300,7 +2396,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
   exim_gnutls_peer_err(US"getting size for cert DN failed");
   return FAIL; /* should not happen */
   }
-dn_buf = store_get_perm(sz, TRUE);     /* tainted */
+dn_buf = store_get_perm(sz, GET_TAINTED);
 rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
 exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
 
@@ -2380,8 +2476,8 @@ else
       for (nrec = 0; state->dane_data_len[nrec]; ) nrec++;
       nrec++;
 
-      dd = store_get(nrec * sizeof(uschar *), FALSE);
-      ddl = store_get(nrec * sizeof(int), FALSE);
+      dd = store_get(nrec * sizeof(uschar *), GET_UNTAINTED);
+      ddl = store_get(nrec * sizeof(int), GET_UNTAINTED);
       nrec--;
 
       if ((rc = dane_state_init(&s, 0)))
@@ -2528,7 +2624,7 @@ else
      )
     {
     DEBUG(D_tls)
-      debug_printf("TLS certificate verification failed: cert name mismatch\n");
+      debug_printf("TLS certificate verification failed: cert name mismatch (per GnuTLS)\n");
     if (state->verify_requirement >= VERIFY_REQUIRED)
       goto badcert;
     return TRUE;
@@ -2627,7 +2723,7 @@ if (sni_type != GNUTLS_NAME_DNS)
 /* We now have a UTF-8 string in sni_name */
 old_pool = store_pool;
 store_pool = POOL_PERM;
-state->received_sni = string_copy_taint(US sni_name, TRUE);
+state->received_sni = string_copy_taint(US sni_name, GET_TAINTED);
 store_pool = old_pool;
 
 /* We set this one now so that variable expansions below will work */
@@ -2643,11 +2739,12 @@ if ((rc = tls_expand_session_files(state, &dummy_errstr)) != OK)
   {
   /* If the setup of certs/etc failed before handshake, TLS would not have
   been offered.  The best we can do now is abort. */
-  return GNUTLS_E_APPLICATION_ERROR_MIN;
+  DEBUG(D_tls) debug_printf("expansion for SNI-dependent session files failed\n");
+  return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
   }
 
 rc = tls_set_remaining_x509(state, &dummy_errstr);
-if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
+if (rc != OK) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
 
 return 0;
 }
@@ -2676,25 +2773,25 @@ exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
 
 if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
   while (cert_list_size--)
-  {
-  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));
-    break;
-    }
+    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));
+      break;
+      }
 
-  state->tlsp->peercert = crt;
-  if ((yield = event_raise(state->event_action,
-             US"tls:cert", string_sprintf("%d", cert_list_size))))
-    {
-    log_write(0, LOG_MAIN,
-             "SSL verify denied by event-action: depth=%d: %s",
-             cert_list_size, yield);
-    return 1;                     /* reject */
+    state->tlsp->peercert = crt;
+    if ((yield = event_raise(state->event_action,
+               US"tls:cert", string_sprintf("%d", cert_list_size), &errno)))
+      {
+      log_write(0, LOG_MAIN,
+               "SSL verify denied by event-action: depth=%d: %s",
+               cert_list_size, yield);
+      return 1;                     /* reject */
+      }
+    state->tlsp->peercert = NULL;
     }
-  state->tlsp->peercert = NULL;
-  }
 
 return 0;
 }
@@ -2756,7 +2853,7 @@ static int
 tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
   unsigned incoming, const gnutls_datum_t * msg)
 {
-DEBUG(D_tls) debug_printf("newticket cb\n");
+DEBUG(D_tls) debug_printf("newticket cb (on server)\n");
 tls_in.resumption |= RESUME_CLIENT_REQUESTED;
 return 0;
 }
@@ -2793,9 +2890,12 @@ tls_server_resume_posthandshake(exim_gnutls_state_st * state)
 {
 if (gnutls_session_resumption_requested(state->session))
   {
-  /* This tells us the client sent a full ticket.  We use a
+  /* This tells us the client sent a full (?) ticket.  We use a
   callback on session-ticket request, elsewhere, to tell
-  if a client asked for a ticket. */
+  if a client asked for a ticket.
+  XXX As of GnuTLS 3.0.1 it seems to be returning true even for
+  a pure ticket-req (a zero-length Session Ticket extension
+  in the Client Hello, for 1.2) which mucks up our logic. */
 
   tls_in.resumption |= RESUME_CLIENT_SUGGESTED;
   DEBUG(D_tls) debug_printf("client requested resumption\n");
@@ -2806,26 +2906,71 @@ if (gnutls_session_is_resumed(state->session))
   DEBUG(D_tls) debug_printf("Session resumed\n");
   }
 }
-#endif
+#endif /* EXIM_HAVE_TLS_RESUME */
 
 
 #ifdef EXIM_HAVE_ALPN
+/* Expand and convert an Exim list to a gnutls_datum list.  False return for fail.
+NULL plist return for silent no-ALPN.
+*/
+
+static BOOL
+tls_alpn_plist(uschar ** tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
+  uschar ** errstr)
+{
+uschar * exp_alpn;
+
+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+  return FALSE;
+
+if (!exp_alpn)
+  {
+  DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+  *plist = NULL;
+  }
+else
+  {
+  const uschar * list = exp_alpn;
+  int sep = 0;
+  unsigned cnt = 0;
+  gnutls_datum_t * p;
+  uschar * s;
+
+  while (string_nextinlist(&list, &sep, NULL, 0)) cnt++;
+
+  p = store_get(sizeof(gnutls_datum_t) * cnt, exp_alpn);
+  list = exp_alpn;
+  for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++)
+    { p[i].data = s; p[i].size = Ustrlen(s); }
+  *plist = (*plen = cnt) ? p : NULL;
+  }
+return TRUE;
+}
+
 static void
-tls_server_set_acceptable_alpns(exim_gnutls_state_st * state)
+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
 {
+uschar * local_alpn = string_copy(tls_alpn);
 int rc;
-gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4},
-                              [1] = {.data = US"esmtp", .size = 5}};
+const gnutls_datum_t * plist;
+unsigned plen;
 
-/* Set non-mandatory set of protocol names */
-if (!(rc = gnutls_alpn_set_protocols(state->session, protocols, 2, 0)))
-  gnutls_handshake_set_hook_function(state->session,
-    GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
-else
-  DEBUG(D_tls)
-    debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+if (tls_alpn_plist(&local_alpn, &plist, &plen, errstr) && plist)
+  {
+  /* This seems to be only mandatory if the client sends an ALPN extension;
+  not trying ALPN is ok. Need to decide how to support server-side must-alpn. */
+
+  server_seen_alpn = 0;
+  if (!(rc = gnutls_alpn_set_protocols(state->session, plist, plen,
+                                       GNUTLS_ALPN_MANDATORY)))
+    gnutls_handshake_set_hook_function(state->session,
+      GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
+  else
+    DEBUG(D_tls)
+      debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+  }
 }
-#endif
+#endif /* EXIM_HAVE_ALPN */
 
 /* ------------------------------------------------------------------------ */
 /* Exported functions */
@@ -2860,7 +3005,7 @@ exim_gnutls_state_st * state = NULL;
 if (tls_in.active.sock >= 0)
   {
   tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
-  smtp_printf("554 Already in TLS\r\n", FALSE);
+  smtp_printf("554 Already in TLS\r\n", SP_NO_MORE);
   return FAIL;
   }
 
@@ -2884,7 +3029,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
   }
 
 #ifdef EXIM_HAVE_ALPN
-tls_server_set_acceptable_alpns(state);
+tls_server_set_acceptable_alpns(state, errstr);
 #endif
 
 #ifdef EXIM_HAVE_TLS_RESUME
@@ -2939,7 +3084,7 @@ mode, the fflush() happens when smtp_getc() is called. */
 
 if (!state->tlsp->on_connect)
   {
-  smtp_printf("220 TLS go ahead\r\n", FALSE);
+  smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE);
   fflush(smtp_out);
   }
 
@@ -2964,6 +3109,9 @@ ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
+  DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n",
+    rc, gnutls_strerror(rc));
+
   /* It seems that, except in the case of a timeout, we have to close the
   connection right here; otherwise if the other end is running OpenSSL it hangs
   until the server times out. */
@@ -2971,15 +3119,19 @@ if (rc != GNUTLS_E_SUCCESS)
   if (sigalrm_seen)
     {
     tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
+#ifndef DISABLE_EVENT
+    (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
     gnutls_db_remove_session(state->session);
     }
   else
     {
-    tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
+    tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
+#ifndef DISABLE_EVENT
+    (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
     (void) gnutls_alert_send_appropriate(state->session, rc);
     gnutls_deinit(state->session);
-    gnutls_certificate_free_credentials(state->lib_state.x509_cred);
-    state->lib_state = null_tls_preload;
     millisleep(500);
     shutdown(state->fd_out, SHUT_WR);
     for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--;  /* drain skt */
@@ -3003,29 +3155,30 @@ tls_server_resume_posthandshake(state);
 DEBUG(D_tls) post_handshake_debug(state);
 
 #ifdef EXIM_HAVE_ALPN
-if (server_seen_alpn)
+if (server_seen_alpn > 0)
   {
-  /* The client offered ALPN.  We were set up with a nonmandatory list;
-  see what was negotiated.  We require a match now, given that something
-  was offered. */
-  gnutls_datum_t p = {.size = 0};
-  int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
-  if (!rc || rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
-    {
-    if (p.size == 0)
-      {
-      *errstr = US"ALPN rejected";
-      return FAIL;
-      }
-    else
-      DEBUG(D_tls)
+  DEBUG(D_tls)
+    {          /* The client offered ALPN.  See what was negotiated. */
+    gnutls_datum_t p = {.size = 0};
+    int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
+    if (!rc)
        debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data);
-    }
-  else
-    DEBUG(D_tls)
-      debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
+    else
+       debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
 
+    }
   }
+else if (server_seen_alpn == 0)
+  if (verify_check_host(&hosts_require_alpn) == OK)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+    tls_error(US"handshake", US"ALPN required but not negotiated", NULL, errstr);
+    return FAIL;
+    }
+  else
+    DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n");
+else
+  DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n");
 #endif
 
 /* Verify after the fact */
@@ -3054,10 +3207,10 @@ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 receive_getc = tls_getc;
 receive_getbuf = tls_getbuf;
 receive_get_cache = tls_get_cache;
+receive_hasc = tls_hasc;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
-receive_smtp_buffered = tls_smtp_buffered;
 
 return OK;
 }
@@ -3107,8 +3260,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
      rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
     ) if (rr->type == T_TLSA) i++;
 
-dane_data = store_get(i * sizeof(uschar *), FALSE);
-dane_data_len = store_get(i * sizeof(int), FALSE);
+dane_data = store_get(i * sizeof(uschar *), GET_UNTAINTED);
+dane_data_len = store_get(i * sizeof(int), GET_UNTAINTED);
 
 i = 0;
 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
@@ -3166,25 +3319,29 @@ however avoid storing and retrieving session information. */
 
 static void
 tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
-  host_item * host, smtp_transport_options_block * ob)
+  smtp_connect_args * conn_args, smtp_transport_options_block * ob)
 {
 tlsp->resumption = RESUME_SUPPORTED;
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+
+if (!conn_args->have_lbserver)
+  { DEBUG(D_tls) debug_printf(
+      "resumption not supported: no LB detection done (continued-conn?)\n"); }
+else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
   {
   dbdata_tls_session * dt;
   int len, rc;
   open_db dbblock, * dbm_file;
 
-  DEBUG(D_tls)
-    debug_printf("check for resumable session for %s\n", host->address);
   tlsp->host_resumable = TRUE;
+  tls_client_resmption_key(tlsp, conn_args, ob);
+
   tlsp->resumption |= RESUME_CLIENT_REQUESTED;
   if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
     {
-    /* Key for the db is the IP.  We'd like to filter the retrieved session
-    for ticket advisory expiry, but 3.6.1 seems to give no access to that */
+    /* We'd like to filter the retrieved session for ticket advisory expiry,
+    but 3.6.1 seems to give no access to that */
 
-    if ((dt = dbfn_read_with_length(dbm_file, host->address, &len)))
+    if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len)))
       if (!(rc = gnutls_session_set_data(session,
                    CUS dt->session, (size_t)len - sizeof(dbdata_tls_session))))
        {
@@ -3196,6 +3353,7 @@ if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
     dbfn_close(dbm_file);
     }
   }
+else DEBUG(D_tls) debug_printf("no resumption for this host\n");
 }
 
 
@@ -3221,25 +3379,28 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
       {
       open_db dbblock, * dbm_file;
       int dlen = sizeof(dbdata_tls_session) + tkt.size;
-      dbdata_tls_session * dt = store_get(dlen, TRUE);
+      dbdata_tls_session * dt = store_get(dlen, GET_TAINTED);
 
-      DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size);
+      DEBUG(D_tls) debug_printf(" session data size %u\n", (unsigned)tkt.size);
       memcpy(dt->session, tkt.data, tkt.size);
       gnutls_free(tkt.data);
 
       if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
        {
        /* key for the db is the IP */
-       dbfn_delete(dbm_file, host->address);
-       dbfn_write(dbm_file, host->address, dt, dlen);
+       dbfn_write(dbm_file, tlsp->resume_index, dt, dlen);
        dbfn_close(dbm_file);
 
        DEBUG(D_tls)
-         debug_printf("wrote session db (len %u)\n", (unsigned)dlen);
+         debug_printf(" wrote session db (len %u)\n", (unsigned)dlen);
        }
       }
-    else DEBUG(D_tls)
-      debug_printf("extract session data: %s\n", US gnutls_strerror(rc));
+    else
+      { DEBUG(D_tls)
+      debug_printf(" extract session data: %s\n", US gnutls_strerror(rc));
+      }
+  else DEBUG(D_tls)
+      debug_printf(" host not resmable; not saving ticket\n");
   }
 }
 
@@ -3256,7 +3417,7 @@ tls_client_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
 exim_gnutls_state_st * state = gnutls_session_get_ptr(sess);
 tls_support * tlsp = state->tlsp;
 
-DEBUG(D_tls) debug_printf("newticket cb\n");
+DEBUG(D_tls) debug_printf("newticket cb (on client)\n");
 
 if (!tlsp->ticket_received)
   tls_save_session(tlsp, sess, state->host);
@@ -3266,14 +3427,14 @@ return 0;
 
 static void
 tls_client_resume_prehandshake(exim_gnutls_state_st * state,
-  tls_support * tlsp, host_item * host,
+  tls_support * tlsp, smtp_connect_args * conn_args,
   smtp_transport_options_block * ob)
 {
 gnutls_session_set_ptr(state->session, state);
 gnutls_handshake_set_hook_function(state->session,
   GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_client_ticket_cb);
 
-tls_retrieve_session(tlsp, state->session, host, ob);
+tls_retrieve_session(tlsp, state->session, conn_args, ob);
 }
 
 static void
@@ -3365,6 +3526,28 @@ if (!cipher_list)
 #endif
   }
 
+if (ob->tls_alpn)
+#ifdef EXIM_HAVE_ALPN
+  {
+  const gnutls_datum_t * plist;
+  unsigned plen;
+
+  if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr))
+    return FALSE;
+  if (plist)
+    if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0)
+      {
+      tls_error(US"alpn init", NULL, state->host, errstr);
+      return FALSE;
+      }
+    else
+      DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+  }
+#else
+  log_write(0, LOG_MAIN, "ALPN unusable with this GnuTLS library version; ignoring \"%s\"\n",
+          ob->tls_alpn);
+#endif
+
   {
   int dh_min_bits = ob->tls_dh_min_bits;
   if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
@@ -3433,7 +3616,7 @@ if (request_ocsp)
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
                    NULL, 0, NULL)) != OK)
     {
-    tls_error_gnu(US"cert-status-req", rc, state->host, errstr);
+    tls_error_gnu(state, US"cert-status-req", rc, errstr);
     return FALSE;
     }
   tlsp->ocsp = OCSP_NOT_RESP;
@@ -3441,7 +3624,7 @@ if (request_ocsp)
 #endif
 
 #ifdef EXIM_HAVE_TLS_RESUME
-tls_client_resume_prehandshake(state, tlsp, host, ob);
+tls_client_resume_prehandshake(state, tlsp, conn_args, ob);
 #endif
 
 #ifndef DISABLE_EVENT
@@ -3475,7 +3658,7 @@ if (rc != GNUTLS_E_SUCCESS)
     tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
     }
   else
-    tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr);
+    tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
   return FALSE;
   }
 
@@ -3520,9 +3703,9 @@ if (request_ocsp)
        gnutls_free(printed.data);
        }
       else
-       (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+       (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
     if (idx == 0 && rc)
-      (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+      (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
     }
 
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
@@ -3544,6 +3727,24 @@ if (request_ocsp)
 tls_client_resume_posthandshake(state, tlsp, host);
 #endif
 
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn)      /* We requested. See what was negotiated. */
+  {
+  gnutls_datum_t p = {.size = 0};
+
+  if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0)
+    { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); }
+  else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+    tls_error(US"handshake", US"ALPN required but not negotiated", state->host, errstr);
+    return FALSE;
+    }
+  else
+    DEBUG(D_tls) debug_printf("No ALPN negotiated");
+  }
+#endif
+
 /* Sets various Exim expansion variables; may need to adjust for ACL callouts */
 
 extract_exim_vars_from_tls_state(state);
@@ -3602,12 +3803,21 @@ if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
 if (do_shutdown)
   {
   DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
-    do_shutdown > 1 ? " (with response-wait)" : "");
+    do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : "");
 
   tls_write(ct_ctx, NULL, 0, FALSE);   /* flush write buffer */
 
+#ifdef EXIM_TCP_CORK
+  if (do_shutdown == TLS_SHUTDOWN_WAIT)
+    (void) setsockopt(tlsp->active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off));
+#endif
+
+  /* The library seems to have no way to only wait for a peer's
+  shutdown, so handle the same as TLS_SHUTDOWN_WAIT */
+
   ALARM(2);
-  gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  gnutls_bye(state->session,
+      do_shutdown > TLS_SHUTDOWN_NOWAIT ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
   ALARM_CLR(0);
   }
 
@@ -3616,16 +3826,13 @@ if (!ct_ctx)    /* server */
   receive_getc =       smtp_getc;
   receive_getbuf =     smtp_getbuf;
   receive_get_cache =  smtp_get_cache;
+  receive_hasc =       smtp_hasc;
   receive_ungetc =     smtp_ungetc;
   receive_feof =       smtp_feof;
   receive_ferror =     smtp_ferror;
-  receive_smtp_buffered = smtp_buffered;
   }
 
 gnutls_deinit(state->session);
-gnutls_certificate_free_credentials(state->lib_state.x509_cred);
-state->lib_state = null_tls_preload;
-
 tlsp->active.sock = -1;
 tlsp->active.tls_ctx = NULL;
 /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
@@ -3730,6 +3937,13 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
+BOOL
+tls_hasc(void)
+{
+exim_gnutls_state_st * state = &state_server;
+return state->xfer_buffer_lwm < state->xfer_buffer_hwm;
+}
+
 uschar *
 tls_getbuf(unsigned * len)
 {
@@ -3754,12 +3968,15 @@ return buf;
 }
 
 
+/* Get up to the given number of bytes from any cached data, and feed to dkim. */
 void
-tls_get_cache(void)
+tls_get_cache(unsigned lim)
 {
 #ifndef DISABLE_DKIM
 exim_gnutls_state_st * state = &state_server;
 int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
+if (n > lim)
+  n = lim;
 if (n > 0)
   dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
 #endif
@@ -3767,7 +3984,7 @@ if (n > 0)
 
 
 BOOL
-tls_could_read(void)
+tls_could_getc(void)
 {
 return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
  || gnutls_record_check_pending(state_server.session) > 0;
@@ -4074,7 +4291,7 @@ DEBUG(D_tls)
 rc = gnutls_priority_init(&priority_cache, CS expciphers, &errpos);
 validate_check_rc(string_sprintf(
       "gnutls_priority_init(%s) failed at offset %ld, \"%.8s..\"",
-      expciphers, errpos - CS expciphers, errpos));
+      expciphers, (long)(errpos - CS expciphers), errpos));
 
 #undef return_deinit
 #undef validate_check_rc
@@ -4094,17 +4311,18 @@ return NULL;
 
 /* See a description in tls-openssl.c for an explanation of why this exists.
 
-Arguments:   a FILE* to print the results to
-Returns:     nothing
+Arguments:   string to append to
+Returns:     string
 */
 
-void
-tls_version_report(FILE *f)
+gstring *
+tls_version_report(gstring * g)
 {
-fprintf(f, "Library version: GnuTLS: Compile: %s\n"
-           "                         Runtime: %s\n",
-           LIBGNUTLS_VERSION,
-           gnutls_check_version(NULL));
+return string_fmt_append(g,
+    "Library version: GnuTLS: Compile: %s\n"
+    "                         Runtime: %s\n",
+            LIBGNUTLS_VERSION,
+            gnutls_check_version(NULL));
 }
 
 #endif /*!MACRO_PREDEF*/