Consolidate base64 encode/decode routines.
[exim.git] / src / src / tls-gnu.c
index d0e1c35d77af1036112b658096cc25937a9aea08..601197475c71ca424f372f7e236ba3a29529779b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -43,7 +43,24 @@ 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(DISABLE_EVENT)
+# warning "GnuTLS library version too old; tls:cert event unsupported"
+# define DISABLE_EVENT
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030306
+# define SUPPORT_CA_DIR
+#else
+# undef  SUPPORT_CA_DIR
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030014
+# define SUPPORT_SYSDEFAULT_CABUNDLE
+#endif
+
+#ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
 
@@ -60,7 +77,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 +113,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;
+  const uschar *exp_tls_verify_cert_hostnames;
+#ifndef DISABLE_EVENT
+  uschar *event_action;
+#endif
 
   tls_support *tlsp;   /* set in tls_init() */
 
@@ -115,7 +138,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,
+#ifndef DISABLE_EVENT
+                                            NULL,
+#endif
   NULL,
   NULL, 0, 0, 0, 0,
 };
@@ -127,7 +154,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;
 
@@ -147,6 +176,10 @@ static const char * const exim_default_gnutls_priority = "NORMAL";
 
 static BOOL exim_gnutls_base_init_done = FALSE;
 
+#ifndef DISABLE_OCSP
+static BOOL gnutls_buggy_ocsp = FALSE;
+#endif
+
 
 /* ------------------------------------------------------------------------ */
 /* macros */
@@ -157,18 +190,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 +210,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 +236,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 +272,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 +281,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,13 +327,19 @@ 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)
+import_cert(const gnutls_datum_t * cert, gnutls_x509_crt_t * crtp)
 {
 int rc;
 
@@ -369,7 +413,7 @@ if (rc) {
 } else {
   old_pool = store_pool;
   store_pool = POOL_PERM;
-  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+  tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size);
   store_pool = old_pool;
   DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
 }
@@ -381,7 +425,7 @@ tlsp->sni =    state->received_sni;
 
 /* record our certificate */
   {
-  const gnutls_datum * cert = gnutls_certificate_get_ours(state->session);
+  const gnutls_datum_t * cert = gnutls_certificate_get_ours(state->session);
   gnutls_x509_crt_t crt;
 
   tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
@@ -413,7 +457,7 @@ init_server_dh(void)
 {
 int fd, rc;
 unsigned int dh_bits;
-gnutls_datum m;
+gnutls_datum_t m;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
@@ -786,23 +830,30 @@ 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 (gnutls_buggy_ocsp)
+    {
+    DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+    }
+  else
+    {
+    if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
+         &state->exp_tls_ocsp_file))
+      return DEFER;
 
-  if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &expanded))
-    return DEFER;
+    /* 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.  */
 
-  /* Lazy way; would like callback to emit debug on actual response */
+    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("OCSP response file = %s\n", state->exp_tls_ocsp_file);
+    }
   }
 #endif
 
@@ -817,6 +868,10 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
   if (!expand_check_tlsvar(tls_verify_certificates))
     return DEFER;
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+  if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+    state->exp_tls_verify_certificates = NULL;
+#endif
   if (state->tls_crl && *state->tls_crl)
     if (!expand_check_tlsvar(tls_crl))
       return DEFER;
@@ -837,46 +892,65 @@ else
   return OK;
   }
 
-if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+#ifdef SUPPORT_SYSDEFAULT_CABUNDLE
+if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+  cert_count = gnutls_certificate_set_x509_system_trust(state->x509_cred);
+else
+#endif
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
-      "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
-      strerror(errno));
-  return DEFER;
-  }
+  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);
-  log_write(0, LOG_MAIN|LOG_PANIC,
-      "tls_verify_certificates \"%s\" is a directory",
-      state->exp_tls_verify_certificates);
-  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
+  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);
+    log_write(0, LOG_MAIN|LOG_PANIC,
+       "tls_verify_certificates \"%s\" is a directory",
+       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);
+  DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+         state->exp_tls_verify_certificates, statbuf.st_size);
 
-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)
+    {
+    DEBUG(D_tls)
+      debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
+    return OK;
+    }
+
+  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);
   }
 
-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");
+  exim_gnutls_err_check(US"setting certificate trust");
   }
 DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
 
@@ -948,6 +1022,38 @@ return OK;
 *            Initialize for GnuTLS               *
 *************************************************/
 
+
+#ifndef DISABLE_OCSP
+
+static BOOL
+tls_is_buggy_ocsp(void)
+{
+const uschar * s;
+uschar maj, mid, mic;
+
+s = CUS gnutls_check_version(NULL);
+maj = atoi(CCS s);
+if (maj == 3)
+  {
+  while (*s && *s != '.') s++;
+  mid = atoi(CCS ++s);
+  if (mid <= 2)
+    return TRUE;
+  else if (mid >= 5)
+    return FALSE;
+  else
+    {
+    while (*s && *s != '.') s++;
+    mic = atoi(CCS ++s);
+    return mic <= (mid == 3 ? 16 : 3);
+    }
+  }
+return FALSE;
+}
+
+#endif
+
+
 /* Called from both server and client code. In the case of a server, errors
 before actual TLS negotiation return DEFER.
 
@@ -1011,6 +1117,11 @@ if (!exim_gnutls_base_init_done)
     }
 #endif
 
+#ifndef DISABLE_OCSP
+  if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp()))
+    log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version");
+#endif
+
   exim_gnutls_base_init_done = TRUE;
   }
 
@@ -1058,15 +1169,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");
     }
   }
@@ -1166,7 +1277,7 @@ static int
 peer_status(exim_gnutls_state_st *state)
 {
 uschar cipherbuf[256];
-const gnutls_datum *cert_list;
+const gnutls_datum_t *cert_list;
 int old_pool, rc;
 unsigned int cert_list_size = 0;
 gnutls_protocol_t protocol;
@@ -1215,7 +1326,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 +1338,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 +1418,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 +1428,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;
+    const 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 +1575,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
+
+
+#ifndef DISABLE_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_t * 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 +1702,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);
   }
 
+#ifndef DISABLE_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 +1753,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_t)(long) fileno(smtp_in),
+    (gnutls_transport_ptr_t)(long) fileno(smtp_out));
 state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
@@ -1600,6 +1827,25 @@ 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 =
+#ifdef SUPPORT_I18N
+    string_domain_utf8_to_alabel(host->name, NULL);
+#else
+    host->name;
+#endif
+  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,7 +1856,7 @@ Arguments:
   fd                the fd of the connection
   host              connected host (for messages)
   addr              the first address (not used)
-  ob                smtp transport options
+  tb                transport (always smtp)
 
 Returns:            OK/DEFER/FAIL (because using common functions),
                     but for a client, DEFER and FAIL have the same meaning
@@ -1619,15 +1865,22 @@ Returns:            OK/DEFER/FAIL (because using common functions),
 int
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
-    void *v_ob)
+    transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+    , dne_answer * unused_tlsa_dnsa
+#endif
+    )
 {
-smtp_transport_options_block *ob = v_ob;
+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(&ob->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);
@@ -1654,47 +1907,62 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
   }
 
-/* Stick to the old behaviour for compatibility if tls_verify_certificates is 
+/* 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
-    && !ob->tls_verify_hosts
-    && !ob->tls_try_verify_hosts
-    )
-    ||
-    verify_check_host(&ob->tls_verify_hosts) == OK
+if (  (  state->exp_tls_verify_certificates
+      && !ob->tls_verify_hosts
+      && (!ob->tls_try_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(&ob->tls_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");
   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
+
+#ifndef DISABLE_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)fd);
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
 state->fd_in = fd;
 state->fd_out = fd;
 
@@ -1722,7 +1990,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)
@@ -1744,8 +2012,12 @@ if (require_ocsp)
     }
 
   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