-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.9 2005/04/06 15:26:52 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* 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, allow_localhost) },
{ "authenticated_sender", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, authenticated_sender) },
+ { "authenticated_sender_force", opt_bool,
+ (void *)offsetof(smtp_transport_options_block, authenticated_sender_force) },
{ "command_timeout", opt_time,
(void *)offsetof(smtp_transport_options_block, command_timeout) },
{ "connect_timeout", opt_time,
(void *)offsetof(smtp_transport_options_block, data_timeout) },
{ "delay_after_cutoff", opt_bool,
(void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
-#ifdef EXPERIMENTAL_DOMAINKEYS
- { "dk_canon", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dk_canon) },
- { "dk_domain", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dk_domain) },
- { "dk_headers", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dk_headers) },
- { "dk_private_key", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dk_private_key) },
- { "dk_selector", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dk_selector) },
- { "dk_strict", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dk_strict) },
+#ifndef DISABLE_DKIM
+ { "dkim_canon", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim_canon) },
+ { "dkim_domain", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim_domain) },
+ { "dkim_private_key", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim_private_key) },
+ { "dkim_selector", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim_selector) },
+ { "dkim_sign_headers", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim_sign_headers) },
+ { "dkim_strict", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, 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) },
+ { "dnssec_require_domains", opt_stringptr,
+ (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, fallback_hosts) },
{ "final_timeout", opt_time,
(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; 4.83 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) },
{ "hosts_avoid_esmtp", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_avoid_esmtp) },
- #ifdef SUPPORT_TLS
+ { "hosts_avoid_pipelining", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_avoid_pipelining) },
+#ifdef SUPPORT_TLS
{ "hosts_avoid_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_avoid_tls) },
- #endif
+#endif
{ "hosts_max_try", opt_int,
(void *)offsetof(smtp_transport_options_block, hosts_max_try) },
{ "hosts_max_try_hardlimit", opt_int,
(void *)offsetof(smtp_transport_options_block, hosts_max_try_hardlimit) },
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
{ "hosts_nopass_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
- #endif
+#endif
{ "hosts_override", opt_bool,
(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(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
+#ifdef SUPPORT_TLS
+# 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
{ "hosts_require_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
- #endif
+#endif
{ "hosts_try_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+#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
+#ifdef SUPPORT_TLS
+ { "hosts_verify_avoid_tls", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
+#endif
{ "interface", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, interface) },
{ "keepalive", opt_bool,
(void *)offsetof(smtp_transport_options_block, keepalive) },
+ { "lmtp_ignore_quota", opt_bool,
+ (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_TLS
+#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) },
{ "tls_crl", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_crl) },
+ { "tls_dh_min_bits", opt_int,
+ (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
{ "tls_privatekey", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_privatekey) },
- { "tls_require_ciphers", opt_stringptr,
+ { "tls_require_ciphers", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
+ { "tls_sni", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, tls_sni) },
{ "tls_tempfail_tryclear", opt_bool,
(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) }
- #endif
+ (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
+ { "tls_verify_hosts", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) }
+#endif
};
/* Size of the options list. An extern variable has to be used so that its
NULL, /* interface */
NULL, /* port */
US"smtp", /* protocol */
+ NULL, /* DSCP */
NULL, /* serialize_hosts */
NULL, /* hosts_try_auth */
NULL, /* hosts_require_auth */
+#ifdef EXPERIMENTAL_DANE
+ NULL, /* hosts_try_dane */
+ NULL, /* hosts_require_dane */
+#endif
+#ifndef DISABLE_PRDR
+ US"*", /* hosts_try_prdr */
+#endif
+#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 */
+ NULL, /* hosts_verify_avoid_tls */
+ NULL, /* hosts_avoid_pipelining */
NULL, /* hosts_avoid_esmtp */
NULL, /* hosts_nopass_tls */
5*60, /* command_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 */
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_SOCKS
+ ,NULL /* socks_proxy */
+#endif
+#ifdef SUPPORT_TLS
,NULL, /* tls_certificate */
NULL, /* tls_crl */
NULL, /* tls_privatekey */
NULL, /* tls_require_ciphers */
- NULL, /* tls_verify_certificates */
- TRUE /* tls_tempfail_tryclear */
- #endif
- #ifdef EXPERIMENTAL_DOMAINKEYS
- ,NULL, /* dk_canon */
- NULL, /* dk_domain */
- NULL, /* dk_headers */
- NULL, /* dk_private_key */
- NULL, /* dk_selector */
- NULL /* dk_strict */
- #endif
+ NULL, /* gnutls_require_kx */
+ NULL, /* gnutls_require_mac */
+ NULL, /* gnutls_require_proto */
+ 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 */
+#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
};
+/* 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 */
static uschar *smtp_command; /* Points to last cmd for error messages */
static uschar *mail_command; /* Points to MAIL cmd for error messages */
+static BOOL update_waiting; /* TRUE to update the "wait" database */
/*************************************************
but before running it in a sub-process. It is used for two things:
(1) To set the fallback host list in addresses, when delivering.
- (2) To pass back the interface, port, and protocol options, for use during
- callout verification.
+ (2) To pass back the interface, port, protocol, and other options, for use
+ during callout verification.
Arguments:
tblock pointer to the transport instance block
addrlist list of addresses about to be transported
tf if not NULL, pointer to block in which to return options
+ uid the uid that will be set (not used)
+ gid the gid that will be set (not used)
errmsg place for error message (not used)
Returns: OK always (FAIL, DEFER not used)
static int
smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
- transport_feedback *tf, uschar **errmsg)
+ transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
{
smtp_transport_options_block *ob =
(smtp_transport_options_block *)(tblock->options_block);
errmsg = errmsg; /* Keep picky compilers happy */
+uid = uid;
+gid = gid;
/* Pass back options if required. This interface is getting very messy. */
tf->gethostbyname = ob->gethostbyname;
tf->qualify_single = ob->dns_qualify_single;
tf->search_parents = ob->dns_search_parents;
+ tf->helo_data = ob->helo_data;
}
/* Set the fallback host list for all the addresses that don't have fallback
/* Set the default port according to the protocol */
if (ob->port == NULL)
- ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" : US"smtp";
+ ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" :
+ (strcmpic(ob->protocol, US"smtps") == 0)? US"smtps" : US"smtp";
/* Set up the setup entry point, to be called before subprocesses for this
transport. */
for them, but do not do any lookups at this time. */
host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
+
+#ifdef SUPPORT_TLS
+if ( ob->gnutls_require_kx
+ || ob->gnutls_require_mac
+ || ob->gnutls_require_proto)
+ log_write(0, LOG_MAIN, "WARNING: smtp transport options"
+ " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols"
+ " are obsolete\n");
+#endif
}
status means that an address is not currently being processed.
Arguments:
- addrlist points to a chain of addresses
- errno_value to put in each address's errno field
- msg to put in each address's message field
- rc to put in each address's transport_return field
+ addrlist points to a chain of addresses
+ errno_value to put in each address's errno field
+ 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
Returns: nothing
*/
-static
-void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc)
+static void
+set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
+ 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) addr->message = msg;
- addr->transport_return = rc;
- }
+for (addr = addrlist; addr; addr = addr->next)
+ if (addr->transport_return >= PENDING)
+ {
+ 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
+ }
+ }
}
+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
+ );
+}
/*************************************************
chosen.
Arguments:
- host the current host, to get its name for messages
- errno_value pointer to the errno value
- more_errno from the top address for use with ERRNO_FILTER_FAIL
- buffer the SMTP response buffer
- yield where to put a one-digit SMTP response code
- message where to put an errror message
-
-Returns: TRUE if an SMTP "QUIT" command should be sent, else FALSE
+ host the current host, to get its name for messages
+ errno_value pointer to the errno value
+ more_errno from the top address for use with ERRNO_FILTER_FAIL
+ buffer the SMTP response buffer
+ yield where to put a one-digit SMTP response code
+ message where to put an errror message
+ pass_message set TRUE if message is an SMTP response
+
+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,
- uschar *buffer, int *yield, uschar **message)
+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);
- *message = US string_sprintf("SMTP error from remote mailer after %s%s: "
- "host %s [%s]: %s", pl, smtp_command, host->name, host->address, s);
+ const uschar *s = string_printing(buffer);
+ *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
+ "%s", pl, smtp_command, s);
+ *pass_message = TRUE;
*yield = buffer[0];
return TRUE;
}
/* No data was read. If there is no errno, this must be the EOF (i.e.
-connection closed) case, which causes deferral. Otherwise, put the host's
-identity in the message, leaving the errno value to be interpreted as well. In
-all cases, we have to assume the connection is now dead. */
+connection closed) case, which causes deferral. An explicit connection reset
+error has the same effect. Otherwise, put the host's identity in the message,
+leaving the errno value to be interpreted as well. In all cases, we have to
+assume the connection is now dead. */
-if (*errno_value == 0)
+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
{
- log_write(0, LOG_MAIN, "%s [%s]: %s",
- host->name,
- host->address,
- strerror(addr->basic_errno));
- deliver_msglog("%s %s [%s]: %s\n",
- tod_stamp(tod_log),
- host->name,
- host->address,
- 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);
+}
+
+#ifndef DISABLE_EVENT
+/*************************************************
+* Post-defer action *
+*************************************************/
+
+/* This expands an arbitrary per-transport string.
+ It might, for example, be used to write to the database log.
+
+Arguments:
+ addr the address item containing error information
+ host the current host
+
+Returns: nothing
+*/
+
+static void
+deferred_event_raise(address_item *addr, host_item *host)
+{
+uschar * action = addr->transport->event_action;
+const uschar * save_domain;
+uschar * save_local;
+
+if (!action)
+ return;
+
+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;
+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 *
*************************************************/
converted to OK at the end.
Arguments:
- addrlist the complete address list
- include_affixes TRUE if affixes include in RCPT
- sync_addr ptr to the ptr of the one to start scanning at (updated)
- host the host we are connected to
- count the number of responses to read
- pending_MAIL true if the first response is for MAIL
- pending_DATA 0 if last command sent was not DATA
- +1 if previously had a good recipient
- -1 if not previously had a good recipient
- inblock incoming SMTP block
- timeout timeout value
- buffer buffer for reading response
- buffsize size of buffer
+ addrlist the complete address list
+ include_affixes TRUE if affixes include in RCPT
+ sync_addr ptr to the ptr of the one to start scanning at (updated)
+ host the host we are connected to
+ count the number of responses to read
+ address_retry_
+ include_sender true if 4xx retry is to include the sender it its key
+ pending_MAIL true if the first response is for MAIL
+ pending_DATA 0 if last command sent was not DATA
+ +1 if previously had a good recipient
+ -1 if not previously had a good recipient
+ inblock incoming SMTP block
+ timeout timeout value
+ buffer buffer for reading response
+ buffsize size of buffer
Returns: 3 if at least one address had 2xx and one had 5xx
2 if at least one address had 5xx but none had 2xx
static int
sync_responses(address_item *addrlist, BOOL include_affixes,
- address_item **sync_addr, host_item *host, int count, BOOL pending_MAIL,
+ address_item **sync_addr, host_item *host, int count,
+ BOOL address_retry_include_sender, BOOL pending_MAIL,
int pending_DATA, smtp_inblock *inblock, int timeout, uschar *buffer,
int buffsize)
{
if (errno == 0 && buffer[0] != 0)
{
uschar flushbuffer[4096];
+ int save_errno = 0;
+ if (buffer[0] == '4')
+ {
+ save_errno = ERRNO_MAIL4XX;
+ addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
while (count-- > 0)
{
if (!smtp_read_response(inblock, flushbuffer, sizeof(flushbuffer),
&& (errno != 0 || flushbuffer[0] == 0))
break;
}
+ 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))
{
addr->transport_return = PENDING_OK;
/* If af_dr_retry_exists is set, there was a routing delay on this address;
- ensure that any address-specific retry record is expunged. */
+ ensure that any address-specific retry record is expunged. We do this both
+ for the basic key and for the version that also includes the sender. */
if (testflag(addr, af_dr_retry_exists))
+ {
+ uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+ sender_address);
+ retry_add_item(addr, altkey, rf_delete);
retry_add_item(addr, addr->address_retry_key, rf_delete);
+ }
}
/* Timeout while reading the response */
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);
+ 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);
- host->update_waiting = FALSE;
+ update_waiting = FALSE;
return -1;
}
else
{
addr->message =
- string_sprintf("SMTP error from remote mailer after RCPT TO:<%s>: "
- "host %s [%s]: %s", transport_rcpt_address(addr, include_affixes),
- host->name, host->address, string_printing(buffer));
- deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
+ string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
+ "%s", transport_rcpt_address(addr, include_affixes),
+ string_printing(buffer));
+ setflag(addr, af_pass_message);
+ msglog_line(host, addr->message);
/* The response was 5xx */
else
{
- int bincode = (buffer[1] - '0')*10 + buffer[2] - '0';
-
addr->transport_return = DEFER;
addr->basic_errno = ERRNO_RCPT4XX;
- addr->more_errno |= bincode << 8;
+ 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
- if (host->next != NULL) log_write(0, LOG_MAIN, "%s", addr->message);
+ /* Log temporary errors if there are more hosts to be tried.
+ If not, log this last one in the == line. */
- /* Do not put this message on the list of those waiting for this host,
- as otherwise it is likely to be tried too often. */
+ if (host->next)
+ log_write(0, LOG_MAIN, "H=%s [%s]: %s", host->name, host->address, addr->message);
- host->update_waiting = FALSE;
+#ifndef DISABLE_EVENT
+ else
+ msg_event_raise(US"msg:rcpt:defer", addr);
+#endif
- /* Add a retry item for the address so that it doesn't get tried
- again too soon. */
+ /* Do not put this message on the list of those waiting for specific
+ hosts, as otherwise it is likely to be tried too often. */
- retry_add_item(addr, addr->address_retry_key, 0);
+ update_waiting = FALSE;
+
+ /* Add a retry item for the address so that it doesn't get tried again
+ too soon. If address_retry_include_sender is true, add the sender address
+ to the retry key. */
+
+ if (address_retry_include_sender)
+ {
+ uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+ sender_address);
+ retry_add_item(addr, altkey, 0);
+ }
+ else retry_add_item(addr, addr->address_retry_key, 0);
}
}
} /* Loop for next RCPT response */
{
int code;
uschar *msg;
- if (pending_DATA > 0 || (yield & 1) != 0) return -3;
- (void)check_response(host, &errno, 0, buffer, &code, &msg);
+ BOOL pass_message;
+ if (pending_DATA > 0 || (yield & 1) != 0)
+ {
+ if (errno == 0 && buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
+ return -3;
+ }
+ (void)check_response(host, &errno, 0, 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);
}
+/* Do the client side of smtp-level authentication */
+/*
+Arguments:
+ 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
+ DEFER Failed authentication (and was required)
+ ERROR Internal problem
+
+ FAIL_SEND Failed communications - transmit
+ 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)
+{
+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_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_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. */
+
+ 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");
+
+ /* 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;
+ }
+
+ /* Loop to scan supported server mechanisms */
+
+ 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;
+ }
+
+ /* 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);
+
+ /* 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_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 */
+ }
+ }
+
+/* If we haven't authenticated, but are required to, give up. */
+
+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;
+}
+
+
+/* Construct AUTH appendix string for MAIL TO */
+/*
+Arguments
+ buffer to build string
+ addrlist chain of potential addresses to deliver
+ ob transport options
+
+Globals smtp_authenticated
+ client_authenticated_sender
+Return True on error, otherwise buffer has (possibly empty) terminated string
+*/
+
+BOOL
+smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
+ smtp_transport_options_block *ob)
+{
+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");
+#endif
+
+if (ob->authenticated_sender != NULL)
+ {
+ uschar *new = expand_string(ob->authenticated_sender);
+ if (new == NULL)
+ {
+ if (!expand_string_forcedfail)
+ {
+ uschar *message = string_sprintf("failed to expand "
+ "authenticated_sender: %s", expand_string_message);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ return TRUE;
+ }
+ }
+ else if (new[0] != 0) local_authenticated_sender = new;
+ }
+
+/* Add the authenticated sender address if present */
+
+if ((smtp_authenticated || ob->authenticated_sender_force) &&
+ local_authenticated_sender != NULL)
+ {
+ string_format(buffer, bufsize, " AUTH=%s",
+ auth_xtextencode(local_authenticated_sender,
+ Ustrlen(local_authenticated_sender)));
+ client_authenticated_sender = string_copy(local_authenticated_sender);
+ }
+else
+ *buffer= 0;
+
+return FALSE;
+}
+
+
+
+#ifdef EXPERIMENTAL_DANE
+int
+tlsa_lookup(const host_item * host, dns_answer * dnsa,
+ BOOL dane_required, BOOL * dane)
+{
+/* 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_AGAIN:
+ return DEFER; /* just defer this TLS'd conn */
+
+ default:
+ case DNS_FAIL:
+ if (dane_required)
+ return FAIL;
+ break;
+
+ case DNS_SUCCEED:
+ if (!dns_is_secure(dnsa))
+ {
+ log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
+ return DEFER;
+ }
+ *dane = TRUE;
+ break;
+ }
+return OK;
+}
+#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)
+ if (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;
+
+#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 *
*************************************************/
/* If continue_hostname is not null, we get here only when continuing to
deliver down an existing channel. The channel was passed as the standard
-input.
+input. TLS is never active on a passed channel; the previous process always
+closes it down before passing the connection on.
Otherwise, we have to make a connection to the remote host, and do the
initial protocol exchange.
failed by one of them.
host host to deliver to
host_af AF_INET or AF_INET6
- port TCP/IP port to use, in host byte order
+ 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;
smtp_transport_options_block *ob =
(smtp_transport_options_block *)(tblock->options_block);
BOOL lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+BOOL smtps = strcmpic(ob->protocol, US"smtps") == 0;
BOOL ok = FALSE;
BOOL send_rset = TRUE;
BOOL send_quit = TRUE;
BOOL completed_address = FALSE;
BOOL esmtp = TRUE;
BOOL pending_MAIL;
+BOOL pass_message = 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 *local_authenticated_sender = authenticated_sender;
-uschar *helo_data;
+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 */
outblock.cmd_count = 0;
outblock.authenticating = FALSE;
-/* Expand the greeting message */
+/* Reset the parameters of a TLS session. */
-helo_data = expand_string(ob->helo_data);
-if (helo_data == NULL)
- {
- uschar *message = string_sprintf("failed to expand helo_data: %s",
- expand_string_message);
- set_errno(addrlist, 0, message, DEFER);
- return ERROR;
- }
+tls_out.bits = 0;
+tls_out.cipher = NULL; /* the one we may use for this transport */
+tls_out.ourcert = NULL;
+tls_out.peercert = NULL;
+tls_out.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_out.sni = NULL;
+#endif
+tls_out.ocsp = OCSP_NOT_REQ;
-/* 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.
-Other expansion failures are serious. An empty result is ignored, but there is
-otherwise no check - this feature is expected to be used with LMTP and other
-cases where non-standard addresses (e.g. without domains) might be required. */
+/* Flip the legacy TLS-related variables over to the outbound set in case
+they're used in the context of the transport. Don't bother resetting
+afterward as we're in a subprocess. */
-if (ob->authenticated_sender != NULL)
+tls_modify_variables(&tls_out);
+
+#ifndef SUPPORT_TLS
+if (smtps)
{
- uschar *new = expand_string(ob->authenticated_sender);
- if (new == NULL)
- {
- if (!expand_string_forcedfail)
- {
- uschar *message = string_sprintf("failed to expand "
- "authenticated_sender: %s", expand_string_message);
- set_errno(addrlist, 0, message, DEFER);
- return ERROR;
- }
- }
- else if (new[0] != 0) local_authenticated_sender = new;
+ set_errno_nohost(addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
+ DEFER, FALSE);
+ return ERROR;
}
+#endif
/* Make a connection to the host if this isn't a continued delivery, and handle
the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
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);
+ smtp_connect(host, host_af, port, interface, ob->connect_timeout, tblock);
+
if (inblock.sock < 0)
{
- set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
- NULL, DEFER);
+ 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
+ )
+ && (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK
+ && dane_required /* do not error on only dane-requested */
+ )
+ {
+ 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,
where you want to escape on any error. */
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout)) goto RESPONSE_FAILED;
+ if (!smtps)
+ {
+ 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. */
+
+ if (helo_data == NULL)
+ {
+ uschar *message = string_sprintf("failed to expand helo_data: %s",
+ expand_string_message);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ }
/** Debugging without sending a message
addrlist->transport_return = DEFER;
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
+ if (smtps)
+ {
+ tls_offered = TRUE;
+ suppress_tls = FALSE;
+ ob->tls_tempfail_tryclear = FALSE;
+ smtp_command = US"SSL-on-connect";
+ goto TLS_NEGOTIATE;
+ }
+#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;
}
+ if (esmtp || lmtp)
+ peer_offered = ehlo_response(buffer, Ustrlen(buffer),
+ PEER_OFFERED_TLS
+ | 0 /* IGNQ checked later */
+ | 0 /* PRDR checked later */
+ | 0 /* UTF8 checked later */
+ | 0 /* DSN checked later */
+ | 0 /* PIPE checked later */
+ | 0 /* SIZE 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 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);
smtp_command = big_buffer;
+ host->port = port; /* Record the port that was used */
}
/* If TLS is available on this connection, whether continued or not, attempt to
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))
{
- Ustrncpy(buffer, buffer2, sizeof(buffer));
if (errno != 0 || buffer2[0] == 0 ||
(buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+ {
+ Ustrncpy(buffer, buffer2, sizeof(buffer));
goto RESPONSE_FAILED;
+ }
}
/* STARTTLS accepted: try to negotiate a TLS session. */
else
+ TLS_NEGOTIATE:
{
- int rc = tls_client_start(inblock.sock, host, addrlist,
- NULL, /* No DH param */
- ob->tls_certificate,
- ob->tls_privatekey,
- ob->tls_verify_certificates,
- ob->tls_crl,
- ob->tls_require_ciphers,
- ob->command_timeout);
+ 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 && !dane_required)
+ {
+ log_write(0, LOG_MAIN, "DANE attempt failed;"
+ " trying CA-root TLS to %s [%s] (not in hosts_require_dane)",
+ host->name, host->address);
+ dane = FALSE;
+ goto TLS_NEGOTIATE;
+ }
+# 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_cipher;
- addr->peerdn = tls_peerdn;
+ addr->cipher = tls_out.cipher;
+ addr->ourcert = tls_out.ourcert;
+ addr->peercert = tls_out.peercert;
+ addr->peerdn = tls_out.peerdn;
+ addr->ocsp = tls_out.ocsp;
}
- }
}
}
-/* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. */
-
-if (tls_active >= 0)
- {
- if (smtp_write_command(&outblock, FALSE, "%s %s\r\n", lmtp? "LHLO" : "EHLO",
- helo_data) < 0)
- goto SEND_FAILED;
- if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout))
- 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)
- {
- 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");
- goto TLS_FAILED;
- }
-#endif
+/* if smtps, we'll have smtp_command set to something else; always safe to
+reset it here. */
+smtp_command = big_buffer;
-/* 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
-continued session down a previously-used socket, we haven't just done EHLO, so
-we skip this. */
+/* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. If
+helo_data is null, we are dealing with a connection that was passed from
+another process, and so we won't have expanded helo_data above. We have to
+expand it here. $sending_ip_address and $sending_port are set up right at the
+start of the Exim process (in exim.c). */
-if (continue_hostname == NULL
- #ifdef SUPPORT_TLS
- || tls_active >= 0
- #endif
- )
+if (tls_out.active >= 0)
{
- int require_auth;
- uschar *fail_reason = US"server did not advertise AUTH support";
-
- /* 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;
+ char *greeting_cmd;
+ BOOL good_response;
- /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
- the current host, esmtp will be false, so PIPELINING can never be used. */
-
- smtp_use_pipelining = esmtp &&
- pcre_exec(regex_PIPELINING, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
-
- DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
- smtp_use_pipelining? "" : "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
- authenticator's client driver is running. */
-
- smtp_authenticated = FALSE;
- require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
- host->name, host->address, NULL);
-
- if (esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+ if (helo_data == NULL)
{
- 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. */
-
- if (require_auth == OK ||
- verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
- host->address, NULL) == OK)
+ helo_data = expand_string(ob->helo_data);
+ if (helo_data == NULL)
{
- auth_instance *au;
- fail_reason = US"no common mechanisms were found";
-
- DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
-
- /* Scan the configured authenticators looking for one which is configured
- for use as a client and whose name matches an authentication mechanism
- supported by the server. If one is found, attempt to authenticate by
- calling its client function. */
+ uschar *message = string_sprintf("failed to expand helo_data: %s",
+ expand_string_message);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ }
- for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
- {
- uschar *p = names;
- if (!au->client) continue;
+ /* For SMTPS we need to wait for the initial OK response. */
+ if (smtps)
+ {
+ 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;
+ }
- /* Loop to scan supported server mechanisms */
+ if (esmtp)
+ greeting_cmd = "EHLO";
+ else
+ {
+ greeting_cmd = "HELO";
+ DEBUG(D_transport)
+ debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+ }
- while (*p != 0)
- {
- int rc;
- int len = Ustrlen(au->public_name);
- while (isspace(*p)) p++;
+ if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
+ lmtp? "LHLO" : greeting_cmd, helo_data) < 0)
+ goto SEND_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 (strncmpic(au->public_name, p, len) != 0 ||
- (p[len] != 0 && !isspace(p[len])))
- {
- while (*p != 0 && !isspace(*p)) p++;
- continue;
- }
+/* If the host is required to use a secure channel, ensure that we
+have one. */
- /* 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. */
+else if (
+# 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, but %s",
+ tls_offered? "an attempt to start TLS failed" :
+ "the server did not offer TLS support");
+ goto TLS_FAILED;
+ }
+#endif /*SUPPORT_TLS*/
- fail_reason = US"authentication attempt(s) failed";
- outblock.authenticating = TRUE;
- rc = (au->info->clientcode)(au, &inblock, &outblock,
- ob->command_timeout, buffer, sizeof(buffer));
- outblock.authenticating = FALSE;
- DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
- au->name, rc);
+/* 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
+continued session down a previously-used socket, we haven't just done EHLO, so
+we skip this. */
- /* 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. */
+if (continue_hostname == NULL
+#ifdef SUPPORT_TLS
+ || tls_out.active >= 0
+#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_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)
+ );
- switch(rc)
- {
- case OK:
- smtp_authenticated = TRUE; /* stops the outer loop */
- break;
+ /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+ lmtp_ignore_quota option was set. */
- /* Failure after writing a command */
+ igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
- case FAIL_SEND:
- goto SEND_FAILED;
+ /* If the response to EHLO specified support for the SIZE parameter, note
+ this, provided size_addition is non-negative. */
- /* Failure after reading a response */
+ smtp_use_size = !!(peer_offered & PEER_OFFERED_SIZE);
- case FAIL:
- if (errno != 0 || buffer[0] != '5') goto RESPONSE_FAILED;
- log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
- au->name, host->name, host->address, buffer);
- break;
+ /* 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. */
- /* 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? */
+ smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE
+ && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK;
- case CANCELLED:
- break;
+ DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
+ smtp_use_pipelining ? "" : "not ");
- /* Internal problem, message in buffer. */
+#ifndef DISABLE_PRDR
+ if ( peer_offered & PEER_OFFERED_PRDR
+ && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
+ peer_offered &= ~PEER_OFFERED_PRDR;
- case ERROR:
- yield = ERROR;
- set_errno(addrlist, 0, string_copy(buffer), DEFER);
- goto SEND_QUIT;
- }
+ if (peer_offered & PEER_OFFERED_PRDR)
+ {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+#endif
- break; /* If not authenticated, try next authenticator */
- } /* Loop for scanning supported server mechanisms */
- } /* Loop for further authenticators */
- }
- }
+ /* 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 ");
- /* If we haven't authenticated, but are required to, give up. */
+ /* 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. */
- if (require_auth == OK && !smtp_authenticated)
+ switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+ ob, esmtp, &inblock, &outblock))
{
- yield = DEFER;
- set_errno(addrlist, ERRNO_AUTHFAIL,
- string_sprintf("authentication required but %s", fail_reason), DEFER);
- goto SEND_QUIT;
+ default: goto SEND_QUIT;
+ case OK: break;
+ case FAIL_SEND: goto SEND_FAILED;
+ case FAIL: goto RESPONSE_FAILED;
}
}
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. */
sprintf(CS buffer, "%.50s transport", tblock->name);
rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
TRUE, DEFER, addrlist, buffer, NULL);
+ transport_filter_timeout = tblock->filter_timeout;
/* On failure, copy the error to all addresses, abandon the SMTP call, and
yield ERROR. */
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++;
}
-/* Add the authenticated sender address if present */
+#ifndef DISABLE_PRDR
+prdr_active = FALSE;
+if (peer_offered & PEER_OFFERED_PRDR)
+ for (addr = first_addr; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ {
+ for (addr = addr->next; 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;
+ break;
+ }
+ break;
+ }
+#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;
+ }
-if (smtp_authenticated && local_authenticated_sender != NULL)
+/* Add any DSN flags to the mail command */
+
+if (smtp_use_dsn && !dsn_all_lasthop)
{
- string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
- auth_xtextencode(local_authenticated_sender,
- Ustrlen(local_authenticated_sender)));
+ if (dsn_ret == dsn_ret_hdrs)
+ {
+ Ustrcpy(p, " RET=HDRS");
+ while (*p) p++;
+ }
+ else if (dsn_ret == dsn_ret_full)
+ {
+ Ustrcpy(p, " RET=FULL");
+ while (*p) p++;
+ }
+ 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.
+Other expansion failures are serious. An empty result is ignored, but there is
+otherwise no check - this feature is expected to be used with LMTP and other
+cases where non-standard addresses (e.g. without domains) might be required. */
+
+if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
+ {
+ yield = ERROR;
+ goto SEND_QUIT;
}
/* From here until we send the DATA command, we can make use of PIPELINING
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',
- ob->command_timeout)) goto RESPONSE_FAILED;
- pending_MAIL = FALSE;
- break;
+ 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;
+ }
+ goto RESPONSE_FAILED;
+ }
+ pending_MAIL = FALSE;
+ break;
}
/* Pass over all the relevant recipient addresses for this host, which are the
{
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);
+ /* 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) != 1)
+ {
+ 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 != NULL)
+ {
+ 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>\r\n",
- transport_rcpt_address(addr, tblock->rcpt_include_affixes));
+ 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)
{
switch(sync_responses(first_addr, tblock->rcpt_include_affixes,
- &sync_addr, host, count, pending_MAIL, 0, &inblock,
- ob->command_timeout, buffer, sizeof(buffer)))
+ &sync_addr, host, count, ob->address_retry_include_sender,
+ pending_MAIL, 0, &inblock, ob->command_timeout, buffer,
+ sizeof(buffer)))
{
case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */
case 2: completed_address = TRUE; /* 5xx (only) => progress made */
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);
- 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
int count = smtp_write_command(&outblock, FALSE, "DATA\r\n");
if (count < 0) goto SEND_FAILED;
switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
- host, count, pending_MAIL, ok? +1 : -1, &inblock,
- ob->command_timeout, buffer, sizeof(buffer)))
+ host, count, ob->address_retry_include_sender, pending_MAIL,
+ ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer)))
{
case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */
case 2: completed_address = TRUE; /* 5xx (only) => progress made */
DEBUG(D_transport|D_v)
debug_printf(" SMTP>> writing message and terminating \".\"\n");
transport_count = 0;
-#ifdef EXPERIMENTAL_DOMAINKEYS
- if ( (ob->dk_private_key != NULL) && (ob->dk_selector != NULL) )
- ok = dk_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 */
- tblock->add_headers, tblock->remove_headers,
- US".", US"..", /* Escaping strings */
- tblock->rewrite_rules, tblock->rewrite_existflags,
- ob->dk_private_key, ob->dk_domain, ob->dk_selector,
- ob->dk_canon, ob->dk_headers, ob->dk_strict);
- else
-#endif
+
+#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 */
+ tblock->add_headers, tblock->remove_headers,
+ US".", US"..", /* 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
+ );
+#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->add_headers, tblock->remove_headers,
US".", US"..", /* Escaping strings */
tblock->rewrite_rules, tblock->rewrite_existflags);
+#endif
/* transport_write_message() uses write() because it is called from other
places to write to non-sockets. This means that under some OS (e.g. Solaris)
smtp_command = US"end of data";
- /* For SMTP, we now read a single response that applies to the whole message.
- If it is OK, then all the addresses have been delivered. */
+#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
+ * with per non-PRDR. */
+ if(prdr_active)
+ {
+ ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
+ ob->final_timeout);
+ if (!ok && errno == 0)
+ switch(buffer[0])
+ {
+ case '2': prdr_active = FALSE;
+ ok = TRUE;
+ break;
+ case '4': errno = ERRNO_DATA4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ break;
+ }
+ }
+ else
+#endif
+
+ /* For non-PRDR SMTP, we now read a single response that applies to the
+ whole message. If it is OK, then all the addresses have been delivered. */
- if (!lmtp) ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->final_timeout);
+ if (!lmtp)
+ {
+ ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+ ob->final_timeout);
+ if (!ok && errno == 0 && buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
+ }
/* For LMTP, we get back a response for every RCPT command that we sent;
some may be accepted and some rejected. For those that get a response, their
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 ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp)
+ if (
+#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, read a status for
+ /* Process all transported addresses - for LMTP or PRDR, read a status for
each one. */
for (addr = addrlist; addr != first_addr; addr = addr->next)
/* LMTP - if the response fails badly (e.g. timeout), use it for all the
remaining addresses. Otherwise, it's a return code for just the one
- address. */
+ address. For temporary errors, add a retry item for the address so that
+ it doesn't get tried again too soon. */
+#ifndef DISABLE_PRDR
+ if (lmtp || prdr_active)
+#else
if (lmtp)
+#endif
{
if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->final_timeout))
{
if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
- addr->message = string_sprintf("LMTP error after %s: %s",
+ addr->message = string_sprintf(
+#ifndef DISABLE_PRDR
+ "%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
+#else
+ "LMTP error after %s: %s",
+#endif
big_buffer, string_printing(buffer));
- addr->transport_return = (buffer[0] == '5')? FAIL : DEFER;
+ setflag(addr, af_pass_message); /* Allow message to go to user */
+ if (buffer[0] == '5')
+ addr->transport_return = FAIL;
+ else
+ {
+ errno = ERRNO_DATA4XX;
+ addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ addr->transport_return = DEFER;
+#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 (LOGGING(smtp_confirmation))
+ {
+ 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;
+ }
}
/* SMTP, or success return from LMTP for this address. Pass back the
- actual port used. */
+ actual host that was used. */
addr->transport_return = OK;
addr->more_errno = delivery_time;
- thost->port = port;
- addr->host_used = thost;
+ addr->host_used = host;
addr->special_action = flag;
addr->message = conf;
+#ifndef DISABLE_PRDR
+ if (prdr_active) addr->flags |= af_prdr_used;
+#endif
flag = '-';
- /* Update the journal. For homonymic addresses, use the base address plus
- the transport name. See lots of comments in deliver.c about the reasons
- for the complications when homonyms are involved. Just carry on after
- write error, as it may prove possible to update the spool file later. */
-
- if (testflag(addr, af_homonym))
- sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
- else
- sprintf(CS buffer, "%.500s\n", addr->unique);
-
- DEBUG(D_deliver) debug_printf("journalling %s", buffer);
- len = Ustrlen(CS buffer);
- if (write(journal_fd, buffer, len) != len)
- log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
- "%s: %s", buffer, strerror(errno));
+#ifndef DISABLE_PRDR
+ if (!prdr_active)
+#endif
+ {
+ /* Update the journal. For homonymic addresses, use the base address plus
+ the transport name. See lots of comments in deliver.c about the reasons
+ for the complications when homonyms are involved. Just carry on after
+ write error, as it may prove possible to update the spool file later. */
+
+ if (testflag(addr, af_homonym))
+ sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+ else
+ sprintf(CS buffer, "%.500s\n", addr->unique);
+
+ 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 "
+ "%s: %s", buffer, strerror(errno));
+ }
}
+#ifndef DISABLE_PRDR
+ if (prdr_active)
+ {
+ /* PRDR - get the final, overall response. For any non-success
+ upgrade all the address statuses. */
+ ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+ ob->final_timeout);
+ if (!ok)
+ {
+ if(errno == 0 && buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
+ for (addr = addrlist; addr != first_addr; addr = addr->next)
+ if (buffer[0] == '5' || addr->transport_return == OK)
+ addr->transport_return = PENDING_OK; /* allow set_errno action */
+ goto RESPONSE_FAILED;
+ }
+
+ /* Update the journal, or setup retry. */
+ for (addr = addrlist; addr != first_addr; addr = addr->next)
+ if (addr->transport_return == OK)
+ {
+ if (testflag(addr, af_homonym))
+ sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+ else
+ sprintf(CS buffer, "%.500s\n", addr->unique);
+
+ 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 "
+ "%s: %s", buffer, strerror(errno));
+ }
+ else if (addr->transport_return == DEFER)
+ retry_add_item(addr, addr->address_retry_key, -2);
+ }
+#endif
+
/* Ensure the journal file is pushed out to disk. */
- if (fsync(journal_fd) < 0)
+ if (EXIMfsync(journal_fd) < 0)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
strerror(errno));
}
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);
- 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);
- }
+ set_rc = FAIL;
else
- {
- set_errno(addrlist, save_errno, message, DEFER);
- yield = DEFER;
- }
+ yield = set_rc = DEFER;
}
- /* If there was an I/O error or timeout or other transportation error,
- indicated by errno being non-zero, defer all addresses and yield DEFER,
- except for the case of failed add_headers expansion, or a transport filter
- failure, when the yield should be ERROR, to stop it trying other hosts.
-
- However, handle timeouts after MAIL FROM or "." and loss of connection after
+ /* We want to handle timeouts after MAIL or "." and loss of connection after
"." specially. They can indicate a problem with the sender address or with
- the contents of the message rather than a real error on the connection.
- Therefore, treat these cases in the same way as a 4xx response.
+ the contents of the message rather than a real error on the connection. These
+ cases are treated in the same way as a 4xx response. This next bit of code
+ does the classification. */
- The following condition tests for NOT these special cases. */
-
- else if (save_errno != 0 &&
- (save_errno != ETIMEDOUT ||
- (Ustrncmp(smtp_command,"MAIL",4) != 0 &&
- Ustrncmp(smtp_command,"end ",4) != 0)) &&
- (save_errno != ERRNO_SMTPCLOSED ||
- Ustrncmp(smtp_command,"end ",4) != 0))
+ else
{
- yield = (save_errno == ERRNO_CHHEADER_FAIL ||
- save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
- set_errno(addrlist, save_errno, message, DEFER);
- }
+ BOOL message_error;
+
+ 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;
+
+ case ETIMEDOUT:
+ 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;
+
+ default:
+ message_error = FALSE;
+ break;
+ }
- /* Otherwise we have a message-specific error response from the remote
- host. This is one of
- (a) negative response or timeout after "mail from"
- (b) negative response after "data"
- (c) negative response or timeout or dropped connection after "."
- It won't be a negative response or timeout after "rcpt to", as that is dealt
- with separately above. The action in all cases is to set an appropriate
- error code for all the addresses, but to leave yield set to OK because
- the host itself has not failed. [It might in practice have failed for a
- timeout after MAIL FROM, or "." but if so, we'll discover that at the next
- delivery attempt.] For a temporary error, set the message_defer flag, and
- write to the logs for information if this is not the last host. The error for
- the last host will be logged as part of the address's log line. */
+ /* Handle the cases that are treated as message errors. These are:
- else
- {
- if (mua_wrapper) code = '5'; /* Force hard failure in wrapper mode */
+ (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
+ error code for all the addresses, but to leave yield set to OK because the
+ host itself has not failed. Of course, it might in practice have failed
+ when we've had a timeout, but if so, we'll discover that at the next
+ delivery attempt. For a temporary error, set the message_defer flag, and
+ write to the logs for information if this is not the last host. The error
+ for the last host will be logged as part of the address's log line. */
+
+ if (message_error)
+ {
+ if (mua_wrapper) code = '5'; /* Force hard failure in wrapper mode */
- set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER);
+ /* If there's an errno, the message contains just the identity of
+ the host. */
- /* If there's an errno, the message contains just the identity of
- the host. */
+ 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);
+ msglog_line(host, message);
+ *message_defer = TRUE;
+ }
+ }
+
+ /* Otherwise, we have an I/O error or a timeout other than after MAIL or
+ ".", or some other transportation error. We defer all addresses and yield
+ DEFER, except for the case of failed add_headers expansion, or a transport
+ filter failure, when the yield should be ERROR, to stop it trying other
+ hosts. */
- if (code != '5') /* Anything other than 5 is treated as temporary */
+ else
{
- 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);
- *message_defer = TRUE;
+ set_rc = DEFER;
+ yield = (save_errno == ERRNO_CHHEADER_FAIL ||
+ save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
}
}
+
+ 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_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;
if (send_rset)
{
ob->command_timeout)))
{
int code;
- send_quit = check_response(host, &errno, 0, buffer, &code, &msg);
+ send_quit = check_response(host, &errno, 0, buffer, &code, &msg,
+ &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
- if (tls_active >= 0)
+#ifdef SUPPORT_TLS
+ if (tls_out.active >= 0)
{
- tls_close(TRUE);
- ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
- smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout);
+ tls_close(FALSE, TRUE);
+ if (smtps)
+ ok = FALSE;
+ else
+ ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
+ 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);
+ else set_errno(first_addr, errno, msg, DEFER, FALSE, host
+#ifdef EXPERIMENTAL_DSN_INFO
+ , smtp_greeting, helo_response
+#endif
+ );
}
}
END_OFF:
#ifdef SUPPORT_TLS
-tls_close(TRUE);
+tls_close(FALSE, TRUE);
#endif
/* Close the socket, and return the appropriate value, first setting
-continue_transport and continue_hostname NULL to prevent any other addresses
-that may include the host from trying to re-use a continuation socket. This
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,
specified in the transports, and therefore not visible at top level, in which
case continue_more won't get set. */
-close(inblock.sock);
+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;
(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout);
-close(inblock.sock);
+(void)close(inblock.sock);
}
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->peerdn = NULL;
- #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;
}
continue_hostname, continue_host_address);
}
+/* Set the flag requesting that these hosts be added to the waiting
+database if the delivery fails temporarily or if we are running with
+queue_smtp or a 2-stage queue run. This gets unset for certain
+kinds of error, typically those that are specific to the message. */
+
+update_waiting = TRUE;
+
/* If a host list is not defined for the addresses - they must all have the
same one in order to be passed to a single transport - or if the transport has
a host list with hosts_override set, use the host list supplied with the
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);
host_build_hostlist(&hostlist, s, ob->hosts_randomize);
+ /* Check that the expansion yielded something useful. */
+ if (hostlist == NULL)
+ {
+ addrlist->message =
+ string_sprintf("%s transport has empty hosts setting", tblock->name);
+ addrlist->transport_return = PANIC;
+ return FALSE; /* Only top address has status */
+ }
+
/* 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 port. Set up a string for adding to the retry key if the port
-number is not the standard SMTP port. */
+/* Sort out the default port. */
if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
-pistring = string_sprintf(":%d", port);
-if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
-
/* For each host-plus-IP-address on the list:
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;
uschar *retry_message_key = NULL;
uschar *serialize_key = NULL;
- /* Set the flag requesting that this host be added to the waiting
- database if the delivery fails temporarily or if we are running with
- queue_smtp or a 2-stage queue run. This gets unset for certain
- kinds of error, typically those that are specific to the message. */
+ /* Default next host is next host. :-) But this can vary if the
+ hosts_max_try limit is hit (see below). It may also be reset if a host
+ address is looked up here (in case the host was multihomed). */
- host->update_waiting = TRUE;
+ nexthost = host->next;
/* If the address hasn't yet been obtained from the host name, look it up
now, unless the host is already marked as unusable. If it is marked as
if (host->address == NULL)
{
- uschar *canonical_name;
+ int new_port, flags;
+ host_item *hh;
if (host->status >= hstatus_unusable)
{
DEBUG(D_transport) debug_printf("getting address for %s\n", host->name);
+ /* The host name is permitted to have an attached port. Find it, and
+ strip it from the name. Just remember it for now. */
+
+ new_port = host_item_get_port(host);
+
+ /* Count hosts looked up */
+
hosts_looked_up++;
/* 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. */
- if (ob->gethostbyname || string_is_ip_address(host->name, NULL) > 0)
- rc = host_find_byname(host, NULL, &canonical_name, TRUE);
+ flags = HOST_FIND_BY_A;
+ if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
+ 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, NULL, TRUE);
else
- {
- int flags = HOST_FIND_BY_A;
- if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
- if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
- &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. */
+
+ for (hh = host; hh != nexthost; hh = hh->next) hh->port = new_port;
/* Failure to find the host at this time (usually DNS temporary failure)
is really a kind of routing failure rather than a transport failure.
continue; /* With next host */
}
- /* The default next host is the next host. :-) But this can vary if the
- hosts_max_try limit is hit (see below). NOTE: we cannot put this setting
- earlier than this, because a multihomed host whose addresses are not looked
- up till just above will add to the host list. */
+ /* Reset the default next host in case a multihomed host whose addresses
+ are not looked up till just above added to the host list. */
nexthost = host->next;
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, NULL, NULL,
- MCL_DOMAIN, TRUE, NULL) == OK))
+ match_isinlist(addrlist->domain,
+ (const uschar **)&queue_smtp_domains, 0,
+ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
{
expired = FALSE;
for (addr = addrlist; addr != NULL; addr = addr->next)
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
+ the default. */
+
+ pistring = string_sprintf(":%d", (host->port == PORT_NONE)?
+ port : host->port);
+ 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,
/* If there was a retry message key, implying that previously there
was a message-specific defer, we don't want to update the list of
- messages waiting for this host. */
+ messages waiting for these hosts. */
- if (retry_message_key != NULL) host->update_waiting = FALSE;
+ if (retry_message_key != NULL) update_waiting = FALSE;
continue; /* With the next host or IP address */
}
}
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);
+ 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);
+#ifndef DISABLE_EVENT
+ if (rc == DEFER)
+ 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
hosts_require_tls, and tls_tempfail_tryclear is true, try again, with
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);
+# ifndef DISABLE_EVENT
+ if (rc == DEFER)
+ 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);
}
to the retry chain. Note that if there was a message defer but now there is
a host defer, the message defer record gets deleted. That seems perfectly
reasonable. Also, stop the message from being remembered as waiting
- for this host. */
+ for specific hosts. */
if (message_defer || retry_message_key != NULL)
{
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);
}
retry_add_item(addrlist, retry_message_key,
rf_message | rf_host | delete_flag);
- host->update_waiting = FALSE;
+ update_waiting = FALSE;
}
/* Any return other than DEFER (that is, OK or ERROR) means that the
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
}
else if (expired)
{
- 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 "
+ 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";
/* If we are already using fallback hosts, or there are no fallback hosts
}
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"");
}
}
}
/* Update the database which keeps information about which messages are waiting
-for which hosts to become available. Each host in the list has a flag which is
-set if the data is to be updated. For some message-specific errors, the flag is
-turned off because we don't want follow-on deliveries in those cases. */
+for which hosts to become available. For some message-specific errors, the
+update_waiting flag is turned off because we don't want follow-on deliveries in
+those cases. If this transport instance is explicitly limited to one message
+per connection then follow-on deliveries are not possible and there's no need
+to create/update the per-transport wait-<transport_name> database. */
-transport_update_waiting(hostlist, tblock->name);
+if (update_waiting && tblock->connection_max_messages != 1)
+ transport_update_waiting(hostlist, tblock->name);
END_TRANSPORT:
return TRUE; /* Each address has its status */
}
+/* vi: aw ai sw=2
+*/
/* End of transport/smtp.c */