* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
(void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
{ "allow_localhost", opt_bool,
(void *)offsetof(smtp_transport_options_block, allow_localhost) },
+#ifdef EXPERIMENTAL_ARC
+ { "arc_sign", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, arc_sign) },
+#endif
{ "authenticated_sender", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, authenticated_sender) },
{ "authenticated_sender_force", opt_bool,
(void *)offsetof(smtp_transport_options_block, connect_timeout) },
{ "connection_max_messages", opt_int | opt_public,
(void *)offsetof(transport_instance, connection_max_messages) },
+# ifdef SUPPORT_DANE
+ { "dane_require_tls_ciphers", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dane_require_tls_ciphers) },
+# endif
{ "data_timeout", opt_time,
(void *)offsetof(smtp_transport_options_block, data_timeout) },
{ "delay_after_cutoff", opt_bool,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
{ "dkim_domain", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
+ { "dkim_hash", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
+ { "dkim_identity", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_identity) },
{ "dkim_private_key", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
{ "dkim_selector", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) },
{ "dkim_strict", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
+ { "dkim_timestamps", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_timestamps) },
#endif
{ "dns_qualify_single", opt_bool,
(void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
#ifdef SUPPORT_TLS
{ "hosts_nopass_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
+ { "hosts_noproxy_tls", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_noproxy_tls) },
#endif
{ "hosts_override", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_override) },
{ "hosts_require_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
#ifdef SUPPORT_TLS
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
{ "hosts_require_dane", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
# endif
(void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
{ "hosts_try_chunking", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
{ "hosts_try_dane", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
#endif
{ "serialize_hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, serialize_hosts) },
{ "size_addition", opt_int,
- (void *)offsetof(smtp_transport_options_block, size_addition) }
+ (void *)offsetof(smtp_transport_options_block, size_addition) },
#ifdef SUPPORT_SOCKS
- ,{ "socks_proxy", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, socks_proxy) }
+ { "socks_proxy", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, socks_proxy) },
#endif
#ifdef SUPPORT_TLS
- ,{ "tls_certificate", opt_stringptr,
+ { "tls_certificate", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_certificate) },
{ "tls_crl", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_crl) },
{ "tls_verify_certificates", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
{ "tls_verify_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) }
+ (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) },
+#endif
+#ifdef SUPPORT_I18N
+ { "utf8_downconvert", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, utf8_downconvert) },
#endif
};
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
-int smtp_transport_options_count =
- sizeof(smtp_transport_options)/sizeof(optionlist);
+int smtp_transport_options_count = nelem(smtp_transport_options);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+smtp_transport_options_block smtp_transport_option_defaults = {0};
+void smtp_transport_init(transport_instance *tblock) {}
+BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+void smtp_transport_closedown(transport_instance *tblock) {}
+
+#else /*!MACRO_PREDEF*/
+
/* Default private options block for the smtp transport. */
smtp_transport_options_block smtp_transport_option_defaults = {
- NULL, /* hosts */
- NULL, /* fallback_hosts */
- NULL, /* hostlist */
- NULL, /* fallback_hostlist */
- NULL, /* authenticated_sender */
- US"$primary_hostname", /* helo_data */
- NULL, /* interface */
- NULL, /* port */
- US"smtp", /* protocol */
- NULL, /* DSCP */
- NULL, /* serialize_hosts */
- NULL, /* hosts_try_auth */
- NULL, /* hosts_require_auth */
- US"*", /* hosts_try_chunking */
-#ifdef EXPERIMENTAL_DANE
- NULL, /* hosts_try_dane */
- NULL, /* hosts_require_dane */
+ .hosts = NULL,
+ .fallback_hosts = NULL,
+ .hostlist = NULL,
+ .fallback_hostlist = NULL,
+ .helo_data = US"$primary_hostname",
+ .interface = NULL,
+ .port = NULL,
+ .protocol = US"smtp",
+ .dscp = NULL,
+ .serialize_hosts = NULL,
+ .hosts_try_auth = NULL,
+ .hosts_require_auth = NULL,
+ .hosts_try_chunking = US"*",
+#ifdef SUPPORT_DANE
+ .hosts_try_dane = NULL,
+ .hosts_require_dane = NULL,
+ .dane_require_tls_ciphers = NULL,
#endif
- NULL, /* hosts_try_fastopen */
+ .hosts_try_fastopen = NULL,
#ifndef DISABLE_PRDR
- US"*", /* hosts_try_prdr */
+ .hosts_try_prdr = US"*",
#endif
#ifndef DISABLE_OCSP
- US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */
- NULL, /* hosts_require_ocsp */
+ .hosts_request_ocsp = US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */
+ .hosts_require_ocsp = NULL,
+#endif
+ .hosts_require_tls = NULL,
+ .hosts_avoid_tls = NULL,
+ .hosts_verify_avoid_tls = NULL,
+ .hosts_avoid_pipelining = NULL,
+ .hosts_avoid_esmtp = NULL,
+#ifdef SUPPORT_TLS
+ .hosts_nopass_tls = NULL,
+ .hosts_noproxy_tls = US"*",
#endif
- NULL, /* hosts_require_tls */
- NULL, /* hosts_avoid_tls */
- NULL, /* hosts_verify_avoid_tls */
- NULL, /* hosts_avoid_pipelining */
- NULL, /* hosts_avoid_esmtp */
- NULL, /* hosts_nopass_tls */
- 5*60, /* command_timeout */
- 5*60, /* connect_timeout; shorter system default overrides */
- 5*60, /* data timeout */
- 10*60, /* final timeout */
- 1024, /* size_addition */
- 5, /* hosts_max_try */
- 50, /* hosts_max_try_hardlimit */
- TRUE, /* address_retry_include_sender */
- FALSE, /* allow_localhost */
- FALSE, /* authenticated_sender_force */
- FALSE, /* gethostbyname */
- TRUE, /* dns_qualify_single */
- FALSE, /* dns_search_parents */
- { NULL, NULL }, /* dnssec_domains {request,require} */
- TRUE, /* delay_after_cutoff */
- FALSE, /* hosts_override */
- FALSE, /* hosts_randomize */
- TRUE, /* keepalive */
- FALSE, /* lmtp_ignore_quota */
- NULL, /* expand_retry_include_ip_address */
- TRUE /* retry_include_ip_address */
+ .command_timeout = 5*60,
+ .connect_timeout = 5*60,
+ .data_timeout = 5*60,
+ .final_timeout = 10*60,
+ .size_addition = 1024,
+ .hosts_max_try = 5,
+ .hosts_max_try_hardlimit = 50,
+ .address_retry_include_sender = TRUE,
+ .allow_localhost = FALSE,
+ .authenticated_sender_force = FALSE,
+ .gethostbyname = FALSE,
+ .dns_qualify_single = TRUE,
+ .dns_search_parents = FALSE,
+ .dnssec = { .request=NULL, .require=NULL },
+ .delay_after_cutoff = TRUE,
+ .hosts_override = FALSE,
+ .hosts_randomize = FALSE,
+ .keepalive = TRUE,
+ .lmtp_ignore_quota = FALSE,
+ .expand_retry_include_ip_address = NULL,
+ .retry_include_ip_address = TRUE,
#ifdef SUPPORT_SOCKS
- ,NULL /* socks_proxy */
+ .socks_proxy = NULL,
#endif
#ifdef SUPPORT_TLS
- ,NULL, /* tls_certificate */
- NULL, /* tls_crl */
- NULL, /* tls_privatekey */
- NULL, /* tls_require_ciphers */
- NULL, /* tls_sni */
- US"system", /* tls_verify_certificates */
- EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
- /* tls_dh_min_bits */
- TRUE, /* tls_tempfail_tryclear */
- NULL, /* tls_verify_hosts */
- US"*", /* tls_try_verify_hosts */
- US"*" /* tls_verify_cert_hostnames */
+ .tls_certificate = NULL,
+ .tls_crl = NULL,
+ .tls_privatekey = NULL,
+ .tls_require_ciphers = NULL,
+ .tls_sni = NULL,
+ .tls_verify_certificates = US"system",
+ .tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+ .tls_tempfail_tryclear = TRUE,
+ .tls_verify_hosts = NULL,
+ .tls_try_verify_hosts = US"*",
+ .tls_verify_cert_hostnames = US"*",
+#endif
+#ifdef SUPPORT_I18N
+ .utf8_downconvert = NULL,
#endif
#ifndef DISABLE_DKIM
- , {NULL, /* dkim_canon */
- NULL, /* dkim_domain */
- NULL, /* dkim_private_key */
- NULL, /* dkim_selector */
- NULL, /* dkim_sign_headers */
- NULL, /* dkim_strict */
- FALSE} /* dot_stuffed */
+ .dkim =
+ {.dkim_domain = NULL,
+ .dkim_identity = NULL,
+ .dkim_private_key = NULL,
+ .dkim_selector = NULL,
+ .dkim_canon = NULL,
+ .dkim_sign_headers = NULL,
+ .dkim_strict = NULL,
+ .dkim_hash = US"sha256",
+ .dkim_timestamps = NULL,
+ .dot_stuffed = FALSE,
+ .force_bodyhash = FALSE,
+# ifdef EXPERIMENTAL_ARC
+ .arc_signspec = NULL,
+# endif
+ },
+# ifdef EXPERIMENTAL_ARC
+ .arc_sign = NULL,
+# endif
#endif
};
static uschar *mail_command; /* Points to MAIL cmd for error messages */
static uschar *data_command = US""; /* Points to DATA cmd for error messages */
static BOOL update_waiting; /* TRUE to update the "wait" database */
+
+/*XXX move to smtp_context */
static BOOL pipelining_active; /* current transaction is in pipe mode */
*message = US string_sprintf("Remote host closed connection "
"in response to %s%s", pl, smtp_command);
}
-else *message = US string_sprintf("%s [%s]", host->name, host->address);
+else
+ *message = US string_sprintf("%s [%s]", host->name, host->address);
return FALSE;
}
/* This writes to the main log and to the message log.
Arguments:
- addr the address item containing error information
host the current host
+ detail the current message (addr_item->message)
+ basic_errno the errno (addr_item->basic_errno)
Returns: nothing
*/
static void
-write_logs(address_item *addr, host_item *host)
+write_logs(const host_item *host, const uschar *suffix, int basic_errno)
{
-uschar * message = LOGGING(outgoing_port)
+
+
+uschar *message = LOGGING(outgoing_port)
? string_sprintf("H=%s [%s]:%d", host->name, host->address,
host->port == PORT_NONE ? 25 : host->port)
: string_sprintf("H=%s [%s]", host->name, host->address);
-if (addr->message)
+if (suffix)
{
- message = string_sprintf("%s: %s", message, addr->message);
- if (addr->basic_errno > 0)
- message = string_sprintf("%s: %s", message, strerror(addr->basic_errno));
- log_write(0, LOG_MAIN, "%s", message);
- deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+ message = string_sprintf("%s: %s", message, suffix);
+ if (basic_errno > 0)
+ message = string_sprintf("%s: %s", message, strerror(basic_errno));
}
else
- {
- const uschar * s = exim_errstr(addr->basic_errno);
- log_write(0, LOG_MAIN, "%s %s", message, s);
- deliver_msglog("%s %s %s\n", tod_stamp(tod_log), message, s);
- }
+ message = string_sprintf("%s %s", message, exim_errstr(basic_errno));
+
+log_write(0, LOG_MAIN, "%s", message);
+deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
}
static void
if (sx->pending_MAIL)
{
+ DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
count--;
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
DEBUG(D_transport) debug_printf("bad response for MAIL\n");
}
while (count-- > 0)
{
- if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer),
+ if (!smtp_read_response(sx, flushbuffer, sizeof(flushbuffer),
'2', ob->command_timeout)
&& (errno != 0 || flushbuffer[0] == 0))
break;
while (count-- > 0)
{
- while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+ while (addr->transport_return != PENDING_DEFER)
+ if (!(addr = addr->next))
+ return -2;
/* The address was accepted */
addr->host_used = sx->host;
- if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
+ if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
yield |= 1;
/* Handle a response to DATA. If we have not had any good recipients, either
previously or in this block, the response is ignored. */
-if (pending_DATA != 0 &&
- !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
- '3', ob->command_timeout))
+if (pending_DATA != 0)
{
- int code;
- uschar *msg;
- BOOL pass_message;
- if (pending_DATA > 0 || (yield & 1) != 0)
+ DEBUG(D_transport) debug_printf("%s expect data\n", __FUNCTION__);
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+ '3', ob->command_timeout))
{
- if (errno == 0 && sx->buffer[0] == '4')
+ int code;
+ uschar *msg;
+ BOOL pass_message;
+ if (pending_DATA > 0 || (yield & 1) != 0)
{
- errno = ERRNO_DATA4XX;
- sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ 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 -3;
+ (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
+ DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
+ "is in use and there were no good recipients\n", msg);
}
- (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
- DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
- "is in use and there were no good recipients\n", msg);
}
/* All responses read and handled; MAIL (if present) received 2xx and DATA (if
/* Do the client side of smtp-level authentication */
/*
Arguments:
+ sx smtp connection
buffer EHLO response from server (gets overwritten)
- addrlist chain of potential addresses to deliver
- host host to deliver to
- ob transport options
- ibp, obp comms channel control blocks
Returns:
OK Success, or failed (but not required): global "smtp_authenticated" set
FAIL - response
*/
-int
-smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
- smtp_transport_options_block *ob, BOOL is_esmtp,
- smtp_inblock *ibp, smtp_outblock *obp)
+static int
+smtp_auth(smtp_context * sx, uschar * buffer, unsigned bufsize)
{
+smtp_transport_options_block * ob = sx->ob;
int require_auth;
uschar *fail_reason = US"server did not advertise AUTH support";
-smtp_authenticated = FALSE;
+f.smtp_authenticated = FALSE;
client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
-require_auth = verify_check_given_host(&ob->hosts_require_auth, host);
+require_auth = verify_check_given_host(CUSS &ob->hosts_require_auth, sx->host);
-if (is_esmtp && !regex_AUTH) regex_AUTH =
+if (sx->esmtp && !regex_AUTH) regex_AUTH =
regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
FALSE, TRUE);
-if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+if (sx->esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
{
uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
expand_nmax = -1; /* reset */
regex match above. */
if (require_auth == OK ||
- verify_check_given_host(&ob->hosts_try_auth, host) == OK)
+ verify_check_given_host(CUSS &ob->hosts_try_auth, sx->host) == OK)
{
auth_instance *au;
fail_reason = US"no common mechanisms were found";
If one is found, attempt to authenticate by calling its client function.
*/
- for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+ for (au = auths; !f.smtp_authenticated && au; au = au->next)
{
uschar *p = names;
if (!au->client ||
/* Loop to scan supported server mechanisms */
- while (*p != 0)
+ while (*p)
{
int rc;
int len = Ustrlen(au->public_name);
that reflections don't show it. */
fail_reason = US"authentication attempt(s) failed";
- obp->authenticating = TRUE;
- rc = (au->info->clientcode)(au, ibp, obp,
+ sx->outblock.authenticating = TRUE;
+ rc = (au->info->clientcode)(au, sx,
ob->command_timeout, buffer, bufsize);
- obp->authenticating = FALSE;
+ sx->outblock.authenticating = FALSE;
DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
au->name, rc);
switch(rc)
{
case OK:
- smtp_authenticated = TRUE; /* stops the outer loop */
+ f.smtp_authenticated = TRUE; /* stops the outer loop */
client_authenticator = au->name;
if (au->set_client_id != NULL)
client_authenticated_id = expand_string(au->set_client_id);
case FAIL:
if (errno != 0 || buffer[0] != '5') return FAIL;
log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
- au->name, host->name, host->address, buffer);
+ au->name, sx->host->name, sx->host->address, buffer);
break;
/* Failure by some other means. In effect, the authenticator
case CANCELLED:
if (*buffer != 0)
log_write(0, LOG_MAIN, "%s authenticator cancelled "
- "authentication H=%s [%s] %s", au->name, host->name,
- host->address, buffer);
+ "authentication H=%s [%s] %s", au->name, sx->host->name,
+ sx->host->address, buffer);
break;
/* Internal problem, message in buffer. */
case ERROR:
- set_errno_nohost(addrlist, ERRNO_AUTHPROB, string_copy(buffer),
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(buffer),
DEFER, FALSE);
return ERROR;
}
/* If we haven't authenticated, but are required to, give up. */
-if (require_auth == OK && !smtp_authenticated)
+if (require_auth == OK && !f.smtp_authenticated)
{
- set_errno_nohost(addrlist, ERRNO_AUTHFAIL,
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
string_sprintf("authentication required but %s", fail_reason), DEFER,
FALSE);
return DEFER;
addrlist chain of potential addresses to deliver
ob transport options
-Globals smtp_authenticated
+Globals f.smtp_authenticated
client_authenticated_sender
Return True on error, otherwise buffer has (possibly empty) terminated string
*/
uschar *local_authenticated_sender = authenticated_sender;
#ifdef notdef
- debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+ debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
#endif
if (ob->authenticated_sender != NULL)
uschar *new = expand_string(ob->authenticated_sender);
if (new == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
uschar *message = string_sprintf("failed to expand "
"authenticated_sender: %s", expand_string_message);
/* Add the authenticated sender address if present */
-if ((smtp_authenticated || ob->authenticated_sender_force) &&
+if ((f.smtp_authenticated || ob->authenticated_sender_force) &&
local_authenticated_sender != NULL)
{
string_format(buffer, bufsize, " AUTH=%s",
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
/* Lookup TLSA record for host/port.
Return: OK success with dnssec; DANE mode
DEFER Do not use this host now, may retry later
/* move this out to host.c given the similarity to dns_lookup() ? */
uschar buffer[300];
const uschar * fullname = buffer;
+int rc;
+BOOL sec;
/* TLSA lookup string */
(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
-switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+rc = dns_lookup(dnsa, buffer, T_TLSA, &fullname);
+sec = dns_is_secure(dnsa);
+DEBUG(D_transport)
+ debug_printf("TLSA lookup ret %d %sDNSSEC\n", rc, sec ? "" : "not ");
+
+switch (rc)
{
+ case DNS_AGAIN:
+ return DEFER; /* just defer this TLS'd conn */
+
case DNS_SUCCEED:
- if (!dns_is_secure(dnsa))
+ if (sec)
{
- log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
- return DEFER;
- }
- return OK;
+ DEBUG(D_transport)
+ {
+ dns_scan dnss;
+ dns_record * rr;
+ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+ if (rr->type == T_TLSA && rr->size > 3)
+ {
+ uint16_t payload_length = rr->size - 3;
+ uschar s[MAX_TLSA_EXPANDED_SIZE], * sp = s, * p = US rr->data;
- case DNS_AGAIN:
- return DEFER; /* just defer this TLS'd conn */
+ sp += sprintf(CS sp, "%d ", *p++); /* usage */
+ sp += sprintf(CS sp, "%d ", *p++); /* selector */
+ sp += sprintf(CS sp, "%d ", *p++); /* matchtype */
+ while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+ sp += sprintf(CS sp, "%02x", *p++);
+
+ debug_printf(" %s\n", s);
+ }
+ }
+ return OK;
+ }
+ log_write(0, LOG_MAIN,
+ "DANE error: TLSA lookup for %s not DNSSEC", host->name);
+ /*FALLTRHOUGH*/
case DNS_NODATA: /* no TLSA RR for this lookup */
case DNS_NOMATCH: /* no records at all for this lookup */
-static uschar
-ehlo_response(uschar * buf, uschar checks)
+static unsigned
+ehlo_response(uschar * buf, unsigned checks)
{
size_t bsize = Ustrlen(buf);
#ifdef SUPPORT_TLS
-if ( checks & PEER_OFFERED_TLS
+if ( checks & OPTION_TLS
&& pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_TLS;
+ checks &= ~OPTION_TLS;
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if ( checks & OPTION_REQUIRETLS
+ && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
+ checks &= ~OPTION_REQUIRETLS;
+# endif
#endif
-if ( checks & PEER_OFFERED_IGNQ
+if ( checks & OPTION_IGNQ
&& pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_IGNQ;
+ checks &= ~OPTION_IGNQ;
-if ( checks & PEER_OFFERED_CHUNKING
+if ( checks & OPTION_CHUNKING
&& pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_CHUNKING;
+ checks &= ~OPTION_CHUNKING;
#ifndef DISABLE_PRDR
-if ( checks & PEER_OFFERED_PRDR
+if ( checks & OPTION_PRDR
&& pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_PRDR;
+ checks &= ~OPTION_PRDR;
#endif
#ifdef SUPPORT_I18N
-if ( checks & PEER_OFFERED_UTF8
+if ( checks & OPTION_UTF8
&& pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_UTF8;
+ checks &= ~OPTION_UTF8;
#endif
-if ( checks & PEER_OFFERED_DSN
+if ( checks & OPTION_DSN
&& pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_DSN;
+ checks &= ~OPTION_DSN;
-if ( checks & PEER_OFFERED_PIPE
+if ( checks & OPTION_PIPE
&& pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_PIPE;
+ checks &= ~OPTION_PIPE;
-if ( checks & PEER_OFFERED_SIZE
+if ( checks & OPTION_SIZE
&& pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_SIZE;
+ checks &= ~OPTION_SIZE;
return checks;
}
If given a nonzero size, first flush any buffered SMTP commands
then emit the command.
-Reap previous SMTP command responses if requested.
-Reap one SMTP command response if requested.
+Reap previous SMTP command responses if requested, and always reap
+the response from a previous BDAT command.
+
+Args:
+ tctx transport context
+ chunk_size value for SMTP BDAT command
+ flags
+ tc_chunk_last add LAST option to SMTP BDAT command
+ tc_reap_prev reap response to previous SMTP commands
Returns: OK or ERROR
*/
static int
-smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
- unsigned chunk_size, unsigned flags)
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+ unsigned flags)
{
smtp_transport_options_block * ob =
(smtp_transport_options_block *)(tctx->tblock->options_block);
int cmd_count = 0;
int prev_cmd_count;
-/* Write SMTP chunk header command */
+/* Write SMTP chunk header command. If not reaping responses, note that
+there may be more writes (like, the chunk data) done soon. */
if (chunk_size > 0)
{
- if((cmd_count = smtp_write_command(&sx->outblock, FALSE, "BDAT %u%s\r\n",
- chunk_size,
- flags & tc_chunk_last ? " LAST" : "")
+ if((cmd_count = smtp_write_command(sx,
+ flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
+ "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "")
) < 0) return ERROR;
if (flags & tc_chunk_last)
data_command = string_copy(big_buffer); /* Save for later error message */
{
DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
ob->command_timeout))
{
if (errno == 0 && sx->buffer[0] == '4')
{
errno = ERRNO_DATA4XX; /*XXX does this actually get used? */
- sx->first_addr->more_errno |=
+ sx->addrlist->more_errno |=
((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
return ERROR;
int
smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
{
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
dns_answer tlsa_dnsa;
#endif
BOOL pass_message = FALSE;
-
uschar * message = NULL;
-int save_errno;
int yield = OK;
int rc;
sx->utf8_needed = FALSE;
#endif
sx->dsn_all_lasthop = TRUE;
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
sx->dane = FALSE;
-sx->dane_required = verify_check_given_host(&sx->ob->hosts_require_dane, sx->host) == OK;
+sx->dane_required =
+ verify_check_given_host(CUSS &sx->ob->hosts_require_dane, sx->host) == OK;
#endif
if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
sx->peer_offered = 0;
+sx->avoid_option = 0;
sx->igquotstr = US"";
if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
#ifdef EXPERIMENTAL_DSN_INFO
the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
specially so they can be identified for retries. */
-if (continue_hostname == NULL)
+if (!continue_hostname)
{
if (sx->verify)
HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
- /* This puts port into host->port */
- sx->inblock.sock = sx->outblock.sock =
- smtp_connect(sx->host, sx->host_af, sx->port, sx->interface,
- sx->ob->connect_timeout, sx->tblock);
+ /* Get the actual port the connection will use, into sx->host */
- if (sx->inblock.sock < 0)
- {
- uschar * msg = NULL;
- int save_errno = errno;
- if (sx->verify)
- {
- msg = strerror(errno);
- HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
- }
- set_errno_nohost(sx->addrlist,
- save_errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : save_errno,
- sx->verify ? string_sprintf("could not connect: %s", msg)
- : NULL,
- DEFER, FALSE);
- sx->send_quit = FALSE;
- return DEFER;
- }
+ smtp_port_for_connect(sx->host, sx->port);
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+ /* Do TLSA lookup for DANE */
{
tls_out.dane_verified = FALSE;
tls_out.tlsa_usage = 0;
if (sx->host->dnssec == DS_YES)
{
if( sx->dane_required
- || verify_check_given_host(&sx->ob->hosts_try_dane, sx->host) == OK
+ || verify_check_given_host(CUSS &sx->ob->hosts_try_dane, sx->host) == OK
)
switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
{
- case OK: sx->dane = TRUE; break;
+ case OK: sx->dane = TRUE;
+ sx->ob->tls_tempfail_tryclear = FALSE;
+ break;
case FAIL_FORCED: break;
default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
string_sprintf("DANE error: tlsa lookup %s",
rc == DEFER ? "DEFER" : "FAIL"),
rc, FALSE);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->tblock->event_action,
+ US"dane:fail", sx->dane_required
+ ? US"dane-required" : US"dnssec-invalid");
+# endif
return rc;
}
}
set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
string_sprintf("DANE error: %s lookup not DNSSEC", sx->host->name),
FAIL, FALSE);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->tblock->event_action,
+ US"dane:fail", US"dane-required");
+# endif
return FAIL;
}
-
- if (sx->dane)
- sx->ob->tls_tempfail_tryclear = FALSE;
}
#endif /*DANE*/
+ /* Make the TCP connection */
+
+ sx->cctx.sock =
+ smtp_connect(sx->host, sx->host_af, sx->interface,
+ sx->ob->connect_timeout, sx->tblock);
+ sx->cctx.tls_ctx = NULL;
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+
+ if (sx->cctx.sock < 0)
+ {
+ uschar * msg = NULL;
+ if (sx->verify)
+ {
+ msg = US strerror(errno);
+ HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+ }
+ set_errno_nohost(sx->addrlist,
+ errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+ sx->verify ? string_sprintf("could not connect: %s", msg)
+ : NULL,
+ DEFER, FALSE);
+ sx->send_quit = FALSE;
+ return DEFER;
+ }
+
/* 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. */
BOOL good_response;
#ifdef TCP_QUICKACK
- (void) setsockopt(sx->inblock.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
#endif
- good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', sx->ob->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
sx->smtp_greeting = string_copy(sx->buffer);
mailers use upper case for some reason (the RFC is quite clear about case
independence) so, for peace of mind, I gave in. */
- sx->esmtp = verify_check_given_host(&sx->ob->hosts_avoid_esmtp, sx->host) != OK;
+ sx->esmtp = verify_check_given_host(CUSS &sx->ob->hosts_avoid_esmtp, sx->host) != OK;
/* Alas; be careful, since this goto is not an error-out, so conceivably
we might set data between here and the target which we assume to exist
#ifdef SUPPORT_TLS
if (sx->smtps)
{
- smtp_peer_options |= PEER_OFFERED_TLS;
+ smtp_peer_options |= OPTION_TLS;
suppress_tls = FALSE;
sx->ob->tls_tempfail_tryclear = FALSE;
smtp_command = US"SSL-on-connect";
if (sx->esmtp)
{
- if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+ if (smtp_write_command(sx, SCMD_FLUSH, "%s %s\r\n",
sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
goto SEND_FAILED;
sx->esmtp_sent = TRUE;
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
sx->ob->command_timeout))
{
if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
{ rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
- if (smtp_write_command(&sx->outblock, FALSE, "HELO %s\r\n", sx->helo_data) < 0)
+ if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
goto SEND_FAILED;
- good_response = smtp_read_response(&sx->inblock, rsp, n,
- '2', sx->ob->command_timeout);
+ good_response = smtp_read_response(sx, rsp, n, '2', sx->ob->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
sx->helo_response = string_copy(rsp);
#endif
if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
{
- sx->send_quit = FALSE;
- save_errno = ERRNO_SMTPCLOSED;
- message = string_sprintf("Remote host closed connection "
- "in response to %s (EHLO response was: %s)",
- smtp_command, sx->buffer);
- goto FAILED;
+ errno = ERRNO_SMTPCLOSED;
+ goto EHLOHELO_FAILED;
}
- Ustrncpy(sx->buffer, rsp, sizeof(sx->buffer)/2);
+ memmove(sx->buffer, rsp, Ustrlen(rsp));
goto RESPONSE_FAILED;
}
}
- sx->peer_offered = smtp_peer_options = 0;
+ sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
if (sx->esmtp || sx->lmtp)
{
sx->peer_offered = ehlo_response(sx->buffer,
- PEER_OFFERED_TLS /* others checked later */
+ OPTION_TLS /* others checked later */
);
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
#ifdef SUPPORT_TLS
- smtp_peer_options |= sx->peer_offered & PEER_OFFERED_TLS;
+ smtp_peer_options |= sx->peer_offered & OPTION_TLS;
#endif
}
}
-/* For continuing deliveries down the same channel, the socket is the standard
-input, and 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
+/* For continuing deliveries down the same channel, having re-exec'd 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. */
else
{
- sx->inblock.sock = sx->outblock.sock = fileno(stdin);
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+ {
+ sx->cctx = cutthrough.cctx;
+ sx->host->port = sx->port = cutthrough.host.port;
+ }
+ else
+ {
+ sx->cctx.sock = 0; /* stdin */
+ sx->cctx.tls_ctx = NULL;
+ smtp_port_for_connect(sx->host, sx->port); /* Record the port that was used */
+ }
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
smtp_command = big_buffer;
- sx->host->port = sx->port; /* Record the port that was used */
sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */
+
+ /* For a continued connection with TLS being proxied for us, or a
+ held-open verify connection with TLS, nothing more to do. */
+
+ if ( continue_proxy_cipher
+ || (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only
+ && cutthrough.is_tls)
+ )
+ {
+ sx->peer_offered = smtp_peer_options;
+ 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");
+ return OK;
+ }
+ HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
}
/* If TLS is available on this connection, whether continued or not, attempt to
for error analysis. */
#ifdef SUPPORT_TLS
-if ( smtp_peer_options & PEER_OFFERED_TLS
+if ( smtp_peer_options & OPTION_TLS
&& !suppress_tls
- && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
+ && verify_check_given_host(CUSS &sx->ob->hosts_avoid_tls, sx->host) != OK
&& ( !sx->verify
- || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK
+ || verify_check_given_host(CUSS &sx->ob->hosts_verify_avoid_tls, sx->host) != OK
) )
{
uschar buffer2[4096];
- if (smtp_write_command(&sx->outblock, FALSE, "STARTTLS\r\n") < 0)
+ if (smtp_write_command(sx, SCMD_FLUSH, "STARTTLS\r\n") < 0)
goto SEND_FAILED;
/* If there is an I/O error, transmission of this message is deferred. If
STARTTLS, we carry on. This means we will try to send the message in clear,
unless the host is in hosts_require_tls (tested below). */
- if (!smtp_read_response(&sx->inblock, buffer2, sizeof(buffer2), '2',
+ if (!smtp_read_response(sx, buffer2, sizeof(buffer2), '2',
sx->ob->command_timeout))
{
if ( errno != 0
)
{
Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
+ sx->buffer[sizeof(sx->buffer)-1] = '\0';
goto RESPONSE_FAILED;
}
}
TLS_NEGOTIATE:
{
address_item * addr;
- int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock
-# ifdef EXPERIMENTAL_DANE
- , sx->dane ? &tlsa_dnsa : NULL
+ uschar * errstr;
+ sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->host,
+ sx->addrlist, sx->tblock,
+# ifdef SUPPORT_DANE
+ sx->dane ? &tlsa_dnsa : NULL,
# endif
- );
+ &tls_out, &errstr);
- /* TLS negotiation failed; give an error. From outside, this function may
- be called again to try in clear on a new connection, if the options permit
- it for this host. */
-
- if (rc != OK)
+ if (!sx->cctx.tls_ctx)
{
-# ifdef EXPERIMENTAL_DANE
- if (sx->dane) log_write(0, LOG_MAIN,
- "DANE attempt failed; no TLS connection to %s [%s]",
- sx->host->name, sx->host->address);
+ /* TLS negotiation failed; give an error. From outside, this function may
+ be called again to try in clear on a new connection, if the options permit
+ it for this host. */
+ DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
+
+# ifdef SUPPORT_DANE
+ if (sx->dane)
+ {
+ log_write(0, LOG_MAIN,
+ "DANE attempt failed; TLS connection to %s [%s]: %s",
+ sx->host->name, sx->host->address, errstr);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->tblock->event_action,
+ US"dane:fail", US"validation-failure"); /* could do with better detail */
+# endif
+ }
# endif
- save_errno = ERRNO_TLSFAILURE;
- message = US"failure while setting up TLS session";
+ errno = ERRNO_TLSFAILURE;
+ message = string_sprintf("TLS session: %s", errstr);
sx->send_quit = FALSE;
goto TLS_FAILED;
}
expand it here. $sending_ip_address and $sending_port are set up right at the
start of the Exim process (in exim.c). */
-if (tls_out.active >= 0)
+if (tls_out.active.sock >= 0)
{
char *greeting_cmd;
BOOL good_response;
/* For SMTPS we need to wait for the initial OK response. */
if (sx->smtps)
{
- good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', sx->ob->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
sx->smtp_greeting = string_copy(sx->buffer);
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
}
- if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+ if (smtp_write_command(sx, SCMD_FLUSH, "%s %s\r\n",
sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
goto SEND_FAILED;
- good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', sx->ob->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
sx->helo_response = string_copy(sx->buffer);
have one. */
else if ( sx->smtps
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
|| sx->dane
# endif
- || verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
+# ifdef EXPERIMENTAL_REQUIRETLS
+ || tls_requiretls & REQUIRETLS_MSG
+# endif
+ || verify_check_given_host(CUSS &sx->ob->hosts_require_tls, sx->host) == OK
)
{
- save_errno = ERRNO_TLSREQUIRED;
+ errno =
+# ifdef EXPERIMENTAL_REQUIRETLS
+ tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS :
+# endif
+ ERRNO_TLSREQUIRED;
message = string_sprintf("a TLS session is required, but %s",
- smtp_peer_options & PEER_OFFERED_TLS
+ smtp_peer_options & OPTION_TLS
? "an attempt to start TLS failed" : "the server did not offer TLS support");
+# if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT)
+ if (sx->dane)
+ (void) event_raise(sx->tblock->event_action, US"dane:fail",
+ smtp_peer_options & OPTION_TLS
+ ? US"validation-failure" /* could do with better detail */
+ : US"starttls-not-supported");
+# endif
goto TLS_FAILED;
}
#endif /*SUPPORT_TLS*/
if (continue_hostname == NULL
#ifdef SUPPORT_TLS
- || tls_out.active >= 0
+ || tls_out.active.sock >= 0
#endif
)
{
{
sx->peer_offered = ehlo_response(sx->buffer,
0 /* no TLS */
- | (sx->lmtp && sx->ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
- | PEER_OFFERED_CHUNKING
- | PEER_OFFERED_PRDR
+ | (sx->lmtp && sx->ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+ | OPTION_CHUNKING
+ | OPTION_PRDR
#ifdef SUPPORT_I18N
- | (sx->addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0)
+ | (sx->addrlist->prop.utf8_msg ? OPTION_UTF8 : 0)
/*XXX if we hand peercaps on to continued-conn processes,
must not depend on this addr */
#endif
- | PEER_OFFERED_DSN
- | PEER_OFFERED_PIPE
- | (sx->ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
+ | OPTION_DSN
+ | OPTION_PIPE
+ | (sx->ob->size_addition >= 0 ? OPTION_SIZE : 0)
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
+#endif
);
/* Set for IGNOREQUOTA if the response to LHLO specifies support and the
lmtp_ignore_quota option was set. */
- sx->igquotstr = sx->peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
+ sx->igquotstr = sx->peer_offered & OPTION_IGNQ ? US" IGNOREQUOTA" : US"";
/* If the response to EHLO specified support for the SIZE parameter, note
this, provided size_addition is non-negative. */
- smtp_peer_options |= sx->peer_offered & PEER_OFFERED_SIZE;
+ smtp_peer_options |= sx->peer_offered & OPTION_SIZE;
/* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
the current host, esmtp will be false, so PIPELINING can never be used. If
the current host matches hosts_avoid_pipelining, don't do it. */
- if ( sx->peer_offered & PEER_OFFERED_PIPE
- && verify_check_given_host(&sx->ob->hosts_avoid_pipelining, sx->host) != OK)
- smtp_peer_options |= PEER_OFFERED_PIPE;
+ if ( sx->peer_offered & OPTION_PIPE
+ && verify_check_given_host(CUSS &sx->ob->hosts_avoid_pipelining, sx->host) != OK)
+ smtp_peer_options |= OPTION_PIPE;
DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
- smtp_peer_options & PEER_OFFERED_PIPE ? "" : "not ");
+ smtp_peer_options & OPTION_PIPE ? "" : "not ");
- if ( sx->peer_offered & PEER_OFFERED_CHUNKING
- && verify_check_given_host(&sx->ob->hosts_try_chunking, sx->host) != OK)
- sx->peer_offered &= ~PEER_OFFERED_CHUNKING;
+ if ( sx->peer_offered & OPTION_CHUNKING
+ && verify_check_given_host(CUSS &sx->ob->hosts_try_chunking, sx->host) != OK)
+ sx->peer_offered &= ~OPTION_CHUNKING;
- if (sx->peer_offered & PEER_OFFERED_CHUNKING)
+ if (sx->peer_offered & OPTION_CHUNKING)
{DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
#ifndef DISABLE_PRDR
- if ( sx->peer_offered & PEER_OFFERED_PRDR
- && verify_check_given_host(&sx->ob->hosts_try_prdr, sx->host) != OK)
- sx->peer_offered &= ~PEER_OFFERED_PRDR;
+ if ( sx->peer_offered & OPTION_PRDR
+ && verify_check_given_host(CUSS &sx->ob->hosts_try_prdr, sx->host) != OK)
+ sx->peer_offered &= ~OPTION_PRDR;
- if (sx->peer_offered & PEER_OFFERED_PRDR)
+ if (sx->peer_offered & OPTION_PRDR)
{DEBUG(D_transport) debug_printf("PRDR usable\n");}
#endif
/* Note if the server supports DSN */
- smtp_peer_options |= sx->peer_offered & PEER_OFFERED_DSN;
+ smtp_peer_options |= sx->peer_offered & OPTION_DSN;
DEBUG(D_transport) debug_printf("%susing DSN\n",
- sx->peer_offered & PEER_OFFERED_DSN ? "" : "not ");
+ sx->peer_offered & OPTION_DSN ? "" : "not ");
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ if (sx->peer_offered & OPTION_REQUIRETLS)
+ {
+ smtp_peer_options |= OPTION_REQUIRETLS;
+ DEBUG(D_transport) debug_printf(
+ tls_requiretls & REQUIRETLS_MSG
+ ? "using REQUIRETLS\n" : "REQUIRETLS offered\n");
+ }
+#endif
/* Note if the response to EHLO specifies support for the AUTH extension.
If it has, check that this host is one we want to authenticate to, and do
the business. The host name and address must be available when the
authenticator's client driver is running. */
- switch (yield = smtp_auth(sx->buffer, sizeof(sx->buffer), sx->addrlist, sx->host,
- sx->ob, sx->esmtp, &sx->inblock, &sx->outblock))
+ switch (yield = smtp_auth(sx, sx->buffer, sizeof(sx->buffer)))
{
default: goto SEND_QUIT;
case OK: break;
}
}
}
-pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
+sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
/* The setting up of the SMTP call is now complete. Any subsequent errors are
message-specific. */
#ifdef SUPPORT_I18N
if (sx->addrlist->prop.utf8_msg)
{
+ uschar * s;
+
+ /* If the transport sets a downconversion mode it overrides any set by ACL
+ for the message. */
+
+ if ((s = sx->ob->utf8_downconvert))
+ {
+ if (!(s = expand_string(s)))
+ {
+ message = string_sprintf("failed to expand utf8_downconvert: %s",
+ expand_string_message);
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ switch (*s)
+ {
+ case '1': sx->addrlist->prop.utf8_downcvt = TRUE;
+ sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ case '0': sx->addrlist->prop.utf8_downcvt = FALSE;
+ sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ case '-': if (s[1] == '1')
+ {
+ sx->addrlist->prop.utf8_downcvt = FALSE;
+ sx->addrlist->prop.utf8_downcvt_maybe = TRUE;
+ }
+ break;
+ }
+ }
+
sx->utf8_needed = !sx->addrlist->prop.utf8_downcvt
&& !sx->addrlist->prop.utf8_downcvt_maybe;
DEBUG(D_transport) if (!sx->utf8_needed)
}
/* If this is an international message we need the host to speak SMTPUTF8 */
-if (sx->utf8_needed && !(sx->peer_offered & PEER_OFFERED_UTF8))
+if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8))
{
errno = ERRNO_UTF8_FWD;
goto RESPONSE_FAILED;
}
+#endif /*SUPPORT_I18N*/
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ /*XXX should tls_requiretls actually be per-addr? */
+
+if ( tls_requiretls & REQUIRETLS_MSG
+ && !(sx->peer_offered & OPTION_REQUIRETLS)
+ )
+ {
+ sx->setting_up = TRUE;
+ errno = ERRNO_REQUIRETLS;
+ message = US"REQUIRETLS support is required from the server"
+ " but it was not offered";
+ DEBUG(D_transport) debug_printf("%s\n", message);
+ goto TLS_FAILED;
+ }
#endif
return OK;
{
int code;
- uschar * set_message;
RESPONSE_FAILED:
- {
- save_errno = errno;
message = NULL;
- sx->send_quit = check_response(sx->host, &save_errno, sx->addrlist->more_errno,
+ sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
sx->buffer, &code, &message, &pass_message);
+ yield = DEFER;
goto FAILED;
- }
SEND_FAILED:
- {
- save_errno = errno;
code = '4';
message = US string_sprintf("send() to %s [%s] failed: %s",
- sx->host->name, sx->host->address, strerror(save_errno));
+ sx->host->name, sx->host->address, strerror(errno));
sx->send_quit = FALSE;
+ yield = DEFER;
+ goto FAILED;
+
+ EHLOHELO_FAILED:
+ code = '4';
+ message = string_sprintf("Remote host closed connection in response to %s"
+ " (EHLO response was: %s)", smtp_command, sx->buffer);
+ sx->send_quit = FALSE;
+ yield = DEFER;
goto FAILED;
- }
/* This label is jumped to directly when a TLS negotiation has failed,
or was not done for a host for which it is required. Values will be set
- in message and save_errno, and setting_up will always be true. Treat as
+ in message and errno, and setting_up will always be true. Treat as
a temporary error. */
#ifdef SUPPORT_TLS
TLS_FAILED:
- code = '4';
+# ifdef EXPERIMENTAL_REQUIRETLS
+ if (errno == ERRNO_REQUIRETLS)
+ code = '5', yield = FAIL;
+ /*XXX DSN will be labelled 500; prefer 530 5.7.4 */
+ else
+# endif
+ code = '4', yield = DEFER;
+ goto FAILED;
#endif
/* The failure happened while setting up the call; see if the failure was
a 5xx response (this will either be on connection, or following HELO - a 5xx
- after EHLO causes it to try HELO). If so, fail all addresses, as this host is
- never going to accept them. For other errors during setting up (timeouts or
- whatever), defer all addresses, and yield DEFER, so that the host is not
- tried again for a while. */
-
- FAILED:
+ after EHLO causes it to try HELO). If so, and there are no more hosts to try,
+ fail all addresses, as this host is never going to accept them. For other
+ errors during setting up (timeouts or whatever), defer all addresses, and
+ yield DEFER, so that the host is not tried again for a while.
+
+ XXX This peeking for another host feels like a layering violation. We want
+ to note the host as unusable, but down here we shouldn't know if this was
+ the last host to try for the addr(list). Perhaps the upper layer should be
+ the one to do set_errno() ? The problem is that currently the addr is where
+ errno etc. are stashed, but until we run out of hosts to try the errors are
+ host-specific. Maybe we should enhance the host_item definition? */
+
+FAILED:
sx->ok = FALSE; /* For when reached by GOTO */
- set_message = message;
-
- yield = code == '5'
+ set_errno(sx->addrlist, errno, message,
+ sx->host->next
+ ? DEFER
+ : code == '5'
#ifdef SUPPORT_I18N
- || errno == ERRNO_UTF8_FWD
+ || errno == ERRNO_UTF8_FWD
#endif
- ? FAIL : DEFER;
-
- set_errno(sx->addrlist, save_errno, set_message, yield, pass_message, sx->host
+ ? FAIL : DEFER,
+ pass_message, sx->host
#ifdef EXPERIMENTAL_DSN_INFO
, sx->smtp_greeting, sx->helo_response
#endif
SEND_QUIT:
if (sx->send_quit)
- (void)smtp_write_command(&sx->outblock, FALSE, "QUIT\r\n");
+ (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
#ifdef SUPPORT_TLS
-tls_close(FALSE, TRUE);
+if (sx->cctx.tls_ctx)
+ {
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx->cctx.tls_ctx = NULL;
+ }
#endif
/* Close the socket, and return the appropriate value, first setting
works because the NULL setting is passed back to the calling process, and
remote_max_parallel is forced to 1 when delivering over an existing connection,
+*/
-If all went well and continue_more is set, we shouldn't actually get here if
-there are further addresses, as the return above will be taken. However,
-writing RSET might have failed, or there may be other addresses whose hosts are
-specified in the transports, and therefore not visible at top level, in which
-case continue_more won't get set. */
-
-HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx->send_quit)
{
- shutdown(sx->outblock.sock, SHUT_WR);
- if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0)
- for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
+ shutdown(sx->cctx.sock, SHUT_WR);
+ if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+ for (rc = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
rc--; /* drain socket */
sx->send_quit = FALSE;
}
-(void)close(sx->inblock.sock);
-sx->inblock.sock = sx->outblock.sock = -1;
+(void)close(sx->cctx.sock);
+sx->cctx.sock = -1;
#ifndef DISABLE_EVENT
(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
*p = 0;
-/* If we know the receiving MTA supports the SIZE qualification,
+/* If we know the receiving MTA supports the SIZE qualification, and we know it,
send it, adding something to the message size to allow for imprecision
and things that get added en route. Exim keeps the number of lines
in a message, so we can give an accurate value for the original message, but we
need some additional to handle added headers. (Double "." characters don't get
included in the count.) */
-if (sx->peer_offered & PEER_OFFERED_SIZE)
+if ( message_size > 0
+ && sx->peer_offered & OPTION_SIZE && !(sx->avoid_option & OPTION_SIZE))
{
+/*XXX problem here under spool_files_wireformat?
+Or just forget about lines? Or inflate by a fixed proportion? */
+
sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
while (*p) p++;
}
request that */
sx->prdr_active = FALSE;
-if (sx->peer_offered & PEER_OFFERED_PRDR)
+if (sx->peer_offered & OPTION_PRDR)
for (addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == PENDING_DEFER)
{
/* If it supports internationalised messages, and this meesage need that,
request it */
-if ( sx->peer_offered & PEER_OFFERED_UTF8
+if ( sx->peer_offered & OPTION_UTF8
&& addrlist->prop.utf8_msg
&& !addrlist->prop.utf8_downcvt
)
Ustrcpy(p, " SMTPUTF8"), p += 9;
#endif
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+ Ustrcpy(p, " REQUIRETLS") , p += 11;
+#endif
+
/* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
addr && address_count < sx->max_rcpt;
/* Add any DSN flags to the mail command */
-if (sx->peer_offered & PEER_OFFERED_DSN && !sx->dsn_all_lasthop)
+if (sx->peer_offered & OPTION_DSN && !sx->dsn_all_lasthop)
{
if (dsn_ret == dsn_ret_hdrs)
{ Ustrcpy(p, " RET=HDRS"); p += 9; }
/* Add any DSN flags to the rcpt command */
-if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
+if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
{
if (addr->dsn_flags & rf_dsnflags)
{
the delivery log line. */
if ( sx->addrlist->prop.utf8_msg
- && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & PEER_OFFERED_UTF8))
+ && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & OPTION_UTF8))
)
{
if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
}
#endif
- rc = smtp_write_command(&sx->outblock, pipelining_active,
+ rc = smtp_write_command(sx, pipelining_active ? SCMD_BUFFER : SCMD_FLUSH,
"MAIL FROM:<%s>%s\r\n", s, sx->buffer);
}
return -5;
case +1: /* Cmd was sent */
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
sx->ob->command_timeout))
{
if (errno == 0 && sx->buffer[0] == '4')
BOOL no_flush;
uschar * rcpt_addr;
- addr->dsn_aware = sx->peer_offered & PEER_OFFERED_DSN
+ if (tcp_out_fastopen && !f.tcp_out_fastopen_logged)
+ {
+ setflag(addr, af_tcp_fastopen_conn);
+ if (tcp_out_fastopen > 1) setflag(addr, af_tcp_fastopen);
+ }
+
+ addr->dsn_aware = sx->peer_offered & OPTION_DSN
? dsn_support_yes : dsn_support_no;
address_count++;
- no_flush = pipelining_active && !sx->verify && (!mua_wrapper || addr->next);
+ no_flush = pipelining_active && !sx->verify
+ && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
build_rcptcmd_options(sx, addr);
}
#endif
- count = smtp_write_command(&sx->outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
- rcpt_addr, sx->igquotstr, sx->buffer);
+ count = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
+ "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
if (count < 0) return -5;
if (count > 0)
}
} /* Loop for next address */
+f.tcp_out_fastopen_logged = TRUE;
sx->next_addr = addr;
return 0;
}
+#ifdef SUPPORT_TLS
+/*****************************************************
+* Proxy TLS connection for another transport process *
+******************************************************/
+/*
+Close the unused end of the pipe, fork once more, then use the given buffer
+as a staging area, and select on both the given fd and the TLS'd client-fd for
+data to read (per the coding in ip_recv() and fd_ready() this is legitimate).
+Do blocking full-size writes, and reads under a timeout. Once both input
+channels are closed, exit the process.
+
+Arguments:
+ ct_ctx tls context
+ buf space to use for buffering
+ bufsiz size of buffer
+ pfd pipe filedescriptor array; [0] is comms to proxied process
+ timeout per-read timeout, seconds
+*/
+
+void
+smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
+ int timeout)
+{
+fd_set rfds, efds;
+int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
+int rc, i, fd_bits, nbytes;
+
+close(pfd[1]);
+if ((rc = fork()))
+ {
+ DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+ _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+
+if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+set_process_info("proxying TLS connection for continued transport");
+FD_ZERO(&rfds);
+FD_SET(tls_out.active.sock, &rfds);
+FD_SET(pfd[0], &rfds);
+
+for (fd_bits = 3; fd_bits; )
+ {
+ time_t time_left = timeout;
+ time_t time_start = time(NULL);
+
+ /* wait for data */
+ efds = rfds;
+ do
+ {
+ struct timeval tv = { time_left, 0 };
+
+ rc = select(max_fd,
+ (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+
+ if (rc < 0 && errno == EINTR)
+ if ((time_left -= time(NULL) - time_start) > 0) continue;
+
+ if (rc <= 0)
+ {
+ DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
+ goto done;
+ }
+
+ if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
+ {
+ DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
+ FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
+ goto done;
+ }
+ }
+ while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
+
+ /* handle inbound data */
+ if (FD_ISSET(tls_out.active.sock, &rfds))
+ if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
+ {
+ fd_bits &= ~1;
+ FD_CLR(tls_out.active.sock, &rfds);
+ shutdown(pfd[0], SHUT_WR);
+ timeout = 5;
+ }
+ else
+ {
+ for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+ if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
+ }
+ else if (fd_bits & 1)
+ FD_SET(tls_out.active.sock, &rfds);
+
+ /* handle outbound data */
+ if (FD_ISSET(pfd[0], &rfds))
+ if ((rc = read(pfd[0], buf, bsize)) <= 0)
+ {
+ fd_bits = 0;
+ tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+ ct_ctx = NULL;
+ }
+ else
+ {
+ for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+ if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
+ goto done;
+ }
+ else if (fd_bits & 2)
+ FD_SET(pfd[0], &rfds);
+ }
+
+done:
+ if (f.running_in_test_harness) millisleep(100); /* let logging complete */
+ exim_exit(0, US"TLS proxy");
+}
+#endif
+
+
/*************************************************
* Deliver address list to given host *
*************************************************/
failed by one of them.
host host to deliver to
host_af AF_INET or AF_INET6
- port default TCP/IP port to use, in host byte order
+ defport default TCP/IP port to use if host does not specify, in host
+ byte order
interface interface to bind to, or NULL
tblock transport instance block
message_defer set TRUE if yield is OK, but all addresses were deferred
*/
static int
-smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
+smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
uschar *interface, transport_instance *tblock,
BOOL *message_defer, BOOL suppress_tls)
{
int yield = OK;
int save_errno;
int rc;
-time_t start_delivery_time = time(NULL);
+struct timeval start_delivery_time;
BOOL pass_message = FALSE;
uschar *message = NULL;
uschar new_message_id[MESSAGE_ID_LENGTH + 1];
-uschar *p;
smtp_context sx;
+gettimeofday(&start_delivery_time, NULL);
suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */
*message_defer = FALSE;
sx.addrlist = addrlist;
sx.host = host;
sx.host_af = host_af,
-sx.port = port;
+sx.port = defport;
sx.interface = interface;
sx.helo_data = NULL;
sx.tblock = tblock;
if (tblock->filter_command)
{
- BOOL rc;
- uschar fbuf[64];
- sprintf(CS fbuf, "%.50s transport", tblock->name);
- rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
- TRUE, DEFER, addrlist, fbuf, NULL);
transport_filter_timeout = tblock->filter_timeout;
/* On failure, copy the error to all addresses, abandon the SMTP call, and
yield ERROR. */
- if (!rc)
+ if (!transport_set_up_command(&transport_filter_argv,
+ tblock->filter_command, TRUE, DEFER, addrlist,
+ string_sprintf("%.50s transport", tblock->name), NULL))
{
set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
FALSE);
if ( transport_filter_argv
&& *transport_filter_argv
&& **transport_filter_argv
- && sx.peer_offered & PEER_OFFERED_CHUNKING
+ && sx.peer_offered & OPTION_CHUNKING
)
{
- sx.peer_offered &= ~PEER_OFFERED_CHUNKING;
+ sx.peer_offered &= ~OPTION_CHUNKING;
DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
}
}
+sx.first_addr = addrlist;
/* For messages that have more than the maximum number of envelope recipients,
we want to send several transactions down the same SMTP connection. (See
SEND_MESSAGE:
sx.from_addr = return_path;
-sx.first_addr = sx.sync_addr = addrlist;
+sx.sync_addr = sx.first_addr;
sx.ok = FALSE;
sx.send_rset = TRUE;
sx.completed_addr = FALSE;
-/* Initiate a message transfer. */
+/* If we are a continued-connection-after-verify the MAIL and RCPT
+commands were already sent; do not re-send but do mark the addrs as
+having been accepted up to RCPT stage. A traditional cont-conn
+always has a sequence number greater than one. */
-switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+if (continue_hostname && continue_sequence == 1)
{
- case 0: break;
- case -1: case -2: goto RESPONSE_FAILED;
- case -3: goto END_OFF;
- case -4: goto SEND_QUIT;
- default: goto SEND_FAILED;
- }
+ address_item * addr;
-/* If we are an MUA wrapper, abort if any RCPTs were rejected, either
-permanently or temporarily. We should have flushed and synced after the last
-RCPT. */
+ sx.peer_offered = smtp_peer_options;
+ sx.pending_MAIL = FALSE;
+ sx.ok = TRUE;
+ sx.next_addr = NULL;
-if (mua_wrapper)
+ for (addr = addrlist; addr; addr = addr->next)
+ addr->transport_return = PENDING_OK;
+ }
+else
{
- address_item *badaddr;
- for (badaddr = sx.first_addr; badaddr; badaddr = badaddr->next)
- if (badaddr->transport_return != PENDING_OK)
- {
- /*XXX could we find a better errno than 0 here? */
- set_errno_nohost(addrlist, 0, badaddr->message, FAIL,
- testflag(badaddr, af_pass_message));
- sx.ok = FALSE;
- break;
- }
+ /* Initiate a message transfer. */
+
+ 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;
+ }
+
+ /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
+ permanently or temporarily. We should have flushed and synced after the last
+ RCPT. */
+
+ if (mua_wrapper)
+ {
+ address_item * a;
+ unsigned cnt;
+
+ for (a = sx.first_addr, cnt = 0; a && cnt < sx.max_rcpt; a = a->next, cnt++)
+ if (a->transport_return != PENDING_OK)
+ {
+ /*XXX could we find a better errno than 0 here? */
+ set_errno_nohost(addrlist, 0, a->message, FAIL,
+ testflag(a, af_pass_message));
+ sx.ok = FALSE;
+ break;
+ }
+ }
}
/* If ok is TRUE, we know we have got at least one good recipient, and must now
If using CHUNKING, do not send a BDAT until we know how big a chunk we want
to send is. */
-if ( !(sx.peer_offered & PEER_OFFERED_CHUNKING)
+if ( !(sx.peer_offered & OPTION_CHUNKING)
&& (sx.ok || (pipelining_active && !mua_wrapper)))
{
- int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n");
+ 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))
well as body. Set the appropriate timeout value to be used for each chunk.
(Haven't been able to make it work using select() for writing yet.) */
-if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
+if (!(sx.peer_offered & OPTION_CHUNKING) && !sx.ok)
{
/* Save the first address of the next batch. */
sx.first_addr = sx.next_addr;
else
{
transport_ctx tctx = {
+ {sx.cctx.sock}, /*XXX will this need TLS info? */
tblock,
addrlist,
US".", US"..", /* Escaping strings */
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 & PEER_OFFERED_CHUNKING)
+ if (sx.peer_offered & OPTION_CHUNKING)
{
tctx.check_string = tctx.escape_string = NULL;
tctx.options |= topt_use_bdat;
transport_write_timeout = sx.ob->data_timeout;
smtp_command = US"sending data block"; /* For error messages */
DEBUG(D_transport|D_v)
- if (sx.peer_offered & PEER_OFFERED_CHUNKING)
+ if (sx.peer_offered & OPTION_CHUNKING)
debug_printf(" will write message using CHUNKING\n");
else
debug_printf(" SMTP>> writing message and terminating \".\"\n");
transport_count = 0;
#ifndef DISABLE_DKIM
- sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim);
+ dkim_exim_sign_init();
+# ifdef EXPERIMENTAL_ARC
+ {
+ uschar * s = sx.ob->arc_sign;
+ if (s)
+ {
+ if (!(sx.ob->dkim.arc_signspec = s = expand_string(s)))
+ {
+ if (!f.expand_string_forcedfail)
+ {
+ message = US"failed to expand arc_sign";
+ sx.ok = FALSE;
+ goto SEND_FAILED;
+ }
+ }
+ else if (*s)
+ {
+ /* Ask dkim code to hash the body for ARC */
+ (void) arc_ams_setup_sign_bodyhash();
+ sx.ob->dkim.force_bodyhash = TRUE;
+ }
+ }
+ }
+# endif
+ sx.ok = dkim_transport_write_message(&tctx, &sx.ob->dkim, CUSS &message);
#else
- sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
+ sx.ok = transport_write_message(&tctx, 0);
#endif
/* transport_write_message() uses write() because it is called from other
Or, when CHUNKING, it can be a protocol-detected failure. */
if (!sx.ok)
- goto RESPONSE_FAILED;
+ if (message) goto SEND_FAILED;
+ else goto RESPONSE_FAILED;
/* We used to send the terminating "." explicitly here, but because of
buffering effects at both ends of TCP/IP connections, you don't gain
smtp_command = US"end of data";
- if (sx.peer_offered & PEER_OFFERED_CHUNKING && sx.cmd_count > 1)
+ if (sx.peer_offered & 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))
* with per non-PRDR. */
if(sx.prdr_active)
{
- sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '3',
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '3',
sx.ob->final_timeout);
if (!sx.ok && errno == 0) switch(sx.buffer[0])
{
if (!sx.lmtp)
{
- sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
sx.ob->final_timeout);
if (!sx.ok && errno == 0 && sx.buffer[0] == '4')
{
if (sx.ok)
{
int flag = '=';
- int delivery_time = (int)(time(NULL) - start_delivery_time);
+ struct timeval delivery_time;
int len;
- uschar *conf = NULL;
+ uschar * conf = NULL;
+ timesince(&delivery_time, &start_delivery_time);
sx.send_rset = FALSE;
pipelining_active = FALSE;
{
const uschar *s = string_printing(sx.buffer);
/* deconst cast ok here as string_printing was checked to have alloc'n'copied */
- conf = (s == sx.buffer)? (uschar *)string_copy(s) : US s;
+ conf = (s == sx.buffer)? US string_copy(s) : US s;
}
/* Process all transported addresses - for LMTP or PRDR, read a status for
if (sx.lmtp)
#endif
{
- if (!smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
+ if (!smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
sx.ob->final_timeout))
{
if (errno != 0 || sx.buffer[0] == 0) goto RESPONSE_FAILED;
actual host that was used. */
addr->transport_return = OK;
- addr->more_errno = delivery_time;
+ addr->more_errno = delivery_time.tv_sec;
+ addr->delivery_usec = delivery_time.tv_usec;
addr->host_used = host;
addr->special_action = flag;
addr->message = conf;
+
+ if (sx.pipelining_used) setflag(addr, af_pipelining);
#ifndef DISABLE_PRDR
- if (sx.prdr_active) addr->flags |= af_prdr_used;
+ if (sx.prdr_active) setflag(addr, af_prdr_used);
#endif
- if (sx.peer_offered & PEER_OFFERED_CHUNKING) addr->flags |= af_chunking_used;
+ if (sx.peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
flag = '-';
#ifndef DISABLE_PRDR
else
sprintf(CS sx.buffer, "%.500s\n", addr->unique);
- DEBUG(D_deliver) debug_printf("journalling %s\n", sx.buffer);
+ DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx.buffer);
len = Ustrlen(CS sx.buffer);
if (write(journal_fd, sx.buffer, len) != len)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
#ifndef DISABLE_PRDR
if (sx.prdr_active)
{
+ const uschar * overall_message;
+
/* PRDR - get the final, overall response. For any non-success
upgrade all the address statuses. */
- sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
+
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
sx.ob->final_timeout);
if (!sx.ok)
{
goto RESPONSE_FAILED;
}
- /* Update the journal, or setup retry. */
+ /* Append the overall response to the individual PRDR response for logging
+ and update the journal, or setup retry. */
+
+ overall_message = string_printing(sx.buffer);
+ for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
+ if (addr->transport_return == OK)
+ addr->message = string_sprintf("%s\\n%s", addr->message, overall_message);
+
for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
if (addr->transport_return == OK)
{
{
save_errno = errno;
code = '4';
- message = US string_sprintf("send() to %s [%s] failed: %s",
- host->name, host->address, strerror(save_errno));
+ message = string_sprintf("send() to %s [%s] failed: %s",
+ host->name, host->address, message ? message : US strerror(save_errno));
sx.send_quit = FALSE;
goto FAILED;
}
set_rc = DEFER;
if (save_errno > 0)
message = US string_sprintf("%s: %s", message, strerror(save_errno));
- if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message);
- msglog_line(host, message);
+
+ write_logs(host, message, sx.first_addr ? sx.first_addr->basic_errno : 0);
+
*message_defer = TRUE;
}
}
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, continue_more, yield, sx.first_addr ? "not " : "");
+ sx.send_rset, f.continue_more, yield, sx.first_addr ? "not " : "");
if (sx.completed_addr && sx.ok && sx.send_quit)
{
t_compare.current_sender_address = sender_address;
if ( sx.first_addr != NULL
- || continue_more
- || ( ( tls_out.active < 0
- || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
+ || f.continue_more
+ || (
+#ifdef SUPPORT_TLS
+ ( tls_out.active.sock < 0 && !continue_proxy_cipher
+ || verify_check_given_host(CUSS &sx.ob->hosts_nopass_tls, host) != OK
)
&&
+#endif
transport_check_waiting(tblock->name, host->name,
tblock->connection_max_messages, new_message_id, &more,
(oicf)smtp_are_same_identities, (void*)&t_compare)
BOOL pass_message;
if (sx.send_rset)
- if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0))
+ if (! (sx.ok = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0))
{
msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
- host->address, strerror(save_errno));
+ host->address, strerror(errno));
sx.send_quit = FALSE;
}
- else if (! (sx.ok = smtp_read_response(&sx.inblock, sx.buffer,
+ else if (! (sx.ok = smtp_read_response(&sx, sx.buffer,
sizeof(sx.buffer), '2', sx.ob->command_timeout)))
{
int code;
if (sx.ok)
{
- if (sx.first_addr != NULL) /* More addresses still to be sent */
+#ifdef SUPPORT_TLS
+ int pfd[2];
+#endif
+ int socket_fd = sx.cctx.sock;
+
+
+ if (sx.first_addr != NULL) /* More addresses still to be sent */
{ /* in this run of the transport */
continue_sequence++; /* Causes * in logging */
goto SEND_MESSAGE;
}
- if (continue_more) return yield; /* More addresses for another run */
- /* Pass the socket to a new Exim process. Before doing so, 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. */
+ /* 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. */
#ifdef SUPPORT_TLS
- if (tls_out.active >= 0)
- {
- tls_close(FALSE, TRUE);
- smtp_peer_options = smtp_peer_options_wrap;
- sx.ok = !sx.smtps
- && smtp_write_command(&sx.outblock, FALSE,
- "EHLO %s\r\n", sx.helo_data) >= 0
- && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
- '2', sx.ob->command_timeout);
- }
+ if (tls_out.active.sock >= 0)
+ if ( f.continue_more
+ || verify_check_given_host(CUSS &sx.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.cctx.tls_ctx = NULL;
+ 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', sx.ob->command_timeout);
+
+ if (sx.ok && f.continue_more)
+ return yield; /* More addresses for another run */
+ }
+ else
+ {
+ /* 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
+ );
+ }
+ else
#endif
+ if (f.continue_more)
+ return yield; /* More addresses for another run */
/* If the socket is successfully passed, we mustn't send QUIT (or
indeed anything!) from here. */
propagate it from the initial
*/
if (sx.ok && transport_pass_socket(tblock->name, host->name,
- host->address, new_message_id, sx.inblock.sock))
+ host->address, new_message_id, socket_fd))
+ {
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. */
+#ifdef SUPPORT_TLS
+ if (tls_out.active.sock >= 0)
+ {
+ int pid = fork();
+ if (pid == 0) /* child; fork again to disconnect totally */
+ {
+ if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+ /* does not return */
+ smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd,
+ sx.ob->command_timeout);
+ }
+
+ if (pid > 0) /* parent */
+ {
+ DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+ close(pfd[0]);
+ /* 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;
+ return yield;
+ }
+ log_write(0, LOG_PANIC_DIE, "fork failed");
+ }
+#endif
+ }
}
/* If RSET failed and there are addresses left, they get deferred. */
-
- else set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
+ else
+ set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
#ifdef EXPERIMENTAL_DSN_INFO
, sx.smtp_greeting, sx.helo_response
#endif
operation, the old commented-out code was removed on 17-Sep-99. */
SEND_QUIT:
-if (sx.send_quit) (void)smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+if (sx.send_quit) (void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
END_OFF:
#ifdef SUPPORT_TLS
-tls_close(FALSE, TRUE);
+tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+sx.cctx.tls_ctx = NULL;
#endif
/* Close the socket, and return the appropriate value, first setting
specified in the transports, and therefore not visible at top level, in which
case continue_more won't get set. */
-HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx.send_quit)
{
- shutdown(sx.outblock.sock, SHUT_WR);
- if (fcntl(sx.inblock.sock, F_SETFL, O_NONBLOCK) == 0)
- for (rc = 16; read(sx.inblock.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
+ shutdown(sx.cctx.sock, SHUT_WR);
+ if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+ for (rc = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
rc--; /* drain socket */
}
-(void)close(sx.inblock.sock);
+(void)close(sx.cctx.sock);
#ifndef DISABLE_EVENT
(void) event_raise(tblock->event_action, US"tcp:close", NULL);
{
smtp_transport_options_block *ob =
(smtp_transport_options_block *)tblock->options_block;
-smtp_inblock inblock;
-smtp_outblock outblock;
+client_conn_ctx cctx;
+smtp_context sx;
uschar buffer[256];
uschar inbuffer[4096];
uschar outbuffer[16];
-inblock.sock = fileno(stdin);
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
-
-outblock.sock = inblock.sock;
-outblock.buffersize = sizeof(outbuffer);
-outblock.buffer = outbuffer;
-outblock.ptr = outbuffer;
-outblock.cmd_count = 0;
-outblock.authenticating = FALSE;
-
-(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
-(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout);
-(void)close(inblock.sock);
+/*XXX really we need an active-smtp-client ctx, rather than assuming stdout */
+cctx.sock = fileno(stdin);
+cctx.tls_ctx = cctx.sock == tls_out.active.sock ? tls_out.active.tls_ctx : NULL;
+
+sx.inblock.cctx = &cctx;
+sx.inblock.buffer = inbuffer;
+sx.inblock.buffersize = sizeof(inbuffer);
+sx.inblock.ptr = inbuffer;
+sx.inblock.ptrend = inbuffer;
+
+sx.outblock.cctx = &cctx;
+sx.outblock.buffersize = sizeof(outbuffer);
+sx.outblock.buffer = outbuffer;
+sx.outblock.ptr = outbuffer;
+sx.outblock.cmd_count = 0;
+sx.outblock.authenticating = FALSE;
+
+(void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
+(void)smtp_read_response(&sx, buffer, sizeof(buffer), '2', ob->command_timeout);
+(void)close(cctx.sock);
}
address_item *addrlist) /* addresses we are working on */
{
int cutoff_retry;
-int port;
+int defport;
int hosts_defer = 0;
int hosts_fail = 0;
int hosts_looked_up = 0;
smtp_transport_options_block *ob =
(smtp_transport_options_block *)(tblock->options_block);
host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+host_item *host;
DEBUG(D_transport)
{
{
debug_printf("hostlist:\n");
for (host = hostlist; host; host = host->next)
- debug_printf(" %s:%d\n", host->name, host->port);
+ debug_printf(" '%s' IP %s port %d\n", host->name, host->address, host->port);
}
- if (continue_hostname) debug_printf("already connected to %s [%s]\n",
- continue_hostname, continue_host_address);
+ if (continue_hostname)
+ debug_printf("already connected to %s [%s] (on fd %d)\n",
+ continue_hostname, continue_host_address,
+ cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
}
/* Set the flag requesting that these hosts be added to the waiting
a host list with hosts_override set, use the host list supplied with the
transport. It is an error for this not to exist. */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+ ob->tls_tempfail_tryclear = FALSE; /*XXX surely we should have a local for this
+ rather than modifying the transport? */
+#endif
+
if (!hostlist || (ob->hosts_override && ob->hosts))
{
if (!ob->hosts)
{
addrlist->message = string_sprintf("failed to expand list of hosts "
"\"%s\" in %s transport: %s", s, tblock->name, expand_string_message);
- addrlist->transport_return = search_find_defer ? DEFER : PANIC;
+ addrlist->transport_return = f.search_find_defer ? DEFER : PANIC;
return FALSE; /* Only top address has status */
}
DEBUG(D_transport) debug_printf("expanded list of hosts \"%s\" to "
/* Sort out the default port. */
-if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
+if (!smtp_get_port(ob->port, addrlist, &defport, tid)) return FALSE;
/* For each host-plus-IP-address on the list:
{
host_item *nexthost = NULL;
int unexpired_hosts_tried = 0;
+ BOOL continue_host_tried = FALSE;
+retry_non_continued:
for (host = hostlist;
host
&& unexpired_hosts_tried < ob->hosts_max_try
/* Find by name if so configured, or if it's an IP address. We don't
just copy the IP address, because we need the test-for-local to happen. */
- flags = HOST_FIND_BY_A;
+ flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
commonly points to a configuration error, but the best action is still
to carry on for the next host. */
- if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_FAILED)
+ if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_SECURITY || rc == HOST_FIND_FAILED)
{
retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
expired = FALSE;
{
if (addr->transport_return != DEFER) continue;
addr->basic_errno = ERRNO_UNKNOWNHOST;
- addr->message =
- string_sprintf("failed to lookup IP address for %s", host->name);
+ addr->message = string_sprintf(
+ rc == HOST_FIND_SECURITY
+ ? "lookup of IP address for %s was insecure"
+ : "failed to lookup IP address for %s",
+ host->name);
}
continue;
}
result of the lookup. Set expired FALSE, to save the outer loop executing
twice. */
- if ( continue_hostname
- && ( Ustrcmp(continue_hostname, host->name) != 0
- || Ustrcmp(continue_host_address, host->address) != 0
- ) )
- {
- expired = FALSE;
- continue; /* With next host */
- }
+ if (continue_hostname)
+ if ( Ustrcmp(continue_hostname, host->name) != 0
+ || Ustrcmp(continue_host_address, host->address) != 0
+ )
+ {
+ expired = FALSE;
+ continue; /* With next host */
+ }
+ else
+ continue_host_tried = TRUE;
/* Reset the default next host in case a multihomed host whose addresses
are not looked up till just above added to the host list. */
were not in it. We don't want to hold up all SMTP deliveries! Except when
doing a two-stage queue run, don't do this if forcing. */
- if ((!deliver_force || queue_2stage) && (queue_smtp ||
+ if ((!f.deliver_force || f.queue_2stage) && (f.queue_smtp ||
match_isinlist(addrlist->domain,
(const uschar **)&queue_smtp_domains, 0,
&domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
the default. */
pistring = string_sprintf(":%d", host->port == PORT_NONE
- ? port : host->port);
+ ? defport : host->port);
if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
/* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
host_is_expired = retry_check_address(addrlist->domain, host, pistring,
incl_ip, &retry_host_key, &retry_message_key);
- DEBUG(D_transport) debug_printf("%s [%s]%s status = %s\n", host->name,
+ DEBUG(D_transport) debug_printf("%s [%s]%s retry-status = %s\n", host->name,
(host->address == NULL)? US"" : host->address, pistring,
(host->status == hstatus_usable)? "usable" :
(host->status == hstatus_unusable)? "unusable" :
{
case hwhy_retry: hosts_retry++; break;
case hwhy_failed: hosts_fail++; break;
+ case hwhy_insecure:
case hwhy_deferred: hosts_defer++; break;
}
{
if ( !host->address
|| host->status != hstatus_unusable_expired
- || host->last_try > received_time)
+ || host->last_try > received_time.tv_sec)
continue;
DEBUG(D_transport) debug_printf("trying expired host %s [%s]%s\n",
host->name, host->address, pistring);
sending the message down a pre-existing connection. */
if ( !continue_hostname
- && verify_check_given_host(&ob->serialize_hosts, host) == OK)
+ && verify_check_given_host(CUSS &ob->serialize_hosts, host) == OK)
{
serialize_key = string_sprintf("host-serialize-%s", host->name);
if (!enq_start(serialize_key, 1))
/* This is not for real; don't do the delivery. If there are
any remaining hosts, list them. */
- if (dont_deliver)
+ if (f.dont_deliver)
{
host_item *host2;
set_errno_nohost(addrlist, 0, NULL, OK, FALSE);
/* Attempt the delivery. */
total_hosts_tried++;
- rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
&message_defer, FALSE);
/* Yield is one of:
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL
&& first_addr->basic_errno != ERRNO_TLSFAILURE)
- write_logs(first_addr, host);
+ write_logs(host, first_addr->message, first_addr->basic_errno);
#ifndef DISABLE_EVENT
if (rc == DEFER)
if ( rc == DEFER
&& first_addr->basic_errno == ERRNO_TLSFAILURE
&& ob->tls_tempfail_tryclear
- && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+ && verify_check_given_host(CUSS &ob->hosts_require_tls, host) != OK
)
{
- log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
- "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+ log_write(0, LOG_MAIN,
+ "%s: delivering unencrypted to H=%s [%s] (not in hosts_require_tls)",
+ first_addr->message, host->name, host->address);
first_addr = prepare_addresses(addrlist, host);
- rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
&message_defer, TRUE);
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
- write_logs(first_addr, host);
+ write_logs(host, first_addr->message, first_addr->basic_errno);
# ifndef DISABLE_EVENT
if (rc == DEFER)
deferred_event_raise(first_addr, host);
for (last_rule = retry->rules;
last_rule->next;
last_rule = last_rule->next);
- timedout = time(NULL) - received_time > last_rule->timeout;
+ timedout = time(NULL) - received_time.tv_sec > last_rule->timeout;
}
else timedout = TRUE; /* No rule => timed out */
"hosts_max_try (message older than host's retry time)\n");
}
}
+ if (f.running_in_test_harness) millisleep(500); /* let server debug out */
} /* End of loop for trying multiple hosts. */
+ /* If we failed to find a matching host in the list, for an already-open
+ connection, just close it and start over with the list. This can happen
+ for routing that changes from run to run, or big multi-IP sites with
+ round-robin DNS. */
+
+ if (continue_hostname && !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");
+#ifdef SUPPORT_TLS
+ /* A TLS conn could be open for a cutthrough, but not for a plain continued-
+ transport */
+/*XXX doublecheck that! */
+
+ if (cutthrough.cctx.sock >= 0 && cutthrough.is_tls)
+ {
+ (void) tls_write(cutthrough.cctx.tls_ctx, US"QUIT\r\n", 6, FALSE);
+ tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ cutthrough.cctx.tls_ctx = NULL;
+ cutthrough.is_tls = FALSE;
+ }
+ else
+#else
+ (void) write(fd, US"QUIT\r\n", 6);
+#endif
+ (void) close(fd);
+ cutthrough.cctx.sock = -1;
+ continue_hostname = NULL;
+ goto retry_non_continued;
+ }
+
/* This is the end of the loop that repeats iff expired is TRUE and
ob->delay_after_cutoff is FALSE. The second time round we will
try those hosts that haven't been tried since the message arrived. */
setflag(addr, af_retry_skipped);
}
- if (queue_smtp) /* no deliveries attempted */
+ if (f.queue_smtp) /* no deliveries attempted */
{
addr->transport_return = DEFER;
addr->basic_errno = 0;
return TRUE; /* Each address has its status */
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of transport/smtp.c */