* 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. */
#include "../exim.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",
+/* 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
+}
+
+
+
/* Wait for and check responses for early-pipelining.
Called from the lower-level smtp_read_response() function
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)
+#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
DEBUG(D_transport)
debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
- HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n");
+ smtp_debug_cmd(US"QUIT", 0);
write(0, "QUIT\r\n", 6);
close(0);
continue_hostname = continue_proxy_cipher = NULL;
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)
{
sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
#endif
sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
+#ifndef DISABLE_CLIENT_CMD_LOG
+ client_cmd_log = NULL;
+#endif
#ifndef DISABLE_PIPE_CONNECT
if ( verify_check_given_host(CUSS &ob->hosts_pipe_connect,
the helo string might use it avoid doing early-pipelining. */
if ( !sx->helo_data
+ || sx->conn_args.interface
|| !Ustrstr(sx->helo_data, "$sending_ip_address")
|| Ustrstr(sx->helo_data, "def:sending_ip_address")
)
}
}
else DEBUG(D_transport)
- debug_printf("helo needs $sending_ip_address\n");
+ debug_printf("helo needs $sending_ip_address; avoid early-pipelining\n");
PIPE_CONNECT_RETRY:
if (sx->early_pipe_active)
+ {
sx->outblock.conn_args = &sx->conn_args;
+ (void) smtp_boundsock(&sx->conn_args);
+ }
else
#endif
{
}
/* Expand the greeting message while waiting for the initial response. (Makes
sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
- delayed till here so that $sending_interface and $sending_port are set. */
-/*XXX early-pipe: they still will not be. Is there any way to find out what they
-will be? Somehow I doubt it. */
+ delayed till here so that $sending_ip_address and $sending_port are set.
+ Those will be known even for a TFO lazy-connect, having been set by the bind().
+ For early-pipe, we are ok if binding to a local interface; otherwise (if
+ $sending_ip_address is seen in helo_data) we disabled early-pipe above. */
if (sx->helo_data)
if (!(sx->helo_data = expand_string(sx->helo_data)))
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
smtp_peer_options & OPTION_PIPE ? "" : "not ");
if ( sx->peer_offered & OPTION_CHUNKING
- && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) != OK)
- sx->peer_offered &= ~OPTION_CHUNKING;
+ && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) == OK)
+ smtp_peer_options |= OPTION_CHUNKING;
- if (sx->peer_offered & OPTION_CHUNKING)
+ if (smtp_peer_options & OPTION_CHUNKING)
DEBUG(D_transport) debug_printf("CHUNKING usable\n");
#ifndef DISABLE_PRDR
if ( sx->peer_offered & OPTION_PRDR
- && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) != OK)
- sx->peer_offered &= ~OPTION_PRDR;
+ && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) == OK)
+ smtp_peer_options |= OPTION_PRDR;
- if (sx->peer_offered & OPTION_PRDR)
+ if (smtp_peer_options & OPTION_PRDR)
DEBUG(D_transport) debug_printf("PRDR usable\n");
#endif
(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL, NULL);
#endif
+smtp_debug_cmd_report();
continue_transport = NULL;
continue_hostname = NULL;
return yield;
request that */
sx->prdr_active = FALSE;
-if (sx->peer_offered & OPTION_PRDR)
+if (smtp_peer_options & OPTION_PRDR)
for (address_item * addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == PENDING_DEFER)
{
bufsiz size of buffer
pfd pipe filedescriptor array; [0] is comms to proxied process
timeout per-read timeout, seconds
+ host hostname of remote
Does not return.
*/
void
smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
- int timeout)
+ int timeout, const uschar * host)
{
struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
{.fd = pfd[0], .events = POLLIN}};
if ((rc = exim_fork(US"tls-proxy")))
_exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
-set_process_info("proxying TLS connection for continued transport");
+set_process_info("proxying TLS connection for continued transport to %s\n", host);
do
{
sx->conn_args.interface = interface;
sx->helo_data = NULL;
sx->conn_args.tblock = tblock;
+sx->conn_args.sock = -1;
gettimeofday(&sx->delivery_start, NULL);
sx->sync_addr = sx->first_addr = addrlist;
yield ERROR. */
if (!transport_set_up_command(&transport_filter_argv,
- tblock->filter_command, TRUE, DEFER, addrlist,
+ tblock->filter_command, TRUE, DEFER, addrlist, FALSE,
string_sprintf("%.50s transport filter", tblock->name), NULL))
{
set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
if ( transport_filter_argv
&& *transport_filter_argv
&& **transport_filter_argv
- && sx->peer_offered & OPTION_CHUNKING
+ && smtp_peer_options & OPTION_CHUNKING
#ifndef DISABLE_DKIM
/* When dkim signing, chunking is handled even with a transport-filter */
&& !(ob->dkim.dkim_private_key && ob->dkim.dkim_domain && ob->dkim.dkim_selector)
#endif
)
{
- sx->peer_offered &= ~OPTION_CHUNKING;
+ smtp_peer_options &= ~OPTION_CHUNKING;
DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
}
}
If using CHUNKING, do not send a BDAT until we know how big a chunk we want
to send is. */
-if ( !(sx->peer_offered & OPTION_CHUNKING)
+if ( !(smtp_peer_options & OPTION_CHUNKING)
&& (sx->ok || (pipelining_active && !mua_wrapper)))
{
int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
(Haven't been able to make it work using select() for writing yet.) */
if ( !sx->ok
- && (!(sx->peer_offered & OPTION_CHUNKING) || !pipelining_active))
+ && (!(smtp_peer_options & OPTION_CHUNKING) || !pipelining_active))
{
/* Save the first address of the next batch. */
sx->first_addr = sx->next_addr;
of responses. The callback needs a whole bunch of state so set up
a transport-context structure to be passed around. */
- if (sx->peer_offered & OPTION_CHUNKING)
+ if (smtp_peer_options & OPTION_CHUNKING)
{
tctx.check_string = tctx.escape_string = NULL;
tctx.options |= topt_use_bdat;
transport_write_timeout = ob->data_timeout;
smtp_command = US"sending data block"; /* For error messages */
DEBUG(D_transport|D_v)
- if (sx->peer_offered & OPTION_CHUNKING)
+ 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
If we can, we want the message-write to not flush (the tail end of) its data out. */
if ( sx->pipelining_used
- && (sx->ok && sx->completed_addr || sx->peer_offered & OPTION_CHUNKING)
+ && (sx->ok && sx->completed_addr || smtp_peer_options & OPTION_CHUNKING)
&& sx->send_quit
&& !(sx->first_addr || f.continue_more)
&& f.deliver_firsttime
sx->send_quit = FALSE; /* avoid sending it later */
#ifndef DISABLE_TLS
- if (sx->cctx.tls_ctx) /* need to send TLS Close Notify */
+ if (sx->cctx.tls_ctx && sx->send_tlsclose) /* need to send TLS Close Notify */
{
# ifdef EXIM_TCP_CORK /* Use _CORK to get Close Notify in FIN segment */
(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
}
}
- if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1)
+ if (smtp_peer_options & OPTION_CHUNKING && sx->cmd_count > 1)
{
/* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
switch(sync_responses(sx, sx->cmd_count-1, 0))
#ifndef DISABLE_PRDR
if (sx->prdr_active) setflag(addr, af_prdr_used);
#endif
- if (sx->peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
+ if (smtp_peer_options & OPTION_CHUNKING) setflag(addr, af_chunking_used);
flag = '-';
#ifndef DISABLE_PRDR
# ifndef DISABLE_TLS
if (sx->cctx.tls_ctx)
{
- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ tls_close(sx->cctx.tls_ctx,
+ sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
sx->cctx.tls_ctx = NULL;
}
# endif
a new EHLO. If we don't get a good response, we don't attempt to pass
the socket on. */
- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ tls_close(sx->cctx.tls_ctx,
+ sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
sx->send_tlsclose = FALSE;
sx->cctx.tls_ctx = NULL;
tls_out.active.sock = -1;
{
/* does not return */
smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
- ob->command_timeout);
+ ob->command_timeout, host->name);
}
if (pid > 0) /* parent */
{ /* Use _MORE to get QUIT in FIN segment */
(void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
#ifndef DISABLE_TLS
- if (sx->cctx.tls_ctx)
+ if (sx->cctx.tls_ctx && sx->send_tlsclose)
{
# ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */
(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
while (!sigalrm_seen && n > 0);
ALARM_CLR(0);
+ if (sx->send_tlsclose)
+ {
# ifdef EXIM_TCP_CORK
- (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
# endif
- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ }
+ else
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WONLY);
sx->cctx.tls_ctx = NULL;
}
#endif
sx->cctx.sock = -1;
continue_transport = NULL;
continue_hostname = NULL;
+smtp_debug_cmd_report();
#ifndef DISABLE_EVENT
(void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL);