compiler quietening
[users/jgh/exim.git] / src / src / tls-openssl.c
index 7c91d9d8fbf5ade9df4d22b027140cb070db9446..d6867200c9e6cdd2810b364186c329cdbb4a1895 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) University of Cambridge 1995 - 2019 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Portions Copyright (c) The OpenSSL Project 1999 */
@@ -72,6 +72,8 @@ change this guard and punt the issue for a while longer. */
 #  define EXIM_HAVE_OPENSSL_TLS_METHOD
 #  define EXIM_HAVE_OPENSSL_KEYLOG
 #  define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
+#  define EXIM_HAVE_SESSION_TICKET
+#  define EXIM_HAVE_OPESSL_TRACE
 # else
 #  define EXIM_NEED_OPENSSL_INIT
 # endif
@@ -106,6 +108,12 @@ change this guard and punt the issue for a while longer. */
 # define DISABLE_OCSP
 #endif
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+# if OPENSSL_VERSION_NUMBER < 0x0101010L
+#  error OpenSSL version too old for session-resumption
+# endif
+#endif
+
 #ifdef EXIM_HAVE_OPENSSL_CHECKHOST
 # include <openssl/x509v3.h>
 #endif
@@ -114,7 +122,9 @@ change this guard and punt the issue for a while longer. */
 # ifndef EXIM_HAVE_OPENSSL_CIPHER_GET_ID
 #  define SSL_CIPHER_get_id(c) (c->id)
 # endif
-# include "tls-cipher-stdname.c"
+# ifndef MACRO_PREDEF
+#  include "tls-cipher-stdname.c"
+# endif
 #endif
 
 /*************************************************
@@ -138,7 +148,7 @@ Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
 static exim_openssl_option exim_openssl_options[] = {
 /* KEEP SORTED ALPHABETICALLY! */
 #ifdef SSL_OP_ALL
-  { US"all", SSL_OP_ALL },
+  { US"all", (long) SSL_OP_ALL },
 #endif
 #ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
   { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
@@ -247,6 +257,10 @@ for (struct exim_openssl_option * o = exim_openssl_options;
   spf(buf, sizeof(buf), US"_OPT_OPENSSL_%T_X", o->name);
   builtin_macro_create(buf);
   }
+
+# ifdef EXPERIMENTAL_TLS_RESUME
+builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
+# endif
 }
 #else
 
@@ -301,7 +315,7 @@ static SSL_CTX *server_sni = NULL;
 
 static char ssl_errstring[256];
 
-static int  ssl_session_timeout = 200;
+static int  ssl_session_timeout = 7200;                /* Two hours */
 static BOOL client_verify_optional = FALSE;
 static BOOL server_verify_optional = FALSE;
 
@@ -309,6 +323,7 @@ static BOOL reexpand_tls_files_for_sni = FALSE;
 
 
 typedef struct tls_ext_ctx_cb {
+  tls_support * tlsp;
   uschar *certificate;
   uschar *privatekey;
   BOOL is_server;
@@ -340,7 +355,7 @@ 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 *client_static_cbinfo = NULL;
+tls_ext_ctx_cb *client_static_cbinfo = NULL;   /*XXX should not use static; multiple concurrent clients! */
 tls_ext_ctx_cb *server_static_cbinfo = NULL;
 
 static int
@@ -356,6 +371,23 @@ static int tls_server_stapling_cb(SSL *s, void *arg);
 #endif
 
 
+
+/* Daemon-called, before every connection, key create/rotate */
+#ifdef EXPERIMENTAL_TLS_RESUME
+static void tk_init(void);
+static int tls_exdata_idx = -1;
+#endif
+
+void
+tls_daemon_init(void)
+{
+#ifdef EXPERIMENTAL_TLS_RESUME
+tk_init();
+#endif
+return;
+}
+
+
 /*************************************************
 *               Handle TLS error                 *
 *************************************************/
@@ -494,6 +526,7 @@ if (ev)
       }
     DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
       "(host in tls_try_verify_hosts)\n");
+    tlsp->verify_override = TRUE;
     }
   X509_free(tlsp->peercert);
   tlsp->peercert = old_cert;
@@ -553,6 +586,7 @@ if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
   }
 dn[sizeof(dn)-1] = '\0';
 
+tlsp->verify_override = FALSE;
 if (preverify_ok == 0)
   {
   uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
@@ -571,6 +605,7 @@ if (preverify_ok == 0)
     }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
+  tlsp->verify_override = TRUE;
   }
 
 else if (depth != 0)
@@ -647,8 +682,9 @@ else
          tlsp->peercert = X509_dup(cert);      /* record failing cert */
        return 0;                               /* reject */
        }
-      DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+      DEBUG(D_tls) debug_printf("SSL verify name failure overridden (host in "
        "tls_try_verify_hosts)\n");
+      tlsp->verify_override = TRUE;
       }
     }
 
@@ -659,7 +695,6 @@ else
 
   DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
     *calledp ? "" : " authenticated", dn);
-  if (!*calledp) tlsp->certificate_verified = TRUE;
   *calledp = TRUE;
   }
 
@@ -716,7 +751,7 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
 
 if (preverify_ok == 1)
   {
-  tls_out.dane_verified = tls_out.certificate_verified = TRUE;
+  tls_out.dane_verified = TRUE;
 #ifndef DISABLE_OCSP
   if (client_static_cbinfo->u_ocsp.client.verify_store)
     {  /* client, wanting stapling  */
@@ -801,6 +836,126 @@ DEBUG(D_tls) debug_printf("%.200s\n", line);
 #endif
 
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+/* Manage the keysets used for encrypting the session tickets, on the server. */
+
+typedef struct {                       /* Session ticket encryption key */
+  uschar       name[16];
+
+  const EVP_CIPHER *   aes_cipher;
+  uschar               aes_key[32];    /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
+  const EVP_MD *       hmac_hash;
+  uschar               hmac_key[16];
+  time_t               renew;
+  time_t               expire;
+} exim_stek;
+
+static exim_stek exim_tk;      /* current key */
+static exim_stek exim_tk_old;  /* previous key */
+
+static void
+tk_init(void)
+{
+time_t t = time(NULL);
+
+if (exim_tk.name[0])
+  {
+  if (exim_tk.renew >= t) return;
+  exim_tk_old = exim_tk;
+  }
+
+if (f.running_in_test_harness) ssl_session_timeout = 6;
+
+DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating");
+if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return;
+if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return;
+if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
+
+exim_tk.name[0] = 'E';
+exim_tk.aes_cipher = EVP_aes_256_cbc();
+exim_tk.hmac_hash = EVP_sha256();
+exim_tk.expire = t + ssl_session_timeout;
+exim_tk.renew = t + ssl_session_timeout/2;
+}
+
+static exim_stek *
+tk_current(void)
+{
+if (!exim_tk.name[0]) return NULL;
+return &exim_tk;
+}
+
+static exim_stek *
+tk_find(const uschar * name)
+{
+return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk
+  : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old
+  : NULL;
+}
+
+/* Callback for session tickets, on server */
+static int
+ticket_key_callback(SSL * ssl, uschar key_name[16],
+  uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc)
+{
+tls_support * tlsp = server_static_cbinfo->tlsp;
+exim_stek * key;
+
+if (enc)
+  {
+  DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n");
+  tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+
+  if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0)
+    return -1; /* insufficient random */
+
+  if (!(key = tk_current()))   /* current key doesn't exist or isn't valid */
+     return 0;                 /* key couldn't be created */
+  memcpy(key_name, key->name, 16);
+  DEBUG(D_tls) debug_printf("STEK expire %ld\n", key->expire - time(NULL));
+
+  /*XXX will want these dependent on the ssl session strength */
+  HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
+               key->hmac_hash, NULL);
+  EVP_EncryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
+
+  DEBUG(D_tls) debug_printf("ticket created\n");
+  return 1;
+  }
+else
+  {
+  time_t now = time(NULL);
+
+  DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n");
+  tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+
+  if (!(key = tk_find(key_name)) || key->expire < now)
+    {
+    DEBUG(D_tls)
+      {
+      debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found");
+      if (key) debug_printf("STEK expire %ld\n", key->expire - now);
+      }
+    return 0;
+    }
+
+  HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
+               key->hmac_hash, NULL);
+  EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
+
+  DEBUG(D_tls) debug_printf("ticket usable, STEK expire %ld\n", key->expire - now);
+
+  /* The ticket lifetime and renewal are the same as the STEK lifetime and
+  renewal, which is overenthusiastic.  A factor of, say, 3x longer STEK would
+  be better.  To do that we'd have to encode ticket lifetime in the name as
+  we don't yet see the restored session.  Could check posthandshake for TLS1.3
+  and trigger a new ticket then, but cannot do that for TLS1.2 */
+  return key->renew < now ? 2 : 1;
+  }
+}
+#endif
+
+
 
 /*************************************************
 *                Initialize for DH               *
@@ -1392,6 +1547,9 @@ Arguments:
   arg             Callback of "our" registered data
 
 Returns:          SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
+
+XXX might need to change to using ClientHello callback,
+per https://www.openssl.org/docs/manmaster/man3/SSL_client_hello_cb_fn.html
 */
 
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
@@ -1557,17 +1715,17 @@ if(!p)
   return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
  }
 
-if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
- {
-  tls_out.ocsp = OCSP_FAILED;
+if (!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
 {
+  tls_out.ocsp = OCSP_FAILED;  /*XXX should use tlsp-> to permit concurrent outbound */
   if (LOGGING(tls_cipher))
     log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
   else
     DEBUG(D_tls) debug_printf(" parse error\n");
   return 0;
- }
 }
 
-if(!(bs = OCSP_response_get1_basic(rsp)))
+if (!(bs = OCSP_response_get1_basic(rsp)))
   {
   tls_out.ocsp = OCSP_FAILED;
   if (LOGGING(tls_cipher))
@@ -1712,7 +1870,9 @@ tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
 #ifndef DISABLE_OCSP
   uschar *ocsp_file,   /*XXX stack, in server*/
 #endif
-  address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr)
+  address_item *addr, tls_ext_ctx_cb ** cbp,
+  tls_support * tlsp,
+  uschar ** errstr)
 {
 SSL_CTX * ctx;
 long init_options;
@@ -1720,6 +1880,7 @@ int rc;
 tls_ext_ctx_cb * cbinfo;
 
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
+cbinfo->tlsp = tlsp;
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
 cbinfo->is_server = host==NULL;
@@ -1793,10 +1954,17 @@ if (!RAND_status())
 /* Set up the information callback, which outputs if debugging is at a suitable
 level. */
 
-DEBUG(D_tls) SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+DEBUG(D_tls)
+  {
+  SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
+  /* this needs a debug build of OpenSSL */
+  SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace);
+#endif
 #ifdef OPENSSL_HAVE_KEYLOG_CB
-DEBUG(D_tls) SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback);
+  SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback);
 #endif
+  }
 
 /* Automatically re-try reads/writes after renegotiation. */
 (void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
@@ -1813,8 +1981,22 @@ availability of the option value macros from OpenSSL.  */
 if (!tls_openssl_options_parse(openssl_options, &init_options))
   return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+tlsp->resumption = RESUME_SUPPORTED;
+#endif
 if (init_options)
   {
+#ifdef EXPERIMENTAL_TLS_RESUME
+  /* Should the server offer session resumption? */
+  if (!host && verify_check_host(&tls_resumption_hosts) == OK)
+    {
+    DEBUG(D_tls) debug_printf("tls_resumption_hosts overrides openssl_options\n");
+    init_options &= ~SSL_OP_NO_TICKET;
+    tlsp->resumption |= RESUME_SERVER_TICKET; /* server will give ticket on request */
+    tlsp->host_resumable = TRUE;
+    }
+#endif
+
   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(
@@ -1823,10 +2005,6 @@ if (init_options)
 else
   DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
-#ifdef OPENSSL_HAVE_NUM_TICKETS
-SSL_CTX_set_num_tickets(ctx, 0);       /* send no TLS1.3 stateful-tickets */
-#endif
-
 /* We'd like to disable session cache unconditionally, but foolish Outlook
 Express clients then give up the first TLS connection and make a second one
 (which works).  Only when there is an IMAP service on the same machine.
@@ -1901,7 +2079,8 @@ cbinfo->verify_cert_hostnames = NULL;
 SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
 #endif
 
-/* Finally, set the timeout, and we are done */
+/* Finally, set the session cache timeout, and we are done.
+The period appears to be also used for (server-generated) session tickets */
 
 SSL_CTX_set_timeout(ctx, ssl_session_timeout);
 DEBUG(D_tls) debug_printf("Initialized TLS\n");
@@ -1983,8 +2162,24 @@ if (tlsp->peercert)
     { DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n"); }
   else
     {
-    peerdn[siz-1] = '\0';
-    tlsp->peerdn = peerdn;             /*XXX a static buffer... */
+    int oldpool = store_pool;
+
+    peerdn[siz-1] = '\0';              /* paranoia */
+    store_pool = POOL_PERM;
+    tlsp->peerdn = string_copy(peerdn);
+    store_pool = oldpool;
+
+    /* We used to set CV in the cert-verify callbacks (either plain or dane)
+    but they don't get called on session-resumption.  So use the official
+    interface, which uses the resumed value.  Unfortunately this claims verified
+    when it actually failed but we're in try-verify mode, due to us wanting the
+    knowlege that it failed so needing to have the callback and forcing a
+    permissive return.  If we don't force it, the TLS startup is failed.
+    The extra bit of information is set in verify_override in the cb, stashed
+    for resumption next to the TLS session, and used here. */
+
+    if (!tlsp->verify_override)
+      tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK;
     }
 }
 
@@ -2224,7 +2419,7 @@ rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
 #ifndef DISABLE_OCSP
     tls_ocsp_file,     /*XXX stack*/
 #endif
-    NULL, &server_static_cbinfo, errstr);
+    NULL, &server_static_cbinfo, &tls_in, errstr);
 if (rc != OK) return rc;
 cbinfo = server_static_cbinfo;
 
@@ -2242,8 +2437,7 @@ TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
 
 if (expciphers)
   {
-  uschar * s = expciphers;
-  while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+  for (uschar * s = expciphers; *s; s++ ) if (*s == '_') *s = '-';
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
   if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
     return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
@@ -2274,6 +2468,19 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK)
   server_verify_optional = TRUE;
   }
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback);
+/* despite working, appears to always return failure, so ignoring */
+#endif
+#ifdef OPENSSL_HAVE_NUM_TICKETS
+# ifdef EXPERIMENTAL_TLS_RESUME
+SSL_CTX_set_num_tickets(server_ctx, tls_in.host_resumable ? 1 : 0);
+# else
+SSL_CTX_set_num_tickets(server_ctx, 0);        /* send no TLS1.3 stateful-tickets */
+# endif
+#endif
+
+
 /* Prepare for new connection */
 
 if (!(server_ssl = SSL_new(server_ctx)))
@@ -2327,7 +2534,15 @@ if (rc <= 0)
 
 DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
 ERR_clear_error();     /* Even success can leave errors in the stack. Seen with
-                       anon-authentication ciphersuite negociated. */
+                       anon-authentication ciphersuite negotiated. */
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+if (SSL_session_reused(server_ssl))
+  {
+  tls_in.resumption |= RESUME_USED;
+  DEBUG(D_tls) debug_printf("Session reused\n");
+  }
+#endif
 
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize things. */
@@ -2350,6 +2565,15 @@ DEBUG(D_tls)
   BIO_free(bp);
   }
 #endif
+
+#ifdef EXIM_HAVE_SESSION_TICKET
+  {
+  SSL_SESSION * ss = SSL_get_session(server_ssl);
+  if (SSL_SESSION_has_ticket(ss))      /* 1.1.0 */
+    debug_printf("The session has a ticket, life %lu seconds\n",
+      SSL_SESSION_get_ticket_lifetime_hint(ss));
+  }
+#endif
   }
 
 /* Record the certificate we presented */
@@ -2481,6 +2705,171 @@ return DEFER;
 
 
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+/* On the client, get any stashed session for the given IP from hints db
+and apply it to the ssl-connection for attempted resumption. */
+
+static void
+tls_retrieve_session(tls_support * tlsp, SSL * ssl, const uschar * key)
+{
+tlsp->resumption |= RESUME_SUPPORTED;
+if (tlsp->host_resumable)
+  {
+  dbdata_tls_session * dt;
+  int len;
+  open_db dbblock, * dbm_file;
+
+  tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+  DEBUG(D_tls) debug_printf("checking for resumable session for %s\n", key);
+  if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
+    {
+    /* key for the db is the IP */
+    if ((dt = dbfn_read_with_length(dbm_file, key, &len)))
+      {
+      SSL_SESSION * ss = NULL;
+      const uschar * sess_asn1 = dt->session;
+
+      len -= sizeof(dbdata_tls_session);
+      if (!(d2i_SSL_SESSION(&ss, &sess_asn1, (long)len)))
+       {
+       DEBUG(D_tls)
+         {
+         ERR_error_string_n(ERR_get_error(),
+           ssl_errstring, sizeof(ssl_errstring));
+         debug_printf("decoding session: %s\n", ssl_errstring);
+         }
+       }
+#ifdef EXIM_HAVE_SESSION_TICKET
+      else if ( SSL_SESSION_get_ticket_lifetime_hint(ss) + dt->time_stamp
+              < time(NULL))
+       {
+       DEBUG(D_tls) debug_printf("session expired\n");
+       dbfn_delete(dbm_file, key);
+       }
+#endif
+      else if (!SSL_set_session(ssl, ss))
+       {
+       DEBUG(D_tls)
+         {
+         ERR_error_string_n(ERR_get_error(),
+           ssl_errstring, sizeof(ssl_errstring));
+         debug_printf("applying session to ssl: %s\n", ssl_errstring);
+         }
+       }
+      else
+       {
+       DEBUG(D_tls) debug_printf("good session\n");
+       tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+       tlsp->verify_override = dt->verify_override;
+       tlsp->ocsp = dt->ocsp;
+       }
+      }
+    else
+      DEBUG(D_tls) debug_printf("no session record\n");
+    dbfn_close(dbm_file);
+    }
+  }
+}
+
+
+/* On the client, save the session for later resumption */
+
+static int
+tls_save_session_cb(SSL * ssl, SSL_SESSION * ss)
+{
+tls_ext_ctx_cb * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx);
+tls_support * tlsp;
+
+DEBUG(D_tls) debug_printf("tls_save_session_cb\n");
+
+if (!cbinfo || !(tlsp = cbinfo->tlsp)->host_resumable) return 0;
+
+# ifdef OPENSSL_HAVE_NUM_TICKETS
+if (SSL_SESSION_is_resumable(ss))      /* 1.1.1 */
+# endif
+  {
+  int len = i2d_SSL_SESSION(ss, NULL);
+  int dlen = sizeof(dbdata_tls_session) + len;
+  dbdata_tls_session * dt = store_get(dlen, TRUE);
+  uschar * s = dt->session;
+  open_db dbblock, * dbm_file;
+
+  DEBUG(D_tls) debug_printf("session is resumable\n");
+  tlsp->resumption |= RESUME_SERVER_TICKET;    /* server gave us a ticket */
+
+  dt->verify_override = tlsp->verify_override;
+  dt->ocsp = tlsp->ocsp;
+  (void) i2d_SSL_SESSION(ss, &s);              /* s gets bumped to end */
+
+  if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
+    {
+    const uschar * key = cbinfo->host->address;
+    dbfn_delete(dbm_file, key);
+    dbfn_write(dbm_file, key, dt, dlen);
+    dbfn_close(dbm_file);
+    DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n",
+                 (unsigned)dlen);
+    }
+  }
+return 1;
+}
+
+
+static void
+tls_client_ctx_resume_prehandshake(
+  exim_openssl_client_tls_ctx * exim_client_ctx, tls_support * tlsp,
+  smtp_transport_options_block * ob, host_item * host)
+{
+/* Should the client request a session resumption ticket? */
+if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+  {
+  tlsp->host_resumable = TRUE;
+
+  SSL_CTX_set_session_cache_mode(exim_client_ctx->ctx,
+       SSL_SESS_CACHE_CLIENT
+       | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR);
+  SSL_CTX_sess_set_new_cb(exim_client_ctx->ctx, tls_save_session_cb);
+  }
+}
+
+static BOOL
+tls_client_ssl_resume_prehandshake(SSL * ssl, tls_support * tlsp,
+  host_item * host, uschar ** errstr)
+{
+if (tlsp->host_resumable)
+  {
+  DEBUG(D_tls)
+    debug_printf("tls_resumption_hosts overrides openssl_options, enabling tickets\n");
+  SSL_clear_options(ssl, SSL_OP_NO_TICKET);
+
+  tls_exdata_idx = SSL_get_ex_new_index(0, 0, 0, 0, 0);
+  if (!SSL_set_ex_data(ssl, tls_exdata_idx, client_static_cbinfo))
+    {
+    tls_error(US"set ex_data", host, NULL, errstr);
+    return FALSE;
+    }
+  debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_cbinfo);
+  }
+
+tlsp->resumption = RESUME_SUPPORTED;
+/* Pick up a previous session, saved on an old ticket */
+tls_retrieve_session(tlsp, ssl, host->address);
+return TRUE;
+}
+
+static void
+tls_client_resume_posthandshake(exim_openssl_client_tls_ctx * exim_client_ctx,
+  tls_support * tlsp)
+{
+if (SSL_session_reused(exim_client_ctx->ssl))
+  {
+  DEBUG(D_tls) debug_printf("The session was reused\n");
+  tlsp->resumption |= RESUME_USED;
+  }
+}
+#endif /* EXPERIMENTAL_TLS_RESUME */
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -2519,7 +2908,7 @@ BOOL require_ocsp = FALSE;
 
 rc = store_pool;
 store_pool = POOL_PERM;
-exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE);
 exim_client_ctx->corked = NULL;
 store_pool = rc;
 
@@ -2560,7 +2949,7 @@ rc = tls_init(&exim_client_ctx->ctx, host, NULL,
 #ifndef DISABLE_OCSP
     (void *)(long)request_ocsp,
 #endif
-    cookie, &client_static_cbinfo, errstr);
+    cookie, &client_static_cbinfo, tlsp, errstr);
 if (rc != OK) return FALSE;
 
 tlsp->certificate_verified = FALSE;
@@ -2627,12 +3016,18 @@ else
        client_static_cbinfo, errstr) != OK)
     return FALSE;
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host);
+#endif
+
+
 if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
   {
   tls_error(US"SSL_new", host, NULL, errstr);
   return FALSE;
   }
 SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+
 SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
 SSL_set_connect_state(exim_client_ctx->ssl);
 
@@ -2692,6 +3087,12 @@ if (request_ocsp)
   }
 #endif
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host,
+      errstr))
+  return FALSE;
+#endif
+
 #ifndef DISABLE_EVENT
 client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
 #endif
@@ -2727,6 +3128,10 @@ DEBUG(D_tls)
 #endif
   }
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_client_resume_posthandshake(exim_client_ctx, tlsp);
+#endif
+
 peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
 
 tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits);
@@ -2784,32 +3189,10 @@ switch(error)
   case SSL_ERROR_ZERO_RETURN:
     DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
 
-    receive_getc = smtp_getc;
-    receive_getbuf = smtp_getbuf;
-    receive_get_cache = smtp_get_cache;
-    receive_ungetc = smtp_ungetc;
-    receive_feof = smtp_feof;
-    receive_ferror = smtp_ferror;
-    receive_smtp_buffered = smtp_buffered;
-
     if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
          SSL_shutdown(server_ssl);
 
-#ifndef DISABLE_OCSP
-    sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
-    server_static_cbinfo->verify_stack = NULL;
-#endif
-    SSL_free(server_ssl);
-    SSL_CTX_free(server_ctx);
-    server_ctx = NULL;
-    server_ssl = NULL;
-    tls_in.active.sock = -1;
-    tls_in.active.tls_ctx = NULL;
-    tls_in.bits = 0;
-    tls_in.cipher = NULL;
-    tls_in.peerdn = NULL;
-    tls_in.sni = NULL;
-
+    tls_close(NULL, TLS_NO_SHUTDOWN);
     return FALSE;
 
   /* Handle genuine errors */
@@ -2990,14 +3373,14 @@ a store reset there, so use POOL_PERM. */
 
 if ((more || corked))
   {
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifdef SUPPORT_PIPE_CONNECT
   int save_pool = store_pool;
   store_pool = POOL_PERM;
 #endif
 
   corked = string_catn(corked, buff, len);
 
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifdef SUPPORT_PIPE_CONNECT
   store_pool = save_pool;
 #endif
 
@@ -3098,14 +3481,25 @@ if (shutdown)
     }
   }
 
-#ifndef DISABLE_OCSP
 if (!o_ctx)            /* server side */
   {
+#ifndef DISABLE_OCSP
   sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
   server_static_cbinfo->verify_stack = NULL;
-  }
 #endif
 
+  receive_getc =       smtp_getc;
+  receive_getbuf =     smtp_getbuf;
+  receive_get_cache =  smtp_get_cache;
+  receive_ungetc =     smtp_ungetc;
+  receive_feof =       smtp_feof;
+  receive_ferror =     smtp_ferror;
+  receive_smtp_buffered = smtp_buffered;
+  tls_in.active.tls_ctx = NULL;
+  tls_in.sni = NULL;
+  /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
+  }
+
 SSL_CTX_free(*ctxp);
 SSL_free(*sslp);
 *ctxp = NULL;
@@ -3375,12 +3769,17 @@ uschar *end;
 uschar keep_c;
 BOOL adding, item_parsed;
 
+/* Server: send no (<= TLS1.2) session tickets */
 result = SSL_OP_NO_TICKET;
+
 /* 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
+#ifdef SSL_OP_NO_SSLv3
+result |= SSL_OP_NO_SSLv3;
+#endif
 #ifdef SSL_OP_SINGLE_DH_USE
 result |= SSL_OP_SINGLE_DH_USE;
 #endif
@@ -3391,7 +3790,7 @@ if (!option_spec)
   return TRUE;
   }
 
-for (uschar * s = option_spec; *s != '\0'; /**/)
+for (uschar * s = option_spec; *s; /**/)
   {
   while (isspace(*s)) ++s;
   if (*s == '\0')