X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fsrc%2Ftransports%2Fsmtp.c;h=59afe42aa84229b636eb96a97769007d7c209248;hb=1a47b633600e808a11d75551306a2bf8ebf87335;hp=dc24e69389cc401b340308e53c08a5c1a58a3e3f;hpb=8b26070532a925dd0a2c1c9d0deb0894a4af8eec;p=exim.git diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index dc24e6938..59afe42aa 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2015 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -19,6 +19,11 @@ before the lower case letters). Some live in the transport_instance block so as 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, @@ -55,6 +60,10 @@ optionlist smtp_transport_options[] = { (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, @@ -65,7 +74,7 @@ optionlist smtp_transport_options[] = { (void *)offsetof(smtp_transport_options_block, gethostbyname) }, #ifdef SUPPORT_TLS /* These are no longer honoured, as of Exim 4.80; for now, we silently - ignore; a later release will warn, and a later-still release will remove + 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) }, @@ -98,14 +107,34 @@ optionlist smtp_transport_options[] = { (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 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 { "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) }, @@ -118,18 +147,22 @@ optionlist smtp_transport_options[] = { (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 EXPERIMENTAL_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) }, @@ -145,8 +178,14 @@ optionlist smtp_transport_options[] = { (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) } + (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) }, + { "tls_verify_hosts", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) } #endif }; @@ -172,9 +211,20 @@ smtp_transport_options_block smtp_transport_option_defaults = { 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 */ - US"*", /* hosts_verify_avoid_tls */ + NULL, /* hosts_verify_avoid_tls */ NULL, /* hosts_avoid_pipelining */ NULL, /* hosts_avoid_esmtp */ NULL, /* hosts_nopass_tls */ @@ -191,12 +241,17 @@ smtp_transport_options_block smtp_transport_option_defaults = { 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 EXPERIMENTAL_SOCKS + ,NULL /* socks_proxy */ +#endif #ifdef SUPPORT_TLS ,NULL, /* tls_certificate */ NULL, /* tls_crl */ @@ -206,10 +261,13 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* gnutls_require_mac */ NULL, /* gnutls_require_proto */ NULL, /* tls_sni */ - NULL, /* tls_verify_certificates */ + US"system", /* tls_verify_certificates */ EXIM_CLIENT_DH_DEFAULT_MIN_BITS, /* tls_dh_min_bits */ - TRUE /* tls_tempfail_tryclear */ + 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 */ @@ -221,6 +279,14 @@ smtp_transport_options_block smtp_transport_option_defaults = { #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 */ @@ -345,6 +411,15 @@ if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE; 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 } @@ -364,6 +439,9 @@ Arguments: 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 @@ -374,7 +452,11 @@ Returns: nothing static void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc, - BOOL pass_message) + BOOL pass_message, host_item * host +#ifdef EXPERIMENTAL_DSN_INFO + , const uschar * smtp_greeting, const uschar * helo_response +#endif + ) { address_item *addr; int orvalue = 0; @@ -383,20 +465,43 @@ if (errno_value == ERRNO_CONNECTTIMEOUT) errno_value = ETIMEDOUT; orvalue = RTEF_CTOUT; } -for (addr = addrlist; addr != NULL; addr = addr->next) - { - if (addr->transport_return < PENDING) continue; - addr->basic_errno = errno_value; - addr->more_errno |= orvalue; - if (msg != NULL) +for (addr = addrlist; addr; addr = addr->next) + if (addr->transport_return >= PENDING) { - addr->message = msg; - if (pass_message) setflag(addr, af_pass_message); + addr->basic_errno = errno_value; + addr->more_errno |= orvalue; + if (msg != NULL) + { + addr->message = msg; + if (pass_message) setflag(addr, af_pass_message); + } + addr->transport_return = rc; + if (host) + { + addr->host_used = host; +#ifdef EXPERIMENTAL_DSN_INFO + if (smtp_greeting) + {uschar * s = Ustrchr(smtp_greeting, '\n'); if (s) *s = '\0';} + addr->smtp_greeting = smtp_greeting; + + if (helo_response) + {uschar * s = Ustrchr(helo_response, '\n'); if (s) *s = '\0';} + addr->helo_response = helo_response; +#endif + } } - addr->transport_return = rc; - } } +static void +set_errno_nohost(address_item *addrlist, int errno_value, uschar *msg, int rc, + BOOL pass_message) +{ +set_errno(addrlist, errno_value, msg, rc, pass_message, NULL +#ifdef EXPERIMENTAL_DSN_INFO + , NULL, NULL +#endif + ); +} /************************************************* @@ -421,7 +526,8 @@ Arguments: Returns: TRUE if an SMTP "QUIT" command should be sent, else FALSE */ -static BOOL check_response(host_item *host, int *errno_value, int more_errno, +static BOOL +check_response(host_item *host, int *errno_value, int more_errno, uschar *buffer, int *yield, uschar **message, BOOL *pass_message) { uschar *pl = US""; @@ -438,8 +544,8 @@ if (smtp_use_pipelining && 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); @@ -450,15 +556,13 @@ if (*errno_value == ETIMEDOUT) 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; } @@ -492,13 +596,23 @@ if (*errno_value == ERRNO_WRITEINCOMPLETE) return FALSE; } +#ifdef EXPERIMENTAL_INTERNATIONAL +/* Handle lack of advertised SMTPUTF8, for international message */ +if (*errno_value == ERRNO_UTF8_FWD) + { + *message = US string_sprintf("utf8 support required but not offered for forwarding"); + DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message); + return TRUE; + } +#endif + /* Handle error responses from the remote mailer. */ if (buffer[0] != 0) { - uschar *s = string_printing(buffer); + const uschar *s = string_printing(buffer); *message = US string_sprintf("SMTP error from remote mail server after %s%s: " - "host %s [%s]: %s", pl, smtp_command, host->name, host->address, s); + "%s", pl, smtp_command, s); *pass_message = TRUE; *yield = buffer[0]; return TRUE; @@ -513,8 +627,8 @@ assume the connection is now dead. */ 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); @@ -539,9 +653,14 @@ Returns: nothing 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); @@ -549,20 +668,74 @@ if (addr->message != NULL) } else { - uschar *msg = - ((log_extra_selector & LX_outgoing_port) != 0)? - string_sprintf("%s [%s]:%d", host->name, host->address, - (host->port == PORT_NONE)? 25 : host->port) - : - string_sprintf("%s [%s]", host->name, host->address); - log_write(0, LOG_MAIN, "%s %s", msg, strerror(addr->basic_errno)); - deliver_msglog("%s %s %s\n", tod_stamp(tod_log), msg, - strerror(addr->basic_errno)); + log_write(0, LOG_MAIN, "%s %s", message, strerror(addr->basic_errno)); + deliver_msglog("%s %s %s\n", tod_stamp(tod_log), message, + strerror(addr->basic_errno)); } } +static void +msglog_line(host_item * host, uschar * message) +{ + deliver_msglog("%s H=%s [%s] %s\n", tod_stamp(tod_log), + host->name, host->address, message); +} + +#ifdef EXPERIMENTAL_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 * *************************************************/ @@ -652,6 +825,14 @@ if (pending_MAIL) } 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; } } @@ -667,6 +848,7 @@ while (count-- > 0) 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)) { @@ -690,11 +872,9 @@ while (count-- > 0) else if (errno == ETIMEDOUT) { - int save_errno = errno; - uschar *message = string_sprintf("SMTP timeout while connected to %s [%s] " - "after RCPT TO:<%s>", host->name, host->address, - transport_rcpt_address(addr, include_affixes)); - set_errno(addrlist, save_errno, message, DEFER, FALSE); + uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>", + transport_rcpt_address(addr, include_affixes)); + set_errno_nohost(addrlist, ETIMEDOUT, message, DEFER, FALSE); retry_add_item(addr, addr->address_retry_key, 0); update_waiting = FALSE; return -1; @@ -718,10 +898,10 @@ while (count-- > 0) { addr->message = string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: " - "host %s [%s]: %s", transport_rcpt_address(addr, include_affixes), - host->name, host->address, string_printing(buffer)); + "%s", transport_rcpt_address(addr, include_affixes), + string_printing(buffer)); setflag(addr, af_pass_message); - deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message); + msglog_line(host, addr->message); /* The response was 5xx */ @@ -739,9 +919,11 @@ while (count-- > 0) addr->basic_errno = ERRNO_RCPT4XX; addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; - /* Log temporary errors if there are more hosts to be tried. */ + /* Log temporary errors if there are more hosts to be tried. + If not, log this last one in the == line. */ - if (host->next != NULL) log_write(0, LOG_MAIN, "%s", addr->message); + if (host->next) + log_write(0, LOG_MAIN, "H=%s [%s]: %s", host->name, host->address, addr->message); /* Do not put this message on the list of those waiting for specific hosts, as otherwise it is likely to be tried too often. */ @@ -800,6 +982,344 @@ return yield; +/* 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; +} + + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -826,8 +1346,6 @@ Arguments: 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 @@ -848,7 +1366,7 @@ Returns: OK - the connection was made and the delivery attempted; 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; @@ -871,18 +1389,37 @@ BOOL completed_address = FALSE; BOOL esmtp = TRUE; BOOL pending_MAIL; BOOL pass_message = FALSE; +#ifndef DISABLE_PRDR +BOOL prdr_offered = FALSE; +BOOL prdr_active; +#endif +#ifdef EXPERIMENTAL_INTERNATIONAL +BOOL utf8_needed = FALSE; +BOOL utf8_offered = FALSE; +#endif +BOOL dsn_all_lasthop = TRUE; +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) +BOOL dane = FALSE; +BOOL dane_required = verify_check_given_host(&ob->hosts_require_dane, host) == OK; +dns_answer tlsa_dnsa; +#endif smtp_inblock inblock; smtp_outblock outblock; int max_rcpt = tblock->max_addresses; uschar *igquotstr = US""; -uschar *local_authenticated_sender = authenticated_sender; + +#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 */ @@ -907,25 +1444,28 @@ outblock.authenticating = FALSE; /* Reset the parameters of a TLS session. */ -tls_in.bits = 0; -tls_in.cipher = NULL; /* for back-compatible behaviour */ -tls_in.peerdn = NULL; -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) -tls_in.sni = NULL; -#endif - 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; + +/* 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. */ + +tls_modify_variables(&tls_out); #ifndef SUPPORT_TLS if (smtps) { - set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE); - return ERROR; + set_errno_nohost(addrlist, ERRNO_TLSFAILURE, US"TLS support not available", + DEFER, FALSE); + return ERROR; } #endif @@ -935,22 +1475,69 @@ specially so they can be identified for retries. */ if (continue_hostname == NULL) { + /* This puts port into host->port */ inblock.sock = outblock.sock = - smtp_connect(host, host_af, port, interface, ob->connect_timeout, - ob->keepalive, ob->dscp); /* This puts port into host->port */ + smtp_connect(host, host_af, port, interface, ob->connect_timeout, tblock); if (inblock.sock < 0) { - set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno, + set_errno_nohost(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno, NULL, DEFER, FALSE); return DEFER; } +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) + { + tls_out.dane_verified = FALSE; + tls_out.tlsa_usage = 0; + + if (host->dnssec == DS_YES) + { + if( ( dane_required + || verify_check_given_host(&ob->hosts_try_dane, host) == OK + ) + && (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 EXPERIMENTAL_INTERNATIONAL + 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, @@ -958,8 +1545,29 @@ if (continue_hostname == NULL) if (!smtps) { - if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', - ob->command_timeout)) goto RESPONSE_FAILED; + BOOL good_response = smtp_read_response(&inblock, buffer, sizeof(buffer), + '2', ob->command_timeout); +#ifdef EXPERIMENTAL_DSN_INFO + smtp_greeting = string_copy(buffer); +#endif + if (!good_response) goto RESPONSE_FAILED; + +#ifdef EXPERIMENTAL_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. */ @@ -968,7 +1576,7 @@ if (continue_hostname == NULL) { uschar *message = string_sprintf("failed to expand helo_data: %s", expand_string_message); - set_errno(addrlist, 0, message, DEFER, FALSE); + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE); yield = DEFER; goto SEND_QUIT; } @@ -1009,13 +1617,12 @@ goto SEND_QUIT; mailers use upper case for some reason (the RFC is quite clear about case independence) so, for peace of mind, I gave in. */ - esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL, - host->name, host->address, NULL) != OK; + esmtp = verify_check_given_host(&ob->hosts_avoid_esmtp, host) != OK; /* Alas; be careful, since this goto is not an error-out, so conceivably we might set data between here and the target which we assume to exist and be usable. I can see this coming back to bite us. */ - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (smtps) { tls_offered = TRUE; @@ -1024,7 +1631,7 @@ goto SEND_QUIT; smtp_command = US"SSL-on-connect"; goto TLS_NEGOTIATE; } - #endif +#endif if (esmtp) { @@ -1034,9 +1641,18 @@ goto SEND_QUIT; 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 { @@ -1046,10 +1662,16 @@ goto SEND_QUIT; if (!esmtp) { + BOOL good_response; + if (smtp_write_command(&outblock, FALSE, "HELO %s\r\n", helo_data) < 0) goto SEND_FAILED; - if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', - ob->command_timeout)) goto RESPONSE_FAILED; + good_response = smtp_read_response(&inblock, buffer, sizeof(buffer), + '2', ob->command_timeout); +#ifdef EXPERIMENTAL_DSN_INFO + helo_response = string_copy(buffer); +#endif + if (!good_response) goto RESPONSE_FAILED; } /* Set IGNOREQUOTA if the response to LHLO specifies support and the @@ -1061,11 +1683,35 @@ goto SEND_QUIT; /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */ - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS tls_offered = esmtp && pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0, PCRE_EOPT, NULL, 0) >= 0; - #endif +#endif + +#ifndef DISABLE_PRDR + prdr_offered = esmtp + && pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0, + PCRE_EOPT, NULL, 0) >= 0 + && verify_check_given_host(&ob->hosts_try_prdr, host) == OK; + + if (prdr_offered) + {DEBUG(D_transport) debug_printf("PRDR usable\n");} +#endif + +#ifdef EXPERIMENTAL_INTERNATIONAL + 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"); + + utf8_offered = esmtp + && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0, + PCRE_EOPT, NULL, 0) >= 0; + } +#endif } /* For continuing deliveries down the same channel, the socket is the standard @@ -1075,6 +1721,11 @@ error messages. Note that smtp_use_size and smtp_use_pipelining will have been 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); @@ -1091,9 +1742,9 @@ the client not be required to use TLS. If the response is bad, copy the buffer 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) @@ -1122,18 +1773,11 @@ if (tls_offered && !suppress_tls && else TLS_NEGOTIATE: { - int rc = tls_client_start(inblock.sock, - host, - addrlist, - NULL, /* No DH param */ - ob->tls_certificate, - ob->tls_privatekey, - ob->tls_sni, - ob->tls_verify_certificates, - ob->tls_crl, - ob->tls_require_ciphers, - ob->tls_dh_min_bits, - 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 @@ -1141,6 +1785,17 @@ if (tls_offered && !suppress_tls && 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; @@ -1149,14 +1804,15 @@ if (tls_offered && !suppress_tls && /* TLS session is set up */ - for (addr = addrlist; addr != NULL; addr = addr->next) - { + for (addr = addrlist; addr; addr = addr->next) if (addr->transport_return == PENDING_DEFER) { addr->cipher = tls_out.cipher; + addr->ourcert = tls_out.ourcert; + addr->peercert = tls_out.peercert; addr->peerdn = tls_out.peerdn; + addr->ocsp = tls_out.ocsp; } - } } } @@ -1173,6 +1829,8 @@ start of the Exim process (in exim.c). */ if (tls_out.active >= 0) { char *greeting_cmd; + BOOL good_response; + if (helo_data == NULL) { helo_data = expand_string(ob->helo_data); @@ -1180,7 +1838,7 @@ if (tls_out.active >= 0) { uschar *message = string_sprintf("failed to expand helo_data: %s", expand_string_message); - set_errno(addrlist, 0, message, DEFER, FALSE); + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE); yield = DEFER; goto SEND_QUIT; } @@ -1189,8 +1847,12 @@ if (tls_out.active >= 0) /* For SMTPS we need to wait for the initial OK response. */ if (smtps) { - if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', - ob->command_timeout)) goto RESPONSE_FAILED; + good_response = smtp_read_response(&inblock, buffer, sizeof(buffer), + '2', ob->command_timeout); +#ifdef EXPERIMENTAL_DSN_INFO + smtp_greeting = string_copy(buffer); +#endif + if (!good_response) goto RESPONSE_FAILED; } if (esmtp) @@ -1205,25 +1867,31 @@ if (tls_out.active >= 0) if (smtp_write_command(&outblock, FALSE, "%s %s\r\n", lmtp? "LHLO" : greeting_cmd, helo_data) < 0) goto SEND_FAILED; - if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', - ob->command_timeout)) - goto RESPONSE_FAILED; + good_response = smtp_read_response(&inblock, buffer, sizeof(buffer), + '2', ob->command_timeout); +#ifdef EXPERIMENTAL_DSN_INFO + helo_response = string_copy(buffer); +#endif + if (!good_response) goto RESPONSE_FAILED; } /* If the host is required to use a secure channel, ensure that we have one. */ -else if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, - host->address, NULL) == OK) +else if ( +# ifdef EXPERIMENTAL_DANE + dane || +# endif + verify_check_given_host(&ob->hosts_require_tls, host) == OK + ) { save_errno = ERRNO_TLSREQUIRED; - message = string_sprintf("a TLS session is required for %s [%s], but %s", - host->name, host->address, + message = string_sprintf("a TLS session is required, but %s", tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support"); goto TLS_FAILED; } -#endif +#endif /*SUPPORT_TLS*/ /* If TLS is active, we have just started it up and re-done the EHLO command, so its response needs to be analyzed. If TLS is not active and this is a @@ -1231,14 +1899,11 @@ continued session down a previously-used socket, we haven't just done EHLO, so we skip this. */ if (continue_hostname == NULL - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS || tls_out.active >= 0 - #endif +#endif ) { - int require_auth; - uschar *fail_reason = US"server did not advertise AUTH support"; - /* Set for IGNOREQUOTA if the response to LHLO specifies support and the lmtp_ignore_quota option was set. */ @@ -1257,149 +1922,49 @@ if (continue_hostname == NULL the current host, esmtp will be false, so PIPELINING can never be used. If the current host matches hosts_avoid_pipelining, don't do it. */ - smtp_use_pipelining = esmtp && - verify_check_this_host(&(ob->hosts_avoid_pipelining), NULL, host->name, - host->address, NULL) != OK && - pcre_exec(regex_PIPELINING, NULL, CS buffer, Ustrlen(CS buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; + smtp_use_pipelining = esmtp + && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK + && 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)) - { - 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) - { - 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. */ +#ifndef DISABLE_PRDR + prdr_offered = esmtp + && pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0, + PCRE_EOPT, NULL, 0) >= 0 + && verify_check_given_host(&ob->hosts_try_prdr, host) == OK; - 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); - - /* 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 (prdr_offered) + {DEBUG(D_transport) debug_printf("PRDR usable\n");} +#endif - switch(rc) - { - case OK: - smtp_authenticated = TRUE; /* stops the outer loop */ - break; - - /* Failure after writing a command */ - - case FAIL_SEND: - goto SEND_FAILED; - - /* Failure after reading a response */ - - 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; - - /* 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: - yield = ERROR; - set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE); - goto SEND_QUIT; - } +#ifdef EXPERIMENTAL_INTERNATIONAL + if (addrlist->prop.utf8_msg) + utf8_offered = esmtp + && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0, + PCRE_EOPT, NULL, 0) >= 0; +#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 = esmtp + && pcre_exec(regex_DSN, NULL, CS buffer, Ustrlen(CS buffer), 0, + PCRE_EOPT, NULL, 0) >= 0; + DEBUG(D_transport) debug_printf("use_dsn=%d\n", smtp_use_dsn); - /* 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, - FALSE); - goto SEND_QUIT; + default: goto SEND_QUIT; + case OK: break; + case FAIL_SEND: goto SEND_FAILED; + case FAIL: goto RESPONSE_FAILED; } } @@ -1408,6 +1973,15 @@ message-specific. */ setting_up = FALSE; +#ifdef EXPERIMENTAL_INTERNATIONAL +/* If this is an international message we need the host to speak SMTPUTF8 */ +if (utf8_needed && !utf8_offered) + { + 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. */ @@ -1425,7 +1999,7 @@ if (tblock->filter_command != NULL) 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; @@ -1465,37 +2039,73 @@ if (smtp_use_size) 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 (ob->authenticated_sender != NULL) +#ifndef DISABLE_PRDR +prdr_active = FALSE; +if (prdr_offered) { - uschar *new = expand_string(ob->authenticated_sender); - if (new == NULL) - { - if (!expand_string_forcedfail) + for (addr = first_addr; addr; addr = addr->next) + if (addr->transport_return == PENDING_DEFER) { - uschar *message = string_sprintf("failed to expand " - "authenticated_sender: %s", expand_string_message); - set_errno(addrlist, 0, message, DEFER, FALSE); - return ERROR; + 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 EXPERIMENTAL_INTERNATIONAL +if (addrlist->prop.utf8_msg && !addrlist->prop.utf8_downcvt && utf8_offered) + sprintf(CS p, " SMTPUTF8"), p += 9; +#endif + +/* check if all addresses have lasthop flag */ +/* do not send RET and ENVID if true */ +for (dsn_all_lasthop = TRUE, addr = first_addr; + address_count < max_rcpt && addr != NULL; + addr = addr->next) + if ((addr->dsn_flags & rf_dsnlasthop) != 1) + { + dsn_all_lasthop = FALSE; + break; + } + +/* Add any DSN flags to the mail command */ + +if (smtp_use_dsn && !dsn_all_lasthop) + { + if (dsn_ret == dsn_ret_hdrs) + { + Ustrcpy(p, " RET=HDRS"); + 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++; } - else if (new[0] != 0) local_authenticated_sender = new; } -/* Add the authenticated sender address if present */ +/* 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_authenticated || ob->authenticated_sender_force) && - local_authenticated_sender != NULL) +if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob)) { - string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s", - auth_xtextencode(local_authenticated_sender, - Ustrlen(local_authenticated_sender))); + yield = ERROR; + goto SEND_QUIT; } /* From here until we send the DATA command, we can make use of PIPELINING @@ -1506,28 +2116,51 @@ buffer. */ 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 EXPERIMENTAL_INTERNATIONAL + 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 || !utf8_offered)) + { + if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr) + { + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE); + yield = ERROR; + goto SEND_QUIT; + } + setflag(addrlist, af_utf8_downcvt); + } +#endif + + rc = smtp_write_command(&outblock, smtp_use_pipelining, + "MAIL FROM:<%s>%s\r\n", s, buffer); + } + mail_command = string_copy(big_buffer); /* Save for later error message */ switch(rc) { case -1: /* Transmission error */ - goto SEND_FAILED; + goto SEND_FAILED; case +1: /* Block was sent */ - if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', + if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', ob->command_timeout)) - { - if (errno == 0 && buffer[0] == '4') { - errno = ERRNO_MAIL4XX; - addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; + if (errno == 0 && buffer[0] == '4') + { + errno = ERRNO_MAIL4XX; + addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; + } + goto RESPONSE_FAILED; } - goto RESPONSE_FAILED; - } - pending_MAIL = FALSE; - break; + pending_MAIL = FALSE; + break; } /* Pass over all the relevant recipient addresses for this host, which are the @@ -1549,19 +2182,70 @@ for (addr = first_addr; { 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>%s\r\n", - transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr); + rcpt_addr = transport_rcpt_address(addr, tblock->rcpt_include_affixes); + +#ifdef EXPERIMENTAL_INTERNATIONAL + { + 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) { @@ -1593,16 +2277,15 @@ RCPT. */ if (mua_wrapper) { address_item *badaddr; - for (badaddr = first_addr; badaddr != NULL; badaddr = badaddr->next) - { - if (badaddr->transport_return != PENDING_OK) break; - } - if (badaddr != NULL) - { - set_errno(addrlist, 0, badaddr->message, FAIL, - testflag(badaddr, af_pass_message)); - ok = FALSE; - } + for (badaddr = first_addr; badaddr; badaddr = badaddr->next) + if (badaddr->transport_return != PENDING_OK) + { + /*XXX could we find a better errno than 0 here? */ + set_errno_nohost(addrlist, 0, badaddr->message, FAIL, + testflag(badaddr, af_pass_message)); + ok = FALSE; + break; + } } /* If ok is TRUE, we know we have got at least one good recipient, and must now @@ -1651,6 +2334,7 @@ if (!ok) ok = TRUE; else DEBUG(D_transport|D_v) debug_printf(" SMTP>> writing message and terminating \".\"\n"); transport_count = 0; + #ifndef DISABLE_DKIM ok = dkim_transport_write_message(addrlist, inblock.sock, topt_use_crlf | topt_end_dot | topt_escape_headers | @@ -1704,8 +2388,31 @@ if (!ok) ok = TRUE; else 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) { @@ -1735,31 +2442,24 @@ if (!ok) ok = TRUE; else 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 ( +#ifndef EXPERIMENTAL_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) @@ -1771,13 +2471,22 @@ if (!ok) ok = TRUE; else 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)); setflag(addr, af_pass_message); /* Allow message to go to user */ if (buffer[0] == '5') @@ -1787,15 +2496,19 @@ if (!ok) ok = TRUE; else errno = ERRNO_DATA4XX; addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; addr->transport_return = DEFER; - retry_add_item(addr, addr->address_retry_key, 0); +#ifndef DISABLE_PRDR + if (!prdr_active) +#endif + retry_add_item(addr, addr->address_retry_key, 0); } continue; } completed_address = TRUE; /* NOW we can set this flag */ - if ((log_extra_selector & LX_smtp_confirmation) != 0) + if (LOGGING(smtp_confirmation)) { - uschar *s = string_printing(buffer); - conf = (s == buffer)? (uschar *)string_copy(s) : s; + const uschar *s = string_printing(buffer); + /* deconst cast ok here as string_printing was checked to have alloc'n'copied */ + conf = (s == buffer)? (uschar *)string_copy(s) : US s; } } @@ -1804,28 +2517,76 @@ if (!ok) ok = TRUE; else addr->transport_return = OK; addr->more_errno = delivery_time; - 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. */ +#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); + 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)); + 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 (EXIMfsync(journal_fd) < 0) @@ -1845,32 +2606,37 @@ the problem is not related to this specific message. */ if (!ok) { - int code; + int code, set_rc; + uschar * set_message; RESPONSE_FAILED: - save_errno = errno; - message = NULL; - send_quit = check_response(host, &save_errno, addrlist->more_errno, - buffer, &code, &message, &pass_message); - goto FAILED; + { + save_errno = errno; + message = NULL; + send_quit = check_response(host, &save_errno, addrlist->more_errno, + buffer, &code, &message, &pass_message); + goto FAILED; + } SEND_FAILED: - save_errno = errno; - code = '4'; - message = US string_sprintf("send() to %s [%s] failed: %s", - host->name, host->address, strerror(save_errno)); - send_quit = FALSE; - goto FAILED; + { + save_errno = errno; + code = '4'; + message = US string_sprintf("send() to %s [%s] failed: %s", + host->name, host->address, strerror(save_errno)); + send_quit = FALSE; + goto FAILED; + } /* This label is jumped to directly when a TLS negotiation has failed, or was not done for a host for which it is required. Values will be set in message and save_errno, and setting_up will always be true. Treat as a temporary error. */ - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS TLS_FAILED: code = '4'; - #endif +#endif /* If the failure happened while setting up the call, see if the failure was a 5xx response (this will either be on connection, or following HELO - a 5xx @@ -1881,18 +2647,14 @@ if (!ok) FAILED: ok = FALSE; /* For when reached by GOTO */ + set_message = message; if (setting_up) { if (code == '5') - { - set_errno(addrlist, save_errno, message, FAIL, pass_message); - } + set_rc = FAIL; else - { - set_errno(addrlist, save_errno, message, DEFER, pass_message); - yield = DEFER; - } + yield = set_rc = DEFER; } /* We want to handle timeouts after MAIL or "." and loss of connection after @@ -1907,24 +2669,29 @@ if (!ok) switch(save_errno) { +#ifdef EXPERIMENTAL_INTERNATIONAL + case ERRNO_UTF8_FWD: + code = '5'; + /*FALLTHROUGH*/ +#endif case 0: case ERRNO_MAIL4XX: case ERRNO_DATA4XX: - message_error = TRUE; - break; + message_error = TRUE; + break; case ETIMEDOUT: - message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 || - Ustrncmp(smtp_command,"end ",4) == 0; - break; + message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 || + Ustrncmp(smtp_command,"end ",4) == 0; + break; case ERRNO_SMTPCLOSED: - message_error = Ustrncmp(smtp_command,"end ",4) == 0; - break; + message_error = Ustrncmp(smtp_command,"end ",4) == 0; + break; default: - message_error = FALSE; - break; + message_error = FALSE; + break; } /* Handle the cases that are treated as message errors. These are: @@ -1932,6 +2699,7 @@ if (!ok) (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 @@ -1945,18 +2713,19 @@ if (!ok) if (message_error) { if (mua_wrapper) code = '5'; /* Force hard failure in wrapper mode */ - set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER, - pass_message); /* If there's an errno, the message contains just the identity of the host. */ - if (code != '5') /* Anything other than 5 is treated as temporary */ + if (code == '5') + set_rc = FAIL; + else /* Anything other than 5 is treated as temporary */ { + set_rc = DEFER; if (save_errno > 0) message = US string_sprintf("%s: %s", message, strerror(save_errno)); if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message); - deliver_msglog("%s %s\n", tod_stamp(tod_log), message); + msglog_line(host, message); *message_defer = TRUE; } } @@ -1969,11 +2738,17 @@ if (!ok) else { + set_rc = DEFER; yield = (save_errno == ERRNO_CHHEADER_FAIL || save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER; - set_errno(addrlist, save_errno, message, DEFER, pass_message); } } + + set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host +#ifdef EXPERIMENTAL_DSN_INFO + , smtp_greeting, helo_response +#endif + ); } @@ -2013,15 +2788,21 @@ DEBUG(D_transport) if (completed_address && ok && send_quit) { BOOL more; - if (first_addr != NULL || continue_more || - ( - (tls_out.active < 0 || - verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name, - host->address, NULL) != OK) + smtp_compare_t t_compare; + + t_compare.tblock = tblock; + t_compare.current_sender_address = sender_address; + + if ( first_addr != NULL + || continue_more + || ( ( tls_out.active < 0 + || verify_check_given_host(&ob->hosts_nopass_tls, host) != OK + ) && transport_check_waiting(tblock->name, host->name, - tblock->connection_max_messages, new_message_id, &more) - )) + tblock->connection_max_messages, new_message_id, &more, + (oicf)smtp_are_same_identities, (void*)&t_compare) + ) ) { uschar *msg; BOOL pass_message; @@ -2042,7 +2823,8 @@ if (completed_address && ok && send_quit) &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); } } } @@ -2063,7 +2845,7 @@ if (completed_address && ok && send_quit) when TLS is shut down. We test for this by sending a new EHLO. If we don't get a good response, we don't attempt to pass the socket on. */ - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (tls_out.active >= 0) { tls_close(FALSE, TRUE); @@ -2074,11 +2856,14 @@ if (completed_address && ok && send_quit) 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)) { @@ -2088,7 +2873,11 @@ if (completed_address && ok && send_quit) /* If RSET failed and there are addresses left, they get deferred. */ - else set_errno(first_addr, errno, msg, DEFER, FALSE); + else set_errno(first_addr, errno, msg, DEFER, FALSE, host +#ifdef EXPERIMENTAL_DSN_INFO + , smtp_greeting, helo_response +#endif + ); } } @@ -2120,8 +2909,6 @@ 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, @@ -2132,6 +2919,11 @@ specified in the transports, and therefore not visible at top level, in which case continue_more won't get set. */ (void)close(inblock.sock); + +#ifdef EXPERIMENTAL_EVENT +(void) event_raise(tblock->event_action, US"tcp:close", NULL); +#endif + continue_transport = NULL; continue_hostname = NULL; return yield; @@ -2213,18 +3005,25 @@ prepare_addresses(address_item *addrlist, host_item *host) 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; } @@ -2313,8 +3112,7 @@ if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL)) 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); @@ -2342,13 +3140,14 @@ if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL)) /* 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 @@ -2392,12 +3191,10 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing) hostlist = addrlist->host_list = newlist; } - /* Sort out the default port. */ if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE; - /* For each host-plus-IP-address on the list: . If this is a continued delivery and the host isn't the one with the @@ -2462,7 +3259,6 @@ for (cutoff_retry = 0; expired && 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; @@ -2494,7 +3290,6 @@ for (cutoff_retry = 0; expired && { int new_port, flags; host_item *hh; - uschar *canonical_name; if (host->status >= hstatus_unusable) { @@ -2522,10 +3317,11 @@ for (cutoff_retry = 0; expired && if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS; if (ob->gethostbyname || string_is_ip_address(host->name, NULL) != 0) - rc = host_find_byname(host, NULL, flags, &canonical_name, TRUE); + rc = host_find_byname(host, NULL, flags, NULL, TRUE); else rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL, - &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. */ @@ -2601,7 +3397,8 @@ for (cutoff_retry = 0; expired && doing a two-stage queue run, don't do this if forcing. */ if ((!deliver_force || queue_2stage) && (queue_smtp || - match_isinlist(addrlist->domain, &queue_smtp_domains, 0, + match_isinlist(addrlist->domain, + (const uschar **)&queue_smtp_domains, 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)) { expired = FALSE; @@ -2624,6 +3421,9 @@ for (cutoff_retry = 0; expired && 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 @@ -2634,15 +3434,18 @@ for (cutoff_retry = 0; expired && 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 @@ -2650,14 +3453,20 @@ for (cutoff_retry = 0; expired && 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, @@ -2720,11 +3529,10 @@ for (cutoff_retry = 0; expired && 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 " @@ -2758,7 +3566,7 @@ for (cutoff_retry = 0; expired && if (dont_deliver) { host_item *host2; - set_errno(addrlist, 0, NULL, OK, FALSE); + set_errno_nohost(addrlist, 0, NULL, OK, FALSE); for (addr = addrlist; addr != NULL; addr = addr->next) { addr->host_used = host; @@ -2791,27 +3599,40 @@ for (cutoff_retry = 0; expired && 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; @@ -2832,6 +3653,11 @@ for (cutoff_retry = 0; expired && first_addr->basic_errno != ERRNO_TLSFAILURE) write_logs(first_addr, host); +#ifdef EXPERIMENTAL_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 @@ -2841,21 +3667,26 @@ for (cutoff_retry = 0; expired && session, so the in-clear transmission after those errors, if permitted, happens inside smtp_deliver().] */ - #ifdef SUPPORT_TLS - if (rc == DEFER && first_addr->basic_errno == ERRNO_TLSFAILURE && - ob->tls_tempfail_tryclear && - verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, - host->address, NULL) != OK) +#ifdef SUPPORT_TLS + if ( rc == DEFER + && first_addr->basic_errno == ERRNO_TLSFAILURE + && ob->tls_tempfail_tryclear + && verify_check_given_host(&ob->hosts_require_tls, host) != OK + ) { log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted " "to %s [%s] (not in hosts_require_tls)", host->name, host->address); first_addr = prepare_addresses(addrlist, host); - rc = smtp_deliver(addrlist, host, host_af, port, interface, tblock, - expanded_hosts != NULL, &message_defer, TRUE); + rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock, + &message_defer, TRUE); if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL) write_logs(first_addr, host); +# ifdef EXPERIMENTAL_EVENT + if (rc == DEFER) + deferred_event_raise(first_addr, host); +# endif } - #endif +#endif /*SUPPORT_TLS*/ } /* Delivery attempt finished */ @@ -2884,7 +3715,13 @@ for (cutoff_retry = 0; expired && 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); } @@ -2926,7 +3763,13 @@ for (cutoff_retry = 0; expired && 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); @@ -2941,16 +3784,12 @@ for (cutoff_retry = 0; expired && 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 @@ -3128,4 +3967,6 @@ DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name); return TRUE; /* Each address has its status */ } +/* vi: aw ai sw=2 +*/ /* End of transport/smtp.c */