TLS: ALPN options
[exim.git] / src / src / tls-gnu.c
index 4a3e1651ee979716738068eb748ce3d73dbefd49..bc16ec03e5b9d88f22af2e7ec1370a8a38939e80 100644 (file)
@@ -119,6 +119,12 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 # endif
 #endif
 
+#if GNUTLS_VERSION_NUMBER >= 0x030200
+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE
+#  define EXIM_HAVE_ALPN
+# endif
+#endif
+
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
@@ -145,7 +151,7 @@ builtin_macro_create(US"_HAVE_TLS_OCSP");
 # ifdef SUPPORT_SRV_OCSP_STACK
 builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
 # endif
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 builtin_macro_create(US"_HAVE_TLS_CA_CACHE");
 # endif
 }
@@ -278,10 +284,14 @@ static BOOL gnutls_buggy_ocsp = FALSE;
 static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
 #endif
 
+#ifdef EXIM_HAVE_ALPN
+static int server_seen_alpn = -1;      /* count of names */
+#endif
 #ifdef EXIM_HAVE_TLS_RESUME
 static gnutls_datum_t server_sessticket_key;
 #endif
 
+
 /* ------------------------------------------------------------------------ */
 /* macros */
 
@@ -345,6 +355,58 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
 #endif
 
 
+/*************************************************
+*               Handle TLS error                 *
+*************************************************/
+
+/* Called from lots of places when errors occur before actually starting to do
+the TLS handshake, that is, while the session is still in clear. Always returns
+DEFER for a server and FAIL for a client so that most calls can use "return
+tls_error(...)" to do this processing and then give an appropriate return. A
+single function is used for both server and client, because it is called from
+some shared functions.
+
+Argument:
+  prefix    text to include in the logged error
+  msg       additional error string (may be NULL)
+            usually obtained from gnutls_strerror()
+  host      NULL if setting up a server;
+            the connected host if setting up a client
+  errstr    pointer to returned error string
+
+Returns:    OK/DEFER/FAIL
+*/
+
+static int
+tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
+  uschar ** errstr)
+{
+if (errstr)
+  *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US"");
+return host ? FAIL : DEFER;
+}
+
+
+static int
+tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
+  uschar ** errstr)
+{
+return tls_error(prefix,
+  state && err == GNUTLS_E_FATAL_ALERT_RECEIVED
+  ? US gnutls_alert_get_name(gnutls_alert_get(state->session))
+  : US gnutls_strerror(err),
+  state ? state->host : NULL,
+  errstr);
+}
+
+static int
+tls_error_sys(const uschar *prefix, int err, const host_item *host,
+  uschar ** errstr)
+{
+return tls_error(prefix, US strerror(err), host, errstr);
+}
+
+
 /* ------------------------------------------------------------------------ */
 /* Initialisation */
 
@@ -379,9 +441,10 @@ return FALSE;
 #endif
 
 
-static void
-tls_g_init(void)
+static int
+tls_g_init(uschar ** errstr)
 {
+int rc;
 DEBUG(D_tls) debug_printf("GnuTLS global init required\n");
 
 #if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL)
@@ -393,12 +456,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, host, 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, host, errstr);
+  return tls_error_gnu(UNULL, S"gnutls_global_init", rc, errstr);
 #endif
 
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
@@ -416,6 +479,7 @@ if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp()))
 #endif
 
 exim_gnutls_base_init_done = TRUE;
+return OK;
 }
 
 
@@ -432,10 +496,11 @@ tls_per_lib_daemon_tick(void)
 static void
 tls_per_lib_daemon_init(void)
 {
+uschar * dummy_errstr;
 static BOOL once = FALSE;
 
 if (!exim_gnutls_base_init_done)
-  tls_g_init();
+  tls_g_init(&dummy_errstr);
 
 if (!once)
   {
@@ -456,54 +521,6 @@ if (!once)
 }
 
 /* ------------------------------------------------------------------------ */
-/* Static functions */
-
-/*************************************************
-*               Handle TLS error                 *
-*************************************************/
-
-/* Called from lots of places when errors occur before actually starting to do
-the TLS handshake, that is, while the session is still in clear. Always returns
-DEFER for a server and FAIL for a client so that most calls can use "return
-tls_error(...)" to do this processing and then give an appropriate return. A
-single function is used for both server and client, because it is called from
-some shared functions.
-
-Argument:
-  prefix    text to include in the logged error
-  msg       additional error string (may be NULL)
-            usually obtained from gnutls_strerror()
-  host      NULL if setting up a server;
-            the connected host if setting up a client
-  errstr    pointer to returned error string
-
-Returns:    OK/DEFER/FAIL
-*/
-
-static int
-tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
-  uschar ** errstr)
-{
-if (errstr)
-  *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US"");
-return host ? FAIL : DEFER;
-}
-
-
-static int
-tls_error_gnu(const uschar *prefix, int err, const host_item *host,
-  uschar ** errstr)
-{
-return tls_error(prefix, US gnutls_strerror(err), host, errstr);
-}
-
-static int
-tls_error_sys(const uschar *prefix, int err, const host_item *host,
-  uschar ** errstr)
-{
-return tls_error(prefix, US strerror(err), host, errstr);
-}
-
 
 /*************************************************
 *    Deal with logging errors during I/O         *
@@ -703,7 +720,7 @@ 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);
 
 if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
   return DEFER;
@@ -733,7 +750,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;
   }
@@ -817,7 +834,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);
   }
 
@@ -849,7 +866,7 @@ if (rc < 0)
     return tls_error(US"Filename too long to generate replacement",
         filename, NULL, errstr);
 
-  temp_fn = string_copy(US"%s.XXXXXXX");
+  temp_fn = string_copy(US"exim-dh.XXXXXXX");
   if ((fd = mkstemp(CS temp_fn)) < 0)  /* modifies temp_fn */
     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 */
@@ -872,7 +889,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
@@ -883,8 +900,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);
@@ -894,7 +911,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 */
 
@@ -927,7 +944,7 @@ return OK;
 
 
 
-/* Create and install a selfsigned certificate, for use in server mode */
+/* Create and install a selfsigned certificate, for use in server mode. */
 
 static int
 tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr)
@@ -944,6 +961,7 @@ rc = GNUTLS_E_NO_CERTIFICATE_FOUND;
 if (TRUE) goto err;
 #endif
 
+DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n");
 where = US"initialising pkey";
 if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
 
@@ -968,7 +986,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, now + 60 * 60)) /* 1 hr */
+   || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60))      /* 2 hour */
    || (rc = gnutls_x509_crt_set_key(cert, pkey))
 
    || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
@@ -998,7 +1016,7 @@ out:
   return rc;
 
 err:
-  rc = tls_error_gnu(where, rc, NULL, errstr);
+  rc = tls_error_gnu(state, where, rc, errstr);
   goto out;
 }
 
@@ -1019,9 +1037,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;
 }
 
@@ -1056,13 +1074,36 @@ 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 */
-if (tls_id == 5)       /* status_request */
+switch (tls_id)
   {
-  DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
-  tls_in.ocsp = OCSP_NOT_RESP;
+  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 */
+    /* 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. */
+
+    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++;
+      DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
+      }
+    DEBUG(D_tls) debug_printf("\n");
+    if (server_seen_alpn > 1)
+      {
+      DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
+      return GNUTLS_E_NO_APPLICATION_PROTOCOL;
+      }
+    break;
+#endif
   }
 return 0;
 }
@@ -1123,12 +1164,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;
 }
 
@@ -1258,9 +1299,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":"");
 
@@ -1278,9 +1319,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
          {
@@ -1382,7 +1423,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);
 
@@ -1397,8 +1438,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;
@@ -1419,26 +1460,25 @@ return gnutls_priority_init( (gnutls_priority_t *) &state->lib_state.pri_cache,
   CCS p, errpos);
 }
 
-static void
+static unsigned
 tls_server_creds_init(void)
 {
 uschar * dummy_errstr;
+unsigned lifetime = 0;
 
 state_server.lib_state = null_tls_preload;
 if (gnutls_certificate_allocate_credentials(
       (gnutls_certificate_credentials_t *) &state_server.lib_state.x509_cred))
   {
   state_server.lib_state.x509_cred = NULL;
-  return;
+  return lifetime;
   }
 creds_basic_init(state_server.lib_state.x509_cred, TRUE);
 
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 /* If tls_certificate has any $ indicating expansions, it is not good.
 If tls_privatekey is set but has $, not good.  Likewise for tls_ocsp_file.
-If all good (and tls_certificate set), load the cert(s).  Do not try
-to handle selfsign generation for now (tls_certificate null/empty;
-XXX will want to do that later though) due to the lifetime/expiry issue. */
+If all good (and tls_certificate set), load the cert(s). */
 
 if (  opt_set_and_noexpand(tls_certificate)
 # ifndef DISABLE_OCSP
@@ -1468,6 +1508,18 @@ if (  opt_set_and_noexpand(tls_certificate)
       state_server.lib_state.conn_certs = TRUE;
     }
   }
+else if (  !tls_certificate && !tls_privatekey
+# ifndef DISABLE_OCSP
+       && !tls_ocsp_file
+# endif
+       )
+  {            /* Generate & preload a selfsigned cert. No files to watch. */
+  if ((tls_install_selfsign(&state_server, &dummy_errstr)) == OK)
+    {
+    state_server.lib_state.conn_certs = TRUE;
+    lifetime = f.running_in_test_harness ? 2 : 60 * 60;                /* 1 hour */
+    }
+  }
 else
   DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
 
@@ -1480,7 +1532,7 @@ if (opt_set_and_noexpand(tls_verify_certificates))
     DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n");
     if (creds_load_cabundle(&state_server, tls_verify_certificates,
                            NULL, &dummy_errstr) != OK)
-      return;
+      return lifetime;
     state_server.lib_state.cabundle = TRUE;
 
     /* If CAs loaded and tls_crl is non-empty and has no $, load it */
@@ -1491,7 +1543,7 @@ if (opt_set_and_noexpand(tls_verify_certificates))
        {
        DEBUG(D_tls) debug_printf("TLS: preloading CRL for server\n");
        if (creds_load_crl(&state_server, tls_crl, &dummy_errstr) != OK)
-         return;
+         return lifetime;
        state_server.lib_state.crl = TRUE;
        }
       }
@@ -1518,6 +1570,7 @@ if (!tls_require_ciphers || opt_set_and_noexpand(tls_require_ciphers))
   }
 else
   DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n");
+return lifetime;
 }
 
 
@@ -1532,8 +1585,9 @@ exim_gnutls_state_st tpt_dummy_state;
 host_item * dummy_host = (host_item *)1;
 uschar * dummy_errstr;
 
-if (!exim_gnutls_base_init_done)
-  tls_g_init();
+if (  !exim_gnutls_base_init_done
+   && tls_g_init(&dummy_errstr) != OK)
+  return;
 
 ob->tls_preload = null_tls_preload;
 if (gnutls_certificate_allocate_credentials(
@@ -1547,7 +1601,7 @@ creds_basic_init(ob->tls_preload.x509_cred, FALSE);
 tpt_dummy_state.session = NULL;
 tpt_dummy_state.lib_state = ob->tls_preload;
 
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 if (  opt_set_and_noexpand(ob->tls_certificate)
    && opt_unset_or_noexpand(ob->tls_privatekey))
   {
@@ -1611,7 +1665,7 @@ depends on DANE or plain usage. */
 }
 
 
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 /* Invalidate the creds cached, by dropping the current ones.
 Call when we notice one of the source files has changed. */
  
@@ -1696,8 +1750,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);
   }
 
@@ -1901,7 +1955,7 @@ if (!state->host)
 
 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;
 }
@@ -1937,8 +1991,9 @@ exim_gnutls_state_st * state;
 int rc;
 size_t sz;
 
-if (!exim_gnutls_base_init_done)
-  tls_g_init();
+if (  !exim_gnutls_base_init_done
+   && (rc = tls_g_init(errstr)) != OK)
+  return rc;
 
 if (host)
   {
@@ -1980,13 +2035,13 @@ 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;
 
 /* This handles the variables that might get re-expanded after TLS SNI;
-that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
+tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
 
 DEBUG(D_tls)
   debug_printf("Expanding various TLS configuration options for session credentials\n");
@@ -2009,7 +2064,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)
@@ -2039,10 +2094,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);
+                   rc, errstr);
   }
 else
   {
@@ -2052,7 +2107,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. */
@@ -2250,7 +2305,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)
@@ -2773,7 +2828,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(const 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, is_tainted(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, uschar ** errstr)
+{
+int rc;
+const gnutls_datum_t * plist;
+unsigned plen;
+
+if (tls_alpn_plist(tls_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 /* EXIM_HAVE_ALPN */
+
 /* ------------------------------------------------------------------------ */
 /* Exported functions */
 
@@ -2830,6 +2949,10 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
 #endif
   }
 
+#ifdef EXIM_HAVE_ALPN
+tls_server_set_acceptable_alpns(state, errstr);
+#endif
+
 #ifdef EXIM_HAVE_TLS_RESUME
 tls_server_resume_prehandshake(state);
 #endif
@@ -2918,7 +3041,7 @@ if (rc != GNUTLS_E_SUCCESS)
     }
   else
     {
-    tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
+    tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
     (void) gnutls_alert_send_appropriate(state->session, rc);
     gnutls_deinit(state->session);
     gnutls_certificate_free_credentials(state->lib_state.x509_cred);
@@ -2945,6 +3068,33 @@ tls_server_resume_posthandshake(state);
 
 DEBUG(D_tls) post_handshake_debug(state);
 
+#ifdef EXIM_HAVE_ALPN
+if (server_seen_alpn > 0)
+  {
+  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_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 */
 
 if (!verify_certificate(state, errstr))
@@ -3282,6 +3432,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)
@@ -3350,7 +3522,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;
@@ -3392,7 +3564,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;
   }
 
@@ -3437,9 +3609,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)
@@ -3461,6 +3633,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);
@@ -3472,6 +3662,25 @@ return TRUE;
 
 
 
+/*
+Arguments:
+  ct_ctx       client TLS context pointer, or NULL for the one global server context
+*/
+
+void
+tls_shutdown_wr(void * ct_ctx)
+{
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+tls_support * tlsp = state->tlsp;
+
+if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
+
+tls_write(ct_ctx, NULL, 0, FALSE);     /* flush write buffer */
+
+HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent("  SMTP(TLS shutdown)>>\n");
+gnutls_bye(state->session, GNUTLS_SHUT_WR);
+}
+
 /*************************************************
 *         Close down a TLS session               *
 *************************************************/
@@ -3482,27 +3691,30 @@ 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
+  do_shutdown  0 no data-flush or TLS close-alert
+               1 if TLS close-alert is to be sent,
+               2 if also response to be waited for (2s timeout)
 
 Returns:     nothing
 */
 
 void
-tls_close(void * ct_ctx, int shutdown)
+tls_close(void * ct_ctx, int do_shutdown)
 {
 exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
 tls_support * tlsp = state->tlsp;
 
 if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
 
-if (shutdown)
+if (do_shutdown)
   {
   DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
-    shutdown > 1 ? " (with response-wait)" : "");
+    do_shutdown > 1 ? " (with response-wait)" : "");
+
+  tls_write(ct_ctx, NULL, 0, FALSE);   /* flush write buffer */
 
   ALARM(2);
-  gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
   ALARM_CLR(0);
   }
 
@@ -3650,7 +3862,7 @@ return buf;
 
 
 void
-tls_get_cache()
+tls_get_cache(void)
 {
 #ifndef DISABLE_DKIM
 exim_gnutls_state_st * state = &state_server;
@@ -3669,8 +3881,6 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
 }
 
 
-
-
 /*************************************************
 *          Read bytes from TLS channel           *
 *************************************************/