Expanded EXPERIMENTAL_TPDA feature
[exim.git] / src / src / tls-openssl.c
index 2f08e43c66cd887e6d1fb74bf73bfd6168958f2e..c031b8e4d10f0013387d4df1efc80acef493d76a 100644 (file)
@@ -22,17 +22,22 @@ functions from the OpenSSL library. */
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
-#ifdef EXPERIMENTAL_OCSP
-#include <openssl/ocsp.h>
+#ifndef DISABLE_OCSP
+# include <openssl/ocsp.h>
 #endif
 
-#ifdef EXPERIMENTAL_OCSP
-#define EXIM_OCSP_SKEW_SECONDS (300L)
-#define EXIM_OCSP_MAX_AGE (-1L)
+#ifndef DISABLE_OCSP
+# define EXIM_OCSP_SKEW_SECONDS (300L)
+# define EXIM_OCSP_MAX_AGE (-1L)
 #endif
 
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-#define EXIM_HAVE_OPENSSL_TLSEXT
+# define EXIM_HAVE_OPENSSL_TLSEXT
+#endif
+
+#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
+# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
+# define DISABLE_OCSP
 #endif
 
 /* Structure for collecting random data for seeding. */
@@ -88,7 +93,7 @@ static BOOL reexpand_tls_files_for_sni = FALSE;
 typedef struct tls_ext_ctx_cb {
   uschar *certificate;
   uschar *privatekey;
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
   BOOL is_server;
   union {
     struct {
@@ -97,7 +102,8 @@ typedef struct tls_ext_ctx_cb {
       OCSP_RESPONSE *response;
     } server;
     struct {
-      X509_STORE    *verify_store;
+      X509_STORE    *verify_store;     /* non-null if status requested */
+      BOOL         verify_required;
     } client;
   } u_ocsp;
 #endif
@@ -106,6 +112,13 @@ typedef struct tls_ext_ctx_cb {
   uschar *server_cipher_list;
   /* only passed down to tls_error: */
   host_item *host;
+
+#ifdef EXPERIMENTAL_CERTNAMES
+  uschar * verify_cert_hostnames;
+#endif
+#ifdef EXPERIMENTAL_TPDA
+  uschar * event_action;
+#endif
 } tls_ext_ctx_cb;
 
 /* should figure out a cleanup of API to handle state preserved per
@@ -122,7 +135,7 @@ setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL opt
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
 #endif
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 static int tls_server_stapling_cb(SSL *s, void *arg);
 #endif
 
@@ -208,7 +221,7 @@ return rsa_key;
 
 
 /* Extreme debug
-#if defined(EXPERIMENTAL_OCSP)
+#ifndef DISABLE_OCSP
 void
 x509_store_dump_cert_s_names(X509_STORE * store)
 {
@@ -252,6 +265,9 @@ when asked. We get here only if a certificate has been received. Handling of
 optional verification for this case is done when requesting SSL to verify, by
 setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
 
+May be called multiple times for different issues with a certificate, even
+for a given "depth" in the certificate chain.
+
 Arguments:
   state      current yes/no state as 1/0
   x509ctx    certificate information.
@@ -261,66 +277,137 @@ Returns:     1 if verified, 0 if not
 */
 
 static int
-verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
+verify_callback(int state, X509_STORE_CTX *x509ctx,
+  tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
 {
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
 static uschar txt[256];
 
-X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert),
-  CS txt, sizeof(txt));
+X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
 
 if (state == 0)
   {
   log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s",
-    x509ctx->error_depth,
-    X509_verify_cert_error_string(x509ctx->error),
+    depth,
+    X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
     txt);
   tlsp->certificate_verified = FALSE;
   *calledp = TRUE;
   if (!*optionalp)
     {
-    tlsp->peercert = X509_dup(x509ctx->current_cert);
+    tlsp->peercert = X509_dup(cert);
     return 0;                      /* reject */
     }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
-  return 1;                          /* accept */
   }
 
-if (x509ctx->error_depth != 0)
+else if (depth != 0)
   {
-  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n",
-     x509ctx->error_depth, txt);
-#ifdef EXPERIMENTAL_OCSP
+  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, txt);
+#ifndef DISABLE_OCSP
   if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store)
     {  /* client, wanting stapling  */
     /* Add the server cert's signing chain as the one
     for the verification of the OCSP stapled information. */
   
     if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
-                             x509ctx->current_cert))
+                             cert))
       ERR_clear_error();
     }
+#endif
+#ifdef EXPERIMENTAL_TPDA
+  if (tlsp == &tls_out && client_static_cbinfo->event_action)
+    {
+    tlsp->peercert = X509_dup(cert);
+    if (tpda_raise_event(client_static_cbinfo->event_action,
+                   US"tls:cert", string_sprintf("%d", depth)) == DEFER)
+      {
+      log_write(0, LOG_MAIN, "SSL verify denied by event-action: "
+                             "depth=%d cert=%s", depth, txt);
+      tlsp->certificate_verified = FALSE;
+      *calledp = TRUE;
+      return 0;                            /* reject */
+      }
+    X509_free(tlsp->peercert);
+    tlsp->peercert = NULL;
+    }
 #endif
   }
 else
   {
-  DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
-    *calledp ? "" : " authenticated", txt);
+#ifdef EXPERIMENTAL_CERTNAMES
+  uschar * verify_cert_hostnames;
+#endif
+
   tlsp->peerdn = txt;
-  tlsp->peercert = X509_dup(x509ctx->current_cert);
-  }
+  tlsp->peercert = X509_dup(cert);
 
-/*XXX JGH: this looks bogus - we set "verified" first time through, which
-will be for the root CS cert (calls work down the chain).  Why should it
-not be on the last call, where we're setting peerdn?
+#ifdef EXPERIMENTAL_CERTNAMES
+  if (  tlsp == &tls_out
+     && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
+       /* client, wanting hostname check */
 
-To test: set up a chain anchored by a good root-CA but with a bad server cert.
-Does certificate_verified get set?
-*/
-if (!*calledp) tlsp->certificate_verified = TRUE;
-*calledp = TRUE;
+# if OPENSSL_VERSION_NUMBER >= 0x010100000L || OPENSSL_VERSION_NUMBER >= 0x010002000L
+#  ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+#   define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0
+#  endif
+    {
+    int sep = 0;
+    uschar * list = verify_cert_hostnames;
+    uschar * name;
+    int rc;
+    while ((name = string_nextinlist(&list, &sep, NULL, 0)))
+      if ((rc = X509_check_host(cert, name, 0,
+                 X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS)))
+       {
+       if (rc < 0)
+         {
+         log_write(0, LOG_MAIN, "SSL verify error: internal error\n");
+         name = NULL;
+         }
+       break;
+       }
+    if (!name)
+      {
+      log_write(0, LOG_MAIN,
+       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
+      return 0;                                /* reject */
+      }
+    }
+# else
+    if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
+      {
+      log_write(0, LOG_MAIN,
+       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
+      return 0;                                /* reject */
+      }
+# endif
+#endif /*EXPERIMENTAL_CERTNAMES*/
 
-return 1;   /* accept */
+#ifdef EXPERIMENTAL_TPDA
+  if (tlsp == &tls_out)
+    {
+    if (tpda_raise_event(client_static_cbinfo->event_action,
+                   US"tls:cert", US"0") == DEFER)
+      {
+      log_write(0, LOG_MAIN, "SSL verify denied by event-action: "
+                             "depth=0 cert=%s", txt);
+      tlsp->certificate_verified = FALSE;
+      *calledp = TRUE;
+      return 0;                            /* reject */
+      }
+    }
+#endif
+
+  DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
+    *calledp ? "" : " authenticated", txt);
+  if (!*calledp) tlsp->certificate_verified = TRUE;
+  *calledp = TRUE;
+  }
+
+return 1;   /* accept, at least for this level */
 }
 
 static int
@@ -387,14 +474,11 @@ const char *pem;
 if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
   return FALSE;
 
-if (dhexpanded == NULL || *dhexpanded == '\0')
-  {
+if (!dhexpanded || !*dhexpanded)
   bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
-  }
 else if (dhexpanded[0] == '/')
   {
-  bio = BIO_new_file(CS dhexpanded, "r");
-  if (bio == NULL)
+  if (!(bio = BIO_new_file(CS dhexpanded, "r")))
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
           host, US strerror(errno));
@@ -409,8 +493,7 @@ else
     return TRUE;
     }
 
-  pem = std_dh_prime_named(dhexpanded);
-  if (!pem)
+  if (!(pem = std_dh_prime_named(dhexpanded)))
     {
     tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
         host, US strerror(errno));
@@ -419,8 +502,7 @@ else
   bio = BIO_new_mem_buf(CS pem, -1);
   }
 
-dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
-if (dh == NULL)
+if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)))
   {
   BIO_free(bio);
   tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
@@ -454,7 +536,7 @@ return TRUE;
 
 
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 /*************************************************
 *       Load OCSP information into state         *
 *************************************************/
@@ -571,24 +653,24 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX
   }
 
 supply_response:
-cbinfo->u_ocsp.server.response = resp;
+  cbinfo->u_ocsp.server.response = resp;
 return;
 
 bad:
-if (running_in_test_harness)
-  {
-  extern char ** environ;
-  uschar ** p;
-  for (p = USS environ; *p != NULL; p++)
-    if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
-      {
-      DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
-      goto supply_response;
-      }
-  }
+  if (running_in_test_harness)
+    {
+    extern char ** environ;
+    uschar ** p;
+    for (p = USS environ; *p != NULL; p++)
+      if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
+       {
+       DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
+       goto supply_response;
+       }
+    }
 return;
 }
-#endif /*EXPERIMENTAL_OCSP*/
+#endif /*!DISABLE_OCSP*/
 
 
 
@@ -650,7 +732,7 @@ if (expanded != NULL && *expanded != 0)
       "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
   }
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if (cbinfo->is_server &&  cbinfo->u_ocsp.server.file != NULL)
   {
   if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
@@ -721,8 +803,7 @@ if (!reexpand_tls_files_for_sni)
 not confident that memcpy wouldn't break some internal reference counting.
 Especially since there's a references struct member, which would be off. */
 
-server_sni = SSL_CTX_new(SSLv23_server_method());
-if (!server_sni)
+if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
   DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
@@ -740,7 +821,7 @@ SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
 SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
 if (cbinfo->server_cipher_list)
   SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if (cbinfo->u_ocsp.server.file)
   {
   SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb);
@@ -756,8 +837,8 @@ OCSP information. */
 rc = tls_expand_session_files(server_sni, cbinfo);
 if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
-rc = init_dh(server_sni, cbinfo->dhparam, NULL);
-if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+if (!init_dh(server_sni, cbinfo->dhparam, NULL))
+  return SSL_TLSEXT_ERR_NOACK;
 
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
 SSL_set_SSL_CTX(s, server_sni);
@@ -769,7 +850,7 @@ return SSL_TLSEXT_ERR_OK;
 
 
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 
 /*************************************************
 *        Callback to handle OCSP Stapling        *
@@ -790,22 +871,22 @@ const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 uschar *response_der;
 int response_der_len;
 
-if (log_extra_selector & LX_tls_cipher)
-  log_write(0, LOG_MAIN, "[%s] Recieved OCSP stapling req;%s responding",
-    sender_host_address, cbinfo->u_ocsp.server.response ? "":" not");
-else
-  DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.",
+DEBUG(D_tls)
+  debug_printf("Received TLS status request (OCSP stapling); %s response.",
     cbinfo->u_ocsp.server.response ? "have" : "lack");
 
+tls_in.ocsp = OCSP_NOT_RESP;
 if (!cbinfo->u_ocsp.server.response)
   return SSL_TLSEXT_ERR_NOACK;
 
 response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, &response_der);
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+                     &response_der);
 if (response_der_len <= 0)
   return SSL_TLSEXT_ERR_NOACK;
 
 SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len);
+tls_in.ocsp = OCSP_VFIED;
 return SSL_TLSEXT_ERR_OK;
 }
 
@@ -832,16 +913,20 @@ DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
 len = SSL_get_tlsext_status_ocsp_resp(s, &p);
 if(!p)
  {
-  if (log_extra_selector & LX_tls_cipher)
-    log_write(0, LOG_MAIN, "Received TLS status response, null content");
+  /* Expect this when we requested ocsp but got none */
+  if (  cbinfo->u_ocsp.client.verify_required
+     && log_extra_selector & LX_tls_cipher)
+    log_write(0, LOG_MAIN, "Received TLS status callback, null content");
   else
     DEBUG(D_tls) debug_printf(" null\n");
-  return 0;    /* This is the fail case for require-ocsp; none from server */
+  return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
  }
+
 if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
  {
+  tls_out.ocsp = OCSP_FAILED;
   if (log_extra_selector & LX_tls_cipher)
-    log_write(0, LOG_MAIN, "Received TLS status response, parse error");
+    log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
   else
     DEBUG(D_tls) debug_printf(" parse error\n");
   return 0;
@@ -849,8 +934,9 @@ if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
 
 if(!(bs = OCSP_response_get1_basic(rsp)))
   {
+  tls_out.ocsp = OCSP_FAILED;
   if (log_extra_selector & LX_tls_cipher)
-    log_write(0, LOG_MAIN, "Received TLS status response, error parsing response");
+    log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
   else
     DEBUG(D_tls) debug_printf(" error parsing response\n");
   OCSP_RESPONSE_free(rsp);
@@ -860,14 +946,12 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
 /* We'd check the nonce here if we'd put one in the request. */
 /* However that would defeat cacheability on the server so we don't. */
 
-
 /* This section of code reworked from OpenSSL apps source;
    The OpenSSL Project retains copyright:
    Copyright (c) 1999 The OpenSSL Project.  All rights reserved.
 */
   {
     BIO * bp = NULL;
-    OCSP_CERTID *id;
     int status, reason;
     ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
 
@@ -878,11 +962,15 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
     /* Use the chain that verified the server cert to verify the stapled info */
     /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */
 
-    if ((i = OCSP_basic_verify(bs, NULL, cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
+    if ((i = OCSP_basic_verify(bs, NULL,
+             cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
       {
+      tls_out.ocsp = OCSP_FAILED;
+      if (log_extra_selector & LX_tls_cipher)
+       log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
-      i = 0;
+      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
       goto out;
       }
 
@@ -894,39 +982,52 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
 
       if (sk_OCSP_SINGLERESP_num(sresp) != 1)
         {
-        log_write(0, LOG_MAIN, "OCSP stapling with multiple responses not handled");
+       tls_out.ocsp = OCSP_FAILED;
+        log_write(0, LOG_MAIN, "OCSP stapling "
+           "with multiple responses not handled");
+       i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
         goto out;
         }
       single = OCSP_resp_get0(bs, 0);
-      status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd);
+      status = OCSP_single_get0_status(single, &reason, &rev,
+                 &thisupd, &nextupd);
       }
 
-    i = 0;
     DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd);
     DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd);
-    if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+    if (!OCSP_check_validity(thisupd, nextupd,
+         EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
       {
+      tls_out.ocsp = OCSP_FAILED;
       DEBUG(D_tls) ERR_print_errors(bp);
       log_write(0, LOG_MAIN, "Server OSCP dates invalid");
-      goto out;
+      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
       }
-
-    DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n", OCSP_cert_status_str(status));
-    switch(status)
+    else
       {
-      case V_OCSP_CERTSTATUS_GOOD:
-        i = 1;
-        break;
-      case V_OCSP_CERTSTATUS_REVOKED:
-        log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
-            reason != -1 ? "; reason: " : "", reason != -1 ? OCSP_crl_reason_str(reason) : "");
-        DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
-        i = 0;
-        break;
-      default:
-        log_write(0, LOG_MAIN, "Server certificate status unknown, in OCSP stapling");
-        i = 0;
-        break;
+      DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n",
+                   OCSP_cert_status_str(status));
+      switch(status)
+       {
+       case V_OCSP_CERTSTATUS_GOOD:
+         tls_out.ocsp = OCSP_VFIED;
+         i = 1;
+         break;
+       case V_OCSP_CERTSTATUS_REVOKED:
+         tls_out.ocsp = OCSP_FAILED;
+         log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
+             reason != -1 ? "; reason: " : "",
+             reason != -1 ? OCSP_crl_reason_str(reason) : "");
+         DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
+         i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+         break;
+       default:
+         tls_out.ocsp = OCSP_FAILED;
+         log_write(0, LOG_MAIN,
+             "Server certificate status unknown, in OCSP stapling");
+         i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+         break;
+       }
       }
   out:
     BIO_free(bp);
@@ -935,7 +1036,7 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
 OCSP_RESPONSE_free(rsp);
 return i;
 }
-#endif /*EXPERIMENTAL_OCSP*/
+#endif /*!DISABLE_OCSP*/
 
 
 
@@ -943,8 +1044,8 @@ return i;
 *            Initialize for TLS                  *
 *************************************************/
 
-/* Called from both server and client code, to do preliminary initialization of
-the library.
+/* Called from both server and client code, to do preliminary initialization
+of the library.  We allocate and return a context structure.
 
 Arguments:
   host            connected host, if client; NULL if server
@@ -953,6 +1054,7 @@ Arguments:
   privatekey      private key
   ocsp_file       file of stapling info (server); flag for require ocsp (client)
   addr            address if client; NULL if server (for some randomness)
+  cbp             place to put allocated context
 
 Returns:          OK/DEFER/FAIL
 */
@@ -960,7 +1062,7 @@ Returns:          OK/DEFER/FAIL
 static int
 tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
   uschar *privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
   uschar *ocsp_file,
 #endif
   address_item *addr, tls_ext_ctx_cb ** cbp)
@@ -968,12 +1070,12 @@ tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
 long init_options;
 int rc;
 BOOL okay;
-tls_ext_ctx_cb *cbinfo;
+tls_ext_ctx_cb * cbinfo;
 
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if ((cbinfo->is_server = host==NULL))
   {
   cbinfo->u_ocsp.server.file = ocsp_file;
@@ -984,7 +1086,11 @@ else
   cbinfo->u_ocsp.client.verify_store = NULL;
 #endif
 cbinfo->dhparam = dhparam;
+cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
+#ifdef EXPERIMENTAL_TPDA
+cbinfo->event_action = NULL;
+#endif
 
 SSL_load_error_strings();          /* basic set up */
 OpenSSL_add_ssl_algorithms();
@@ -1075,7 +1181,7 @@ if (rc != OK) return rc;
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 if (host == NULL)              /* server */
   {
-# ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
   /* We check u_ocsp.server.file, not server.response, because we care about if
   the option exists, not what the current expansion might be, as SNI might
   change the certificate and OCSP file in use between now and the time the
@@ -1091,7 +1197,7 @@ if (host == NULL)         /* server */
   SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
   SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
   }
-# ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
 else                   /* client */
   if(ocsp_file)                /* wanting stapling */
     {
@@ -1106,6 +1212,10 @@ else                     /* client */
 # endif
 #endif
 
+#ifdef EXPERIMENTAL_CERTNAMES
+cbinfo->verify_cert_hostnames = NULL;
+#endif
+
 /* Set up the RSA callback */
 
 SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
@@ -1324,7 +1434,7 @@ if (tls_in.active >= 0)
 the error. */
 
 rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
     tls_ocsp_file,
 #endif
     NULL, &server_static_cbinfo);
@@ -1477,17 +1587,7 @@ Argument:
   fd               the fd of the connection
   host             connected host (for messages)
   addr             the first address
-  certificate      certificate file
-  privatekey       private key file
-  sni              TLS SNI to send to remote host
-  verify_certs     file for certificate verify
-  crl              file containing CRL
-  require_ciphers  list of allowed ciphers
-  dh_min_bits      minimum number of bits acceptable in server's DH prime
-                   (unused in OpenSSL)
-  timeout          startup timeout
-  verify_hosts     mandatory client verification 
-  try_verify_hosts optional client verification
+  tb               transport (always smtp)
 
 Returns:           OK on success
                    FAIL otherwise - note that tls_error() will not give DEFER
@@ -1496,28 +1596,27 @@ Returns:           OK on success
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr,
-  uschar *certificate, uschar *privatekey, uschar *sni,
-  uschar *verify_certs, uschar *crl,
-  uschar *require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
-  uschar *hosts_require_ocsp,
-#endif
-  int dh_min_bits ARG_UNUSED, int timeout,
-  uschar *verify_hosts, uschar *try_verify_hosts)
+  transport_instance *tb)
 {
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
 static uschar txt[256];
 uschar *expciphers;
 X509* server_cert;
 int rc;
 static uschar cipherbuf[256];
-#ifdef EXPERIMENTAL_OCSP
-BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp,
+#ifndef DISABLE_OCSP
+BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
   NULL, host->name, host->address, NULL) == OK;
+BOOL request_ocsp = require_ocsp ? TRUE
+  : verify_check_this_host(&ob->hosts_request_ocsp,
+      NULL, host->name, host->address, NULL) == OK;
 #endif
 
-rc = tls_init(&client_ctx, host, NULL, certificate, privatekey,
-#ifdef EXPERIMENTAL_OCSP
-    require_ocsp ? US"" : NULL,
+rc = tls_init(&client_ctx, host, NULL,
+    ob->tls_certificate, ob->tls_privatekey,
+#ifndef DISABLE_OCSP
+    (void *)(long)request_ocsp,
 #endif
     addr, &client_static_cbinfo);
 if (rc != OK) return rc;
@@ -1525,7 +1624,8 @@ if (rc != OK) return rc;
 tls_out.certificate_verified = FALSE;
 client_verify_callback_called = FALSE;
 
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+    &expciphers))
   return FAIL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
@@ -1542,30 +1642,47 @@ if (expciphers != NULL)
   }
 
 /* stick to the old behaviour for compatibility if tls_verify_certificates is 
-   set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only 
+   set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
    the specified host patterns if one of them is defined */
-if (((verify_hosts == NULL) && (try_verify_hosts == NULL)) ||
-    (verify_check_host(&verify_hosts) == OK))
+
+if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
+    (verify_check_host(&ob->tls_verify_hosts) == OK))
   {
-  rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, verify_callback_client);
-  if (rc != OK) return rc;
+  if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
+       ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
+    return rc;
   client_verify_optional = FALSE;
+
+#ifdef EXPERIMENTAL_CERTNAMES
+  if (ob->tls_verify_cert_hostnames)
+    {
+    if (!expand_check(ob->tls_verify_cert_hostnames,
+                     US"tls_verify_cert_hostnames",
+                     &client_static_cbinfo->verify_cert_hostnames))
+      return FAIL;
+    if (client_static_cbinfo->verify_cert_hostnames)
+      DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+                     client_static_cbinfo->verify_cert_hostnames);
+    }
+#endif
   }
-else if (verify_check_host(&try_verify_hosts) == OK)
+else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
   {
-  rc = setup_certs(client_ctx, verify_certs, crl, host, TRUE, verify_callback_client);
-  if (rc != OK) return rc;
+  if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
+       ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
+    return rc;
   client_verify_optional = TRUE;
   }
 
-if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
+if ((client_ssl = SSL_new(client_ctx)) == NULL)
+  return tls_error(US"SSL_new", host, NULL);
 SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
 SSL_set_fd(client_ssl, fd);
 SSL_set_connect_state(client_ssl);
 
-if (sni)
+if (ob->tls_sni)
   {
-  if (!expand_check(sni, US"tls_sni", &tls_out.sni))
+  if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
     return FAIL;
   if (tls_out.sni == NULL)
     {
@@ -1586,18 +1703,26 @@ if (sni)
     }
   }
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 /* Request certificate status at connection-time.  If the server
 does OCSP stapling we will get the callback (set in tls_init()) */
-if (require_ocsp)
+if (request_ocsp)
+  {
   SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+  client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
+  tls_out.ocsp = OCSP_NOT_RESP;
+  }
+#endif
+
+#ifdef EXPERIMENTAL_TPDA
+client_static_cbinfo->event_action = tb->tpda_event_action;
 #endif
 
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
 sigalrm_seen = FALSE;
-alarm(timeout);
+alarm(ob->command_timeout);
 rc = SSL_connect(client_ssl);
 alarm(0);