summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt4
-rw-r--r--doc/doc-txt/ChangeLog2
-rw-r--r--doc/doc-txt/NewStuff3
-rw-r--r--doc/doc-txt/experimental-spec.txt44
-rw-r--r--src/OS/Makefile-Base4
-rw-r--r--src/src/acl.c2
-rw-r--r--src/src/config.h.defaults1
-rw-r--r--src/src/daemon.c5
-rw-r--r--src/src/dbfn.c7
-rw-r--r--src/src/dbfunctions.h2
-rw-r--r--src/src/dbstuff.h6
-rw-r--r--src/src/deliver.c23
-rw-r--r--src/src/enq.c4
-rw-r--r--src/src/exim.c3
-rw-r--r--src/src/exim_dbutil.c33
-rw-r--r--src/src/expand.c8
-rw-r--r--src/src/functions.h1
-rw-r--r--src/src/globals.c39
-rw-r--r--src/src/globals.h8
-rw-r--r--src/src/macro_predef.c5
-rw-r--r--src/src/macro_predef.h2
-rw-r--r--src/src/macros.h22
-rw-r--r--src/src/readconf.c3
-rw-r--r--src/src/receive.c6
-rw-r--r--src/src/retry.c4
-rw-r--r--src/src/smtp_in.c6
-rw-r--r--src/src/spool_in.c5
-rw-r--r--src/src/spool_out.c3
-rw-r--r--src/src/structs.h3
-rw-r--r--src/src/tls-gnu.c339
-rw-r--r--src/src/tls-openssl.c399
-rw-r--r--src/src/tls.c6
-rw-r--r--src/src/transport.c4
-rw-r--r--src/src/transports/smtp.c18
-rw-r--r--src/src/transports/smtp.h3
-rw-r--r--src/src/verify.c8
-rw-r--r--test/aux-var-src/tls_conf_prefix2
-rw-r--r--test/confs/589094
-rw-r--r--test/confs/589194
-rw-r--r--test/log/21022
-rw-r--r--test/log/5890116
-rw-r--r--test/log/5891130
-rwxr-xr-xtest/runtest14
-rw-r--r--test/scripts/5890-Resume-GnuTLS/589062
-rw-r--r--test/scripts/5890-Resume-GnuTLS/REQUIRES3
-rw-r--r--test/scripts/5891-Resume-OpenSSL/589157
-rw-r--r--test/scripts/5891-Resume-OpenSSL/REQUIRES3
-rw-r--r--test/stderr/58906
-rw-r--r--test/stderr/58916
-rw-r--r--test/stdout/05724
-rw-r--r--test/stdout/05772
-rw-r--r--test/stdout/58906
-rw-r--r--test/stdout/58916
53 files changed, 1470 insertions, 172 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 7a7608bd6..783aeb429 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 a85841af6..59a025b2a 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 e776a4f95..352833c4b 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 2f1e5c591..a2861c4a9 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 0fbee9d03..8ca9a485c 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 fdd32b8e7..19938affa 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 a27ad3fd2..c5d5389ba 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 4addf0ac7..cf5e09201 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 5555c710b..a60775681 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 93d12efc3..2e18e0eff 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 02cfa1446..6b1ae0ebb 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 696effdee..f79522d8e 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 573fc00ca..7feba5531 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 df76de15e..1952d91a4 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 a0514f009..2c7aad63b 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 ff1b72615..d8ea87dee 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 2eb74579f..87953c413 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 acabeb8c3..a2fa032bc 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 a0c1977a2..1aacaf7e6 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 86be52f54..33249133a 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 f265750ca..79a8d6f15 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 f98d5e6bd..e3f1f4c28 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 150d7973d..cac8fe5c2 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 701d540b0..fbd32c8fa 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 dc39813fb..509de123c 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 b46f3e876..40fd3083b 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 786eb514e..95004c103 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 46a490a93..d14914f94 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 7fb32777c..349aa38e8 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 91ddc579b..32625d6d8 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 c7b22a0db..06008decc 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 23e9d4111..7b8d7a2a3 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 f34db0914..fb74dfdcd 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 806c41fed..f76b4f730 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 056efbcf7..ab0e93ff8 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 528a668b8..720f85d8c 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 965bc8b24..1c464f63e 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 000000000..6daf59667
--- /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 000000000..78d22f7ff
--- /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 5ec2c1cb9..215bbe243 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@test.ex 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@test.ex 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 000000000..9d098e526
--- /dev/null
+++ b/test/log/5890
@@ -0,0 +1,116 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for getticket@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for resume@test.ex abcd@test.ex xyz@test.ex
+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@test.ex 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@test.ex 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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for renewal@test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbC-0005vi-00 => renewal@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbE-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbE-0005vi-00 => postrenewal@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for timeout@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for notreq@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for getticket@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for resume@test.ex abcd@test.ex xyz@test.ex
+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@test.ex 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@test.ex 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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for renewal@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for postrenewal@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for timeout@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for notreq@test.ex
+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@test.ex 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@myhost.test.ex 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@myhost.test.ex for getticket@test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <getticket@test.ex> 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@myhost.test.ex 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@myhost.test.ex for resume@test.ex xyz@test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <xyz@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <resume@test.ex> 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@myhost.test.ex 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@myhost.test.ex for abcd@test.ex
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <abcd@test.ex> 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@myhost.test.ex 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@myhost.test.ex for renewal@test.ex
+1999-03-02 09:44:33 10HmbD-0005vi-00 => :blackhole: <renewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbF-0005vi-00 => :blackhole: <postrenewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for timeout@test.ex
+1999-03-02 09:44:33 10HmbH-0005vi-00 => :blackhole: <timeout@test.ex> 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@myhost.test.ex 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@myhost.test.ex for notreq@test.ex
+1999-03-02 09:44:33 10HmbJ-0005vi-00 => :blackhole: <notreq@test.ex> 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@myhost.test.ex 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@myhost.test.ex for getticket@test.ex
+1999-03-02 09:44:33 10HmbL-0005vi-00 => :blackhole: <getticket@test.ex> 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@myhost.test.ex 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@myhost.test.ex for resume@test.ex xyz@test.ex
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <xyz@test.ex> R=server
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <resume@test.ex> 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@myhost.test.ex 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@myhost.test.ex for abcd@test.ex
+1999-03-02 09:44:33 10HmbO-0005vi-00 => :blackhole: <abcd@test.ex> 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@myhost.test.ex 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@myhost.test.ex for renewal@test.ex
+1999-03-02 09:44:33 10HmbQ-0005vi-00 => :blackhole: <renewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbS-0005vi-00 => :blackhole: <postrenewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for timeout@test.ex
+1999-03-02 09:44:33 10HmbU-0005vi-00 => :blackhole: <timeout@test.ex> 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@myhost.test.ex 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@myhost.test.ex for notreq@test.ex
+1999-03-02 09:44:33 10HmbW-0005vi-00 => :blackhole: <notreq@test.ex> 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 000000000..8131404e5
--- /dev/null
+++ b/test/log/5891
@@ -0,0 +1,130 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for getticket@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for resume@test.ex abcd@test.ex xyz@test.ex
+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@test.ex 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@test.ex 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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for renewal@test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbC-0005vi-00 => renewal@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbE-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbE-0005vi-00 => postrenewal@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for timeout@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for notreq@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for getticket@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for resume@test.ex abcd@test.ex xyz@test.ex
+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@test.ex 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@test.ex 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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for renewal@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbR-0005vi-00 tls_out_resumption session resumed
+1999-03-02 09:44:33 10HmbR-0005vi-00 => postrenewal@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for timeout@test.ex
+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@test.ex 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@myhost.test.ex U=CALLER P=local S=sss for notreq@test.ex
+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@test.ex 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@myhost.test.ex 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@myhost.test.ex for getticket@test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <getticket@test.ex> 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@myhost.test.ex 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@myhost.test.ex for resume@test.ex xyz@test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <xyz@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <resume@test.ex> 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@myhost.test.ex 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@myhost.test.ex for abcd@test.ex
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <abcd@test.ex> 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@myhost.test.ex 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@myhost.test.ex for renewal@test.ex
+1999-03-02 09:44:33 10HmbD-0005vi-00 => :blackhole: <renewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbF-0005vi-00 => :blackhole: <postrenewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for timeout@test.ex
+1999-03-02 09:44:33 10HmbH-0005vi-00 => :blackhole: <timeout@test.ex> 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@myhost.test.ex 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@myhost.test.ex for notreq@test.ex
+1999-03-02 09:44:33 10HmbJ-0005vi-00 => :blackhole: <notreq@test.ex> 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@myhost.test.ex 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@myhost.test.ex for getticket@test.ex
+1999-03-02 09:44:33 10HmbL-0005vi-00 => :blackhole: <getticket@test.ex> 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@myhost.test.ex 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@myhost.test.ex for resume@test.ex xyz@test.ex
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <xyz@test.ex> R=server
+1999-03-02 09:44:33 10HmbN-0005vi-00 => :blackhole: <resume@test.ex> 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@myhost.test.ex 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@myhost.test.ex for abcd@test.ex
+1999-03-02 09:44:33 10HmbO-0005vi-00 => :blackhole: <abcd@test.ex> 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@myhost.test.ex 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@myhost.test.ex for renewal@test.ex
+1999-03-02 09:44:33 10HmbQ-0005vi-00 => :blackhole: <renewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for postrenewal@test.ex
+1999-03-02 09:44:33 10HmbS-0005vi-00 => :blackhole: <postrenewal@test.ex> 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@myhost.test.ex 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@myhost.test.ex for timeout@test.ex
+1999-03-02 09:44:33 10HmbU-0005vi-00 => :blackhole: <timeout@test.ex> 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@myhost.test.ex 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@myhost.test.ex for notreq@test.ex
+1999-03-02 09:44:33 10HmbW-0005vi-00 => :blackhole: <notreq@test.ex> R=server
+1999-03-02 09:44:33 10HmbW-0005vi-00 Completed
diff --git a/test/runtest b/test/runtest
index 6566579ef..a9927475e 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 000000000..9db5403b9
--- /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.ex
+Test message. Contains FF: ÿ
+****
+exim -DVALUE=resume -odf resume@test.ex abcd@test.ex xyz@test.ex
+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.ex
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@test.ex
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@test.ex
+Test message.
+****
+exim -odf notreq@test.ex
+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.ex
+Test message. Contains FF: ÿ
+****
+exim -DVALUE=resume -odf resume@test.ex abcd@test.ex xyz@test.ex
+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.ex
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@test.ex
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@test.ex
+Test message.
+****
+exim -odf notreq@test.ex
+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 000000000..2f12f2742
--- /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 000000000..116f5cfe9
--- /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.ex
+Test message. Contains FF: ÿ
+****
+exim -DVALUE=resume -odf resume@test.ex abcd@test.ex xyz@test.ex
+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.ex
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@test.ex
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@test.ex
+Test message.
+****
+exim -odf notreq@test.ex
+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.ex
+Test message. Contains FF: ÿ
+****
+exim -DVALUE=resume -odf resume@test.ex abcd@test.ex xyz@test.ex
+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.ex
+Test message.
+****
+exim -DVALUE=resume -odf postrenewal@test.ex
+Test message.
+****
+sleep 3
+exim -DVALUE=resume -odf timeout@test.ex
+Test message.
+****
+exim -odf notreq@test.ex
+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 000000000..027b4dcab
--- /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 000000000..6b5c4341e
--- /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 000000000..6b5c4341e
--- /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 272aa06a1..0ea381190 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 ef918a86b..f08550f6f 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 000000000..6b5c4341e
--- /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 000000000..6b5c4341e
--- /dev/null
+++ b/test/stdout/5891
@@ -0,0 +1,6 @@
+### TLS1.2
+### TLS1.3
+
+******** SERVER ********
+### TLS1.2
+### TLS1.3