[exim-cvs] TLS: Session resumption, under the EXPERIMENTAL_…

Inizio della pagina
Delete this message
Reply to this message
Autore: Exim Git Commits Mailing List
Data:  
To: exim-cvs
Oggetto: [exim-cvs] TLS: Session resumption, under the EXPERIMENTAL_TLS_RESUME build option.
Gitweb: https://git.exim.org/exim.git/commitdiff/b10c87b38c2345d15d30da5c18c823355ac506a9
Commit:     b10c87b38c2345d15d30da5c18c823355ac506a9
Parent:     0565fc5a1155f97f29fb6e081343cfc4e477c611
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Thu May 2 17:16:05 2019 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Thu May 2 17:23:05 2019 +0100


    TLS: Session resumption, under the EXPERIMENTAL_TLS_RESUME build option.
---
 doc/doc-docbook/spec.xfpt                 |   4 +-
 doc/doc-txt/ChangeLog                     |   2 +
 doc/doc-txt/NewStuff                      |   3 +
 doc/doc-txt/experimental-spec.txt         |  44 ++++
 src/OS/Makefile-Base                      |   4 +-
 src/src/acl.c                             |   2 +-
 src/src/config.h.defaults                 |   1 +
 src/src/daemon.c                          |   5 +
 src/src/dbfn.c                            |   7 +-
 src/src/dbfunctions.h                     |   2 +-
 src/src/dbstuff.h                         |   6 +
 src/src/deliver.c                         |  23 +-
 src/src/enq.c                             |   4 +-
 src/src/exim.c                            |   3 +
 src/src/exim_dbutil.c                     |  33 ++-
 src/src/expand.c                          |   8 +-
 src/src/functions.h                       |   1 +
 src/src/globals.c                         |  39 +--
 src/src/globals.h                         |   8 +
 src/src/macro_predef.c                    |   5 +-
 src/src/macro_predef.h                    |   2 +-
 src/src/macros.h                          |  22 +-
 src/src/readconf.c                        |   3 +
 src/src/receive.c                         |   6 +
 src/src/retry.c                           |   4 +-
 src/src/smtp_in.c                         |   6 +
 src/src/spool_in.c                        |   5 +
 src/src/spool_out.c                       |   3 +
 src/src/structs.h                         |   3 +
 src/src/tls-gnu.c                         | 339 +++++++++++++++++++------
 src/src/tls-openssl.c                     | 399 ++++++++++++++++++++++++++++--
 src/src/tls.c                             |   6 +-
 src/src/transport.c                       |   4 +-
 src/src/transports/smtp.c                 |  18 +-
 src/src/transports/smtp.h                 |   3 +
 src/src/verify.c                          |   8 +-
 test/aux-var-src/tls_conf_prefix          |   2 +-
 test/confs/5890                           |  94 +++++++
 test/confs/5891                           |  94 +++++++
 test/log/2102                             |   2 +-
 test/log/5890                             | 116 +++++++++
 test/log/5891                             | 130 ++++++++++
 test/runtest                              |  14 +-
 test/scripts/5890-Resume-GnuTLS/5890      |  62 +++++
 test/scripts/5890-Resume-GnuTLS/REQUIRES  |   3 +
 test/scripts/5891-Resume-OpenSSL/5891     |  57 +++++
 test/scripts/5891-Resume-OpenSSL/REQUIRES |   3 +
 test/stderr/5890                          |   6 +
 test/stderr/5891                          |   6 +
 test/stdout/0572                          |   4 +-
 test/stdout/0577                          |   2 +-
 test/stdout/5890                          |   6 +
 test/stdout/5891                          |   6 +
 53 files changed, 1470 insertions(+), 172 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 7a7608b..783aeb4 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -16169,7 +16169,7 @@ harm. This option overrides the &%pipe_as_creator%& option of the &(pipe)&
transport driver.


-.option openssl_options main "string list" "+no_sslv2 +single_dh_use +no_ticket"
+.option openssl_options main "string list" "+no_sslv2 +no_sslv3 +single_dh_use +no_ticket"
.cindex "OpenSSL "compatibility options"
This option allows an administrator to adjust the SSL options applied
by OpenSSL to connections. It is given as a space-separated list of items,
@@ -28319,7 +28319,7 @@ There is no current way to staple a proof for a client certificate.



-.section "Configuring an Exim client to use TLS" "SECID185"
+.section "Configuring an Exim client to use TLS" "SECTclientTLS"
 .cindex "cipher" "logging"
 .cindex "log" "TLS cipher"
 .cindex "log" "distinguished name"
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index a85841a..59a025b 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -89,6 +89,8 @@ JH/16 GnuTLS: rework ciphersuite strings under recent library versions.  Thanks
       This affects log line X= elements, the $tls_{in,out}_cipher variables,
       and the use of specific cipher names in the encrypted= ACL condition.


+JH/17 OpenSSL: the default openssl_options now disables ssl_v3.
+

Exim version 4.92
-----------------
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index e776a4f..352833c 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -20,6 +20,9 @@ Version 4.93

5. A case_insensitive option for verify=not_blind.

+ 6. EXPERIMENTAL_TLS_RESUME optional build feature.  See the experimental.spec
+    file.
+


Version 4.92
--------------
diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index 2f1e5c5..a2861c4 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -951,6 +951,50 @@ Transport configurations should be checked for this. An example avoidance:



+TLS Session Resumption
+----------------------
+TLS Session Resumption for TLS 1.2 and TLS1.3 connections can be used (defined
+in RFC 5077 for 1.2). The support for this can be included by building with
+EXPERIMENTAL_TLS_RESUME defined.
+
+Session resumption (this is the "stateless" variant) involves the server sending
+a "session ticket" to the client on one connection, which can be stored by the
+client and used for a later session. The ticket contains sufficient state for
+the server to reconstruct the TLS session, avoiding some expensive crypto
+calculation and one full packet roundtrip time.
+
+Operational cost/benefit:
+ The extra data being transmitted costs a minor amount, and the client has
+extra costs in storing and retrieving the data.
+
+In the Exim/Gnutls implementation the extra cost on an initial connection
+which is TLS1.2 over a loopback path is about 6ms on 2017-laptop class hardware.
+The saved cost on a subsequent connection is about 4ms; three or more
+connections become a net win. On longer network paths, two or more
+connections will have an average lower startup time thanks to the one
+saved packet roundtrip. TLS1.3 will save the crypto cpu costs but not any
+packet roundtrips.
+
+Security aspects:
+ The session ticket is encrypted, but is obviously an additional security
+vulnarability surface. An attacker able to decrypt it would have access
+all connections using the resumed session.
+The session ticket encryption key is not committed to storage by the server
+and is rotated regularly. Tickets have limited lifetime.
+
+There is a question-mark over the security of the Diffie-Helman parameters
+used for session negotiation. TBD. q-value; cf bug 1895
+
+Observability:
+ New log_selector "tls_resumption", appends an asterisk to the tls_cipher "X="
+element.
+
+Variables $tls_{in,out}_resumption have bit 0-4 indicating respectively
+support built, client requested ticket, client offered session,
+server issued ticket, resume used. A suitable decode list is provided
+in the builtin macro _RESUME_DECODE for ${listextract {}{}}.
+
+
--------------------------------------------------------------
End of file
--------------------------------------------------------------
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 0fbee9d..8ca9a48 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -123,7 +123,7 @@ config.h: Makefile buildconfig ../src/config.h.defaults $(EDITME)

# Build the builtin-macros data struct

-MACRO_HSRC = macro_predef.h os.h globals.h config.h \
+MACRO_HSRC = macro_predef.h os.h globals.h config.h macros.h \
     routers/accept.h routers/dnslookup.h routers/ipliteral.h \
     routers/iplookup.h routers/manualroute.h routers/queryprogram.h \
     routers/redirect.h
@@ -158,7 +158,7 @@ macro-transport.o:    transport.c
 macro-drtables.o :    drtables.c
     @echo "$(CC) -DMACRO_PREDEF drtables.c"
     $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ drtables.c
-macro-tls.o:    tls.c
+macro-tls.o:    tls.c tls-gnu.c tls-openssl.c
     @echo "$(CC) -DMACRO_PREDEF tls.c"
     $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ tls.c
 macro-appendfile.o :    transports/appendfile.c
diff --git a/src/src/acl.c b/src/src/acl.c
index fdd32b8..19938af 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -2406,7 +2406,7 @@ if ((t = tree_search(*anchor, key)))
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */


-if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE)))
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE, TRUE)))
{
store_pool = old_pool;
sender_rate = NULL;
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index a27ad3f..c5d5389 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -203,6 +203,7 @@ Do not put spaces between # and the 'define'.
#define EXPERIMENTAL_PIPE_CONNECT
#define EXPERIMENTAL_QUEUEFILE
#define EXPERIMENTAL_SRS
+#define EXPERIMENTAL_TLS_RESUME


 /* For developers */
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 4addf0a..cf5e092 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -1985,6 +1985,11 @@ for (;;)
     handle_ending_processes();
     errno = select_errno;


+#ifdef SUPPORT_TLS
+    /* Create or rotate any required keys */
+    tls_daemon_init();
+#endif
+
     /* Loop for all the sockets that are currently ready to go. If select
     actually failed, we have set the count to 1 and select_failed=TRUE, so as
     to use the common error code for select/accept below. */
diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index 5555c71..a607756 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -72,6 +72,7 @@ Arguments:
   dbblock  Points to an open_db block to be filled in.
   lof      If TRUE, write to the log for actual open failures (locking failures
            are always logged).
+  panic       If TRUE, panic on failure to create the db directory


 Returns:   NULL if the open failed, or the locking failed. After locking
            failures, errno is zero.
@@ -85,7 +86,7 @@ moment I haven't changed them.
 */


open_db *
-dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
+dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
{
int rc, save_errno;
BOOL read_only = flags == O_RDONLY;
@@ -114,7 +115,7 @@ snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
{
created = TRUE;
- (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
+ (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, panic);
dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
}

@@ -536,7 +537,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
       }


     start = clock();
-    odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE);
+    odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE, TRUE);
     stop = clock();


     if (odb)
diff --git a/src/src/dbfunctions.h b/src/src/dbfunctions.h
index 93d12ef..2e18e0e 100644
--- a/src/src/dbfunctions.h
+++ b/src/src/dbfunctions.h
@@ -10,7 +10,7 @@


 void     dbfn_close(open_db *);
 int      dbfn_delete(open_db *, const uschar *);
-open_db *dbfn_open(uschar *, int, open_db *, BOOL);
+open_db *dbfn_open(uschar *, int, open_db *, BOOL, BOOL);
 void    *dbfn_read_with_length(open_db *, const uschar *, int *);
 uschar  *dbfn_scan(open_db *, BOOL, EXIM_CURSOR **);
 int      dbfn_write(open_db *, const uschar *, void *, int);
diff --git a/src/src/dbstuff.h b/src/src/dbstuff.h
index 02cfa14..6b1ae0e 100644
--- a/src/src/dbstuff.h
+++ b/src/src/dbstuff.h
@@ -804,5 +804,11 @@ typedef struct {
 } dbdata_ehlo_resp;
 #endif


+typedef struct {
+ time_t time_stamp;
+ /*************/
+ uschar session[1];
+} dbdata_tls_session;
+

/* End of dbstuff.h */
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 696effd..f79522d 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -803,12 +803,18 @@ return g;

 #ifdef SUPPORT_TLS
 static gstring *
-d_tlslog(gstring * s, address_item * addr)
+d_tlslog(gstring * g, address_item * addr)
 {
 if (LOGGING(tls_cipher) && addr->cipher)
-  s = string_append(s, 2, US" X=", addr->cipher);
+  {
+  g = string_append(g, 2, US" X=", addr->cipher);
+#ifdef EXPERIMENTAL_TLS_RESUME
+  if (LOGGING(tls_resumption) && testflag(addr, af_tls_resume))
+    g = string_catn(g, US"*", 1);
+#endif
+  }
 if (LOGGING(tls_certificate_verified) && addr->cipher)
-  s = string_append(s, 2, US" CV=",
+  g = string_append(g, 2, US" CV=",
     testflag(addr, af_cert_verified)
     ?
 #ifdef SUPPORT_DANE
@@ -819,8 +825,8 @@ if (LOGGING(tls_certificate_verified) && addr->cipher)
       "yes"
     : "no");
 if (LOGGING(tls_peerdn) && addr->peerdn)
-  s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
-return s;
+  g = string_append(g, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
+return g;
 }
 #endif


@@ -2900,7 +2906,7 @@ while (addr_local)
of these checks, rather than for all local deliveries, because some local
deliveries (e.g. to pipes) can take a substantial time. */

-  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
     {
     DEBUG(D_deliver|D_retry|D_hints_lookup)
       debug_printf("no retry data available\n");
@@ -4794,6 +4800,9 @@ all pipes, so I do not see a reason to use non-blocking IO here
 #ifdef SUPPORT_DANE
       if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
 #endif
+# ifdef EXPERIMENTAL_TLS_RESUME
+      if (tls_out.resumption & RESUME_USED) setflag(addr, af_tls_resume);
+# endif


       /* Use an X item only if there's something to send */
 #ifdef SUPPORT_TLS
@@ -6321,7 +6330,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
   /* Failure to open the retry database is treated the same as if it does
   not exist. In both cases, dbm_file is NULL. */


-  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
     DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
       debug_printf("no retry data available\n");


diff --git a/src/src/enq.c b/src/src/enq.c
index 573fc00..7feba55 100644
--- a/src/src/enq.c
+++ b/src/src/enq.c
@@ -47,7 +47,7 @@ deliberate; the dbfn_open() function - which is an Exim function - always tries
to create if it can't open a read/write file. It expects only O_RDWR or
O_RDONLY as its argument. */

-if (!(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+if (!(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
return FALSE;

/* See if there is a record for this host or queue run; if there is, we cannot
@@ -101,7 +101,7 @@ dbdata_serialize *serial_record;

DEBUG(D_transport) debug_printf("end serialized: %s\n", key);

-if (  !(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE))
+if (  !(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))
    || !(serial_record = dbfn_read(dbm_file, key))
    )
   return;
diff --git a/src/src/exim.c b/src/src/exim.c
index df76de1..1952d91 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -934,6 +934,9 @@ fprintf(fp, "Support for:");
 #ifdef EXPERIMENTAL_PIPE_CONNECT
   fprintf(fp, " Experimental_PIPE_CONNECT");
 #endif
+#ifdef EXPERIMENTAL_TLS_RESUME
+  fprintf(fp, " Experimental_TLS_resume");
+#endif
 fprintf(fp, "\n");


 fprintf(fp, "Lookups (built-in):");
diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index a0514f0..2c7aad6 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -20,6 +20,7 @@ argument is the name of the database file. The available names are:
   misc:       miscellaneous hints data
   wait-<t>:   message waiting information; <t> is a transport name
   callout:    callout verification cache
+  tls:          TLS session resumption cache


 There are a number of common subroutines, followed by three main programs,
 whose inclusion is controlled by -D on the compilation command. */
@@ -35,6 +36,7 @@ whose inclusion is controlled by -D on the compilation command. */
 #define type_misc      3
 #define type_callout   4
 #define type_ratelimit 5
+#define type_tls       6



/* This is used by our cut-down dbfn_open(). */
@@ -91,7 +93,7 @@ static void
usage(uschar *name, uschar *options)
{
printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
-printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
+printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
exit(1);
}

@@ -114,6 +116,7 @@ if (argc == 3)
   if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
   if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
   if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
+  if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
   }
 usage(name, options);
 return -1;              /* Never obeyed */
@@ -240,6 +243,7 @@ Arguments:
   flags    O_RDONLY or O_RDWR
   dbblock  Points to an open_db block to be filled in.
   lof      Unused.
+  panic       Unused


 Returns:   NULL if the open failed, or the locking failed.
            On success, dbblock is returned. This contains the dbm pointer and
@@ -247,7 +251,7 @@ Returns:   NULL if the open failed, or the locking failed.
 */


open_db *
-dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
+dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
{
int rc;
struct flock lock_data;
@@ -525,7 +529,7 @@ uschar keybuffer[1024];

dbdata_type = check_args(argc, argv, US"dumpdb", US"");
spool_directory = argv[1];
-if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
exit(1);

 /* Scan the file, formatting the information for each entry. Note
@@ -541,6 +545,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
   dbdata_callout_cache *callout;
   dbdata_ratelimit *ratelimit;
   dbdata_ratelimit_unique *rate_unique;
+  dbdata_tls_session *session;
   int count_bad = 0;
   int length;
   uschar *t;
@@ -673,6 +678,11 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
         keybuffer);
       }
     break;
+
+      case type_tls:
+    session = (dbdata_tls_session *)value;
+    printf("  %s %.*s\n", keybuffer, length, session->session);
+    break;
       }
     store_reset(value);
     }
@@ -745,6 +755,7 @@ for(;;)
   dbdata_callout_cache *callout;
   dbdata_ratelimit *ratelimit;
   dbdata_ratelimit_unique *rate_unique;
+  dbdata_tls_session *session;
   int oldlength;
   uschar *t;
   uschar field[256], value[256];
@@ -783,7 +794,7 @@ for(;;)
     int verify = 1;
     spool_directory = argv[1];


-    if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+    if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
       continue;


     if (Ustrcmp(field, "d") == 0)
@@ -925,6 +936,10 @@ for(;;)
              break;
         }
           break;
+
+            case type_tls:
+          printf("Can't change contents of tls database record\n");
+          break;
             }


           dbfn_write(dbm, name, record, oldlength);
@@ -949,7 +964,7 @@ for(;;)
   /* Handle a read request, or verify after an update. */


   spool_directory = argv[1];
-  if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+  if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
     continue;


   if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
@@ -1036,6 +1051,12 @@ for(;;)
      printf("5 add element to filter\n");
      }
     break;
+
+      case type_tls:
+    session = (dbdata_tls_session *)value;
+    printf("0 time stamp:  %s\n", print_time(session->time_stamp));
+    printf("1 session: .%s\n", session->session);
+    break;
       }
     }


@@ -1134,7 +1155,7 @@ oldest = time(NULL) - maxkeep;
printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);

spool_directory = argv[1];
-if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
exit(1);

 /* Prepare for building file names */
diff --git a/src/src/expand.c b/src/src/expand.c
index ff1b726..d8ea87d 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -751,6 +751,9 @@ static var_entry var_table[] = {
   { "tls_in_ourcert",      vtype_cert,        &tls_in.ourcert },
   { "tls_in_peercert",     vtype_cert,        &tls_in.peercert },
   { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
+#ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_in_resumption",   vtype_int,         &tls_in.resumption },
+#endif
 #if defined(SUPPORT_TLS)
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
 #endif
@@ -765,6 +768,9 @@ static var_entry var_table[] = {
   { "tls_out_ourcert",     vtype_cert,        &tls_out.ourcert },
   { "tls_out_peercert",    vtype_cert,        &tls_out.peercert },
   { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
+#ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_out_resumption",  vtype_int,         &tls_out.resumption },
+#endif
 #if defined(SUPPORT_TLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
@@ -5039,7 +5045,7 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }


-      /*XXX we trust that the request is idempotent.  Hmm. */
+      /*XXX we trust that the request is idempotent for TFO.  Hmm. */
       cctx.sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
           timeout, &host, &expand_string_message,
           do_tls ? NULL : &reqstr);
diff --git a/src/src/functions.h b/src/src/functions.h
index 2eb7457..87953c4 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -51,6 +51,7 @@ extern BOOL    tls_client_start(client_conn_ctx *, smtp_connect_args *,


 extern void    tls_close(void *, int);
 extern BOOL    tls_could_read(void);
+extern void    tls_daemon_init(void);
 extern int     tls_export_cert(uschar *, size_t, void *);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
diff --git a/src/src/globals.c b/src/src/globals.c
index acabeb8..a2fa032 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -102,38 +102,12 @@ them. Also, the tls_ variables are now always visible.  Note that these are
 only used for smtp connections, not for service-daemon access. */


 tls_support tls_in = {
- .active =        {.sock = -1},
- .bits =        0,
- .certificate_verified = FALSE,
-#ifdef SUPPORT_DANE
- .dane_verified =    FALSE,
- .tlsa_usage =        0,
-#endif
- .cipher =        NULL,
- .on_connect =        FALSE,
- .on_connect_ports =    NULL,
- .ourcert =        NULL,
- .peercert =        NULL,
- .peerdn =        NULL,
- .sni =            NULL,
- .ocsp =        OCSP_NOT_REQ
+ .active =        {.sock = -1}
+ /* all other elements zero */
 };
 tls_support tls_out = {
  .active =        {.sock = -1},
- .bits =        0,
- .certificate_verified = FALSE,
-#ifdef SUPPORT_DANE
- .dane_verified =    FALSE,
- .tlsa_usage =        0,
-#endif
- .cipher =        NULL,
- .on_connect =        FALSE,
- .on_connect_ports =    NULL,
- .ourcert =        NULL,
- .peercert =        NULL,
- .peerdn =        NULL,
- .sni =            NULL,
- .ocsp =        OCSP_NOT_REQ
+ /* all other elements zero */
 };


 uschar *dsn_envid              = NULL;
@@ -161,6 +135,9 @@ uschar *tls_ocsp_file          = NULL;
 uschar *tls_privatekey         = NULL;
 BOOL    tls_remember_esmtp     = FALSE;
 uschar *tls_require_ciphers    = NULL;
+# ifdef EXPERIMENTAL_TLS_RESUME
+uschar *tls_resumption_hosts   = NULL;
+# endif
 uschar *tls_try_verify_hosts   = NULL;
 uschar *tls_verify_certificates= US"system";
 uschar *tls_verify_hosts       = NULL;
@@ -1047,7 +1024,8 @@ uschar *log_file_path          = US LOG_FILE_PATH
 int     log_notall[]           = {
   -1
 };
-bit_table log_options[]        = { /* must be in alphabetical order */
+bit_table log_options[]        = { /* must be in alphabetical order,
+                with definitions from enum logbit. */
   BIT_TABLE(L, 8bitmime),
   BIT_TABLE(L, acl_warn_skipped),
   BIT_TABLE(L, address_rewrite),
@@ -1105,6 +1083,7 @@ bit_table log_options[]        = { /* must be in alphabetical order */
   BIT_TABLE(L, tls_certificate_verified),
   BIT_TABLE(L, tls_cipher),
   BIT_TABLE(L, tls_peerdn),
+  BIT_TABLE(L, tls_resumption),
   BIT_TABLE(L, tls_sni),
   BIT_TABLE(L, unknown_in_list),
 };
diff --git a/src/src/globals.h b/src/src/globals.h
index a0c1977..1aacaf7 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -103,6 +103,11 @@ typedef struct {
     OCSP_FAILED,        /* verify failed */
     OCSP_VFIED            /* verified */
     }     ocsp;              /* Stapled OCSP status */
+#ifdef EXPERIMENTAL_TLS_RESUME
+  unsigned resumption;        /* Session resumption */
+  BOOL      host_resumable:1;
+  BOOL      ticket_received:1;
+#endif
 } tls_support;
 extern tls_support tls_in;
 extern tls_support tls_out;
@@ -124,6 +129,9 @@ extern uschar *tls_ocsp_file;          /* OCSP stapling proof file */
 extern uschar *tls_privatekey;         /* Private key file */
 extern BOOL    tls_remember_esmtp;     /* For YAEB */
 extern uschar *tls_require_ciphers;    /* So some can be avoided */
+# ifdef EXPERIMENTAL_TLS_RESUME
+extern uschar *tls_resumption_hosts;   /* TLS session resumption */
+# endif
 extern uschar *tls_try_verify_hosts;   /* Optional client verification */
 extern uschar *tls_verify_certificates;/* Path for certificates to check */
 extern uschar *tls_verify_hosts;       /* Mandatory client verification */
diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c
index 86be52f..3324913 100644
--- a/src/src/macro_predef.c
+++ b/src/src/macro_predef.c
@@ -200,6 +200,9 @@ due to conflicts with other common macros. */
 #ifdef EXPERIMENTAL_PIPE_CONNECT
   builtin_macro_create(US"_HAVE_PIPE_CONNECT");
 #endif
+#ifdef EXPERIMENTAL_TLS_RESUME
+  builtin_macro_create(US"_HAVE_TLS_RESUME");
+#endif


#ifdef LOOKUP_LSEARCH
builtin_macro_create(US"_HAVE_LOOKUP_LSEARCH");
@@ -287,7 +290,7 @@ options_routers();
options_transports();
options_auths();
options_logging();
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
options_tls();
#endif
}
diff --git a/src/src/macro_predef.h b/src/src/macro_predef.h
index f265750..79a8d6f 100644
--- a/src/src/macro_predef.h
+++ b/src/src/macro_predef.h
@@ -20,7 +20,7 @@ extern void options_transports(void);
extern void options_auths(void);
extern void options_logging(void);
extern void params_dkim(void);
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
extern void options_tls(void);
#endif

diff --git a/src/src/macros.h b/src/src/macros.h
index f98d5e6..e3f1f4c 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -435,11 +435,12 @@ enum {
/* Options bits for logging. Those that have values < BITWORDSIZE can be used
in calls to log_write(). The others are put into later words in log_selector
and are only ever tested independently, so they do not need bit mask
-declarations. The Li_all value is recognized specially by decode_bits(). */
+declarations. The Li_all value is recognized specially by decode_bits().
+Add also to log_options[] when creating new ones. */

#define LOG_BIT(name) Li_##name = IOTA(Li_iota), L_##name = BIT(Li_##name)

-enum {
+enum logbit {
Li_all = -1,

Li_iota = IOTA_INIT(0),
@@ -495,6 +496,7 @@ enum {
Li_tls_certificate_verified,
Li_tls_cipher,
Li_tls_peerdn,
+ Li_tls_resumption,
Li_tls_sni,
Li_unknown_in_list,

@@ -1077,5 +1079,21 @@ should not be one active. */
 #define AUTH_ITEM_IGN64    BIT(2)



+/* Flags for tls_{in,out}_resumption */
+#define RESUME_SUPPORTED    BIT(0)
+#define RESUME_CLIENT_REQUESTED    BIT(1)
+#define RESUME_CLIENT_SUGGESTED    BIT(2)
+#define RESUME_SERVER_TICKET    BIT(3)
+#define RESUME_USED        BIT(4)
+
+#define RESUME_DECODE_STRING \
+      US"not requested or offered : 0x02 :client requested, no server ticket" \
+    ": 0x04 : 0x05 : 0x06 :client offered session, no server action" \
+    ": 0x08 :no client request: 0x0A :client requested new ticket, server provided" \
+    ": 0x0C :client offered session, not used: 0x0E :client offered session, server only provided new ticket" \
+    ": 0x10 :session resumed unasked: 0x12 :session resumed unasked" \
+    ": 0x14 : 0x15 : 0x16 :session resumed" \
+    ": 0x18 :session resumed unasked: 0x1A :session resumed unasked" \
+    ": 0x1C :session resumed: 0x1E :session resumed, also new ticket"


 /* End of macros.h */
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 150d797..cac8fe5 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -367,6 +367,9 @@ static optionlist optionlist_config[] = {
   { "tls_privatekey",           opt_stringptr,   &tls_privatekey },
   { "tls_remember_esmtp",       opt_bool,        &tls_remember_esmtp },
   { "tls_require_ciphers",      opt_stringptr,   &tls_require_ciphers },
+# ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_resumption_hosts",     opt_stringptr,   &tls_resumption_hosts },
+# endif
   { "tls_try_verify_hosts",     opt_stringptr,   &tls_try_verify_hosts },
   { "tls_verify_certificates",  opt_stringptr,   &tls_verify_certificates },
   { "tls_verify_hosts",         opt_stringptr,   &tls_verify_hosts },
diff --git a/src/src/receive.c b/src/src/receive.c
index 701d540..fbd32c8 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -3980,7 +3980,13 @@ g = add_host_info_for_log(g);


 #ifdef SUPPORT_TLS
 if (LOGGING(tls_cipher) && tls_in.cipher)
+  {
   g = string_append(g, 2, US" X=", tls_in.cipher);
+# ifdef EXPERIMENTAL_TLS_RESUME
+  if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED)
+    g = string_catn(g, US"*", 1);
+# endif
+  }
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
diff --git a/src/src/retry.c b/src/src/retry.c
index dc39813..509de12 100644
--- a/src/src/retry.c
+++ b/src/src/retry.c
@@ -170,7 +170,7 @@ if ((node = tree_search(tree_unusable, host_key)))
 /* Open the retry database, giving up if there isn't one. Otherwise, search for
 the retry records, and then close the database again. */


-if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
   {
   DEBUG(D_deliver|D_retry|D_hints_lookup)
     debug_printf("no retry data available\n");
@@ -580,7 +580,7 @@ for (int i = 0; i < 3; i++)
         reached their retry next try time. */


         if (!dbm_file)
-          dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE);
+          dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE, TRUE);


         if (!dbm_file)
           {
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index b46f3e8..40fd308 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1786,7 +1786,13 @@ static gstring *
 s_tlslog(gstring * g)
 {
 if (LOGGING(tls_cipher) && tls_in.cipher)
+  {
   g = string_append(g, 2, US" X=", tls_in.cipher);
+#ifdef EXPERIMENTAL_TLS_RESUME
+  if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED)
+    g = string_catn(g, US"*", 1);
+#endif
+  }
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 786eb51..95004c1 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -667,6 +667,11 @@ for (;;)
     tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
       else if (Ustrncmp(q, "ocsp", 4) == 0)
     tls_in.ocsp = big_buffer[10] - '0';
+# ifdef EXPERIMENTAL_TLS_RESUME
+      else if (Ustrncmp(q, "resumption", 10) == 0)
+    tls_in.resumption = big_buffer[16] - 'A';
+# endif
+
       }
     break;
 #endif
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index 46a490a..d14914f 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -249,6 +249,9 @@ if (tls_in.ourcert)
   fprintf(fp, "-tls_ourcert %s\n", CS big_buffer);
   }
 if (tls_in.ocsp)     fprintf(fp, "-tls_ocsp %d\n",   tls_in.ocsp);
+# ifdef EXPERIMENTAL_TLS_RESUME
+fprintf(fp, "-tls_resumption %c\n", 'A' + tls_in.resumption);
+# endif
 #endif


 #ifdef SUPPORT_I18N
diff --git a/src/src/structs.h b/src/src/structs.h
index 7fb3277..349aa38 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -637,6 +637,9 @@ typedef struct address_item {
 #ifdef SUPPORT_I18N
     BOOL af_utf8_downcvt:1;        /* downconvert was done for delivery */
 #endif
+#ifdef EXPERIMENTAL_TLS_RESUME
+    BOOL af_tls_resume:1;        /* TLS used a resumed session */
+#endif
   } flags;


unsigned int domain_cache[(MAX_NAMED_LIST * 2)/32];
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 91ddc57..32625d6 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -99,6 +99,17 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
#include "tls-cipher-stdname.c"


+#ifdef MACRO_PREDEF
+void
+options_tls(void)
+{
+# ifdef EXPERIMENTAL_TLS_RESUME
+builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
+# endif
+}
+#else
+
+
/* GnuTLS 2 vs 3

GnuTLS 3 only:
@@ -174,45 +185,9 @@ typedef struct exim_gnutls_state {
} exim_gnutls_state_st;

 static const exim_gnutls_state_st exim_gnutls_state_init = {
-  .session =        NULL,
-  .x509_cred =        NULL,
-  .priority_cache =    NULL,
-  .verify_requirement =    VERIFY_NONE,
+  /* all elements not explicitly intialised here get 0/NULL/FALSE */
   .fd_in =        -1,
   .fd_out =        -1,
-  .peer_cert_verified =    FALSE,
-  .peer_dane_verified =    FALSE,
-  .trigger_sni_changes =FALSE,
-  .have_set_peerdn =    FALSE,
-  .host =        NULL,
-  .peercert =        NULL,
-  .peerdn =        NULL,
-  .ciphersuite =    NULL,
-  .received_sni =    NULL,
-
-  .tls_certificate =    NULL,
-  .tls_privatekey =    NULL,
-  .tls_sni =        NULL,
-  .tls_verify_certificates = NULL,
-  .tls_crl =        NULL,
-  .tls_require_ciphers =NULL,
-
-  .exp_tls_certificate = NULL,
-  .exp_tls_privatekey =    NULL,
-  .exp_tls_verify_certificates = NULL,
-  .exp_tls_crl =    NULL,
-  .exp_tls_require_ciphers = NULL,
-  .exp_tls_verify_cert_hostnames = NULL,
-#ifndef DISABLE_EVENT
-  .event_action =    NULL,
-#endif
-  .tlsp =        NULL,
-
-  .xfer_buffer =    NULL,
-  .xfer_buffer_lwm =    0,
-  .xfer_buffer_hwm =    0,
-  .xfer_eof =        FALSE,
-  .xfer_error =        FALSE,
 };


/* Not only do we have our own APIs which don't pass around state, assuming
@@ -234,9 +209,7 @@ don't want to repeat this. */

static gnutls_dh_params_t dh_server_params = NULL;

-/* No idea how this value was chosen; preserving it.  Default is 3600. */
-
-static const int ssl_session_timeout = 200;
+static int ssl_session_timeout = 3600;    /* One hour */


static const uschar * const exim_default_gnutls_priority = US"NORMAL";

@@ -248,6 +221,9 @@ static BOOL exim_gnutls_base_init_done = FALSE;
static BOOL gnutls_buggy_ocsp = FALSE;
#endif

+#ifdef EXPERIMENTAL_TLS_RESUME
+static gnutls_datum_t server_sessticket_key;
+#endif

/* ------------------------------------------------------------------------ */
/* macros */
@@ -261,7 +237,7 @@ setuid binaries, making it useless - "GNUTLS_DEBUG_LEVEL".
Allegedly the testscript line "GNUTLS_DEBUG_LEVEL=9 sudo exim ..." would work,
but the env var must be added to /etc/sudoers too. */
#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
-# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 9
#endif

#ifndef EXIM_CLIENT_DH_MIN_BITS
@@ -312,6 +288,24 @@ static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,



+/* Daemon one-time initialisation */
+void
+tls_daemon_init(void)
+{
+#ifdef EXPERIMENTAL_TLS_RESUME
+/* We are dependent on the GnuTLS implementation of the Session Ticket
+encryption; both the strength and the key rotation period.  We hope that
+the strength at least matches that of the ciphersuite (but GnuTLS does not
+document this). */
+
+static BOOL once = FALSE;
+if (once) return;
+once = TRUE;
+gnutls_session_ticket_key_generate(&server_sessticket_key);    /* >= 2.10.0 */
+if (f.running_in_test_harness) ssl_session_timeout = 6;
+#endif
+}
+
 /* ------------------------------------------------------------------------ */
 /* Static functions */


@@ -463,7 +457,6 @@ Argument:
static void
extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
{
-gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
int old_pool;
int rc;
@@ -474,12 +467,6 @@ tls_support * tlsp = state->tlsp;
tlsp->active.sock = state->fd_out;
tlsp->active.tls_ctx = state;

-cipher = gnutls_cipher_get(state->session);
-/* returns size in "bytes" */
-tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
-
-tlsp->cipher = state->ciphersuite;
-
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);

tlsp->certificate_verified = state->peer_cert_verified;
@@ -1423,6 +1410,9 @@ if ((rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos)))
if ((rc = gnutls_priority_set(state->session, state->priority_cache)))
return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr);

+/* This also sets the server ticket expiration time to the same, and
+the STEK rotation time to 3x. */
+
gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);

 /* Reduce security in favour of increased compatibility, if the admin
@@ -1492,9 +1482,10 @@ Returns:          OK/DEFER/FAIL
 */


static int
-peer_status(exim_gnutls_state_st *state, uschar ** errstr)
+peer_status(exim_gnutls_state_st * state, uschar ** errstr)
{
-const gnutls_datum_t *cert_list;
+gnutls_session_t session = state->session;
+const gnutls_datum_t * cert_list;
int old_pool, rc;
unsigned int cert_list_size = 0;
gnutls_protocol_t protocol;
@@ -1503,7 +1494,7 @@ gnutls_kx_algorithm_t kx;
gnutls_mac_algorithm_t mac;
gnutls_certificate_type_t ct;
gnutls_x509_crt_t crt;
-uschar *dn_buf;
+uschar * dn_buf;
size_t sz;

if (state->have_set_peerdn)
@@ -1513,23 +1504,24 @@ state->have_set_peerdn = TRUE;
state->peerdn = NULL;

 /* tls_cipher */
-cipher = gnutls_cipher_get(state->session);
-protocol = gnutls_protocol_get_version(state->session);
-mac = gnutls_mac_get(state->session);
+cipher = gnutls_cipher_get(session);
+protocol = gnutls_protocol_get_version(session);
+mac = gnutls_mac_get(session);
 kx =
 #ifdef GNUTLS_TLS1_3
     protocol >= GNUTLS_TLS1_3 ? 0 :
 #endif
-  gnutls_kx_get(state->session);
+  gnutls_kx_get(session);


old_pool = store_pool;
{
+ tls_support * tlsp = state->tlsp;
store_pool = POOL_PERM;

 #ifdef SUPPORT_GNUTLS_SESS_DESC
     {
     gstring * g = NULL;
-    uschar * s = US gnutls_session_get_desc(state->session), c;
+    uschar * s = US gnutls_session_get_desc(session), c;


     /* Nikos M suggests we use this by preference.  It returns like:
     (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
@@ -1568,15 +1560,15 @@ old_pool = store_pool;


/* debug_printf("peer_status: ciphersuite %s\n", state->ciphersuite); */

- state->tlsp->cipher = state->ciphersuite;
- state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
+ tlsp->cipher = state->ciphersuite;
+ tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;

- state->tlsp->cipher_stdname = cipher_stdname_kcm(kx, cipher, mac);
+ tlsp->cipher_stdname = cipher_stdname_kcm(kx, cipher, mac);
}
store_pool = old_pool;

/* tls_peerdn */
-cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
+cert_list = gnutls_certificate_get_peers(session, &cert_list_size);

if (!cert_list || cert_list_size == 0)
{
@@ -1588,7 +1580,7 @@ if (!cert_list || cert_list_size == 0)
return OK;
}

-if ((ct = gnutls_certificate_type_get(state->session)) != GNUTLS_CRT_X509)
+if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
{
const uschar * ctn = US gnutls_certificate_type_get_name(ct);
DEBUG(D_tls)
@@ -1660,13 +1652,14 @@ verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
int rc;
uint verify;

-if (state->verify_requirement == VERIFY_NONE)
- return TRUE;
-
DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
*errstr = NULL;
+rc = peer_status(state, errstr);

-if ((rc = peer_status(state, errstr)) != OK || !state->peerdn)
+if (state->verify_requirement == VERIFY_NONE)
+ return TRUE;
+
+if (rc != OK || !state->peerdn)
{
verify = GNUTLS_CERT_INVALID;
*errstr = US"certificate not supplied";
@@ -2065,7 +2058,6 @@ return g;
static void
post_handshake_debug(exim_gnutls_state_st * state)
{
-debug_printf("gnutls_handshake was successful\n");
#ifdef SUPPORT_GNUTLS_SESS_DESC
debug_printf("%s\n", gnutls_session_get_desc(state->session));
#endif
@@ -2094,6 +2086,63 @@ else
#endif
}

+
+#ifdef EXPERIMENTAL_TLS_RESUME
+static int
+tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
+  unsigned incoming, const gnutls_datum_t * msg)
+{
+DEBUG(D_tls) debug_printf("newticket cb\n");
+tls_in.resumption |= RESUME_CLIENT_REQUESTED;
+return 0;
+}
+
+static void
+tls_server_resume_prehandshake(exim_gnutls_state_st * state)
+{
+/* Should the server offer session resumption? */
+tls_in.resumption = RESUME_SUPPORTED;
+if (verify_check_host(&tls_resumption_hosts) == OK)
+  {
+  int rc;
+  /* GnuTLS appears to not do ticket overlap, but does emit a fresh ticket when
+  an offered resumption is unacceptable.  We lose one resumption per ticket
+  lifetime, and sessions cannot be indefinitely re-used.  There seems to be no
+  way (3.6.7) of changing the default number of 2 TLS1.3 tickets issued, but at
+  least they go out in a single packet. */
+
+  if (!(rc = gnutls_session_ticket_enable_server(state->session,
+          &server_sessticket_key)))
+    tls_in.resumption |= RESUME_SERVER_TICKET;
+  else
+    DEBUG(D_tls)
+      debug_printf("enabling session tickets: %s\n", US gnutls_strerror(rc));
+
+  /* Try to tell if we see a ticket request */
+  gnutls_handshake_set_hook_function(state->session,
+    GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_server_ticket_cb);
+  }
+}
+
+static void
+tls_server_resume_posthandshake(exim_gnutls_state_st * state)
+{
+if (gnutls_session_resumption_requested(state->session))
+  {
+  /* This tells us the client sent a full ticket.  We use a
+  callback on session-ticket request, elsewhere, to tell
+  if a client asked for a ticket. */
+
+  tls_in.resumption |= RESUME_CLIENT_SUGGESTED;
+  DEBUG(D_tls) debug_printf("client requested resumption\n");
+  }
+if (gnutls_session_is_resumed(state->session))
+  {
+  tls_in.resumption |= RESUME_USED;
+  DEBUG(D_tls) debug_printf("Session resumed\n");
+  }
+}
+#endif
 /* ------------------------------------------------------------------------ */
 /* Exported functions */


@@ -2141,6 +2190,10 @@ if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
     NULL, tls_verify_certificates, tls_crl,
     require_ciphers, &state, &tls_in, errstr)) != OK) return rc;


+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_server_resume_prehandshake(state);
+#endif
+
/* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */

@@ -2240,6 +2293,10 @@ if (rc != GNUTLS_E_SUCCESS)
return FAIL;
}

+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_server_resume_posthandshake(state);
+#endif
+
DEBUG(D_tls) post_handshake_debug(state);

 /* Verify after the fact */
@@ -2256,10 +2313,6 @@ if (!verify_certificate(state, errstr))
     *errstr);
   }


-/* Figure out peer DN, and if authenticated, etc. */
-
-if ((rc = peer_status(state, NULL)) != OK) return rc;
-
/* Sets various Exim expansion variables; always safe within server */

extract_exim_vars_from_tls_state(state);
@@ -2372,6 +2425,140 @@ return TRUE;



+#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.  Although
+there is a gnutls_session_ticket_enable_client() interface it is
+documented as unnecessary (as of 3.6.7) as "session tickets are emabled
+by deafult".  There seems to be no way to disable them, so even hosts not
+enabled by the transport option will be sent a ticket request.  We will
+however avoid storing and retrieving session information. */
+
+static void
+tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
+  host_item * host, smtp_transport_options_block * ob)
+{
+tlsp->resumption = RESUME_SUPPORTED;
+if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+  {
+  dbdata_tls_session * dt;
+  int len, rc;
+  open_db dbblock, * dbm_file;
+
+  DEBUG(D_tls)
+    debug_printf("check for resumable session for %s\n", host->address);
+  tlsp->host_resumable = TRUE;
+  tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+  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, host->address, &len)))
+      if (!(rc = gnutls_session_set_data(session,
+            CUS dt->session, (size_t)len - sizeof(dbdata_tls_session))))
+    {
+    DEBUG(D_tls) debug_printf("good session\n");
+    tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+    }
+      else DEBUG(D_tls) debug_printf("setting session resumption data: %s\n",
+        US gnutls_strerror(rc));
+    dbfn_close(dbm_file);
+    }
+  }
+}
+
+
+static void
+tls_save_session(tls_support * tlsp, gnutls_session_t session, const host_item * host)
+{
+/* TLS 1.2 - we get both the callback and the direct posthandshake call,
+but this flag is not set until the second.  TLS 1.3 it's the other way about.
+Keep both calls as the session data cannot be extracted before handshake
+completes. */
+
+if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
+  {
+  gnutls_datum_t tkt;
+  int rc;
+
+  DEBUG(D_tls) debug_printf("server offered session ticket\n");
+  tlsp->ticket_received = TRUE;
+  tlsp->resumption |= RESUME_SERVER_TICKET;
+
+  if (tlsp->host_resumable)
+    if (!(rc = gnutls_session_get_data2(session, &tkt)))
+      {
+      open_db dbblock, * dbm_file;
+      int dlen = sizeof(dbdata_tls_session) + tkt.size;
+      dbdata_tls_session * dt = store_get(dlen);
+
+      DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size);
+      memcpy(dt->session, tkt.data, tkt.size);
+      gnutls_free(tkt.data);
+
+      if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
+    {
+    /* key for the db is the IP */
+    dbfn_delete(dbm_file, host->address);
+    dbfn_write(dbm_file, host->address, dt, dlen);
+    dbfn_close(dbm_file);
+
+    DEBUG(D_tls)
+      debug_printf("wrote session db (len %u)\n", (unsigned)dlen);
+    }
+      }
+    else DEBUG(D_tls)
+      debug_printf("extract session data: %s\n", US gnutls_strerror(rc));
+  }
+}
+
+
+/* With a TLS1.3 session, the ticket(s) are not seen until
+the first data read is attempted.  And there's often two of them.
+Pick them up with this callback.  We are also called for 1.2
+but we do nothing.
+*/
+static int
+tls_client_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
+  unsigned incoming, const gnutls_datum_t * msg)
+{
+exim_gnutls_state_st * state = gnutls_session_get_ptr(sess);
+tls_support * tlsp = state->tlsp;
+
+DEBUG(D_tls) debug_printf("newticket cb\n");
+
+if (!tlsp->ticket_received)
+  tls_save_session(tlsp, sess, state->host);
+return 0;
+}
+
+
+static void
+tls_client_resume_prehandshake(exim_gnutls_state_st * state,
+  tls_support * tlsp, host_item * host,
+  smtp_transport_options_block * ob)
+{
+gnutls_session_set_ptr(state->session, state);
+gnutls_handshake_set_hook_function(state->session,
+  GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_client_ticket_cb);
+
+tls_retrieve_session(tlsp, state->session, host, ob);
+}
+
+static void
+tls_client_resume_posthandshake(exim_gnutls_state_st * state,
+  tls_support * tlsp, host_item * host)
+{
+if (gnutls_session_is_resumed(state->session))
+  {
+  DEBUG(D_tls) debug_printf("Session resumed\n");
+  tlsp->resumption |= RESUME_USED;
+  }
+
+tls_save_session(tlsp, state->session, host);
+}
+#endif    /* EXPERIMENTAL_TLS_RESUME */
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -2512,6 +2699,10 @@ if (request_ocsp)
   }
 #endif


+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_client_resume_prehandshake(state, tlsp, host, ob);
+#endif
+
#ifndef DISABLE_EVENT
if (tb && tb->event_action)
{
@@ -2589,10 +2780,9 @@ if (require_ocsp)
}
#endif

-/* Figure out peer DN, and if authenticated, etc. */
-
-if (peer_status(state, errstr) != OK)
- return FALSE;
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_client_resume_posthandshake(state, tlsp, host);
+#endif

/* Sets various Exim expansion variables; may need to adjust for ACL callouts */

@@ -3090,6 +3280,7 @@ fprintf(f, "Library version: GnuTLS: Compile: %s\n"
            gnutls_check_version(NULL));
 }


+#endif    /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of tls-gnu.c */
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index c7b22a0..06008de 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -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,7 @@ 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
# else
# define EXIM_NEED_OPENSSL_INIT
# endif
@@ -249,6 +250,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

@@ -303,7 +308,7 @@ static SSL_CTX *server_sni = NULL;

static char ssl_errstring[256];

-static int ssl_session_timeout = 200;
+static int ssl_session_timeout = 3600;
static BOOL client_verify_optional = FALSE;
static BOOL server_verify_optional = FALSE;

@@ -311,6 +316,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;
@@ -342,7 +348,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
@@ -358,6 +364,23 @@ static int tls_server_stapling_cb(SSL *s, void *arg);
#endif


+
+/* Daemon-called 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                 *
 *************************************************/
@@ -803,6 +826,121 @@ 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[16];    /* 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;
+
+/*XXX for now just always create/find the one key.
+Worry about rotation and overlap later. */
+
+static exim_stek exim_tk;
+static exim_stek exim_tk_old;
+
+static void
+tk_init(void)
+{
+if (exim_tk.name[0])
+  {
+  if (exim_tk.renew >= time(NULL)) 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_128_cbc();
+exim_tk.hmac_hash = EVP_sha256();
+exim_tk.expire = time(NULL) + ssl_session_timeout;
+exim_tk.renew = exim_tk.expire - 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);
+  return key->renew < now ? 2 : 1;
+  }
+}
+#endif
+
+


 /*************************************************
 *                Initialize for DH               *
@@ -1394,6 +1532,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
@@ -1714,7 +1855,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;
@@ -1722,6 +1865,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;
@@ -1795,10 +1939,16 @@ 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);
+#ifndef 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);
@@ -1815,8 +1965,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(
@@ -1825,10 +1989,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.
@@ -1903,7 +2063,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");
@@ -2226,7 +2387,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;


@@ -2244,8 +2405,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);
@@ -2276,6 +2436,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)))
@@ -2329,7 +2502,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. */
@@ -2352,6 +2533,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))
+    debug_printf("The session has a ticket, life %lu seconds\n",
+      SSL_SESSION_get_ticket_lifetime_hint(ss));
+  }
+#endif
   }


/* Record the certificate we presented */
@@ -2483,6 +2673,160 @@ 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);
+      }
+    }
+      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;
+    }
+      }
+    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 EXIM_HAVE_SESSION_TICKET
+
+if (SSL_SESSION_is_resumable(ss)) 
+  {
+  int len = i2d_SSL_SESSION(ss, NULL);
+  int dlen = sizeof(dbdata_tls_session) + len;
+  dbdata_tls_session * dt = store_get(dlen);
+  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 */
+
+  len = 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);
+    }
+  }
+# endif
+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             *
 *************************************************/
@@ -2562,7 +2906,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;
@@ -2629,12 +2973,24 @@ 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));
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host,
+      errstr))
+  return FALSE;
+#endif
+
 SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
 SSL_set_connect_state(exim_client_ctx->ssl);


@@ -2729,6 +3085,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);
@@ -3377,12 +3737,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
@@ -3393,7 +3758,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')
diff --git a/src/src/tls.c b/src/src/tls.c
index 23e9d41..7b8d7a2 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -20,8 +20,10 @@ functions from the OpenSSL or GNU TLS libraries. */
#include "transports/smtp.h"

#if defined(MACRO_PREDEF) && defined(SUPPORT_TLS)
-# ifndef USE_GNUTLS
-# include "macro_predef.h"
+# include "macro_predef.h"
+# ifdef USE_GNUTLS
+# include "tls-gnu.c"
+# else
# include "tls-openssl.c"
# endif
#endif
diff --git a/src/src/transport.c b/src/src/transport.c
index f34db09..fb74dfd 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1475,7 +1475,7 @@ DEBUG(D_transport) debug_printf("updating wait-%s database\n", tpname);
/* Open the database for this transport */

 if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", tpname),
-              O_RDWR, &dbblock, TRUE)))
+              O_RDWR, &dbblock, TRUE, TRUE)))
   return;


/* Scan the list of hosts for which this message is waiting, and ensure
@@ -1648,7 +1648,7 @@ if (local_message_max > 0 && continue_sequence >= local_message_max)
/* Open the waiting information database. */

 if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
-              O_RDWR, &dbblock, TRUE)))
+              O_RDWR, &dbblock, TRUE, TRUE)))
   return FALSE;


 /* See if there is a record for this host; if not, there's nothing to do. */
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 806c41f..f76b4f7 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -183,6 +183,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
   { "tls_require_ciphers",  opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
+# ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_resumption_hosts", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tls_resumption_hosts) },
+# endif
   { "tls_sni",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_sni) },
   { "tls_tempfail_tryclear", opt_bool,
@@ -293,6 +297,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .tls_verify_certificates =    US"system",
   .tls_dh_min_bits =        EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
   .tls_tempfail_tryclear =    TRUE,
+# ifdef EXPERIMENTAL_TLS_RESUME
+  .tls_resumption_hosts =    NULL,
+# endif
   .tls_verify_hosts =        NULL,
   .tls_try_verify_hosts =    US"*",
   .tls_verify_cert_hostnames =    US"*",
@@ -825,7 +832,7 @@ write_ehlo_cache_entry(const smtp_context * sx)
 {
 open_db dbblock, * dbm_file;


-if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
{
uschar * ehlo_resp_key = ehlo_cache_key(sx);
dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
@@ -845,7 +852,7 @@ invalidate_ehlo_cache_entry(smtp_context * sx)
open_db dbblock, * dbm_file;

if ( sx->early_pipe_active
- && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
{
uschar * ehlo_resp_key = ehlo_cache_key(sx);
dbfn_delete(dbm_file, ehlo_resp_key);
@@ -859,7 +866,7 @@ read_ehlo_cache_entry(smtp_context * sx)
open_db dbblock;
open_db * dbm_file;

-if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE)))
+if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE, TRUE)))
   { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); }
 else
   {
@@ -872,7 +879,7 @@ else
     {
     DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
     dbfn_close(dbm_file);
-    if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+    if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
       dbfn_delete(dbm_file, ehlo_resp_key);
     }
   else
@@ -2016,6 +2023,9 @@ tls_out.peerdn = NULL;
 tls_out.sni = NULL;
 #endif
 tls_out.ocsp = OCSP_NOT_REQ;
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_out.resumption = 0;
+#endif


 /* Flip the legacy TLS-related variables over to the outbound set in case
 they're used in the context of the transport.  Don't bother resetting
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 056efbc..ab0e93f 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -83,6 +83,9 @@ typedef struct {
   uschar *tls_crl;
   uschar *tls_privatekey;
   uschar *tls_require_ciphers;
+# ifdef EXPERIMENTAL_TLS_RESUME
+  uschar *tls_resumption_hosts;
+# endif
   uschar *tls_sni;
   uschar *tls_verify_certificates;
   int     tls_dh_min_bits;
diff --git a/src/src/verify.c b/src/src/verify.c
index 528a668..720f85d 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -140,7 +140,7 @@ if (options & vopt_callout_no_cache)
   {
   HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
   }
-else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)))
+else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE)))
   {
   HDEBUG(D_verify) debug_printf("callout cache: not available\n");
   }
@@ -313,7 +313,7 @@ implying some kind of I/O error. We don't want to write the cache in that case.
 Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */


 if (dom_rec->result != ccache_unknown)
-  if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE)))
+  if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
     {
     HDEBUG(D_verify) debug_printf("callout cache: not available\n");
     }
@@ -335,7 +335,7 @@ is disabled. */
 if (done  &&  addr_rec->result != ccache_unknown)
   {
   if (!dbm_file)
-    dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
+    dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE);
   if (!dbm_file)
     {
     HDEBUG(D_verify) debug_printf("no callout cache available\n");
@@ -3245,7 +3245,7 @@ int
 verify_check_host(uschar **listptr)
 {
 return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
-  (sender_host_address == NULL)? US"" : sender_host_address, NULL);
+  sender_host_address ? sender_host_address : US"", NULL);
 }



diff --git a/test/aux-var-src/tls_conf_prefix b/test/aux-var-src/tls_conf_prefix
index 965bc8b..1c464f6 100644
--- a/test/aux-var-src/tls_conf_prefix
+++ b/test/aux-var-src/tls_conf_prefix
@@ -1,4 +1,4 @@
-keep_environment = PATH
+keep_environment = PATH:SSLKEYLOGFILE
 exim_path = EXIM_PATH
 host_lookup_order = bydns
 spool_directory = DIR/spool
diff --git a/test/confs/5890 b/test/confs/5890
new file mode 100644
index 0000000..6daf596
--- /dev/null
+++ b/test/confs/5890
@@ -0,0 +1,94 @@
+# Exim test configuration 5890
+
+SERVER =
+OPTION = NORMAL
+
+.include DIR/aux-var/tls_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+domainlist local_domains = test.ex : *.test.ex
+
+acl_smtp_helo = check_helo
+acl_smtp_rcpt = check_recipient
+log_selector = +received_recipients +tls_resumption
+
+tls_advertise_hosts = *
+
+# Set certificate only if server
+
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+tls_require_ciphers = OPTION
+tls_resumption_hosts = 127.0.0.1
+
+
+# ------ ACL ------
+
+begin acl
+
+check_helo:
+  accept  condition =    ${if def:tls_in_cipher}
+      logwrite =    tls_in_resumption ${listextract {$tls_in_resumption} {_RESUME_DECODE}}
+  accept
+
+check_recipient:
+  accept  domains =    +local_domains
+  deny    message =    relay not permitted
+
+log_resumption:
+  accept condition =    ${if def:tls_out_cipher}
+     condition =    ${if eq {$event_name}{tcp:close}}
+     logwrite =    tls_out_resumption ${listextract {$tls_out_resumption} {_RESUME_DECODE}}
+
+
+# ----- Routers -----
+
+begin routers
+
+client:
+  driver = accept
+  condition = ${if eq {SERVER}{server}{no}{yes}}
+  retry_use_local_part
+  transport = send_to_server${if eq{$local_part}{abcd}{2}{1}}
+
+server:
+  driver = redirect
+  data = :blackhole:
+
+# ----- Transports -----
+
+begin transports
+
+send_to_server1:
+  driver = smtp
+  allow_localhost
+  hosts = 127.0.0.1
+  port = PORT_D
+  helo_data = helo.data.changed
+.ifdef VALUE
+  tls_resumption_hosts = *
+.else
+  tls_resumption_hosts = :
+.endif
+  event_action =    ${acl {log_resumption}}
+
+send_to_server2:
+  driver = smtp
+  allow_localhost
+  hosts = HOSTIPV4
+  port = PORT_D
+  event_action =    ${acl {log_resumption}}
+
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
diff --git a/test/confs/5891 b/test/confs/5891
new file mode 100644
index 0000000..78d22f7
--- /dev/null
+++ b/test/confs/5891
@@ -0,0 +1,94 @@
+# Exim test configuration 5891
+
+SERVER =
+OPTION =
+
+.include DIR/aux-var/tls_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+domainlist local_domains = test.ex : *.test.ex
+
+acl_smtp_helo = check_helo
+acl_smtp_rcpt = check_recipient
+log_selector = +received_recipients +tls_resumption
+
+openssl_options = +no_sslv2 +no_sslv3 +single_dh_use OPTION
+tls_advertise_hosts = *
+
+# Set certificate only if server
+
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+tls_resumption_hosts = 127.0.0.1
+
+
+# ------ ACL ------
+
+begin acl
+
+check_helo:
+  accept  condition =    ${if def:tls_in_cipher}
+      logwrite =    tls_in_resumption ${listextract {$tls_in_resumption} {_RESUME_DECODE}}
+  accept
+
+check_recipient:
+  accept  domains =    +local_domains
+  deny    message =    relay not permitted
+
+log_resumption:
+  accept condition =    ${if def:tls_out_cipher}
+     condition =    ${if eq {$event_name}{tcp:close}}
+     logwrite =    tls_out_resumption ${listextract {$tls_out_resumption} {_RESUME_DECODE}}
+
+
+# ----- Routers -----
+
+begin routers
+
+client:
+  driver = accept
+  condition = ${if eq {SERVER}{server}{no}{yes}}
+  retry_use_local_part
+  transport = send_to_server${if eq{$local_part}{abcd}{2}{1}}
+
+server:
+  driver = redirect
+  data = :blackhole:
+
+# ----- Transports -----
+
+begin transports
+
+send_to_server1:
+  driver = smtp
+  allow_localhost
+  hosts = 127.0.0.1
+  port = PORT_D
+  helo_data = helo.data.changed
+.ifdef VALUE
+  tls_resumption_hosts = *
+.else
+  tls_resumption_hosts = :
+.endif
+  event_action =    ${acl {log_resumption}}
+
+send_to_server2:
+  driver = smtp
+  allow_localhost
+  hosts = HOSTIPV4
+  port = PORT_D
+  event_action =    ${acl {log_resumption}}
+
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
diff --git a/test/log/2102 b/test/log/2102
index 5ec2c1c..215bbe2 100644
--- a/test/log/2102
+++ b/test/log/2102
@@ -40,7 +40,7 @@
 1999-03-02 09:44:33 sha256 fingerprint E251FA7D0372CB784294CF92B243DCE53FDDABD9F58A1B89226586C07C82CAC6
 1999-03-02 09:44:33 der_b64 MIIDuDCCAqCgAwIBAgICAMkwDQYJKoZIhvcNAQELBQAwNzEUMBIGA1UEChMLZXhhbXBsZS5jb20xHzAdBgNVBAMTFmNsaWNhIFNpZ25pbmcgQ2VydCByc2EwHhcNMTIxMTAxMTI0MDA0WhcNMzcxMjAxMTI0MDA0WjAeMRwwGgYDVQQDExNzZXJ2ZXIyLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA52Rfiv2Igy0NiaKN5gc0VPLbEoHngkdJWv3wEORp+iFl6skQRbsCylT8djJ2pvHstFpnzSodF3Wwjj2/EDuj3iKBzN9HeXJOvJz8j9Si1xkgCxJeUjPGgYcvKdxybaZAOpi9l3xwPCCEXN4JBq/WaQQ9+eP1PczeMNfvFtXma+VcHXG743ttPOv7eSMr0JxQl3zjQvYGOhFP/KAw6jh/N6YPqii9kV0cC/ubeVzpqJ5/+hndx5YrmAu39N5qzwWujhDPkFNSgCJUhfkEiMaQiPxFxDTbUzWnQ5jpAQ5El4WJVkGWkqxose1bOjSSNzFPJt59YtxxJC3IWN3UtGODTwIDAQABo4HmMIHjMA4GA1UdDwEB/wQEAwIE8DAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTgYDVR0jBEcwRYANQUFidHdDeGNYZ2IwUaExpC8wLTEUMBIGA1UEChMLZXhhbXBsZS5jb20xFTATBgNVBAMTDGNsaWNhIENBIHJzYYIBQjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vc2NwLmV4YW1wbGUuY29tLzApBgNVHREEIjAgghNzZXJ2ZXIyLmV4YW1wbGUuY29tggkqLnRlc3QuZXgwDQYJKoZIhvcNAQELBQADggEBALHOkZkvHLpNm0QSof09vmmdNFE6/+0TCIoPExeqqSOsy4NsF+Ha46WttjJRSVtbhRxF8jxEU7btPiFgQUaOcJZTwQPDhmQSOPNO8GS46oJ57aQ7U7O+X3M1sVS5Pa2IzE6vrJSh349/CNbTA8WPQdWLlxVJhJXAcZNtaEu6lCsZuDSMTpAsW5I4+snyrm3yvP5t0eD28K5LgCKePX962drkAOP6XGQ51VnbMQ7b1TSdQedtYKIpR3VKUvG5Ky/+0c+Rmwfi2aQ8oXXwekzJyS5jvovdVVsdhO68It+Rz/zursN5Pn+Gj1YuQNUs2nDrGHN+VIIFpgWUjLZO4bcJctY=
 1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=[ip4.ip4.ip4.ip4] P=smtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes DN="/CN=server2.example.com" S=sss
-1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
 1999-03-02 09:44:33 Our cert SN: <CN=server1.example_ec.com>
 1999-03-02 09:44:33 Peer did not present a cert
 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? H=[127.0.0.1] P=smtps X=TLS1.x:ke-ECDSA-AES256-SHAnnn:xxx CV=no S=sss
diff --git a/test/log/5890 b/test/log/5890
new file mode 100644
index 0000000..9d098e5
--- /dev/null
+++ b/test/log/5890
@@ -0,0 +1,116 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for getticket@???
+1999-03-02 09:44:33 10HmaX-0005vi-00 tls_out_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmaX-0005vi-00 => getticket@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for resume@??? abcd@??? xyz@???
+1999-03-02 09:44:33 10HmaZ-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 tls_out_resumption not requested or offered
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => resume@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 -> xyz@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => abcd@??? R=client T=send_to_server2 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for renewal@???
+1999-03-02 09:44:33 10HmbC-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbC-0005vi-00 => renewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbD-0005vi-00"
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for postrenewal@???
+1999-03-02 09:44:33 10HmbE-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbE-0005vi-00 => postrenewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbF-0005vi-00"
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for timeout@???
+1999-03-02 09:44:33 10HmbG-0005vi-00 tls_out_resumption client offered session, server only provided new ticket
+1999-03-02 09:44:33 10HmbG-0005vi-00 => timeout@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbH-0005vi-00"
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for notreq@???
+1999-03-02 09:44:33 10HmbI-0005vi-00 tls_out_resumption no client request
+1999-03-02 09:44:33 10HmbI-0005vi-00 => notreq@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbJ-0005vi-00"
+1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbK-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for getticket@???
+1999-03-02 09:44:33 10HmbK-0005vi-00 tls_out_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbK-0005vi-00 => getticket@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbL-0005vi-00"
+1999-03-02 09:44:33 10HmbK-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbM-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for resume@??? abcd@??? xyz@???
+1999-03-02 09:44:33 10HmbM-0005vi-00 tls_out_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbM-0005vi-00 tls_out_resumption not requested or offered
+1999-03-02 09:44:33 10HmbM-0005vi-00 => resume@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbN-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 -> xyz@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbN-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 => abcd@??? R=client T=send_to_server2 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbO-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbP-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for renewal@???
+1999-03-02 09:44:33 10HmbP-0005vi-00 tls_out_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbP-0005vi-00 => renewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbQ-0005vi-00"
+1999-03-02 09:44:33 10HmbP-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbR-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for postrenewal@???
+1999-03-02 09:44:33 10HmbR-0005vi-00 tls_out_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbR-0005vi-00 => postrenewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbS-0005vi-00"
+1999-03-02 09:44:33 10HmbR-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbT-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for timeout@???
+1999-03-02 09:44:33 10HmbT-0005vi-00 tls_out_resumption client offered session, server only provided new ticket
+1999-03-02 09:44:33 10HmbT-0005vi-00 => timeout@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbU-0005vi-00"
+1999-03-02 09:44:33 10HmbT-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbV-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for notreq@???
+1999-03-02 09:44:33 10HmbV-0005vi-00 tls_out_resumption no client request
+1999-03-02 09:44:33 10HmbV-0005vi-00 => notreq@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbW-0005vi-00"
+1999-03-02 09:44:33 10HmbV-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaX-0005vi-00@??? for getticket@???
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <getticket@???> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no S=sss id=E10HmaZ-0005vi-00@??? for resume@??? xyz@???
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <xyz@???> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <resume@???> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption not requested or offered
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@??? H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaZ-0005vi-00@??? for abcd@???
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <abcd@???> R=server
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbC-0005vi-00@??? for renewal@???
+1999-03-02 09:44:33 10HmbD-0005vi-00 => :blackhole: <renewal@???> R=server
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke--AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbE-0005vi-00@??? for postrenewal@???
+1999-03-02 09:44:33 10HmbF-0005vi-00 => :blackhole: <postrenewal@???> R=server
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption client offered session, server only provided new ticket
+1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbG-0005vi-00@??? for timeout@???
+1999-03-02 09:44:33 10HmbH-0005vi-00 => :blackhole: <timeout@???> R=server
+1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbJ-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbI-0005vi-00@??? for notreq@???
+1999-03-02 09:44:33 10HmbJ-0005vi-00 => :blackhole: <notreq@???> R=server
+1999-03-02 09:44:33 10HmbJ-0005vi-00 Completed
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbL-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbK-0005vi-00@??? for getticket@???
+1999-03-02 09:44:33 10HmbL-0005vi-00 => :blackhole: <getticket@???> R=server
+1999-03-02 09:44:33 10HmbL-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbN-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbM-0005vi-00@??? for resume@??? xyz@???
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <xyz@???> R=server
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <resume@???> R=server
+1999-03-02 09:44:33 10HmbN-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption not requested or offered
+1999-03-02 09:44:33 10HmbO-0005vi-00 <= CALLER@??? H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbM-0005vi-00@??? for abcd@???
+1999-03-02 09:44:33 10HmbO-0005vi-00 => :blackhole: <abcd@???> R=server
+1999-03-02 09:44:33 10HmbO-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbQ-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbP-0005vi-00@??? for renewal@???
+1999-03-02 09:44:33 10HmbQ-0005vi-00 => :blackhole: <renewal@???> R=server
+1999-03-02 09:44:33 10HmbQ-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbS-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-PSK-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbR-0005vi-00@??? for postrenewal@???
+1999-03-02 09:44:33 10HmbS-0005vi-00 => :blackhole: <postrenewal@???> R=server
+1999-03-02 09:44:33 10HmbS-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbU-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbT-0005vi-00@??? for timeout@???
+1999-03-02 09:44:33 10HmbU-0005vi-00 => :blackhole: <timeout@???> R=server
+1999-03-02 09:44:33 10HmbU-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbW-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbV-0005vi-00@??? for notreq@???
+1999-03-02 09:44:33 10HmbW-0005vi-00 => :blackhole: <notreq@???> R=server
+1999-03-02 09:44:33 10HmbW-0005vi-00 Completed
diff --git a/test/log/5891 b/test/log/5891
new file mode 100644
index 0000000..8131404
--- /dev/null
+++ b/test/log/5891
@@ -0,0 +1,130 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for getticket@???
+1999-03-02 09:44:33 10HmaX-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmaX-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="127.0.0.1"
+1999-03-02 09:44:33 10HmaX-0005vi-00 tls_out_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmaX-0005vi-00 => getticket@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for resume@??? abcd@??? xyz@???
+1999-03-02 09:44:33 10HmaZ-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 [ip4.ip4.ip4.ip4] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmaZ-0005vi-00 [ip4.ip4.ip4.ip4] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="ip4.ip4.ip4.ip4"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 tls_out_resumption not requested or offered
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => resume@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 -> xyz@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => abcd@??? R=client T=send_to_server2 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for renewal@???
+1999-03-02 09:44:33 10HmbC-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbC-0005vi-00 => renewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbD-0005vi-00"
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for postrenewal@???
+1999-03-02 09:44:33 10HmbE-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbE-0005vi-00 => postrenewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbF-0005vi-00"
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for timeout@???
+1999-03-02 09:44:33 10HmbG-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmbG-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="127.0.0.1"
+1999-03-02 09:44:33 10HmbG-0005vi-00 tls_out_resumption client offered session, server only provided new ticket
+1999-03-02 09:44:33 10HmbG-0005vi-00 => timeout@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbH-0005vi-00"
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for notreq@???
+1999-03-02 09:44:33 10HmbI-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmbI-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="127.0.0.1"
+1999-03-02 09:44:33 10HmbI-0005vi-00 tls_out_resumption not requested or offered
+1999-03-02 09:44:33 10HmbI-0005vi-00 => notreq@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbJ-0005vi-00"
+1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbK-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for getticket@???
+1999-03-02 09:44:33 10HmbK-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmbK-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="127.0.0.1"
+1999-03-02 09:44:33 10HmbK-0005vi-00 tls_out_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbK-0005vi-00 => getticket@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbL-0005vi-00"
+1999-03-02 09:44:33 10HmbK-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbM-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for resume@??? abcd@??? xyz@???
+1999-03-02 09:44:33 10HmbM-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbM-0005vi-00 [ip4.ip4.ip4.ip4] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmbM-0005vi-00 [ip4.ip4.ip4.ip4] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="ip4.ip4.ip4.ip4"
+1999-03-02 09:44:33 10HmbM-0005vi-00 tls_out_resumption not requested or offered
+1999-03-02 09:44:33 10HmbM-0005vi-00 => resume@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbN-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 -> xyz@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbN-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 => abcd@??? R=client T=send_to_server2 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbO-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbP-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for renewal@???
+1999-03-02 09:44:33 10HmbP-0005vi-00 tls_out_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbP-0005vi-00 => renewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbQ-0005vi-00"
+1999-03-02 09:44:33 10HmbP-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbR-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for postrenewal@???
+1999-03-02 09:44:33 10HmbR-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbR-0005vi-00 => postrenewal@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbS-0005vi-00"
+1999-03-02 09:44:33 10HmbR-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbT-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for timeout@???
+1999-03-02 09:44:33 10HmbT-0005vi-00 tls_out_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbT-0005vi-00 => timeout@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no C="250 OK id=10HmbU-0005vi-00"
+1999-03-02 09:44:33 10HmbT-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbV-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for notreq@???
+1999-03-02 09:44:33 10HmbV-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmbV-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" H="127.0.0.1"
+1999-03-02 09:44:33 10HmbV-0005vi-00 tls_out_resumption not requested or offered
+1999-03-02 09:44:33 10HmbV-0005vi-00 => notreq@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmbW-0005vi-00"
+1999-03-02 09:44:33 10HmbV-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaX-0005vi-00@??? for getticket@???
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <getticket@???> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmaZ-0005vi-00@??? for resume@??? xyz@???
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <xyz@???> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <resume@???> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption not requested or offered
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@??? H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaZ-0005vi-00@??? for abcd@???
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <abcd@???> R=server
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbC-0005vi-00@??? for renewal@???
+1999-03-02 09:44:33 10HmbD-0005vi-00 => :blackhole: <renewal@???> R=server
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbE-0005vi-00@??? for postrenewal@???
+1999-03-02 09:44:33 10HmbF-0005vi-00 => :blackhole: <postrenewal@???> R=server
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption client offered session, server only provided new ticket
+1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbG-0005vi-00@??? for timeout@???
+1999-03-02 09:44:33 10HmbH-0005vi-00 => :blackhole: <timeout@???> R=server
+1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption no client request
+1999-03-02 09:44:33 10HmbJ-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbI-0005vi-00@??? for notreq@???
+1999-03-02 09:44:33 10HmbJ-0005vi-00 => :blackhole: <notreq@???> R=server
+1999-03-02 09:44:33 10HmbJ-0005vi-00 Completed
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbL-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbK-0005vi-00@??? for getticket@???
+1999-03-02 09:44:33 10HmbL-0005vi-00 => :blackhole: <getticket@???> R=server
+1999-03-02 09:44:33 10HmbL-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed
+1999-03-02 09:44:33 10HmbN-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbM-0005vi-00@??? for resume@??? xyz@???
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <xyz@???> R=server
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <resume@???> R=server
+1999-03-02 09:44:33 10HmbN-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption not requested or offered
+1999-03-02 09:44:33 10HmbO-0005vi-00 <= CALLER@??? H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbM-0005vi-00@??? for abcd@???
+1999-03-02 09:44:33 10HmbO-0005vi-00 => :blackhole: <abcd@???> R=server
+1999-03-02 09:44:33 10HmbO-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbQ-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbP-0005vi-00@??? for renewal@???
+1999-03-02 09:44:33 10HmbQ-0005vi-00 => :blackhole: <renewal@???> R=server
+1999-03-02 09:44:33 10HmbQ-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed
+1999-03-02 09:44:33 10HmbS-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbR-0005vi-00@??? for postrenewal@???
+1999-03-02 09:44:33 10HmbS-0005vi-00 => :blackhole: <postrenewal@???> R=server
+1999-03-02 09:44:33 10HmbS-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption session resumed, also new ticket
+1999-03-02 09:44:33 10HmbU-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx* CV=no S=sss id=E10HmbT-0005vi-00@??? for timeout@???
+1999-03-02 09:44:33 10HmbU-0005vi-00 => :blackhole: <timeout@???> R=server
+1999-03-02 09:44:33 10HmbU-0005vi-00 Completed
+1999-03-02 09:44:33 tls_in_resumption client requested new ticket, server provided
+1999-03-02 09:44:33 10HmbW-0005vi-00 <= CALLER@??? H=(helo.data.changed) [127.0.0.1] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbV-0005vi-00@??? for notreq@???
+1999-03-02 09:44:33 10HmbW-0005vi-00 => :blackhole: <notreq@???> R=server
+1999-03-02 09:44:33 10HmbW-0005vi-00 Completed
diff --git a/test/runtest b/test/runtest
index 6566579..a992747 100755
--- a/test/runtest
+++ b/test/runtest
@@ -615,9 +615,10 @@ RESET_AFTER_EXTRA_LINE_READ:
   #   TLS1.2:ECDHE_SECP256R1__RSA_SHA256__AES_256_GCM:256
   #   TLS1.2:ECDHE_SECP256R1__RSA_SHA256__AES_128_CBC__SHA256:128
   #   TLS1.2:ECDHE_SECP256R1__ECDSA_SHA512__AES_256_GCM:256
-  #   TLS1.2:ECDHE_RSA_SECP256R1__AES_256_GCM:256 (! 3.5.18 !)
-  #   TLS1.2:RSA__CAMELLIA_256_GCM:256    (leave the cipher name)
-  #   TLS1.2-PKIX:RSA__AES_128_GCM__AEAD:128 (the -PKIX seems to be a 3.1.20 thing)
+  #   TLS1.2:ECDHE_SECP256R1__AES_256_GCM:256        (3.6.7 resumption)
+  #   TLS1.2:ECDHE_RSA_SECP256R1__AES_256_GCM:256    (! 3.5.18 !)
+  #   TLS1.2:RSA__CAMELLIA_256_GCM:256            (leave the cipher name)
+  #   TLS1.2-PKIX:RSA__AES_128_GCM__AEAD:128        (the -PKIX seems to be a 3.1.20 thing)
   #   TLS1.2-PKIX:ECDHE_RSA_SECP521R1__AES_256_GCM__AEAD:256
   #
   #   X=TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256
@@ -1000,6 +1001,10 @@ RESET_AFTER_EXTRA_LINE_READ:


     # ARC is not always supported by the build
     next if /^arc_sign =/;
+
+    # TLS resumption is not always supported by the build
+    next if /^tls_resumption_hosts =/;
+    next if /^-tls_resumption/;
     }


   # ======== stderr ========
@@ -1225,6 +1230,9 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Not all builds include DMARC
     next if /^DMARC: no (dmarc_tld_file|sender_host_address)$/ ;


+    # TLS resumption is not always supported by the build
+    next if /in tls_resumption_hosts\?/;
+
     # When Exim is checking the size of directories for maildir, it uses
     # the check_dir_size() function to scan directories. Of course, the order
     # of the files that are obtained using readdir() varies from system to
diff --git a/test/scripts/5890-Resume-GnuTLS/5890 b/test/scripts/5890-Resume-GnuTLS/5890
new file mode 100644
index 0000000..9db5403
--- /dev/null
+++ b/test/scripts/5890-Resume-GnuTLS/5890
@@ -0,0 +1,62 @@
+# TLS session resumption
+gnutls
+#
+# For keying info:
+# (requires SSLKEYLOGFILE added to /etc/sudoers)
+# SSLKEYLOGFILE=/home/jgh/git/exim/test/foo sudo exim -DSERVER=server -bd -oX PORT_D
+#
+### TLS1.2
+exim -DSERVER=server -DOPTION=NORMAL:!VERS-TLS1.3 -bd -oX PORT_D
+****
+exim -DVALUE=resume -odf getticket@???
+Test message. Contains FF: ?
+****
+exim -DVALUE=resume -odf resume@??? abcd@??? xyz@???
+Test message to two different hosts, one does not support resume
+****
+# allow time for ticket to hit renewal time
+sleep 3
+exim -DVALUE=resume -odf renewal@???
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@???
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@???
+Test message.
+****
+exim -odf notreq@???
+Test message, not requesting resumption.
+****
+killdaemon
+sleep 1
+sudo rm DIR/spool/db/tls
+#
+#
+### TLS1.3
+exim -DSERVER=server -DOPTION=NORMAL -bd -oX PORT_D
+****
+exim -DVALUE=resume -odf getticket@???
+Test message. Contains FF: ?
+****
+exim -DVALUE=resume -odf resume@??? abcd@??? xyz@???
+Test message to two different hosts, one does not support resume
+****
+# allow time for ticket to hit renewal time
+sleep 3
+exim -DVALUE=resume -odf renewal@???
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@???
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@???
+Test message.
+****
+exim -odf notreq@???
+Test message, not requesting resumption.
+****
+killdaemon
+no_msglog_check
diff --git a/test/scripts/5890-Resume-GnuTLS/REQUIRES b/test/scripts/5890-Resume-GnuTLS/REQUIRES
new file mode 100644
index 0000000..2f12f27
--- /dev/null
+++ b/test/scripts/5890-Resume-GnuTLS/REQUIRES
@@ -0,0 +1,3 @@
+support GnuTLS
+running IPv4
+support Experimental_TLS_resume
diff --git a/test/scripts/5891-Resume-OpenSSL/5891 b/test/scripts/5891-Resume-OpenSSL/5891
new file mode 100644
index 0000000..116f5cf
--- /dev/null
+++ b/test/scripts/5891-Resume-OpenSSL/5891
@@ -0,0 +1,57 @@
+# TLS session resumption
+#
+### TLS1.2
+exim -DSERVER=server -DOPTION=+no_tlsv1_3 -bd -oX PORT_D
+****
+exim -DVALUE=resume -odf getticket@???
+Test message. Contains FF: ?
+****
+exim -DVALUE=resume -odf resume@??? abcd@??? xyz@???
+Test message to two different hosts, one does not support resume
+****
+# allow time for ticket to hit renewal time
+sleep 3
+exim -DVALUE=resume -odf renewal@???
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@???
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@???
+Test message.
+****
+exim -odf notreq@???
+Test message, not requesting resumption.
+****
+killdaemon
+sleep 1
+sudo rm DIR/spool/db/tls
+#
+#
+### TLS1.3
+exim -DSERVER=server -bd -oX PORT_D
+****
+exim -DVALUE=resume -odf getticket@???
+Test message. Contains FF: ?
+****
+exim -DVALUE=resume -odf resume@??? abcd@??? xyz@???
+Test message to two different hosts, one does not support resume
+****
+# allow time for ticket to hit renewal time
+sleep 3
+exim -DVALUE=resume -odf renewal@???
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@???
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@???
+Test message.
+****
+exim -odf notreq@???
+Test message, not requesting resumption.
+****
+killdaemon
+no_msglog_check
diff --git a/test/scripts/5891-Resume-OpenSSL/REQUIRES b/test/scripts/5891-Resume-OpenSSL/REQUIRES
new file mode 100644
index 0000000..027b4dc
--- /dev/null
+++ b/test/scripts/5891-Resume-OpenSSL/REQUIRES
@@ -0,0 +1,3 @@
+support OpenSSL
+running IPv4
+support Experimental_TLS_resume
diff --git a/test/stderr/5890 b/test/stderr/5890
new file mode 100644
index 0000000..6b5c434
--- /dev/null
+++ b/test/stderr/5890
@@ -0,0 +1,6 @@
+### TLS1.2
+### TLS1.3
+
+******** SERVER ********
+### TLS1.2
+### TLS1.3
diff --git a/test/stderr/5891 b/test/stderr/5891
new file mode 100644
index 0000000..6b5c434
--- /dev/null
+++ b/test/stderr/5891
@@ -0,0 +1,6 @@
+### TLS1.2
+### TLS1.3
+
+******** SERVER ********
+### TLS1.2
+### TLS1.3
diff --git a/test/stdout/0572 b/test/stdout/0572
index 272aa06..0ea3811 100644
--- a/test/stdout/0572
+++ b/test/stdout/0572
@@ -78,7 +78,7 @@ OPT =
 # 1 "TESTSUITE/aux-var/std_conf_prefix"
 # 1 "TESTSUITE/aux-var/std_conf_prefix"
 # 1 "TESTSUITE/aux-var/tls_conf_prefix"
-keep_environment = PATH
+keep_environment = PATH:SSLKEYLOGFILE
 exim_path = TESTSUITE/eximdir/exim
 host_lookup_order = bydns
 spool_directory = TESTSUITE/spool
@@ -118,7 +118,7 @@ OPT =
 # 1 "TESTSUITE/aux-var/std_conf_prefix"
 # 1 "TESTSUITE/aux-var/std_conf_prefix"
 # 1 "TESTSUITE/aux-var/tls_conf_prefix"
-keep_environment = PATH
+keep_environment = PATH:SSLKEYLOGFILE
 exim_path = TESTSUITE/eximdir/exim
 host_lookup_order = bydns
 spool_directory = TESTSUITE/spool
diff --git a/test/stdout/0577 b/test/stdout/0577
index ef918a8..f08550f 100644
--- a/test/stdout/0577
+++ b/test/stdout/0577
@@ -4,7 +4,7 @@
 # 1 "TESTSUITE/aux-var/std_conf_prefix"
 # 1 "TESTSUITE/aux-var/std_conf_prefix"
 # 1 "TESTSUITE/aux-var/tls_conf_prefix"
-keep_environment = PATH
+keep_environment = PATH:SSLKEYLOGFILE
 exim_path = TESTSUITE/eximdir/exim
 host_lookup_order = bydns
 spool_directory = TESTSUITE/spool
diff --git a/test/stdout/5890 b/test/stdout/5890
new file mode 100644
index 0000000..6b5c434
--- /dev/null
+++ b/test/stdout/5890
@@ -0,0 +1,6 @@
+### TLS1.2
+### TLS1.3
+
+******** SERVER ********
+### TLS1.2
+### TLS1.3
diff --git a/test/stdout/5891 b/test/stdout/5891
new file mode 100644
index 0000000..6b5c434
--- /dev/null
+++ b/test/stdout/5891
@@ -0,0 +1,6 @@
+### TLS1.2
+### TLS1.3
+
+******** SERVER ********
+### TLS1.2
+### TLS1.3