Use %ld not %l
[exim.git] / src / src / tls-gnu.c
index 9b684e3cd64446d3990146cdd96957c07903f605..618caf6a225f19b35fb4780aff6d7b6df5b34231 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
@@ -148,6 +154,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
 
@@ -278,10 +287,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 */
 
@@ -378,10 +391,15 @@ return host ? FAIL : DEFER;
 
 
 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
+  ? US gnutls_alert_get_name(gnutls_alert_get(state->session))
+  : US gnutls_strerror(err),
+  state ? state->host : NULL,
+  errstr);
 }
 
 static int
@@ -441,12 +459,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
@@ -705,7 +723,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;
@@ -735,7 +753,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;
   }
@@ -819,7 +837,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);
   }
 
@@ -851,7 +869,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 */
@@ -874,7 +892,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
@@ -885,8 +903,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);
@@ -896,7 +914,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 */
 
@@ -929,7 +947,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)
@@ -946,6 +964,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;
 
@@ -970,7 +989,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,
@@ -1000,7 +1019,7 @@ out:
   return rc;
 
 err:
-  rc = tls_error_gnu(where, rc, NULL, errstr);
+  rc = tls_error_gnu(state, where, rc, errstr);
   goto out;
 }
 
@@ -1021,9 +1040,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;
 }
 
@@ -1058,13 +1077,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;
 }
@@ -1125,12 +1167,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;
 }
 
@@ -1260,9 +1302,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":"");
 
@@ -1280,9 +1322,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
          {
@@ -1384,7 +1426,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);
 
@@ -1399,8 +1441,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;
@@ -1421,26 +1463,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);
 
 #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
@@ -1470,19 +1511,36 @@ 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");
 
-/* 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))
     {
     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 */
@@ -1493,7 +1551,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;
        }
       }
@@ -1520,6 +1578,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;
 }
 
 
@@ -1578,7 +1637,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))
     {
@@ -1699,8 +1765,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);
   }
 
@@ -1794,7 +1860,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)
   {
@@ -1904,7 +1971,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;
 }
@@ -1984,13 +2051,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");
@@ -2013,7 +2080,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)
@@ -2043,10 +2110,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
   {
@@ -2056,7 +2123,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. */
@@ -2254,7 +2321,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)
@@ -2777,7 +2844,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 */
 
@@ -2834,6 +2965,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
@@ -2922,7 +3057,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);
@@ -2949,6 +3084,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))
@@ -3286,6 +3448,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)
@@ -3354,7 +3538,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;
@@ -3396,7 +3580,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;
   }
 
@@ -3441,9 +3625,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)
@@ -3465,6 +3649,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);
@@ -3476,6 +3678,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               *
 *************************************************/
@@ -3486,27 +3707,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);
   }
 
@@ -3654,7 +3878,7 @@ return buf;
 
 
 void
-tls_get_cache()
+tls_get_cache(void)
 {
 #ifndef DISABLE_DKIM
 exim_gnutls_state_st * state = &state_server;
@@ -3673,8 +3897,6 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
 }
 
 
-
-
 /*************************************************
 *          Read bytes from TLS channel           *
 *************************************************/
@@ -3975,7 +4197,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