* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
/* 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"
LOFF(address_retry_include_sender) },
{ "allow_localhost", opt_bool, LOFF(allow_localhost) },
#ifdef EXPERIMENTAL_ARC
- { "arc_sign", opt_stringptr, LOFF(arc_sign) },
+ { "arc_sign", opt_stringptr, LOFF(arc_sign) },
#endif
{ "authenticated_sender", opt_stringptr, LOFF(authenticated_sender) },
{ "authenticated_sender_force", opt_bool, LOFF(authenticated_sender_force) },
{ "data_timeout", opt_time, LOFF(data_timeout) },
{ "delay_after_cutoff", opt_bool, LOFF(delay_after_cutoff) },
#ifndef DISABLE_DKIM
+ /*XXX dkim module */
{ "dkim_canon", opt_stringptr, LOFF(dkim.dkim_canon) },
{ "dkim_domain", opt_stringptr, LOFF(dkim.dkim_domain) },
{ "dkim_hash", opt_stringptr, LOFF(dkim.dkim_hash) },
{ "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) },
{ "interface", opt_stringptr, LOFF(interface) },
{ "keepalive", opt_bool, LOFF(keepalive) },
{ "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) },
- { "max_rcpt", opt_int | opt_public,
+ { "max_rcpt", opt_stringptr | opt_public,
OPT_OFF(transport_instance, max_addresses) },
{ "message_linelength_limit", opt_int, LOFF(message_linelength_limit) },
{ "multi_domain", opt_expand_bool | opt_public,
/* Dummy values */
smtp_transport_options_block smtp_transport_option_defaults = {0};
-void smtp_transport_init(transport_instance *tblock) {}
+void smtp_transport_init(driver_instance *tblock) {}
BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
void smtp_transport_closedown(transport_instance *tblock) {}
static unsigned ehlo_response(uschar * buf, unsigned checks);
+/* sync_responses() return codes */
+
+#define RESP_BIT_HAD_5XX BIT(1)
+#define RESP_BIT_HAD_2XX BIT(0)
+#define RESP_HAD_2_AND_5 (RESP_BIT_HAD_2XX | RESP_BIT_HAD_5XX)
+#define RESP_NOERROR 0
+#define RESP_RCPT_TIMEO -1
+#define RESP_RCPT_ERROR -2
+#define RESP_MAIL_OR_DATA_ERROR -3
+#define RESP_EPIPE_EHLO_ERR -4
+#define RESP_EHLO_ERR_TLS -5
/******************************************************************************/
#ifndef DISABLE_PIPE_CONNECT
{ ®ex_EARLY_PIPE, US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" },
#endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
{ ®ex_LIMITS, US"\\n250[\\s\\-]LIMITS\\s" },
#endif
};
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);
}
smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
{
-smtp_transport_options_block *ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = tblock->drinst.options_block;
/* Pass back options if required. This interface is getting very messy. */
*/
void
-smtp_transport_init(transport_instance *tblock)
+smtp_transport_init(driver_instance * t)
{
-smtp_transport_options_block *ob = SOB tblock->options_block;
+transport_instance * tblock = (transport_instance *)t;
+smtp_transport_options_block * ob = t->options_block;
int old_pool = store_pool;
/* Retry_use_local_part defaults FALSE if unset */
ob->final_timeout <= 0)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"command, data, or final timeout value is zero for %s transport",
- tblock->name);
+ t->name);
/* If hosts_override is set and there are local hosts, set the global
flag that stops verify from showing router hosts. */
if (host)
{
addr->host_used = host;
+ if (continue_sequence > 1)
+ { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+ else
+ { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
#ifdef EXPERIMENTAL_DSN_INFO
if (smtp_greeting)
{uschar * s = Ustrchr(smtp_greeting, '\n'); if (s) *s = '\0';}
case ERRNO_SMTPFORMAT: /* Handle malformed SMTP response */
s = string_printing(buffer);
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
*message = *s == 0
? string_sprintf("Malformed SMTP reply (an empty line) "
"in response to %s%s", pl, smtp_command)
else
message = string_fmt_append(message, " %s", exim_errstr(basic_errno));
-log_write(0, LOG_MAIN, "%s", string_from_gstring(message));
-deliver_msglog("%s %s\n", tod_stamp(tod_log), message->s);
+log_write(0, LOG_MAIN, "%Y", message);
+deliver_msglog("%s %.*s\n", tod_stamp(tod_log), message->ptr, message->s);
}
static void
deferred_event_raise(address_item * addr, host_item * host, uschar * evstr)
{
uschar * action = addr->transport->event_action;
-const uschar * save_domain;
-uschar * save_local;
+const uschar * save_domain, * save_local;
+const uschar * save_rn, * save_tn;
if (!action)
return;
save_domain = deliver_domain;
save_local = deliver_localpart;
+save_rn = router_name;
+save_tn = transport_name;
/*XXX would ip & port already be set up? */
deliver_host_address = string_copy(host->address);
deliver_host_port = host->port == PORT_NONE ? 25 : host->port;
event_defer_errno = addr->basic_errno;
-router_name = addr->router->name;
-transport_name = addr->transport->name;
+router_name = addr->router->drinst.name;
+transport_name = addr->transport->drinst.name;
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
: string_copy(addr->message)
: addr->basic_errno > 0
? string_copy(US strerror(addr->basic_errno))
- : NULL);
+ : NULL,
+ NULL);
deliver_localpart = save_local;
deliver_domain = save_domain;
-router_name = transport_name = NULL;
+router_name = save_rn;
+router_name = save_tn;
}
#endif
#endif
#ifndef DISABLE_EVENT
(void) event_raise(sx->conn_args.tblock->event_action,
- US"smtp:ehlo", sx->buffer);
+ US"smtp:ehlo", sx->buffer, NULL);
#endif
return TRUE;
}
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+
+/* 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, const uschar * name_extract)
+{
+const uschar * s;
+uschar * save_item = iterate_item;
+
+if (sx->conn_args.have_lbserver)
+ return;
+iterate_item = sx->buffer;
+s = expand_cstring(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
+#ifndef DISABLE_ESMTP_LIMITS
/* If TLS, or TLS not offered, called with the EHLO response in the buffer.
Check it for a LIMITS keyword and parse values into the smtp context structure.
if (regex_match(regex_LIMITS, sx->buffer, -1, &match))
for (const uschar * s = sx->buffer + Ustrlen(match); *s; )
{
- while (isspace(*s)) s++;
- if (*s == '\n') break;
+ if (Uskip_whitespace(&s) == '\n') break;
if (strncmpic(s, US"MAILMAX=", 8) == 0)
{
- sx->peer_limit_mail = atoi(CS (s += 8));
+ continue_limit_mail = sx->peer_limit_mail = atoi(CS (s += 8));
while (isdigit(*s)) s++;
}
else if (strncmpic(s, US"RCPTMAX=", 8) == 0)
{
- sx->peer_limit_rcpt = atoi(CS (s += 8));
+ continue_limit_rcpt = sx->peer_limit_rcpt = atoi(CS (s += 8));
while (isdigit(*s)) s++;
}
else if (strncmpic(s, US"RCPTDOMAINMAX=", 14) == 0)
{
- sx->peer_limit_rcptdom = atoi(CS (s += 14));
+ continue_limit_rcptdom = sx->peer_limit_rcptdom = atoi(CS (s += 14));
while (isdigit(*s)) s++;
}
else
sx->peer_limit_rcptdom);
}
+# ifndef DISABLE_PIPE_CONNECT
/* Apply values read from cache to the current connection */
static void
ehlo_cache_limits_apply(smtp_context * sx)
ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
sx->ehlo_resp.limit_rcptdom);
}
-#endif
+# endif
+#endif /*DISABLE_ESMTP_LIMITS*/
/******************************************************************************/
{
open_db dbblock, * dbm_file;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_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)))
+if ((dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
{
uschar * ehlo_resp_key = ehlo_cache_key(sx);
dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
HDEBUG(D_transport)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_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);
open_db dbblock, * dbm_file;
if ( sx->early_pipe_active
- && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
+ && (dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
{
uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ HDEBUG(D_transport)
+ {
+ dbdata_ehlo_resp * er;
+
+ if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
+ debug_printf("no ehlo-resp record!\n");
+ else
+ debug_printf("ehlo-resp record is %.0f seconds old\n",
+ difftime(time(NULL), er->time_stamp));
+ }
+
dbfn_delete(dbm_file, ehlo_resp_key);
dbfn_close(dbm_file);
}
{
DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
dbfn_close(dbm_file);
- if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
+ if ((dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
dbfn_delete(dbm_file, ehlo_resp_key);
}
else
{
DEBUG(D_transport)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_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
+# ifndef DISABLE_ESMTP_LIMITS
ehlo_cache_limits_apply(sx);
-#endif
+# endif
dbfn_close(dbm_file);
return TRUE;
}
/* Return an auths bitmap for the set of AUTH methods offered by the server
-which match our authenticators. */
+which match our client-side authenticators. */
static unsigned short
study_ehlo_auths(smtp_context * sx)
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]);
-for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
- {
- const uschar * list = names;
- uschar * s;
- for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
- if (strcmpic(au->public_name, s) == 0)
- { authbits |= BIT(authnum); break; }
- }
+for (au = auths, authnum = 0; au; au = au->drinst.next, authnum++)
+ if (au->client)
+ {
+ const uschar * list = names;
+ uschar * s;
+ for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
+ if (strcmpic(au->public_name, s) == 0)
+ { authbits |= BIT(authnum); break; }
+ }
DEBUG(D_transport)
- debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n",
+ debug_printf("server offers %s AUTH, methods '%s', usable-bitmap 0x%04x\n",
tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
if (tls_out.active.sock >= 0)
if (pending_BANNER)
{
+ const uschar * s;
+
DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
(*countp)--;
if (!smtp_reap_banner(sx))
if (tls_out.active.sock >= 0) rc = DEFER;
goto fail;
}
+ /*XXX DISABLE_ESMTP_LIMITS ? */
+
+# if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+ GET_OPTION("host_name_extract");
+ s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
+ if (!s) s = HNE_DEFAULT;
+ ehlo_response_lbserver(sx, s);
+# endif
}
if (pending_EHLO)
| OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
| OPTION_UTF8 | OPTION_EARLY_PIPE
);
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_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)
{
write_ehlo_cache_entry(sx);
}
else
+ {
invalidate_ehlo_cache_entry(sx);
+ sx->early_pipe_active = FALSE; /* cancel further early-pipe on this conn */
+ }
return OK; /* just carry on */
}
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_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;
#ifndef DISABLE_PIPE_CONNECT
int rc;
if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
- return rc == FAIL ? -4 : -5;
+ return rc == FAIL ? RESP_EPIPE_EHLO_ERR : RESP_EHLO_ERR_TLS;
#endif
/* Handle the response for a MAIL command. On error, reinstate the original
DEBUG(D_transport) debug_printf("bad response for MAIL\n");
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
if (errno == ERRNO_TLSFAILURE)
- return -5;
+ return RESP_EHLO_ERR_TLS;
if (errno == 0 && sx->buffer[0] != 0)
{
int save_errno = 0;
{
while (addr->transport_return != PENDING_DEFER) addr = addr->next;
addr->host_used = sx->conn_args.host;
+ if (continue_sequence > 1)
+ { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+ else
+ { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
addr = addr->next;
}
- return -3;
+ return RESP_MAIL_OR_DATA_ERROR;
}
}
{
while (addr->transport_return != PENDING_DEFER)
if (!(addr = addr->next))
- return -2;
+ return RESP_RCPT_ERROR;
/* The address was accepted */
addr->host_used = sx->conn_args.host;
+ if (continue_sequence > 1)
+ { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+ else
+ { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address);
if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
- yield |= 1;
+ yield |= RESP_BIT_HAD_2XX;
addr->transport_return = PENDING_OK;
/* If af_dr_retry_exists is set, there was a routing delay on this address;
if (testflag(addr, af_dr_retry_exists))
{
- uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+ uschar * altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
sender_address);
retry_add_item(addr, altkey, rf_delete);
retry_add_item(addr, addr->address_retry_key, rf_delete);
/* Error on first TLS read */
else if (errno == ERRNO_TLSFAILURE)
- return -5;
+ return RESP_EHLO_ERR_TLS;
/* Timeout while reading the response */
else if (errno == ETIMEDOUT)
{
- uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
+ uschar * message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start);
retry_add_item(addr, addr->address_retry_key, 0);
update_waiting = FALSE;
- return -1;
+ return RESP_RCPT_TIMEO;
}
/* Handle other errors in obtaining an SMTP response by returning -1. This
g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
string_from_gstring(g);
- return -2;
+ return RESP_RCPT_ERROR;
}
/* Handle SMTP permanent and temporary response codes. */
if (sx->buffer[0] == '5')
{
addr->transport_return = FAIL;
- yield |= 2;
+ yield |= RESP_BIT_HAD_5XX;
}
/* The response was 4xx */
/* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the
start point for another MAIL command. */
- if (addr->more_errno >> 8 == 52 && yield & 3)
+ if (addr->more_errno >> 8 == 52 && yield > 0)
{
if (!sx->RCPT_452) /* initialised at MAIL-ack above */
{
}
}
if (count && !(addr = addr->next))
- return -2;
+ return RESP_RCPT_ERROR;
} /* Loop for next RCPT response */
/* Update where to start at for the next block of responses, unless we
BOOL pass_message;
if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */
- return -5;
+ return RESP_EHLO_ERR_TLS;
- if (pending_DATA > 0 || (yield & 1) != 0)
+ if (pending_DATA > 0 || yield & RESP_BIT_HAD_2XX)
{
if (errno == 0 && sx->buffer[0] == '4')
{
errno = ERRNO_DATA4XX;
sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
- return -3;
+ return RESP_MAIL_OR_DATA_ERROR;
}
(void)check_response(sx->conn_args.host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
host_item * host = sx->conn_args.host; /* host to deliver to */
int rc;
-sx->outblock.authenticating = TRUE;
-rc = (au->info->clientcode)(au, sx, ob->command_timeout,
- sx->buffer, sizeof(sx->buffer));
-sx->outblock.authenticating = FALSE;
-DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+/* Set up globals for error messages */
+
+authenticator_name = au->drinst.name;
+driver_srcfile = au->drinst.srcfile;
+driver_srcline = au->drinst.srcline;
+
+ {
+ auth_info * ai = au->drinst.info;
+ sx->outblock.authenticating = TRUE;
+ rc = (ai->clientcode)(au, sx, ob->command_timeout,
+ 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 %s\n",
+ au->drinst.name, rc_names[rc]);
/* A temporary authentication failure must hold up delivery to
this host. After a permanent authentication failure, we carry on
{
case OK:
f.smtp_authenticated = TRUE; /* stops the outer loop */
- client_authenticator = au->name;
+ client_authenticator = au->drinst.name;
if (au->set_client_id)
client_authenticated_id = expand_string(au->set_client_id);
break;
/* 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->drinst.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->drinst.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
case CANCELLED:
if (*sx->buffer != 0)
log_write(0, LOG_MAIN, "%s authenticator cancelled "
- "authentication H=%s [%s] %s", au->name, host->name,
+ "authentication H=%s [%s] %s", au->drinst.name, host->name,
host->address, sx->buffer);
break;
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? */
for (bitnum = 0, au = auths;
!f.smtp_authenticated && au && bitnum < 16;
- bitnum++, au = au->next) if (authbits & BIT(bitnum))
- {
- if ( au->client_condition
- && !expand_check_condition(au->client_condition, au->name,
- US"client authenticator"))
+ bitnum++, au = au->drinst.next)
+ if (authbits & BIT(bitnum))
{
- DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
- au->name, "client_condition is false");
- continue;
- }
+ if ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->drinst.name,
+ US"client authenticator"))
+ {
+ DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+ au->drinst.name, "client_condition is false");
+ continue;
+ }
- /* Found data for a listed mechanism. Call its client entry. Set
- a flag in the outblock so that data is overwritten after sending so
- that reflections don't show it. */
+ /* Found data for a listed mechanism. Call its client entry. Set
+ a flag in the outblock so that data is overwritten after sending so
+ that reflections don't show it. */
- fail_reason = US"authentication attempt(s) failed";
+ fail_reason = US"authentication attempt(s) failed";
- if ((rc = try_authenticator(sx, au)) != OK)
- return rc;
- }
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
+ }
}
else
#endif
If one is found, attempt to authenticate by calling its client function.
*/
- for (auth_instance * au = auths; !f.smtp_authenticated && au; au = au->next)
+ for (auth_instance * au = auths; !f.smtp_authenticated && au;
+ au = au->drinst.next)
{
- uschar *p = names;
+ uschar * p = names;
if ( !au->client
|| ( au->client_condition
- && !expand_check_condition(au->client_condition, au->name,
+ && !expand_check_condition(au->client_condition, au->drinst.name,
US"client authenticator")))
{
DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
- au->name,
- (au->client)? "client_condition is false" :
- "not configured as a client");
+ au->drinst.name,
+ au->client ? "client_condition is false"
+ : "not configured as a client");
continue;
}
while (*p)
{
- int len = Ustrlen(au->public_name);
- int rc;
+ int len = Ustrlen(au->public_name), rc;
- while (isspace(*p)) p++;
+ Uskip_whitespace(&p);
if (strncmpic(au->public_name, p, len) != 0 ||
- (p[len] != 0 && !isspace(p[len])))
+ (p[len] && !isspace(p[len])))
{
- while (*p != 0 && !isspace(*p)) p++;
+ while (*p && !isspace(*p)) p++;
continue;
}
authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
#endif
+GET_OPTION("authenticated_sender");
if (ob->authenticated_sender)
{
uschar * new = expand_string(ob->authenticated_sender);
&& local_authenticated_sender)
{
string_format_nt(p, sizeof(sx->buffer) - (p-sx->buffer), " AUTH=%s",
- auth_xtextencode(local_authenticated_sender,
+ xtextencode(local_authenticated_sender,
Ustrlen(local_authenticated_sender)));
client_authenticated_sender = string_copy(local_authenticated_sender);
}
typedef struct smtp_compare_s
{
- uschar * current_sender_address;
+ const uschar * current_sender_address;
struct transport_instance * tblock;
} smtp_compare_t;
*/
static uschar *
-smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+smtp_local_identity(const uschar * sender, struct transport_instance * tblock)
{
+smtp_transport_options_block * ob = tblock->drinst.options_block;
address_item * addr1;
uschar * if1 = US"";
uschar * helo1 = US"";
#ifndef DISABLE_TLS
uschar * tlsc1 = US"";
#endif
-uschar * save_sender_address = sender_address;
+const uschar * save_sender_address = sender_address;
uschar * local_identity = NULL;
-smtp_transport_options_block * ob = SOB tblock->options_block;
sender_address = sender;
#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;
}
smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
unsigned flags)
{
-smtp_transport_options_block * ob = SOB tctx->tblock->options_block;
+smtp_transport_options_block * ob = tctx->tblock->drinst.options_block;
smtp_context * sx = tctx->smtp_context;
int cmd_count = 0;
int prev_cmd_count;
switch(sync_responses(sx, prev_cmd_count, 0))
{
- case 1: /* 2xx (only) => OK */
- case 3: sx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
- case 0: break; /* No 2xx or 5xx, but no probs */
+ case RESP_BIT_HAD_2XX: /* OK */
+ case RESP_HAD_2_AND_5: sx->good_RCPT = TRUE; /* OK & progress made */
+ case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
+ case RESP_NOERROR: break; /* No 2xx or 5xx, but no probs */
- case -5: errno = ERRNO_TLSFAILURE;
- return DEFER;
+ case RESP_EHLO_ERR_TLS:errno = ERRNO_TLSFAILURE;
+ return DEFER;
#ifndef DISABLE_PIPE_CONNECT
- case -4: /* non-2xx for pipelined banner or EHLO */
+ case RESP_EPIPE_EHLO_ERR: /* non-2xx for pipelined banner or EHLO */
#endif
- case -1: /* Timeout on RCPT */
- default: return ERROR; /* I/O error, or any MAIL/DATA error */
+ case RESP_RCPT_TIMEO: /* Timeout on RCPT */
+ default: return ERROR;/* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
if (!sx->pending_BDAT)
+#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 *
*************************************************/
int
smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
{
-smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
+smtp_transport_options_block * ob = sx->conn_args.tblock->drinst.options_block;
BOOL pass_message = FALSE;
uschar * message = NULL;
int yield = OK;
uschar * tls_errstr;
#endif
+/* Many lines of clearing individual elements of *sx that used to
+be here have been replaced by a full memset to zero (de41aff051).
+There are two callers, this file and verify.c . Now we only set
+up nonzero elements. */
+
sx->conn_args.ob = ob;
sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
sx->smtps = strcmpic(ob->protocol, US"smtps") == 0;
-/* sx->ok = FALSE; */
sx->send_rset = TRUE;
sx->send_quit = TRUE;
sx->setting_up = TRUE;
sx->esmtp = TRUE;
-/* sx->esmtp_sent = FALSE; */
-#ifdef SUPPORT_I18N
-/* sx->utf8_needed = FALSE; */
-#endif
sx->dsn_all_lasthop = TRUE;
#ifdef SUPPORT_DANE
-/* sx->conn_args.dane = FALSE; */
sx->dane_required =
verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
#endif
-#ifndef DISABLE_PIPE_CONNECT
-/* sx->early_pipe_active = sx->early_pipe_ok = FALSE; */
-/* sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0; */
-/* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */
-#endif
-if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 999999;
-if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
-/* sx->peer_offered = 0; */
-/* sx->avoid_option = 0; */
+if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0)
+ sx->max_mail = UNLIMITED_ADDRS;
+sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses);
sx->igquotstr = US"";
if (!sx->helo_data) sx->helo_data = ob->helo_data;
-#ifdef EXPERIMENTAL_DSN_INFO
-/* sx->smtp_greeting = NULL; */
-/* sx->helo_response = NULL; */
-#endif
smtp_command = US"initial connection";
-/* sx->buffer[0] = '\0'; */
/* Set up the buffer for reading SMTP response packets. */
sx->outblock.buffer = sx->outbuffer;
sx->outblock.buffersize = sizeof(sx->outbuffer);
sx->outblock.ptr = sx->outbuffer;
-/* sx->outblock.cmd_count = 0; */
-/* sx->outblock.authenticating = FALSE; */
-/* sx->outblock.conn_args = NULL; */
/* Reset the parameters of a TLS session. */
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");
-# 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
else
{
DEBUG(D_transport)
- debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+# ifdef SUPPORT_DANE
+ if (continue_proxy_dane != sx->conn_args.dane)
+ debug_printf(
+ "Closing proxied-TLS connection due to dane requirement mismatch\n");
+ else
+# endif
+ debug_printf("Closing proxied-TLS connection (SNI '%s') "
+ "due to SNI mismatch (transport requirement '%s')\n",
+ continue_proxy_sni, sni);
- 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;
f.continue_more = FALSE;
- continue_sequence = 1; /* Unfortunately, this process cannot affect success log
- which is done by delivery proc. Would have to pass this
- back through reporting pipe. */
+ continue_sequence = 1; /* Ensure proper logging of non-cont-conn */
}
}
#endif /*!DISABLE_TLS*/
if (!continue_hostname)
{
- if (sx->verify)
- HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
-
- /* Arrange to report to calling process this is a new connection */
-
- clearflag(sx->first_addr, af_cont_conn);
- setflag(sx->first_addr, af_new_conn);
+ if (sx->verify) HDEBUG(D_verify)
+ debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
/* Get the actual port the connection will use, into sx->conn_args.host */
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");
-# endif
- return rc;
- }
+ if ((rc = check_force_dane_conn(sx, ob)) != OK)
+ return rc;
}
else if (sx->dane_required)
{
FAIL, FALSE, &sx->delivery_start);
# ifndef DISABLE_EVENT
(void) event_raise(sx->conn_args.tblock->event_action,
- US"dane:fail", US"dane-required");
+ US"dane:fail", US"dane-required", NULL);
# endif
return FAIL;
}
+ else DEBUG(D_transport)
+ debug_printf("lack of DNSSEC traceability precludes DANE\n");
}
#endif /*DANE*/
sx->cctx.tls_ctx = NULL;
sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
#endif
sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
+ smtp_debug_cmd_log_init();
#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. */
+ GET_OPTION("helo_data");
if (sx->helo_data)
if (!(sx->helo_data = expand_string(sx->helo_data)))
if (sx->verify)
uschar * s;
lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes"
: sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL;
- s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer);
+ s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer, NULL);
if (s)
{
set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
#ifndef DISABLE_TLS
if (sx->smtps)
{
+ const uschar * s;
+
smtp_peer_options |= OPTION_TLS;
suppress_tls = FALSE;
ob->tls_tempfail_tryclear = FALSE;
smtp_command = US"SSL-on-connect";
+
+# ifndef DISABLE_TLS_RESUME
+ /* Having no EHLO response yet, cannot peek there for a servername to detect
+ an LB. Call this anyway, so that a dummy host_name_extract option value can
+ force resumption attempts. */
+
+ GET_OPTION("host_name_extract");
+ if (!(s = ob->host_name_extract)) s = US"never-LB";
+ ehlo_response_lbserver(sx, s);
+# endif
goto TLS_NEGOTIATE;
}
#endif
if ( (ob->hosts_require_auth || ob->hosts_try_auth)
&& f.smtp_in_early_pipe_no_auth)
{
- DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
+ DEBUG(D_transport)
+ debug_printf("may need to auth, so pipeline no further\n");
if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
goto SEND_FAILED;
- if (sync_responses(sx, 2, 0) != 0)
+ if (sync_responses(sx, 2, 0) != RESP_NOERROR)
{
HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
if (!sx->early_pipe_active)
#endif
{
+ const uschar * s;
+
sx->peer_offered = ehlo_response(sx->buffer,
OPTION_TLS /* others checked later */
#ifndef DISABLE_PIPE_CONNECT
)
#endif
);
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
{
ehlo_response_limits_read(sx);
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
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+ GET_OPTION("host_name_extract");
+ if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
+ ehlo_response_lbserver(sx, s);
#endif
}
}
}
-/* For continuing deliveries down the same channel, having re-exec'd the socket
+/* For continuing deliveries down the same channel, the socket
is the standard input; for a socket held open from verify it is recorded
in the cutthrough context block. Either way we don't need to redo EHLO here
(but may need to do so for TLS - see below).
-Set up the pointer to where subsequent commands will be left, for
-error messages. Note that smtp_peer_options will have been
-set from the command line if they were set in the process that passed the
-connection on. */
+Set up the pointer "smtp_command" to where subsequent commands will be left,
+for error messages. Other stuff was set up for us by the delivery process. */
/*XXX continue case needs to propagate DSN_INFO, prob. in deliver.c
-as the continue goes via transport_pass_socket() and doublefork and exec.
+as the continue goes via pass-fd to the delivery process.
It does not wait. Unclear how we keep separate host's responses
separate - we could match up by host ip+port as a bodge. */
{
sx->cctx.sock = 0; /* stdin */
sx->cctx.tls_ctx = NULL;
- smtp_port_for_connect(sx->conn_args.host, sx->port); /* Record the port that was used */
+ smtp_port_for_connect(sx->conn_args.host, sx->port); /* Record the port that was used */
}
- sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
smtp_command = big_buffer;
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
sx->peer_offered = smtp_peer_options;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
/* Limits passed by cmdline over exec. */
ehlo_limits_apply(sx,
sx->peer_limit_mail = continue_limit_mail,
sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
continue_proxy_cipher ? "proxied" : "verify conn with");
+
+ tls_out.certificate_verified = !!(continue_flags & CTF_CV);
+#ifdef SUPPORT_DANE
+ tls_out.dane_verified = !!(continue_flags & CTF_DV);
+#endif
+#ifndef DISABLE_TLS_RESUME
+ if (continue_flags & CTF_TR) tls_out.resumption |= RESUME_USED;
+#endif
return OK;
}
HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
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) != RESP_NOERROR)
+ {
+ 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
sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
# ifndef DISABLE_EVENT
(void) event_raise(sx->conn_args.tblock->event_action,
- US"dane:fail", US"validation-failure"); /* could do with better detail */
+ US"dane:fail", US"validation-failure", NULL); /* could do with better detail */
# endif
}
# endif
{
uschar * greeting_cmd;
- if (!sx->helo_data && !(sx->helo_data = expand_string(ob->helo_data)))
+ if (!sx->helo_data)
{
- uschar *message = string_sprintf("failed to expand helo_data: %s",
- expand_string_message);
- set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
- yield = DEFER;
- goto SEND_QUIT;
+ GET_OPTION("helo_data");
+ if (!(sx->helo_data = expand_string(ob->helo_data)))
+ {
+ uschar *message = string_sprintf("failed to expand helo_data: %s",
+ expand_string_message);
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
}
#ifndef DISABLE_PIPE_CONNECT
#ifdef EXPERIMMENTAL_ESMTP_LIMITS
/* As we are about to send another EHLO, forget any LIMITS received so far. */
sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = 0;
- if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) sx->max_mail = 999999;
- if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+ if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0)
+ sx->max_mail = UNLIMITED_ADDRS;
+ sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses);
sx->single_rcpt_domain = FALSE;
#endif
(void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
smtp_peer_options & OPTION_TLS
? US"validation-failure" /* could do with better detail */
- : US"starttls-not-supported");
+ : US"starttls-not-supported",
+ NULL);
# endif
goto TLS_FAILED;
}
sx->ehlo_resp.crypted_features = sx->peer_offered;
#endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
{
ehlo_response_limits_read(sx);
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
/* If the transport sets a downconversion mode it overrides any set by ACL
for the message. */
+ GET_OPTION("utf8_downconvert");
if ((s = ob->utf8_downconvert))
{
if (!(s = expand_string(s)))
sx->cctx.sock = -1;
#ifndef DISABLE_EVENT
-(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL);
+(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL, NULL);
#endif
continue_transport = NULL;
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)
{
-/*
-Return:
- 0 good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
- -1 MAIL response error
- -2 any non-MAIL read i/o error
- -3 non-MAIL response timeout
- -4 internal error; channel still usable
- -5 transmit failed
- */
+/* Send MAIL FROM and RCPT TO commands.
+See sw_mrc_t definition for return codes.
+*/
-int
+sw_mrc_t
smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
{
address_item * addr;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
address_item * restart_addr = NULL;
#endif
int address_count, pipe_limit;
if (build_mailcmd_options(sx, sx->first_addr) != OK)
{
*yield = ERROR;
- return -4;
+ return sw_mrc_bad_internal;
}
/* From here until we send the DATA command, we can make use of PIPELINING
sx->pending_MAIL = TRUE; /* The block starts with MAIL */
{
- uschar * s = sx->from_addr;
+ const uschar * s = sx->from_addr;
#ifdef SUPPORT_I18N
uschar * errstr = NULL;
{
set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE, &sx->delivery_start);
*yield = ERROR;
- return -4;
+ return sw_mrc_bad_internal;
}
setflag(sx->addrlist, af_utf8_downcvt);
}
switch(rc)
{
case -1: /* Transmission error */
- return -5;
+ return sw_mrc_bad_mail;
case +1: /* Cmd was sent */
if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
errno = ERRNO_MAIL4XX;
sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
- return -1;
+ return sw_mrc_bad_mail;
}
sx->pending_MAIL = FALSE;
break;
{
int cmds_sent;
BOOL no_flush;
- uschar * rcpt_addr;
+ const uschar * rcpt_addr;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
if ( sx->single_rcpt_domain /* restriction on domains */
&& address_count > 0 /* not first being sent */
&& Ustrcmp(addr->domain, sx->first_addr->domain) != 0 /* dom diff from first */
{
/*XXX could we use a per-address errstr here? Not fail the whole send? */
errno = ERRNO_EXPANDFAIL;
- return -5; /*XXX too harsh? */
+ return sw_mrc_tx_fail; /*XXX too harsh? */
}
#endif
cmds_sent = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
"RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
- if (cmds_sent < 0) return -5;
+ if (cmds_sent < 0) return sw_mrc_tx_fail;
if (cmds_sent > 0)
{
switch(sync_responses(sx, cmds_sent, 0))
{
- case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
+ case RESP_HAD_2_AND_5: sx->ok = TRUE; /* OK & progress made */
+ case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
break;
- case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ case RESP_BIT_HAD_2XX: sx->ok = TRUE; /* OK, but if LMTP, */
if (!sx->lmtp) /* can't tell about progress yet */
sx->completed_addr = TRUE;
- case 0: /* No 2xx or 5xx, but no probs */
+ case RESP_NOERROR: /* No 2xx or 5xx, but no probs */
/* If any RCPT got a 452 response then next_addr has been updated
for restarting with a new MAIL on the same connection. Send no more
RCPTs for this MAIL. */
DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
sx->RCPT_452 = FALSE;
/* sx->next_addr has been reset for fast_retry */
- return 0;
+ return sw_mrc_ok;
}
break;
- case -1: return -3; /* Timeout on RCPT */
- case -2: return -2; /* non-MAIL read i/o error */
- default: return -1; /* any MAIL error */
+ case RESP_RCPT_TIMEO: return sw_mrc_nonmail_read_timeo;
+ case RESP_RCPT_ERROR: return sw_mrc_bad_read;
+ default: return sw_mrc_bad_mail; /* any MAIL error */
#ifndef DISABLE_PIPE_CONNECT
- case -4: return -1; /* non-2xx for pipelined banner or EHLO */
- case -5: return -1; /* TLS first-read error */
+ case RESP_EPIPE_EHLO_ERR: return sw_mrc_bad_mail; /* non-2xx for pipelined banner or EHLO */
+ case RESP_EHLO_ERR_TLS: return sw_mrc_bad_mail; /* TLS first-read error */
#endif
}
}
} /* Loop for next address */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
sx->next_addr = restart_addr ? restart_addr : addr;
#else
sx->next_addr = addr;
#endif
-return 0;
+return sw_mrc_ok;
}
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}};
int rc, i;
BOOL send_tls_shutdown = TRUE;
+acl_level++;
close(pfd[1]);
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
{
for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
- /* Handle outbound data. We cannot combine payload and the TLS-close
- due to the limitations of the (pipe) channel feeding us. Maybe use a unix-domain
- socket? */
+ /* Handle outbound data. We cannot yet combine payload and the TLS-close
+ due to the limitations of the (pipe) channel feeding us. Could we use
+ a poll/POLLRDHUP? Would that need an extra poll call after every read
+ (likely not worth it), or (best case) could we get POLLIN+POLLRDHUP for
+ the final data blob? */
+
if (p[1].revents & POLLIN)
if ((rc = read(pfd[0], buf, bsize)) <= 0)
{
uschar *interface, transport_instance *tblock,
BOOL *message_defer, BOOL suppress_tls)
{
-smtp_transport_options_block * ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = tblock->drinst.options_block;
+const uschar * trname = tblock->drinst.name;
int yield = OK;
int save_errno;
int rc;
uschar *message = NULL;
-uschar new_message_id[MESSAGE_ID_LENGTH + 1];
-smtp_context * sx = store_get(sizeof(*sx), TRUE); /* tainted, for the data buffers */
+smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted, for the data buffers */
BOOL pass_message = FALSE;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
BOOL mail_limit = FALSE;
#endif
#ifdef SUPPORT_DANE
BOOL dane_held;
#endif
-BOOL tcw_done = FALSE, tcw = FALSE;
+BOOL tcw_done = FALSE, tcw = FALSE, passback_conn = FALSE;
*message_defer = FALSE;
+continue_next_id[0] = '\0';
memset(sx, 0, sizeof(*sx));
sx->addrlist = addrlist;
sx->conn_args.host = host;
-sx->conn_args.host_af = host_af,
+sx->conn_args.host_af = host_af;
sx->port = defport;
sx->conn_args.interface = interface;
sx->helo_data = NULL;
sx->conn_args.tblock = tblock;
-/* sx->verify = FALSE; */
+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,
- string_sprintf("%.50s transport", tblock->name), NULL))
+ tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist,
+ string_sprintf("%.50s transport filter", trname), NULL))
{
set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
FALSE, &sx->delivery_start);
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");
}
}
switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
{
- case 0: break;
- case -1: case -2: goto RESPONSE_FAILED;
- case -3: goto END_OFF;
- case -4: goto SEND_QUIT;
- default: goto SEND_FAILED;
+ case sw_mrc_ok: break;
+ case sw_mrc_bad_mail: goto RESPONSE_FAILED;
+ case sw_mrc_bad_read: goto RESPONSE_FAILED;
+ case sw_mrc_nonmail_read_timeo: goto END_OFF;
+ case sw_mrc_bad_internal: goto SEND_QUIT;
+ default: goto SEND_FAILED;
}
/* If we are an MUA wrapper, abort if any RCPTs were rejected, either
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");
if (count < 0) goto SEND_FAILED;
+
switch(sync_responses(sx, count, sx->ok ? +1 : -1))
{
- case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
- break;
+ case RESP_HAD_2_AND_5: sx->ok = TRUE; /* OK & progress made */
+ case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
+ break;
- case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
- if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
- case 0: break; /* No 2xx or 5xx, but no probs */
+ case RESP_BIT_HAD_2XX: sx->ok = TRUE; /* OK, but if LMTP, */
+ if (!sx->lmtp) /* can't tell about progress yet */
+ sx->completed_addr = TRUE;
+ case RESP_NOERROR: break; /* No 2xx or 5xx, but no probs */
- case -1: goto END_OFF; /* Timeout on RCPT */
+ case RESP_RCPT_TIMEO: goto END_OFF;
#ifndef DISABLE_PIPE_CONNECT
case -5: /* TLS first-read error */
(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
{
+ misc_module_info * mi;
+
# ifdef MEASURE_TIMING
struct timeval t0;
gettimeofday(&t0, NULL);
# endif
- dkim_exim_sign_init();
-# ifdef EXPERIMENTAL_ARC
+
+ if ((mi = misc_mod_find(US"dkim", NULL)))
{
- uschar * s = ob->arc_sign;
- if (s)
+ typedef void (*fn_t)(void);
+ (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) ();
+
+# ifdef EXPERIMENTAL_ARC
{
- if (!(ob->dkim.arc_signspec = s = expand_string(s)))
- {
- if (!f.expand_string_forcedfail)
+ uschar * s = ob->arc_sign;
+ if (s)
+ if (!(ob->dkim.arc_signspec = s = expand_string(s)))
{
- message = US"failed to expand arc_sign";
- sx->ok = FALSE;
- goto SEND_FAILED;
+ if (!f.expand_string_forcedfail)
+ {
+ message = US"failed to expand arc_sign";
+ sx->ok = FALSE;
+ goto SEND_FAILED;
+ }
+ }
+ else if (*s && (mi = misc_mod_find(US"arc", NULL)))
+ {
+ typedef void (*fn_t)(void);
+ (((fn_t *) mi->functions)[ARC_SIGN_INIT]) ();
+
+ /* Ask dkim code to hash the body for ARC */
+ ob->dkim.force_bodyhash = TRUE;
}
- }
- else if (*s)
- {
- /* Ask dkim code to hash the body for ARC */
- (void) arc_ams_setup_sign_bodyhash();
- ob->dkim.force_bodyhash = TRUE;
- }
}
+# endif /*ARC*/
}
-# endif
# ifdef MEASURE_TIMING
report_time_since(&t0, US"dkim_exim_sign_init (delta)");
# endif
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
)
&&
#endif
- transport_check_waiting(tblock->name, host->name,
- tblock->connection_max_messages, new_message_id,
+ transport_check_waiting(trname, host->name,
+ tblock->connection_max_messages, continue_next_id,
(oicf)smtp_are_same_identities, (void*)&t_compare);
if (!tcw)
{
}
#ifndef DISABLE_DKIM
- sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
+ {
+ misc_module_info * mi = misc_mod_find(US"dkim", NULL);
+ typedef BOOL (*fn_t)(transport_ctx *, struct ob_dkim *, const uschar **);
+
+ sx->ok = mi
+ ? (((fn_t *) mi->functions)[DKIM_TRANSPORT_WRITE])
+ (&tctx, &ob->dkim, CUSS &message)
+ : transport_write_message(&tctx, 0);
+ }
#else
sx->ok = transport_write_message(&tctx, 0);
#endif
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))
{
- case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
- break;
+ case RESP_HAD_2_AND_5: sx->ok = TRUE; /* OK & progress made */
+ case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
+ break;
- case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
- if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
- case 0: break; /* No 2xx or 5xx, but no probs */
+ case RESP_BIT_HAD_2XX: sx->ok = TRUE; /* OK, but if LMTP, */
+ if (!sx->lmtp) /* can't tell about progress yet */
+ sx->completed_addr = TRUE;
+ case RESP_NOERROR: break; /* No 2xx or 5xx, but no probs */
- case -1: goto END_OFF; /* Timeout on RCPT */
+ case RESP_RCPT_TIMEO: goto END_OFF; /* Timeout on RCPT */
#ifndef DISABLE_PIPE_CONNECT
- case -5: /* TLS first-read error */
- case -4: HDEBUG(D_transport)
+ case RESP_EHLO_ERR_TLS: /* TLS first-read error */
+ case RESP_EPIPE_EHLO_ERR: HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
#endif
- default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
+ default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
}
}
addr->delivery_time = delivery_time;
addr->special_action = flag;
addr->message = conf;
+ if (continue_sequence > 1)
+ { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+ else
+ { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
if (tcp_out_fastopen)
{
#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
write error, as it may prove possible to update the spool file later. */
if (testflag(addr, af_homonym))
- sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+ sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, trname);
else
sprintf(CS sx->buffer, "%.500s\n", addr->unique);
if (addr->transport_return == OK)
{
if (testflag(addr, af_homonym))
- sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+ sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, trname);
else
sprintf(CS sx->buffer, "%.500s\n", addr->unique);
break;
case ERRNO_SMTPCLOSED:
- message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+ /* If the peer closed the TCP connection after end-of-data, but before
+ we could send QUIT, do TLS close, etc - it is a message error.
+ If not, and all the recipients have been dealt with, call such a close
+ no error at all; each address_item should have a suitable result already
+ (2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL).
+ Otherwise, it is a non-message error. */
+
+ if (!(message_error = Ustrncmp(smtp_command,"end ",4) == 0))
+ {
+ address_item * addr;
+ for (addr = sx->addrlist; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ break;
+ if (!addr) /* all rcpts fates determined */
+ {
+ log_write(0, LOG_MAIN, "peer close after all rcpt responses;"
+ " converting i/o-error to no-error");
+ sx->ok = TRUE;
+ goto happy;
+ }
+ }
break;
#ifndef DISABLE_DKIM
# 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
break;
}
- /* Handle the cases that are treated as message errors. These are:
+ /* Handle the cases that are treated as message errors (as opposed to
+ host-errors). These are:
(a) negative response or timeout after MAIL
(b) negative response after DATA
rest of them. Also, it is desirable to send more than one message down the SMTP
connection if there are several waiting, provided we haven't already sent so
many as to hit the configured limit. The function transport_check_waiting looks
-for a waiting message and returns its id. Then transport_pass_socket tries to
-set up a continued delivery by passing the socket on to another process. The
-variable send_rset is FALSE if a message has just been successfully transferred.
+for a waiting message and returns its id. We pass it back to the delivery
+process via the reporting pipe. The variable send_rset is FALSE if a message has
+just been successfully transferred.
If we are already sending down a continued channel, there may be further
addresses not yet delivered that are aimed at the same host, but which have not
can), so we do not pass such a connection on if the host matches
hosts_nopass_tls. */
+happy:
+
DEBUG(D_transport)
debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
"yield=%d first_address is %sNULL\n", sx->ok, sx->send_quit,
sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : "");
if (sx->completed_addr && sx->ok && sx->send_quit)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
if (mail_limit = continue_sequence >= sx->max_mail)
{
DEBUG(D_transport)
debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail);
+ /* We will close the smtp session and connection, and clear
+ continue_hostname. Then if there are further addrs for the message we will
+ loop to the top of this function and make a fresh connection. Any further
+ message found in the wait-tpt hintsdb would then do a pass-fd over the
+ transport reporting pipe to get the connection fd back to the delivery
+ process. */
}
else
#endif
smtp_compare_t t_compare =
{.tblock = tblock, .current_sender_address = sender_address};
- if ( sx->first_addr /* more addrs for this message */
- || f.continue_more /* more addrs for continued-host */
- || tcw_done && tcw /* more messages for host */
+ if ( sx->first_addr /* more addrs for this message */
+ || f.continue_more /* more addrs for continued-host */
+ || tcw_done && tcw /* more messages for host */
|| (
#ifndef DISABLE_TLS
( tls_out.active.sock < 0 && !continue_proxy_cipher
)
&&
#endif
- transport_check_waiting(tblock->name, host->name,
- sx->max_mail, new_message_id,
+ transport_check_waiting(trname, host->name,
+ sx->max_mail, continue_next_id,
(oicf)smtp_are_same_identities, (void*)&t_compare)
) )
{
#ifndef DISABLE_TLS
int pfd[2];
#endif
- int socket_fd = sx->cctx.sock;
-
+ continue_fd = sx->cctx.sock;
if (sx->first_addr) /* More addresses still to be sent */
{ /* for this message */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
/* Any that we marked as skipped, reset to do now */
for (address_item * a = sx->first_addr; a; a = a->next)
if (a->transport_return == SKIP)
a->transport_return = PENDING_DEFER;
#endif
continue_sequence++; /* for consistency */
- clearflag(sx->first_addr, af_new_conn);
- setflag(sx->first_addr, af_cont_conn); /* Causes * in logging */
pipelining_active = sx->pipelining_used; /* was cleared at DATA */
goto SEND_MESSAGE;
}
- /* Unless caller said it already has more messages listed for this host,
- pass the connection on to a new Exim process (below, the call to
- transport_pass_socket). If the caller has more ready, just return with
- the connection still open. */
+ /* If there is a next-message-id from the wait-transport hintsdb,
+ pretend caller said it has further message for us. Note that we lose
+ the TLS session (below), and that our caller will pass back the id to
+ the delivery process. */
+
+ if (f.continue_more)
+ {
+ passback_conn = TRUE;
+ continue_next_id[0] = '\0';
+ }
+ else if (*continue_next_id)
+ passback_conn = f.continue_more = TRUE;
#ifndef DISABLE_TLS
+ /* If we will be returning with the connection still open and have a TLS
+ endpoint, shut down TLS if we must, or if this is a first-time passback
+ fork a proxy process with the TLS state. */
+
if (tls_out.active.sock >= 0)
- if ( f.continue_more
- || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
+ {
+ if ( (continue_hostname || passback_conn)
+ && verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK
+ )
{
- /* Before passing the socket on, or returning to caller with it still
- 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. */
-
- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
- sx->send_tlsclose = FALSE;
- sx->cctx.tls_ctx = NULL;
- tls_out.active.sock = -1;
- smtp_peer_options = smtp_peer_options_wrap;
- sx->ok = !sx->smtps
- && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
- >= 0
- && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
- '2', ob->command_timeout);
-
- if (sx->ok && f.continue_more)
- goto TIDYUP; /* More addresses for another run */
+ /* 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.
+ 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.
+ XXX TODO */
+
+ 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;
+ smtp_peer_options = smtp_peer_options_wrap;
+ sx->ok = !sx->smtps
+ && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n",sx->helo_data)
+ >= 0
+ && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+ '2', ob->command_timeout);
}
- else
+ else if (passback_conn)
{
/* Set up a pipe for proxying TLS for the new transport process */
smtp_peer_options |= OPTION_TLS;
if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
- socket_fd = pfd[1];
- else
- set_errno(sx->first_addr, errno, US"internal allocation problem",
- DEFER, FALSE, host,
-# ifdef EXPERIMENTAL_DSN_INFO
- sx->smtp_greeting, sx->helo_response,
-# endif
- &sx->delivery_start);
- }
- else
-#endif
- if (f.continue_more)
- goto TIDYUP; /* More addresses for another run */
-
- /* If the socket is successfully passed, we mustn't send QUIT (or
- indeed anything!) from here. */
-
- /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
- propagate it from the initial
- */
- if (sx->ok && transport_pass_socket(tblock->name, host->name,
- host->address, new_message_id, socket_fd
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
- , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom
-#endif
- ))
- {
- sx->send_quit = FALSE;
-
- /* We have passed the client socket to a fresh transport process.
- If TLS is still active, we need to proxy it for the transport we
- just passed the baton to. Fork a child to to do it, and return to
- get logging done asap. Which way to place the work makes assumptions
- about post-fork prioritisation which may not hold on all platforms. */
-#ifndef DISABLE_TLS
- if (tls_out.active.sock >= 0)
- {
- int pid = exim_fork(US"tls-proxy-interproc");
- if (pid == 0) /* child; fork again to disconnect totally */
{
- /* does not return */
- smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
- ob->command_timeout);
- }
+ int pid = exim_fork(US"tls-proxy-interproc");
+ if (pid == 0) /* child; fork again to disconnect totally */
+ {
+ /* does not return */
+ smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer),
+ pfd, ob->command_timeout, host->name);
+ }
+
+ if (pid < 0)
+ log_write(0, LOG_PANIC_DIE, "fork failed");
- if (pid > 0) /* parent */
- {
close(pfd[0]);
+ continue_fd = pfd[1];
/* tidy the inter-proc to disconn the proxy proc */
waitpid(pid, NULL, 0);
tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
sx->cctx.tls_ctx = NULL;
(void)close(sx->cctx.sock);
sx->cctx.sock = -1;
- continue_transport = NULL;
- continue_hostname = NULL;
- goto TIDYUP;
+
+ continue_proxy_cipher = tls_out.cipher;
+ continue_proxy_sni = tls_out.sni;
+# ifdef SUPPORT_DANE
+ continue_proxy_dane = tls_out.sni && tls_out.dane_verified;
+# endif
}
- log_write(0, LOG_PANIC_DIE, "fork failed");
+ else
+ set_errno(sx->first_addr, errno, US"internal allocation problem",
+ DEFER, FALSE, host,
+# ifdef EXPERIMENTAL_DSN_INFO
+ sx->smtp_greeting, sx->helo_response,
+# endif
+ &sx->delivery_start);
}
-#endif
}
+#endif /*DISABLE_TLS*/
+
+ /* If a connection re-use is possible, arrange to pass back all the info
+ about it so that further forks of the delivery process see it. */
+
+ if (passback_conn)
+ {
+ continue_transport = transport_name;
+ continue_hostname = host->name;
+ continue_host_address = host->address;
+ }
+ else
+ continue_hostname = NULL;
+
+ if (sx->ok && f.continue_more) /* More addresses for another run; */
+ goto TIDYUP; /* skip the channel closedown */
}
- /* If RSET failed and there are addresses left, they get deferred. */
+ /* If RSET failed and there are addresses left, they get deferred.
+ Do not pass back a next-id or conn info. */
+
else
set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host,
#ifdef EXPERIMENTAL_DSN_INFO
{ /* 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
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
(void)close(sx->cctx.sock);
sx->cctx.sock = -1;
-continue_transport = NULL;
continue_hostname = NULL;
+continue_next_id[0] = '\0';
#ifndef DISABLE_EVENT
-(void) event_raise(tblock->event_action, US"tcp:close", NULL);
+(void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL);
#endif
#ifdef SUPPORT_DANE
to get the domain string for SNI */
sx->first_addr = a;
- clearflag(a, af_cont_conn);
- setflag(a, af_new_conn); /* clear * from logging */
DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
}
}
}
#endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
if (mail_limit && sx->first_addr)
{
/* Reset the sequence count since we closed the connection. This is flagged
- on the pipe back to the delivery process so that a non-continued-conn delivery
- is logged. */
+ on the pipe back to the delivery process so that it can reset it's count.
+ Also set flags on the addr so that a non-continued-conn delivery is logged. */
continue_sequence = 1; /* for consistency */
- clearflag(sx->first_addr, af_cont_conn);
- setflag(sx->first_addr, af_new_conn); /* clear * from logging */
- goto REPEAT_CONN;
+ goto REPEAT_CONN; /* open a fresh connection */
}
#endif
-return yield;
+OUT:
+ smtp_debug_cmd_report();
+ return yield;
TIDYUP:
#ifdef SUPPORT_DANE
-if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
- if (a->transport_return == DANE)
- a->transport_return = PENDING_DEFER;
+ if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
+ if (a->transport_return == DANE)
+ a->transport_return = PENDING_DEFER;
#endif
-return yield;
+ goto OUT;
}
*/
void
-smtp_transport_closedown(transport_instance *tblock)
+smtp_transport_closedown(transport_instance * tblock)
{
-smtp_transport_options_block * ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = tblock->drinst.options_block;
client_conn_ctx cctx;
-smtp_context sx;
+smtp_context sx = {0};
uschar buffer[256];
uschar inbuffer[4096];
uschar outbuffer[16];
*/
static address_item *
-prepare_addresses(address_item *addrlist, host_item *host)
+prepare_addresses(address_item * addrlist, host_item * host)
{
-address_item *first_addr = NULL;
+address_item * first_addr = NULL;
for (address_item * addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == DEFER)
{
if (!first_addr) first_addr = addr;
addr->transport_return = PENDING_DEFER;
addr->basic_errno = 0;
- addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+ addr->more_errno = host->mx >= 0 ? 'M' : 'A';
addr->message = NULL;
#ifndef DISABLE_TLS
addr->cipher = NULL;
BOOL
smtp_transport_entry(
- transport_instance *tblock, /* data for this instantiation */
- address_item *addrlist) /* addresses we are working on */
+ transport_instance * tblock, /* data for this instantiation */
+ address_item * addrlist) /* addresses we are working on */
{
+smtp_transport_options_block * ob = tblock->drinst.options_block;
+const uschar * trname = tblock->drinst.name;
int defport;
-int hosts_defer = 0;
-int hosts_fail = 0;
-int hosts_looked_up = 0;
-int hosts_retry = 0;
-int hosts_serial = 0;
-int hosts_total = 0;
-int total_hosts_tried = 0;
+int hosts_defer = 0, hosts_fail = 0, hosts_looked_up = 0;
+int hosts_retry = 0, hosts_serial = 0, hosts_total = 0, total_hosts_tried = 0;
BOOL expired = TRUE;
-uschar *expanded_hosts = NULL;
-uschar *pistring;
-uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob = SOB tblock->options_block;
-host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+uschar * expanded_hosts = NULL, * pistring;
+uschar * tid = string_sprintf("%s transport", trname);
+host_item * hostlist = addrlist->host_list, * host = NULL;
DEBUG(D_transport)
{
- debug_printf("%s transport entered\n", tblock->name);
+ debug_printf("%s transport entered\n", trname);
for (address_item * addr = addrlist; addr; addr = addr->next)
debug_printf(" %s\n", addr->address);
if (hostlist)
addr->transport_return = PENDING_DEFER;
set_errno_nohost(addrlist, ERRNO_SMTPFORMAT,
- US"message has lines too long for transport", FAIL, TRUE, &now);
+ string_sprintf("message has lines too long for transport "
+ "(received %d, limit %d)",
+ max_received_linelength, ob->message_linelength_limit),
+ FAIL, TRUE, &now);
goto END_TRANSPORT;
}
queue_smtp or a 2-stage queue run. This gets unset for certain
kinds of error, typically those that are specific to the message. */
-update_waiting = TRUE;
+update_waiting = TRUE;
/* If a host list is not defined for the addresses - they must all have the
same one in order to be passed to a single transport - or if the transport has
if (!ob->hosts)
{
addrlist->message = string_sprintf("%s transport called with no hosts set",
- tblock->name);
+ trname);
addrlist->transport_return = PANIC;
return FALSE; /* Only top address has status */
}
if (!(expanded_hosts = expand_string(s)))
{
addrlist->message = string_sprintf("failed to expand list of hosts "
- "\"%s\" in %s transport: %s", s, tblock->name, expand_string_message);
+ "\"%s\" in %s transport: %s", s, trname, expand_string_message);
addrlist->transport_return = f.search_find_defer ? DEFER : PANIC;
return FALSE; /* Only top address has status */
}
else
if (ob->hosts_randomize) s = expanded_hosts = string_copy(s);
- if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted host list '%s' from '%s' in transport %s", s, ob->hosts, tblock->name))
+ if (is_tainted(s))
{
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "attempt to use tainted host list '%s' from '%s' in transport %s",
+ s, ob->hosts, trname);
/* Avoid leaking info to an attacker */
addrlist->message = US"internal configuration error";
addrlist->transport_return = PANIC;
if (!hostlist)
{
addrlist->message =
- string_sprintf("%s transport has empty hosts setting", tblock->name);
+ string_sprintf("%s transport has empty hosts setting", trname);
addrlist->transport_return = PANIC;
return FALSE; /* Only top address has status */
}
&& total_hosts_tried < ob->hosts_max_try_hardlimit;
host = nexthost)
{
- int rc;
- int host_af;
- BOOL host_is_expired = FALSE;
- BOOL message_defer = FALSE;
- BOOL some_deferred = FALSE;
- address_item *first_addr = NULL;
- uschar *interface = NULL;
- uschar *retry_host_key = NULL;
- uschar *retry_message_key = NULL;
- uschar *serialize_key = NULL;
+ int rc, host_af;
+ BOOL host_is_expired = FALSE, message_defer = FALSE, some_deferred = FALSE;
+ address_item * first_addr = NULL;
+ uschar * interface = NULL;
+ const uschar * retry_host_key = NULL, * 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
{
addr->basic_errno = ERRNO_HOST_IS_LOCAL;
addr->message = string_sprintf("%s transport found host %s to be "
- "local", tblock->name, host->name);
+ "local", trname, host->name);
}
goto END_TRANSPORT;
}
result of the lookup. Set expired FALSE, to save the outer loop executing
twice. */
- if (continue_hostname)
+ if (continue_sequence > 1)
if ( Ustrcmp(continue_hostname, host->name) != 0
|| Ustrcmp(continue_host_address, host->address) != 0
)
host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
{
- uschar * s = ob->interface;
- if (s && *s)
+ uschar * s;
+ GET_OPTION("interface");
+ if ((s = ob->interface) && *s)
{
if (!smtp_get_interface(s, host_af, addrlist, &interface, tid))
return FALSE;
+
+ if (continue_sequence > 1)
+ {
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
+ const uschar * local_ip_addr;
+
+ /* Assume the connection is on fd 0 */
+ if (getsockname(0, (struct sockaddr *) &interface_sock, &size) < 0)
+ {
+ DEBUG(D_transport)
+ debug_printf_indent("failed getsockname: %s\n", strerror(errno));
+ return FALSE;
+ }
+ local_ip_addr = host_ntoa(-1, &interface_sock, NULL, &sending_port);
+ if (Ustrcmp(interface, local_ip_addr) != 0)
+ {
+ DEBUG(D_transport) debug_printf_indent(
+ "tpt interface option mismatch with continued-connection\n");
+ /* Close the conn and recheck retry info */
+ continue_host_tried = FALSE;
+ break;
+ }
+ }
+
pistring = string_sprintf("%s/%s", pistring, interface);
}
}
If either of these retry records are actually read, the keys used are
returned to save recomputing them later. */
- if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+ if (exp_bool(addrlist, US"transport", trname, D_transport,
US"retry_include_ip_address", ob->retry_include_ip_address,
ob->expand_retry_include_ip_address, &incl_ip) != OK)
continue; /* with next host */
out the result of previous attempts, and finding the first address that
is still to be delivered. */
- first_addr = prepare_addresses(addrlist, host);
+ if (!(first_addr = prepare_addresses(addrlist, host)))
+ {
+ /* Obscure situation; at least one case (bug 3059, fixed) where
+ a previous host try returned DEFER, but having moved all
+ recipients away from DEFER (the waiting-to-be-done state). */
+ DEBUG(D_transport) debug_printf("no pending recipients\n");
+ goto END_TRANSPORT;
+ }
DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
message_id, host->name, host->address, addrlist->address,
DEBUG(D_transport)
{
debug_printf("*** delivery by %s transport bypassed by -N option\n"
- "*** host and remaining hosts:\n", tblock->name);
+ "*** host and remaining hosts:\n", trname);
for (host_item * host2 = host; host2; host2 = host2->next)
debug_printf(" %s [%s]\n", host2->name,
host2->address ? host2->address : US"unset");
{
host_item * thost;
/* Make a copy of the host if it is local to this invocation
- of the transport. */
+ of the transport. */
if (expanded_hosts)
{
- thost = store_get(sizeof(host_item), FALSE);
+ thost = store_get(sizeof(host_item), GET_UNTAINTED);
*thost = *host;
thost->name = string_copy(host->name);
thost->address = string_copy(host->address);
if (!retry_host_key)
{
BOOL incl_ip;
- if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+ if (exp_bool(addrlist, US"transport", trname, D_transport,
US"retry_include_ip_address", ob->retry_include_ip_address,
ob->expand_retry_include_ip_address, &incl_ip) != OK)
incl_ip = TRUE; /* error; use most-specific retry record */
- retry_host_key = incl_ip
- ? string_sprintf("T:%S:%s%s", host->name, host->address, pistring)
- : string_sprintf("T:%S%s", host->name, pistring);
+ retry_host_key = retry_host_key_build(host, incl_ip, pistring);
}
/* If a delivery of another message over an existing SMTP connection
if (!retry_message_key)
{
BOOL incl_ip;
- if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+ if (exp_bool(addrlist, US"transport", trname, D_transport,
US"retry_include_ip_address", ob->retry_include_ip_address,
ob->expand_retry_include_ip_address, &incl_ip) != OK)
incl_ip = TRUE; /* error; use most-specific retry record */
- retry_message_key = incl_ip
- ? string_sprintf("T:%S:%s%s:%s", host->name, host->address, pistring,
- message_id)
- : string_sprintf("T:%S%s:%s", host->name, pistring, message_id);
+ retry_message_key = string_sprintf("%s:%s",
+ retry_host_key_build(host, incl_ip, pistring), message_id);
}
retry_add_item(addrlist, retry_message_key,
rf_message | rf_host | delete_flag);
if (rc == OK)
for (address_item * addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == DEFER)
- {
- some_deferred = TRUE;
- break;
- }
+ { some_deferred = TRUE; break; }
/* If no addresses deferred or the result was ERROR, return. We do this for
ERROR because a failing filter set-up or add_headers expansion is likely to
fail for any host we try. */
if (rc == ERROR || (rc == OK && !some_deferred))
- {
- DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
- return TRUE; /* Each address has its status */
- }
+ goto END_TRANSPORT;
/* If the result was DEFER or some individual addresses deferred, let
the loop run to try other hosts with the deferred addresses, except for the
case when we were trying to deliver down an existing channel and failed.
Don't try any other hosts in this case. */
- if (continue_hostname) break;
+ if (continue_sequence > 1) break;
/* If the whole delivery, or some individual addresses, were deferred and
there are more hosts that could be tried, do not count this host towards
if ((rc == DEFER || some_deferred) && nexthost)
{
BOOL timedout;
- retry_config *retry = retry_find_config(host->name, NULL, 0, 0);
+ retry_config * retry = retry_find_config(host->name, NULL, 0, 0);
if (retry && retry->rules)
{
for routing that changes from run to run, or big multi-IP sites with
round-robin DNS. */
- if (continue_hostname && !continue_host_tried)
+ if (continue_sequence > 1 && !continue_host_tried)
{
int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0;
DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
+ DEBUG(D_transport) debug_printf(" SMTP>>QUIT\n");
#ifndef DISABLE_TLS
/* A TLS conn could be open for a cutthrough, but not for a plain continued-
transport */
#else
(void) write(fd, US"QUIT\r\n", 6);
#endif
+
+ DEBUG(D_transport) debug_printf(" SMTP(close)>>\n");
(void) close(fd);
cutthrough.cctx.sock = -1;
continue_hostname = NULL;
+ continue_sequence = 1;
goto retry_non_continued;
}
to create/update the per-transport wait-<transport_name> database. */
if (update_waiting && tblock->connection_max_messages != 1)
- transport_update_waiting(hostlist, tblock->name);
+ transport_update_waiting(hostlist, trname);
END_TRANSPORT:
-DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
+DEBUG(D_transport) debug_printf("Leaving %s transport\n", trname);
return TRUE; /* Each address has its status */
}
+
+
+
+# ifdef DYNLOOKUP
+# define smtp_transport_info _transport_info
+# endif
+
+transport_info smtp_transport_info = {
+.drinfo = {
+ .driver_name = US"smtp",
+ .options = smtp_transport_options,
+ .options_count = &smtp_transport_options_count,
+ .options_block = &smtp_transport_option_defaults,
+ .options_len = sizeof(smtp_transport_options_block),
+ .init = smtp_transport_init,
+# ifdef DYNLOOKUP
+ .dyn_magic = TRANSPORT_MAGIC,
+# endif
+ },
+.code = smtp_transport_entry,
+.tidyup = NULL,
+.closedown = smtp_transport_closedown,
+.local = FALSE
+};
+
#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/