GnuTLS control constants exposed to Makefile.
[exim.git] / src / src / tls-openssl.c
index a4a8e8b9f0a25e4d1e5c4173e993adb200b94d8b..1938d2fb7a72ad1bc4f8f299621f5b04e9952294 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/tls-openssl.c,v 1.3 2005/01/04 10:00:42 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL
@@ -22,12 +20,20 @@ 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
 
 /* Structure for collecting random data for seeding. */
 
 typedef struct randstuff {
-  time_t t;
-  pid_t  p;
+  struct timeval tv;
+  pid_t          p;
 } randstuff;
 
 /* Local static variables */
@@ -36,6 +42,7 @@ static BOOL 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;
 
 static char ssl_errstring[256];
@@ -43,8 +50,37 @@ static char ssl_errstring[256];
 static int  ssl_session_timeout = 200;
 static BOOL verify_optional = FALSE;
 
+static BOOL    reexpand_tls_files_for_sni = FALSE;
 
 
+typedef struct tls_ext_ctx_cb {
+  uschar *certificate;
+  uschar *privatekey;
+#ifdef EXPERIMENTAL_OCSP
+  uschar *ocsp_file;
+  uschar *ocsp_file_expanded;
+  OCSP_RESPONSE *ocsp_response;
+#endif
+  uschar *dhparam;
+  /* these are cached from first expand */
+  uschar *server_cipher_list;
+  /* only passed down to tls_error: */
+  host_item *host;
+} 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;
+
+static int
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
+
+/* Callbacks */
+static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
+#ifdef EXPERIMENTAL_OCSP
+static int tls_stapling_cb(SSL *s, void *arg);
+#endif
 
 
 /*************************************************
@@ -62,25 +98,33 @@ Argument:
   prefix    text to include in the logged error
   host      NULL if setting up a server;
             the connected host if setting up a client
+  msg       error message or NULL if we should ask OpenSSL
 
 Returns:    OK/DEFER/FAIL
 */
 
 static int
-tls_error(uschar *prefix, host_item *host)
+tls_error(uschar *prefix, host_item *host, uschar *msg)
 {
-ERR_error_string(ERR_get_error(), ssl_errstring);
+if (msg == NULL)
+  {
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  msg = (uschar *)ssl_errstring;
+  }
+
 if (host == NULL)
   {
-  log_write(0, LOG_MAIN, "TLS error on connection from %s (%s): %s",
-    (sender_fullhost != NULL)? sender_fullhost : US"local process",
-    prefix, ssl_errstring);
+  uschar *conn_info = smtp_get_connection_info();
+  if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
+    conn_info += 5;
+  log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
+    conn_info, prefix, msg);
   return DEFER;
   }
 else
   {
   log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s",
-    host->name, host->address, prefix, ssl_errstring);
+    host->name, host->address, prefix, msg);
   return FAIL;
   }
 }
@@ -182,9 +226,6 @@ else
   tls_peerdn = txt;
   }
 
-
-debug_printf("+++verify_callback_called=%d\n", verify_callback_called);
-
 if (!verify_callback_called) tls_certificate_verified = TRUE;
 verify_callback_called = TRUE;
 
@@ -198,8 +239,8 @@ return 1;   /* accept */
 *************************************************/
 
 /* The SSL library functions call this from time to time to indicate what they
-are doing. We copy the string to the debugging output when the level is high
-enough.
+are doing. We copy the string to the debugging output when TLS debugging has
+been requested.
 
 Arguments:
   s         the SSL connection
@@ -227,12 +268,13 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
 
 Arguments:
   dhparam   DH parameter file
+  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)
+init_dh(uschar *dhparam, host_item *host)
 {
 BOOL yield = TRUE;
 BIO *bio;
@@ -246,16 +288,16 @@ if (dhexpanded == NULL) return TRUE;
 
 if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL)
   {
-  log_write(0, LOG_MAIN, "DH: could not read %s: %s", dhexpanded,
-    strerror(errno));
+  tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
+    host, (uschar *)strerror(errno));
   yield = FALSE;
   }
 else
   {
   if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL)
     {
-    log_write(0, LOG_MAIN, "DH: could not load params from %s",
-      dhexpanded);
+    tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
+      host, NULL);
     yield = FALSE;
     }
   else
@@ -275,6 +317,338 @@ return yield;
 
 
 
+#ifdef EXPERIMENTAL_OCSP
+/*************************************************
+*       Load OCSP information into state         *
+*************************************************/
+
+/* Called to load the 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->ocsp_file_expanded = string_copy(expanded);
+if (cbinfo->ocsp_response)
+  {
+  OCSP_RESPONSE_free(cbinfo->ocsp_response);
+  cbinfo->ocsp_response = NULL;
+  }
+
+bio = BIO_new_file(CS cbinfo->ocsp_file_expanded, "rb");
+if (!bio)
+  {
+  DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
+      cbinfo->ocsp_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);
+  return;
+  }
+
+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");
+  return;
+  }
+
+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);
+  }
+  return;
+  }
+
+/* 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");
+  return;
+  }
+
+status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
+/* how does this status differ from the one above? */
+if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+  {
+  DEBUG(D_tls) debug_printf("OCSP response not valid (take 2): %s (%d)\n",
+      OCSP_response_status_str(status), status);
+  return;
+  }
+
+if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+  {
+  DEBUG(D_tls) debug_printf("OCSP status invalid times.\n");
+  return;
+  }
+
+cbinfo->ocsp_response = resp;
+}
+#endif
+
+
+
+
+/*************************************************
+*        Expand key and cert file specs          *
+*************************************************/
+
+/* Called once during tls_init and possibly againt during TLS setup, for a
+new context, if Server Name Indication was used and tls_sni was seen in
+the certificate string.
+
+Arguments:
+  sctx            the SSL_CTX* to update
+  cbinfo          various parts of session state
+
+Returns:          OK/DEFER/FAIL
+*/
+
+static int
+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"))
+  reexpand_tls_files_for_sni = TRUE;
+
+if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
+  return DEFER;
+
+if (expanded != NULL)
+  {
+  DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
+  if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
+    return tls_error(string_sprintf(
+      "SSL_CTX_use_certificate_chain_file file=%s", expanded),
+        cbinfo->host, NULL);
+  }
+
+if (cbinfo->privatekey != NULL &&
+    !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
+  return DEFER;
+
+/* If expansion was forced to fail, key_expanded will be NULL. If the result
+of the expansion is an empty string, ignore it also, and assume the private
+key is in the same file as the certificate. */
+
+if (expanded != NULL && *expanded != 0)
+  {
+  DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
+  if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
+    return tls_error(string_sprintf(
+      "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
+  }
+
+#ifdef EXPERIMENTAL_OCSP
+if (cbinfo->ocsp_file != NULL)
+  {
+  if (!expand_check(cbinfo->ocsp_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->ocsp_file_expanded &&
+        (Ustrcmp(expanded, cbinfo->ocsp_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;
+}
+
+
+
+
+/*************************************************
+*            Callback to handle SNI              *
+*************************************************/
+
+/* Called when acting as server during the TLS session setup if a Server Name
+Indication extension was sent by the client.
+
+API documentation is OpenSSL s_server.c implementation.
+
+Arguments:
+  s               SSL* of the current session
+  ad              unknown (part of OpenSSL API) (unused)
+  arg             Callback of "our" registered data
+
+Returns:          SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
+*/
+
+static int
+tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg)
+{
+const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
+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("Received TLS SNI \"%s\"%s\n", servername,
+    reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)");
+
+/* Make the extension value available for expansion */
+store_pool = POOL_PERM;
+tls_sni = string_copy(US servername);
+store_pool = old_pool;
+
+if (!reexpand_tls_files_for_sni)
+  return SSL_TLSEXT_ERR_OK;
+
+/* Can't find an SSL_CTX_clone() or equivalent, so we do it manually;
+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)
+  {
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
+  return SSL_TLSEXT_ERR_NOACK;
+  }
+
+/* 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);
+if (cbinfo->server_cipher_list)
+  SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list);
+#ifdef EXPERIMENTAL_OCSP
+if (cbinfo->ocsp_file)
+  {
+  SSL_CTX_set_tlsext_status_cb(ctx_sni, tls_stapling_cb);
+  SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
+  }
+#endif
+
+rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
+if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+
+/* do this after setup_certs, because this can require the certs for verifying
+OCSP information. */
+rc = tls_expand_session_files(ctx_sni, cbinfo);
+if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+
+DEBUG(D_tls) debug_printf("Switching SSL context.\n");
+SSL_set_SSL_CTX(s, ctx_sni);
+
+return SSL_TLSEXT_ERR_OK;
+}
+
+
+
+
+#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_stapling_cb(SSL *s, void *arg)
+{
+const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
+uschar *response_der;
+int response_der_len;
+
+DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.\n",
+    cbinfo->ocsp_response ? "have" : "lack");
+if (!cbinfo->ocsp_response)
+  return SSL_TLSEXT_ERR_NOACK;
+
+response_der = NULL;
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->ocsp_response, &response_der);
+if (response_der_len <= 0)
+  return SSL_TLSEXT_ERR_NOACK;
+
+SSL_set_tlsext_status_ocsp_resp(ssl, response_der, response_der_len);
+return SSL_TLSEXT_ERR_OK;
+}
+
+#endif /* EXPERIMENTAL_OCSP */
+
+
+
+
 /*************************************************
 *            Initialize for TLS                  *
 *************************************************/
@@ -293,18 +667,42 @@ Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_init(host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey,
+tls_init(host_item *host, uschar *dhparam, uschar *certificate,
+  uschar *privatekey,
+#ifdef EXPERIMENTAL_OCSP
+  uschar *ocsp_file,
+#endif
   address_item *addr)
 {
+long init_options;
+int rc;
+BOOL okay;
+tls_ext_ctx_cb *cbinfo;
+
+cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
+cbinfo->certificate = certificate;
+cbinfo->privatekey = privatekey;
+#ifdef EXPERIMENTAL_OCSP
+cbinfo->ocsp_file = ocsp_file;
+#endif
+cbinfo->dhparam = dhparam;
+cbinfo->host = host;
+
 SSL_load_error_strings();          /* basic set up */
 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
+
 /* Create a context */
 
 ctx = SSL_CTX_new((host == NULL)?
   SSLv23_server_method() : SSLv23_client_method());
 
-if (ctx == NULL) return tls_error(US"SSL_CTX_new", host);
+if (ctx == 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
@@ -317,7 +715,7 @@ afterwards. */
 if (!RAND_status())
   {
   randstuff r;
-  r.t = time(NULL);
+  gettimeofday(&r.tv, NULL);
   r.p = getpid();
 
   RAND_seed((uschar *)(&r), sizeof(r));
@@ -325,78 +723,71 @@ if (!RAND_status())
   if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
 
   if (!RAND_status())
-    {
-    if (host == NULL)
-      {
-      log_write(0, LOG_MAIN, "TLS error on connection from %s: "
-        "unable to seed random number generator",
-        (sender_fullhost != NULL)? sender_fullhost : US"local process");
-      return DEFER;
-      }
-    else
-      {
-      log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
-        "unable to seed random number generator",
-        host->name, host->address);
-      return FAIL;
-      }
-    }
+    return tls_error(US"RAND_status", host,
+      US"unable to seed random number generator");
   }
 
 /* Set up the information callback, which outputs if debugging is at a suitable
 level. */
 
-if (!(SSL_CTX_set_info_callback(ctx, (void (*)())info_callback)))
-  return tls_error(US"SSL_CTX_set_info_callback", host);
+SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
 
-/* The following patch was supplied by Robert Roselius */
+/* Automatically re-try reads/writes after renegotiation. */
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
 
-#if OPENSSL_VERSION_NUMBER > 0x00906040L
-/* Enable client-bug workaround.
-   Versions of OpenSSL as of 0.9.6d include a "CBC countermeasure" feature,
-   which causes problems with some clients (such as the Certicom SSL Plus
-   library used by Eudora).  This option, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS,
-   disables the coutermeasure allowing Eudora to connect.
-   Some poppers and MTAs use SSL_OP_ALL, which enables all such bug
-   workarounds. */
-/* XXX (Silently?) ignore failure here? XXX*/
+/* Apply administrator-supplied work-arounds.
+Historically we applied just one requested option,
+SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, but when bug 994 requested a second, we
+moved to an administrator-controlled list of options to specify and
+grandfathered in the first one as the default value for "openssl_options".
 
-if (!(SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)))
-  return tls_error(US"SSL_CTX_set_option", host);
-#endif
+No OpenSSL version number checks: the options we accept depend upon the
+availability of the option value macros from OpenSSL.  */
 
-/* Initialize with DH parameters if supplied */
+okay = tls_openssl_options_parse(openssl_options, &init_options);
+if (!okay)
+  return tls_error(US"openssl_options parsing failed", host, NULL);
 
-if (!init_dh(dhparam)) return DEFER;
+if (init_options)
+  {
+  DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
+  if (!(SSL_CTX_set_options(ctx, init_options)))
+    return tls_error(string_sprintf(
+          "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+  }
+else
+  DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
-/* Set up certificate and key */
+/* Initialize with DH parameters if supplied */
 
-if (certificate != NULL)
-  {
-  uschar *expanded;
-  if (!expand_check(certificate, US"tls_certificate", &expanded))
-    return DEFER;
+if (!init_dh(dhparam, host)) return DEFER;
 
-  if (expanded != NULL)
-    {
-    DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
-    if (!SSL_CTX_use_certificate_chain_file(ctx, CS expanded))
-      return tls_error(string_sprintf(
-        "SSL_CTX_use_certificate_chain_file file=%s", expanded), host);
-    }
+/* Set up certificate and key (and perhaps OCSP info) */
 
-  if (privatekey != NULL &&
-      !expand_check(privatekey, US"tls_privatekey", &expanded))
-    return DEFER;
+rc = tls_expand_session_files(ctx, cbinfo);
+if (rc != OK) return rc;
 
-  if (expanded != NULL)
+/* If we need to handle SNI, do so */
+#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
+if (host == NULL)
+  {
+#ifdef EXPERIMENTAL_OCSP
+  /* We check ocsp_file, not ocsp_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->ocsp_file)
     {
-    DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
-    if (!SSL_CTX_use_PrivateKey_file(ctx, CS expanded, SSL_FILETYPE_PEM))
-      return tls_error(string_sprintf(
-        "SSL_CTX_use_PrivateKey_file file=%s", expanded), host);
+    SSL_CTX_set_tlsext_status_cb(ctx, tls_stapling_cb);
+    SSL_CTX_set_tlsext_status_arg(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(ctx, tls_servername_cb);
+  SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
   }
+#endif
 
 /* Set up the RSA callback */
 
@@ -406,6 +797,9 @@ SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
 
 SSL_CTX_set_timeout(ctx, ssl_session_timeout);
 DEBUG(D_tls) debug_printf("Initialized TLS\n");
+
+static_cbinfo = cbinfo;
+
 return OK;
 }
 
@@ -427,9 +821,11 @@ static void
 construct_cipher_name(SSL *ssl)
 {
 static uschar cipherbuf[256];
-SSL_CIPHER *c;
+/* 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;
-int bits;
 
 switch (ssl->session->ssl_version)
   {
@@ -445,15 +841,27 @@ switch (ssl->session->ssl_version)
   ver = US"TLSv1";
   break;
 
+#ifdef TLS1_1_VERSION
+  case TLS1_1_VERSION:
+  ver = US"TLSv1.1";
+  break;
+#endif
+
+#ifdef TLS1_2_VERSION
+  case TLS1_2_VERSION:
+  ver = US"TLSv1.2";
+  break;
+#endif
+
   default:
   ver = US"UNKNOWN";
   }
 
-c = SSL_get_current_cipher(ssl);
-SSL_CIPHER_get_bits(c, &bits);
+c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
+SSL_CIPHER_get_bits(c, &tls_bits);
 
 string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
-  SSL_CIPHER_get_name(c), bits);
+  SSL_CIPHER_get_name(c), tls_bits);
 tls_cipher = cipherbuf;
 
 DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
@@ -470,6 +878,7 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
 /* Called by both client and server startup
 
 Arguments:
+  sctx          SSL_CTX* to initialise
   certs         certs file or NULL
   crl           CRL file or NULL
   host          NULL in a server; the remote host in a client
@@ -480,7 +889,7 @@ Returns:        OK/DEFER/FAIL
 */
 
 static int
-setup_certs(uschar *certs, uschar *crl, host_item *host, BOOL optional)
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional)
 {
 uschar *expcerts, *expcrl;
 
@@ -490,8 +899,8 @@ if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
 if (expcerts != NULL)
   {
   struct stat statbuf;
-  if (!SSL_CTX_set_default_verify_paths(ctx))
-    return tls_error(US"SSL_CTX_set_default_verify_paths", host);
+  if (!SSL_CTX_set_default_verify_paths(sctx))
+    return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
 
   if (Ustat(expcerts, &statbuf) < 0)
     {
@@ -513,12 +922,12 @@ if (expcerts != NULL)
     says no certificate was supplied.) But this is better. */
 
     if ((file == NULL || statbuf.st_size > 0) &&
-          !SSL_CTX_load_verify_locations(ctx, CS file, CS dir))
-      return tls_error(US"SSL_CTX_load_verify_locations", host);
+          !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
+      return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
 
     if (file != NULL)
       {
-      SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CS file));
+      SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file));
       }
     }
 
@@ -526,41 +935,58 @@ if (expcerts != NULL)
 
   #if OPENSSL_VERSION_NUMBER > 0x00907000L
 
+  /* This bit of code is now the version supplied by Lars Mainka. (I have
+   * merely reformatted it into the Exim code style.)
+
+   * "From here I changed the code to add support for multiple crl's
+   * in pem format in one file or to support hashed directory entries in
+   * pem format instead of a file. This method now uses the library function
+   * X509_STORE_load_locations to add the CRL location to the SSL context.
+   * OpenSSL will then handle the verify against CA certs and CRLs by
+   * itself in the verify callback." */
+
   if (!expand_check(crl, US"tls_crl", &expcrl)) return DEFER;
   if (expcrl != NULL && *expcrl != 0)
     {
-    BIO *crl_bio;
-    X509_CRL *crl_x509;
-    X509_STORE *cvstore;
-
-    cvstore = SSL_CTX_get_cert_store(ctx);  /* cert validation store */
-
-    crl_bio = BIO_new(BIO_s_file_internal());
-    if (crl_bio != NULL)
+    struct stat statbufcrl;
+    if (Ustat(expcrl, &statbufcrl) < 0)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+        "failed to stat %s for certificates revocation lists", expcrl);
+      return DEFER;
+      }
+    else
       {
-      if (BIO_read_filename(crl_bio, expcrl))
+      /* is it a file or directory? */
+      uschar *file, *dir;
+      X509_STORE *cvstore = SSL_CTX_get_cert_store(sctx);
+      if ((statbufcrl.st_mode & S_IFMT) == S_IFDIR)
         {
-        crl_x509 = PEM_read_bio_X509_CRL(crl_bio, NULL, NULL, NULL);
-        BIO_free(crl_bio);
-        X509_STORE_add_crl(cvstore, crl_x509);
-        X509_CRL_free(crl_x509);
-        X509_STORE_set_flags(cvstore,
-          X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+        file = NULL;
+        dir = expcrl;
+        DEBUG(D_tls) debug_printf("SSL CRL value is a directory %s\n", dir);
         }
       else
         {
-        BIO_free(crl_bio);
-        return tls_error(US"BIO_read_filename", host);
+        file = expcrl;
+        dir = NULL;
+        DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
         }
+      if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
+        return tls_error(US"X509_STORE_load_locations", host, NULL);
+
+      /* setting the flags to check against the complete crl chain */
+
+      X509_STORE_set_flags(cvstore,
+        X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
       }
-    else return tls_error(US"BIO_new", host);
     }
 
   #endif  /* OPENSSL_VERSION_NUMBER > 0x00907000L */
 
   /* If verification is optional, don't fail if no certificate */
 
-  SSL_CTX_set_verify(ctx,
+  SSL_CTX_set_verify(sctx,
     SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
     verify_callback);
   }
@@ -588,18 +1014,17 @@ Returns:            OK on success
 */
 
 int
-tls_server_start(uschar *require_ciphers)
+tls_server_start(const uschar *require_ciphers)
 {
 int rc;
 uschar *expciphers;
+tls_ext_ctx_cb *cbinfo;
 
 /* Check for previous activation */
 
 if (tls_active >= 0)
   {
-  log_write(0, LOG_MAIN, "STARTTLS received in already encrypted "
-    "connection from %s",
-    (sender_fullhost != NULL)? sender_fullhost : US"local process");
+  tls_error(US"STARTTLS received after TLS started", NULL, US"");
   smtp_printf("554 Already in TLS\r\n");
   return FAIL;
   }
@@ -607,15 +1032,21 @@ 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(NULL, tls_dhparam, tls_certificate, tls_privatekey,
+#ifdef EXPERIMENTAL_OCSP
+    tls_ocsp_file,
+#endif
+    NULL);
 if (rc != OK) return rc;
+cbinfo = 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)
   {
@@ -623,7 +1054,8 @@ if (expciphers != NULL)
   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))
-    return tls_error(US"SSL_CTX_set_cipher_list", NULL);
+    return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
+  cbinfo->server_cipher_list = expciphers;
   }
 
 /* If this is a host for which certificate verification is mandatory or
@@ -634,21 +1066,33 @@ verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
-  rc = setup_certs(tls_verify_certificates, tls_crl, NULL, FALSE);
+  rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
   if (rc != OK) return rc;
   verify_optional = FALSE;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
-  rc = setup_certs(tls_verify_certificates, tls_crl, NULL, TRUE);
+  rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
   if (rc != OK) return rc;
   verify_optional = TRUE;
   }
 
 /* Prepare for new connection */
 
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL);
-SSL_clear(ssl);
+if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
+
+/* Warning: we used to SSL_clear(ssl) here, it was removed.
+ *
+ * With the SSL_clear(), we get strange interoperability bugs with
+ * OpenSSL 1.0.1b and TLS1.1/1.2.  It looks as though this may be a bug in
+ * OpenSSL itself, as a clear should not lead to inability to follow protocols.
+ *
+ * The SSL_clear() call is to let an existing SSL* be reused, typically after
+ * session shutdown.  In this case, we have a brand new object and there's no
+ * obvious reason to immediately clear it.  I'm guessing that this was
+ * originally added because of incomplete initialisation which the clear fixed,
+ * in some historic release.
+ */
 
 /* Set context and tell client to go ahead, except in the case of TLS startup
 on connection, where outputting anything now upsets the clients and tends to
@@ -666,7 +1110,8 @@ 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_fd(ssl, fileno(smtp_out));
+SSL_set_wfd(ssl, fileno(smtp_out));
+SSL_set_rfd(ssl, fileno(smtp_in));
 SSL_set_accept_state(ssl);
 
 DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
@@ -678,11 +1123,10 @@ alarm(0);
 
 if (rc <= 0)
   {
-  if (sigalrm_seen) Ustrcpy(ssl_errstring, "timed out");
-    else ERR_error_string(ERR_get_error(), ssl_errstring);
-  log_write(0, LOG_MAIN, "TLS error on connection from %s (SSL_accept): %s",
-    (sender_fullhost != NULL)? sender_fullhost : US"local process",
-    ssl_errstring);
+  tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
+  if (ERR_get_error() == 0)
+    log_write(0, LOG_MAIN,
+        "TLS client disconnected cleanly (rejected our certificate?)");
   return FAIL;
   }
 
@@ -709,6 +1153,7 @@ receive_getc = tls_getc;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
+receive_smtp_buffered = tls_smtp_buffered;
 
 tls_active = fileno(smtp_out);
 return OK;
@@ -727,12 +1172,15 @@ return OK;
 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
+  sni              TLS SNI to send to remote host
   verify_certs     file for certificate verify
   crl              file containing CRL
   require_ciphers  list of allowed ciphers
+  timeout          startup timeout
 
 Returns:           OK on success
                    FAIL otherwise - note that tls_error() will not give DEFER
@@ -741,7 +1189,8 @@ 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 *certificate, uschar *privatekey, uschar *sni,
+  uschar *verify_certs, uschar *crl,
   uschar *require_ciphers, int timeout)
 {
 static uschar txt[256];
@@ -749,7 +1198,11 @@ uschar *expciphers;
 X509* server_cert;
 int rc;
 
-rc = tls_init(host, dhparam, certificate, privatekey, addr);
+rc = tls_init(host, dhparam, certificate, privatekey,
+#ifdef EXPERIMENTAL_OCSP
+    NULL,
+#endif
+    addr);
 if (rc != OK) return rc;
 
 tls_certificate_verified = FALSE;
@@ -768,17 +1221,30 @@ if (expciphers != NULL)
   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))
-    return tls_error(US"SSL_CTX_set_cipher_list", host);
+    return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
   }
 
-rc = setup_certs(verify_certs, crl, host, FALSE);
+rc = setup_certs(ctx, verify_certs, crl, host, FALSE);
 if (rc != OK) return rc;
 
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host);
+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);
 
+if (sni)
+  {
+  if (!expand_check(sni, US"tls_sni", &tls_sni))
+    return FAIL;
+  if (!Ustrlen(tls_sni))
+    tls_sni = NULL;
+  else
+    {
+    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni);
+    SSL_set_tlsext_host_name(ssl, tls_sni);
+    }
+  }
+
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
@@ -788,22 +1254,20 @@ rc = SSL_connect(ssl);
 alarm(0);
 
 if (rc <= 0)
-  {
-  if (sigalrm_seen)
-    {
-    log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
-      "SSL_connect timed out", host->name, host->address);
-    return FAIL;
-    }
-  else return tls_error(US"SSL_connect", host);
-  }
+  return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
 
 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);
-tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
-  CS txt, sizeof(txt));
-tls_peerdn = txt;
+if (server_cert)
+  {
+  tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
+    CS txt, sizeof(txt));
+  tls_peerdn = txt;
+  }
+else
+  tls_peerdn = NULL;
 
 construct_cipher_name(ssl);   /* Sets tls_cipher */
 
@@ -834,8 +1298,8 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
   int error;
   int inbytes;
 
-  DEBUG(D_tls) debug_printf("Calling SSL_read(%lx, %lx, %u)\n", (long)ssl,
-    (long)ssl_xfer_buffer, ssl_xfer_buffer_size);
+  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", 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);
@@ -854,18 +1318,29 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     receive_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
+    receive_smtp_buffered = smtp_buffered;
 
     SSL_free(ssl);
     ssl = NULL;
     tls_active = -1;
+    tls_bits = 0;
     tls_cipher = NULL;
     tls_peerdn = NULL;
+    tls_sni = NULL;
 
     return smtp_getc();
     }
 
   /* Handle genuine errors */
 
+  else if (error == SSL_ERROR_SSL)
+    {
+    ERR_error_string(ERR_get_error(), ssl_errstring);
+    log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
+    ssl_xfer_error = 1;
+    return EOF;
+    }
+
   else if (error != SSL_ERROR_NONE)
     {
     DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
@@ -873,6 +1348,9 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     return EOF;
     }
 
+#ifndef DISABLE_DKIM
+  dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+#endif
   ssl_xfer_buffer_hwm = inbytes;
   ssl_xfer_buffer_lwm = 0;
   }
@@ -903,8 +1381,8 @@ tls_read(uschar *buff, size_t len)
 int inbytes;
 int error;
 
-DEBUG(D_tls) debug_printf("Calling SSL_read(%lx, %lx, %u)\n", (long)ssl,
-  (long)buff, (unsigned int)len);
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+  buff, (unsigned int)len);
 
 inbytes = SSL_read(ssl, CS buff, len);
 error = SSL_get_error(ssl, inbytes);
@@ -946,10 +1424,10 @@ int outbytes;
 int error;
 int left = len;
 
-DEBUG(D_tls) debug_printf("tls_do_write(%lx, %d)\n", (long)buff, left);
+DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
 while (left > 0)
   {
-  DEBUG(D_tls) debug_printf("SSL_write(SSL, %lx, %d)\n", (long)buff, left);
+  DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
   outbytes = SSL_write(ssl, CS buff, left);
   error = SSL_get_error(ssl, outbytes);
   DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
@@ -1008,4 +1486,313 @@ ssl = NULL;
 tls_active = -1;
 }
 
+
+
+
+/*************************************************
+*         Report the library versions.           *
+*************************************************/
+
+/* There have historically been some issues with binary compatibility in
+OpenSSL libraries; if Exim (like many other applications) is built against
+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.
+
+Arguments:   a FILE* to print the results to
+Returns:     nothing
+*/
+
+void
+tls_version_report(FILE *f)
+{
+fprintf(f, "Library version: OpenSSL: Compile: %s\n"
+           "                          Runtime: %s\n",
+           OPENSSL_VERSION_TEXT,
+           SSLeay_version(SSLEAY_VERSION));
+}
+
+
+
+
+/*************************************************
+*            Random number generation            *
+*************************************************/
+
+/* Pseudo-random number generation.  The result is not expected to be
+cryptographically strong but not so weak that someone will shoot themselves
+in the foot using it as a nonce in input in some email header scheme or
+whatever weirdness they'll twist this into.  The result should handle fork()
+and avoid repeating sequences.  OpenSSL handles that for us.
+
+Arguments:
+  max       range maximum
+Returns     a random number in range [0, max-1]
+*/
+
+int
+vaguely_random_number(int max)
+{
+unsigned int r;
+int i, needed_len;
+uschar *p;
+uschar smallbuf[sizeof(r)];
+
+if (max <= 1)
+  return 0;
+
+/* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */
+if (!RAND_status())
+  {
+  randstuff r;
+  gettimeofday(&r.tv, NULL);
+  r.p = getpid();
+
+  RAND_seed((uschar *)(&r), sizeof(r));
+  }
+/* We're after pseudo-random, not random; if we still don't have enough data
+in the internal PRNG then our options are limited.  We could sleep and hope
+for entropy to come along (prayer technique) but if the system is so depleted
+in the first place then something is likely to just keep taking it.  Instead,
+we'll just take whatever little bit of pseudo-random we can still manage to
+get. */
+
+needed_len = sizeof(r);
+/* Don't take 8 times more entropy than needed if int is 8 octets and we were
+asked for a number less than 10. */
+for (r = max, i = 0; r; ++i)
+  r >>= 1;
+i = (i + 7) / 8;
+if (i < needed_len)
+  needed_len = i;
+
+/* We do not care if crypto-strong */
+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)
+  {
+  r *= 256;
+  r += *p;
+  }
+
+/* We don't particularly care about weighted results; if someone wants
+smooth distribution and cares enough then they should submit a patch then. */
+return r % max;
+}
+
+
+
+
+/*************************************************
+*        OpenSSL option parse                    *
+*************************************************/
+
+/* Parse one option for tls_openssl_options_parse below
+
+Arguments:
+  name    one option name
+  value   place to store a value for it
+Returns   success or failure in parsing
+*/
+
+struct exim_openssl_option {
+  uschar *name;
+  long    value;
+};
+/* We could use a macro to expand, but we need the ifdef and not all the
+options document which version they were introduced in.  Policylet: include
+all options unless explicitly for DTLS, let the administrator choose which
+to apply.
+
+This list is current as of:
+  ==>  1.0.1b  <==  */
+static struct exim_openssl_option exim_openssl_options[] = {
+/* KEEP SORTED ALPHABETICALLY! */
+#ifdef SSL_OP_ALL
+  { US"all", SSL_OP_ALL },
+#endif
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+  { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+  { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
+#endif
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+  { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
+#endif
+#ifdef SSL_OP_EPHEMERAL_RSA
+  { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
+#endif
+#ifdef SSL_OP_LEGACY_SERVER_CONNECT
+  { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
+#endif
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+  { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
+#endif
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+  { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
+#endif
+#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
+  { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
+#endif
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+  { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
+#endif
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+  { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
+#endif
+#ifdef SSL_OP_NO_COMPRESSION
+  { US"no_compression", SSL_OP_NO_COMPRESSION },
+#endif
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+  { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_NO_SSLv2
+  { US"no_sslv2", SSL_OP_NO_SSLv2 },
+#endif
+#ifdef SSL_OP_NO_SSLv3
+  { US"no_sslv3", SSL_OP_NO_SSLv3 },
+#endif
+#ifdef SSL_OP_NO_TICKET
+  { US"no_ticket", SSL_OP_NO_TICKET },
+#endif
+#ifdef SSL_OP_NO_TLSv1
+  { US"no_tlsv1", SSL_OP_NO_TLSv1 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_1
+#if SSL_OP_NO_TLSv1_1 == 0x00000400L
+  /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
+#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
+#else
+  { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
+#endif
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+  { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+  { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
+#endif
+#ifdef SSL_OP_SINGLE_ECDH_USE
+  { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
+#endif
+#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+  { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
+#endif
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+  { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
+#endif
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+  { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
+#endif
+#ifdef SSL_OP_TLS_D5_BUG
+  { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
+#endif
+#ifdef SSL_OP_TLS_ROLLBACK_BUG
+  { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
+#endif
+};
+static int exim_openssl_options_size =
+  sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option);
+
+
+static BOOL
+tls_openssl_one_option_parse(uschar *name, long *value)
+{
+int first = 0;
+int last = exim_openssl_options_size;
+while (last > first)
+  {
+  int middle = (first + last)/2;
+  int c = Ustrcmp(name, exim_openssl_options[middle].name);
+  if (c == 0)
+    {
+    *value = exim_openssl_options[middle].value;
+    return TRUE;
+    }
+  else if (c > 0)
+    first = middle + 1;
+  else
+    last = middle;
+  }
+return FALSE;
+}
+
+
+
+
+/*************************************************
+*        OpenSSL option parsing logic            *
+*************************************************/
+
+/* OpenSSL has a number of compatibility options which an administrator might
+reasonably wish to set.  Interpret a list similarly to decode_bits(), so that
+we look like log_selector.
+
+Arguments:
+  option_spec  the administrator-supplied string of options
+  results      ptr to long storage for the options bitmap
+Returns        success or failure
+*/
+
+BOOL
+tls_openssl_options_parse(uschar *option_spec, long *results)
+{
+long result, item;
+uschar *s, *end;
+uschar keep_c;
+BOOL adding, item_parsed;
+
+result = 0L;
+/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
+ * from default because it increases BEAST susceptibility. */
+
+if (option_spec == NULL)
+  {
+  *results = result;
+  return TRUE;
+  }
+
+for (s=option_spec; *s != '\0'; /**/)
+  {
+  while (isspace(*s)) ++s;
+  if (*s == '\0')
+    break;
+  if (*s != '+' && *s != '-')
+    {
+    DEBUG(D_tls) debug_printf("malformed openssl option setting: "
+        "+ or - expected but found \"%s\"\n", s);
+    return FALSE;
+    }
+  adding = *s++ == '+';
+  for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ;
+  keep_c = *end;
+  *end = '\0';
+  item_parsed = tls_openssl_one_option_parse(s, &item);
+  if (!item_parsed)
+    {
+    DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
+    return FALSE;
+    }
+  DEBUG(D_tls) debug_printf("openssl option, %s from %lx: %lx (%s)\n",
+      adding ? "adding" : "removing", result, item, s);
+  if (adding)
+    result |= item;
+  else
+    result &= ~item;
+  *end = keep_c;
+  s = end;
+  }
+
+*results = result;
+return TRUE;
+}
+
 /* End of tls-openssl.c */