GnuTLS website moves
[exim.git] / src / src / tls-gnu.c
index f0e391f9713ef53c7edd7940138a411e3a4ea16c..c90ae1411caaa64234f826ace24af37f1e7b650f 100644 (file)
@@ -39,6 +39,10 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #include <gnutls/x509.h>
 /* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
 #include <gnutls/crypto.h>
+/* needed to disable PKCS11 autoload unless requested */
+#if GNUTLS_VERSION_NUMBER >= 0x020c00
+# include <gnutls/pkcs11.h>
+#endif
 
 /* GnuTLS 2 vs 3
 
@@ -63,8 +67,7 @@ Some of these correspond to variables in globals.c; those variables will
 be set to point to content in one of these instances, as appropriate for
 the stage of the process lifetime.
 
-Not handled here: globals tls_active, tls_bits, tls_cipher, tls_peerdn,
-tls_certificate_verified, tls_channelbinding_b64, tls_sni.
+Not handled here: global tls_channelbinding_b64.
 */
 
 typedef struct exim_gnutls_state {
@@ -76,8 +79,10 @@ typedef struct exim_gnutls_state {
   int fd_out;
   BOOL peer_cert_verified;
   BOOL trigger_sni_changes;
+  BOOL have_set_peerdn;
   const struct host_item *host;
   uschar *peerdn;
+  uschar *ciphersuite;
   uschar *received_sni;
 
   const uschar *tls_certificate;
@@ -93,22 +98,22 @@ typedef struct exim_gnutls_state {
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
 
+  tls_support *tlsp;   /* set in tls_init() */
+
   uschar *xfer_buffer;
   int xfer_buffer_lwm;
   int xfer_buffer_hwm;
   int xfer_eof;
   int xfer_error;
-
-  uschar cipherbuf[256];
 } exim_gnutls_state_st;
 
 static const exim_gnutls_state_st exim_gnutls_state_init = {
-  NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE,
-  NULL, NULL, NULL,
+  NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
+  NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL,
   NULL, 0, 0, 0, 0,
-  ""
 };
 
 /* Not only do we have our own APIs which don't pass around state, assuming
@@ -121,7 +126,6 @@ there's no way for heart-beats to be responded to, for the duration of the
 second connection. */
 
 static exim_gnutls_state_st state_server, state_client;
-static exim_gnutls_state_st *current_global_tls_state;
 
 /* 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
@@ -140,12 +144,6 @@ static const char * const exim_default_gnutls_priority = "NORMAL";
 static BOOL exim_gnutls_base_init_done = FALSE;
 
 
-/* ------------------------------------------------------------------------ */
-/* Callback declarations */
-
-static void exim_gnutls_logger_cb(int level, const char *message);
-static int exim_sni_handling_cb(gnutls_session_t session);
-
 /* ------------------------------------------------------------------------ */
 /* macros */
 
@@ -154,24 +152,48 @@ static int exim_sni_handling_cb(gnutls_session_t session);
 /* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
 the library logging; a value less than 0 disables the calls to set up logging
 callbacks. */
+#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
 #define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
+#endif
 
+#ifndef EXIM_CLIENT_DH_MIN_BITS
 #define EXIM_CLIENT_DH_MIN_BITS 1024
+#endif
+
+/* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we
+can ask for a bit-strength.  Without that, we stick to the constant we had
+before, for now. */
+#ifndef EXIM_SERVER_DH_BITS_PRE2_12
+#define EXIM_SERVER_DH_BITS_PRE2_12 1024
+#endif
 
 #define exim_gnutls_err_check(Label) do { \
   if (rc != GNUTLS_E_SUCCESS) { return tls_error((Label), gnutls_strerror(rc), host); } } while (0)
 
-#define exim_gnutls_err_debugreturn0(Label) do { \
-  if (rc != GNUTLS_E_SUCCESS) { \
-    DEBUG(D_tls) debug_printf("TLS failure: %s: %s", (Label), gnutls_strerror(rc)); \
-    return 0; } } while (0)
-
 #define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
 
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 #define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+#define HAVE_GNUTLS_RND
+#define HAVE_GNUTLS_PKCS11
 #endif
 
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Callback declarations */
+
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void exim_gnutls_logger_cb(int level, const char *message);
+#endif
+
+static int exim_sni_handling_cb(gnutls_session_t session);
+
+
+
+
 /* ------------------------------------------------------------------------ */
 /* Static functions */
 
@@ -270,59 +292,32 @@ Sets:
   tls_cipher                a string
   tls_peerdn                a string
   tls_sni                   a (UTF-8) string
-Also:
-  current_global_tls_state  for API limitations
 
 Argument:
   state      the relevant exim_gnutls_state_st *
 */
 
 static void
-extract_exim_vars_from_tls_state(exim_gnutls_state_st *state)
+extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server)
 {
-gnutls_protocol_t protocol;
 gnutls_cipher_algorithm_t cipher;
-gnutls_kx_algorithm_t kx;
-gnutls_mac_algorithm_t mac;
-uschar *p;
 #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
 int old_pool;
 int rc;
 gnutls_datum_t channel;
 #endif
 
-current_global_tls_state = state;
-
-tls_active = state->fd_out;
+state->tlsp->active = state->fd_out;
 
 cipher = gnutls_cipher_get(state->session);
 /* returns size in "bytes" */
-tls_bits = gnutls_cipher_get_key_size(cipher) * 8;
+state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
 
-if (!*state->cipherbuf)
-  {
-  protocol = gnutls_protocol_get_version(state->session);
-  mac = gnutls_mac_get(state->session);
-  kx = gnutls_kx_get(state->session);
-
-  string_format(state->cipherbuf, sizeof(state->cipherbuf),
-      "%s:%s:%u",
-      gnutls_protocol_get_name(protocol),
-      gnutls_cipher_suite_get_name(kx, cipher, mac),
-      tls_bits);
-
-  /* I don't see a way that spaces could occur, in the current GnuTLS
-  code base, but it was a concern in the old code and perhaps older GnuTLS
-  releases did return "TLS 1.0"; play it safe, just in case. */
-  for (p = state->cipherbuf; *p != '\0'; ++p)
-    if (isspace(*p))
-      *p = '-';
-  }
-tls_cipher = state->cipherbuf;
+state->tlsp->cipher = state->ciphersuite;
 
-DEBUG(D_tls) debug_printf("cipher: %s\n", tls_cipher);
+DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
 
-tls_certificate_verified = state->peer_cert_verified;
+state->tlsp->certificate_verified = state->peer_cert_verified;
 
 /* note that tls_channelbinding_b64 is not saved to the spool file, since it's
 only available for use for authenticators while this TLS session is running. */
@@ -343,9 +338,8 @@ if (rc) {
 }
 #endif
 
-tls_peerdn = state->peerdn;
-
-tls_sni = state->received_sni;
+state->tlsp->peerdn = state->peerdn;
+state->tlsp->sni =    state->received_sni;
 }
 
 
@@ -365,9 +359,6 @@ 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.
 
-Argument:
-  host       NULL for server, server for client (for error handling)
-
 Returns:     OK/DEFER/FAIL
 */
 
@@ -377,25 +368,91 @@ init_server_dh(void)
 int fd, rc;
 unsigned int dh_bits;
 gnutls_datum m;
-uschar filename[PATH_MAX];
+uschar filename_buf[PATH_MAX];
+uschar *filename = NULL;
 size_t sz;
+uschar *exp_tls_dhparam;
+BOOL use_file_in_spool = FALSE;
+BOOL use_fixed_file = FALSE;
 host_item *host = NULL; /* dummy for macros */
-const char * const dh_param_fn_ext = "normal"; /* change as dh_bits changes */
 
 DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
 
 rc = gnutls_dh_params_init(&dh_server_params);
 exim_gnutls_err_check(US"gnutls_dh_params_init");
 
-/* If you change this, also change dh_param_fn_ext so that we can use a
+m.data = NULL;
+m.size = 0;
+
+if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam))
+  return DEFER;
+
+if (!exp_tls_dhparam)
+  {
+  DEBUG(D_tls) debug_printf("Loading default hard-coded DH params\n");
+  m.data = US std_dh_prime_default();
+  m.size = Ustrlen(m.data);
+  }
+else if (Ustrcmp(exp_tls_dhparam, "historic") == 0)
+  use_file_in_spool = TRUE;
+else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
+  {
+  DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+  return OK;
+  }
+else if (exp_tls_dhparam[0] != '/')
+  {
+  m.data = US std_dh_prime_named(exp_tls_dhparam);
+  if (m.data == NULL)
+    return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL);
+  m.size = Ustrlen(m.data);
+  }
+else
+  {
+  use_fixed_file = TRUE;
+  filename = exp_tls_dhparam;
+  }
+
+if (m.data)
+  {
+  rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
+  exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+  DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
+  return OK;
+  }
+
+#ifdef HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+/* If you change this constant, also change dh_param_fn_ext so that we can use a
 different filename and ensure we have sufficient bits. */
 dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
 if (!dh_bits)
   return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL);
+DEBUG(D_tls)
+  debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
+      dh_bits);
+#else
+dh_bits = EXIM_SERVER_DH_BITS_PRE2_12;
+DEBUG(D_tls)
+  debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits.\n",
+      dh_bits);
+#endif
+
+/* Some clients have hard-coded limits. */
+if (dh_bits > tls_dh_max_bits)
+  {
+  DEBUG(D_tls)
+    debug_printf("tls_dh_max_bits clamping override, using %d bits instead.\n",
+        tls_dh_max_bits);
+  dh_bits = tls_dh_max_bits;
+  }
 
-if (!string_format(filename, sizeof(filename),
-      "%s/gnutls-params-%s", spool_directory, dh_param_fn_ext))
-  return tls_error(US"overlong filename", NULL, NULL);
+if (use_file_in_spool)
+  {
+  if (!string_format(filename_buf, sizeof(filename_buf),
+        "%s/gnutls-params-%d", spool_directory, dh_bits))
+    return tls_error(US"overlong filename", NULL, NULL);
+  filename = filename_buf;
+  }
 
 /* Open the cache file for reading and if successful, read it and set up the
 parameters. */
@@ -472,6 +529,7 @@ case. */
 if (rc < 0)
   {
   uschar *temp_fn;
+  unsigned int dh_bits_gen = dh_bits;
 
   if ((PATH_MAX - Ustrlen(filename)) < 10)
     return tls_error(US"Filename too long to generate replacement",
@@ -483,8 +541,26 @@ if (rc < 0)
     return tls_error(US"Unable to open temp file", strerror(errno), NULL);
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
-  DEBUG(D_tls) debug_printf("generating %d bits Diffie-Hellman key ...\n", dh_bits);
-  rc = gnutls_dh_params_generate2(dh_server_params, dh_bits);
+  /* 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;
+    DEBUG(D_tls)
+      debug_printf("being paranoid about DH generation, make it '%d' bits'\n",
+          dh_bits_gen);
+    }
+
+  DEBUG(D_tls)
+    debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
+        dh_bits_gen);
+  rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
   exim_gnutls_err_check(US"gnutls_dh_params_generate2");
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
@@ -501,6 +577,7 @@ if (rc < 0)
   m.data = malloc(m.size);
   if (m.data == NULL)
     return tls_error(US"memory allocation failed", strerror(errno), NULL);
+  /* this will return a size 1 less than the allocation size above */
   rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz);
   if (rc != GNUTLS_E_SUCCESS)
@@ -508,6 +585,7 @@ if (rc < 0)
     free(m.data);
     exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
     }
+  m.size = sz; /* shrink by 1, probably */
 
   sz = write_to_fd_buf(fd, m.data, (size_t) m.size);
   if (sz != m.size)
@@ -562,6 +640,7 @@ Returns:          OK/DEFER/FAIL
 static int
 tls_expand_session_files(exim_gnutls_state_st *state)
 {
+struct stat statbuf;
 int rc;
 const host_item *host = state->host;  /* macro should be reconsidered? */
 uschar *saved_tls_certificate = NULL;
@@ -575,7 +654,11 @@ if (!state->host)
   {
   if (!state->received_sni)
     {
-    if (Ustrstr(state->tls_certificate, US"tls_sni"))
+    if (state->tls_certificate &&
+        (Ustrstr(state->tls_certificate, US"tls_sni") ||
+         Ustrstr(state->tls_certificate, US"tls_in_sni") ||
+         Ustrstr(state->tls_certificate, US"tls_out_sni")
+       ))
       {
       DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
       state->trigger_sni_changes = TRUE;
@@ -583,6 +666,7 @@ if (!state->host)
     }
   else
     {
+    /* useful for debugging */
     saved_tls_certificate = state->exp_tls_certificate;
     saved_tls_privatekey = state->exp_tls_privatekey;
     saved_tls_verify_certificates = state->exp_tls_verify_certificates;
@@ -590,6 +674,9 @@ if (!state->host)
     }
   }
 
+rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
+exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
+
 /* remember: expand_check_tlsvar() is expand_check() but fiddling with
 state members, assuming consistent naming; and expand_check() returns
 false if expansion failed, unless expansion was forced to fail. */
@@ -625,7 +712,6 @@ if (state->tls_privatekey == NULL || *state->tls_privatekey == '\0')
 
 if (state->exp_tls_certificate && *state->exp_tls_certificate)
   {
-  BOOL setit = TRUE;
   DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n",
       state->exp_tls_certificate, state->exp_tls_privatekey);
 
@@ -634,25 +720,22 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
     if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
         (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
       {
-      DEBUG(D_tls) debug_printf("cert and key unchanged with SNI.\n");
-      setit = FALSE;
+      DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
       }
     else
       {
-      DEBUG(D_tls) debug_printf("SNI changed cert/key pair.\n");
+      DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
       }
     }
 
-  if (setit)
-    {
-    rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
-        CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
-        GNUTLS_X509_FMT_PEM);
-    exim_gnutls_err_check(
-        string_sprintf("cert/key setup: cert=%s key=%s",
-          state->exp_tls_certificate, state->exp_tls_privatekey));
-    }
-  }
+  rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+      CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
+      GNUTLS_X509_FMT_PEM);
+  exim_gnutls_err_check(
+      string_sprintf("cert/key setup: cert=%s key=%s",
+        state->exp_tls_certificate, state->exp_tls_privatekey));
+  DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+  } /* tls_certificate */
 
 /* Set the trusted CAs file if one is provided, and then add the CRL if one is
 provided. Experiment shows that, if the certificate file is empty, an unhelpful
@@ -662,89 +745,135 @@ behaviour. */
 
 if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
-  struct stat statbuf;
-  BOOL setit_vc = TRUE, setit_crl = TRUE;
-
   if (!expand_check_tlsvar(tls_verify_certificates))
     return DEFER;
   if (state->tls_crl && *state->tls_crl)
     if (!expand_check_tlsvar(tls_crl))
       return DEFER;
 
-  if (state->received_sni)
+  if (!(state->exp_tls_verify_certificates &&
+        *state->exp_tls_verify_certificates))
     {
-    if (Ustrcmp(state->exp_tls_verify_certificates, saved_tls_verify_certificates) == 0)
-      setit_vc = FALSE;
-    if (Ustrcmp(state->exp_tls_crl, saved_tls_crl) == 0)
-      setit_crl = FALSE;
-    }
-
-  /* nb: early exit; change if add more expansions to this function */
-  if (!(setit_vc || setit_crl))
+    DEBUG(D_tls)
+      debug_printf("TLS: tls_verify_certificates expanded empty, ignoring\n");
+    /* With no tls_verify_certificates, we ignore tls_crl too */
     return OK;
-
-  if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
-        "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
-        strerror(errno));
-    return DEFER;
     }
+  }
+else
+  {
+  DEBUG(D_tls)
+    debug_printf("TLS: tls_verify_certificates not set or empty, ignoring\n");
+  return OK;
+  }
 
-  if (!S_ISREG(statbuf.st_mode))
-    {
-    DEBUG(D_tls)
-      debug_printf("verify certificates path is not a file: \"%s\"\n%s\n",
-          state->exp_tls_verify_certificates,
-          S_ISDIR(statbuf.st_mode)
-            ? " it's a directory, that's OpenSSL, this is GnuTLS"
-            : " (not a directory either)");
-    log_write(0, LOG_MAIN|LOG_PANIC,
-        "tls_verify_certificates \"%s\" is not a file",
+if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
+      "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
+      strerror(errno));
+  return DEFER;
+  }
+
+/* The test suite passes in /dev/null; we could check for that path explicitly,
+but who knows if someone has some weird FIFO which always dumps some certs, or
+other weirdness.  The thing we really want to check is that it's not a
+directory, since while OpenSSL supports that, GnuTLS does not.
+So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */
+if (S_ISDIR(statbuf.st_mode))
+  {
+  DEBUG(D_tls)
+    debug_printf("verify certificates path is a dir: \"%s\"\n",
         state->exp_tls_verify_certificates);
-    return DEFER;
-    }
+  log_write(0, LOG_MAIN|LOG_PANIC,
+      "tls_verify_certificates \"%s\" is a directory",
+      state->exp_tls_verify_certificates);
+  return DEFER;
+  }
 
-  DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
-          state->exp_tls_verify_certificates, statbuf.st_size);
+DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+        state->exp_tls_verify_certificates, statbuf.st_size);
 
-  /* If the CA cert file is empty, there's no point in loading the CRL file,
-  as we aren't verifying, so checking for revocation is pointless. */
+if (statbuf.st_size == 0)
+  {
+  DEBUG(D_tls)
+    debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
+  return OK;
+  }
 
-  if (statbuf.st_size > 0)
-    {
-    if (setit_vc)
-      {
-      cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
-          CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
-      if (cert_count < 0)
-        {
-        rc = cert_count;
-        exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
-        }
-      DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
-      }
+cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
+    CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
+if (cert_count < 0)
+  {
+  rc = cert_count;
+  exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
+  }
+DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
 
-    if (setit_crl && state->tls_crl && *state->tls_crl)
-      {
-      if (state->exp_tls_crl && *state->exp_tls_crl)
-        {
-        DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl);
-        rc = gnutls_certificate_set_x509_crl_file(state->x509_cred,
-            CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM);
-        exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
-        }
-      }
-    } /* statbuf.st_size */
-  } /* tls_verify_certificates */
+if (state->tls_crl && *state->tls_crl &&
+    state->exp_tls_crl && *state->exp_tls_crl)
+  {
+  DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl);
+  cert_count = gnutls_certificate_set_x509_crl_file(state->x509_cred,
+      CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM);
+  if (cert_count < 0)
+    {
+    rc = cert_count;
+    exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+    }
+  DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
+  }
 
 return OK;
-/* also above, during verify_certificates/crl, during SNI, if unchanged */
 }
 
 
 
 
+/*************************************************
+*          Set X.509 state variables             *
+*************************************************/
+
+/* In GnuTLS, the registered cert/key are not replaced by a later
+set of a cert/key, so for SNI support we need a whole new x509_cred
+structure.  Which means various other non-re-expanded pieces of state
+need to be re-set in the new struct, so the setting logic is pulled
+out to this.
+
+Arguments:
+  state           exim_gnutls_state_st *
+
+Returns:          OK/DEFER/FAIL
+*/
+
+static int
+tls_set_remaining_x509(exim_gnutls_state_st *state)
+{
+int rc;
+const host_item *host = state->host;  /* macro should be reconsidered? */
+
+/* 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. */
+
+if (!state->host)
+  {
+  if (!dh_server_params)
+    {
+    rc = init_server_dh();
+    if (rc != OK) return rc;
+    }
+  gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
+  }
+
+/* Link the credentials to the session. */
+
+rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
+exim_gnutls_err_check(US"gnutls_credentials_set");
+
+return OK;
+}
+
 /*************************************************
 *            Initialize for GnuTLS               *
 *************************************************/
@@ -760,6 +889,7 @@ Arguments:
   cas             CA certs file
   crl             CRL file
   require_ciphers tls_require_ciphers setting
+  caller_state    returned state-info structure
 
 Returns:          OK/DEFER/FAIL
 */
@@ -786,6 +916,19 @@ if (!exim_gnutls_base_init_done)
   {
   DEBUG(D_tls) debug_printf("GnuTLS global init required.\n");
 
+#ifdef HAVE_GNUTLS_PKCS11
+  /* By default, gnutls_global_init will init PKCS11 support in auto mode,
+  which loads modules from a config file, which sounds good and may be wanted
+  by some sysadmin, but also means in common configurations that GNOME keyring
+  environment variables are used and so breaks for users calling mailq.
+  To prevent this, we init PKCS11 first, which is the documented approach. */
+  if (!gnutls_enable_pkcs11)
+    {
+    rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+    exim_gnutls_err_check(US"gnutls_pkcs11_init");
+    }
+#endif
+
   rc = gnutls_global_init();
   exim_gnutls_err_check(US"gnutls_global_init");
 
@@ -805,6 +948,7 @@ if (host)
   {
   state = &state_client;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+  state->tlsp = &tls_out;
   DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
   rc = gnutls_init(&state->session, GNUTLS_CLIENT);
   }
@@ -812,6 +956,7 @@ else
   {
   state = &state_server;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+  state->tlsp = &tls_in;
   DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
   rc = gnutls_init(&state->session, GNUTLS_SERVER);
   }
@@ -821,13 +966,11 @@ state->host = host;
 
 state->tls_certificate = certificate;
 state->tls_privatekey = privatekey;
+state->tls_require_ciphers = require_ciphers;
 state->tls_sni = sni;
 state->tls_verify_certificates = cas;
 state->tls_crl = crl;
 
-rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
-
 /* This handles the variables that might get re-expanded after TLS SNI;
 that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
 
@@ -836,26 +979,16 @@ DEBUG(D_tls)
 rc = tls_expand_session_files(state);
 if (rc != OK) return rc;
 
-/* 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. */
+/* These are all other parts of the x509_cred handling, since SNI in GnuTLS
+requires a new structure afterwards. */
 
-if (!host)
-  {
-  rc = init_server_dh();
-  if (rc != OK) return rc;
-  gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
-  }
-
-/* Link the credentials to the session. */
-
-rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-exim_gnutls_err_check(US"gnutls_credentials_set");
+rc = tls_set_remaining_x509(state);
+if (rc != OK) return rc;
 
 /* set SNI in client, only */
 if (host)
   {
-  if (!expand_check_tlsvar(tls_sni))
+  if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni))
     return DEFER;
   if (state->exp_tls_sni && *state->exp_tls_sni)
     {
@@ -872,7 +1005,7 @@ else if (state->tls_sni)
       "have an SNI set for a client [%s]\n", state->tls_sni);
 
 /* This is the priority string support,
-http://www.gnu.org/software/gnutls/manual/html_node/Priority-Strings.html
+http://www.gnutls.org/manual/html_node/Priority-Strings.html
 and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
 This was backwards incompatible, but means Exim no longer needs to track
 all algorithms and provide string forms for them. */
@@ -896,6 +1029,9 @@ if (state->tls_require_ciphers && *state->tls_require_ciphers)
   }
 if (want_default_priorities)
   {
+  DEBUG(D_tls)
+    debug_printf("GnuTLS using default session cipher/priority \"%s\"\n",
+        exim_default_gnutls_priority);
   rc = gnutls_priority_init(&state->priority_cache,
       exim_default_gnutls_priority, &errpos);
   p = US exim_default_gnutls_priority;
@@ -923,8 +1059,6 @@ if (gnutls_compat_mode)
   }
 
 *caller_state = state;
-/* needs to happen before callbacks during handshake */
-current_global_tls_state = state;
 return OK;
 }
 
@@ -936,7 +1070,22 @@ return OK;
 *************************************************/
 
 /* Called from both server and client code.
-Only this is allowed to set state->peerdn and we use that to detect double-calls.
+Only this is allowed to set state->peerdn and state->have_set_peerdn
+and we use that to detect double-calls.
+
+NOTE: the state blocks last while the TLS connection is up, which is fine
+for logging in the server side, but for the client side, we log after teardown
+in src/deliver.c.  While the session is up, we can twist about states and
+repoint tls_* globals, but those variables used for logging or other variable
+expansion that happens _after_ delivery need to have a longer life-time.
+
+So for those, we get the data from POOL_PERM; the re-invoke guard keeps us from
+doing this more than once per generation of a state context.  We set them in
+the state context, and repoint tls_* to them.  After the state goes away, the
+tls_* copies of the pointers remain valid and client delivery logging is happy.
+
+tls_certificate_verified is a BOOL, so the tls_peerdn and tls_cipher issues
+don't apply.
 
 Arguments:
   state           exim_gnutls_state_st *
@@ -947,24 +1096,54 @@ Returns:          OK/DEFER/FAIL
 static int
 peer_status(exim_gnutls_state_st *state)
 {
+uschar cipherbuf[256];
 const gnutls_datum *cert_list;
-int rc;
+int old_pool, rc;
 unsigned int cert_list_size = 0;
+gnutls_protocol_t protocol;
+gnutls_cipher_algorithm_t cipher;
+gnutls_kx_algorithm_t kx;
+gnutls_mac_algorithm_t mac;
 gnutls_certificate_type_t ct;
 gnutls_x509_crt_t crt;
-uschar *dn_buf;
+uschar *p, *dn_buf;
 size_t sz;
 
-if (state->peerdn)
+if (state->have_set_peerdn)
   return OK;
+state->have_set_peerdn = TRUE;
 
-state->peerdn = US"unknown";
+state->peerdn = NULL;
+
+/* tls_cipher */
+cipher = gnutls_cipher_get(state->session);
+protocol = gnutls_protocol_get_version(state->session);
+mac = gnutls_mac_get(state->session);
+kx = gnutls_kx_get(state->session);
+
+string_format(cipherbuf, sizeof(cipherbuf),
+    "%s:%s:%d",
+    gnutls_protocol_get_name(protocol),
+    gnutls_cipher_suite_get_name(kx, cipher, mac),
+    (int) gnutls_cipher_get_key_size(cipher) * 8);
+
+/* I don't see a way that spaces could occur, in the current GnuTLS
+code base, but it was a concern in the old code and perhaps older GnuTLS
+releases did return "TLS 1.0"; play it safe, just in case. */
+for (p = cipherbuf; *p != '\0'; ++p)
+  if (isspace(*p))
+    *p = '-';
+old_pool = store_pool;
+store_pool = POOL_PERM;
+state->ciphersuite = string_copy(cipherbuf);
+store_pool = old_pool;
+state->tlsp->cipher = state->ciphersuite;
 
+/* tls_peerdn */
 cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
 
 if (cert_list == NULL || cert_list_size == 0)
   {
-  state->peerdn = US"unknown (no certificate)";
   DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
       cert_list, cert_list_size);
   if (state->verify_requirement == VERIFY_REQUIRED)
@@ -977,7 +1156,6 @@ ct = gnutls_certificate_type_get(state->session);
 if (ct != GNUTLS_CRT_X509)
   {
   const char *ctn = gnutls_certificate_type_get_name(ct);
-  state->peerdn = string_sprintf("unknown (type %s)", ctn);
   DEBUG(D_tls)
     debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
   if (state->verify_requirement == VERIFY_REQUIRED)
@@ -1064,7 +1242,7 @@ if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
 
   DEBUG(D_tls)
     debug_printf("TLS certificate verification failed (%s): peerdn=%s\n",
-        *error, state->peerdn);
+        *error, state->peerdn ? state->peerdn : US"<unset>");
 
   if (state->verify_requirement == VERIFY_REQUIRED)
     {
@@ -1072,15 +1250,16 @@ if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
     return FALSE;
     }
   DEBUG(D_tls)
-    debug_printf("TLS verify failure overriden (host in tls_try_verify_hosts)\n");
+    debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
   }
 else
   {
   state->peer_cert_verified = TRUE;
-  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n", state->peerdn);
+  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n",
+      state->peerdn ? state->peerdn : US"<unset>");
   }
 
-tls_peerdn = state->peerdn;
+state->tlsp->peerdn = state->peerdn;
 
 return TRUE;
 }
@@ -1095,11 +1274,20 @@ return TRUE;
  *   gnutls_global_set_log_function()
  *   gnutls_global_set_log_level() 0..9
  */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
 static void
 exim_gnutls_logger_cb(int level, const char *message)
 {
-  DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s\n", level, message);
+  size_t len = strlen(message);
+  if (len < 1)
+    {
+    DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+    return;
+    }
+  DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+      message[len-1] == '\n' ? "" : "\n");
 }
+#endif
 
 
 /* Called after client hello, should handle SNI work.
@@ -1115,6 +1303,7 @@ handshake.".
 
 For inability to get SNI information, we return 0.
 We only return non-zero if re-setup failed.
+Only used for server-side TLS.
 */
 
 static int
@@ -1122,12 +1311,23 @@ exim_sni_handling_cb(gnutls_session_t session)
 {
 char sni_name[MAX_HOST_LEN];
 size_t data_len = MAX_HOST_LEN;
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = &state_server;
 unsigned int sni_type;
 int rc, old_pool;
 
 rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
-exim_gnutls_err_debugreturn0("gnutls_server_name_get()");
+if (rc != GNUTLS_E_SUCCESS)
+  {
+  DEBUG(D_tls) {
+    if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+      debug_printf("TLS: no SNI presented in handshake.\n");
+    else
+      debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n",
+        gnutls_strerror(rc), rc);
+  };
+  return 0;
+  }
+
 if (sni_type != GNUTLS_NAME_DNS)
   {
   DEBUG(D_tls) debug_printf("TLS: ignoring SNI of unhandled type %u\n", sni_type);
@@ -1141,7 +1341,7 @@ state->received_sni = string_copyn(US sni_name, data_len);
 store_pool = old_pool;
 
 /* We set this one now so that variable expansions below will work */
-tls_sni = state->received_sni;
+state->tlsp->sni = state->received_sni;
 
 DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", sni_name,
     state->trigger_sni_changes ? "" : " (unused for certificate selection)");
@@ -1157,8 +1357,10 @@ if (rc != OK)
   return GNUTLS_E_APPLICATION_ERROR_MIN;
   }
 
-rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-return (rc == GNUTLS_E_SUCCESS) ? 0 : rc;
+rc = tls_set_remaining_x509(state);
+if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
+
+return 0;
 }
 
 
@@ -1195,9 +1397,7 @@ const char *error;
 exim_gnutls_state_st *state = NULL;
 
 /* Check for previous activation */
-/* nb: this will not be TLS callout safe, needs reworking as part of that. */
-
-if (tls_active >= 0)
+if (tls_in.active >= 0)
   {
   tls_error(US"STARTTLS received after TLS started", "", NULL);
   smtp_printf("554 Already in TLS\r\n");
@@ -1248,10 +1448,10 @@ make them disconnect. We need to have an explicit fflush() here, to force out
 the response. Other smtp_printf() calls do not need it, because in non-TLS
 mode, the fflush() happens when smtp_getc() is called. */
 
-if (!tls_on_connect)
+if (!state->tlsp->on_connect)
   {
   smtp_printf("220 TLS go ahead\r\n");
-  fflush(smtp_out);
+  fflush(smtp_out);            /*XXX JGH */
   }
 
 /* Now negotiate the TLS session. We put our own timer on it, since it seems
@@ -1268,7 +1468,8 @@ if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 do
   {
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+  } while ((rc == GNUTLS_E_AGAIN) ||
+      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
@@ -1317,7 +1518,7 @@ if (rc != OK) return rc;
 
 /* Sets various Exim expansion variables; always safe within server */
 
-extract_exim_vars_from_tls_state(state);
+extract_exim_vars_from_tls_state(state, TRUE);
 
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize appropriately. */
@@ -1346,13 +1547,13 @@ Arguments:
   fd                the fd of the connection
   host              connected host (for messages)
   addr              the first address (not used)
-  dhparam           DH parameter file (ignored, we're a client)
   certificate       certificate file
   privatekey        private key file
   sni               TLS SNI to send to remote host
   verify_certs      file for certificate verify
   verify_crl        CRL for verify
   require_ciphers   list of allowed ciphers or NULL
+  dh_min_bits       minimum number of bits acceptable in server's DH prime
   timeout           startup timeout
 
 Returns:            OK/DEFER/FAIL (because using common functions),
@@ -1361,10 +1562,14 @@ Returns:            OK/DEFER/FAIL (because using common functions),
 
 int
 tls_client_start(int fd, host_item *host,
-    address_item *addr ARG_UNUSED, uschar *dhparam ARG_UNUSED,
+    address_item *addr ARG_UNUSED,
     uschar *certificate, uschar *privatekey, uschar *sni,
     uschar *verify_certs, uschar *verify_crl,
-    uschar *require_ciphers, int timeout)
+    uschar *require_ciphers,
+#ifdef EXPERIMENTAL_OCSP
+    uschar *require_ocsp ARG_UNUSED,
+#endif
+    int dh_min_bits, int timeout)
 {
 int rc;
 const char *error;
@@ -1376,7 +1581,17 @@ rc = tls_init(host, certificate, privatekey,
     sni, verify_certs, verify_crl, require_ciphers, &state);
 if (rc != OK) return rc;
 
-gnutls_dh_set_prime_bits(state->session, EXIM_CLIENT_DH_MIN_BITS);
+if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
+  {
+  DEBUG(D_tls)
+    debug_printf("WARNING: tls_dh_min_bits far too low, clamping %d up to %d\n",
+        dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
+  dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
+  }
+
+DEBUG(D_tls) debug_printf("Setting D-H prime minimum acceptable bits to %d\n",
+    dh_min_bits);
+gnutls_dh_set_prime_bits(state->session, dh_min_bits);
 
 if (verify_certs == NULL)
   {
@@ -1403,9 +1618,14 @@ alarm(timeout);
 do
   {
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+  } while ((rc == GNUTLS_E_AGAIN) ||
+      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
 alarm(0);
 
+if (rc != GNUTLS_E_SUCCESS)
+  return tls_error(US"gnutls_handshake",
+      sigalrm_seen ? "timed out" : gnutls_strerror(rc), state->host);
+
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
 /* Verify late */
@@ -1419,9 +1639,9 @@ if (state->verify_requirement != VERIFY_NONE &&
 rc = peer_status(state);
 if (rc != OK) return rc;
 
-/* Sets various Exim expansion variables; always safe within server */
+/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
 
-extract_exim_vars_from_tls_state(state);
+extract_exim_vars_from_tls_state(state, FALSE);
 
 return OK;
 }
@@ -1442,11 +1662,11 @@ Returns:     nothing
 */
 
 void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
 {
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
 
-if (tls_active < 0) return;  /* TLS was not active */
+if (!state->tlsp || state->tlsp->active < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
@@ -1456,6 +1676,7 @@ if (shutdown)
 
 gnutls_deinit(state->session);
 
+state->tlsp->active = -1;
 memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
 
 if ((state_server.session == NULL) && (state_client.session == NULL))
@@ -1464,7 +1685,6 @@ if ((state_server.session == NULL) && (state_client.session == NULL))
   exim_gnutls_base_init_done = FALSE;
   }
 
-tls_active = -1;
 }
 
 
@@ -1476,6 +1696,7 @@ tls_active = -1;
 
 /* This gets the next byte from the TLS input buffer. If the buffer is empty,
 it refills the buffer via the GnuTLS reading function.
+Only used by the server-side TLS.
 
 This feeds DKIM and should be used for all message-body reads.
 
@@ -1486,7 +1707,7 @@ Returns:    the next character or EOF
 int
 tls_getc(void)
 {
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = &state_server;
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
   {
   ssize_t inbytes;
@@ -1515,12 +1736,12 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 
     gnutls_deinit(state->session);
     state->session = NULL;
-    tls_active = -1;
-    tls_bits = 0;
-    tls_certificate_verified = FALSE;
-    tls_channelbinding_b64 = NULL;
-    tls_cipher = NULL;
-    tls_peerdn = NULL;
+    state->tlsp->active = -1;
+    state->tlsp->bits = 0;
+    state->tlsp->certificate_verified = FALSE;
+    tls_channelbinding_b64 = NULL;     /*XXX JGH */
+    state->tlsp->cipher = NULL;
+    state->tlsp->peerdn = NULL;
 
     return smtp_getc();
     }
@@ -1554,6 +1775,7 @@ return state->xfer_buffer[state->xfer_buffer_lwm++];
 
 /* This does not feed DKIM, so if the caller uses this for reading message body,
 then the caller must feed DKIM.
+
 Arguments:
   buff      buffer of data
   len       size of buffer
@@ -1563,9 +1785,9 @@ Returns:    the number of bytes read
 */
 
 int
-tls_read(uschar *buff, size_t len)
+tls_read(BOOL is_server, uschar *buff, size_t len)
 {
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
 ssize_t inbytes;
 
 if (len > INT_MAX)
@@ -1601,6 +1823,7 @@ return -1;
 
 /*
 Arguments:
+  is_server channel specifier
   buff      buffer of data
   len       number of bytes
 
@@ -1609,11 +1832,11 @@ Returns:    the number of bytes after a successful write,
 */
 
 int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
 {
 ssize_t outbytes;
 size_t left = len;
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
 
 DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
 while (left > 0)
@@ -1667,6 +1890,7 @@ Arguments:
 Returns     a random number in range [0, max-1]
 */
 
+#ifdef HAVE_GNUTLS_RND
 int
 vaguely_random_number(int max)
 {
@@ -1704,6 +1928,78 @@ for (p = smallbuf; needed_len; --needed_len, ++p)
  * smooth distribution and cares enough then they should submit a patch then. */
 return r % max;
 }
+#else /* HAVE_GNUTLS_RND */
+int
+vaguely_random_number(int max)
+{
+  return vaguely_random_number_fallback(max);
+}
+#endif /* HAVE_GNUTLS_RND */
+
+
+
+
+/*************************************************
+*  Let tls_require_ciphers be checked at startup *
+*************************************************/
+
+/* The tls_require_ciphers option, if set, must be something which the
+library can parse.
+
+Returns:     NULL on success, or error message
+*/
+
+uschar *
+tls_validate_require_cipher(void)
+{
+int rc;
+uschar *expciphers = NULL;
+gnutls_priority_t priority_cache;
+const char *errpos;
+
+#define validate_check_rc(Label) do { \
+  if (rc != GNUTLS_E_SUCCESS) { if (exim_gnutls_base_init_done) gnutls_global_deinit(); \
+  return string_sprintf("%s failed: %s", (Label), gnutls_strerror(rc)); } } while (0)
+#define return_deinit(Label) do { gnutls_global_deinit(); return (Label); } while (0)
+
+if (exim_gnutls_base_init_done)
+  log_write(0, LOG_MAIN|LOG_PANIC,
+      "already initialised GnuTLS, Exim developer bug");
+
+#ifdef HAVE_GNUTLS_PKCS11
+if (!gnutls_enable_pkcs11)
+  {
+  rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+  validate_check_rc(US"gnutls_pkcs11_init");
+  }
+#endif
+rc = gnutls_global_init();
+validate_check_rc(US"gnutls_global_init()");
+exim_gnutls_base_init_done = TRUE;
+
+if (!(tls_require_ciphers && *tls_require_ciphers))
+  return_deinit(NULL);
+
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+  return_deinit(US"failed to expand tls_require_ciphers");
+
+if (!(expciphers && *expciphers))
+  return_deinit(NULL);
+
+DEBUG(D_tls)
+  debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers);
+
+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));
+
+#undef return_deinit
+#undef validate_check_rc
+gnutls_global_deinit();
+
+return NULL;
+}