summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2021-07-18 00:15:01 +0100
committerJeremy Harris <jgh146exb@wizmail.org>2021-07-18 13:23:11 +0100
commitc4b4086235b1d5e21fcf1ad72a1b05813e15dcbd (patch)
treeb2e3301128b2e35510dfc1b563d9b41581d40439
parentf7ea5ba1049ba2a53b8cb0bf98893bff6c6bc77f (diff)
downloadexim4-c4b4086235b1d5e21fcf1ad72a1b05813e15dcbd.tar.gz
TLS: ALPN options
-rw-r--r--doc/doc-docbook/spec.xfpt86
-rw-r--r--doc/doc-txt/NewStuff4
-rw-r--r--doc/doc-txt/OptionLists.txt5
-rw-r--r--src/src/globals.c2
-rw-r--r--src/src/globals.h2
-rw-r--r--src/src/readconf.c2
-rw-r--r--src/src/tls-gnu.c249
-rw-r--r--src/src/tls-openssl.c156
-rw-r--r--src/src/tls.c1
-rw-r--r--src/src/transports/smtp.c2
-rw-r--r--src/src/transports/smtp.h2
-rw-r--r--test/confs/111573
l---------test/confs/20381
l---------test/confs/21381
-rw-r--r--test/log/111545
-rw-r--r--test/log/203826
-rw-r--r--test/log/213825
-rwxr-xr-xtest/runtest2
-rw-r--r--test/scripts/1100-Basic-TLS/111550
-rw-r--r--test/scripts/2000-GnuTLS/203836
-rw-r--r--test/scripts/2100-OpenSSL/213835
-rw-r--r--test/stdout/05721
22 files changed, 718 insertions, 88 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 3384d60d9..2687f6048 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -14829,8 +14829,10 @@ listed in more than one group.
.table2
.row &%gnutls_compat_mode%& "use GnuTLS compatibility mode"
.row &%gnutls_allow_auto_pkcs11%& "allow GnuTLS to autoload PKCS11 modules"
+.row &%hosts_require_alpn%& "mandatory ALPN"
.row &%openssl_options%& "adjust OpenSSL compatibility options"
.row &%tls_advertise_hosts%& "advertise TLS to these hosts"
+.row &%tls_alpn%& "acceptable protocol names"
.row &%tls_certificate%& "location of server certificate"
.row &%tls_crl%& "certificate revocation list"
.row &%tls_dh_max_bits%& "clamp D-H bit count suggestion"
@@ -16331,6 +16333,20 @@ hosts_connection_nolog = :
If the &%smtp_connection%& log selector is not set, this option has no effect.
+.new
+.option hosts_require_alpn main "host list&!!" unset
+.cindex ALPN "require negotiation in server"
+.cindex TLS ALPN
+.cindex TLS "Application Layer Protocol Names"
+If the TLS library supports ALPN
+then a successful negotiation of ALPN will be required for any client
+matching the list, for TLS to be used.
+See also the &%tls_alpn%& option.
+
+&*Note*&: prevention of fallback to in-clear connection is not
+managed by this option, and should be done separately.
+.wen
+
.option hosts_proxy main "host list&!!" unset
.cindex proxy "proxy protocol"
@@ -18357,6 +18373,19 @@ using the &%tls_certificate%& option. If TLS support for incoming connections
is not required the &%tls_advertise_hosts%& option should be set empty.
+.new
+.option tls_alpn main "string list&!!" "smtp : esmtp"
+.cindex TLS "Application Layer Protocol Names"
+.cindex TLS ALPN
+.cindex ALPN "set acceptable names for server"
+If this option is set,
+the TLS library supports ALPN,
+and the client offers either more than
+ALPN name or a name which does not match the list,
+the TLS connection is declined.
+.wen
+
+
.option tls_certificate main "string list&!!" unset
.cindex "TLS" "server certificate; location of"
.cindex "certificate" "server, location of"
@@ -25657,6 +25686,20 @@ Exim will request a Certificate Status on a
TLS session for any host that matches this list.
&%tls_verify_certificates%& should also be set for the transport.
+.new
+.option hosts_require_alpn smtp "host list&!!" unset
+.cindex ALPN "require negotiation in client"
+.cindex TLS ALPN
+.cindex TLS "Application Layer Protocol Names"
+If the TLS library supports ALPN
+then a successful negotiation of ALPN will be required for any host
+matching the list, for TLS to be used.
+See also the &%tls_alpn%& option.
+
+&*Note*&: prevention of fallback to in-clear connection is not
+managed by this option; see &%hosts_require_tls%&.
+.wen
+
.option hosts_require_dane smtp "host list&!!" unset
.cindex DANE "transport options"
.cindex DANE "requiring for certain servers"
@@ -25940,6 +25983,21 @@ This option enables use of SOCKS proxies for connections made by the
transport. For details see section &<<SECTproxySOCKS>>&.
+.new
+.option tls_alpn smtp string&!! unset
+.cindex TLS "Application Layer Protocol Names"
+.cindex TLS ALPN
+.cindex ALPN "set name in client"
+If this option is set
+and the TLS library supports ALPN,
+the value given is used.
+
+As of writing no value has been standardised for email use.
+The authors suggest using &"smtp"&.
+.wen
+
+
+
.option tls_certificate smtp string&!! unset
.cindex "TLS" "client certificate, location of"
.cindex "certificate" "client, location of"
@@ -29870,6 +29928,34 @@ When Exim is built against GnuTLS, SNI support is available as of GnuTLS
0.5.10. (Its presence predates the current API which Exim uses, so if Exim
built, then you have SNI support).
+.new
+.cindex TLS ALPN
+.cindex ALPN "general information"
+.cindex TLS "Application Layer Protocol Names"
+There is a TLS feature related to SNI
+called Application Layer Protocol Name (ALPN).
+This is intended to declare, or select, what protocol layer will be using a TLS
+connection.
+The client for the connection proposes a set of protocol names, and
+the server responds with a selected one.
+It is not, as of 2021, commonly used for SMTP connections.
+However, to guard against misirected or malicious use of web clients
+(which often do use ALPN) against MTA ports, Exim by default check that
+there is no incompatible ALPN specified by a client for a TLS connection.
+If there is, the connection is rejected.
+
+As a client Exim does not supply ALPN by default.
+The behaviour of both client and server can be configured using the options
+&%tls_alpn%& and &%hosts_require_alpn%&.
+There are no variables providing observability.
+Some feature-specific logging may appear on denied connections, but this
+depends on the behavious of the peer
+(not all peers can send a feature-specific TLS Alert).
+
+This feature is available when Exim is built with
+OpenSSL 1.1.0 or later or GnuTLS 3.2.0 or later.
+.wen
+
.section "Multiple messages on the same encrypted TCP/IP connection" &&&
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 60bb3762e..6f3d4b31c 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -57,7 +57,9 @@ Version 4.95
16. Main option "hosts_require_helo", requiring HELO or EHLO before MAIL.
17. A main config option "allow_insecure_tainted_data" allows to turn
- taint errors into warnings.
+
+18. TLS ALPN handling. By default, refuse TLS connections that try to specify
+ a non-smtp (eg. http) use. Options for customising.
Version 4.94
diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt
index 366a74b18..4314c534b 100644
--- a/doc/doc-txt/OptionLists.txt
+++ b/doc/doc-txt/OptionLists.txt
@@ -314,6 +314,8 @@ hosts_pipe_connect host_list unset smtp 4.93 if experimental
hosts_randomize boolean false manualroute 4.00
false smtp 3.14
hosts_require_auth host list unset smtp 4.00
+hosts_require_alpn host list unset main 4.95
+ smtp 4.95
hosts_require_dane host list unset smtp 4.91 (4.85 experimental)
hosts_require_helo host list "*" main 4.95
hosts_require_ocsp host list unset smtp 4.82 if experimental_ocsp
@@ -598,7 +600,8 @@ timeout_defer boolean false pipe
timeout_frozen_after time 0s main 3.20
timezone string + main 3.15
tls_advertise_hosts host list * main 3.20
-tls_advertise_requiretls host list * main 4.92 if experimental_requiretls
+tls_alpn string* unset main 4.95
+ smtp 4.95
tls_certificate string* unset main 3.20
unset smtp 3.20
tls_dh_max_bits integer 2236 main 4.80
diff --git a/src/src/globals.c b/src/src/globals.c
index 9e68aaca8..1e12bcb92 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -124,9 +124,11 @@ uschar *dsn_advertise_hosts = NULL;
#ifndef DISABLE_TLS
BOOL gnutls_compat_mode = FALSE;
BOOL gnutls_allow_auto_pkcs11 = FALSE;
+uschar *hosts_require_alpn = NULL;
uschar *openssl_options = NULL;
const pcre *regex_STARTTLS = NULL;
uschar *tls_advertise_hosts = US"*";
+uschar *tls_alpn = US"smtp:esmtp";
uschar *tls_certificate = NULL;
uschar *tls_crl = NULL;
/* This default matches NSS DH_MAX_P_BITS value at current time (2012), because
diff --git a/src/src/globals.h b/src/src/globals.h
index 657e6c706..d5d93148f 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -125,8 +125,10 @@ extern tls_support tls_out;
#ifndef DISABLE_TLS
extern BOOL gnutls_compat_mode; /* Less security, more compatibility */
extern BOOL gnutls_allow_auto_pkcs11; /* Let GnuTLS autoload PKCS11 modules */
+extern uschar *hosts_require_alpn; /* Mandatory ALPN successful nogitiation */
extern uschar *openssl_options; /* OpenSSL compatibility options */
extern const pcre *regex_STARTTLS; /* For recognizing STARTTLS settings */
+extern uschar *tls_alpn; /* ALPN names acceptable */
extern uschar *tls_certificate; /* Certificate file */
extern uschar *tls_crl; /* CRL File */
extern int tls_dh_max_bits; /* don't accept higher lib suggestions */
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 01e85a329..a1eafb2ec 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -184,6 +184,7 @@ static optionlist optionlist_config[] = {
#ifdef SUPPORT_PROXY
{ "hosts_proxy", opt_stringptr, {&hosts_proxy} },
#endif
+ { "hosts_require_alpn", opt_stringptr, {&hosts_require_alpn} },
{ "hosts_require_helo", opt_stringptr, {&hosts_require_helo} },
{ "hosts_treat_as_local", opt_stringptr, {&hosts_treat_as_local} },
#ifdef LOOKUP_IBASE
@@ -378,6 +379,7 @@ static optionlist optionlist_config[] = {
{ "timezone", opt_stringptr, {&timezone_string} },
{ "tls_advertise_hosts", opt_stringptr, {&tls_advertise_hosts} },
#ifndef DISABLE_TLS
+ { "tls_alpn", opt_stringptr, {&tls_alpn} },
{ "tls_certificate", opt_stringptr, {&tls_certificate} },
{ "tls_crl", opt_stringptr, {&tls_crl} },
{ "tls_dh_max_bits", opt_int, {&tls_dh_max_bits} },
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 2d7041f3e..bc16ec03e 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -285,7 +285,7 @@ static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
#endif
#ifdef EXIM_HAVE_ALPN
-static BOOL server_seen_alpn = FALSE;
+static int server_seen_alpn = -1; /* count of names */
#endif
#ifdef EXIM_HAVE_TLS_RESUME
static gnutls_datum_t server_sessticket_key;
@@ -388,10 +388,15 @@ return host ? FAIL : DEFER;
static int
-tls_error_gnu(const uschar *prefix, int err, const host_item *host,
+tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
uschar ** errstr)
{
-return tls_error(prefix, US gnutls_strerror(err), host, errstr);
+return tls_error(prefix,
+ state && err == GNUTLS_E_FATAL_ALERT_RECEIVED
+ ? US gnutls_alert_get_name(gnutls_alert_get(state->session))
+ : US gnutls_strerror(err),
+ state ? state->host : NULL,
+ errstr);
}
static int
@@ -451,12 +456,12 @@ To prevent this, we init PKCS11 first, which is the documented approach. */
if (!gnutls_allow_auto_pkcs11)
if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL)))
- return tls_error_gnu(US"gnutls_pkcs11_init", rc, NULL, errstr);
+ return tls_error_gnu(NULL, US"gnutls_pkcs11_init", rc, errstr);
#endif
#ifndef GNUTLS_AUTO_GLOBAL_INIT
if ((rc = gnutls_global_init()))
- return tls_error_gnu(US"gnutls_global_init", rc, NULL, errstr);
+ return tls_error_gnu(UNULL, S"gnutls_global_init", rc, errstr);
#endif
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
@@ -715,7 +720,7 @@ host_item *host = NULL; /* dummy for macros */
DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n");
if ((rc = gnutls_dh_params_init(&dh_server_params)))
- return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr);
if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
return DEFER;
@@ -745,7 +750,7 @@ else
if (m.data)
{
if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM)))
- return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
return OK;
}
@@ -829,7 +834,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
store_free(m.data);
if (rc)
- return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
}
@@ -884,7 +889,7 @@ if (rc < 0)
debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
dh_bits_gen);
if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen)))
- return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_generate2", rc, errstr);
/* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
and I confirmed that a NULL call to get the size first is how the GnuTLS
@@ -895,8 +900,8 @@ if (rc < 0)
if ( (rc = gnutls_dh_params_export_pkcs3(dh_server_params,
GNUTLS_X509_FMT_PEM, m.data, &sz))
&& rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
- return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing",
- rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3(NULL) sizing",
+ rc, errstr);
m.size = sz;
if (!(m.data = store_malloc(m.size)))
return tls_error_sys(US"memory allocation failed", errno, NULL, errstr);
@@ -906,7 +911,7 @@ if (rc < 0)
m.data, &sz)))
{
store_free(m.data);
- return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3() real", rc, errstr);
}
m.size = sz; /* shrink by 1, probably */
@@ -1011,7 +1016,7 @@ out:
return rc;
err:
- rc = tls_error_gnu(where, rc, NULL, errstr);
+ rc = tls_error_gnu(state, where, rc, errstr);
goto out;
}
@@ -1032,9 +1037,9 @@ tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred,
CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM);
if (rc < 0)
- return tls_error_gnu(
+ return tls_error_gnu(state,
string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
- rc, host, errstr);
+ rc, errstr);
return -rc;
}
@@ -1069,19 +1074,34 @@ return 0;
/* Make a note that we saw a status-request */
static int
tls_server_clienthello_ext(void * ctx, unsigned tls_id,
- const unsigned char *data, unsigned size)
+ const uschar * data, unsigned size)
{
/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
switch (tls_id)
{
- case 5: /* status_request */
+ case 5: /* Status Request */
DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
tls_in.ocsp = OCSP_NOT_RESP;
break;
#ifdef EXIM_HAVE_ALPN
case 16: /* Application Layer Protocol Notification */
- DEBUG(D_tls) debug_printf("Seen ALPN extension from client\n");
- server_seen_alpn = TRUE;
+ /* The format of "data" here doesn't seem to be documented, but appears
+ to be a 2-byte field with a (redundant, given the "size" arg) total length
+ then a sequence of one-byte size then string (not nul-term) names. The
+ latter is as described in OpenSSL documentation. */
+
+ DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
+ for (const uschar * s = data+2; s-data < size-1; s += *s + 1)
+ {
+ server_seen_alpn++;
+ DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
+ }
+ DEBUG(D_tls) debug_printf("\n");
+ if (server_seen_alpn > 1)
+ {
+ DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
+ return GNUTLS_E_NO_APPLICATION_PROTOCOL;
+ }
break;
#endif
}
@@ -1279,9 +1299,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
if ((rc = gnutls_certificate_set_ocsp_status_request_file2(
state->lib_state.x509_cred, CCS ofile, gnutls_cert_index,
ocsp_fmt)) < 0)
- return tls_error_gnu(
+ return tls_error_gnu(state,
US"gnutls_certificate_set_ocsp_status_request_file2",
- rc, NULL, errstr);
+ rc, errstr);
DEBUG(D_tls)
debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":"");
@@ -1299,9 +1319,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
if ((rc = gnutls_certificate_set_ocsp_status_request_function2(
state->lib_state.x509_cred, gnutls_cert_index,
server_ocsp_stapling_cb, ofile)))
- return tls_error_gnu(
+ return tls_error_gnu(state,
US"gnutls_certificate_set_ocsp_status_request_function2",
- rc, NULL, errstr);
+ rc, errstr);
else
# endif
{
@@ -1403,7 +1423,7 @@ else
}
if (cert_count < 0)
- return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr);
+ return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr);
DEBUG(D_tls)
debug_printf("Added %d certificate authorities\n", cert_count);
@@ -1418,8 +1438,8 @@ int cert_count;
DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl);
if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred,
CS crl, GNUTLS_X509_FMT_PEM)) < 0)
- return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file",
- cert_count, state->host, errstr);
+ return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file",
+ cert_count, errstr);
DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count);
return OK;
@@ -1730,8 +1750,8 @@ if (!state->lib_state.x509_cred)
{
if ((rc = gnutls_certificate_allocate_credentials(
(gnutls_certificate_credentials_t *) &state->lib_state.x509_cred)))
- return tls_error_gnu(US"gnutls_certificate_allocate_credentials",
- rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_certificate_allocate_credentials",
+ rc, errstr);
creds_basic_init(state->lib_state.x509_cred, !host);
}
@@ -1935,7 +1955,7 @@ if (!state->host)
if ((rc = gnutls_credentials_set(state->session,
GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred)))
- return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_credentials_set", rc, errstr);
return OK;
}
@@ -2015,7 +2035,7 @@ else
state->tls_crl = tls_crl;
}
if (rc)
- return tls_error_gnu(US"gnutls_init", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_init", rc, errstr);
state->tls_require_ciphers = require_ciphers;
state->host = host;
@@ -2044,7 +2064,7 @@ if (host)
sz = Ustrlen(state->tlsp->sni);
if ((rc = gnutls_server_name_set(state->session,
GNUTLS_NAME_DNS, state->tlsp->sni, sz)))
- return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_server_name_set", rc, errstr);
}
}
else if (state->tls_sni)
@@ -2074,10 +2094,10 @@ if (!state->lib_state.pri_string)
}
if ((rc = creds_load_pristring(state, p, &errpos)))
- return tls_error_gnu(string_sprintf(
+ return tls_error_gnu(state, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
p, errpos - CS p, errpos),
- rc, host, errstr);
+ rc, errstr);
}
else
{
@@ -2087,7 +2107,7 @@ else
if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache)))
- return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_priority_set", rc, errstr);
/* This also sets the server ticket expiration time to the same, and
the STEK rotation time to 3x. */
@@ -2285,7 +2305,7 @@ if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
(Label), gnutls_strerror(rc)); \
if (state->verify_requirement >= VERIFY_REQUIRED) \
- return tls_error_gnu((Label), rc, state->host, errstr); \
+ return tls_error_gnu(state, (Label), rc, errstr); \
return OK; \
} \
} while (0)
@@ -2808,26 +2828,70 @@ if (gnutls_session_is_resumed(state->session))
DEBUG(D_tls) debug_printf("Session resumed\n");
}
}
-#endif
+#endif /* EXIM_HAVE_TLS_RESUME */
#ifdef EXIM_HAVE_ALPN
+/* Expand and convert an Exim list to a gnutls_datum list. False return for fail.
+NULL plist return for silent no-ALPN.
+*/
+
+static BOOL
+tls_alpn_plist(const 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))
+ return FALSE;
+
+if (!exp_alpn)
+ {
+ DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+ *plist = NULL;
+ }
+else
+ {
+ const uschar * list = exp_alpn;
+ int sep = 0;
+ unsigned cnt = 0;
+ gnutls_datum_t * p;
+ uschar * s;
+
+ while (string_nextinlist(&list, &sep, NULL, 0)) cnt++;
+
+ p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(exp_alpn));
+ list = exp_alpn;
+ for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++)
+ { p[i].data = s; p[i].size = Ustrlen(s); }
+ *plist = (*plen = cnt) ? p : NULL;
+ }
+return TRUE;
+}
+
static void
-tls_server_set_acceptable_alpns(exim_gnutls_state_st * state)
+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
{
int rc;
-gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4},
- [1] = {.data = US"esmtp", .size = 5}};
+const gnutls_datum_t * plist;
+unsigned plen;
-/* Set non-mandatory set of protocol names */
-if (!(rc = gnutls_alpn_set_protocols(state->session, protocols, 2, 0)))
- gnutls_handshake_set_hook_function(state->session,
- GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
-else
- DEBUG(D_tls)
- debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+if (tls_alpn_plist(tls_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. */
+
+ server_seen_alpn = 0;
+ if (!(rc = gnutls_alpn_set_protocols(state->session, plist, plen,
+ GNUTLS_ALPN_MANDATORY)))
+ gnutls_handshake_set_hook_function(state->session,
+ GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
+ else
+ DEBUG(D_tls)
+ debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+ }
}
-#endif
+#endif /* EXIM_HAVE_ALPN */
/* ------------------------------------------------------------------------ */
/* Exported functions */
@@ -2886,7 +2950,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
}
#ifdef EXIM_HAVE_ALPN
-tls_server_set_acceptable_alpns(state);
+tls_server_set_acceptable_alpns(state, errstr);
#endif
#ifdef EXIM_HAVE_TLS_RESUME
@@ -2977,7 +3041,7 @@ if (rc != GNUTLS_E_SUCCESS)
}
else
{
- tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
(void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->lib_state.x509_cred);
@@ -3005,29 +3069,30 @@ tls_server_resume_posthandshake(state);
DEBUG(D_tls) post_handshake_debug(state);
#ifdef EXIM_HAVE_ALPN
-if (server_seen_alpn)
+if (server_seen_alpn > 0)
{
- /* The client offered ALPN. We were set up with a nonmandatory list;
- see what was negotiated. We require a match now, given that something
- was offered. */
- gnutls_datum_t p = {.size = 0};
- int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
- if (!rc || rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
- {
- if (p.size == 0)
- {
- *errstr = US"ALPN rejected";
- return FAIL;
- }
- else
- DEBUG(D_tls)
+ DEBUG(D_tls)
+ { /* The client offered ALPN. See what was negotiated. */
+ gnutls_datum_t p = {.size = 0};
+ int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
+ if (!rc)
debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data);
- }
- else
- DEBUG(D_tls)
- debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
+ else
+ debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
+ }
}
+else if (server_seen_alpn == 0)
+ if (verify_check_host(&hosts_require_alpn) == OK)
+ {
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+ tls_error(US"handshake", US"ALPN required but not negotiated", NULL, errstr);
+ return FAIL;
+ }
+ else
+ DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n");
+else
+ DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n");
#endif
/* Verify after the fact */
@@ -3367,6 +3432,28 @@ if (!cipher_list)
#endif
}
+if (ob->tls_alpn)
+#ifdef EXIM_HAVE_ALPN
+ {
+ const gnutls_datum_t * plist;
+ unsigned plen;
+
+ 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)
+ {
+ tls_error(US"alpn init", NULL, state->host, errstr);
+ return FALSE;
+ }
+ else
+ DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+ }
+#else
+ log_write(0, LOG_MAIN, "ALPN unusable with this GnuTLS library version; ignoring \"%s\"\n",
+ ob->tls_alpn);
+#endif
+
{
int dh_min_bits = ob->tls_dh_min_bits;
if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
@@ -3435,7 +3522,7 @@ if (request_ocsp)
if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
NULL, 0, NULL)) != OK)
{
- tls_error_gnu(US"cert-status-req", rc, state->host, errstr);
+ tls_error_gnu(state, US"cert-status-req", rc, errstr);
return FALSE;
}
tlsp->ocsp = OCSP_NOT_RESP;
@@ -3477,7 +3564,7 @@ if (rc != GNUTLS_E_SUCCESS)
tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
}
else
- tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr);
+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
return FALSE;
}
@@ -3522,9 +3609,9 @@ if (request_ocsp)
gnutls_free(printed.data);
}
else
- (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+ (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
if (idx == 0 && rc)
- (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+ (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
}
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
@@ -3546,6 +3633,24 @@ if (request_ocsp)
tls_client_resume_posthandshake(state, tlsp, host);
#endif
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn) /* We requested. See what was negotiated. */
+ {
+ gnutls_datum_t p = {.size = 0};
+
+ if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0)
+ { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); }
+ else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
+ {
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+ tls_error(US"handshake", US"ALPN required but not negotiated", state->host, errstr);
+ return FALSE;
+ }
+ else
+ DEBUG(D_tls) debug_printf("No ALPN negotiated");
+ }
+#endif
+
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index d99de53d6..678288ca3 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -359,6 +359,9 @@ typedef struct {
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
static SSL_CTX *server_sni = NULL;
#endif
+#ifdef EXIM_HAVE_ALPN
+static BOOL server_seen_alpn = FALSE;
+#endif
static char ssl_errstring[256];
@@ -2140,9 +2143,9 @@ bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
* Callback to handle ALPN *
*************************************************/
-/* SSL_CTX_set_alpn_select_cb() */
-/* Called on server when client offers ALPN, after the SNI callback.
-If set and not e?smtp then we dump the connection */
+/* Called on server if tls_alpn nonblank after expansion,
+when client offers ALPN, after the SNI callback.
+If set and not matching the list then we dump the connection */
static int
tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen,
@@ -2150,6 +2153,7 @@ tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen,
{
const exim_openssl_state_st * state = arg;
+server_seen_alpn = TRUE;
DEBUG(D_tls)
{
debug_printf("Received TLS ALPN offer:");
@@ -2159,23 +2163,32 @@ DEBUG(D_tls)
if (pos + 1 + siz > inlen) siz = inlen - pos - 1;
debug_printf(" '%.*s'", siz, in + pos + 1);
}
- debug_printf("\n");
+ debug_printf(". Our list: '%s'\n", tls_alpn);
}
/* Look for an acceptable ALPN */
+
if ( inlen > 1 /* at least one name */
&& in[0]+1 == inlen /* filling the vector, so exactly one name */
- && ( Ustrncmp(in+1, "smtp", in[0]) == 0
- || Ustrncmp(in+1, "esmtp", in[0]) == 0
- ) )
+ )
{
- *out = in; /* we checked for exactly one, so can just point to it */
- *outlen = inlen;
- return SSL_TLSEXT_ERR_OK; /* use ALPN */
+ const uschar * list = tls_alpn;
+ int sep = 0;
+ for (uschar * name; name = string_nextinlist(&list, &sep, NULL, 0); )
+ if (Ustrncmp(in+1, name, in[0]) == 0)
+ {
+ *out = in; /* we checked for exactly one, so can just point to it */
+ *outlen = inlen;
+ return SSL_TLSEXT_ERR_OK; /* use ALPN */
+ }
}
-/* Reject unacceptable ALPN */
-/* This will be fatal to the TLS conn; would be nice to kill TCP also */
+/* More than one name from clilent, or name did not match our list. */
+
+/* This will be fatal to the TLS conn; would be nice to kill TCP also.
+Maybe as an option in future; for now leave control to the config (must-tls). */
+
+DEBUG(D_tls) debug_printf("TLS ALPN rejected\n");
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif /* EXIM_HAVE_ALPN */
@@ -2649,8 +2662,20 @@ if (!host) /* server */
tls_certificate */
SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
SSL_CTX_set_tlsext_servername_arg(ctx, state);
+
# ifdef EXIM_HAVE_ALPN
- SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state);
+ if (tls_alpn && *tls_alpn)
+ {
+ uschar * exp_alpn;
+ if ( expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr)
+ && *exp_alpn && !isblank(*exp_alpn))
+ {
+ tls_alpn = exp_alpn; /* subprocess so ok to overwrite */
+ SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state);
+ }
+ else
+ tls_alpn = NULL;
+ }
# endif
}
# ifndef DISABLE_OCSP
@@ -3226,6 +3251,30 @@ if (SSL_session_reused(ssl))
}
#endif
+#ifdef EXIM_HAVE_ALPN
+/* If require-alpn, check server_seen_alpn here. Else abort TLS */
+if (!tls_alpn || !*tls_alpn)
+ { DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); }
+else if (!server_seen_alpn)
+ if (verify_check_host(&hosts_require_alpn) == OK)
+ {
+ /* We'd like to send a definitive Alert but OpenSSL provides no facility */
+ SSL_shutdown(ssl);
+ tls_error(US"handshake", NULL, US"ALPN required but not negotiated", errstr);
+ return FAIL;
+ }
+ else
+ { DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); }
+else DEBUG(D_tls)
+ {
+ const uschar * name;
+ unsigned len;
+ SSL_get0_alpn_selected(ssl, &name, &len);
+ debug_printf("ALPN negotiated: '%.*s'\n", (int)*name, name+1);
+ }
+#endif
+
+
/* TLS has been set up. Record data for the connection,
adjust the input functions to read via TLS, and initialize things. */
@@ -3593,6 +3642,47 @@ if (SSL_session_reused(exim_client_ctx->ssl))
#endif /* !DISABLE_TLS_RESUME */
+#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.
+*/
+
+static BOOL
+tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen,
+ uschar ** errstr)
+{
+uschar * exp_alpn;
+
+if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+ return FALSE;
+
+if (!exp_alpn)
+ {
+ DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+ *plist = NULL;
+ }
+else
+ {
+ /* The server implementation only accepts exactly one protocol name
+ but it's little extra code complexity in the client. */
+
+ const uschar * list = exp_alpn;
+ uschar * p = store_get(Ustrlen(exp_alpn), is_tainted(exp_alpn)), * s, * t;
+ int sep = 0;
+ uschar len;
+
+ for (t = p; s = string_nextinlist(&list, &sep, NULL, 0); t += len)
+ {
+ *t++ = len = (uschar) Ustrlen(s);
+ memcpy(t, s, len);
+ }
+ *plist = (*plen = t - p) ? p : NULL;
+ }
+return TRUE;
+}
+#endif /* EXIM_HAVE_ALPN */
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
@@ -3778,6 +3868,28 @@ if (ob->tls_sni)
}
}
+if (ob->tls_alpn)
+#ifdef EXIM_HAVE_ALPN
+ {
+ const uschar * plist;
+ unsigned plen;
+
+ 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)
+ {
+ tls_error(US"alpn init", host, NULL, errstr);
+ return FALSE;
+ }
+ else
+ DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+ }
+#else
+ log_write(0, LOG_MAIN, "ALPN unusable with this OpenSSL library version; ignoring \"%s\"\n",
+ ob->alpn);
+#endif
+
#ifdef SUPPORT_DANE
if (conn_args->dane)
if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
@@ -3857,6 +3969,24 @@ DEBUG(D_tls)
tls_client_resume_posthandshake(exim_client_ctx, tlsp);
#endif
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn) /* We requested. See what was negotiated. */
+ {
+ const uschar * name;
+ unsigned len;
+
+ SSL_get0_alpn_selected(exim_client_ctx->ssl, &name, &len);
+ if (len > 0)
+ { DEBUG(D_tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); }
+ else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
+ {
+ /* Would like to send a relevant fatal Alert, but OpenSSL has no API */
+ tls_error(US"handshake", host, US"ALPN required but not negotiated", errstr);
+ return FALSE;
+ }
+ }
+#endif
+
#ifdef SSL_get_extms_support
tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1;
#endif
diff --git a/src/src/tls.c b/src/src/tls.c
index 0df99845c..c497ac307 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -685,7 +685,6 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
return FALSE;
}
-
/* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
and writes a file by that name. Our OpenSSL code does the same, using keying
info from the library API.
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 3210e596c..c62de724d 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -84,6 +84,7 @@ optionlist smtp_transport_options[] = {
#if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP)
{ "hosts_request_ocsp", opt_stringptr, LOFF(hosts_request_ocsp) },
#endif
+ { "hosts_require_alpn", opt_stringptr, LOFF(hosts_require_alpn) },
{ "hosts_require_auth", opt_stringptr, LOFF(hosts_require_auth) },
#ifndef DISABLE_TLS
# ifdef SUPPORT_DANE
@@ -123,6 +124,7 @@ optionlist smtp_transport_options[] = {
{ "socks_proxy", opt_stringptr, LOFF(socks_proxy) },
#endif
#ifndef DISABLE_TLS
+ { "tls_alpn", opt_stringptr, LOFF(tls_alpn) },
{ "tls_certificate", opt_stringptr, LOFF(tls_certificate) },
{ "tls_crl", opt_stringptr, LOFF(tls_crl) },
{ "tls_dh_min_bits", opt_int, LOFF(tls_dh_min_bits) },
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index aff3f5452..2ce8b11c5 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -48,6 +48,7 @@ typedef struct {
uschar *dscp;
uschar *serialize_hosts;
uschar *hosts_try_auth;
+ uschar *hosts_require_alpn;
uschar *hosts_require_auth;
uschar *hosts_try_chunking;
#ifdef SUPPORT_DANE
@@ -101,6 +102,7 @@ typedef struct {
uschar *socks_proxy;
#endif
#ifndef DISABLE_TLS
+ uschar *tls_alpn;
uschar *tls_certificate;
uschar *tls_crl;
uschar *tls_privatekey;
diff --git a/test/confs/1115 b/test/confs/1115
new file mode 100644
index 000000000..c1d571821
--- /dev/null
+++ b/test/confs/1115
@@ -0,0 +1,73 @@
+# Exim test configuration 1115
+# ALPN
+
+SERVER =
+CONTROL =
+
+.include DIR/aux-var/tls_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+domainlist local_domains = test.ex : *.test.ex
+
+acl_smtp_rcpt = accept
+
+tls_advertise_hosts = *
+tls_certificate = DIR/aux-fixed/cert1
+
+.ifdef STRICT
+tls_alpn = STRICT
+.endif
+.ifdef REQUIRE
+hosts_require_alpn = *
+.endif
+
+
+# ------ ACL ------
+
+begin acl
+
+# ----- Routers -----
+
+begin routers
+
+client:
+ driver = accept
+ condition = ${if eq {SERVER}{server} {no}{yes}}
+ transport = send_to_server
+
+server:
+ driver = redirect
+ data = :blackhole:
+
+
+# ----- Transports -----
+
+begin transports
+
+send_to_server:
+ driver = smtp
+ allow_localhost
+ hosts = HOSTIPV4
+ port = PORT_D
+ tls_verify_certificates = DIR/aux-fixed/cert1
+ tls_verify_cert_hostnames = :
+
+ hosts_require_tls = *
+ hosts_try_fastopen = :
+ tls_alpn = CONTROL
+.ifdef REQUIRE
+ hosts_require_alpn = *
+.endif
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
diff --git a/test/confs/2038 b/test/confs/2038
new file mode 120000
index 000000000..03a524df8
--- /dev/null
+++ b/test/confs/2038
@@ -0,0 +1 @@
+1115 \ No newline at end of file
diff --git a/test/confs/2138 b/test/confs/2138
new file mode 120000
index 000000000..03a524df8
--- /dev/null
+++ b/test/confs/2138
@@ -0,0 +1 @@
+1115 \ No newline at end of file
diff --git a/test/log/1115 b/test/log/1115
new file mode 100644
index 000000000..af3f1df34
--- /dev/null
+++ b/test/log/1115
@@ -0,0 +1,45 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes 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
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == b@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (SSL_connect): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 == c@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (SSL_connect): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmbA-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbB-0005vi-00 => d@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbD-0005vi-00 => e@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbF-0005vi-00 => f@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbG-0005vi-00"
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbH-0005vi-00 => g@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbI-0005vi-00"
+1999-03-02 09:44:33 10HmbH-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 10HmaY-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=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (SSL_accept): error: <<detail omitted>>
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (SSL_accept): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmbC-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=E10HmbB-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <d@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbE-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=E10HmbD-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <e@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-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 10HmbG-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=E10HmbF-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbG-0005vi-00 => :blackhole: <f@test.ex> R=server
+1999-03-02 09:44:33 10HmbG-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 10HmbI-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=E10HmbH-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbI-0005vi-00 => :blackhole: <g@test.ex> R=server
+1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
diff --git a/test/log/2038 b/test/log/2038
new file mode 100644
index 000000000..a93f4d3ed
--- /dev/null
+++ b/test/log/2038
@@ -0,0 +1,26 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => client_require@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes 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
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == client_require_fail@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (handshake): ALPN required but not negotiated
+1999-03-02 09:44:33 10HmaZ-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 => server_require_good@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbC-0005vi-00 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] TLS error on connection (recv): A TLS fatal alert has been received: No supported application protocol could be negotiated
+1999-03-02 09:44:33 10HmbC-0005vi-00 == server_require_bad@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: error on first read
+
+******** 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 10HmaY-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=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <client_require@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-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 error on connection from the.local.host.name [ip4.ip4.ip4.ip4] (recv): A TLS fatal alert has been received: No supported application protocol could be negotiated
+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 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=E10HmbA-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <server_require_good@test.ex> R=server
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (handshake): ALPN required but not negotiated
diff --git a/test/log/2138 b/test/log/2138
new file mode 100644
index 000000000..bbc80a45a
--- /dev/null
+++ b/test/log/2138
@@ -0,0 +1,25 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => client_require@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes 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
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == client_require_fail@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (handshake): ALPN required but not negotiated
+1999-03-02 09:44:33 10HmaZ-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 => server_require_good@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbC-0005vi-00 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: Remote host closed connection in response to EHLO myhost.test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 == server_require_bad@test.ex R=client T=send_to_server defer (-18) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: Remote host closed connection in response to EHLO myhost.test.ex
+
+******** 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 10HmaY-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=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <client_require@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-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 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+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=E10HmbA-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <server_require_good@test.ex> R=server
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (handshake): ALPN required but not negotiated
diff --git a/test/runtest b/test/runtest
index 670672a70..6f142c540 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1507,6 +1507,8 @@ RESET_AFTER_EXTRA_LINE_READ:
s/session: \K\((SSL_connect|gnutls_handshake)\): timed out/(tls lib connect fn): timed out/;
s/TLS error on connection from .*\K\((SSL_accept|gnutls_handshake)\): timed out/(tls lib accept fn): timed out/;
s/TLS error on connection from .*\K(SSL_accept: TCP connection closed by peer|\(gnutls_handshake\): The TLS connection was non-properly terminated.)/(tls lib accept fn): TCP connection closed by peer/;
+ s/TLS session: \K\(gnutls_handshake\): No supported application protocol could be negotiated/(SSL_connect): error: <<detail omitted>>/;
+ s/\(gnutls_handshake\): No common application protocol could be negotiated./(SSL_accept): error: <<detail omitted>>/;
}
# ======== mail ========
diff --git a/test/scripts/1100-Basic-TLS/1115 b/test/scripts/1100-Basic-TLS/1115
new file mode 100644
index 000000000..d74caca52
--- /dev/null
+++ b/test/scripts/1100-Basic-TLS/1115
@@ -0,0 +1,50 @@
+# TLS: ALPN
+gnutls
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+# Basic: is good ALPN set on tpt acceptable to server
+exim -DCONTROL=smtp -odf a@test.ex
+Test message.
+****
+#
+# Bad ALPN rejected
+exim -DCONTROL=http -odf b@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+#
+# Multiple ALPN rejected
+exim -DCONTROL=smtp:smtp -odf c@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+#
+# Empty client option is ok
+exim -DCONTROL="" -odf d@test.ex
+****
+# Content-free client option is ok
+exim -DCONTROL=" " -odf e@test.ex
+****
+killdaemon
+#
+# Server can be told to ignore (bad) ALPN from client
+exim -DSERVER=server -DSTRICT="" -bd -oX PORT_D
+****
+exim -DCONTROL=http -odf f@test.ex
+****
+killdaemon
+#
+# Server can be told custom names list
+exim -DSERVER=server -DSTRICT='${if eq {$sender_host_address}{HOSTIPV4} {smtp:weird} {smtp}}' -bd -oX PORT_D
+****
+exim -DCONTROL=weird -odf g@test.ex
+****
+killdaemon
+#
+#
+no_msglog_check
+no_stdout_check
+millisleep 500
diff --git a/test/scripts/2000-GnuTLS/2038 b/test/scripts/2000-GnuTLS/2038
new file mode 100644
index 000000000..227af8475
--- /dev/null
+++ b/test/scripts/2000-GnuTLS/2038
@@ -0,0 +1,36 @@
+# TLS: ALPN: mandatory
+# Separated from the OpenSSL equivalent as we deliberately send a Fatal Alert, and that gets logged by the receiver
+# OpenSSL does not provides the facility.
+gnutls
+exim -DSERVER=server -bd -oX PORT_D
+****
+# Client requires ALPN (success)
+exim -DCONTROL=smtp -DREQUIRE=y -odf client_require@test.ex
+****
+killdaemon
+#
+# Server can be told to ignore (bad) ALPN from client
+exim -DSERVER=server -DSTRICT="" -bd -oX PORT_D
+****
+# Client requires ALPN (fail)
+exim -DCONTROL=http -DREQUIRE=y -odf client_require_fail@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+killdaemon
+#
+#
+# Server can be told ALPN mandatory
+exim -DSERVER=server -DREQUIRE=y -bd -oX PORT_D
+****
+# Client supplies ALPN, good
+exim -DCONTROL=smtp -odf server_require_good@test.ex
+****
+# Client does not supply ALPN, fails
+exim -odf server_require_bad@test.ex
+****
+killdaemon
+#
+no_msglog_check
+no_stdout_check
diff --git a/test/scripts/2100-OpenSSL/2138 b/test/scripts/2100-OpenSSL/2138
new file mode 100644
index 000000000..7dadb3051
--- /dev/null
+++ b/test/scripts/2100-OpenSSL/2138
@@ -0,0 +1,35 @@
+# TLS: ALPN: mandatory
+#
+# Plain server
+exim -DSERVER=server -bd -oX PORT_D
+****
+# Client requires ALPN (success)
+exim -DCONTROL=smtp -DREQUIRE=y -odf client_require@test.ex
+****
+killdaemon
+#
+# Server can be told to ignore (bad) ALPN from client
+exim -DSERVER=server -DSTRICT="" -bd -oX PORT_D
+****
+# Client requires ALPN (fail)
+exim -DCONTROL=http -DREQUIRE=y -odf client_require_fail@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+killdaemon
+#
+#
+# Server can be told ALPN mandatory
+exim -DSERVER=server -DREQUIRE=y -bd -oX PORT_D
+****
+# Client supplies ALPN, good
+exim -DCONTROL=smtp -odf server_require_good@test.ex
+****
+# Client does not supply ALPN, fails
+exim -odf server_require_bad@test.ex
+****
+killdaemon
+#
+no_msglog_check
+no_stdout_check
diff --git a/test/stdout/0572 b/test/stdout/0572
index fd77c72b7..0ba712dcf 100644
--- a/test/stdout/0572
+++ b/test/stdout/0572
@@ -55,6 +55,7 @@ hosts_max_try = 5
hosts_max_try_hardlimit = 50
no_hosts_override
no_hosts_randomize
+hosts_require_alpn =
hosts_require_auth =
hosts_try_auth =
hosts_try_chunking = *