X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fverify.c;h=de4ffbe48d9ab4f77c39b9b218fc0195b24f5830;hp=def9b31a8c4b299272c3cbc1d5388b66beb0a36f;hb=4e910c01eea401e36044816744691789ef4656fa;hpb=707ee5b1b5a4dc605242e95e988022b87514ce6b diff --git a/src/src/verify.c b/src/src/verify.c index def9b31a8..de4ffbe48 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2016 */ +/* Copyright (c) University of Cambridge 1995 - 2017 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with verifying things. The original code for callout @@ -39,7 +39,7 @@ static tree_node *dnsbl_cache = NULL; #define MT_NOT 1 #define MT_ALL 2 -static uschar cutthrough_response(char, uschar **, int); +static uschar cutthrough_response(int, char, uschar **, int); @@ -356,6 +356,114 @@ if (dbm_file) dbfn_close(dbm_file); } +/* Cutthrough-multi. If the existing cached cutthrough connection matches +the one we would make for a subsequent recipient, use it. Send the RCPT TO +and check the result, nonpipelined as it may be wanted immediately for +recipient-verification. + +It seems simpler to deal with this case separately from the main callout loop. +We will need to remember it has sent, or not, so that rcpt-acl tail code +can do it there for the non-rcpt-verify case. For this we keep an addresscount. + +Return: TRUE for a definitive result for the recipient +*/ +static int +cutthrough_multi(address_item * addr, host_item * host_list, + transport_feedback * tf, int * yield) +{ +BOOL done = FALSE; +host_item * host; + +if (addr->transport == cutthrough.addr.transport) + for (host = host_list; host; host = host->next) + if (Ustrcmp(host->address, cutthrough.host.address) == 0) + { + int host_af; + uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ + int port = 25; + + deliver_host = host->name; + deliver_host_address = host->address; + deliver_host_port = host->port; + deliver_domain = addr->domain; + transport_name = addr->transport->name; + + host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET; + + if (!smtp_get_interface(tf->interface, host_af, addr, &interface, + US"callout") || + !smtp_get_port(tf->port, addr, &port, US"callout")) + log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, + addr->message); + + if ( ( interface == cutthrough.interface + || ( interface + && cutthrough.interface + && Ustrcmp(interface, cutthrough.interface) == 0 + ) ) + && port == cutthrough.host.port + ) + { + uschar * resp = NULL; + + /* Match! Send the RCPT TO, set done from the response */ + done = + smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n", + transport_rcpt_address(addr, + addr->transport->rcpt_include_affixes)) >= 0 && + cutthrough_response(cutthrough.fd, '2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2'; + + /* This would go horribly wrong if a callout fail was ignored by ACL. + We punt by abandoning cutthrough on a reject, like the + first-rcpt does. */ + + if (done) + { + address_item * na = store_get(sizeof(address_item)); + *na = cutthrough.addr; + cutthrough.addr = *addr; + cutthrough.addr.host_used = &cutthrough.host; + cutthrough.addr.next = na; + + cutthrough.nrcpt++; + } + else + { + cancel_cutthrough_connection(TRUE, US"recipient rejected"); + if (!resp || errno == ETIMEDOUT) + { + HDEBUG(D_verify) debug_printf("SMTP timeout\n"); + } + else if (errno == 0) + { + if (*resp == 0) + Ustrcpy(resp, US"connection dropped"); + + addr->message = + string_sprintf("response to \"%s\" was: %s", + big_buffer, string_printing(resp)); + + addr->user_message = + string_sprintf("Callout verification failed:\n%s", resp); + + /* Hard rejection ends the process */ + + if (resp[0] == '5') /* Address rejected */ + { + *yield = FAIL; + done = TRUE; + } + } + } + } + break; /* host_list */ + } +if (!done) + cancel_cutthrough_connection(TRUE, US"incompatible connection"); +return done; +} + + /************************************************* * Do callout verification for an address * *************************************************/ @@ -382,6 +490,7 @@ Arguments: vopt_callout_random => do the "random" thing vopt_callout_recipsender => use real sender for recipient vopt_callout_recippmaster => use postmaster for recipient + vopt_callout_hold => lazy close connection se_mailfrom MAIL FROM address for sender verify; NULL => "" pm_mailfrom if non-NULL, do the postmaster check with this sender @@ -404,7 +513,6 @@ uschar **failure_ptr = options & vopt_is_recipient ? &recipient_verify_failure : &sender_verify_failure; dbdata_callout_cache new_domain_record; dbdata_callout_cache_address new_address_record; -host_item *host; time_t callout_start_time; new_domain_record.result = ccache_unknown; @@ -449,7 +557,10 @@ else if (cached_callout_lookup(addr, address_key, from_address, &options, &pm_mailfrom, &yield, failure_ptr, &new_domain_record, &old_domain_cache_result)) + { + cancel_cutthrough_connection(TRUE, US"cache-hit"); goto END_CALLOUT; + } if (!addr->transport) { @@ -462,6 +573,7 @@ else { smtp_transport_options_block *ob = (smtp_transport_options_block *)addr->transport->options_block; + host_item * host; /* The information wasn't available in the cache, so we have to do a real callout and save the result in the cache for next time, unless no_cache is set, @@ -489,14 +601,13 @@ else if (smtp_out && !disable_callout_flush) mac_smtp_fflush(); + clearflag(addr, af_verify_pmfail); /* postmaster callout flag */ + clearflag(addr, af_verify_nsfail); /* null sender callout flag */ + /* cutthrough-multi: if a nonfirst rcpt has the same routing as the first, and we are holding a cutthrough conn open, we can just append the rcpt to that conn for verification purposes (and later delivery also). Simplest -coding means skipping this whole loop and doing the append separately. - -We will need to remember it has been appended so that rcpt-acl tail code -can do it there for the non-rcpt-verify case. For this we keep an addresscount. -*/ +coding means skipping this whole loop and doing the append separately. */ /* Can we re-use an open cutthrough connection? */ if ( cutthrough.fd >= 0 @@ -505,99 +616,10 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. && !random_local_part && !pm_mailfrom ) - { - if (addr->transport == cutthrough.addr.transport) - for (host = host_list; host; host = host->next) - if (Ustrcmp(host->address, cutthrough.host.address) == 0) - { - int host_af; - uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ - int port = 25; - - deliver_host = host->name; - deliver_host_address = host->address; - deliver_host_port = host->port; - deliver_domain = addr->domain; - transport_name = addr->transport->name; - - host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6; - - if (!smtp_get_interface(tf->interface, host_af, addr, &interface, - US"callout") || - !smtp_get_port(tf->port, addr, &port, US"callout")) - log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, - addr->message); - - if ( ( interface == cutthrough.interface - || ( interface - && cutthrough.interface - && Ustrcmp(interface, cutthrough.interface) == 0 - ) ) - && port == cutthrough.host.port - ) - { - uschar * resp = NULL; - - /* Match! Send the RCPT TO, append the addr, set done */ - done = - smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n", - transport_rcpt_address(addr, - (addr->transport == NULL)? FALSE : - addr->transport->rcpt_include_affixes)) >= 0 && - cutthrough_response('2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2'; - - /* This would go horribly wrong if a callout fail was ignored by ACL. - We punt by abandoning cutthrough on a reject, like the - first-rcpt does. */ - - if (done) - { - address_item * na = store_get(sizeof(address_item)); - *na = cutthrough.addr; - cutthrough.addr = *addr; - cutthrough.addr.host_used = &cutthrough.host; - cutthrough.addr.next = na; - - cutthrough.nrcpt++; - } - else - { - cancel_cutthrough_connection("recipient rejected"); - if (!resp || errno == ETIMEDOUT) - { - HDEBUG(D_verify) debug_printf("SMTP timeout\n"); - } - else if (errno == 0) - { - if (*resp == 0) - Ustrcpy(resp, US"connection dropped"); - - addr->message = - string_sprintf("response to \"%s\" from %s [%s] was: %s", - big_buffer, host->name, host->address, - string_printing(resp)); - - addr->user_message = - string_sprintf("Callout verification failed:\n%s", resp); - - /* Hard rejection ends the process */ - - if (resp[0] == '5') /* Address rejected */ - { - yield = FAIL; - done = TRUE; - } - } - } - } - break; /* host_list */ - } - if (!done) - cancel_cutthrough_connection("incompatible connection"); - } + done = cutthrough_multi(addr, host_list, tf, &yield); - /* Now make connections to the hosts and do real callouts. The list of hosts - is passed in as an argument. */ + /* If we did not use a cached connection, make connections to the hosts + and do real callouts. The list of hosts is passed in as an argument. */ for (host = host_list; host && !done; host = host->next) { @@ -605,12 +627,6 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. int port = 25; uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ smtp_context sx; - uschar responsebuffer[4096]; - - clearflag(addr, af_verify_pmfail); /* postmaster callout flag */ - clearflag(addr, af_verify_nsfail); /* null sender callout flag */ - - /* Skip this host if we don't have an IP address for it. */ if (!host->address) { @@ -629,7 +645,7 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. /* Set IPv4 or IPv6 */ - host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6; + host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET; /* Expand and interpret the interface and port strings. The latter will not be used if there is a host-specific port (e.g. from a manualroute router). @@ -657,6 +673,7 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. sx.interface = interface; sx.helo_data = tf->helo_data; sx.tblock = addr->transport; + sx.verify = TRUE; tls_retry_connection: /* Set the address state so that errors are recorded in it */ @@ -669,21 +686,23 @@ tls_retry_connection: SMTP command to send. If we tried TLS but it failed, try again without if permitted */ - if ( (yield = smtp_setup_conn(&sx, FALSE, TRUE)) == DEFER + yield = smtp_setup_conn(&sx, FALSE); +#ifdef SUPPORT_TLS + if ( yield == DEFER && addr->basic_errno == ERRNO_TLSFAILURE && ob->tls_tempfail_tryclear && verify_check_given_host(&ob->hosts_require_tls, host) != OK ) { - log_write(0, LOG_MAIN, "TLS session failure:" - " callout unencrypted to %s [%s] (not in hosts_require_tls)", - host->name, host->address); - yield = smtp_setup_conn(&sx, TRUE, TRUE); + log_write(0, LOG_MAIN, + "%s: callout unencrypted to %s [%s] (not in hosts_require_tls)", + addr->message, host->name, host->address); + addr->transport_return = PENDING_DEFER; + yield = smtp_setup_conn(&sx, TRUE); } +#endif if (yield != OK) { - if (addr->message) addr->message = string_sprintf("%s [%s] %s", - host->name, host->address, addr->message); errno = addr->basic_errno; transport_name = NULL; deliver_host = deliver_host_address = NULL; @@ -709,66 +728,21 @@ tls_retry_connection: addr->authenticator = client_authenticator; addr->auth_id = client_authenticated_id; - /* Build a mail-AUTH string (re-using responsebuffer for convenience */ - - done = - !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob) - && ( - (addr->auth_sndr = client_authenticated_sender), - - /* Send the MAIL command */ - - (smtp_write_command(&sx.outblock, FALSE, -#ifdef SUPPORT_I18N - addr->prop.utf8_msg && !addr->prop.utf8_downcvt - ? "MAIL FROM:<%s>%s%s SMTPUTF8\r\n" - : -#endif - "MAIL FROM:<%s>%s%s\r\n", - from_address, - responsebuffer, - options & vopt_is_recipient && sx.peer_offered & PEER_OFFERED_SIZE - ? string_sprintf(" SIZE=%d", message_size + ob->size_addition) - : US"" - - ) >= 0) - ) - - && smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + sx.from_addr = from_address; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; /*XXX these 3 last might not be needed for verify? */ + sx.send_rset = TRUE; + sx.completed_addr = FALSE; - deliver_host = deliver_host_address = NULL; - deliver_domain = save_deliver_domain; + new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull + ? ccache_reject_mfnull : ccache_accept; - /* If the host does not accept MAIL FROM:<>, arrange to cache this - information, but again, don't record anything for an I/O error or a defer. Do - not cache rejections of MAIL when a non-empty sender has been used, because - that blocks the whole domain for all senders. */ + /* Do the random local part check first. Temporarily replace the recipient + with the "random" value */ - if (!done) - { - *failure_ptr = US"mail"; /* At or before MAIL */ - if (errno == 0 && responsebuffer[0] == '5') - { - setflag(addr, af_verify_nsfail); - if (from_address[0] == 0) - new_domain_record.result = ccache_reject_mfnull; - } - } - - /* Otherwise, proceed to check a "random" address (if required), then the - given address, and the postmaster address (if required). Between each check, - issue RSET, because some servers accept only one recipient after MAIL - FROM:<>. - - Before doing this, set the result in the domain cache record to "accept", - unless its previous value was ccache_reject_mfnull. In that case, the domain - rejects MAIL FROM:<> and we want to continue to remember that. When that is - the case, we have got here only in the case of a recipient verification with - a non-null sender. */ - - else + if (random_local_part) { + uschar * main_address = addr->address; const uschar * rcpt_domain = addr->domain; #ifdef SUPPORT_I18N @@ -786,179 +760,188 @@ tls_retry_connection: } #endif - new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull - ? ccache_reject_mfnull : ccache_accept; - - /* Do the random local part check first */ - - if (random_local_part != NULL) - { - uschar randombuffer[1024]; - BOOL random_ok = - smtp_write_command(&sx.outblock, FALSE, - "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part, - rcpt_domain) >= 0 && - smtp_read_response(&sx.inblock, randombuffer, - sizeof(randombuffer), '2', callout); - - /* Remember when we last did a random test */ - - new_domain_record.random_stamp = time(NULL); - - /* If accepted, we aren't going to do any further tests below. */ - - if (random_ok) - new_domain_record.random_result = ccache_accept; - - /* Otherwise, cache a real negative response, and get back to the right - state to send RCPT. Unless there's some problem such as a dropped - connection, we expect to succeed, because the commands succeeded above. - However, some servers drop the connection after responding to an - invalid recipient, so on (any) error we drop and remake the connection. - */ - - else if (errno == 0) - { - /* This would be ok for 1st rcpt a cutthrough, but no way to - handle a subsequent. So refuse to support any */ - cancel_cutthrough_connection("random-recipient"); - - if (randombuffer[0] == '5') - new_domain_record.random_result = ccache_reject; + /* This would be ok for 1st rcpt of a cutthrough (the case handled here; + subsequents are done in cutthrough_multi()), but no way to + handle a subsequent because of the RSET vaporising the MAIL FROM. + So refuse to support any. Most cutthrough use will not involve + random_local_part, so no loss. */ + cancel_cutthrough_connection(TRUE, US"random-recipient"); + + addr->address = string_sprintf("%s@%.1000s", + random_local_part, rcpt_domain); + done = FALSE; + + /* If accepted, we aren't going to do any further tests below. + Otherwise, cache a real negative response, and get back to the right + state to send RCPT. Unless there's some problem such as a dropped + connection, we expect to succeed, because the commands succeeded above. + However, some servers drop the connection after responding to an + invalid recipient, so on (any) error we drop and remake the connection. + XXX We don't care about that for postmaster_full. Should we? + + XXX could we add another flag to the context, and have the common + code emit the RSET too? Even pipelined after the RCPT... + Then the main-verify call could use it if there's to be a subsequent + postmaster-verify. + The sync_responses() would need to be taught about it and we'd + need another return code filtering out to here. + */ - done = - smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout) && + /* Remember when we last did a random test */ + new_domain_record.random_stamp = time(NULL); - smtp_write_command(&sx.outblock, FALSE, -#ifdef SUPPORT_I18N - addr->prop.utf8_msg && !addr->prop.utf8_downcvt - ? "MAIL FROM:<%s> SMTPUTF8\r\n" - : -#endif - "MAIL FROM:<%s>\r\n", - from_address) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0) + switch(addr->transport_return) + { + case PENDING_OK: + new_domain_record.random_result = ccache_accept; + break; + case FAIL: + new_domain_record.random_result = ccache_reject; + + /* Between each check, issue RSET, because some servers accept only + one recipient after MAIL FROM:<>. + XXX We don't care about that for postmaster_full. Should we? */ + + if ((done = + smtp_write_command(&sx.outblock, SCMD_FLUSH, "RSET\r\n") >= 0 && + smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), + '2', callout))) + break; - if (!done) - { HDEBUG(D_acl|D_v) - debug_printf("problem after random/rset/mfrom; reopen conn\n"); + debug_printf_indent("problem after random/rset/mfrom; reopen conn\n"); random_local_part = NULL; #ifdef SUPPORT_TLS tls_close(FALSE, TRUE); #endif - HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n"); + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); (void)close(sx.inblock.sock); + sx.inblock.sock = sx.outblock.sock = -1; #ifndef DISABLE_EVENT (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); #endif + addr->address = main_address; + addr->transport_return = PENDING_DEFER; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; + sx.send_rset = TRUE; + sx.completed_addr = FALSE; goto tls_retry_connection; - } - } - else done = FALSE; /* Some timeout/connection problem */ - } /* Random check */ + } + + /* Re-setup for main verify, or for the error message when failing */ + addr->address = main_address; + addr->transport_return = PENDING_DEFER; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; + sx.send_rset = TRUE; + sx.completed_addr = FALSE; + } + else + done = TRUE; - /* If the host is accepting all local parts, as determined by the "random" - check, we don't need to waste time doing any further checking. */ + /* Main verify. If the host is accepting all local parts, as determined + by the "random" check, we don't need to waste time doing any further + checking. */ - if (new_domain_record.random_result != ccache_accept && done) - { - /* Get the rcpt_include_affixes flag from the transport if there is one, - but assume FALSE if there is not. */ + if (done) + { + done = FALSE; + switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield)) + { + case 0: switch(addr->transport_return) /* ok so far */ + { + case PENDING_OK: done = TRUE; + new_address_record.result = ccache_accept; + break; + case FAIL: done = TRUE; + yield = FAIL; + *failure_ptr = US"recipient"; + new_address_record.result = ccache_reject; + break; + default: break; + } + break; + + case -1: /* MAIL response error */ + *failure_ptr = US"mail"; + if (errno == 0 && sx.buffer[0] == '5') + { + setflag(addr, af_verify_nsfail); + if (from_address[0] == 0) + new_domain_record.result = ccache_reject_mfnull; + } + break; + /* non-MAIL read i/o error */ + /* non-MAIL response timeout */ + /* internal error; channel still usable */ + default: break; /* transmit failed */ + } + } - uschar * rcpt = transport_rcpt_address(addr, - addr->transport ? addr->transport->rcpt_include_affixes : FALSE); + addr->auth_sndr = client_authenticated_sender; -#ifdef SUPPORT_I18N - /*XXX should the conversion be moved into transport_rcpt_address() ? */ - if ( testflag(addr, af_utf8_downcvt) - && !(rcpt = string_address_utf8_to_alabel(rcpt, NULL)) - ) - { - errno = ERRNO_EXPANDFAIL; - *failure_ptr = US"recipient"; - done = FALSE; - } - else -#endif + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; - done = - smtp_write_command(&sx.outblock, FALSE, "RCPT TO:<%.1000s>\r\n", - rcpt) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + /* Do postmaster check if requested; if a full check is required, we + check for RCPT TO: (no domain) in accordance with RFC 821. */ - if (done) - new_address_record.result = ccache_accept; - else if (errno == 0 && responsebuffer[0] == '5') - { - *failure_ptr = US"recipient"; - new_address_record.result = ccache_reject; - } + if (done && pm_mailfrom) + { + /* Could possibly shift before main verify, just above, and be ok + for cutthrough. But no way to handle a subsequent rcpt, so just + refuse any */ + cancel_cutthrough_connection(TRUE, US"postmaster verify"); + HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n"); - /* Do postmaster check if requested; if a full check is required, we - check for RCPT TO: (no domain) in accordance with RFC 821. */ + done = smtp_write_command(&sx.outblock, SCMD_FLUSH, "RSET\r\n") >= 0 + && smtp_read_response(&sx.inblock, sx.buffer, + sizeof(sx.buffer), '2', callout); - if (done && pm_mailfrom) - { - /* Could possibly shift before main verify, just above, and be ok - for cutthrough. But no way to handle a subsequent rcpt, so just - refuse any */ - cancel_cutthrough_connection("postmaster verify"); - HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n"); - - done = - smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) && - - smtp_write_command(&sx.outblock, FALSE, - "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) && - - /* First try using the current domain */ - - (( - smtp_write_command(&sx.outblock, FALSE, - "RCPT TO:\r\n", rcpt_domain) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) - ) - - || - - /* If that doesn't work, and a full check is requested, - try without the domain. */ - - ( - (options & vopt_callout_fullpm) != 0 && - smtp_write_command(&sx.outblock, FALSE, - "RCPT TO:\r\n") >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) - )); - - /* Sort out the cache record */ - - new_domain_record.postmaster_stamp = time(NULL); - - if (done) - new_domain_record.postmaster_result = ccache_accept; - else if (errno == 0 && responsebuffer[0] == '5') - { - *failure_ptr = US"postmaster"; - setflag(addr, af_verify_pmfail); - new_domain_record.postmaster_result = ccache_reject; - } - } - } /* Random not accepted */ - } /* MAIL FROM: accepted */ + if (done) + { + uschar * main_address = addr->address; + + /*XXX oops, affixes */ + addr->address = string_sprintf("postmaster@%.1000s", addr->domain); + addr->transport_return = PENDING_DEFER; + sx.from_addr = pm_mailfrom; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; + sx.send_rset = TRUE; + sx.completed_addr = FALSE; + + if( smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0 + && addr->transport_return == PENDING_OK + ) + done = TRUE; + else + done = (options & vopt_callout_fullpm) != 0 + && smtp_write_command(&sx.outblock, SCMD_FLUSH, + "RCPT TO:\r\n") >= 0 + && smtp_read_response(&sx.inblock, sx.buffer, + sizeof(sx.buffer), '2', callout); + + /* Sort out the cache record */ + + new_domain_record.postmaster_stamp = time(NULL); + + if (done) + new_domain_record.postmaster_result = ccache_accept; + else if (errno == 0 && sx.buffer[0] == '5') + { + *failure_ptr = US"postmaster"; + setflag(addr, af_verify_pmfail); + new_domain_record.postmaster_result = ccache_reject; + } + + addr->address = main_address; + } + } /* For any failure of the main check, other than a negative response, we just close the connection and carry on. We can identify a negative response by the fact that errno is zero. For I/O errors it will be non-zero @@ -970,7 +953,7 @@ tls_retry_connection: is not to be widely broadcast. */ no_conn: - if (!done) switch(errno) + switch(errno) { case ETIMEDOUT: HDEBUG(D_verify) debug_printf("SMTP timeout\n"); @@ -983,8 +966,7 @@ no_conn: extern int acl_where; /* src/acl.c */ errno = 0; addr->message = string_sprintf( - "response to \"EHLO\" from %s [%s] did not include SMTPUTF8", - host->name, host->address); + "response to \"EHLO\" did not include SMTPUTF8"); addr->user_message = acl_where == ACL_WHERE_RCPT ? US"533 no support for internationalised mailbox name" : US"550 mailbox unavailable"; @@ -998,21 +980,25 @@ no_conn: break; case 0: - if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped"); + if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped"); - addr->message = - string_sprintf("response to \"%s\" from %s [%s] was: %s", - big_buffer, host->name, host->address, - string_printing(responsebuffer)); + /*XXX test here is ugly; seem to have a split of responsibility for + building this message. Need to reationalise. Where is it done + before here, and when not? + Not == 5xx resp to MAIL on main-verify + */ + if (!addr->message) addr->message = + string_sprintf("response to \"%s\" was: %s", + big_buffer, string_printing(sx.buffer)); addr->user_message = options & vopt_is_recipient - ? string_sprintf("Callout verification failed:\n%s", responsebuffer) + ? string_sprintf("Callout verification failed:\n%s", sx.buffer) : string_sprintf("Called: %s\nSent: %s\nResponse: %s", - host->address, big_buffer, responsebuffer); + host->address, big_buffer, sx.buffer); /* Hard rejection ends the process */ - if (responsebuffer[0] == '5') /* Address rejected */ + if (sx.buffer[0] == '5') /* Address rejected */ { yield = FAIL; done = TRUE; @@ -1024,8 +1010,10 @@ no_conn: /* Cutthrough - on a successful connect and recipient-verify with use-sender and we are 1st rcpt and have no cutthrough conn so far - here is where we want to leave the conn open */ - if ( cutthrough.delivery + here is where we want to leave the conn open. Ditto for a lazy-close + verify. */ + + if ( (cutthrough.delivery || options & vopt_callout_hold) && rcpt_count == 1 && done && yield == OK @@ -1037,14 +1025,29 @@ no_conn: && !sx.lmtp ) { - HDEBUG(D_acl|D_v) debug_printf("holding verify callout open for cutthrough delivery\n"); - - cutthrough.fd = sx.outblock.sock; /* We assume no buffer in use in the outblock */ - cutthrough.nrcpt = 1; - cutthrough.interface = interface; - cutthrough.host = *host; - cutthrough.addr = *addr; /* Save the address_item for later logging */ - cutthrough.addr.next = NULL; + HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n", + cutthrough.delivery + ? "cutthrough delivery" : "potential further verifies and delivery"); + + cutthrough.callout_hold_only = !cutthrough.delivery; + cutthrough.is_tls = tls_out.active >= 0; + cutthrough.fd = sx.outblock.sock; /* We assume no buffer in use in the outblock */ + cutthrough.nrcpt = 1; + cutthrough.transport = addr->transport->name; + cutthrough.interface = interface; + cutthrough.snd_port = sending_port; + cutthrough.peer_options = smtp_peer_options; + cutthrough.host = *host; + { + int oldpool = store_pool; + store_pool = POOL_PERM; + cutthrough.snd_ip = string_copy(sending_ip_address); + cutthrough.host.name = string_copy(host->name); + cutthrough.host.address = string_copy(host->address); + store_pool = oldpool; + } + cutthrough.addr = *addr; /* Save the address_item for later logging */ + cutthrough.addr.next = NULL; cutthrough.addr.host_used = &cutthrough.host; if (addr->parent) *(cutthrough.addr.parent = store_get(sizeof(address_item))) = @@ -1057,15 +1060,15 @@ no_conn: } else { - /* Ensure no cutthrough on multiple address verifies */ + /* Ensure no cutthrough on multiple verifies that were incompatible */ if (options & vopt_callout_recipsender) - cancel_cutthrough_connection("not usable for cutthrough"); + cancel_cutthrough_connection(TRUE, US"not usable for cutthrough"); if (sx.send_quit) { - (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n"); + (void) smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n"); /* Wait a short time for response, and discard it */ - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), + smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2', 1); } @@ -1074,7 +1077,7 @@ no_conn: #ifdef SUPPORT_TLS tls_close(FALSE, TRUE); #endif - HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n"); + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); (void)close(sx.inblock.sock); sx.inblock.sock = sx.outblock.sock = -1; #ifndef DISABLE_EVENT @@ -1083,6 +1086,9 @@ no_conn: } } + if (!done || yield != OK) + addr->message = string_sprintf("%s [%s] : %s", host->name, host->address, + addr->message); } /* Loop through all hosts, while !done */ } @@ -1148,7 +1154,7 @@ int rc; get rewritten. */ addr2 = *addr; -HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n", +HDEBUG(D_acl) debug_printf_indent("----------- %s cutthrough setup ------------\n", rcpt_count > 1 ? "more" : "start"); rc = verify_address(&addr2, NULL, vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache, @@ -1156,7 +1162,7 @@ rc = verify_address(&addr2, NULL, NULL, NULL, NULL); addr->message = addr2.message; addr->user_message = addr2.user_message; -HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n"); +HDEBUG(D_acl) debug_printf_indent("----------- end cutthrough setup ------------\n"); return rc; } @@ -1181,7 +1187,7 @@ if( return TRUE; } -HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno)); +HDEBUG(D_transport|D_acl) debug_printf_indent("cutthrough_send failed: %s\n", strerror(errno)); return FALSE; } @@ -1202,20 +1208,27 @@ return TRUE; } /* Buffered output of counted data block. Return boolean success */ -BOOL +static BOOL cutthrough_puts(uschar * cp, int n) { if (cutthrough.fd < 0) return TRUE; if (_cutthrough_puts(cp, n)) return TRUE; -cancel_cutthrough_connection("transmit failed"); +cancel_cutthrough_connection(TRUE, US"transmit failed"); return FALSE; } +void +cutthrough_data_puts(uschar * cp, int n) +{ +if (cutthrough.delivery) (void) cutthrough_puts(cp, n); +return; +} + static BOOL _cutthrough_flush_send(void) { -int n= ctblock.ptr-ctblock.buffer; +int n = ctblock.ptr - ctblock.buffer; if(n>0) if(!cutthrough_send(n)) @@ -1229,21 +1242,28 @@ BOOL cutthrough_flush_send(void) { if (_cutthrough_flush_send()) return TRUE; -cancel_cutthrough_connection("transmit failed"); +cancel_cutthrough_connection(TRUE, US"transmit failed"); return FALSE; } -BOOL +static BOOL cutthrough_put_nl(void) { return cutthrough_puts(US"\r\n", 2); } +void +cutthrough_data_put_nl(void) +{ +cutthrough_data_puts(US"\r\n", 2); +} + + /* Get and check response from cutthrough target */ static uschar -cutthrough_response(char expect, uschar ** copy, int timeout) +cutthrough_response(int fd, char expect, uschar ** copy, int timeout) { smtp_inblock inblock; uschar inbuffer[4096]; @@ -1253,12 +1273,12 @@ inblock.buffer = inbuffer; inblock.buffersize = sizeof(inbuffer); inblock.ptr = inbuffer; inblock.ptrend = inbuffer; -inblock.sock = cutthrough.fd; +inblock.sock = fd; /* this relies on (inblock.sock == tls_out.active) */ if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout)) - cancel_cutthrough_connection("target timeout on read"); + cancel_cutthrough_connection(TRUE, US"target timeout on read"); -if(copy != NULL) +if(copy) { uschar * cp; *copy = cp = string_copy(responsebuffer); @@ -1276,15 +1296,15 @@ return responsebuffer[0]; BOOL cutthrough_predata(void) { -if(cutthrough.fd < 0) +if(cutthrough.fd < 0 || cutthrough.callout_hold_only) return FALSE; -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n"); +HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> DATA\n"); cutthrough_puts(US"DATA\r\n", 6); cutthrough_flush_send(); /* Assume nothing buffered. If it was it gets ignored. */ -return cutthrough_response('3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3'; +return cutthrough_response(cutthrough.fd, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3'; } @@ -1311,13 +1331,13 @@ cutthrough_headers_send(void) { transport_ctx tctx; -if(cutthrough.fd < 0) +if(cutthrough.fd < 0 || cutthrough.callout_hold_only) return FALSE; /* We share a routine with the mainline transport to handle header add/remove/rewrites, but having a separate buffered-output function (for now) */ -HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n"); +HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n"); tctx.tblock = cutthrough.addr.transport; tctx.addr = &cutthrough.addr; @@ -1328,44 +1348,55 @@ tctx.options = topt_use_crlf; if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk)) return FALSE; -HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n"); +HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n"); return TRUE; } static void -close_cutthrough_connection(const char * why) +close_cutthrough_connection(const uschar * why) { -if(cutthrough.fd >= 0) +int fd = cutthrough.fd; +if(fd >= 0) { /* We could be sending this after a bunch of data, but that is ok as the only way to cancel the transfer in dataphase is to drop the tcp conn before the final dot. */ ctblock.ptr = ctbuffer; - HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n"); + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */ _cutthrough_flush_send(); + cutthrough.fd = -1; /* avoid recursion via read timeout */ /* Wait a short time for response, and discard it */ - cutthrough_response('2', NULL, 1); + cutthrough_response(fd, '2', NULL, 1); - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS tls_close(FALSE, TRUE); - #endif - HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n"); - (void)close(cutthrough.fd); - cutthrough.fd = -1; - HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why); +#endif + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); + (void)close(fd); + HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why); } ctblock.ptr = ctbuffer; } void -cancel_cutthrough_connection(const char * why) +cancel_cutthrough_connection(BOOL close_noncutthrough_verifies, const uschar * why) { -close_cutthrough_connection(why); -cutthrough.delivery = FALSE; +if (cutthrough.delivery || close_noncutthrough_verifies) + close_cutthrough_connection(why); +cutthrough.delivery = cutthrough.callout_hold_only = FALSE; +} + + +void +release_cutthrough_connection(const uschar * why) +{ +HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why); +cutthrough.fd = -1; +cutthrough.delivery = cutthrough.callout_hold_only = FALSE; } @@ -1381,7 +1412,7 @@ cutthrough_finaldot(void) { uschar res; address_item * addr; -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n"); +HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> .\n"); /* Assume data finshed with new-line */ if( !cutthrough_puts(US".", 1) @@ -1390,7 +1421,7 @@ if( !cutthrough_puts(US".", 1) ) return cutthrough.addr.message; -res = cutthrough_response('2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT); +res = cutthrough_response(cutthrough.fd, '2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT); for (addr = &cutthrough.addr; addr; addr = addr->next) { addr->message = cutthrough.addr.message; @@ -1398,7 +1429,7 @@ for (addr = &cutthrough.addr; addr; addr = addr->next) { case '2': delivery_log(LOG_MAIN, addr, (int)'>', NULL); - close_cutthrough_connection("delivered"); + close_cutthrough_connection(US"delivered"); break; case '4': @@ -1821,7 +1852,7 @@ while (addr_new) dnssec_domains = &ob->dnssec; } - (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL, + (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL, dnssec_domains, NULL, NULL); } } @@ -1897,7 +1928,7 @@ while (addr_new) } respond_printf(f, "%s\n", cr); } - cancel_cutthrough_connection("routing hard fail"); + cancel_cutthrough_connection(TRUE, US"routing hard fail"); if (!full_info) { @@ -1936,7 +1967,7 @@ while (addr_new) } respond_printf(f, "%s\n", cr); } - cancel_cutthrough_connection("routing soft fail"); + cancel_cutthrough_connection(TRUE, US"routing soft fail"); if (!full_info) { @@ -2009,7 +2040,7 @@ while (addr_new) /* If stopped because more than one new address, cannot cutthrough */ if (addr_new && addr_new->next) - cancel_cutthrough_connection("multiple addresses from routing"); + cancel_cutthrough_connection(TRUE, US"multiple addresses from routing"); yield = OK; goto out; @@ -2259,18 +2290,16 @@ verify_check_header_names_ascii(uschar **msgptr) header_line *h; uschar *colon, *s; -for (h = header_list; h != NULL; h = h->next) +for (h = header_list; h; h = h->next) { - colon = Ustrchr(h->text, ':'); - for(s = h->text; s < colon; s++) - { - if ((*s < 33) || (*s > 126)) - { - *msgptr = string_sprintf("Invalid character in header \"%.*s\" found", - colon - h->text, h->text); - return FAIL; - } - } + colon = Ustrchr(h->text, ':'); + for(s = h->text; s < colon; s++) + if ((*s < 33) || (*s > 126)) + { + *msgptr = string_sprintf("Invalid character in header \"%.*s\" found", + colon - h->text, h->text); + return FAIL; + } } return OK; }