* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../exim.h"
#include "smtp.h"
{ "final_timeout", opt_time, LOFF(final_timeout) },
{ "gethostbyname", opt_bool, LOFF(gethostbyname) },
{ "helo_data", opt_stringptr, LOFF(helo_data) },
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+ { "host_name_extract", opt_stringptr, LOFF(host_name_extract) },
+# endif
{ "hosts", opt_stringptr, LOFF(hosts) },
{ "hosts_avoid_esmtp", opt_stringptr, LOFF(hosts_avoid_esmtp) },
{ "hosts_avoid_pipelining", opt_stringptr, LOFF(hosts_avoid_pipelining) },
.tls_tempfail_tryclear = TRUE,
.tls_try_verify_hosts = US"*",
.tls_verify_cert_hostnames = US"*",
+# ifndef DISABLE_TLS_RESUME
+ .host_name_extract = US"${if and {{match{$host}{.outlook.com\\$}} {match{$item}{\\N^250-([\\w.]+)\\s\\N}}} {$1}}",
+# endif
#endif
#ifdef SUPPORT_I18N
.utf8_downconvert = US"-1",
for (struct list * l = list; l < list + nelem(list); l++)
if (!*l->re)
- *l->re = regex_must_compile(l->string, FALSE, TRUE);
+ *l->re = regex_must_compile(l->string, MCS_NOFLAGS, TRUE);
}
}
+/* Grab a string differentiating server behind a loadbalancer, for TLS
+resumption when such servers do not share a session-cache */
+
+static void
+ehlo_response_lbserver(smtp_context * sx, smtp_transport_options_block * ob)
+{
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+const uschar * s;
+uschar * save_item = iterate_item;
+
+if (sx->conn_args.have_lbserver)
+ return;
+iterate_item = sx->buffer;
+s = expand_cstring(ob->host_name_extract);
+iterate_item = save_item;
+sx->conn_args.host_lbserver = s && !*s ? NULL : s;
+sx->conn_args.have_lbserver = TRUE;
+#endif
+}
+
+
+
/******************************************************************************/
#ifdef EXPERIMENTAL_ESMTP_LIMITS
static void
ehlo_cache_limits_apply(smtp_context * sx)
{
+# ifndef DISABLE_PIPE_CONNECT
ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
sx->ehlo_resp.limit_rcptdom);
+# endif
}
-#endif
+#endif /*EXPERIMENTAL_ESMTP_LIMITS*/
/******************************************************************************/
{
open_db dbblock, * dbm_file;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
sx->ehlo_resp.limit_mail = sx->peer_limit_mail;
sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt;
sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom;
-#endif
+# endif
if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
{
dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
HDEBUG(D_transport)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom)
debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n",
sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
sx->ehlo_resp.limit_rcptdom);
else
-#endif
+# endif
debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);
else
{
DEBUG(D_transport)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom)
debug_printf("EHLO response bits from cache:"
" cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n",
er->data.crypted_features, er->data.crypted_auths,
er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom);
else
-#endif
+# endif
debug_printf("EHLO response bits from cache:"
" cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n",
er->data.cleartext_features, er->data.cleartext_auths,
er->data.crypted_features, er->data.crypted_auths);
sx->ehlo_resp = er->data;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
ehlo_cache_limits_apply(sx);
-#endif
+# endif
dbfn_close(dbm_file);
return TRUE;
}
unsigned short authbits = 0;
if (!sx->esmtp) return 0;
-if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE);
if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
expand_nmax = -1; /* reset */
names = string_copyn(expand_nstring[1], expand_nlength[1]);
if (tls_out.active.sock >= 0) rc = DEFER;
goto fail;
}
+ /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
+ ehlo_response_lbserver(sx, sx->conn_args.ob);
}
if (pending_EHLO)
| OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
| OPTION_UTF8 | OPTION_EARLY_PIPE
);
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
ehlo_response_limits_read(sx);
-#endif
+# endif
if ( peer_offered != sx->peer_offered
|| (authbits = study_ehlo_auths(sx)) != *ap)
{
return OK; /* just carry on */
}
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
/* If we are handling LIMITS, compare the actual EHLO LIMITS values with the
cached values and invalidate cache if different. OK to carry on with
connect since values are advisory. */
invalidate_ehlo_cache_entry(sx);
}
}
-#endif
+# endif
}
return OK;
sx->buffer, sizeof(sx->buffer));
sx->outblock.authenticating = FALSE;
driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
-DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+DEBUG(D_transport) debug_printf("%s authenticator yielded %s\n", au->name, rc_names[rc]);
/* A temporary authentication failure must hold up delivery to
this host. After a permanent authentication failure, we carry on
/* Failure after reading a response */
case FAIL:
+ {
+ uschar * logmsg = NULL;
+
if (errno != 0 || sx->buffer[0] != '5') return FAIL;
- log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
- au->name, host->name, host->address, sx->buffer);
+#ifndef DISABLE_EVENT
+ {
+ uschar * save_name = sender_host_authenticated;
+ sender_host_authenticated = au->name;
+ if ((logmsg = event_raise(sx->conn_args.tblock->event_action, US"auth:fail",
+ sx->buffer, NULL)))
+ log_write(0, LOG_MAIN, "%s", logmsg);
+ sender_host_authenticated = save_name;
+ }
+#endif
+ if (!logmsg)
+ log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+ au->name, host->name, host->address, sx->buffer);
break;
+ }
/* Failure by some other means. In effect, the authenticator
decided it wasn't prepared to handle this case. Typically this
client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
if (!regex_AUTH)
- regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+ regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE);
/* Is the server offering AUTH? */
#ifndef DISABLE_TLS
if ( checks & OPTION_TLS
&& pcre2_match(regex_STARTTLS,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
#endif
checks &= ~OPTION_TLS;
if ( checks & OPTION_IGNQ
&& pcre2_match(regex_IGNOREQUOTA,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
checks &= ~OPTION_IGNQ;
if ( checks & OPTION_CHUNKING
&& pcre2_match(regex_CHUNKING,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
checks &= ~OPTION_CHUNKING;
#ifndef DISABLE_PRDR
if ( checks & OPTION_PRDR
&& pcre2_match(regex_PRDR,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
#endif
checks &= ~OPTION_PRDR;
#ifdef SUPPORT_I18N
if ( checks & OPTION_UTF8
&& pcre2_match(regex_UTF8,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
#endif
checks &= ~OPTION_UTF8;
if ( checks & OPTION_DSN
&& pcre2_match(regex_DSN,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
checks &= ~OPTION_DSN;
if ( checks & OPTION_PIPE
&& pcre2_match(regex_PIPELINING,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
checks &= ~OPTION_PIPE;
if ( checks & OPTION_SIZE
&& pcre2_match(regex_SIZE,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
checks &= ~OPTION_SIZE;
#ifndef DISABLE_PIPE_CONNECT
if ( checks & OPTION_EARLY_PIPE
&& pcre2_match(regex_EARLY_PIPE,
- (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
#endif
checks &= ~OPTION_EARLY_PIPE;
-pcre2_match_data_free(md);
+/* pcre2_match_data_free(md); gen ctx needs no free */
/* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */
return checks;
}
+#ifdef SUPPORT_DANE
+static int
+check_force_dane_conn(smtp_context * sx, smtp_transport_options_block * ob)
+{
+int rc;
+if( sx->dane_required
+ || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
+ )
+ switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
+ {
+ case OK: sx->conn_args.dane = TRUE;
+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */
+ ob->tls_sni = sx->conn_args.host->name; /* force SNI */
+ break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: tlsa lookup %s",
+ rc_to_string(rc)),
+ rc, FALSE, &sx->delivery_start);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", sx->dane_required
+ ? US"dane-required" : US"dnssec-invalid",
+ NULL);
+# endif
+ return rc;
+ }
+return OK;
+}
+#endif
+
+
/*************************************************
* Make connection for given message *
*************************************************/
const uschar * sni = US"";
# ifdef SUPPORT_DANE
- /* Check if the message will be DANE-verified; if so force its SNI */
+ /* Check if the message will be DANE-verified; if so force TLS and its SNI */
tls_out.dane_verified = FALSE;
smtp_port_for_connect(sx->conn_args.host, sx->port);
if ( sx->conn_args.host->dnssec == DS_YES
- && ( sx->dane_required
- || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
- ) )
- switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
- {
- case OK: sx->conn_args.dane = TRUE;
- ob->tls_tempfail_tryclear = FALSE; /* force TLS */
- ob->tls_sni = sx->conn_args.host->name; /* force SNI */
- break;
- case FAIL_FORCED: break;
- default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
- string_sprintf("DANE error: tlsa lookup %s",
- rc_to_string(rc)),
- rc, FALSE, &sx->delivery_start);
-# ifndef DISABLE_EVENT
- (void) event_raise(sx->conn_args.tblock->event_action,
- US"dane:fail", sx->dane_required
- ? US"dane-required" : US"dnssec-invalid",
- NULL);
-# endif
- return rc;
- }
+ && (rc = check_force_dane_conn(sx, ob)) != OK
+ )
+ return rc;
# endif
/* If the SNI or the DANE status required for the new message differs from the
if (sx->conn_args.host->dnssec == DS_YES)
{
int rc;
- if( sx->dane_required
- || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
- )
- switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
- {
- case OK: sx->conn_args.dane = TRUE;
- ob->tls_tempfail_tryclear = FALSE; /* force TLS */
- ob->tls_sni = sx->conn_args.host->name; /* force SNI */
- break;
- case FAIL_FORCED: break;
- default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
- string_sprintf("DANE error: tlsa lookup %s",
- rc_to_string(rc)),
- rc, FALSE, &sx->delivery_start);
-# ifndef DISABLE_EVENT
- (void) event_raise(sx->conn_args.tblock->event_action,
- US"dane:fail", sx->dane_required
- ? US"dane-required" : US"dnssec-invalid",
- NULL);
-# endif
- return rc;
- }
+ if ((rc = check_force_dane_conn(sx, ob)) != OK)
+ return rc;
}
else if (sx->dane_required)
{
if ( (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
== (OPTION_PIPE | OPTION_EARLY_PIPE))
{
- DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+ DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n");
sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
write_ehlo_cache_entry(sx);
}
}
#endif
+ ehlo_response_lbserver(sx, ob);
}
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
the response for the STARTTLS we just sent alone. On fail, assume wrong
cached capability and retry with the pipelining disabled. */
- if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0)
+ if (sx->early_pipe_active)
{
- HDEBUG(D_transport)
- debug_printf("failed reaping pipelined cmd responses\n");
- close(sx->cctx.sock);
- sx->cctx.sock = -1;
- sx->early_pipe_active = FALSE;
- goto PIPE_CONNECT_RETRY;
+ if (sync_responses(sx, 2, 0) != 0)
+ {
+ HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ close(sx->cctx.sock);
+ sx->cctx.sock = -1;
+ sx->early_pipe_active = FALSE;
+ goto PIPE_CONNECT_RETRY;
+ }
}
#endif
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
if (smtp_peer_options & OPTION_CHUNKING)
debug_printf(" will write message using CHUNKING\n");
else
- debug_printf(" SMTP>> writing message and terminating \".\"\n");
+ debug_printf(" SMTP>> (writing message)\n");
transport_count = 0;
#ifndef DISABLE_DKIM
open, we must shut down TLS. Not all MTAs allow for the continuation
of the SMTP session when TLS is shut down. We test for this by sending
a new EHLO. If we don't get a good response, we don't attempt to pass
- the socket on. */
+ the socket on.
+ NB: TLS close is *required* per RFC 9266 when tls-exporter info has
+ been used, which we do under TLSv1.3 for the gsasl SCRAM*PLUS methods.
+ But we were always doing it anyway. */
tls_close(sx->cctx.tls_ctx,
sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
uschar *retry_message_key = NULL;
uschar *serialize_key = NULL;
+ /* Deal slightly better with a possible Linux kernel bug that results
+ in intermittent TFO-conn fails deep into the TCP flow. Bug 2907 tracks.
+ Hack: Clear TFO option for any further hosts on this tpt run. */
+
+ if (total_hosts_tried > 0)
+ {
+ DEBUG(D_transport|D_acl|D_v)
+ debug_printf("Clearing TFO as not first host for message\n");
+ ob->hosts_try_fastopen = US"";
+ }
+
/* Default next host is next host. :-) But this can vary if the
hosts_max_try limit is hit (see below). It may also be reset if a host
address is looked up here (in case the host was multihomed). */