Merge branch 'master' of ssh://git.exim.org/home/git/exim
[users/jgh/exim.git] / src / src / tls-openssl.c
index 8cc2457e5b6cbe88d6486db9d12813c51ad5b1b6..db2544c037f56ffcfa1a0ebb985fbc5f57445ca9 100644 (file)
@@ -2,9 +2,11 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
+/* Portions Copyright (c) The OpenSSL Project 1999 */
+
 /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL
 library. It is #included into the tls.c file when that library is used. The
 code herein is based on a patch that was originally contributed by Steve
@@ -20,6 +22,18 @@ functions from the OpenSSL library. */
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
+#ifdef EXPERIMENTAL_OCSP
+#include <openssl/ocsp.h>
+#endif
+
+#ifdef EXPERIMENTAL_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
+#endif
 
 /* Structure for collecting random data for seeding. */
 
@@ -30,24 +44,64 @@ typedef struct randstuff {
 
 /* Local static variables */
 
-static BOOL verify_callback_called = FALSE;
+static BOOL client_verify_callback_called = FALSE;
+static BOOL server_verify_callback_called = FALSE;
 static const uschar *sid_ctx = US"exim";
 
-static SSL_CTX *ctx = NULL;
-static SSL_CTX *ctx_sni = NULL;
-static SSL *ssl = NULL;
+/* We have three different contexts to care about.
+
+Simple case: client, `client_ctx`
+ As a client, we can be doing a callout or cut-through delivery while receiving
+ a message.  So we have a client context, which should have options initialised
+ from the SMTP Transport.
+
+Server:
+ There are two cases: with and without ServerNameIndication from the client.
+ Given TLS SNI, we can be using different keys, certs and various other
+ configuration settings, because they're re-expanded with $tls_sni set.  This
+ allows vhosting with TLS.  This SNI is sent in the handshake.
+ A client might not send SNI, so we need a fallback, and an initial setup too.
+ So as a server, we start out using `server_ctx`.
+ If SNI is sent by the client, then we as server, mid-negotiation, try to clone
+ `server_sni` from `server_ctx` and then initialise settings by re-expanding
+ configuration.
+*/
+
+static SSL_CTX *client_ctx = NULL;
+static SSL_CTX *server_ctx = NULL;
+static SSL     *client_ssl = NULL;
+static SSL     *server_ssl = NULL;
+
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+static SSL_CTX *server_sni = NULL;
+#endif
 
 static char ssl_errstring[256];
 
 static int  ssl_session_timeout = 200;
-static BOOL verify_optional = FALSE;
+static BOOL client_verify_optional = FALSE;
+static BOOL server_verify_optional = FALSE;
 
-static BOOL    reexpand_tls_files_for_sni = FALSE;
+static BOOL reexpand_tls_files_for_sni = FALSE;
 
 
 typedef struct tls_ext_ctx_cb {
   uschar *certificate;
   uschar *privatekey;
+#ifdef EXPERIMENTAL_OCSP
+  BOOL is_server;
+  union {
+    struct {
+      uschar        *file;
+      uschar        *file_expanded;
+      OCSP_RESPONSE *response;
+    } server;
+    struct {
+      X509_STORE    *verify_store;     /* non-null if status requested */
+      BOOL         verify_required;
+    } client;
+  } u_ocsp;
+#endif
   uschar *dhparam;
   /* these are cached from first expand */
   uschar *server_cipher_list;
@@ -58,10 +112,20 @@ typedef struct tls_ext_ctx_cb {
 /* should figure out a cleanup of API to handle state preserved per
 implementation, for various reasons, which can be void * in the APIs.
 For now, we hack around it. */
-tls_ext_ctx_cb *static_cbinfo = NULL;
+tls_ext_ctx_cb *client_static_cbinfo = NULL;
+tls_ext_ctx_cb *server_static_cbinfo = NULL;
 
 static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+    int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
+
+/* Callbacks */
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
+#endif
+#ifdef EXPERIMENTAL_OCSP
+static int tls_server_stapling_cb(SSL *s, void *arg);
+#endif
 
 
 /*************************************************
@@ -144,6 +208,29 @@ return rsa_key;
 
 
 
+/* Extreme debug
+#if defined(EXPERIMENTAL_OCSP)
+void
+x509_store_dump_cert_s_names(X509_STORE * store)
+{
+STACK_OF(X509_OBJECT) * roots= store->objs;
+int i;
+static uschar name[256];
+
+for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
+  {
+  X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
+  if(tmp_obj->type == X509_LU_X509)
+    {
+    X509 * current_cert= tmp_obj->data.x509;
+    X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
+    debug_printf(" %s\n", name);
+    }
+  }
+}
+#endif
+*/
+
 
 /*************************************************
 *        Callback for verification               *
@@ -169,12 +256,13 @@ setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
 Arguments:
   state      current yes/no state as 1/0
   x509ctx    certificate information.
+  client     TRUE for client startup, FALSE for server startup
 
 Returns:     1 if verified, 0 if not
 */
 
 static int
-verify_callback(int state, X509_STORE_CTX *x509ctx)
+verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
 {
 static uschar txt[256];
 
@@ -187,9 +275,13 @@ if (state == 0)
     x509ctx->error_depth,
     X509_verify_cert_error_string(x509ctx->error),
     txt);
-  tls_certificate_verified = FALSE;
-  verify_callback_called = TRUE;
-  if (!verify_optional) return 0;    /* reject */
+  tlsp->certificate_verified = FALSE;
+  *calledp = TRUE;
+  if (!*optionalp)
+    {
+    tlsp->peercert = X509_dup(x509ctx->current_cert);
+    return 0;                      /* reject */
+    }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
   return 1;                          /* accept */
@@ -199,20 +291,51 @@ if (x509ctx->error_depth != 0)
   {
   DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n",
      x509ctx->error_depth, txt);
+#ifdef EXPERIMENTAL_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))
+      ERR_clear_error();
+    }
+#endif
   }
 else
   {
   DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
-    verify_callback_called? "" : " authenticated", txt);
-  tls_peerdn = txt;
+    *calledp ? "" : " authenticated", txt);
+  tlsp->peerdn = txt;
+  tlsp->peercert = X509_dup(x509ctx->current_cert);
   }
 
-if (!verify_callback_called) tls_certificate_verified = TRUE;
-verify_callback_called = TRUE;
+/*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?
+
+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;
 
 return 1;   /* accept */
 }
 
+static int
+verify_callback_client(int state, X509_STORE_CTX *x509ctx)
+{
+return verify_callback(state, x509ctx, &tls_out, &client_verify_callback_called, &client_verify_optional);
+}
+
+static int
+verify_callback_server(int state, X509_STORE_CTX *x509ctx)
+{
+return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called, &server_verify_optional);
+}
+
 
 
 /*************************************************
@@ -248,61 +371,234 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
 /* If dhparam is set, expand it, and load up the parameters for DH encryption.
 
 Arguments:
-  dhparam   DH parameter file
+  dhparam   DH parameter file or fixed parameter identity string
   host      connected host, if client; NULL if server
 
 Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
-init_dh(uschar *dhparam, host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
 {
-BOOL yield = TRUE;
 BIO *bio;
 DH *dh;
 uschar *dhexpanded;
+const char *pem;
 
 if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
   return FALSE;
 
-if (dhexpanded == NULL) return TRUE;
-
-if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL)
+if (dhexpanded == NULL || *dhexpanded == '\0')
   {
-  tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-    host, (uschar *)strerror(errno));
-  yield = FALSE;
+  bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
   }
-else
+else if (dhexpanded[0] == '/')
   {
-  if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL)
+  bio = BIO_new_file(CS dhexpanded, "r");
+  if (bio == NULL)
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-      host, NULL);
-    yield = FALSE;
+          host, US strerror(errno));
+    return FALSE;
     }
-  else
+  }
+else
+  {
+  if (Ustrcmp(dhexpanded, "none") == 0)
     {
-    SSL_CTX_set_tmp_dh(ctx, dh);
-    DEBUG(D_tls)
-      debug_printf("Diffie-Hellman initialized from %s with %d-bit key\n",
-        dhexpanded, 8*DH_size(dh));
-    DH_free(dh);
+    DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+    return TRUE;
+    }
+
+  pem = std_dh_prime_named(dhexpanded);
+  if (!pem)
+    {
+    tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
+        host, US strerror(errno));
+    return FALSE;
     }
+  bio = BIO_new_mem_buf(CS pem, -1);
+  }
+
+dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+if (dh == NULL)
+  {
   BIO_free(bio);
+  tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
+      host, NULL);
+  return FALSE;
+  }
+
+/* Even if it is larger, we silently return success rather than cause things
+ * to fail out, so that a too-large DH will not knock out all TLS; it's a
+ * debatable choice. */
+if ((8*DH_size(dh)) > tls_dh_max_bits)
+  {
+  DEBUG(D_tls)
+    debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
+        8*DH_size(dh), tls_dh_max_bits);
   }
+else
+  {
+  SSL_CTX_set_tmp_dh(sctx, dh);
+  DEBUG(D_tls)
+    debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
+      dhexpanded ? dhexpanded : US"default", 8*DH_size(dh));
+  }
+
+DH_free(dh);
+BIO_free(bio);
 
-return yield;
+return TRUE;
 }
 
 
 
 
+#ifdef EXPERIMENTAL_OCSP
+/*************************************************
+*       Load OCSP information into state         *
+*************************************************/
+
+/* Called to load the server OCSP response from the given file into memory, once
+caller has determined this is needed.  Checks validity.  Debugs a message
+if invalid.
+
+ASSUMES: single response, for single cert.
+
+Arguments:
+  sctx            the SSL_CTX* to update
+  cbinfo          various parts of session state
+  expanded        the filename putatively holding an OCSP response
+
+*/
+
+static void
+ocsp_load_response(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo, const uschar *expanded)
+{
+BIO *bio;
+OCSP_RESPONSE *resp;
+OCSP_BASICRESP *basic_response;
+OCSP_SINGLERESP *single_response;
+ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+X509_STORE *store;
+unsigned long verify_flags;
+int status, reason, i;
+
+cbinfo->u_ocsp.server.file_expanded = string_copy(expanded);
+if (cbinfo->u_ocsp.server.response)
+  {
+  OCSP_RESPONSE_free(cbinfo->u_ocsp.server.response);
+  cbinfo->u_ocsp.server.response = NULL;
+  }
+
+bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb");
+if (!bio)
+  {
+  DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
+      cbinfo->u_ocsp.server.file_expanded);
+  return;
+  }
+
+resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
+BIO_free(bio);
+if (!resp)
+  {
+  DEBUG(D_tls) debug_printf("Error reading OCSP response.\n");
+  return;
+  }
+
+status = OCSP_response_status(resp);
+if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+  {
+  DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
+      OCSP_response_status_str(status), status);
+  goto bad;
+  }
+
+basic_response = OCSP_response_get1_basic(resp);
+if (!basic_response)
+  {
+  DEBUG(D_tls)
+    debug_printf("OCSP response parse error: unable to extract basic response.\n");
+  goto bad;
+  }
+
+store = SSL_CTX_get_cert_store(sctx);
+verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */
+
+/* May need to expose ability to adjust those flags?
+OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT
+OCSP_TRUSTOTHER OCSP_NOINTERN */
+
+i = OCSP_basic_verify(basic_response, NULL, store, verify_flags);
+if (i <= 0)
+  {
+  DEBUG(D_tls) {
+    ERR_error_string(ERR_get_error(), ssl_errstring);
+    debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
+    }
+  goto bad;
+  }
+
+/* Here's the simplifying assumption: there's only one response, for the
+one certificate we use, and nothing for anything else in a chain.  If this
+proves false, we need to extract a cert id from our issued cert
+(tls_certificate) and use that for OCSP_resp_find_status() (which finds the
+right cert in the stack and then calls OCSP_single_get0_status()).
+
+I'm hoping to avoid reworking a bunch more of how we handle state here. */
+single_response = OCSP_resp_get0(basic_response, 0);
+if (!single_response)
+  {
+  DEBUG(D_tls)
+    debug_printf("Unable to get first response from OCSP basic response.\n");
+  goto bad;
+  }
+
+status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
+if (status != V_OCSP_CERTSTATUS_GOOD)
+  {
+  DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
+      OCSP_cert_status_str(status), status,
+      OCSP_crl_reason_str(reason), reason);
+  goto bad;
+  }
+
+if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+  {
+  DEBUG(D_tls) debug_printf("OCSP status invalid times.\n");
+  goto bad;
+  }
+
+supply_response:
+  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;
+       }
+    }
+return;
+}
+#endif /*EXPERIMENTAL_OCSP*/
+
+
+
+
 /*************************************************
 *        Expand key and cert file specs          *
 *************************************************/
 
-/* Called once during tls_init and possibly againt during TLS setup, for a
+/* Called once during tls_init and possibly again during TLS setup, for a
 new context, if Server Name Indication was used and tls_sni was seen in
 the certificate string.
 
@@ -314,14 +610,17 @@ Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_expand_session_files(SSL_CTX *sctx, const tls_ext_ctx_cb *cbinfo)
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
 {
 uschar *expanded;
 
 if (cbinfo->certificate == NULL)
   return OK;
 
-if (Ustrstr(cbinfo->certificate, US"tls_sni"))
+if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
+    Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
+    Ustrstr(cbinfo->certificate, US"tls_out_sni")
+   )
   reexpand_tls_files_for_sni = TRUE;
 
 if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
@@ -352,6 +651,27 @@ if (expanded != NULL && *expanded != 0)
       "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
   }
 
+#ifdef EXPERIMENTAL_OCSP
+if (cbinfo->is_server &&  cbinfo->u_ocsp.server.file != NULL)
+  {
+  if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
+    return DEFER;
+
+  if (expanded != NULL && *expanded != 0)
+    {
+    DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded);
+    if (cbinfo->u_ocsp.server.file_expanded &&
+        (Ustrcmp(expanded, cbinfo->u_ocsp.server.file_expanded) == 0))
+      {
+      DEBUG(D_tls)
+        debug_printf("tls_ocsp_file value unchanged, using existing values.\n");
+      } else {
+        ocsp_load_response(sctx, cbinfo, expanded);
+      }
+    }
+  }
+#endif
+
 return OK;
 }
 
@@ -375,25 +695,25 @@ Arguments:
 Returns:          SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
 */
 
-static int
-tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
-/* pre-declared for SSL_CTX_set_tlsext_servername_callback call within func */
-
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static int
 tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg)
 {
 const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
-const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
+tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 int rc;
+int old_pool = store_pool;
 
 if (!servername)
   return SSL_TLSEXT_ERR_OK;
 
-DEBUG(D_tls) debug_printf("TLS SNI: %s%s\n", servername,
+DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername,
     reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)");
 
 /* Make the extension value available for expansion */
-tls_sni = servername;
+store_pool = POOL_PERM;
+tls_in.sni = string_copy(US servername);
+store_pool = old_pool;
 
 if (!reexpand_tls_files_for_sni)
   return SSL_TLSEXT_ERR_OK;
@@ -402,8 +722,8 @@ 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. */
 
-ctx_sni = SSL_CTX_new(SSLv23_server_method());
-if (!ctx_sni)
+server_sni = SSL_CTX_new(SSLv23_server_method());
+if (!server_sni)
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
   DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
@@ -413,28 +733,232 @@ if (!ctx_sni)
 /* Not sure how many of these are actually needed, since SSL object
 already exists.  Might even need this selfsame callback, for reneg? */
 
-SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx));
-SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx));
-SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx));
-SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx));
-SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo);
+SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx));
+SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx));
+SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
+SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
+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(ctx_sni, CS cbinfo->server_cipher_list);
+  SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
+#ifdef EXPERIMENTAL_OCSP
+if (cbinfo->u_ocsp.server.file)
+  {
+  SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb);
+  SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo);
+  }
+#endif
+
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, verify_callback_server);
+if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
-rc = tls_expand_session_files(ctx_sni, cbinfo);
+/* do this after setup_certs, because this can require the certs for verifying
+OCSP information. */
+rc = tls_expand_session_files(server_sni, cbinfo);
 if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
-rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
+rc = init_dh(server_sni, cbinfo->dhparam, NULL);
 if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
-SSL_set_SSL_CTX(s, ctx_sni);
+SSL_set_SSL_CTX(s, server_sni);
+
+return SSL_TLSEXT_ERR_OK;
+}
+#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
+
+
+
+
+#ifdef EXPERIMENTAL_OCSP
+
+/*************************************************
+*        Callback to handle OCSP Stapling        *
+*************************************************/
+
+/* Called when acting as server during the TLS session setup if the client
+requests OCSP information with a Certificate Status Request.
+
+Documentation via openssl s_server.c and the Apache patch from the OpenSSL
+project.
+
+*/
+
+static int
+tls_server_stapling_cb(SSL *s, void *arg)
+{
+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.",
+    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);
+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;
 }
 
 
+static void
+time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time)
+{
+BIO_printf(bp, "\t%s: ", str);
+ASN1_GENERALIZEDTIME_print(bp, time);
+BIO_puts(bp, "\n");
+}
+
+static int
+tls_client_stapling_cb(SSL *s, void *arg)
+{
+tls_ext_ctx_cb * cbinfo = arg;
+const unsigned char * p;
+int len;
+OCSP_RESPONSE * rsp;
+OCSP_BASICRESP * bs;
+int i;
+
+DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
+len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+if(!p)
+ {
+  /* 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 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");
+  else
+    DEBUG(D_tls) debug_printf(" parse error\n");
+  return 0;
+ }
+
+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");
+  else
+    DEBUG(D_tls) debug_printf(" error parsing response\n");
+  OCSP_RESPONSE_free(rsp);
+  return 0;
+  }
+
+/* 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;
+    int status, reason;
+    ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+
+    DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+
+    /*OCSP_RESPONSE_print(bp, rsp, 0);   extreme debug: stapling content */
+
+    /* 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)
+      {
+      tls_out.ocsp = OCSP_FAILED;
+      BIO_printf(bp, "OCSP response verify failure\n");
+      ERR_print_errors(bp);
+      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+      goto out;
+      }
+
+    BIO_printf(bp, "OCSP response well-formed and signed OK\n");
+
+      {
+      STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
+      OCSP_SINGLERESP * single;
+
+      if (sk_OCSP_SINGLERESP_num(sresp) != 1)
+        {
+       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);
+      }
+
+    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))
+      {
+      tls_out.ocsp = OCSP_FAILED;
+      DEBUG(D_tls) ERR_print_errors(bp);
+      log_write(0, LOG_MAIN, "Server OSCP dates invalid");
+      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+      }
+    else
+      {
+      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);
+  }
+
+OCSP_RESPONSE_free(rsp);
+return i;
+}
+#endif /*EXPERIMENTAL_OCSP*/
+
 
 
 /*************************************************
@@ -449,14 +973,19 @@ Arguments:
   dhparam         DH parameter file
   certificate     certificate file
   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)
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_init(host_item *host, uschar *dhparam, uschar *certificate,
-  uschar *privatekey, address_item *addr)
+tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
+  uschar *privatekey,
+#ifdef EXPERIMENTAL_OCSP
+  uschar *ocsp_file,
+#endif
+  address_item *addr, tls_ext_ctx_cb ** cbp)
 {
 long init_options;
 int rc;
@@ -466,6 +995,16 @@ tls_ext_ctx_cb *cbinfo;
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
+#ifdef EXPERIMENTAL_OCSP
+if ((cbinfo->is_server = host==NULL))
+  {
+  cbinfo->u_ocsp.server.file = ocsp_file;
+  cbinfo->u_ocsp.server.file_expanded = NULL;
+  cbinfo->u_ocsp.server.response = NULL;
+  }
+else
+  cbinfo->u_ocsp.client.verify_store = NULL;
+#endif
 cbinfo->dhparam = dhparam;
 cbinfo->host = host;
 
@@ -478,12 +1017,18 @@ list of available digests. */
 EVP_add_digest(EVP_sha256());
 #endif
 
-/* Create a context */
+/* Create a context.
+The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant
+negotiation in the different methods; as far as I can tell, the only
+*_{server,client}_method which allows negotiation is SSLv23, which exists even
+when OpenSSL is built without SSLv2 support.
+By disabling with openssl_options, we can let admins re-enable with the
+existing knob. */
 
-ctx = SSL_CTX_new((host == NULL)?
+*ctxp = SSL_CTX_new((host == NULL)?
   SSLv23_server_method() : SSLv23_client_method());
 
-if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
 
 /* It turns out that we need to seed the random number generator this early in
 order to get the full complement of ciphers to work. It took me roughly a day
@@ -511,10 +1056,10 @@ if (!RAND_status())
 /* Set up the information callback, which outputs if debugging is at a suitable
 level. */
 
-SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
 
 /* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
 
 /* Apply administrator-supplied work-arounds.
 Historically we applied just one requested option,
@@ -532,7 +1077,7 @@ if (!okay)
 if (init_options)
   {
   DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
-  if (!(SSL_CTX_set_options(ctx, init_options)))
+  if (!(SSL_CTX_set_options(*ctxp, init_options)))
     return tls_error(string_sprintf(
           "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
   }
@@ -541,31 +1086,58 @@ else
 
 /* Initialize with DH parameters if supplied */
 
-if (!init_dh(dhparam, host)) return DEFER;
+if (!init_dh(*ctxp, dhparam, host)) return DEFER;
 
-/* Set up certificate and key */
+/* Set up certificate and key (and perhaps OCSP info) */
 
-rc = tls_expand_session_files(ctx, cbinfo);
+rc = tls_expand_session_files(*ctxp, cbinfo);
 if (rc != OK) return rc;
 
 /* If we need to handle SNI, do so */
-#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-/* We always do this, so that $tls_sni is available even if not used in
-tls_certificate */
-SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+if (host == NULL)              /* server */
+  {
+# ifdef EXPERIMENTAL_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
+  callback is invoked. */
+  if (cbinfo->u_ocsp.server.file)
+    {
+    SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_stapling_cb);
+    SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
+    }
+# endif
+  /* We always do this, so that $tls_sni is available even if not used in
+  tls_certificate */
+  SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
+  SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+  }
+# ifdef EXPERIMENTAL_OCSP
+else                   /* client */
+  if(ocsp_file)                /* wanting stapling */
+    {
+    if (!(cbinfo->u_ocsp.client.verify_store = X509_STORE_new()))
+      {
+      DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
+      return FAIL;
+      }
+    SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
+    SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+    }
+# endif
 #endif
 
 /* Set up the RSA callback */
 
-SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
 
 /* Finally, set the timeout, and we are done */
 
-SSL_CTX_set_timeout(ctx, ssl_session_timeout);
+SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
 DEBUG(D_tls) debug_printf("Initialized TLS\n");
 
-static_cbinfo = cbinfo;
+*cbp = cbinfo;
 
 return OK;
 }
@@ -577,59 +1149,30 @@ return OK;
 *           Get name of cipher in use            *
 *************************************************/
 
-/* The answer is left in a static buffer, and tls_cipher is set to point
-to it.
-
+/*
 Argument:   pointer to an SSL structure for the connection
+            buffer to use for answer
+            size of buffer
+           pointer to number of bits for cipher
 Returns:    nothing
 */
 
 static void
-construct_cipher_name(SSL *ssl)
+construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
 {
-static uschar cipherbuf[256];
 /* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
 yet reflect that.  It should be a safe change anyway, even 0.9.8 versions have
 the accessor functions use const in the prototype. */
 const SSL_CIPHER *c;
-uschar *ver;
-
-switch (ssl->session->ssl_version)
-  {
-  case SSL2_VERSION:
-  ver = US"SSLv2";
-  break;
-
-  case SSL3_VERSION:
-  ver = US"SSLv3";
-  break;
-
-  case TLS1_VERSION:
-  ver = US"TLSv1";
-  break;
-
-#ifdef TLS1_1_VERSION
-  case TLS1_1_VERSION:
-  ver = US"TLSv1.1";
-  break;
-#endif
+const uschar *ver;
 
-#ifdef TLS1_2_VERSION
-  case TLS1_2_VERSION:
-  ver = US"TLSv1.2";
-  break;
-#endif
-
-  default:
-  ver = US"UNKNOWN";
-  }
+ver = (const uschar *)SSL_get_version(ssl);
 
 c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
-SSL_CIPHER_get_bits(c, &tls_bits);
+SSL_CIPHER_get_bits(c, bits);
 
-string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
-  SSL_CIPHER_get_name(c), tls_bits);
-tls_cipher = cipherbuf;
+string_format(cipherbuf, bsize, "%s:%s:%u", ver,
+  SSL_CIPHER_get_name(c), *bits);
 
 DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
 }
@@ -651,19 +1194,21 @@ Arguments:
   host          NULL in a server; the remote host in a client
   optional      TRUE if called from a server for a host in tls_try_verify_hosts;
                 otherwise passed as FALSE
+  cert_vfy_cb  Callback function for certificate verification
 
 Returns:        OK/DEFER/FAIL
 */
 
 static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional)
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+    int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
 {
 uschar *expcerts, *expcrl;
 
 if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
   return DEFER;
 
-if (expcerts != NULL)
+if (expcerts != NULL && *expcerts != '\0')
   {
   struct stat statbuf;
   if (!SSL_CTX_set_default_verify_paths(sctx))
@@ -755,7 +1300,7 @@ if (expcerts != NULL)
 
   SSL_CTX_set_verify(sctx,
     SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
-    verify_callback);
+    cert_vfy_cb);
   }
 
 return OK;
@@ -773,11 +1318,6 @@ a TLS session.
 
 Arguments:
   require_ciphers   allowed ciphers
-  ------------------------------------------------------
-  require_mac      list of allowed MACs                 ) Not used
-  require_kx       list of allowed key_exchange methods )   for
-  require_proto    list of allowed protocols            ) OpenSSL
-  ------------------------------------------------------
 
 Returns:            OK on success
                     DEFER for errors before the start of the negotiation
@@ -786,16 +1326,16 @@ Returns:            OK on success
 */
 
 int
-tls_server_start(uschar *require_ciphers, uschar *require_mac,
-  uschar *require_kx, uschar *require_proto)
+tls_server_start(const uschar *require_ciphers)
 {
 int rc;
 uschar *expciphers;
 tls_ext_ctx_cb *cbinfo;
+static uschar cipherbuf[256];
 
 /* Check for previous activation */
 
-if (tls_active >= 0)
+if (tls_in.active >= 0)
   {
   tls_error(US"STARTTLS received after TLS started", NULL, US"");
   smtp_printf("554 Already in TLS\r\n");
@@ -805,23 +1345,28 @@ if (tls_active >= 0)
 /* Initialize the SSL library. If it fails, it will already have logged
 the error. */
 
-rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, NULL);
+rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
+#ifdef EXPERIMENTAL_OCSP
+    tls_ocsp_file,
+#endif
+    NULL, &server_static_cbinfo);
 if (rc != OK) return rc;
-cbinfo = static_cbinfo;
+cbinfo = server_static_cbinfo;
 
 if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
   return FAIL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
-are separated by underscores. So that I can use either form in my tests, and
-also for general convenience, we turn underscores into hyphens here. */
+were historically separated by underscores. So that I can use either form in my
+tests, and also for general convenience, we turn underscores into hyphens here.
+*/
 
 if (expciphers != NULL)
   {
   uschar *s = expciphers;
   while (*s != 0) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
-  if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+  if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
     return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
   cbinfo->server_cipher_list = expciphers;
   }
@@ -829,25 +1374,27 @@ if (expciphers != NULL)
 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */
 
-tls_certificate_verified = FALSE;
-verify_callback_called = FALSE;
+tls_in.certificate_verified = FALSE;
+server_verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
-  rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
+  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
+                       FALSE, verify_callback_server);
   if (rc != OK) return rc;
-  verify_optional = FALSE;
+  server_verify_optional = FALSE;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
-  rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
+  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
+                       TRUE, verify_callback_server);
   if (rc != OK) return rc;
-  verify_optional = TRUE;
+  server_verify_optional = TRUE;
   }
 
 /* Prepare for new connection */
 
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
+if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
 
 /* Warning: we used to SSL_clear(ssl) here, it was removed.
  *
@@ -868,8 +1415,8 @@ 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. */
 
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-if (!tls_on_connect)
+SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
+if (!tls_in.on_connect)
   {
   smtp_printf("220 TLS go ahead\r\n");
   fflush(smtp_out);
@@ -878,15 +1425,15 @@ if (!tls_on_connect)
 /* Now negotiate the TLS session. We put our own timer on it, since it seems
 that the OpenSSL library doesn't. */
 
-SSL_set_wfd(ssl, fileno(smtp_out));
-SSL_set_rfd(ssl, fileno(smtp_in));
-SSL_set_accept_state(ssl);
+SSL_set_wfd(server_ssl, fileno(smtp_out));
+SSL_set_rfd(server_ssl, fileno(smtp_in));
+SSL_set_accept_state(server_ssl);
 
 DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
 
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-rc = SSL_accept(ssl);
+rc = SSL_accept(server_ssl);
 alarm(0);
 
 if (rc <= 0)
@@ -903,16 +1450,27 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize things. */
 
-construct_cipher_name(ssl);
+construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
+tls_in.cipher = cipherbuf;
 
 DEBUG(D_tls)
   {
   uschar buf[2048];
-  if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)) != NULL)
+  if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
     debug_printf("Shared ciphers: %s\n", buf);
   }
 
+/* Record the certificate we presented */
+  {
+  X509 * crt = SSL_get_certificate(server_ssl);
+  tls_in.ourcert = crt ? X509_dup(crt) : NULL;
+  }
 
+/* Only used by the server-side tls (tls_in), including tls_getc.
+   Client-side (tls_out) reads (seem to?) go via
+   smtp_read_response()/ip_recv().
+   Hence no need to duplicate for _in and _out.
+ */
 ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
 ssl_xfer_eof = ssl_xfer_error = 0;
@@ -923,7 +1481,7 @@ receive_feof = tls_feof;
 receive_ferror = tls_ferror;
 receive_smtp_buffered = tls_smtp_buffered;
 
-tls_active = fileno(smtp_out);
+tls_in.active = fileno(smtp_out);
 return OK;
 }
 
@@ -941,18 +1499,7 @@ Argument:
   fd               the fd of the connection
   host             connected host (for messages)
   addr             the first address
-  dhparam          DH parameter file
-  certificate      certificate file
-  privatekey       private key file
-  verify_certs     file for certificate verify
-  crl              file containing CRL
-  require_ciphers  list of allowed ciphers
-  ------------------------------------------------------
-  require_mac      list of allowed MACs                 ) Not used
-  require_kx       list of allowed key_exchange methods )   for
-  require_proto    list of allowed protocols            ) OpenSSL
-  ------------------------------------------------------
-  timeout          startup timeout
+  ob               smtp transport options
 
 Returns:           OK on success
                    FAIL otherwise - note that tls_error() will not give DEFER
@@ -960,23 +1507,36 @@ Returns:           OK on success
 */
 
 int
-tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
-  uschar *certificate, uschar *privatekey, uschar *verify_certs, uschar *crl,
-  uschar *require_ciphers, uschar *require_mac, uschar *require_kx,
-  uschar *require_proto, int timeout)
+tls_client_start(int fd, host_item *host, address_item *addr,
+  void *v_ob)
 {
+smtp_transport_options_block * ob = v_ob;
 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(&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(host, dhparam, certificate, privatekey, addr);
+rc = tls_init(&client_ctx, host, NULL,
+    ob->tls_certificate, ob->tls_privatekey,
+#ifdef EXPERIMENTAL_OCSP
+    (void *)(long)request_ocsp,
+#endif
+    addr, &client_static_cbinfo);
 if (rc != OK) return rc;
 
-tls_certificate_verified = FALSE;
-verify_callback_called = FALSE;
+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
@@ -988,24 +1548,75 @@ if (expciphers != NULL)
   uschar *s = expciphers;
   while (*s != 0) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
-  if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+  if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
     return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
   }
 
-rc = setup_certs(ctx, verify_certs, crl, host, FALSE);
-if (rc != OK) return rc;
+/* 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
+   the specified host patterns if one of them is defined */
+if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
+    (verify_check_host(&ob->tls_verify_hosts) == OK))
+  {
+  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;
+  }
+else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+  {
+  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);
+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 (ob->tls_sni)
+  {
+  if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
+    return FAIL;
+  if (tls_out.sni == NULL)
+    {
+    DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
+    }
+  else if (!Ustrlen(tls_out.sni))
+    tls_out.sni = NULL;
+  else
+    {
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
+    SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+#else
+    DEBUG(D_tls)
+      debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
+          tls_out.sni);
+#endif
+    }
+  }
 
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(ssl, fd);
-SSL_set_connect_state(ssl);
+#ifdef EXPERIMENTAL_OCSP
+/* Request certificate status at connection-time.  If the server
+does OCSP stapling we will get the callback (set in tls_init()) */
+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
 
 /* 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);
-rc = SSL_connect(ssl);
+alarm(ob->command_timeout);
+rc = SSL_connect(client_ssl);
 alarm(0);
 
 if (rc <= 0)
@@ -1014,19 +1625,27 @@ if (rc <= 0)
 DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
 
 /* Beware anonymous ciphers which lead to server_cert being NULL */
-server_cert = SSL_get_peer_certificate (ssl);
+/*XXX server_cert is never freed... use X509_free() */
+server_cert = SSL_get_peer_certificate (client_ssl);
 if (server_cert)
   {
-  tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
+  tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
     CS txt, sizeof(txt));
-  tls_peerdn = txt;
+  tls_out.peerdn = txt;                /*XXX a static buffer... */
   }
 else
-  tls_peerdn = NULL;
+  tls_out.peerdn = NULL;
 
-construct_cipher_name(ssl);   /* Sets tls_cipher */
+construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
+tls_out.cipher = cipherbuf;
 
-tls_active = fd;
+/* Record the certificate we presented */
+  {
+  X509 * crt = SSL_get_certificate(client_ssl);
+  tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+  }
+
+tls_out.active = fd;
 return OK;
 }
 
@@ -1043,6 +1662,8 @@ it refills the buffer via the SSL reading function.
 
 Arguments:  none
 Returns:    the next character or EOF
+
+Only used by the server-side TLS.
 */
 
 int
@@ -1053,12 +1674,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
   int error;
   int inbytes;
 
-  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
     ssl_xfer_buffer, ssl_xfer_buffer_size);
 
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
-  error = SSL_get_error(ssl, inbytes);
+  inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
+  error = SSL_get_error(server_ssl, inbytes);
   alarm(0);
 
   /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
@@ -1075,11 +1696,13 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     receive_ferror = smtp_ferror;
     receive_smtp_buffered = smtp_buffered;
 
-    SSL_free(ssl);
-    ssl = NULL;
-    tls_active = -1;
-    tls_cipher = NULL;
-    tls_peerdn = NULL;
+    SSL_free(server_ssl);
+    server_ssl = NULL;
+    tls_in.active = -1;
+    tls_in.bits = 0;
+    tls_in.cipher = NULL;
+    tls_in.peerdn = NULL;
+    tls_in.sni = NULL;
 
     return smtp_getc();
     }
@@ -1126,11 +1749,14 @@ Arguments:
 
 Returns:    the number of bytes read
             -1 after a failed read
+
+Only used by the client-side TLS.
 */
 
 int
-tls_read(uschar *buff, size_t len)
+tls_read(BOOL is_server, uschar *buff, size_t len)
 {
+SSL *ssl = is_server ? server_ssl : client_ssl;
 int inbytes;
 int error;
 
@@ -1163,19 +1789,23 @@ return inbytes;
 
 /*
 Arguments:
+  is_server channel specifier
   buff      buffer of data
   len       number of bytes
 
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
+
+Used by both server-side and client-side TLS.
 */
 
 int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
 {
 int outbytes;
 int error;
 int left = len;
+SSL *ssl = is_server ? server_ssl : client_ssl;
 
 DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
 while (left > 0)
@@ -1200,6 +1830,11 @@ while (left > 0)
     log_write(0, LOG_MAIN, "SSL channel closed on write");
     return -1;
 
+    case SSL_ERROR_SYSCALL:
+    log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+      sender_fullhost ? sender_fullhost : US"<unknown>",
+      strerror(errno));
+
     default:
     log_write(0, LOG_MAIN, "SSL_write error %d", error);
     return -1;
@@ -1220,23 +1855,94 @@ would tamper with the SSL session in the parent process).
 
 Arguments:   TRUE if SSL_shutdown is to be called
 Returns:     nothing
+
+Used by both server-side and client-side TLS.
 */
 
 void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
 {
-if (tls_active < 0) return;  /* TLS was not active */
+SSL **sslp = is_server ? &server_ssl : &client_ssl;
+int *fdp = is_server ? &tls_in.active : &tls_out.active;
+
+if (*fdp < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
   DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
-  SSL_shutdown(ssl);
+  SSL_shutdown(*sslp);
+  }
+
+SSL_free(*sslp);
+*sslp = NULL;
+
+*fdp = -1;
+}
+
+
+
+
+/*************************************************
+*  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)
+{
+SSL_CTX *ctx;
+uschar *s, *expciphers, *err;
+
+/* this duplicates from tls_init(), we need a better "init just global
+state, for no specific purpose" singleton function of our own */
+
+SSL_load_error_strings();
+OpenSSL_add_ssl_algorithms();
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+/* SHA256 is becoming ever more popular. This makes sure it gets added to the
+list of available digests. */
+EVP_add_digest(EVP_sha256());
+#endif
+
+if (!(tls_require_ciphers && *tls_require_ciphers))
+  return NULL;
+
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+  return US"failed to expand tls_require_ciphers";
+
+if (!(expciphers && *expciphers))
+  return NULL;
+
+/* normalisation ripped from above */
+s = expciphers;
+while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+
+err = NULL;
+
+ctx = SSL_CTX_new(SSLv23_server_method());
+if (!ctx)
+  {
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
+  }
+
+DEBUG(D_tls)
+  debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers);
+
+if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+  {
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
   }
 
-SSL_free(ssl);
-ssl = NULL;
+SSL_CTX_free(ctx);
 
-tls_active = -1;
+return err;
 }
 
 
@@ -1252,6 +1958,11 @@ one version of OpenSSL but the run-time linker picks up another version,
 it can result in serious failures, including crashing with a SIGSEGV.  So
 report the version found by the compiler and the run-time version.
 
+Note: some OS vendors backport security fixes without changing the version
+number/string, and the version date remains unchanged.  The _build_ date
+will change, so we can more usefully assist with version diagnosis by also
+reporting the build date.
+
 Arguments:   a FILE* to print the results to
 Returns:     nothing
 */
@@ -1260,16 +1971,20 @@ void
 tls_version_report(FILE *f)
 {
 fprintf(f, "Library version: OpenSSL: Compile: %s\n"
-           "                          Runtime: %s\n",
+           "                          Runtime: %s\n"
+           "                                 : %s\n",
            OPENSSL_VERSION_TEXT,
-           SSLeay_version(SSLEAY_VERSION));
+           SSLeay_version(SSLEAY_VERSION),
+           SSLeay_version(SSLEAY_BUILT_ON));
+/* third line is 38 characters for the %s and the line is 73 chars long;
+the OpenSSL output includes a "built on: " prefix already. */
 }
 
 
 
 
 /*************************************************
-*        Pseudo-random number generation         *
+*            Random number generation            *
 *************************************************/
 
 /* Pseudo-random number generation.  The result is not expected to be
@@ -1284,16 +1999,30 @@ Returns     a random number in range [0, max-1]
 */
 
 int
-pseudo_random_number(int max)
+vaguely_random_number(int max)
 {
 unsigned int r;
 int i, needed_len;
+static pid_t pidlast = 0;
+pid_t pidnow;
 uschar *p;
 uschar smallbuf[sizeof(r)];
 
 if (max <= 1)
   return 0;
 
+pidnow = getpid();
+if (pidnow != pidlast)
+  {
+  /* Although OpenSSL documents that "OpenSSL makes sure that the PRNG state
+  is unique for each thread", this doesn't apparently apply across processes,
+  so our own warning from vaguely_random_number_fallback() applies here too.
+  Fix per PostgreSQL. */
+  if (pidlast != 0)
+    RAND_cleanup();
+  pidlast = pidnow;
+  }
+
 /* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */
 if (!RAND_status())
   {
@@ -1320,7 +2049,14 @@ if (i < needed_len)
   needed_len = i;
 
 /* We do not care if crypto-strong */
-(void) RAND_pseudo_bytes(smallbuf, needed_len);
+i = RAND_pseudo_bytes(smallbuf, needed_len);
+if (i < 0)
+  {
+  DEBUG(D_all)
+    debug_printf("OpenSSL RAND_pseudo_bytes() not supported by RAND method, using fallback.\n");
+  return vaguely_random_number_fallback(max);
+  }
+
 r = 0;
 for (p = smallbuf; needed_len; --needed_len, ++p)
   {
@@ -1358,7 +2094,9 @@ all options unless explicitly for DTLS, let the administrator choose which
 to apply.
 
 This list is current as of:
-  ==>  1.0.1b  <==  */
+  ==>  1.0.1b  <==
+Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
+*/
 static struct exim_openssl_option exim_openssl_options[] = {
 /* KEEP SORTED ALPHABETICALLY! */
 #ifdef SSL_OP_ALL
@@ -1423,6 +2161,9 @@ static struct exim_openssl_option exim_openssl_options[] = {
 #ifdef SSL_OP_NO_TLSv1_2
   { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
 #endif
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+  { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
+#endif
 #ifdef SSL_OP_SINGLE_DH_USE
   { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
 #endif
@@ -1497,8 +2238,11 @@ uschar keep_c;
 BOOL adding, item_parsed;
 
 result = 0L;
-/* Prior to 4.78 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
+/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
  * from default because it increases BEAST susceptibility. */
+#ifdef SSL_OP_NO_SSLv2
+result |= SSL_OP_NO_SSLv2;
+#endif
 
 if (option_spec == NULL)
   {
@@ -1541,4 +2285,6 @@ for (s=option_spec; *s != '\0'; /**/)
 return TRUE;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of tls-openssl.c */