X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Ftransports%2Fsmtp.c;h=3a887c1519927d6b64318712b17b09175bed6366;hp=b0fe177e905139b88cfb3779a0fdc0e2ce1be6b3;hb=20b9a2dc027844f7288508d0f81df815110e4e69;hpb=6c9ed72eaa948d340dba0ea0a878f9570852ab35;ds=sidebyside diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index b0fe177e9..3a887c151 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 - 2014 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -61,9 +61,9 @@ optionlist smtp_transport_options[] = { { "dns_search_parents", opt_bool, (void *)offsetof(smtp_transport_options_block, dns_search_parents) }, { "dnssec_request_domains", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dnssec_request_domains) }, + (void *)offsetof(smtp_transport_options_block, dnssec.request) }, { "dnssec_require_domains", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dnssec_require_domains) }, + (void *)offsetof(smtp_transport_options_block, dnssec.require) }, { "dscp", opt_stringptr, (void *)offsetof(smtp_transport_options_block, dscp) }, { "fallback_hosts", opt_stringptr, @@ -72,17 +72,6 @@ optionlist smtp_transport_options[] = { (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, @@ -159,7 +148,7 @@ optionlist smtp_transport_options[] = { (void *)offsetof(smtp_transport_options_block, serialize_hosts) }, { "size_addition", opt_int, (void *)offsetof(smtp_transport_options_block, size_addition) } -#ifdef EXPERIMENTAL_SOCKS +#ifdef SUPPORT_SOCKS ,{ "socks_proxy", opt_stringptr, (void *)offsetof(smtp_transport_options_block, socks_proxy) } #endif @@ -241,8 +230,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { FALSE, /* gethostbyname */ TRUE, /* dns_qualify_single */ FALSE, /* dns_search_parents */ - NULL, /* dnssec_request_domains */ - NULL, /* dnssec_require_domains */ + { NULL, NULL }, /* dnssec_domains {request,require} */ TRUE, /* delay_after_cutoff */ FALSE, /* hosts_override */ FALSE, /* hosts_randomize */ @@ -250,7 +238,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { FALSE, /* lmtp_ignore_quota */ NULL, /* expand_retry_include_ip_address */ TRUE /* retry_include_ip_address */ -#ifdef EXPERIMENTAL_SOCKS +#ifdef SUPPORT_SOCKS ,NULL /* socks_proxy */ #endif #ifdef SUPPORT_TLS @@ -258,9 +246,6 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* tls_crl */ NULL, /* tls_privatekey */ NULL, /* tls_require_ciphers */ - NULL, /* gnutls_require_kx */ - NULL, /* gnutls_require_mac */ - NULL, /* gnutls_require_proto */ NULL, /* tls_sni */ US"system", /* tls_verify_certificates */ EXIM_CLIENT_DH_DEFAULT_MIN_BITS, @@ -412,15 +397,6 @@ 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 } @@ -441,6 +417,8 @@ Arguments: 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 @@ -451,7 +429,11 @@ Returns: nothing static void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc, - BOOL pass_message, host_item * host) + 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; @@ -460,7 +442,7 @@ if (errno_value == ERRNO_CONNECTTIMEOUT) errno_value = ETIMEDOUT; orvalue = RTEF_CTOUT; } -for (addr = addrlist; addr != NULL; addr = addr->next) +for (addr = addrlist; addr; addr = addr->next) if (addr->transport_return >= PENDING) { addr->basic_errno = errno_value; @@ -472,10 +454,31 @@ for (addr = addrlist; addr != NULL; addr = addr->next) } 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 + ); +} /************************************************* @@ -570,6 +573,16 @@ if (*errno_value == ERRNO_WRITEINCOMPLETE) 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) @@ -619,6 +632,9 @@ write_logs(address_item *addr, host_item *host) { 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) { message = string_sprintf("%s: %s", message, addr->message); @@ -629,25 +645,22 @@ if (addr->message) } else { - if (log_extra_selector & LX_outgoing_port) - message = string_sprintf("%s:%d", message, - host->port == PORT_NONE ? 25 : host->port); - 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)); + 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); +deliver_msglog("%s H=%s [%s] %s\n", tod_stamp(tod_log), + host->name, host->address, message); } -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT /************************************************* * Post-defer action * *************************************************/ @@ -838,7 +851,7 @@ while (count-- > 0) { uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>", transport_rcpt_address(addr, include_affixes)); - set_errno(addrlist, ETIMEDOUT, message, DEFER, FALSE, NULL); + set_errno_nohost(addrlist, ETIMEDOUT, message, DEFER, FALSE); retry_add_item(addr, addr->address_retry_key, 0); update_waiting = FALSE; return -1; @@ -883,12 +896,22 @@ while (count-- > 0) addr->basic_errno = ERRNO_RCPT4XX; addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; +#ifndef DISABLE_EVENT + event_defer_errno = addr->more_errno; + msg_event_raise(US"msg:rcpt:host:defer", addr); +#endif + /* Log temporary errors if there are more hosts to be tried. If not, log this last one in the == line. */ if (host->next) log_write(0, LOG_MAIN, "H=%s [%s]: %s", host->name, host->address, addr->message); +#ifndef DISABLE_EVENT + else + msg_event_raise(US"msg:rcpt:defer", addr); +#endif + /* Do not put this message on the list of those waiting for specific hosts, as otherwise it is likely to be tried too often. */ @@ -1087,8 +1110,8 @@ if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1)) /* Internal problem, message in buffer. */ case ERROR: - set_errno(addrlist, ERRNO_AUTHPROB, string_copy(buffer), - DEFER, FALSE, NULL); + set_errno_nohost(addrlist, ERRNO_AUTHPROB, string_copy(buffer), + DEFER, FALSE); return ERROR; } @@ -1102,9 +1125,9 @@ if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1)) if (require_auth == OK && !smtp_authenticated) { - set_errno(addrlist, ERRNO_AUTHFAIL, + set_errno_nohost(addrlist, ERRNO_AUTHFAIL, string_sprintf("authentication required but %s", fail_reason), DEFER, - FALSE, NULL); + FALSE); return DEFER; } @@ -1143,7 +1166,7 @@ if (ob->authenticated_sender != NULL) { uschar *message = string_sprintf("failed to expand " "authenticated_sender: %s", expand_string_message); - set_errno(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, NULL); + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE); return TRUE; } } @@ -1169,9 +1192,15 @@ return FALSE; #ifdef EXPERIMENTAL_DANE +/* Lookup TLSA record for host/port. +Return: OK success with dnssec; DANE mode + DEFER Do not use this host now, may retry later + FAIL_FORCED No TLSA record; DANE not usable + FAIL Do not use this connection +*/ + int -tlsa_lookup(const host_item * host, dns_answer * dnsa, - BOOL dane_required, BOOL * dane) +tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required) { /* move this out to host.c given the similarity to dns_lookup() ? */ uschar buffer[300]; @@ -1182,28 +1211,24 @@ const uschar * fullname = buffer; 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) - { - log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed"); - 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; + + case DNS_AGAIN: + return DEFER; /* just defer this TLS'd conn */ + + case DNS_NOMATCH: + return dane_required ? FAIL : FAIL_FORCED; + + default: + case DNS_FAIL: + return dane_required ? FAIL : DEFER; } -return OK; } #endif @@ -1268,20 +1293,67 @@ we will veto this new message. */ static BOOL smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare) { -uschar * save_sender_address = sender_address; -uschar * current_local_identity = +uschar * message_local_identity, + * current_local_identity, + * new_sender_address; + +current_local_identity = smtp_local_identity(s_compare->current_sender_address, s_compare->tblock); -uschar * new_sender_address = deliver_get_sender_address(message_id); -uschar * message_local_identity = - smtp_local_identity(new_sender_address, s_compare->tblock); -sender_address = save_sender_address; +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 * *************************************************/ @@ -1351,14 +1423,17 @@ 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_offered = FALSE; 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; +BOOL dane_required = verify_check_given_host(&ob->hosts_require_dane, host) == OK; dns_answer tlsa_dnsa; #endif smtp_inblock inblock; @@ -1366,6 +1441,10 @@ smtp_outblock outblock; int max_rcpt = tblock->max_addresses; uschar *igquotstr = US""; +#ifdef EXPERIMENTAL_DSN_INFO +uschar *smtp_greeting = NULL; +uschar *helo_response = NULL; +#endif uschar *helo_data = NULL; uschar *message = NULL; @@ -1374,7 +1453,6 @@ uschar *p; uschar buffer[4096]; uschar inbuffer[4096]; uschar outbuffer[4096]; -address_item * current_address; suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */ @@ -1418,8 +1496,8 @@ tls_modify_variables(&tls_out); #ifndef SUPPORT_TLS if (smtps) { - set_errno(addrlist, ERRNO_TLSFAILURE, US"TLS support not available", - DEFER, FALSE, NULL); + set_errno_nohost(addrlist, ERRNO_TLSFAILURE, US"TLS support not available", + DEFER, FALSE); return ERROR; } #endif @@ -1436,8 +1514,8 @@ if (continue_hostname == NULL) if (inblock.sock < 0) { - set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno, - NULL, DEFER, FALSE, NULL); + set_errno_nohost(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno, + NULL, DEFER, FALSE); return DEFER; } @@ -1446,20 +1524,27 @@ if (continue_hostname == NULL) tls_out.dane_verified = FALSE; tls_out.tlsa_usage = 0; - dane_required = verify_check_given_host(&ob->hosts_require_dane, host) == OK; - 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 + if( dane_required + || verify_check_given_host(&ob->hosts_try_dane, host) == OK ) - return rc; + switch (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required)) + { + case OK: dane = TRUE; break; + case FAIL_FORCED: break; + default: set_errno_nohost(addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc == DEFER ? "DEFER" : "FAIL"), + rc, FALSE); + return rc; + } } else if (dane_required) { - log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name); + set_errno_nohost(addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: %s lookup not DNSSEC", host->name), + FAIL, FALSE); return FAIL; } @@ -1473,6 +1558,19 @@ if (continue_hostname == NULL) 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, @@ -1480,10 +1578,14 @@ 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 +#ifndef DISABLE_EVENT { uschar * s; lookup_dnssec_authenticated = host->dnssec==DS_YES ? US"yes" @@ -1491,9 +1593,9 @@ if (continue_hostname == NULL) s = event_raise(tblock->event_action, US"smtp:connect", buffer); if (s) { - set_errno(addrlist, ERRNO_EXPANDFAIL, + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, string_sprintf("deferred by smtp:connect event expansion: %s", s), - DEFER, FALSE, NULL); + DEFER, FALSE); yield = DEFER; goto SEND_QUIT; } @@ -1507,7 +1609,7 @@ if (continue_hostname == NULL) { uschar *message = string_sprintf("failed to expand helo_data: %s", expand_string_message); - set_errno(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, NULL); + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE); yield = DEFER; goto SEND_QUIT; } @@ -1572,9 +1674,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 { @@ -1584,35 +1695,33 @@ 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 - lmtp_ignore_quota option was set. */ - - igquotstr = (lmtp && ob->lmtp_ignore_quota && - pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0, - PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US""; + if (esmtp || lmtp) + peer_offered = ehlo_response(buffer, Ustrlen(buffer), + PEER_OFFERED_TLS + | 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 - -#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");} + tls_offered = !!(peer_offered & PEER_OFFERED_TLS); #endif } @@ -1623,6 +1732,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); @@ -1657,8 +1771,10 @@ if ( tls_offered if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2', ob->command_timeout)) { - if (errno != 0 || buffer2[0] == 0 || - (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)) + if ( errno != 0 + || buffer2[0] == 0 + || (buffer2[0] == '4' && !ob->tls_tempfail_tryclear) + ) { Ustrncpy(buffer, buffer2, sizeof(buffer)); goto RESPONSE_FAILED; @@ -1683,13 +1799,11 @@ if ( tls_offered if (rc != OK) { # ifdef EXPERIMENTAL_DANE - if (rc == DEFER && dane && !dane_required) + if (rc == DEFER && dane) { - log_write(0, LOG_MAIN, "DANE attempt failed;" - " trying CA-root TLS to %s [%s] (not in hosts_require_dane)", + log_write(0, LOG_MAIN, + "DANE attempt failed; no TLS connection to %s [%s]", host->name, host->address); - dane = FALSE; - goto TLS_NEGOTIATE; } # endif @@ -1701,7 +1815,7 @@ if ( tls_offered /* 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; @@ -1726,6 +1840,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); @@ -1733,7 +1849,7 @@ if (tls_out.active >= 0) { uschar *message = string_sprintf("failed to expand helo_data: %s", expand_string_message); - set_errno(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, NULL); + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE); yield = DEFER; goto SEND_QUIT; } @@ -1742,8 +1858,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) @@ -1758,9 +1878,12 @@ 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 @@ -1792,46 +1915,53 @@ if (continue_hostname == NULL #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) + ); + /* Set for IGNOREQUOTA if the response to LHLO specifies support and the lmtp_ignore_quota option was set. */ - igquotstr = (lmtp && ob->lmtp_ignore_quota && - pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0, - PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US""; + igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US""; /* If the response to EHLO specified support for the SIZE parameter, note this, provided size_addition is non-negative. */ - smtp_use_size = esmtp && ob->size_addition >= 0 && - pcre_exec(regex_SIZE, NULL, CS buffer, Ustrlen(CS buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; + smtp_use_size = !!(peer_offered & PEER_OFFERED_SIZE); /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched the current host, esmtp will be false, so PIPELINING can never be used. If the current host matches hosts_avoid_pipelining, don't do it. */ - smtp_use_pipelining = esmtp - && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK - && pcre_exec(regex_PIPELINING, NULL, CS buffer, Ustrlen(CS buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; + smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE + && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK; DEBUG(D_transport) debug_printf("%susing PIPELINING\n", - smtp_use_pipelining? "" : "not "); + smtp_use_pipelining ? "" : "not "); #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; + if ( peer_offered & PEER_OFFERED_PRDR + && verify_check_given_host(&ob->hosts_try_prdr, host) != OK) + peer_offered &= ~PEER_OFFERED_PRDR; - if (prdr_offered) + if (peer_offered & PEER_OFFERED_PRDR) {DEBUG(D_transport) debug_printf("PRDR usable\n");} #endif /* Note if the server supports DSN */ - smtp_use_dsn = esmtp && pcre_exec(regex_DSN, NULL, CS buffer, (int)Ustrlen(CS buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; - DEBUG(D_transport) debug_printf("use_dsn=%d\n", smtp_use_dsn); + smtp_use_dsn = !!(peer_offered & PEER_OFFERED_DSN); + DEBUG(D_transport) debug_printf("%susing DSN\n", smtp_use_dsn ? "" : "not "); /* Note if the response to EHLO specifies support for the AUTH extension. If it has, check that this host is one we want to authenticate to, and do @@ -1853,6 +1983,23 @@ message-specific. */ 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. */ @@ -1870,8 +2017,8 @@ if (tblock->filter_command != NULL) if (!rc) { - set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER, - FALSE, NULL); + set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER, + FALSE); yield = ERROR; goto SEND_QUIT; } @@ -1912,8 +2059,7 @@ if (smtp_use_size) #ifndef DISABLE_PRDR prdr_active = FALSE; -if (prdr_offered) - { +if (peer_offered & PEER_OFFERED_PRDR) for (addr = first_addr; addr; addr = addr->next) if (addr->transport_return == PENDING_DEFER) { @@ -1926,21 +2072,30 @@ if (prdr_offered) } 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 */ -dsn_all_lasthop = TRUE; -for (addr = first_addr; +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 == FALSE)) +if (smtp_use_dsn && !dsn_all_lasthop) { if (dsn_ret == dsn_ret_hdrs) { @@ -1980,28 +2135,53 @@ 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 SUPPORT_I18N + uschar * errstr = NULL; + + /* If we must downconvert, do the from-address here. Remember we had to + for the to-addresses (done below), and also (ugly) for re-doing when building + the delivery log line. */ + + if ( addrlist->prop.utf8_msg + && (addrlist->prop.utf8_downcvt || !(peer_offered & PEER_OFFERED_UTF8)) + ) + { + if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr) + { + set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE); + yield = ERROR; + goto SEND_QUIT; + } + setflag(addrlist, af_utf8_downcvt); + } +#endif + + rc = smtp_write_command(&outblock, smtp_use_pipelining, + "MAIL FROM:<%s>%s\r\n", s, buffer); + } + mail_command = string_copy(big_buffer); /* Save for later error message */ switch(rc) { case -1: /* Transmission error */ - goto SEND_FAILED; + goto SEND_FAILED; case +1: /* Block was sent */ - if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', + if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2', ob->command_timeout)) - { - if (errno == 0 && buffer[0] == '4') { - errno = ERRNO_MAIL4XX; - addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; + if (errno == 0 && buffer[0] == '4') + { + errno = ERRNO_MAIL4XX; + addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; + } + goto RESPONSE_FAILED; } - goto RESPONSE_FAILED; - } - pending_MAIL = FALSE; - break; + pending_MAIL = FALSE; + break; } /* Pass over all the relevant recipient addresses for this host, which are the @@ -2023,6 +2203,7 @@ for (addr = first_addr; { int count; BOOL no_flush; + uschar * rcpt_addr; addr->dsn_aware = smtp_use_dsn ? dsn_support_yes : dsn_support_no; @@ -2067,8 +2248,24 @@ for (addr = first_addr; 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. */ + 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", - transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr, buffer); + rcpt_addr, igquotstr, buffer); if (count < 0) goto SEND_FAILED; if (count > 0) @@ -2105,8 +2302,8 @@ if (mua_wrapper) if (badaddr->transport_return != PENDING_OK) { /*XXX could we find a better errno than 0 here? */ - set_errno(addrlist, 0, badaddr->message, FAIL, - testflag(badaddr, af_pass_message), NULL); + set_errno_nohost(addrlist, 0, badaddr->message, FAIL, + testflag(badaddr, af_pass_message)); ok = FALSE; break; } @@ -2272,8 +2469,8 @@ if (!ok) ok = TRUE; else /* Set up confirmation if needed - applies only to SMTP */ if ( -#ifndef EXPERIMENTAL_EVENT - (log_extra_selector & LX_smtp_confirmation) != 0 && +#ifdef DISABLE_EVENT + LOGGING(smtp_confirmation) && #endif !lmtp ) @@ -2328,7 +2525,7 @@ if (!ok) ok = TRUE; else continue; } completed_address = TRUE; /* NOW we can set this flag */ - if ((log_extra_selector & LX_smtp_confirmation) != 0) + if (LOGGING(smtp_confirmation)) { const uschar *s = string_printing(buffer); /* deconst cast ok here as string_printing was checked to have alloc'n'copied */ @@ -2363,7 +2560,7 @@ if (!ok) ok = TRUE; else else sprintf(CS buffer, "%.500s\n", addr->unique); - DEBUG(D_deliver) debug_printf("journalling %s", buffer); + DEBUG(D_deliver) debug_printf("journalling %s\n", buffer); len = Ustrlen(CS buffer); if (write(journal_fd, buffer, len) != len) log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for " @@ -2400,7 +2597,7 @@ if (!ok) ok = TRUE; else else sprintf(CS buffer, "%.500s\n", addr->unique); - DEBUG(D_deliver) debug_printf("journalling(PRDR) %s", buffer); + DEBUG(D_deliver) debug_printf("journalling(PRDR) %s\n", buffer); len = Ustrlen(CS buffer); if (write(journal_fd, buffer, len) != len) log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for " @@ -2430,22 +2627,27 @@ 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 @@ -2466,16 +2668,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, host); + set_rc = FAIL; else - { - set_errno(addrlist, save_errno, message, DEFER, pass_message, host); - yield = DEFER; - } + yield = set_rc = DEFER; } /* We want to handle timeouts after MAIL or "." and loss of connection after @@ -2490,24 +2690,29 @@ if (!ok) switch(save_errno) { +#ifdef SUPPORT_I18N + case ERRNO_UTF8_FWD: + code = '5'; + /*FALLTHROUGH*/ +#endif case 0: case ERRNO_MAIL4XX: case ERRNO_DATA4XX: - message_error = TRUE; - break; + message_error = TRUE; + break; case ETIMEDOUT: - message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 || - Ustrncmp(smtp_command,"end ",4) == 0; - break; + message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 || + Ustrncmp(smtp_command,"end ",4) == 0; + break; case ERRNO_SMTPCLOSED: - message_error = Ustrncmp(smtp_command,"end ",4) == 0; - break; + message_error = Ustrncmp(smtp_command,"end ",4) == 0; + break; default: - message_error = FALSE; - break; + message_error = FALSE; + break; } /* Handle the cases that are treated as message errors. These are: @@ -2515,6 +2720,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 @@ -2528,14 +2734,15 @@ 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, host); /* 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); @@ -2552,11 +2759,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, host); } } + + set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host +#ifdef EXPERIMENTAL_DSN_INFO + , smtp_greeting, helo_response +#endif + ); } @@ -2669,6 +2882,9 @@ if (completed_address && ok && send_quit) /* 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)) { @@ -2678,7 +2894,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, host); + else set_errno(first_addr, errno, msg, DEFER, FALSE, host +#ifdef EXPERIMENTAL_DSN_INFO + , smtp_greeting, helo_response +#endif + ); } } @@ -2719,9 +2939,10 @@ writing RSET might have failed, or there may be other addresses whose hosts are specified in the transports, and therefore not visible at top level, in which case continue_more won't get set. */ +HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n"); (void)close(inblock.sock); -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT (void) event_raise(tblock->event_action, US"tcp:close", NULL); #endif @@ -2819,6 +3040,10 @@ for (addr = addrlist; addr != NULL; addr = addr->next) 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; @@ -3056,7 +3281,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; @@ -3118,7 +3342,7 @@ for (cutoff_retry = 0; expired && rc = host_find_byname(host, NULL, flags, NULL, TRUE); else rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL, - ob->dnssec_request_domains, ob->dnssec_require_domains, + &ob->dnssec, /* domains for request/require */ NULL, NULL); /* Update the host (and any additional blocks, resulting from @@ -3232,15 +3456,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 @@ -3327,7 +3554,7 @@ for (cutoff_retry = 0; expired && 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 " @@ -3361,7 +3588,7 @@ for (cutoff_retry = 0; expired && if (dont_deliver) { host_item *host2; - set_errno(addrlist, 0, NULL, OK, FALSE, NULL); + set_errno_nohost(addrlist, 0, NULL, OK, FALSE); for (addr = addrlist; addr != NULL; addr = addr->next) { addr->host_used = host; @@ -3413,15 +3640,14 @@ for (cutoff_retry = 0; expired && 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. */ @@ -3449,7 +3675,7 @@ for (cutoff_retry = 0; expired && first_addr->basic_errno != ERRNO_TLSFAILURE) write_logs(first_addr, host); -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT if (rc == DEFER) deferred_event_raise(first_addr, host); #endif @@ -3477,7 +3703,7 @@ for (cutoff_retry = 0; expired && &message_defer, TRUE); if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL) write_logs(first_addr, host); -# ifdef EXPERIMENTAL_EVENT +# ifndef DISABLE_EVENT if (rc == DEFER) deferred_event_raise(first_addr, host); # endif @@ -3580,16 +3806,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 @@ -3676,7 +3898,7 @@ If queue_smtp is set, or this transport was called to send a subsequent message down an existing TCP/IP connection, and something caused the host not to be found, we end up here, but can detect these cases and handle them specially. */ -for (addr = addrlist; addr != NULL; addr = addr->next) +for (addr = addrlist; addr; addr = addr->next) { /* If host is not NULL, it means that we stopped processing the host list because of hosts_max_try or hosts_max_try_hardlimit. In the former case, this @@ -3685,8 +3907,7 @@ for (addr = addrlist; addr != NULL; addr = addr->next) However, if we have hit hosts_max_try_hardlimit, we want to behave as if all hosts were tried. */ - if (host != NULL) - { + if (host) if (total_hosts_tried >= ob->hosts_max_try_hardlimit) { DEBUG(D_transport) @@ -3699,7 +3920,6 @@ for (addr = addrlist; addr != NULL; addr = addr->next) debug_printf("hosts_max_try limit caused some hosts to be skipped\n"); setflag(addr, af_retry_skipped); } - } if (queue_smtp) /* no deliveries attempted */ { @@ -3708,44 +3928,49 @@ for (addr = addrlist; addr != NULL; addr = addr->next) addr->message = US"SMTP delivery explicitly queued"; } - else if (addr->transport_return == DEFER && - (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0) && - addr->message == NULL) + else if ( addr->transport_return == DEFER + && (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0) + && !addr->message + ) { addr->basic_errno = ERRNO_HRETRY; - if (continue_hostname != NULL) - { + if (continue_hostname) addr->message = US"no host found for existing SMTP connection"; - } else if (expired) { setflag(addr, af_pass_message); /* This is not a security risk */ - addr->message = (ob->delay_after_cutoff)? - US"retry time not reached for any host after a long failure period" : - US"all hosts have been failing for a long time and were last tried " - "after this message arrived"; + addr->message = string_sprintf( + "all hosts%s have been failing for a long time %s", + addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"", + ob->delay_after_cutoff + ? US"(and retry time not reached)" + : US"and were last tried after this message arrived"); /* If we are already using fallback hosts, or there are no fallback hosts defined, convert the result to FAIL to cause a bounce. */ - if (addr->host_list == addr->fallback_hosts || - addr->fallback_hosts == NULL) + if (addr->host_list == addr->fallback_hosts || !addr->fallback_hosts) addr->transport_return = FAIL; } else { + const char * s; if (hosts_retry == hosts_total) - addr->message = US"retry time not reached for any host"; + s = "retry time not reached for any host%s"; else if (hosts_fail == hosts_total) - addr->message = US"all host address lookups failed permanently"; + s = "all host address lookups%s failed permanently"; else if (hosts_defer == hosts_total) - addr->message = US"all host address lookups failed temporarily"; + s = "all host address lookups%s failed temporarily"; else if (hosts_serial == hosts_total) - addr->message = US"connection limit reached for all hosts"; + s = "connection limit reached for all hosts%s"; else if (hosts_fail+hosts_defer == hosts_total) - addr->message = US"all host address lookups failed"; - else addr->message = US"some host address lookups failed and retry time " - "not reached for other hosts or connection limit reached"; + s = "all host address lookups%s failed"; + else + s = "some host address lookups failed and retry time " + "not reached for other hosts or connection limit reached%s"; + + addr->message = string_sprintf(s, + addr->domain ? string_sprintf(" for '%s'", addr->domain) : US""); } } }