passed on the wire for the restarted session. Fix by using the recorded
ocsp status of the stored session for the new connection.
+JH/29 TLS resumption: the key for session lookup in the client now includes
+ more info that a server could potentially use in configuring a TLS
+ session, avoiding oferring mismatching sessions to such a server.
+ Previously only the server IP was used.
+
Exim version 4.95
-----------------
#ifndef NS_MAXMSG
# define NS_MAXMSG 65535
#endif
-typedef void hctx;
+typedef void * hctx;
#include "local_scan.h"
#include "macros.h"
# ifndef DISABLE_TLS
if (ob->server_tls)
{
- uschar * s;
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
smtp_connect_args conn_args = { .host = &host };
- tls_support tls_dummy = {.sni=NULL};
+ tls_support tls_dummy = { .sni = NULL };
uschar * errstr;
+ if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0)
+ conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL);
+ else
+ {
+ *errmsg = string_sprintf("getsockname failed: %s", strerror(errno));
+ goto bad;
+ }
+
if (!tls_client_start(&cctx, &conn_args, NULL, &tls_dummy, &errstr))
{
auth_defer_msg = string_sprintf("TLS connect failed: %s", errstr);
#include "hintsdb_structs.h"
#include "structs.h"
#include "blob.h"
-#include "globals.h"
#include "hash.h"
+#include "globals.h"
#include "functions.h"
#include "dbfunctions.h"
#include "osfunctions.h"
OCSP_VFIED /* verified */
} ocsp; /* Stapled OCSP status */
#ifndef DISABLE_TLS_RESUME
+ hctx resume_hctx; /* session lookup key accumulation */
+ const uschar * resume_index; /* session lookup key */
+
unsigned resumption; /* Session resumption */
BOOL host_resumable:1;
BOOL ticket_received:1;
struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
sizeof(addr_buffer));
- if (portptr != NULL) *portptr = ntohs(sk->sin6_port);
+ if (portptr) *portptr = ntohs(sk->sin6_port);
}
else
{
struct sockaddr_in *sk = (struct sockaddr_in *)arg;
yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
sizeof(addr_buffer));
- if (portptr != NULL) *portptr = ntohs(sk->sin_port);
+ if (portptr) *portptr = ntohs(sk->sin_port);
}
}
else
if (type < 0)
{
yield = US inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
- if (portptr != NULL) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port);
+ if (portptr) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port);
}
else
yield = US inet_ntoa(*((struct in_addr *)arg));
#ifndef DISABLE_TLS
if (do_tls)
{
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
smtp_connect_args conn_args = {.host = &host };
- tls_support tls_dummy = {.sni=NULL};
+ tls_support tls_dummy = { .sni = NULL };
uschar * errstr;
+ if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0)
+ conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL);
+ else
+ {
+ *errmsg = string_sprintf("getsockname failed: %s", strerror(errno));
+ goto bad;
+ }
+
if (!tls_client_start(cctx, &conn_args, NULL, &tls_dummy, &errstr))
{
*errmsg = string_sprintf("TLS connect failed: %s", errstr);
host_item * host;
int host_af;
uschar * interface;
+ uschar * sending_ip_address; /* used for TLS resumption */
int sock; /* used for a bound but not connected socket */
#ifdef SUPPORT_DANE
*/
static BOOL
-tls_alpn_plist(const uschar * tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
+tls_alpn_plist(uschar ** tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
uschar ** errstr)
{
uschar * exp_alpn;
-if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
return FALSE;
if (!exp_alpn)
static void
tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
{
+uschar * local_alpn = string_copy(tls_alpn);
int rc;
const gnutls_datum_t * plist;
unsigned plen;
-if (tls_alpn_plist(tls_alpn, &plist, &plen, errstr) && plist)
+if (tls_alpn_plist(&local_alpn, &plist, &plen, errstr) && plist)
{
/* This seems to be only mandatory if the client sends an ALPN extension;
not trying ALPN is ok. Need to decide how to support server-side must-alpn. */
static void
tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
- host_item * host, smtp_transport_options_block * ob)
+ smtp_connect_args * conn_args, smtp_transport_options_block * ob)
{
tlsp->resumption = RESUME_SUPPORTED;
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->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;
+ tls_client_resmption_key(tlsp, conn_args, ob);
+
tlsp->resumption |= RESUME_CLIENT_REQUESTED;
if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
{
- /* Key for the db is the IP. We'd like to filter the retrieved session
- for ticket advisory expiry, but 3.6.1 seems to give no access to that */
+ /* We'd like to filter the retrieved session for ticket advisory expiry,
+ but 3.6.1 seems to give no access to that */
- if ((dt = dbfn_read_with_length(dbm_file, host->address, &len)))
+ if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len)))
if (!(rc = gnutls_session_set_data(session,
CUS dt->session, (size_t)len - sizeof(dbdata_tls_session))))
{
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_write(dbm_file, tlsp->resume_index, dt, dlen);
dbfn_close(dbm_file);
DEBUG(D_tls)
static void
tls_client_resume_prehandshake(exim_gnutls_state_st * state,
- tls_support * tlsp, host_item * host,
+ tls_support * tlsp, smtp_connect_args * conn_args,
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);
+tls_retrieve_session(tlsp, state->session, conn_args, ob);
}
static void
const gnutls_datum_t * plist;
unsigned plen;
- if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+ if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr))
return FALSE;
if (plist)
if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0)
#endif
#ifdef EXIM_HAVE_TLS_RESUME
-tls_client_resume_prehandshake(state, tlsp, host, ob);
+tls_client_resume_prehandshake(state, tlsp, conn_args, ob);
#endif
#ifndef DISABLE_EVENT
and apply it to the ssl-connection for attempted resumption. */
static void
-tls_retrieve_session(tls_support * tlsp, SSL * ssl, const uschar * key)
+tls_retrieve_session(tls_support * tlsp, SSL * ssl)
{
-tlsp->resumption |= RESUME_SUPPORTED;
if (tlsp->host_resumable)
{
+ const uschar * key = tlsp->resume_index;
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);
+ DEBUG(D_tls)
+ debug_printf("checking for resumable session for %s\n", tlsp->resume_index);
if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
{
- /* key for the db is the IP */
- if ((dt = dbfn_read_with_length(dbm_file, key, &len)))
+ if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len)))
{
SSL_SESSION * ss = NULL;
const uschar * sess_asn1 = dt->session;
if (lifetime + dt->time_stamp < time(NULL))
{
DEBUG(D_tls) debug_printf("session expired\n");
- dbfn_delete(dbm_file, key);
+ dbfn_delete(dbm_file, tlsp->resume_index);
}
else if (SSL_set_session(ssl, ss))
{
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_write(dbm_file, tlsp->resume_index, dt, dlen);
dbfn_close(dbm_file);
DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n",
(unsigned)dlen);
}
+/* Construct a key for session DB lookup, and setup the SSL_CTX for resumption */
+
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)
+ exim_openssl_client_tls_ctx * exim_client_ctx, smtp_connect_args * conn_args,
+ tls_support * tlsp, smtp_transport_options_block * ob)
{
-/* Should the client request a session resumption ticket? */
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
- {
- tlsp->host_resumable = TRUE;
+tlsp->host_resumable = TRUE;
+tls_client_resmption_key(tlsp, conn_args, ob);
- 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);
- }
+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
tlsp->resumption = RESUME_SUPPORTED;
/* Pick up a previous session, saved on an old ticket */
-tls_retrieve_session(tlsp, ssl, host->address);
+tls_retrieve_session(tlsp, ssl);
return TRUE;
}
#ifdef EXIM_HAVE_ALPN
/* Expand and convert an Exim list to an ALPN list. False return for fail.
NULL plist return for silent no-ALPN.
+
+Overwite the passed-in list with the expanded version.
*/
static BOOL
-tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen,
+tls_alpn_plist(uschar ** tls_alpn, const uschar ** plist, unsigned * plen,
uschar ** errstr)
{
uschar * exp_alpn;
-if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
return FALSE;
+*tls_alpn = exp_alpn;
if (!exp_alpn)
{
client_static_state, errstr) != OK)
return FALSE;
-#ifndef DISABLE_TLS_RESUME
-tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host);
-#endif
-
-
-if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
- {
- tls_error(US"SSL_new", host, NULL, errstr);
- return FALSE;
- }
-SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
-
-SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
-SSL_set_connect_state(exim_client_ctx->ssl);
-
if (ob->tls_sni)
{
if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
return FALSE;
if (!tlsp->sni)
- {
- DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
- }
+ { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); }
else if (!Ustrlen(tlsp->sni))
tlsp->sni = NULL;
else
{
-#ifdef EXIM_HAVE_OPENSSL_TLSEXT
- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
- SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
-#else
+#ifndef EXIM_HAVE_OPENSSL_TLSEXT
log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
tlsp->sni);
+ tlsp->sni = NULL;
#endif
}
}
const uschar * plist;
unsigned plen;
- if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+ if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr))
return FALSE;
if (plist)
- if (SSL_set_alpn_protos(exim_client_ctx->ssl, plist, plen) != 0)
+ if (SSL_CTX_set_alpn_protos(exim_client_ctx->ctx, plist, plen) != 0)
{
tls_error(US"alpn init", host, NULL, errstr);
return FALSE;
ob->tls_alpn);
#endif
+#ifndef DISABLE_TLS_RESUME
+if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+ tls_client_ctx_resume_prehandshake(exim_client_ctx, conn_args, tlsp, ob);
+#endif
+
+
+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
+ {
+ tls_error(US"SSL_new", host, NULL, errstr);
+ return FALSE;
+ }
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
+SSL_set_connect_state(exim_client_ctx->ssl);
+
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+if (tlsp->sni)
+ {
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+ SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
+ }
+#endif
+
#ifdef SUPPORT_DANE
if (conn_args->dane)
if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
#endif
+/* Forward decl. */
+static void tls_client_resmption_key(tls_support *, smtp_connect_args *,
+ smtp_transport_options_block *);
+
+
#if defined(MACRO_PREDEF) && !defined(DISABLE_TLS)
# include "macro_predef.h"
# ifdef USE_GNUTLS
+static void
+tls_client_resmption_key(tls_support * tlsp, smtp_connect_args * conn_args,
+ smtp_transport_options_block * ob)
+{
+hctx * h = &tlsp->resume_hctx;
+blob b;
+gstring * g;
+
+#ifdef EXIM_HAVE_SHA2
+exim_sha_init(h, HASH_SHA2_256);
+#else
+exim_sha_init(h, HASH_SHA1);
+#endif
+
+// TODO: word from server EHLO resp /* how, fer gossakes? Add item to conn_args or tls_support? */
+
+if (conn_args->dane)
+ exim_sha_update(h, CUS &conn_args->tlsa_dnsa, sizeof(dns_answer));
+exim_sha_update(h, conn_args->host->address, Ustrlen(conn_args->host->address));
+exim_sha_update(h, CUS &conn_args->host->port, sizeof(conn_args->host->port));
+exim_sha_update(h, conn_args->sending_ip_address, Ustrlen(conn_args->sending_ip_address));
+if (openssl_options)
+ exim_sha_update(h, openssl_options, Ustrlen(openssl_options));
+if (ob->tls_require_ciphers)
+ exim_sha_update(h, ob->tls_require_ciphers, Ustrlen(ob->tls_require_ciphers));
+if (tlsp->sni)
+ exim_sha_update(h, tlsp->sni, Ustrlen(tlsp->sni));
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn)
+ exim_sha_update(h, ob->tls_alpn, Ustrlen(ob->tls_alpn));
+#endif
+exim_sha_finish(h, &b);
+for (g = string_get(b.len*2+1); b.len-- > 0; )
+ g = string_fmt_append(g, "%02x", *b.data++);
+tlsp->resume_index = string_from_gstring(g);
+DEBUG(D_tls) debug_printf("TLS: resume session index %s\n", tlsp->resume_index);
+}
+
#endif /*!DISABLE_TLS*/
#endif /*!MACRO_PREDEF*/
else
TLS_NEGOTIATE:
{
+ sx->conn_args.sending_ip_address = sending_ip_address;
if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr))
{
/* TLS negotiation failed; give an error. From outside, this function may