* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
to be publicly visible; these are flagged with opt_public. */
optionlist smtp_transport_options[] = {
+ { "*expand_multi_domain", opt_stringptr | opt_hidden | opt_public,
+ (void *)offsetof(transport_instance, expand_multi_domain) },
+ { "*expand_retry_include_ip_address", opt_stringptr | opt_hidden,
+ (void *)(offsetof(smtp_transport_options_block, expand_retry_include_ip_address)) },
+
{ "address_retry_include_sender", opt_bool,
(void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
{ "allow_localhost", opt_bool,
(void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
#ifndef DISABLE_DKIM
{ "dkim_canon", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim_canon) },
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
{ "dkim_domain", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim_domain) },
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
{ "dkim_private_key", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim_private_key) },
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
{ "dkim_selector", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim_selector) },
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_selector) },
{ "dkim_sign_headers", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim_sign_headers) },
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) },
{ "dkim_strict", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim_strict) },
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
#endif
{ "dns_qualify_single", opt_bool,
(void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
{ "dns_search_parents", opt_bool,
(void *)offsetof(smtp_transport_options_block, dns_search_parents) },
{ "dnssec_request_domains", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dnssec_request_domains) },
+ (void *)offsetof(smtp_transport_options_block, dnssec.request) },
{ "dnssec_require_domains", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dnssec_require_domains) },
+ (void *)offsetof(smtp_transport_options_block, dnssec.require) },
{ "dscp", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dscp) },
{ "fallback_hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, final_timeout) },
{ "gethostbyname", opt_bool,
(void *)offsetof(smtp_transport_options_block, gethostbyname) },
-#ifdef SUPPORT_TLS
- /* These are no longer honoured, as of Exim 4.80; for now, we silently
- ignore; a later release will warn, and a later-still release will remove
- these options, so that using them becomes an error. */
- { "gnutls_require_kx", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) },
- { "gnutls_require_mac", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, gnutls_require_mac) },
- { "gnutls_require_protocols", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, gnutls_require_proto) },
-#endif
{ "helo_data", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, helo_data) },
{ "hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_override) },
{ "hosts_randomize", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_randomize) },
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_OCSP)
+#if defined(SUPPORT_TLS) && !defined(DISABLE_OCSP)
{ "hosts_request_ocsp", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_request_ocsp) },
#endif
{ "hosts_require_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
#ifdef SUPPORT_TLS
-# if defined EXPERIMENTAL_OCSP
+# ifdef EXPERIMENTAL_DANE
+ { "hosts_require_dane", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
+# endif
+# ifndef DISABLE_OCSP
{ "hosts_require_ocsp", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
# endif
#endif
{ "hosts_try_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
-#ifdef EXPERIMENTAL_PRDR
+ { "hosts_try_chunking", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+ { "hosts_try_dane", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
+#endif
+#ifndef DISABLE_PRDR
{ "hosts_try_prdr", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
#endif
(void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
{ "max_rcpt", opt_int | opt_public,
(void *)offsetof(transport_instance, max_addresses) },
- { "multi_domain", opt_bool | opt_public,
+ { "multi_domain", opt_expand_bool | opt_public,
(void *)offsetof(transport_instance, multi_domain) },
{ "port", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, port) },
{ "protocol", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, protocol) },
- { "retry_include_ip_address", opt_bool,
+ { "retry_include_ip_address", opt_expand_bool,
(void *)offsetof(smtp_transport_options_block, retry_include_ip_address) },
{ "serialize_hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, serialize_hosts) },
{ "size_addition", opt_int,
(void *)offsetof(smtp_transport_options_block, size_addition) }
+#ifdef SUPPORT_SOCKS
+ ,{ "socks_proxy", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, socks_proxy) }
+#endif
#ifdef SUPPORT_TLS
,{ "tls_certificate", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_certificate) },
(void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
{ "tls_try_verify_hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_try_verify_hosts) },
+ { "tls_verify_cert_hostnames", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block,tls_verify_cert_hostnames)},
{ "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) }
#endif
-#ifdef EXPERIMENTAL_TPDA
- ,{ "tpda_host_defer_action", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tpda_host_defer_action) },
-#endif
};
/* Size of the options list. An extern variable has to be used so that its
NULL, /* serialize_hosts */
NULL, /* hosts_try_auth */
NULL, /* hosts_require_auth */
-#ifdef EXPERIMENTAL_PRDR
- NULL, /* hosts_try_prdr */
+ US"*", /* hosts_try_chunking */
+#ifdef EXPERIMENTAL_DANE
+ NULL, /* hosts_try_dane */
+ NULL, /* hosts_require_dane */
+#endif
+#ifndef DISABLE_PRDR
+ US"*", /* hosts_try_prdr */
#endif
-#ifdef EXPERIMENTAL_OCSP
- US"*", /* hosts_request_ocsp */
+#ifndef DISABLE_OCSP
+ US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */
NULL, /* hosts_require_ocsp */
#endif
NULL, /* hosts_require_tls */
NULL, /* hosts_avoid_tls */
- US"*", /* hosts_verify_avoid_tls */
+ NULL, /* hosts_verify_avoid_tls */
NULL, /* hosts_avoid_pipelining */
NULL, /* hosts_avoid_esmtp */
NULL, /* hosts_nopass_tls */
FALSE, /* gethostbyname */
TRUE, /* dns_qualify_single */
FALSE, /* dns_search_parents */
- NULL, /* dnssec_request_domains */
- NULL, /* dnssec_require_domains */
+ { 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 */
+#ifdef SUPPORT_SOCKS
+ ,NULL /* socks_proxy */
+#endif
#ifdef SUPPORT_TLS
,NULL, /* tls_certificate */
NULL, /* tls_crl */
NULL, /* tls_privatekey */
NULL, /* tls_require_ciphers */
- NULL, /* gnutls_require_kx */
- NULL, /* gnutls_require_mac */
- NULL, /* gnutls_require_proto */
NULL, /* tls_sni */
- NULL, /* tls_verify_certificates */
+ US"system", /* tls_verify_certificates */
EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
/* tls_dh_min_bits */
TRUE, /* tls_tempfail_tryclear */
NULL, /* tls_verify_hosts */
- NULL /* tls_try_verify_hosts */
+ US"*", /* tls_try_verify_hosts */
+ US"*" /* tls_verify_cert_hostnames */
#endif
#ifndef DISABLE_DKIM
- ,NULL, /* dkim_canon */
- NULL, /* dkim_domain */
- NULL, /* dkim_private_key */
- NULL, /* dkim_selector */
- NULL, /* dkim_sign_headers */
- NULL /* dkim_strict */
-#endif
-#ifdef EXPERIMENTAL_TPDA
- ,NULL /* tpda_host_defer_action */
+ , {NULL, /* dkim_canon */
+ NULL, /* dkim_domain */
+ NULL, /* dkim_private_key */
+ NULL, /* dkim_selector */
+ NULL, /* dkim_sign_headers */
+ NULL} /* dkim_strict */
#endif
};
+/* some DSN flags for use later */
+
+static int rf_list[] = {rf_notify_never, rf_notify_success,
+ rf_notify_failure, rf_notify_delay };
+
+static uschar *rf_names[] = { US"NEVER", US"SUCCESS", US"FAILURE", US"DELAY" };
+
+
/* Local statics */
msg to put in each address's message field
rc to put in each address's transport_return field
pass_message if TRUE, set the "pass message" flag in the address
+ host if set, mark addrs as having used this host
+ smtp_greeting from peer
+ helo_response from peer
If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
static void
set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
- BOOL pass_message)
+ BOOL pass_message, host_item * host
+#ifdef EXPERIMENTAL_DSN_INFO
+ , const uschar * smtp_greeting, const uschar * helo_response
+#endif
+ )
{
address_item *addr;
int orvalue = 0;
errno_value = ETIMEDOUT;
orvalue = RTEF_CTOUT;
}
-for (addr = addrlist; addr != NULL; addr = addr->next)
- {
- if (addr->transport_return < PENDING) continue;
- addr->basic_errno = errno_value;
- addr->more_errno |= orvalue;
- if (msg != NULL)
+for (addr = addrlist; addr; addr = addr->next)
+ if (addr->transport_return >= PENDING)
{
- addr->message = msg;
- if (pass_message) setflag(addr, af_pass_message);
+ addr->basic_errno = errno_value;
+ addr->more_errno |= orvalue;
+ if (msg != NULL)
+ {
+ addr->message = msg;
+ if (pass_message) setflag(addr, af_pass_message);
+ }
+ addr->transport_return = rc;
+ if (host)
+ {
+ addr->host_used = host;
+#ifdef EXPERIMENTAL_DSN_INFO
+ if (smtp_greeting)
+ {uschar * s = Ustrchr(smtp_greeting, '\n'); if (s) *s = '\0';}
+ addr->smtp_greeting = smtp_greeting;
+
+ if (helo_response)
+ {uschar * s = Ustrchr(helo_response, '\n'); if (s) *s = '\0';}
+ addr->helo_response = helo_response;
+#endif
+ }
}
- addr->transport_return = rc;
- }
}
+static void
+set_errno_nohost(address_item *addrlist, int errno_value, uschar *msg, int rc,
+ BOOL pass_message)
+{
+set_errno(addrlist, errno_value, msg, rc, pass_message, NULL
+#ifdef EXPERIMENTAL_DSN_INFO
+ , NULL, NULL
+#endif
+ );
+}
/*************************************************
Returns: TRUE if an SMTP "QUIT" command should be sent, else FALSE
*/
-static BOOL check_response(host_item *host, int *errno_value, int more_errno,
+static BOOL
+check_response(host_item *host, int *errno_value, int more_errno,
uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
{
uschar *pl = US"";
if (*errno_value == ETIMEDOUT)
{
- *message = US string_sprintf("SMTP timeout while connected to %s [%s] "
- "after %s%s", host->name, host->address, pl, smtp_command);
+ *message = US string_sprintf("SMTP timeout after %s%s",
+ pl, smtp_command);
if (transport_count > 0)
*message = US string_sprintf("%s (%d bytes written)", *message,
transport_count);
if (*errno_value == ERRNO_SMTPFORMAT)
{
- uschar *malfresp = string_printing(buffer);
+ const uschar *malfresp = string_printing(buffer);
while (isspace(*malfresp)) malfresp++;
- if (*malfresp == 0)
- *message = string_sprintf("Malformed SMTP reply (an empty line) from "
- "%s [%s] in response to %s%s", host->name, host->address, pl,
- smtp_command);
- else
- *message = string_sprintf("Malformed SMTP reply from %s [%s] in response "
- "to %s%s: %s", host->name, host->address, pl, smtp_command, malfresp);
+ *message = *malfresp == 0
+ ? string_sprintf("Malformed SMTP reply (an empty line) "
+ "in response to %s%s", pl, smtp_command)
+ : string_sprintf("Malformed SMTP reply in response to %s%s: %s",
+ pl, smtp_command, malfresp);
return FALSE;
}
return FALSE;
}
+#ifdef SUPPORT_I18N
+/* Handle lack of advertised SMTPUTF8, for international message */
+if (*errno_value == ERRNO_UTF8_FWD)
+ {
+ *message = US string_sprintf("utf8 support required but not offered for forwarding");
+ DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message);
+ return TRUE;
+ }
+#endif
+
/* Handle error responses from the remote mailer. */
if (buffer[0] != 0)
{
- uschar *s = string_printing(buffer);
+ const uschar *s = string_printing(buffer);
*message = US string_sprintf("SMTP error from remote mail server after %s%s: "
- "host %s [%s]: %s", pl, smtp_command, host->name, host->address, s);
+ "%s", pl, smtp_command, s);
*pass_message = TRUE;
*yield = buffer[0];
return TRUE;
if (*errno_value == 0 || *errno_value == ECONNRESET)
{
*errno_value = ERRNO_SMTPCLOSED;
- *message = US string_sprintf("Remote host %s [%s] closed connection "
- "in response to %s%s", host->name, host->address, pl, smtp_command);
+ *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);
static void
write_logs(address_item *addr, host_item *host)
{
-if (addr->message != NULL)
+uschar * message = string_sprintf("H=%s [%s]", host->name, host->address);
+
+if (LOGGING(outgoing_port))
+ message = string_sprintf("%s:%d", message,
+ host->port == PORT_NONE ? 25 : host->port);
+if (addr->message)
{
- uschar *message = addr->message;
+ 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);
}
else
{
- uschar *msg =
- ((log_extra_selector & LX_outgoing_port) != 0)?
- string_sprintf("%s [%s]:%d", host->name, host->address,
- (host->port == PORT_NONE)? 25 : host->port)
- :
- string_sprintf("%s [%s]", host->name, host->address);
- log_write(0, LOG_MAIN, "%s %s", msg, strerror(addr->basic_errno));
- deliver_msglog("%s %s %s\n", tod_stamp(tod_log), msg,
- strerror(addr->basic_errno));
+ 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);
}
}
+static void
+msglog_line(host_item * host, uschar * message)
+{
+deliver_msglog("%s H=%s [%s] %s\n", tod_stamp(tod_log),
+ host->name, host->address, message);
+}
-#ifdef EXPERIMENTAL_TPDA
+
+#ifndef DISABLE_EVENT
/*************************************************
* Post-defer action *
*************************************************/
It might, for example, be used to write to the database log.
Arguments:
- ob transport options block
addr the address item containing error information
host the current host
*/
static void
-tpda_deferred(smtp_transport_options_block *ob, address_item *addr, host_item *host)
+deferred_event_raise(address_item *addr, host_item *host)
{
-uschar *action = ob->tpda_host_defer_action;
+uschar * action = addr->transport->event_action;
+const uschar * save_domain;
+uschar * save_local;
+
if (!action)
- return;
-
-tpda_delivery_ip = string_copy(host->address);
-tpda_delivery_port = (host->port == PORT_NONE)? 25 : host->port;
-tpda_delivery_fqdn = string_copy(host->name);
-tpda_delivery_local_part = string_copy(addr->local_part);
-tpda_delivery_domain = string_copy(addr->domain);
-tpda_defer_errno = addr->basic_errno;
-
-tpda_defer_errstr = addr->message
- ? addr->basic_errno > 0
- ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
- : string_copy(addr->message)
- : addr->basic_errno > 0
- ? string_copy(US strerror(addr->basic_errno))
- : NULL;
+ return;
-DEBUG(D_transport)
- debug_printf(" TPDA(host defer): tpda_host_defer_action=|%s| tpda_delivery_IP=%s\n",
- action, tpda_delivery_ip);
+save_domain = deliver_domain;
+save_local = deliver_localpart;
+
+/*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;
-if (!expand_string(action) && *expand_string_message)
- log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_defer_action in %s: %s\n",
- transport_name, expand_string_message);
+deliver_domain = addr->domain;
+deliver_localpart = addr->local_part;
+
+(void) event_raise(action, US"msg:host:defer",
+ addr->message
+ ? addr->basic_errno > 0
+ ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
+ : string_copy(addr->message)
+ : addr->basic_errno > 0
+ ? string_copy(US strerror(addr->basic_errno))
+ : NULL);
+
+deliver_localpart = save_local;
+deliver_domain = save_domain;
router_name = transport_name = NULL;
}
#endif
-
-
/*************************************************
* Synchronize SMTP responses *
*************************************************/
}
errno = save_errno;
}
+
+ if (pending_DATA) count--; /* Number of RCPT responses to come */
+ while (count-- > 0) /* Mark any pending addrs with the host used */
+ {
+ while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+ addr->host_used = host;
+ addr = addr->next;
+ }
return -3;
}
}
while (addr->transport_return != PENDING_DEFER) addr = addr->next;
/* The address was accepted */
+ addr->host_used = host;
if (smtp_read_response(inblock, buffer, buffsize, '2', timeout))
{
else if (errno == ETIMEDOUT)
{
- int save_errno = errno;
- uschar *message = string_sprintf("SMTP timeout while connected to %s [%s] "
- "after RCPT TO:<%s>", host->name, host->address,
- transport_rcpt_address(addr, include_affixes));
- set_errno(addrlist, save_errno, message, DEFER, FALSE);
+ uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
+ transport_rcpt_address(addr, include_affixes));
+ set_errno_nohost(addrlist, ETIMEDOUT, message, DEFER, FALSE);
retry_add_item(addr, addr->address_retry_key, 0);
update_waiting = FALSE;
return -1;
{
addr->message =
string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
- "host %s [%s]: %s", transport_rcpt_address(addr, include_affixes),
- host->name, host->address, string_printing(buffer));
+ "%s", transport_rcpt_address(addr, include_affixes),
+ string_printing(buffer));
setflag(addr, af_pass_message);
- deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
+ msglog_line(host, addr->message);
/* The response was 5xx */
addr->basic_errno = ERRNO_RCPT4XX;
addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
- /* Log temporary errors if there are more hosts to be tried. */
+#ifndef DISABLE_EVENT
+ event_defer_errno = addr->more_errno;
+ msg_event_raise(US"msg:rcpt:host:defer", addr);
+#endif
+
+ /* Log temporary errors if there are more hosts to be tried.
+ If not, log this last one in the == line. */
- if (host->next != NULL) log_write(0, LOG_MAIN, "%s", addr->message);
+ if (host->next)
+ log_write(0, LOG_MAIN, "H=%s [%s]: %s", host->name, host->address, addr->message);
+
+#ifndef DISABLE_EVENT
+ else
+ msg_event_raise(US"msg:rcpt:defer", addr);
+#endif
/* Do not put this message on the list of those waiting for specific
hosts, as otherwise it is likely to be tried too often. */
smtp_transport_options_block *ob, BOOL is_esmtp,
smtp_inblock *ibp, smtp_outblock *obp)
{
- int require_auth;
- uschar *fail_reason = US"server did not advertise AUTH support";
+int require_auth;
+uschar *fail_reason = US"server did not advertise AUTH support";
- smtp_authenticated = FALSE;
- client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
- require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
- host->name, host->address, NULL);
+smtp_authenticated = FALSE;
+client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+require_auth = verify_check_given_host(&ob->hosts_require_auth, host);
- if (is_esmtp && !regex_AUTH) regex_AUTH =
- regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
- FALSE, TRUE);
+if (is_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))
- {
- uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
- expand_nmax = -1; /* reset */
+if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+ {
+ uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+ expand_nmax = -1; /* reset */
- /* Must not do this check until after we have saved the result of the
- regex match above. */
+ /* Must not do this check until after we have saved the result of the
+ regex match above. */
- if (require_auth == OK ||
- verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
- host->address, NULL) == OK)
- {
- auth_instance *au;
- fail_reason = US"no common mechanisms were found";
+ if (require_auth == OK ||
+ verify_check_given_host(&ob->hosts_try_auth, host) == OK)
+ {
+ auth_instance *au;
+ fail_reason = US"no common mechanisms were found";
- DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+ DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
- /* Scan the configured authenticators looking for one which is configured
- for use as a client, which is not suppressed by client_condition, and
- whose name matches an authentication mechanism supported by the server.
- If one is found, attempt to authenticate by calling its client function.
- */
+ /* Scan the configured authenticators looking for one which is configured
+ for use as a client, which is not suppressed by client_condition, and
+ whose name matches an authentication mechanism supported by the server.
+ If one is found, attempt to authenticate by calling its client function.
+ */
- for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
- {
- uschar *p = names;
- if (!au->client ||
- (au->client_condition != NULL &&
- !expand_check_condition(au->client_condition, au->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");
- continue;
- }
+ for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+ {
+ uschar *p = names;
+ if (!au->client ||
+ (au->client_condition != NULL &&
+ !expand_check_condition(au->client_condition, au->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");
+ continue;
+ }
- /* Loop to scan supported server mechanisms */
+ /* Loop to scan supported server mechanisms */
- while (*p != 0)
- {
- int rc;
- int len = Ustrlen(au->public_name);
- while (isspace(*p)) p++;
+ while (*p != 0)
+ {
+ int rc;
+ int len = Ustrlen(au->public_name);
+ while (isspace(*p)) p++;
- if (strncmpic(au->public_name, p, len) != 0 ||
- (p[len] != 0 && !isspace(p[len])))
- {
- while (*p != 0 && !isspace(*p)) p++;
- continue;
- }
+ if (strncmpic(au->public_name, p, len) != 0 ||
+ (p[len] != 0 && !isspace(p[len])))
+ {
+ while (*p != 0 && !isspace(*p)) p++;
+ 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";
- obp->authenticating = TRUE;
- rc = (au->info->clientcode)(au, ibp, obp,
- ob->command_timeout, buffer, bufsize);
- obp->authenticating = FALSE;
- DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
- au->name, rc);
+ fail_reason = US"authentication attempt(s) failed";
+ obp->authenticating = TRUE;
+ rc = (au->info->clientcode)(au, ibp, obp,
+ ob->command_timeout, buffer, bufsize);
+ obp->authenticating = FALSE;
+ DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
+ au->name, rc);
- /* A temporary authentication failure must hold up delivery to
- this host. After a permanent authentication failure, we carry on
- to try other authentication methods. If all fail hard, try to
- deliver the message unauthenticated unless require_auth was set. */
+ /* A temporary authentication failure must hold up delivery to
+ this host. After a permanent authentication failure, we carry on
+ to try other authentication methods. If all fail hard, try to
+ deliver the message unauthenticated unless require_auth was set. */
- switch(rc)
- {
- case OK:
- 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);
- break;
-
- /* Failure after writing a command */
-
- case FAIL_SEND:
- return FAIL_SEND;
-
- /* Failure after reading a response */
-
- 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);
- break;
-
- /* Failure by some other means. In effect, the authenticator
- decided it wasn't prepared to handle this case. Typically this
- is the result of "fail" in an expansion string. Do we need to
- log anything here? Feb 2006: a message is now put in the buffer
- if logging is required. */
-
- 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);
- break;
-
- /* Internal problem, message in buffer. */
-
- case ERROR:
- set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
- return ERROR;
- }
+ switch(rc)
+ {
+ case OK:
+ 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);
+ break;
+
+ /* Failure after writing a command */
+
+ case FAIL_SEND:
+ return FAIL_SEND;
+
+ /* Failure after reading a response */
+
+ 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);
+ break;
+
+ /* Failure by some other means. In effect, the authenticator
+ decided it wasn't prepared to handle this case. Typically this
+ is the result of "fail" in an expansion string. Do we need to
+ log anything here? Feb 2006: a message is now put in the buffer
+ if logging is required. */
+
+ 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);
+ break;
+
+ /* Internal problem, message in buffer. */
+
+ case ERROR:
+ set_errno_nohost(addrlist, ERRNO_AUTHPROB, string_copy(buffer),
+ DEFER, FALSE);
+ return ERROR;
+ }
- break; /* If not authenticated, try next authenticator */
- } /* Loop for scanning supported server mechanisms */
- } /* Loop for further authenticators */
- }
+ break; /* If not authenticated, try next authenticator */
+ } /* Loop for scanning supported server mechanisms */
+ } /* Loop for further authenticators */
}
+ }
- /* If we haven't authenticated, but are required to, give up. */
+/* If we haven't authenticated, but are required to, give up. */
- if (require_auth == OK && !smtp_authenticated)
- {
- set_errno(addrlist, ERRNO_AUTHFAIL,
- string_sprintf("authentication required but %s", fail_reason), DEFER,
- FALSE);
- return DEFER;
- }
+if (require_auth == OK && !smtp_authenticated)
+ {
+ set_errno_nohost(addrlist, ERRNO_AUTHFAIL,
+ string_sprintf("authentication required but %s", fail_reason), DEFER,
+ FALSE);
+ return DEFER;
+ }
- return OK;
+return OK;
}
{
uschar *message = string_sprintf("failed to expand "
"authenticated_sender: %s", expand_string_message);
- set_errno(addrlist, 0, message, DEFER, FALSE);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
return TRUE;
}
}
+#ifdef EXPERIMENTAL_DANE
+/* Lookup TLSA record for host/port.
+Return: OK success with dnssec; DANE mode
+ DEFER Do not use this host now, may retry later
+ FAIL_FORCED No TLSA record; DANE not usable
+ FAIL Do not use this connection
+*/
+
+int
+tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required)
+{
+/* move this out to host.c given the similarity to dns_lookup() ? */
+uschar buffer[300];
+const uschar * fullname = buffer;
+
+/* TLSA lookup string */
+(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
+
+switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+ {
+ case DNS_SUCCEED:
+ if (!dns_is_secure(dnsa))
+ {
+ log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
+ return DEFER;
+ }
+ return OK;
+
+ case DNS_AGAIN:
+ return DEFER; /* just defer this TLS'd conn */
+
+ case DNS_NOMATCH:
+ return dane_required ? FAIL : FAIL_FORCED;
+
+ default:
+ case DNS_FAIL:
+ return dane_required ? FAIL : DEFER;
+ }
+}
+#endif
+
+
+
+typedef struct smtp_compare_s
+{
+ uschar *current_sender_address;
+ struct transport_instance *tblock;
+} smtp_compare_t;
+
+/*
+Create a unique string that identifies this message, it is based on
+sender_address, helo_data and tls_certificate if enabled. */
+
+static uschar *
+smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+{
+address_item * addr1;
+uschar * if1 = US"";
+uschar * helo1 = US"";
+#ifdef SUPPORT_TLS
+uschar * tlsc1 = US"";
+#endif
+uschar * save_sender_address = sender_address;
+uschar * local_identity = NULL;
+smtp_transport_options_block * ob =
+ (smtp_transport_options_block *)tblock->options_block;
+
+sender_address = sender;
+
+addr1 = deliver_make_addr (sender, TRUE);
+deliver_set_expansions(addr1);
+
+if (ob->interface)
+ if1 = expand_string(ob->interface);
+
+if (ob->helo_data)
+ helo1 = expand_string(ob->helo_data);
+
+#ifdef SUPPORT_TLS
+if (ob->tls_certificate)
+ tlsc1 = expand_string(ob->tls_certificate);
+local_identity = string_sprintf ("%s^%s^%s", if1, helo1, tlsc1);
+#else
+local_identity = string_sprintf ("%s^%s", if1, helo1);
+#endif
+
+deliver_set_expansions(NULL);
+sender_address = save_sender_address;
+
+return local_identity;
+}
+
+
+
+/* This routine is a callback that is called from transport_check_waiting.
+This function will evaluate the incoming message versus the previous
+message. If the incoming message is using a different local identity then
+we will veto this new message. */
+
+static BOOL
+smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare)
+{
+uschar * message_local_identity,
+ * current_local_identity,
+ * new_sender_address;
+
+current_local_identity =
+ smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
+
+if (!(new_sender_address = deliver_get_sender_address(message_id)))
+ return 0;
+
+message_local_identity =
+ smtp_local_identity(new_sender_address, s_compare->tblock);
+
+return Ustrcmp(current_local_identity, message_local_identity) == 0;
+}
+
+
+
+uschar
+ehlo_response(uschar * buf, size_t bsize, uschar checks)
+{
+#ifdef SUPPORT_TLS
+if ( checks & PEER_OFFERED_TLS
+ && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_TLS;
+#endif
+
+if ( checks & PEER_OFFERED_IGNQ
+ && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_IGNQ;
+
+if ( checks & PEER_OFFERED_CHUNKING
+ && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_CHUNKING;
+
+#ifndef DISABLE_PRDR
+if ( checks & PEER_OFFERED_PRDR
+ && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_PRDR;
+#endif
+
+#ifdef SUPPORT_I18N
+if ( checks & PEER_OFFERED_UTF8
+ && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_UTF8;
+#endif
+
+if ( checks & PEER_OFFERED_DSN
+ && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_DSN;
+
+if ( checks & PEER_OFFERED_PIPE
+ && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_PIPE;
+
+if ( checks & PEER_OFFERED_SIZE
+ && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~PEER_OFFERED_SIZE;
+
+return checks;
+}
+
+
/*************************************************
* Deliver address list to given host *
*************************************************/
port default TCP/IP port to use, in host byte order
interface interface to bind to, or NULL
tblock transport instance block
- copy_host TRUE if host set in addr->host_used must be copied, because
- it is specific to this call of the transport
message_defer set TRUE if yield is OK, but all addresses were deferred
because of a non-recipient, non-host failure, that is, a
4xx response to MAIL FROM, DATA, or ".". This is a defer
static int
smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
- uschar *interface, transport_instance *tblock, BOOL copy_host,
+ uschar *interface, transport_instance *tblock,
BOOL *message_defer, BOOL suppress_tls)
{
address_item *addr;
BOOL esmtp = TRUE;
BOOL pending_MAIL;
BOOL pass_message = FALSE;
-#ifdef EXPERIMENTAL_PRDR
-BOOL prdr_offered = FALSE;
+uschar peer_offered = 0; /*XXX should this be handed on cf. tls_offered, smtp_use_dsn ? */
+#ifndef DISABLE_PRDR
BOOL prdr_active;
#endif
+#ifdef SUPPORT_I18N
+BOOL utf8_needed = FALSE;
+#endif
+BOOL dsn_all_lasthop = TRUE;
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+BOOL dane = FALSE;
+BOOL dane_required = verify_check_given_host(&ob->hosts_require_dane, host) == OK;
+dns_answer tlsa_dnsa;
+#endif
smtp_inblock inblock;
smtp_outblock outblock;
int max_rcpt = tblock->max_addresses;
uschar *igquotstr = US"";
+
+#ifdef EXPERIMENTAL_DSN_INFO
+uschar *smtp_greeting = NULL;
+uschar *helo_response = NULL;
+#endif
uschar *helo_data = NULL;
+
uschar *message = NULL;
uschar new_message_id[MESSAGE_ID_LENGTH + 1];
uschar *p;
uschar buffer[4096];
uschar inbuffer[4096];
-uschar outbuffer[1024];
+uschar outbuffer[4096];
suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */
#ifndef SUPPORT_TLS
if (smtps)
{
- set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
+ set_errno_nohost(addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
+ DEFER, FALSE);
return ERROR;
}
#endif
if (continue_hostname == NULL)
{
+ /* This puts port into host->port */
inblock.sock = outblock.sock =
- smtp_connect(host, host_af, port, interface, ob->connect_timeout,
- ob->keepalive, ob->dscp); /* This puts port into host->port */
+ smtp_connect(host, host_af, port, interface, ob->connect_timeout, tblock);
if (inblock.sock < 0)
{
- set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
+ set_errno_nohost(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
NULL, DEFER, FALSE);
return DEFER;
}
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+ {
+ tls_out.dane_verified = FALSE;
+ tls_out.tlsa_usage = 0;
+
+ if (host->dnssec == DS_YES)
+ {
+ if( dane_required
+ || verify_check_given_host(&ob->hosts_try_dane, host) == OK
+ )
+ switch (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required))
+ {
+ case OK: dane = TRUE; break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: tlsa lookup %s",
+ rc == DEFER ? "DEFER" : "FAIL"),
+ rc, FALSE);
+ return rc;
+ }
+ }
+ else if (dane_required)
+ {
+ set_errno_nohost(addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: %s lookup not DNSSEC", host->name),
+ FAIL, FALSE);
+ return FAIL;
+ }
+
+ if (dane)
+ ob->tls_tempfail_tryclear = FALSE;
+ }
+#endif /*DANE*/
+
/* 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. */
helo_data = expand_string(ob->helo_data);
+#ifdef SUPPORT_I18N
+ if (helo_data)
+ {
+ uschar * errstr = NULL;
+ if ((helo_data = string_domain_utf8_to_alabel(helo_data, &errstr)), errstr)
+ {
+ errstr = string_sprintf("failed to expand helo_data: %s", errstr);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ }
+#endif
/* The first thing is to wait for an initial OK response. The dreaded "goto"
is nevertheless a reasonably clean way of programming this kind of logic,
if (!smtps)
{
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout)) goto RESPONSE_FAILED;
+ BOOL good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
+ '2', ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ smtp_greeting = string_copy(buffer);
+#endif
+ if (!good_response) goto RESPONSE_FAILED;
+
+#ifndef DISABLE_EVENT
+ {
+ uschar * s;
+ lookup_dnssec_authenticated = host->dnssec==DS_YES ? US"yes"
+ : host->dnssec==DS_NO ? US"no" : NULL;
+ s = event_raise(tblock->event_action, US"smtp:connect", buffer);
+ if (s)
+ {
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL,
+ string_sprintf("deferred by smtp:connect event expansion: %s", s),
+ DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ }
+#endif
/* Now check if the helo_data expansion went well, and sign off cleanly if
it didn't. */
{
uschar *message = string_sprintf("failed to expand helo_data: %s",
expand_string_message);
- set_errno(addrlist, 0, message, DEFER, FALSE);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
yield = DEFER;
goto SEND_QUIT;
}
mailers use upper case for some reason (the RFC is quite clear about case
independence) so, for peace of mind, I gave in. */
- esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
- host->name, host->address, NULL) != OK;
+ esmtp = verify_check_given_host(&ob->hosts_avoid_esmtp, 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
and be usable. I can see this coming back to bite us. */
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
if (smtps)
{
tls_offered = TRUE;
smtp_command = US"SSL-on-connect";
goto TLS_NEGOTIATE;
}
- #endif
+#endif
if (esmtp)
{
if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout))
{
- if (errno != 0 || buffer[0] == 0 || lmtp) goto RESPONSE_FAILED;
+ if (errno != 0 || buffer[0] == 0 || lmtp)
+ {
+#ifdef EXPERIMENTAL_DSN_INFO
+ helo_response = string_copy(buffer);
+#endif
+ goto RESPONSE_FAILED;
+ }
esmtp = FALSE;
}
+#ifdef EXPERIMENTAL_DSN_INFO
+ helo_response = string_copy(buffer);
+#endif
}
else
{
if (!esmtp)
{
+ BOOL good_response;
+
if (smtp_write_command(&outblock, FALSE, "HELO %s\r\n", helo_data) < 0)
goto SEND_FAILED;
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout)) goto RESPONSE_FAILED;
+ good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
+ '2', ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ helo_response = string_copy(buffer);
+#endif
+ if (!good_response) goto RESPONSE_FAILED;
}
- /* Set IGNOREQUOTA if the response to LHLO specifies support and the
- lmtp_ignore_quota option was set. */
-
- igquotstr = (lmtp && ob->lmtp_ignore_quota &&
- pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+ if (esmtp || lmtp)
+ peer_offered = ehlo_response(buffer, Ustrlen(buffer),
+ PEER_OFFERED_TLS /* others checked later */
+ );
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
- #ifdef SUPPORT_TLS
- tls_offered = esmtp &&
- pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
- #endif
-
- #ifdef EXPERIMENTAL_PRDR
- prdr_offered = esmtp &&
- (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0) &&
- (verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
- host->address, NULL) == OK);
-
- if (prdr_offered)
- {DEBUG(D_transport) debug_printf("PRDR usable\n");}
- #endif
+#ifdef SUPPORT_TLS
+ tls_offered = !!(peer_offered & PEER_OFFERED_TLS);
+#endif
}
/* For continuing deliveries down the same channel, the socket is the standard
set from the command line if they were set in the process that passed the
connection on. */
+/*XXX continue case needs to propagate DSN_INFO, prob. in deliver.c
+as the contine goes via transport_pass_socket() and doublefork and exec.
+It does not wait. Unclear how we keep separate host's responses
+separate - we could match up by host ip+port as a bodge. */
+
else
{
inblock.sock = outblock.sock = fileno(stdin);
for error analysis. */
#ifdef SUPPORT_TLS
-if (tls_offered && !suppress_tls &&
- verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
- host->address, NULL) != OK)
+if ( tls_offered
+ && !suppress_tls
+ && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK)
{
uschar buffer2[4096];
if (smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") < 0)
if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
ob->command_timeout))
{
- if (errno != 0 || buffer2[0] == 0 ||
- (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+ if ( errno != 0
+ || buffer2[0] == 0
+ || (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)
+ )
{
Ustrncpy(buffer, buffer2, sizeof(buffer));
goto RESPONSE_FAILED;
else
TLS_NEGOTIATE:
{
- int rc = tls_client_start(inblock.sock, host, addrlist, ob);
+ int rc = tls_client_start(inblock.sock, host, addrlist, tblock
+# ifdef EXPERIMENTAL_DANE
+ , dane ? &tlsa_dnsa : NULL
+# endif
+ );
/* 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
if (rc != OK)
{
+# ifdef EXPERIMENTAL_DANE
+ if (rc == DEFER && dane)
+ {
+ log_write(0, LOG_MAIN,
+ "DANE attempt failed; no TLS connection to %s [%s]",
+ host->name, host->address);
+ }
+# endif
+
save_errno = ERRNO_TLSFAILURE;
message = US"failure while setting up TLS session";
send_quit = FALSE;
/* TLS session is set up */
- for (addr = addrlist; addr != NULL; addr = addr->next)
- {
+ for (addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == PENDING_DEFER)
{
addr->cipher = tls_out.cipher;
addr->peerdn = tls_out.peerdn;
addr->ocsp = tls_out.ocsp;
}
- }
}
}
if (tls_out.active >= 0)
{
char *greeting_cmd;
+ BOOL good_response;
+
if (helo_data == NULL)
{
helo_data = expand_string(ob->helo_data);
{
uschar *message = string_sprintf("failed to expand helo_data: %s",
expand_string_message);
- set_errno(addrlist, 0, message, DEFER, FALSE);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
yield = DEFER;
goto SEND_QUIT;
}
/* For SMTPS we need to wait for the initial OK response. */
if (smtps)
{
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout)) goto RESPONSE_FAILED;
+ good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
+ '2', ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ smtp_greeting = string_copy(buffer);
+#endif
+ if (!good_response) goto RESPONSE_FAILED;
}
if (esmtp)
if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
lmtp? "LHLO" : greeting_cmd, helo_data) < 0)
goto SEND_FAILED;
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout))
- goto RESPONSE_FAILED;
+ good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
+ '2', ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ helo_response = string_copy(buffer);
+#endif
+ if (!good_response) goto RESPONSE_FAILED;
}
/* If the host is required to use a secure channel, ensure that we
have one. */
-else if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) == OK)
+else if ( smtps
+# ifdef EXPERIMENTAL_DANE
+ || dane
+# endif
+ || verify_check_given_host(&ob->hosts_require_tls, host) == OK
+ )
{
save_errno = ERRNO_TLSREQUIRED;
- message = string_sprintf("a TLS session is required for %s [%s], but %s",
- host->name, host->address,
- tls_offered? "an attempt to start TLS failed" :
- "the server did not offer TLS support");
+ message = string_sprintf("a TLS session is required, but %s",
+ tls_offered ? "an attempt to start TLS failed"
+ : "the server did not offer TLS support");
goto TLS_FAILED;
}
-#endif
+#endif /*SUPPORT_TLS*/
/* If TLS is active, we have just started it up and re-done the EHLO command,
so its response needs to be analyzed. If TLS is not active and this is a
we skip this. */
if (continue_hostname == NULL
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
|| tls_out.active >= 0
- #endif
+#endif
)
{
+ if (esmtp || lmtp)
+ peer_offered = ehlo_response(buffer, Ustrlen(buffer),
+ 0 /* no TLS */
+ | (lmtp && ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
+ | PEER_OFFERED_CHUNKING
+ | PEER_OFFERED_PRDR
+#ifdef SUPPORT_I18N
+ | (addrlist->prop.utf8_msg ? PEER_OFFERED_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
+ | (ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
+ );
+
/* Set for IGNOREQUOTA if the response to LHLO specifies support and the
lmtp_ignore_quota option was set. */
- igquotstr = (lmtp && ob->lmtp_ignore_quota &&
- pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+ igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
/* If the response to EHLO specified support for the SIZE parameter, note
this, provided size_addition is non-negative. */
- smtp_use_size = esmtp && ob->size_addition >= 0 &&
- pcre_exec(regex_SIZE, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
+ smtp_use_size = !!(peer_offered & PEER_OFFERED_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. */
- smtp_use_pipelining = esmtp &&
- verify_check_this_host(&(ob->hosts_avoid_pipelining), NULL, host->name,
- host->address, NULL) != OK &&
- pcre_exec(regex_PIPELINING, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
+ smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE
+ && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK;
DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
- smtp_use_pipelining? "" : "not ");
+ smtp_use_pipelining ? "" : "not ");
-#ifdef EXPERIMENTAL_PRDR
- prdr_offered = esmtp &&
- pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0 &&
- verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
- host->address, NULL) == OK;
+ if ( peer_offered & PEER_OFFERED_CHUNKING
+ && verify_check_given_host(&ob->hosts_try_chunking, host) != OK)
+ peer_offered &= ~PEER_OFFERED_CHUNKING;
- if (prdr_offered)
+ if (peer_offered & PEER_OFFERED_CHUNKING)
+ {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
+
+#ifndef DISABLE_PRDR
+ if ( peer_offered & PEER_OFFERED_PRDR
+ && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
+ peer_offered &= ~PEER_OFFERED_PRDR;
+
+ if (peer_offered & PEER_OFFERED_PRDR)
{DEBUG(D_transport) debug_printf("PRDR usable\n");}
#endif
+ /* Note if the server supports DSN */
+ smtp_use_dsn = !!(peer_offered & PEER_OFFERED_DSN);
+ DEBUG(D_transport) debug_printf("%susing DSN\n", smtp_use_dsn ? "" : "not ");
+
/* 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
setting_up = FALSE;
+#ifdef SUPPORT_I18N
+if (addrlist->prop.utf8_msg)
+ {
+ utf8_needed = !addrlist->prop.utf8_downcvt
+ && !addrlist->prop.utf8_downcvt_maybe;
+ DEBUG(D_transport) if (!utf8_needed) debug_printf("utf8: %s downconvert\n",
+ addrlist->prop.utf8_downcvt ? "mandatory" : "optional");
+ }
+
+/* If this is an international message we need the host to speak SMTPUTF8 */
+if (utf8_needed && !(peer_offered & PEER_OFFERED_UTF8))
+ {
+ errno = ERRNO_UTF8_FWD;
+ goto RESPONSE_FAILED;
+ }
+#endif
+
/* If there is a filter command specified for this transport, we can now
set it up. This cannot be done until the identify of the host is known. */
if (!rc)
{
- set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+ set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
FALSE);
yield = ERROR;
goto SEND_QUIT;
while (*p) p++;
}
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
prdr_active = FALSE;
-if (prdr_offered)
- {
+if (peer_offered & PEER_OFFERED_PRDR)
for (addr = first_addr; addr; addr = addr->next)
if (addr->transport_return == PENDING_DEFER)
{
{ /* at least two recipients to send */
prdr_active = TRUE;
sprintf(CS p, " PRDR"); p += 5;
- goto prdr_is_active;
+ break;
}
break;
}
- }
-prdr_is_active:
#endif
+#ifdef SUPPORT_I18N
+if ( addrlist->prop.utf8_msg
+ && !addrlist->prop.utf8_downcvt
+ && peer_offered & PEER_OFFERED_UTF8
+ )
+ sprintf(CS p, " SMTPUTF8"), p += 9;
+#endif
+
+/* check if all addresses have lasthop flag */
+/* do not send RET and ENVID if true */
+for (dsn_all_lasthop = TRUE, addr = first_addr;
+ address_count < max_rcpt && addr != NULL;
+ addr = addr->next)
+ if ((addr->dsn_flags & rf_dsnlasthop) != 1)
+ {
+ dsn_all_lasthop = FALSE;
+ break;
+ }
+
+/* Add any DSN flags to the mail command */
+
+if (smtp_use_dsn && !dsn_all_lasthop)
+ {
+ if (dsn_ret == dsn_ret_hdrs)
+ {
+ Ustrcpy(p, " RET=HDRS"); p += 9;
+ }
+ else if (dsn_ret == dsn_ret_full)
+ {
+ Ustrcpy(p, " RET=FULL"); p += 9;
+ }
+ if (dsn_envid != NULL)
+ {
+ string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid);
+ while (*p) p++;
+ }
+ }
+
/* If an authenticated_sender override has been specified for this transport
instance, expand it. If the expansion is forced to fail, and there was already
an authenticated_sender for this message, the original value will be used.
cases where non-standard addresses (e.g. without domains) might be required. */
if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
- return ERROR;
+ {
+ yield = ERROR;
+ goto SEND_QUIT;
+ }
/* From here until we send the DATA command, we can make use of PIPELINING
if the server host supports it. The code has to be able to check the responses
pending_MAIL = TRUE; /* The block starts with MAIL */
-rc = smtp_write_command(&outblock, smtp_use_pipelining,
- "MAIL FROM:<%s>%s\r\n", return_path, buffer);
+ {
+ uschar * s = return_path;
+#ifdef SUPPORT_I18N
+ uschar * errstr = NULL;
+
+ /* If we must downconvert, do the from-address here. Remember we had to
+ for the to-addresses (done below), and also (ugly) for re-doing when building
+ the delivery log line. */
+
+ if ( addrlist->prop.utf8_msg
+ && (addrlist->prop.utf8_downcvt || !(peer_offered & PEER_OFFERED_UTF8))
+ )
+ {
+ if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr)
+ {
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
+ yield = ERROR;
+ goto SEND_QUIT;
+ }
+ setflag(addrlist, af_utf8_downcvt);
+ }
+#endif
+
+ rc = smtp_write_command(&outblock, smtp_use_pipelining,
+ "MAIL FROM:<%s>%s\r\n", s, buffer);
+ }
+
mail_command = string_copy(big_buffer); /* Save for later error message */
switch(rc)
{
case -1: /* Transmission error */
- goto SEND_FAILED;
+ goto SEND_FAILED;
case +1: /* Block was sent */
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+ if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout))
- {
- if (errno == 0 && buffer[0] == '4')
{
- errno = ERRNO_MAIL4XX;
- addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ if (errno == 0 && buffer[0] == '4')
+ {
+ errno = ERRNO_MAIL4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
+ goto RESPONSE_FAILED;
}
- goto RESPONSE_FAILED;
- }
- pending_MAIL = FALSE;
- break;
+ pending_MAIL = FALSE;
+ break;
}
/* Pass over all the relevant recipient addresses for this host, which are the
that max_rcpt will be large, so all addresses will be done at once. */
for (addr = first_addr;
- address_count < max_rcpt && addr != NULL;
+ addr && address_count < max_rcpt;
addr = addr->next)
{
int count;
BOOL no_flush;
+ uschar * rcpt_addr;
+
+ addr->dsn_aware = smtp_use_dsn ? dsn_support_yes : dsn_support_no;
if (addr->transport_return != PENDING_DEFER) continue;
address_count++;
- no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL);
+ no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next);
+
+ /* Add any DSN flags to the rcpt command and add to the sent string */
+
+ p = buffer;
+ *p = 0;
+
+ if (smtp_use_dsn && !(addr->dsn_flags & rf_dsnlasthop))
+ {
+ if ((addr->dsn_flags & rf_dsnflags) != 0)
+ {
+ int i;
+ BOOL first = TRUE;
+ Ustrcpy(p, " NOTIFY=");
+ while (*p) p++;
+ for (i = 0; i < 4; i++)
+ if ((addr->dsn_flags & rf_list[i]) != 0)
+ {
+ if (!first) *p++ = ',';
+ first = FALSE;
+ Ustrcpy(p, rf_names[i]);
+ while (*p) p++;
+ }
+ }
+
+ if (addr->dsn_orcpt)
+ {
+ string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s",
+ addr->dsn_orcpt);
+ while (*p) p++;
+ }
+ }
/* Now send the RCPT command, and process outstanding responses when
necessary. After a timeout on RCPT, we just end the function, leaving the
yield as OK, because this error can often mean that there is a problem with
just one address, so we don't want to delay the host. */
- count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n",
- transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr);
+ rcpt_addr = transport_rcpt_address(addr, tblock->rcpt_include_affixes);
+
+#ifdef SUPPORT_I18N
+ {
+ uschar * dummy_errstr;
+ if ( testflag(addrlist, af_utf8_downcvt)
+ && (rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, &dummy_errstr),
+ dummy_errstr
+ ) )
+ {
+ errno = ERRNO_EXPANDFAIL;
+ goto SEND_FAILED;
+ }
+ }
+#endif
+
+ count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
+ rcpt_addr, igquotstr, buffer);
+
if (count < 0) goto SEND_FAILED;
if (count > 0)
{
if (mua_wrapper)
{
address_item *badaddr;
- for (badaddr = first_addr; badaddr != NULL; badaddr = badaddr->next)
- {
- if (badaddr->transport_return != PENDING_OK) break;
- }
- if (badaddr != NULL)
- {
- set_errno(addrlist, 0, badaddr->message, FAIL,
- testflag(badaddr, af_pass_message));
- ok = FALSE;
- }
+ for (badaddr = 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));
+ ok = FALSE;
+ break;
+ }
}
/* If ok is TRUE, we know we have got at least one good recipient, and must now
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 (!ok) ok = TRUE; else
+if (!ok)
+ ok = TRUE;
+else
{
+ int options = topt_use_crlf | topt_escape_headers
+ | (tblock->body_only ? topt_no_headers : 0)
+ | (tblock->headers_only ? topt_no_body : 0)
+ | (tblock->return_path_add ? topt_add_return_path : 0)
+ | (tblock->delivery_date_add ?topt_add_delivery_date :0)
+ | (tblock->envelope_to_add ? topt_add_envelope_to : 0);
+ uschar * str_spot, * str_repl;
+
+ if (peer_offered & PEER_OFFERED_CHUNKING)
+ {
+ options |= topt_use_bdat;
+ str_spot = str_repl = NULL;
+ }
+ else
+ {
+ options |= topt_end_dot;
+ str_spot = US"."; str_repl = US"..";
+ }
+
sigalrm_seen = FALSE;
transport_write_timeout = ob->data_timeout;
smtp_command = US"sending data block"; /* For error messages */
DEBUG(D_transport|D_v)
- debug_printf(" SMTP>> writing message and terminating \".\"\n");
+ debug_printf(" SMTP>> writing message %s\n",
+ peer_offered & PEER_OFFERED_CHUNKING
+ ? "using CHUNKING" : "and terminating \".\"");
transport_count = 0;
+
#ifndef DISABLE_DKIM
ok = dkim_transport_write_message(addrlist, inblock.sock,
- topt_use_crlf | topt_end_dot | topt_escape_headers |
- (tblock->body_only? topt_no_headers : 0) |
- (tblock->headers_only? topt_no_body : 0) |
- (tblock->return_path_add? topt_add_return_path : 0) |
- (tblock->delivery_date_add? topt_add_delivery_date : 0) |
- (tblock->envelope_to_add? topt_add_envelope_to : 0),
- 0, /* No size limit */
+ options,
tblock->add_headers, tblock->remove_headers,
- US".", US"..", /* Escaping strings */
+ str_spot, str_repl, /* Escaping strings */
tblock->rewrite_rules, tblock->rewrite_existflags,
- ob->dkim_private_key, ob->dkim_domain, ob->dkim_selector,
- ob->dkim_canon, ob->dkim_strict, ob->dkim_sign_headers
+ &ob->dkim
);
#else
ok = transport_write_message(addrlist, inblock.sock,
- topt_use_crlf | topt_end_dot | topt_escape_headers |
- (tblock->body_only? topt_no_headers : 0) |
- (tblock->headers_only? topt_no_body : 0) |
- (tblock->return_path_add? topt_add_return_path : 0) |
- (tblock->delivery_date_add? topt_add_delivery_date : 0) |
- (tblock->envelope_to_add? topt_add_envelope_to : 0),
+ options,
0, /* No size limit */
tblock->add_headers, tblock->remove_headers,
- US".", US"..", /* Escaping strings */
+ str_spot, str_repl, /* Escaping strings */
tblock->rewrite_rules, tblock->rewrite_existflags);
#endif
smtp_command = US"end of data";
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
/* For PRDR we optionally get a partial-responses warning
* followed by the individual responses, before going on with
* the overall response. If we don't get the warning then deal
int flag = '=';
int delivery_time = (int)(time(NULL) - start_delivery_time);
int len;
- host_item *thost;
uschar *conf = NULL;
send_rset = FALSE;
- /* Make a copy of the host if it is local to this invocation
- of the transport. */
-
- if (copy_host)
- {
- thost = store_get(sizeof(host_item));
- *thost = *host;
- thost->name = string_copy(host->name);
- thost->address = string_copy(host->address);
- }
- else thost = host;
-
/* Set up confirmation if needed - applies only to SMTP */
if (
- #ifndef EXPERIMENTAL_TPDA
- (log_extra_selector & LX_smtp_confirmation) != 0 &&
- #endif
+#ifdef DISABLE_EVENT
+ LOGGING(smtp_confirmation) &&
+#endif
!lmtp
)
{
- uschar *s = string_printing(buffer);
- conf = (s == buffer)? (uschar *)string_copy(s) : s;
+ const uschar *s = string_printing(buffer);
+ /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
+ conf = (s == buffer)? (uschar *)string_copy(s) : US s;
}
/* Process all transported addresses - for LMTP or PRDR, read a status for
address. For temporary errors, add a retry item for the address so that
it doesn't get tried again too soon. */
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
if (lmtp || prdr_active)
#else
if (lmtp)
{
if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
addr->message = string_sprintf(
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
"%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
#else
"LMTP error after %s: %s",
errno = ERRNO_DATA4XX;
addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
addr->transport_return = DEFER;
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
if (!prdr_active)
#endif
retry_add_item(addr, addr->address_retry_key, 0);
continue;
}
completed_address = TRUE; /* NOW we can set this flag */
- if ((log_extra_selector & LX_smtp_confirmation) != 0)
+ if (LOGGING(smtp_confirmation))
{
- uschar *s = string_printing(buffer);
- conf = (s == buffer)? (uschar *)string_copy(s) : s;
+ const uschar *s = string_printing(buffer);
+ /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
+ conf = (s == buffer)? (uschar *)string_copy(s) : US s;
}
}
addr->transport_return = OK;
addr->more_errno = delivery_time;
- addr->host_used = thost;
+ addr->host_used = host;
addr->special_action = flag;
addr->message = conf;
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
if (prdr_active) addr->flags |= af_prdr_used;
#endif
flag = '-';
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
if (!prdr_active)
#endif
{
else
sprintf(CS buffer, "%.500s\n", addr->unique);
- DEBUG(D_deliver) debug_printf("journalling %s", buffer);
+ DEBUG(D_deliver) debug_printf("journalling %s\n", buffer);
len = Ustrlen(CS buffer);
if (write(journal_fd, buffer, len) != len)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
}
}
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
if (prdr_active)
{
/* PRDR - get the final, overall response. For any non-success
else
sprintf(CS buffer, "%.500s\n", addr->unique);
- DEBUG(D_deliver) debug_printf("journalling(PRDR) %s", buffer);
+ DEBUG(D_deliver) debug_printf("journalling(PRDR) %s\n", buffer);
len = Ustrlen(CS buffer);
if (write(journal_fd, buffer, len) != len)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
if (!ok)
{
- int code;
+ int code, set_rc;
+ uschar * set_message;
RESPONSE_FAILED:
- save_errno = errno;
- message = NULL;
- send_quit = check_response(host, &save_errno, addrlist->more_errno,
- buffer, &code, &message, &pass_message);
- goto FAILED;
+ {
+ save_errno = errno;
+ message = NULL;
+ send_quit = check_response(host, &save_errno, addrlist->more_errno,
+ buffer, &code, &message, &pass_message);
+ goto FAILED;
+ }
SEND_FAILED:
- save_errno = errno;
- code = '4';
- message = US string_sprintf("send() to %s [%s] failed: %s",
- host->name, host->address, strerror(save_errno));
- send_quit = FALSE;
- goto FAILED;
+ {
+ save_errno = errno;
+ code = '4';
+ message = US string_sprintf("send() to %s [%s] failed: %s",
+ host->name, host->address, strerror(save_errno));
+ send_quit = FALSE;
+ 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
a temporary error. */
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
TLS_FAILED:
code = '4';
- #endif
+#endif
/* If 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
FAILED:
ok = FALSE; /* For when reached by GOTO */
+ set_message = message;
if (setting_up)
{
if (code == '5')
- {
- set_errno(addrlist, save_errno, message, FAIL, pass_message);
- }
+ set_rc = FAIL;
else
- {
- set_errno(addrlist, save_errno, message, DEFER, pass_message);
- yield = DEFER;
- }
+ yield = set_rc = DEFER;
}
/* We want to handle timeouts after MAIL or "." and loss of connection after
switch(save_errno)
{
+#ifdef SUPPORT_I18N
+ case ERRNO_UTF8_FWD:
+ code = '5';
+ /*FALLTHROUGH*/
+#endif
case 0:
case ERRNO_MAIL4XX:
case ERRNO_DATA4XX:
- message_error = TRUE;
- break;
+ message_error = TRUE;
+ break;
case ETIMEDOUT:
- message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 ||
- Ustrncmp(smtp_command,"end ",4) == 0;
- break;
+ message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 ||
+ Ustrncmp(smtp_command,"end ",4) == 0;
+ break;
case ERRNO_SMTPCLOSED:
- message_error = Ustrncmp(smtp_command,"end ",4) == 0;
- break;
+ message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+ break;
default:
- message_error = FALSE;
- break;
+ message_error = FALSE;
+ break;
}
/* Handle the cases that are treated as message errors. These are:
(a) negative response or timeout after MAIL
(b) negative response after DATA
(c) negative response or timeout or dropped connection after "."
+ (d) utf8 support required and not offered
It won't be a negative response or timeout after RCPT, as that is dealt
with separately above. The action in all cases is to set an appropriate
if (message_error)
{
if (mua_wrapper) code = '5'; /* Force hard failure in wrapper mode */
- set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER,
- pass_message);
/* If there's an errno, the message contains just the identity of
the host. */
- if (code != '5') /* Anything other than 5 is treated as temporary */
+ if (code == '5')
+ set_rc = FAIL;
+ else /* Anything other than 5 is treated as temporary */
{
+ 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);
- deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+ msglog_line(host, message);
*message_defer = TRUE;
}
}
else
{
+ set_rc = DEFER;
yield = (save_errno == ERRNO_CHHEADER_FAIL ||
save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
- set_errno(addrlist, save_errno, message, DEFER, pass_message);
}
}
+
+ set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host
+#ifdef EXPERIMENTAL_DSN_INFO
+ , smtp_greeting, helo_response
+#endif
+ );
}
if (completed_address && ok && send_quit)
{
BOOL more;
- if (first_addr != NULL || continue_more ||
- (
- (tls_out.active < 0 ||
- verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
- host->address, NULL) != OK)
+ smtp_compare_t t_compare;
+
+ t_compare.tblock = tblock;
+ t_compare.current_sender_address = sender_address;
+
+ if ( first_addr != NULL
+ || continue_more
+ || ( ( tls_out.active < 0
+ || verify_check_given_host(&ob->hosts_nopass_tls, host) != OK
+ )
&&
transport_check_waiting(tblock->name, host->name,
- tblock->connection_max_messages, new_message_id, &more)
- ))
+ tblock->connection_max_messages, new_message_id, &more,
+ (oicf)smtp_are_same_identities, (void*)&t_compare)
+ ) )
{
uschar *msg;
BOOL pass_message;
&pass_message);
if (!send_quit)
{
- DEBUG(D_transport) debug_printf("%s\n", msg);
+ DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
+ host->name, host->address, msg);
}
}
}
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. */
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
if (tls_out.active >= 0)
{
tls_close(FALSE, TRUE);
smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout);
}
- #endif
+#endif
/* If the socket is successfully passed, we musn'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 (ok && transport_pass_socket(tblock->name, host->name, host->address,
new_message_id, inblock.sock))
{
/* If RSET failed and there are addresses left, they get deferred. */
- else set_errno(first_addr, errno, msg, DEFER, FALSE);
+ else set_errno(first_addr, errno, msg, DEFER, FALSE, host
+#ifdef EXPERIMENTAL_DSN_INFO
+ , smtp_greeting, helo_response
+#endif
+ );
}
}
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");
(void)close(inblock.sock);
+
+#ifndef DISABLE_EVENT
+(void) event_raise(tblock->event_action, US"tcp:close", NULL);
+#endif
+
continue_transport = NULL;
continue_hostname = NULL;
return yield;
address_item *first_addr = NULL;
address_item *addr;
for (addr = addrlist; addr != NULL; addr = addr->next)
- {
- if (addr->transport_return != DEFER) continue;
- if (first_addr == NULL) first_addr = addr;
- addr->transport_return = PENDING_DEFER;
- addr->basic_errno = 0;
- addr->more_errno = (host->mx >= 0)? 'M' : 'A';
- addr->message = NULL;
- #ifdef SUPPORT_TLS
- addr->cipher = NULL;
- addr->ourcert = NULL;
- addr->peercert = NULL;
- addr->peerdn = NULL;
- addr->ocsp = OCSP_NOT_REQ;
- #endif
- }
+ if (addr->transport_return == DEFER)
+ {
+ if (first_addr == NULL) first_addr = addr;
+ addr->transport_return = PENDING_DEFER;
+ addr->basic_errno = 0;
+ addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+ addr->message = NULL;
+#ifdef SUPPORT_TLS
+ addr->cipher = NULL;
+ addr->ourcert = NULL;
+ addr->peercert = NULL;
+ addr->peerdn = NULL;
+ addr->ocsp = OCSP_NOT_REQ;
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+ addr->smtp_greeting = NULL;
+ addr->helo_response = NULL;
+#endif
+ }
return first_addr;
}
if (Ustrchr(s, '$') != NULL)
{
- expanded_hosts = expand_string(s);
- if (expanded_hosts == NULL)
+ 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);
/* If there was no expansion of hosts, save the host list for
next time. */
- if (expanded_hosts == NULL) ob->hostlist = hostlist;
+ if (!expanded_hosts) ob->hostlist = hostlist;
}
/* This is not the first time this transport has been run in this delivery;
the host list was built previously. */
- else hostlist = ob->hostlist;
+ else
+ hostlist = ob->hostlist;
}
/* The host list was supplied with the address. If hosts_randomize is set, we
hostlist = addrlist->host_list = newlist;
}
-
/* Sort out the default port. */
if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
-
/* For each host-plus-IP-address on the list:
. If this is a continued delivery and the host isn't the one with the
BOOL serialized = FALSE;
BOOL host_is_expired = FALSE;
BOOL message_defer = FALSE;
- BOOL ifchanges = FALSE;
BOOL some_deferred = FALSE;
address_item *first_addr = NULL;
uschar *interface = NULL;
{
int new_port, flags;
host_item *hh;
- uschar *canonical_name;
if (host->status >= hstatus_unusable)
{
if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
if (ob->gethostbyname || string_is_ip_address(host->name, NULL) != 0)
- rc = host_find_byname(host, NULL, flags, &canonical_name, TRUE);
+ rc = host_find_byname(host, NULL, flags, NULL, TRUE);
else
rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
- ob->dnssec_request_domains, ob->dnssec_require_domains,
- &canonical_name, NULL);
+ &ob->dnssec, /* domains for request/require */
+ NULL, NULL);
/* Update the host (and any additional blocks, resulting from
multihoming) with a host-specific port, if any. */
doing a two-stage queue run, don't do this if forcing. */
if ((!deliver_force || queue_2stage) && (queue_smtp ||
- match_isinlist(addrlist->domain, &queue_smtp_domains, 0,
+ match_isinlist(addrlist->domain,
+ (const uschar **)&queue_smtp_domains, 0,
&domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
{
expired = FALSE;
deliver_host = host->name;
deliver_host_address = host->address;
+ lookup_dnssec_authenticated = host->dnssec == DS_YES ? US"yes"
+ : host->dnssec == DS_NO ? US"no"
+ : US"";
/* Set up a string for adding to the retry key if the port number is not
the standard SMTP port. A host may have its own port setting that overrides
if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
/* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
- string changes upon expansion, we must add it to the key that is used for
- retries, because connections to the same host from a different interface
- should be treated separately. */
+ string is set, even if constant (as different transports can have different
+ constant settings), we must add it to the key that is used for retries,
+ because connections to the same host from a different interface should be
+ treated separately. */
host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET : AF_INET6;
- if (!smtp_get_interface(ob->interface, host_af, addrlist, &ifchanges,
- &interface, tid))
- return FALSE;
- if (ifchanges) pistring = string_sprintf("%s/%s", pistring, interface);
+ if ((rs = ob->interface) && *rs)
+ {
+ if (!smtp_get_interface(rs, host_af, addrlist, &interface, tid))
+ return FALSE;
+ pistring = string_sprintf("%s/%s", pistring, interface);
+ }
/* The first time round the outer loop, check the status of the host by
inspecting the retry data. The second time round, we are interested only
if (cutoff_retry == 0)
{
+ BOOL incl_ip;
/* Ensure the status of the address is set by checking retry data if
- necessary. There maybe host-specific retry data (applicable to all
+ necessary. There may be host-specific retry data (applicable to all
messages) and also data for retries of a specific message at this host.
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,
+ US"retry_include_ip_address", ob->retry_include_ip_address,
+ ob->expand_retry_include_ip_address, &incl_ip) != OK)
+ continue; /* with next host */
+
host_is_expired = retry_check_address(addrlist->domain, host, pistring,
- ob->retry_include_ip_address, &retry_host_key, &retry_message_key);
+ incl_ip, &retry_host_key, &retry_message_key);
DEBUG(D_transport) debug_printf("%s [%s]%s status = %s\n", host->name,
(host->address == NULL)? US"" : host->address, pistring,
sending the message down a pre-existing connection. */
if (!continuing &&
- verify_check_this_host(&(ob->serialize_hosts), NULL, host->name,
- host->address, NULL) == OK)
+ verify_check_given_host(&ob->serialize_hosts, host) == OK)
{
serialize_key = string_sprintf("host-serialize-%s", host->name);
- if (!enq_start(serialize_key))
+ if (!enq_start(serialize_key, 1))
{
DEBUG(D_transport)
debug_printf("skipping host %s because another Exim process "
if (dont_deliver)
{
host_item *host2;
- set_errno(addrlist, 0, NULL, OK, FALSE);
+ set_errno_nohost(addrlist, 0, NULL, OK, FALSE);
for (addr = addrlist; addr != NULL; addr = addr->next)
{
addr->host_used = host;
else
{
+ host_item * thost;
+ /* Make a copy of the host if it is local to this invocation
+ of the transport. */
+
+ if (expanded_hosts)
+ {
+ thost = store_get(sizeof(host_item));
+ *thost = *host;
+ thost->name = string_copy(host->name);
+ thost->address = string_copy(host->address);
+ }
+ else
+ thost = host;
+
if (!host_is_expired && ++unexpired_hosts_tried >= ob->hosts_max_try)
{
host_item *h;
DEBUG(D_transport)
debug_printf("hosts_max_try limit reached with this host\n");
- for (h = host; h != NULL; h = h->next)
- if (h->mx != host->mx) break;
- if (h != NULL)
- {
- nexthost = h;
- unexpired_hosts_tried--;
- DEBUG(D_transport) debug_printf("however, a higher MX host exists "
- "and will be tried\n");
- }
+ for (h = host; h; h = h->next) if (h->mx != host->mx)
+ {
+ nexthost = h;
+ unexpired_hosts_tried--;
+ DEBUG(D_transport) debug_printf("however, a higher MX host exists "
+ "and will be tried\n");
+ break;
+ }
}
/* Attempt the delivery. */
total_hosts_tried++;
- rc = smtp_deliver(addrlist, host, host_af, port, interface, tblock,
- expanded_hosts != NULL, &message_defer, FALSE);
+ rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ &message_defer, FALSE);
/* Yield is one of:
OK => connection made, each address contains its result;
first_addr->basic_errno != ERRNO_TLSFAILURE)
write_logs(first_addr, host);
- #ifdef EXPERIMENTAL_TPDA
+#ifndef DISABLE_EVENT
if (rc == DEFER)
- tpda_deferred(ob, first_addr, host);
- #endif
+ deferred_event_raise(first_addr, host);
+#endif
/* If STARTTLS was accepted, but there was a failure in setting up the
TLS session (usually a certificate screwup), and the host is not in
session, so the in-clear transmission after those errors, if permitted,
happens inside smtp_deliver().] */
- #ifdef SUPPORT_TLS
- if (rc == DEFER && first_addr->basic_errno == ERRNO_TLSFAILURE &&
- ob->tls_tempfail_tryclear &&
- verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) != OK)
+#ifdef SUPPORT_TLS
+ if ( rc == DEFER
+ && first_addr->basic_errno == ERRNO_TLSFAILURE
+ && ob->tls_tempfail_tryclear
+ && verify_check_given_host(&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);
first_addr = prepare_addresses(addrlist, host);
- rc = smtp_deliver(addrlist, host, host_af, port, interface, tblock,
- expanded_hosts != NULL, &message_defer, TRUE);
+ rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ &message_defer, TRUE);
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
write_logs(first_addr, host);
- #ifdef EXPERIMENTAL_TPDA
+# ifndef DISABLE_EVENT
if (rc == DEFER)
- tpda_deferred(ob, first_addr, host);
- #endif
+ deferred_event_raise(first_addr, host);
+# endif
}
- #endif
+#endif /*SUPPORT_TLS*/
}
/* Delivery attempt finished */
int delete_flag = (rc != DEFER)? rf_delete : 0;
if (retry_host_key == NULL)
{
- retry_host_key = ob->retry_include_ip_address?
+ BOOL incl_ip;
+ if (exp_bool(addrlist, US"transport", tblock->name, 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);
}
int delete_flag = message_defer? 0 : rf_delete;
if (retry_message_key == NULL)
{
- retry_message_key = ob->retry_include_ip_address?
+ BOOL incl_ip;
+ if (exp_bool(addrlist, US"transport", tblock->name, 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);
case, see if any of them are deferred. */
if (rc == OK)
- {
- for (addr = addrlist; addr != NULL; addr = addr->next)
- {
+ for (addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == DEFER)
{
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
down an existing TCP/IP connection, and something caused the host not to be
found, we end up here, but can detect these cases and handle them specially. */
-for (addr = addrlist; addr != NULL; addr = addr->next)
+for (addr = addrlist; addr; addr = addr->next)
{
/* If host is not NULL, it means that we stopped processing the host list
because of hosts_max_try or hosts_max_try_hardlimit. In the former case, this
However, if we have hit hosts_max_try_hardlimit, we want to behave as if all
hosts were tried. */
- if (host != NULL)
- {
+ if (host)
if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
{
DEBUG(D_transport)
debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
setflag(addr, af_retry_skipped);
}
- }
if (queue_smtp) /* no deliveries attempted */
{
addr->message = US"SMTP delivery explicitly queued";
}
- else if (addr->transport_return == DEFER &&
- (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0) &&
- addr->message == NULL)
+ else if ( addr->transport_return == DEFER
+ && (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0)
+ && !addr->message
+ )
{
addr->basic_errno = ERRNO_HRETRY;
- if (continue_hostname != NULL)
- {
+ if (continue_hostname)
addr->message = US"no host found for existing SMTP connection";
- }
else if (expired)
{
setflag(addr, af_pass_message); /* This is not a security risk */
- addr->message = (ob->delay_after_cutoff)?
- US"retry time not reached for any host after a long failure period" :
- US"all hosts have been failing for a long time and were last tried "
- "after this message arrived";
+ addr->message = string_sprintf(
+ "all hosts%s have been failing for a long time %s",
+ addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"",
+ ob->delay_after_cutoff
+ ? US"(and retry time not reached)"
+ : US"and were last tried after this message arrived");
/* If we are already using fallback hosts, or there are no fallback hosts
defined, convert the result to FAIL to cause a bounce. */
- if (addr->host_list == addr->fallback_hosts ||
- addr->fallback_hosts == NULL)
+ if (addr->host_list == addr->fallback_hosts || !addr->fallback_hosts)
addr->transport_return = FAIL;
}
else
{
+ const char * s;
if (hosts_retry == hosts_total)
- addr->message = US"retry time not reached for any host";
+ s = "retry time not reached for any host%s";
else if (hosts_fail == hosts_total)
- addr->message = US"all host address lookups failed permanently";
+ s = "all host address lookups%s failed permanently";
else if (hosts_defer == hosts_total)
- addr->message = US"all host address lookups failed temporarily";
+ s = "all host address lookups%s failed temporarily";
else if (hosts_serial == hosts_total)
- addr->message = US"connection limit reached for all hosts";
+ s = "connection limit reached for all hosts%s";
else if (hosts_fail+hosts_defer == hosts_total)
- addr->message = US"all host address lookups failed";
- else addr->message = US"some host address lookups failed and retry time "
- "not reached for other hosts or connection limit reached";
+ s = "all host address lookups%s failed";
+ else
+ s = "some host address lookups failed and retry time "
+ "not reached for other hosts or connection limit reached%s";
+
+ addr->message = string_sprintf(s,
+ addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"");
}
}
}