Move certificate name checking to mainline, default enabled
[exim.git] / src / src / tls-gnu.c
index 880aaeb14831565a9ae4b752bfe2506f687c2c72..b520ebfd84edcad4c49f2e8bca1831c7398aae09 100644 (file)
@@ -43,7 +43,21 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # include <gnutls/pkcs11.h>
 #endif
-#ifdef EXPERIMENTAL_OCSP
+#if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
+# warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile"
+# define DISABLE_OCSP
+#endif
+#if GNUTLS_VERSION_NUMBER < 0x020a00 && defined(EXPERIMENTAL_EVENT)
+# warning "GnuTLS library version too old; tls:cert event unsupported"
+# undef EXPERIMENTAL_EVENT
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030306
+# define SUPPORT_CA_DIR
+#else
+# undef  SUPPORT_CA_DIR
+#endif
+
+#ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
 
@@ -60,7 +74,8 @@ Changes:
 
 /* Values for verify_requirement */
 
-enum peer_verify_requirement { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
+enum peer_verify_requirement
+  { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
 
 /* This holds most state for server or client; with this, we can set up an
 outbound TLS-enabled connection in an ACL callout, while not stomping all
@@ -95,12 +110,17 @@ typedef struct exim_gnutls_state {
   const uschar *tls_verify_certificates;
   const uschar *tls_crl;
   const uschar *tls_require_ciphers;
+
   uschar *exp_tls_certificate;
   uschar *exp_tls_privatekey;
-  uschar *exp_tls_sni;
   uschar *exp_tls_verify_certificates;
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
+  uschar *exp_tls_ocsp_file;
+  uschar *exp_tls_verify_cert_hostnames;
+#ifdef EXPERIMENTAL_EVENT
+  uschar *event_action;
+#endif
 
   tls_support *tlsp;   /* set in tls_init() */
 
@@ -115,7 +135,11 @@ static const exim_gnutls_state_st exim_gnutls_state_init = {
   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, NULL, NULL, NULL, NULL, NULL,
+  NULL,
+#ifdef EXPERIMENTAL_EVENT
+                                            NULL,
+#endif
   NULL,
   NULL, 0, 0, 0, 0,
 };
@@ -127,7 +151,9 @@ context we're currently dealing with" pointer and rely upon being
 single-threaded to keep from processing data on an inbound TLS connection while
 talking to another TLS connection for an outbound check.  This does mean that
 there's no way for heart-beats to be responded to, for the duration of the
-second connection. */
+second connection.
+XXX But see gnutls_session_get_ptr()
+*/
 
 static exim_gnutls_state_st state_server, state_client;
 
@@ -157,18 +183,18 @@ static BOOL exim_gnutls_base_init_done = FALSE;
 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
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
 #endif
 
 #ifndef EXIM_CLIENT_DH_MIN_BITS
-#define EXIM_CLIENT_DH_MIN_BITS 1024
+# 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
+# define EXIM_SERVER_DH_BITS_PRE2_12 1024
 #endif
 
 #define exim_gnutls_err_check(Label) do { \
@@ -177,18 +203,18 @@ before, for now. */
 #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_SESSION_CHANNEL_BINDING
+# define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+# define HAVE_GNUTLS_RND
 /* The security fix we provide with the gnutls_allow_auto_pkcs11 option
  * (4.82 PP/09) introduces a compatibility regression. The symbol simply
  * isn't available sometimes, so this needs to become a conditional
  * compilation; the sanest way to deal with this being a problem on
  * older OSes is to block it in the Local/Makefile with this compiler
  * definition  */
-#ifndef AVOID_GNUTLS_PKCS11
-#define HAVE_GNUTLS_PKCS11
-#endif /* AVOID_GNUTLS_PKCS11 */
+# ifndef AVOID_GNUTLS_PKCS11
+#  define HAVE_GNUTLS_PKCS11
+# endif /* AVOID_GNUTLS_PKCS11 */
 #endif
 
 
@@ -203,6 +229,10 @@ static void exim_gnutls_logger_cb(int level, const char *message);
 
 static int exim_sni_handling_cb(gnutls_session_t session);
 
+#ifndef DISABLE_OCSP
+static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+  gnutls_datum_t * ocsp_response);
+#endif
 
 
 
@@ -235,7 +265,7 @@ tls_error(const uschar *prefix, const char *msg, const host_item *host)
 {
 if (host)
   {
-  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
+  log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s)%s%s",
       host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
   return FAIL;
   }
@@ -244,6 +274,7 @@ else
   uschar *conn_info = smtp_get_connection_info();
   if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
     conn_info += 5;
+  /* I'd like to get separated H= here, but too hard for now */
   log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
       conn_info, prefix, msg ? ": " : "", msg ? msg : "");
   return DEFER;
@@ -289,10 +320,16 @@ tls_error(when, msg, state->host);
 *        Set various Exim expansion vars         *
 *************************************************/
 
-#define exim_gnutls_cert_err(Label) do { \
-  if (rc != GNUTLS_E_SUCCESS) { \
-    DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \
-    return rc; } } while (0)
+#define exim_gnutls_cert_err(Label) \
+  do \
+    { \
+    if (rc != GNUTLS_E_SUCCESS) \
+      { \
+      DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", \
+       (Label), gnutls_strerror(rc)); \
+      return rc; \
+      } \
+    } while (0)
 
 static int
 import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp)
@@ -786,23 +823,23 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
 
 /* Set the OCSP stapling server info */
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if (  !host    /* server */
    && tls_ocsp_file
    )
   {
-  uschar * expanded;
-  int rc;
-
-  if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &expanded))
+  if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
+       &state->exp_tls_ocsp_file))
     return DEFER;
 
-  /* Lazy way; would like callback to emit debug on actual response */
+  /* Use the full callback method for stapling just to get observability.
+  More efficient would be to read the file once only, if it never changed
+  (due to SNI). Would need restart on file update, or watch datestamp.  */
+
+  gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
+    server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
 
-  rc = gnutls_certificate_set_ocsp_status_request_file(state->x509_cred,
-      expanded, 0);
-  exim_gnutls_err_check(US"gnutls_certificate_set_ocsp_status_request_file");
-  DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", expanded);
+  DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file);
   }
 #endif
 
@@ -845,6 +882,7 @@ if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
   return DEFER;
   }
 
+#ifndef SUPPORT_CA_DIR
 /* 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
@@ -860,6 +898,7 @@ if (S_ISDIR(statbuf.st_mode))
       state->exp_tls_verify_certificates);
   return DEFER;
   }
+#endif
 
 DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
         state->exp_tls_verify_certificates, statbuf.st_size);
@@ -871,8 +910,18 @@ if (statbuf.st_size == 0)
   return OK;
   }
 
-cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
+cert_count =
+
+#ifdef SUPPORT_CA_DIR
+  (statbuf.st_mode & S_IFMT) == S_IFDIR
+  ?
+  gnutls_certificate_set_x509_trust_dir(state->x509_cred,
+    CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM)
+  :
+#endif
+  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;
@@ -1058,15 +1107,15 @@ if (rc != OK) return rc;
 /* set SNI in client, only */
 if (host)
   {
-  if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni))
+  if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni))
     return DEFER;
-  if (state->exp_tls_sni && *state->exp_tls_sni)
+  if (state->tlsp->sni && *state->tlsp->sni)
     {
     DEBUG(D_tls)
-      debug_printf("Setting TLS client SNI to \"%s\"\n", state->exp_tls_sni);
-    sz = Ustrlen(state->exp_tls_sni);
+      debug_printf("Setting TLS client SNI to \"%s\"\n", state->tlsp->sni);
+    sz = Ustrlen(state->tlsp->sni);
     rc = gnutls_server_name_set(state->session,
-        GNUTLS_NAME_DNS, state->exp_tls_sni, sz);
+        GNUTLS_NAME_DNS, state->tlsp->sni, sz);
     exim_gnutls_err_check(US"gnutls_server_name_set");
     }
   }
@@ -1215,7 +1264,7 @@ if (cert_list == NULL || cert_list_size == 0)
   {
   DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
       cert_list, cert_list_size);
-  if (state->verify_requirement == VERIFY_REQUIRED)
+  if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification failed",
         "no certificate received from peer", state->host);
   return OK;
@@ -1227,17 +1276,23 @@ if (ct != GNUTLS_CRT_X509)
   const char *ctn = gnutls_certificate_type_get_name(ct);
   DEBUG(D_tls)
     debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
-  if (state->verify_requirement == VERIFY_REQUIRED)
+  if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification not possible, unhandled type",
         ctn, state->host);
   return OK;
   }
 
-#define exim_gnutls_peer_err(Label) do { \
-  if (rc != GNUTLS_E_SUCCESS) { \
-    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((Label), gnutls_strerror(rc), state->host); } \
-    return OK; } } while (0)
+#define exim_gnutls_peer_err(Label) \
+  do { \
+    if (rc != GNUTLS_E_SUCCESS) \
+      { \
+      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((Label), gnutls_strerror(rc), state->host); \
+      return OK; \
+      } \
+    } while (0)
 
 rc = import_cert(&cert_list[0], &crt);
 exim_gnutls_peer_err(US"cert 0");
@@ -1301,7 +1356,9 @@ else
 /* Handle the result of verification. INVALID seems to be set as well
 as REVOKED, but leave the test for both. */
 
-if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
+if (rc < 0 ||
+    verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
+   )
   {
   state->peer_cert_verified = FALSE;
   if (!*error)
@@ -1309,21 +1366,44 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
       ? "certificate revoked" : "certificate invalid";
 
   DEBUG(D_tls)
-    debug_printf("TLS certificate verification failed (%s): peerdn=%s\n",
+    debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
         *error, state->peerdn ? state->peerdn : US"<unset>");
 
-  if (state->verify_requirement == VERIFY_REQUIRED)
+  if (state->verify_requirement >= VERIFY_REQUIRED)
     {
-    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+    gnutls_alert_send(state->session,
+      GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
     return FALSE;
     }
   DEBUG(D_tls)
     debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
   }
+
 else
   {
+  if (state->exp_tls_verify_cert_hostnames)
+    {
+    int sep = 0;
+    uschar * list = state->exp_tls_verify_cert_hostnames;
+    uschar * name;
+    while (name = string_nextinlist(&list, &sep, NULL, 0))
+      if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
+       break;
+    if (!name)
+      {
+      DEBUG(D_tls)
+       debug_printf("TLS certificate verification failed: cert name mismatch\n");
+      if (state->verify_requirement >= VERIFY_REQUIRED)
+       {
+       gnutls_alert_send(state->session,
+         GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+       return FALSE;
+       }
+      return TRUE;
+      }
+    }
   state->peer_cert_verified = TRUE;
-  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n",
+  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
       state->peerdn ? state->peerdn : US"<unset>");
   }
 
@@ -1433,6 +1513,79 @@ return 0;
 
 
 
+#ifndef DISABLE_OCSP
+
+static int
+server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+  gnutls_datum_t * ocsp_response)
+{
+int ret;
+
+if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
+  {
+  DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
+                             (char *)ptr);
+  tls_in.ocsp = OCSP_NOT_RESP;
+  return GNUTLS_E_NO_CERTIFICATE_STATUS;
+  }
+
+tls_in.ocsp = OCSP_VFY_NOT_TRIED;
+return 0;
+}
+
+#endif
+
+
+#ifdef EXPERIMENTAL_EVENT
+/*
+We use this callback to get observability and detail-level control
+for an exim TLS connection (either direction), raising a tls:cert event
+for each cert in the chain presented by the peer.  Any event
+can deny verification.
+
+Return 0 for the handshake to continue or non-zero to terminate.
+*/
+
+static int
+verify_cb(gnutls_session_t session)
+{
+const gnutls_datum * cert_list;
+unsigned int cert_list_size = 0;
+gnutls_x509_crt_t crt;
+int rc;
+uschar * yield;
+exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
+
+cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+if (cert_list)
+  while (cert_list_size--)
+  {
+  rc = import_cert(&cert_list[cert_list_size], &crt);
+  if (rc != GNUTLS_E_SUCCESS)
+    {
+    DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
+      cert_list_size, gnutls_strerror(rc));
+    break;
+    }
+
+  state->tlsp->peercert = crt;
+  if ((yield = event_raise(state->event_action,
+             US"tls:cert", string_sprintf("%d", cert_list_size))))
+    {
+    log_write(0, LOG_MAIN,
+             "SSL verify denied by event-action: depth=%d: %s",
+             cert_list_size, yield);
+    return 1;                     /* reject */
+    }
+  state->tlsp->peercert = NULL;
+  }
+
+return 0;
+}
+
+#endif
+
+
 
 /* ------------------------------------------------------------------------ */
 /* Exported functions */
@@ -1487,23 +1640,35 @@ optional, set up appropriately. */
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
-  DEBUG(D_tls) debug_printf("TLS: a client certificate will be required.\n");
+  DEBUG(D_tls)
+    debug_printf("TLS: a client certificate will be required.\n");
   state->verify_requirement = VERIFY_REQUIRED;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
-  DEBUG(D_tls) debug_printf("TLS: a client certificate will be requested but not required.\n");
+  DEBUG(D_tls)
+    debug_printf("TLS: a client certificate will be requested but not required.\n");
   state->verify_requirement = VERIFY_OPTIONAL;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
   }
 else
   {
-  DEBUG(D_tls) debug_printf("TLS: a client certificate will not be requested.\n");
+  DEBUG(D_tls)
+    debug_printf("TLS: a client certificate will not be requested.\n");
   state->verify_requirement = VERIFY_NONE;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
   }
 
+#ifdef EXPERIMENTAL_EVENT
+if (event_action)
+  {
+  state->event_action = event_action;
+  gnutls_session_set_ptr(state->session, state);
+  gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
+  }
+#endif
+
 /* Register SNI handling; always, even if not in tls_certificate, so that the
 expansion variable $tls_sni is always available. */
 
@@ -1526,8 +1691,8 @@ if (!state->tlsp->on_connect)
 that the GnuTLS library doesn't. */
 
 gnutls_transport_set_ptr2(state->session,
-    (gnutls_transport_ptr)fileno(smtp_in),
-    (gnutls_transport_ptr)fileno(smtp_out));
+    (gnutls_transport_ptr)(long) fileno(smtp_in),
+    (gnutls_transport_ptr)(long) fileno(smtp_out));
 state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
@@ -1600,6 +1765,20 @@ return OK;
 
 
 
+static void
+tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
+  smtp_transport_options_block * ob)
+{
+if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+  {
+  state->exp_tls_verify_cert_hostnames = host->name;
+  DEBUG(D_tls)
+    debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
+                   state->exp_tls_verify_cert_hostnames);
+  }
+}
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -1610,17 +1789,7 @@ Arguments:
   fd                the fd of the connection
   host              connected host (for messages)
   addr              the first address (not used)
-  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
-  hosts_require_ocsp hosts for which to request certificate-status (OCSP)
-  dh_min_bits       minimum number of bits acceptable in server's DH prime
-  timeout           startup timeout
-  verify_hosts      mandatory client verification 
-  try_verify_hosts  optional client verification
+  tb                transport (always smtp)
 
 Returns:            OK/DEFER/FAIL (because using common functions),
                     but for a client, DEFER and FAIL have the same meaning
@@ -1629,83 +1798,104 @@ Returns:            OK/DEFER/FAIL (because using common functions),
 int
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
-    uschar *certificate, uschar *privatekey, uschar *sni,
-    uschar *verify_certs, uschar *verify_crl,
-    uschar *require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
-    uschar *hosts_require_ocsp,
+    transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+    , dne_answer * unused_tlsa_dnsa
 #endif
-    int dh_min_bits, int timeout,
-    uschar *verify_hosts, uschar *try_verify_hosts)
+    )
 {
+smtp_transport_options_block *ob =
+  (smtp_transport_options_block *)tb->options_block;
 int rc;
 const char *error;
 exim_gnutls_state_st *state = NULL;
-#ifdef EXPERIMENTAL_OCSP
-BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp,
-  NULL, host->name, host->address, NULL) == OK;
+#ifndef DISABLE_OCSP
+BOOL require_ocsp =
+  verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+BOOL request_ocsp = require_ocsp ? TRUE
+  : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
 
-if ((rc = tls_init(host, certificate, privatekey,
-    sni, verify_certs, verify_crl, require_ciphers, &state)) != OK)
+if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+    ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
+    ob->tls_require_ciphers, &state)) != OK)
   return rc;
 
-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;
-  }
+  int dh_min_bits = ob->tls_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);
+  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);
+  }
 
 /* Stick to the old behaviour for compatibility if tls_verify_certificates is 
 set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
 the specified host patterns if one of them is defined */
 
-if ((  state->exp_tls_verify_certificates
-    && !verify_hosts
-    && !try_verify_hosts
-    )
-    ||
-    verify_check_host(&verify_hosts) == OK
+if (  (  state->exp_tls_verify_certificates
+      && !ob->tls_verify_hosts
+      && !ob->tls_try_verify_hosts
+      )
+    || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
    )
   {
-  DEBUG(D_tls) debug_printf("TLS: server certificate verification required.\n");
+  tls_client_setup_hostname_checks(host, state, ob);
+  DEBUG(D_tls)
+    debug_printf("TLS: server certificate verification required.\n");
   state->verify_requirement = VERIFY_REQUIRED;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
   }
-else if (verify_check_host(&try_verify_hosts) == OK)
+else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
   {
-  DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n");
+  tls_client_setup_hostname_checks(host, state, ob);
+  DEBUG(D_tls)
+    debug_printf("TLS: server certificate verification optional.\n");
   state->verify_requirement = VERIFY_OPTIONAL;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
   }
 else
   {
-  DEBUG(D_tls) debug_printf("TLS: server certificate verification not required.\n");
+  DEBUG(D_tls)
+    debug_printf("TLS: server certificate verification not required.\n");
   state->verify_requirement = VERIFY_NONE;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
   }
 
-#ifdef EXPERIMENTAL_OCSP       /* since GnuTLS 3.1.3 */
-if (require_ocsp)
+#ifndef DISABLE_OCSP
+                       /* supported since GnuTLS 3.1.3 */
+if (request_ocsp)
   {
   DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
-  rc = gnutls_ocsp_status_request_enable_client(state->session,
-                   NULL, 0, NULL);
-  if (rc != OK)
+  if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
+                   NULL, 0, NULL)) != OK)
     return tls_error(US"cert-status-req",
                    gnutls_strerror(rc), state->host);
+  tls_out.ocsp = OCSP_NOT_RESP;
   }
 #endif
 
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd);
+#ifdef EXPERIMENTAL_EVENT
+if (tb->event_action)
+  {
+  state->event_action = tb->event_action;
+  gnutls_session_set_ptr(state->session, state);
+  gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
+  }
+#endif
+
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd);
 state->fd_in = fd;
 state->fd_out = fd;
 
@@ -1713,7 +1903,7 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
 /* There doesn't seem to be a built-in timeout on connection. */
 
 sigalrm_seen = FALSE;
-alarm(timeout);
+alarm(ob->command_timeout);
 do
   {
   rc = gnutls_handshake(state->session);
@@ -1733,7 +1923,7 @@ if (state->verify_requirement != VERIFY_NONE &&
     !verify_certificate(state, &error))
   return tls_error(US"certificate verification failed", error, state->host);
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if (require_ocsp)
   {
   DEBUG(D_tls)
@@ -1747,17 +1937,20 @@ if (require_ocsp)
        && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0
        )
       {
-      fprintf(stderr, "%.4096s", printed.data);
+      debug_printf("%.4096s", printed.data);
       gnutls_free(printed.data);
       }
     else
       (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host);
     }
 
-  fprintf(stderr, "%s: checking ocsp\n", __FUNCTION__);
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
+    {
+    tls_out.ocsp = OCSP_FAILED;
     return tls_error(US"certificate status check failed", NULL, state->host);
+    }
   DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
+  tls_out.ocsp = OCSP_VFIED;
   }
 #endif