X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Ftransports%2Fsmtp.c;h=234467437c6a8ff4c86da5db8688767b59ceff9f;hb=fc16abb4d06c7ae375b227bd83473412c8985c6f;hp=135069d0f189e487f2cfb304dd155f59c4f9244a;hpb=0cbf2b821bb13da0268556d0e30ea627d5592c60;p=exim.git diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 135069d0f..234467437 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 - 2015 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -668,17 +668,17 @@ if (addr->message) } else { - 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); } @@ -1215,9 +1215,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]; @@ -1228,25 +1234,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) - 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 @@ -1311,7 +1316,6 @@ 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; @@ -1330,6 +1334,49 @@ 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 * *************************************************/ @@ -1399,13 +1446,12 @@ 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; -BOOL utf8_offered = FALSE; #endif BOOL dsn_all_lasthop = TRUE; #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) @@ -1503,26 +1549,26 @@ if (continue_hostname == NULL) 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 */ + if( dane_required + || verify_check_given_host(&ob->hosts_try_dane, host) == OK ) - { - set_errno_nohost(addrlist, ERRNO_DNSDEFER, - string_sprintf("DANE error: tlsa lookup %s", - rc == DEFER ? "DEFER" : "FAIL"), - rc, FALSE); - 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) { set_errno_nohost(addrlist, ERRNO_DNSDEFER, string_sprintf("DANE error: %s lookup not DNSSEC", host->name), FAIL, FALSE); - return FAIL; + return FAIL; } if (dane) @@ -1684,43 +1730,21 @@ goto SEND_QUIT; 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 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"); - - utf8_offered = esmtp - && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0, - PCRE_EOPT, NULL, 0) >= 0; - } + tls_offered = !!(peer_offered & PEER_OFFERED_TLS); #endif } @@ -1770,8 +1794,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; @@ -1796,13 +1822,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 @@ -1914,54 +1938,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 SUPPORT_I18N - if (addrlist->prop.utf8_msg) - utf8_offered = esmtp - && 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 @@ -1984,8 +2007,16 @@ 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 && !utf8_offered) +if (utf8_needed && !(peer_offered & PEER_OFFERED_UTF8)) { errno = ERRNO_UTF8_FWD; goto RESPONSE_FAILED; @@ -2051,8 +2082,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) { @@ -2065,11 +2095,13 @@ if (prdr_offered) } break; } - } #endif #ifdef SUPPORT_I18N -if (addrlist->prop.utf8_msg && !addrlist->prop.utf8_downcvt && utf8_offered) +if ( addrlist->prop.utf8_msg + && !addrlist->prop.utf8_downcvt + && peer_offered & PEER_OFFERED_UTF8 + ) sprintf(CS p, " SMTPUTF8"), p += 9; #endif @@ -2135,7 +2167,9 @@ pending_MAIL = TRUE; /* The block starts with MAIL */ 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 ( addrlist->prop.utf8_msg + && (addrlist->prop.utf8_downcvt || !(peer_offered & PEER_OFFERED_UTF8)) + ) { if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr) { @@ -2928,6 +2962,7 @@ 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); #ifndef DISABLE_EVENT @@ -3930,9 +3965,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 @@ -3944,18 +3979,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""); } } }