X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/7ade712cc84d7f822f04baf2f46daee81701174d..4b0fe31936b336d12836875101dcac6599d127ee:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index ffba14662..361531a9f 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, @@ -159,7 +159,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 +241,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 +249,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 @@ -441,6 +440,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 +452,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 +465,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 +477,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 +596,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 +655,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 +668,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 +874,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 +919,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 +1133,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 +1148,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 +1189,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; } } @@ -1170,8 +1216,7 @@ return FALSE; #ifdef EXPERIMENTAL_DANE 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]; @@ -1187,12 +1232,7 @@ switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) default: case DNS_FAIL: - if (dane_required) - { - log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed"); - return FAIL; - } - break; + return dane_required ? FAIL : DEFER; case DNS_SUCCEED: if (!dns_is_secure(dnsa)) @@ -1200,10 +1240,8 @@ switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); return DEFER; } - *dane = TRUE; - break; + return OK; } -return OK; } #endif @@ -1268,20 +1306,68 @@ 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,17 +1437,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 EXPERIMENTAL_INTERNATIONAL -BOOL utf8_offered = FALSE; +#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; @@ -1369,6 +1455,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; @@ -1377,7 +1467,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 */ @@ -1421,8 +1510,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 @@ -1439,8 +1528,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; } @@ -1449,20 +1538,28 @@ 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; + { + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required)) != OK) + { + set_errno_nohost(addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc == DEFER ? "DEFER" : "FAIL"), + rc, FALSE); + return rc; + } + dane = TRUE; + } } 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; } @@ -1476,6 +1573,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, @@ -1483,10 +1593,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" @@ -1494,9 +1608,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; } @@ -1510,7 +1624,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; } @@ -1575,9 +1689,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 { @@ -1587,42 +1710,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");} -#endif - -#ifdef EXPERIMENTAL_INTERNATIONAL - utf8_offered = esmtp - && addrlist->p.utf8 - && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; + tls_offered = !!(peer_offered & PEER_OFFERED_TLS); #endif } @@ -1633,6 +1747,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); @@ -1667,8 +1786,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; @@ -1693,13 +1814,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 @@ -1711,7 +1830,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; @@ -1736,6 +1855,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); @@ -1743,7 +1864,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; } @@ -1752,8 +1873,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) @@ -1768,9 +1893,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 @@ -1802,54 +1930,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 -#ifdef EXPERIMENTAL_INTERNATIONAL - utf8_offered = esmtp - && addrlist->p.utf8 - && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; -#endif - /* 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); + 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 @@ -1871,9 +1998,17 @@ message-specific. */ setting_up = FALSE; -#ifdef EXPERIMENTAL_INTERNATIONAL +#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 (addrlist->p.utf8 && !utf8_offered) +if (utf8_needed && !(peer_offered & PEER_OFFERED_UTF8)) { errno = ERRNO_UTF8_FWD; goto RESPONSE_FAILED; @@ -1897,8 +2032,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; } @@ -1939,8 +2074,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) { @@ -1953,11 +2087,13 @@ if (prdr_offered) } break; } - } #endif -#ifdef EXPERIMENTAL_INTERNATIONAL -if (addrlist->p.utf8) +#ifdef SUPPORT_I18N +if ( addrlist->prop.utf8_msg + && !addrlist->prop.utf8_downcvt + && peer_offered & PEER_OFFERED_UTF8 + ) sprintf(CS p, " SMTPUTF8"), p += 9; #endif @@ -2014,8 +2150,33 @@ 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) @@ -2057,6 +2218,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; @@ -2101,8 +2263,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) @@ -2139,8 +2317,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; } @@ -2306,8 +2484,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 ) @@ -2362,7 +2540,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 */ @@ -2397,7 +2575,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 " @@ -2434,7 +2612,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 " @@ -2464,22 +2642,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 @@ -2500,16 +2683,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 @@ -2524,24 +2705,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: @@ -2549,6 +2735,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 @@ -2562,14 +2749,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); @@ -2586,11 +2774,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 + ); } @@ -2703,6 +2897,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)) { @@ -2712,7 +2909,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 + ); } } @@ -2753,9 +2954,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 @@ -2853,6 +3055,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; @@ -3090,7 +3296,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; @@ -3152,7 +3357,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 @@ -3266,15 +3471,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 @@ -3361,7 +3569,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 " @@ -3395,7 +3603,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; @@ -3447,15 +3655,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. */ @@ -3483,7 +3690,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 @@ -3511,7 +3718,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 @@ -3614,16 +3821,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 @@ -3754,9 +3957,9 @@ for (addr = addrlist; addr != NULL; addr = addr->next) 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 " + addr->message = ob->delay_after_cutoff + ? US"retry time not reached for any host after a long failure period" + : US"all hosts have been failing for a long time and were last tried " "after this message arrived"; /* If we are already using fallback hosts, or there are no fallback hosts @@ -3768,18 +3971,23 @@ for (addr = addrlist; addr != NULL; addr = addr->next) } 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""); } } }