X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/57233af5f91cdca9a0232a71fab2d12a538cb1a6..ea90b71819daaacf691fb3ec5526397d12790c62:/src/src/verify.c?ds=sidebyside diff --git a/src/src/verify.c b/src/src/verify.c index 52404575f..69ed33e23 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 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with verifying things. The original code for callout @@ -14,7 +14,6 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */ #define CUTTHROUGH_CMD_TIMEOUT 30 /* timeout for cutthrough-routing calls */ #define CUTTHROUGH_DATA_TIMEOUT 60 /* timeout for cutthrough-routing calls */ -address_item cutthrough_addr; static smtp_outblock ctblock; uschar ctbuffer[8192]; @@ -22,6 +21,7 @@ uschar ctbuffer[8192]; /* Structure for caching DNSBL lookups */ typedef struct dnsbl_cache_block { + time_t expiry; dns_address *rhs; uschar *text; int rc; @@ -39,6 +39,7 @@ static tree_node *dnsbl_cache = NULL; #define MT_NOT 1 #define MT_ALL 2 +static uschar cutthrough_response(char, uschar **); /************************************************* @@ -58,7 +59,7 @@ Returns: the cache record if a non-expired one exists, else NULL */ static dbdata_callout_cache * -get_callout_cache_record(open_db *dbm_file, uschar *key, uschar *type, +get_callout_cache_record(open_db *dbm_file, const uschar *key, uschar *type, int positive_expire, int negative_expire) { BOOL negative; @@ -70,7 +71,7 @@ cache_record = dbfn_read_with_length(dbm_file, key, &length); if (cache_record == NULL) { - HDEBUG(D_verify) debug_printf("callout cache: no %s record found\n", type); + HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key); return NULL; } @@ -84,7 +85,7 @@ now = time(NULL); if (now - cache_record->time_stamp > expire) { - HDEBUG(D_verify) debug_printf("callout cache: %s record expired\n", type); + HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key); return NULL; } @@ -111,7 +112,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject) cache_record->random_result = ccache_unknown; } -HDEBUG(D_verify) debug_printf("callout cache: found %s record\n", type); +HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key); return cache_record; } @@ -164,7 +165,7 @@ BOOL done = FALSE; uschar *address_key; uschar *from_address; uschar *random_local_part = NULL; -uschar *save_deliver_domain = deliver_domain; +const uschar *save_deliver_domain = deliver_domain; uschar **failure_ptr = is_recipient? &recipient_verify_failure : &sender_verify_failure; open_db dbblock; @@ -173,6 +174,7 @@ dbdata_callout_cache new_domain_record; dbdata_callout_cache_address new_address_record; host_item *host; time_t callout_start_time; +uschar peer_offered = 0; new_domain_record.result = ccache_unknown; new_domain_record.postmaster_result = ccache_unknown; @@ -189,12 +191,12 @@ from_address = US""; if (is_recipient) { - if ((options & vopt_callout_recipsender) != 0) + if (options & vopt_callout_recipsender) { address_key = string_sprintf("%s/<%s>", addr->address, sender_address); from_address = sender_address; } - else if ((options & vopt_callout_recippmaster) != 0) + else if (options & vopt_callout_recippmaster) { address_key = string_sprintf("%s/", addr->address, qualify_domain_sender); @@ -373,10 +375,13 @@ if (!addr->transport) { HDEBUG(D_verify) debug_printf("cannot callout via null transport\n"); } +else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0) + log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp", + addr->transport->name, addr->transport->driver_name); else { smtp_transport_options_block *ob = - (smtp_transport_options_block *)(addr->transport->options_block); + (smtp_transport_options_block *)addr->transport->options_block; /* 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, @@ -385,12 +390,9 @@ else log the fact, but carry on without randomming. */ if (callout_random && callout_random_local_part != NULL) - { - random_local_part = expand_string(callout_random_local_part); - if (random_local_part == NULL) + if (!(random_local_part = expand_string(callout_random_local_part))) log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " "callout_random_local_part: %s", expand_string_message); - } /* Default the connect and overall callout timeouts if not set, and record the time we are starting so that we can enforce it. */ @@ -407,6 +409,113 @@ else if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush(); +/* 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. +*/ + + /* Can we re-use an open cutthrough connection? */ + if ( cutthrough.fd >= 0 + && (options & (vopt_callout_recipsender | vopt_callout_recippmaster)) + == vopt_callout_recipsender + && !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) == '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; + } + if (!done) + cancel_cutthrough_connection("incompatible connection"); + } + /* Now make connections to the hosts and do real callouts. The list of hosts is passed in as an argument. */ @@ -423,9 +532,15 @@ else BOOL esmtp; BOOL suppress_tls = FALSE; uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) + BOOL dane = FALSE; + BOOL dane_required; + dns_answer tlsa_dnsa; +#endif uschar inbuffer[4096]; uschar outbuffer[1024]; uschar responsebuffer[4096]; + uschar * size_str; clearflag(addr, af_verify_pmfail); /* postmaster callout flag */ clearflag(addr, af_verify_nsfail); /* null sender callout flag */ @@ -459,11 +574,14 @@ else deliver_host = host->name; deliver_host_address = host->address; + deliver_host_port = host->port; deliver_domain = addr->domain; + transport_name = addr->transport->name; - if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface, - US"callout") || - !smtp_get_port(tf->port, addr, &port, US"callout")) + 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); @@ -489,26 +607,59 @@ else outblock.cmd_count = 0; outblock.authenticating = FALSE; - /* Reset the parameters of a TLS session */ - tls_out.cipher = tls_out.peerdn = NULL; - /* Connect to the host; on failure, just loop for the next one, but we set the error for the last one. Use the callout_connect timeout. */ tls_retry_connection: + /* Reset the parameters of a TLS session */ + tls_out.cipher = tls_out.peerdn = tls_out.peercert = NULL; + inblock.sock = outblock.sock = - smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL); - /* reconsider DSCP here */ + smtp_connect(host, host_af, port, interface, callout_connect, + addr->transport); if (inblock.sock < 0) { addr->message = string_sprintf("could not connect to %s [%s]: %s", host->name, host->address, strerror(errno)); + transport_name = NULL; deliver_host = deliver_host_address = NULL; deliver_domain = save_deliver_domain; continue; } +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) + { + int rc; + + 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 + ) + { + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required)) != OK) + return rc; + dane = TRUE; + } + } + else if (dane_required) + { + log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name); + return FAIL; + } + + if (dane) + ob->tls_tempfail_tryclear = FALSE; + } +#endif /*DANE*/ + /* Expand the helo_data string to find the host name to use. */ if (tf->helo_data != NULL) @@ -521,9 +672,6 @@ else else active_hostname = s; } - deliver_host = deliver_host_address = NULL; - deliver_domain = save_deliver_domain; - /* Wait for initial response, and send HELO. The smtp_write_command() function leaves its command in big_buffer. This is used in error responses. Initialize it in case the connection is rejected. */ @@ -533,28 +681,42 @@ else /* Unless ssl-on-connect, wait for the initial greeting */ smtps_redo_greeting: - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (!smtps || (smtps && tls_out.active >= 0)) - #endif +#endif + { if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))) goto RESPONSE_FAILED; - + +#ifndef DISABLE_EVENT + lookup_dnssec_authenticated = host->dnssec==DS_YES ? US"yes" + : host->dnssec==DS_NO ? US"no" : NULL; + if (event_raise(addr->transport->event_action, + US"smtp:connect", responsebuffer)) + { + lookup_dnssec_authenticated = NULL; + /* Logging? Debug? */ + goto RESPONSE_FAILED; + } + lookup_dnssec_authenticated = NULL; +#endif + } + /* Not worth checking greeting line for ESMTP support */ - if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL, - host->name, host->address, NULL) != OK)) + if (!(esmtp = verify_check_given_host(&ob->hosts_avoid_esmtp, host) != OK)) DEBUG(D_transport) debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n"); tls_redo_helo: - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (smtps && tls_out.active < 0) /* ssl-on-connect, first pass */ { - tls_offered = TRUE; + peer_offered &= ~PEER_OFFERED_TLS; ob->tls_tempfail_tryclear = FALSE; } - else /* all other cases */ - #endif + else /* all other cases */ +#endif { esmtp_retry: @@ -568,28 +730,41 @@ else done= FALSE; goto RESPONSE_FAILED; } - #ifdef SUPPORT_TLS - tls_offered = FALSE; - #endif +#ifdef SUPPORT_TLS + peer_offered &= ~PEER_OFFERED_TLS; +#endif esmtp = FALSE; goto esmtp_retry; /* fallback to HELO */ } /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */ - #ifdef SUPPORT_TLS - if (esmtp && !suppress_tls && tls_out.active < 0) - { - if (regex_STARTTLS == NULL) regex_STARTTLS = - regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE); - tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer, - Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0; - } - else - tls_offered = FALSE; - #endif + peer_offered = esmtp + ? ehlo_response(responsebuffer, sizeof(responsebuffer), + (!suppress_tls && tls_out.active < 0 ? PEER_OFFERED_TLS : 0) + | 0 /* no IGNQ */ + | 0 /* no PRDR */ +#ifdef SUPPORT_I18N + | (addr->prop.utf8_msg && !addr->prop.utf8_downcvt + ? PEER_OFFERED_UTF8 : 0) +#endif + | 0 /* no DSN */ + | 0 /* no PIPE */ + + /* only care about SIZE if we have size from inbound */ + | (message_size > 0 && ob->size_addition >= 0 + ? PEER_OFFERED_SIZE : 0) + ) + : 0; } + size_str = peer_offered & PEER_OFFERED_SIZE + ? string_sprintf(" SIZE=%d", message_size + ob->size_addition) : US""; + +#ifdef SUPPORT_TLS + tls_offered = !!(peer_offered & PEER_OFFERED_TLS); +#endif + /* If TLS is available on this connection attempt to start up a TLS session, unless the host is in hosts_avoid_tls. If successful, send another EHLO - the server may give a different answer in secure mode. We @@ -598,12 +773,10 @@ else the client not be required to use TLS. If the response is bad, copy the buffer for error analysis. */ - #ifdef SUPPORT_TLS - if (tls_offered && - verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name, - host->address, NULL) != OK && - verify_check_this_host(&(ob->hosts_verify_avoid_tls), NULL, host->name, - host->address, NULL) != OK +#ifdef SUPPORT_TLS + if ( peer_offered & PEER_OFFERED_TLS + && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK + && verify_check_given_host(&ob->hosts_verify_avoid_tls, host) != OK ) { uschar buffer2[4096]; @@ -621,48 +794,62 @@ else if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2', ob->command_timeout)) { - if (errno != 0 || buffer2[0] == 0 || - (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)) - { - Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer)); - done= FALSE; - goto RESPONSE_FAILED; - } + if ( errno != 0 + || buffer2[0] == 0 + || buffer2[0] == '4' && !ob->tls_tempfail_tryclear + ) + { + Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer)); + done= FALSE; + goto RESPONSE_FAILED; + } } /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */ else { - int rc = tls_client_start(inblock.sock, host, addr, - ob->tls_certificate, ob->tls_privatekey, - ob->tls_sni, - ob->tls_verify_certificates, ob->tls_crl, - ob->tls_require_ciphers, -#ifdef EXPERIMENTAL_OCSP - ob->hosts_require_ocsp, -#endif - ob->tls_dh_min_bits, callout); - - /* TLS negotiation failed; give an error. Try in clear on a new connection, - if the options permit it for this host. */ + int oldtimeout = ob->command_timeout; + int rc; + + tls_negotiate: + ob->command_timeout = callout; + rc = tls_client_start(inblock.sock, host, addr, addr->transport +# ifdef EXPERIMENTAL_DANE + , dane ? &tlsa_dnsa : NULL +# endif + ); + ob->command_timeout = oldtimeout; + + /* TLS negotiation failed; give an error. Try in clear on a new + connection, if the options permit it for this host. */ if (rc != OK) { - if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps && - verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, - host->address, NULL) != OK) - { - (void)close(inblock.sock); - log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted " - "to %s [%s] (not in hosts_require_tls)", host->name, host->address); - suppress_tls = TRUE; - goto tls_retry_connection; - } - /*save_errno = ERRNO_TLSFAILURE;*/ - /*message = US"failure while setting up TLS session";*/ - send_quit = FALSE; - done= FALSE; - goto TLS_FAILED; - } + if (rc == DEFER) + { + (void)close(inblock.sock); +# ifndef DISABLE_EVENT + (void) event_raise(addr->transport->event_action, + US"tcp:close", NULL); +# endif + if ( ob->tls_tempfail_tryclear + && !smtps + && verify_check_given_host(&ob->hosts_require_tls, host) != OK + ) + { + log_write(0, LOG_MAIN, "TLS session failure:" + " delivering unencrypted to %s [%s] (not in hosts_require_tls)", + host->name, host->address); + suppress_tls = TRUE; + goto tls_retry_connection; + } + } + + /*save_errno = ERRNO_TLSFAILURE;*/ + /*message = US"failure while setting up TLS session";*/ + send_quit = FALSE; + done= FALSE; + goto TLS_FAILED; + } /* TLS session is set up. Copy info for logging. */ addr->cipher = tls_out.cipher; @@ -670,7 +857,7 @@ else /* For SMTPS we need to wait for the initial OK response, then do HELO. */ if (smtps) - goto smtps_redo_greeting; + goto smtps_redo_greeting; /* For STARTTLS we need to redo EHLO */ goto tls_redo_helo; @@ -679,28 +866,45 @@ else /* If the host is required to use a secure channel, ensure that we have one. */ if (tls_out.active < 0) - if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, - host->address, NULL) == OK) + if ( +# ifdef EXPERIMENTAL_DANE + dane || +# endif + verify_check_given_host(&ob->hosts_require_tls, host) == OK + ) { /*save_errno = ERRNO_TLSREQUIRED;*/ - log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s", + log_write(0, LOG_MAIN, + "H=%s [%s]: a TLS session is required for this host, but %s", host->name, host->address, - tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support"); + peer_offered & PEER_OFFERED_TLS + ? "an attempt to start TLS failed" + : "the server did not offer TLS support"); done= FALSE; goto TLS_FAILED; } - #endif /*SUPPORT_TLS*/ +#endif /*SUPPORT_TLS*/ done = TRUE; /* so far so good; have response to HELO */ - /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING, AUTH */ - /* If we haven't authenticated, but are required to, give up. */ - - /*XXX "filter command specified for this transport" ??? */ - /* for now, transport_filter by cutthrough-delivery is not supported */ + /* For now, transport_filter by cutthrough-delivery is not supported */ /* Need proper integration with the proper transport mechanism. */ - + if (cutthrough.delivery) + { + if (addr->transport->filter_command) + { + cutthrough.delivery = FALSE; + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n"); + } +#ifndef DISABLE_DKIM + if (ob->dkim_domain) + { + cutthrough.delivery = FALSE; + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n"); + } +#endif + } SEND_FAILED: RESPONSE_FAILED: @@ -708,7 +912,6 @@ else ; /* Clear down of the TLS, SMTP and TCP layers on error is handled below. */ - /* Failure to accept HELO is cached; this blocks the whole domain for all senders. I/O errors and defer responses are not cached. */ @@ -722,14 +925,64 @@ else } } +#ifdef SUPPORT_I18N + else if ( addr->prop.utf8_msg + && !addr->prop.utf8_downcvt + && !(peer_offered & PEER_OFFERED_UTF8) + ) + { + HDEBUG(D_acl|D_v) debug_printf("utf8 required but not offered\n"); + errno = ERRNO_UTF8_FWD; + setflag(addr, af_verify_nsfail); + done = FALSE; + } + else if ( addr->prop.utf8_msg + && (addr->prop.utf8_downcvt || !(peer_offered & PEER_OFFERED_UTF8)) + && (setflag(addr, af_utf8_downcvt), + from_address = string_address_utf8_to_alabel(from_address, + &addr->message), + addr->message + ) ) + { + errno = ERRNO_EXPANDFAIL; + setflag(addr, af_verify_nsfail); + done = FALSE; + } +#endif + + /* If we haven't authenticated, but are required to, give up. */ + /* Try to AUTH */ + + else done = smtp_auth(responsebuffer, sizeof(responsebuffer), + addr, host, ob, esmtp, &inblock, &outblock) == OK && + + /* Copy AUTH info for logging */ + ( (addr->authenticator = client_authenticator), + (addr->auth_id = client_authenticated_id), + + /* Build a mail-AUTH string (re-using responsebuffer for convenience */ + !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob) + ) && + + ( (addr->auth_sndr = client_authenticated_sender), + /* Send the MAIL command */ + (smtp_write_command(&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, size_str) >= 0) + ) && - else done = - smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", - from_address) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; + /* 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 @@ -759,6 +1012,23 @@ else else { + const uschar * rcpt_domain = addr->domain; + +#ifdef SUPPORT_I18N + uschar * errstr = NULL; + if ( testflag(addr, af_utf8_downcvt) + && (rcpt_domain = string_domain_utf8_to_alabel(rcpt_domain, + &errstr), errstr) + ) + { + addr->message = errstr; + errno = ERRNO_EXPANDFAIL; + setflag(addr, af_verify_nsfail); + done = FALSE; + rcpt_domain = US""; /*XXX errorhandling! */ + } +#endif + new_domain_record.result = (old_domain_cache_result == ccache_reject_mfnull)? ccache_reject_mfnull: ccache_accept; @@ -771,7 +1041,7 @@ else BOOL random_ok = smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part, - addr->domain) >= 0 && + rcpt_domain) >= 0 && smtp_read_response(&inblock, randombuffer, sizeof(randombuffer), '2', callout); @@ -782,16 +1052,21 @@ else /* 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. */ + 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; @@ -800,10 +1075,32 @@ else smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout) && - smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", + smtp_write_command(&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(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); + + if (!done) + { + HDEBUG(D_acl|D_v) + debug_printf("problem after random/rset/mfrom; reopen conn\n"); + random_local_part = NULL; +#ifdef SUPPORT_TLS + tls_close(FALSE, TRUE); +#endif + (void)close(inblock.sock); +#ifndef DISABLE_EVENT + (void) event_raise(addr->transport->event_action, + US"tcp:close", NULL); +#endif + goto tls_retry_connection; + } } else done = FALSE; /* Some timeout/connection problem */ } /* Random check */ @@ -816,11 +1113,27 @@ else /* Get the rcpt_include_affixes flag from the transport if there is one, but assume FALSE if there is not. */ + uschar * rcpt = transport_rcpt_address(addr, + addr->transport ? addr->transport->rcpt_include_affixes : FALSE); + +#ifdef SUPPORT_I18N + /*XXX should the conversion be moved into transport_rcpt_address() ? */ + uschar * dummy_errstr = NULL; + if ( testflag(addr, af_utf8_downcvt) + && (rcpt = string_address_utf8_to_alabel(rcpt, &dummy_errstr), + dummy_errstr + ) ) + { + errno = ERRNO_EXPANDFAIL; + *failure_ptr = US"recipient"; + done = FALSE; + } + else +#endif + done = smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n", - transport_rcpt_address(addr, - (addr->transport == NULL)? FALSE : - addr->transport->rcpt_include_affixes)) >= 0 && + rcpt) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); @@ -837,8 +1150,10 @@ else if (done && pm_mailfrom != NULL) { - /*XXX not suitable for cutthrough - sequencing problems */ - cutthrough_delivery= FALSE; + /* 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 = @@ -855,7 +1170,7 @@ else (( smtp_write_command(&outblock, FALSE, - "RCPT TO:\r\n", addr->domain) >= 0 && + "RCPT TO:\r\n", rcpt_domain) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout) ) @@ -906,6 +1221,21 @@ else HDEBUG(D_verify) debug_printf("SMTP timeout\n"); send_quit = FALSE; } +#ifdef SUPPORT_I18N + else if (errno == ERRNO_UTF8_FWD) + { + extern int acl_where; /* src/acl.c */ + errno = 0; + addr->message = string_sprintf( + "response to \"%s\" from %s [%s] did not include SMTPUTF8", + big_buffer, host->name, host->address); + addr->user_message = acl_where == ACL_WHERE_RCPT + ? US"533 mailbox name not allowed" + : US"550 mailbox unavailable"; + yield = FAIL; + done = TRUE; + } +#endif else if (errno == 0) { if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped"); @@ -933,31 +1263,35 @@ else /* End the SMTP conversation and close the connection. */ - /* Cutthrough - on a successfull connect and recipient-verify with use-sender - and we have no cutthrough conn so far + /* Cutthrough - on a successfull 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 + if ( cutthrough.delivery + && rcpt_count == 1 && done && yield == OK && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender && !random_local_part && !pm_mailfrom - && cutthrough_fd < 0 + && cutthrough.fd < 0 + && !lmtp ) { - cutthrough_fd= outblock.sock; /* We assume no buffer in use in the outblock */ - cutthrough_addr = *addr; /* Save the address_item for later logging */ - cutthrough_addr.host_used = store_get(sizeof(host_item)); - cutthrough_addr.host_used->name = host->name; - cutthrough_addr.host_used->address = host->address; - cutthrough_addr.host_used->port = port; + cutthrough.fd = 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; + cutthrough.addr.host_used = &cutthrough.host; if (addr->parent) - *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent; + *(cutthrough.addr.parent = store_get(sizeof(address_item))) = + *addr->parent; ctblock.buffer = ctbuffer; ctblock.buffersize = sizeof(ctbuffer); ctblock.ptr = ctbuffer; /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */ - ctblock.sock = cutthrough_fd; + ctblock.sock = cutthrough.fd; } else { @@ -966,10 +1300,13 @@ else cancel_cutthrough_connection("multiple verify calls"); if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS tls_close(FALSE, TRUE); - #endif +#endif (void)close(inblock.sock); +#ifndef DISABLE_EVENT + (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); +#endif } } /* Loop through all hosts, while !done */ @@ -1068,23 +1405,25 @@ return yield; /* Called after recipient-acl to get a cutthrough connection open when one was requested and a recipient-verify wasn't subsequently done. */ -void +int open_cutthrough_connection( address_item * addr ) { address_item addr2; +int rc; /* Use a recipient-verify-callout to set up the cutthrough connection. */ /* We must use a copy of the address for verification, because it might get rewritten. */ addr2 = *addr; -HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n"); -(void) verify_address(&addr2, NULL, +HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n", + rcpt_count > 1 ? "more" : "start"); +rc= verify_address(&addr2, NULL, vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache, CUTTHROUGH_CMD_TIMEOUT, -1, -1, NULL, NULL, NULL); HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n"); -return; +return rc; } @@ -1093,14 +1432,14 @@ return; static BOOL cutthrough_send(int n) { -if(cutthrough_fd < 0) +if(cutthrough.fd < 0) return TRUE; if( #ifdef SUPPORT_TLS - (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) : + (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) : #endif - send(cutthrough_fd, ctblock.buffer, n, 0) > 0 + send(cutthrough.fd, ctblock.buffer, n, 0) > 0 ) { transport_count += n; @@ -1132,7 +1471,7 @@ return TRUE; BOOL cutthrough_puts(uschar * cp, int n) { -if (cutthrough_fd < 0) return TRUE; +if (cutthrough.fd < 0) return TRUE; if (_cutthrough_puts(cp, n)) return TRUE; cancel_cutthrough_connection("transmit failed"); return FALSE; @@ -1140,7 +1479,7 @@ return FALSE; static BOOL -_cutthrough_flush_send( void ) +_cutthrough_flush_send(void) { int n= ctblock.ptr-ctblock.buffer; @@ -1153,7 +1492,7 @@ return TRUE; /* Send out any bufferred output. Return boolean success. */ BOOL -cutthrough_flush_send( void ) +cutthrough_flush_send(void) { if (_cutthrough_flush_send()) return TRUE; cancel_cutthrough_connection("transmit failed"); @@ -1162,7 +1501,7 @@ return FALSE; BOOL -cutthrough_put_nl( void ) +cutthrough_put_nl(void) { return cutthrough_puts(US"\r\n", 2); } @@ -1180,7 +1519,7 @@ inblock.buffer = inbuffer; inblock.buffersize = sizeof(inbuffer); inblock.ptr = inbuffer; inblock.ptrend = inbuffer; -inblock.sock = cutthrough_fd; +inblock.sock = cutthrough.fd; /* this relies on (inblock.sock == tls_out.active) */ if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT)) cancel_cutthrough_connection("target timeout on read"); @@ -1188,7 +1527,7 @@ if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, if(copy != NULL) { uschar * cp; - *copy= cp= string_copy(responsebuffer); + *copy = cp = string_copy(responsebuffer); /* Trim the trailing end of line */ cp += Ustrlen(responsebuffer); if(cp > *copy && cp[-1] == '\n') *--cp = '\0'; @@ -1201,9 +1540,9 @@ return responsebuffer[0]; /* Negotiate dataphase with the cutthrough target, returning success boolean */ BOOL -cutthrough_predata( void ) +cutthrough_predata(void) { -if(cutthrough_fd < 0) +if(cutthrough.fd < 0) return FALSE; HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n"); @@ -1215,34 +1554,52 @@ return cutthrough_response('3', NULL) == '3'; } +/* fd and use_crlf args only to match write_chunk() */ +static BOOL +cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf) +{ +uschar * s2; +while(s && (s2 = Ustrchr(s, '\n'))) + { + if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl()) + return FALSE; + s = s2+1; + } +return TRUE; +} + + /* Buffered send of headers. Return success boolean. */ /* Expands newlines to wire format (CR,NL). */ /* Also sends header-terminating blank line. */ BOOL -cutthrough_headers_send( void ) +cutthrough_headers_send(void) { -header_line * h; -uschar * cp1, * cp2; - -if(cutthrough_fd < 0) +if(cutthrough.fd < 0) return FALSE; -for(h= header_list; h != NULL; h= h->next) - if(h->type != htype_old && h->text != NULL) - for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1) - if( !cutthrough_puts(cp1, cp2-cp1) - || !cutthrough_put_nl()) - 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"); + +if (!transport_headers_send(&cutthrough.addr, cutthrough.fd, + cutthrough.addr.transport->add_headers, + cutthrough.addr.transport->remove_headers, + &cutthrough_write_chunk, TRUE, + cutthrough.addr.transport->rewrite_rules, + cutthrough.addr.transport->rewrite_existflags)) + return FALSE; -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n"); -return cutthrough_put_nl(); +HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n"); +return TRUE; } static void -close_cutthrough_connection( const char * why ) +close_cutthrough_connection(const char * why) { -if(cutthrough_fd >= 0) +if(cutthrough.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 @@ -1257,18 +1614,18 @@ if(cutthrough_fd >= 0) #ifdef SUPPORT_TLS tls_close(FALSE, TRUE); #endif - (void)close(cutthrough_fd); - cutthrough_fd= -1; + (void)close(cutthrough.fd); + cutthrough.fd = -1; HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why); } ctblock.ptr = ctbuffer; } void -cancel_cutthrough_connection( const char * why ) +cancel_cutthrough_connection(const char * why) { close_cutthrough_connection(why); -cutthrough_delivery= FALSE; +cutthrough.delivery = FALSE; } @@ -1280,33 +1637,45 @@ cutthrough_delivery= FALSE; Return smtp response-class digit. */ uschar * -cutthrough_finaldot( void ) +cutthrough_finaldot(void) { +uschar res; +address_item * addr; HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n"); /* Assume data finshed with new-line */ -if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send()) - return cutthrough_addr.message; +if( !cutthrough_puts(US".", 1) + || !cutthrough_put_nl() + || !cutthrough_flush_send() + ) + return cutthrough.addr.message; -switch(cutthrough_response('2', &cutthrough_addr.message)) +res = cutthrough_response('2', &cutthrough.addr.message); +for (addr = &cutthrough.addr; addr; addr = addr->next) { - case '2': - delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL); - close_cutthrough_connection("delivered"); - break; + addr->message = cutthrough.addr.message; + switch(res) + { + case '2': + delivery_log(LOG_MAIN, addr, (int)'>', NULL); + close_cutthrough_connection("delivered"); + break; - case '4': - delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:"); - break; + case '4': + delivery_log(LOG_MAIN, addr, 0, + US"tmp-reject from cutthrough after DATA:"); + break; - case '5': - delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:"); - break; + case '5': + delivery_log(LOG_MAIN|LOG_REJECT, addr, 0, + US"rejected after DATA:"); + break; - default: - break; + default: + break; + } } - return cutthrough_addr.message; +return cutthrough.addr.message; } @@ -1337,7 +1706,7 @@ if (addr != vaddr) vaddr->user_message = addr->user_message; vaddr->basic_errno = addr->basic_errno; vaddr->more_errno = addr->more_errno; - vaddr->p.address_data = addr->p.address_data; + vaddr->prop.address_data = addr->prop.address_data; copyflag(vaddr, addr, af_pass_message); } return yield; @@ -1525,21 +1894,19 @@ if (address[0] == 0) return OK; /* Flip the legacy TLS-related variables over to the outbound set in case they're used in the context of a transport used by verification. Reset them -at exit from this routine. */ - -modify_variable(US"tls_bits", &tls_out.bits); -modify_variable(US"tls_certificate_verified", &tls_out.certificate_verified); -modify_variable(US"tls_cipher", &tls_out.cipher); -modify_variable(US"tls_peerdn", &tls_out.peerdn); -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) -modify_variable(US"tls_sni", &tls_out.sni); -#endif +at exit from this routine (so no returns allowed from here on). */ + +tls_modify_variables(&tls_out); /* Save a copy of the sender address for re-instating if we change it to <> while verifying a sender address (a nice bit of self-reference there). */ save_sender = sender_address; +/* Observability variable for router/transport use */ + +verify_mode = is_recipient ? US"R" : US"S"; + /* Update the address structure with the possibly qualified and rewritten address. Set it up as the starting address on the chain of new addresses. */ @@ -1555,7 +1922,7 @@ If an address generates more than one child, the loop is used only when full_info is set, and this can only be set locally. Remote enquiries just get information about the top level address, not anything that it generated. */ -while (addr_new != NULL) +while (addr_new) { int rc; address_item *addr = addr_new; @@ -1604,8 +1971,8 @@ while (addr_new != NULL) /* Just in case some router parameter refers to it. */ - return_path = (addr->p.errors_address != NULL)? - addr->p.errors_address : sender_address; + return_path = (addr->prop.errors_address != NULL)? + addr->prop.errors_address : sender_address; /* Split the address into domain and local part, handling the %-hack if necessary, and then route it. While routing a sender address, set @@ -1633,6 +2000,7 @@ while (addr_new != NULL) if (callout > 0) { host_item *host_list = addr->host_list; + transport_instance * tp; /* Make up some data for use in the case where there is no remote transport. */ @@ -1654,9 +2022,9 @@ while (addr_new != NULL) transport's options, so as to mimic what would happen if we were really sending a message to this address. */ - if (addr->transport != NULL && !addr->transport->info->local) + if ((tp = addr->transport) && !tp->info->local) { - (void)(addr->transport->setup)(addr->transport, addr, &tf, 0, 0, NULL); + (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL); /* If the transport has hosts and the router does not, or if the transport is configured to override the router's hosts, we must build a @@ -1665,7 +2033,7 @@ while (addr_new != NULL) if (tf.hosts != NULL && (host_list == NULL || tf.hosts_override)) { uschar *s; - uschar *save_deliver_domain = deliver_domain; + const uschar *save_deliver_domain = deliver_domain; uschar *save_deliver_localpart = deliver_localpart; host_list = NULL; /* Ignore the router's hosts */ @@ -1680,12 +2048,11 @@ while (addr_new != NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts " "\"%s\" in %s transport for callout: %s", tf.hosts, - addr->transport->name, expand_string_message); + tp->name, expand_string_message); } else { int flags; - uschar *canonical_name; host_item *host, *nexthost; host_build_hostlist(&host_list, s, tf.hosts_randomize); @@ -1704,10 +2071,20 @@ while (addr_new != NULL) nexthost = host->next; if (tf.gethostbyname || string_is_ip_address(host->name, NULL) != 0) - (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE); + (void)host_find_byname(host, NULL, flags, NULL, TRUE); else + { + dnssec_domains * dnssec_domains = NULL; + if (Ustrcmp(tp->driver_name, "smtp") == 0) + { + smtp_transport_options_block * ob = + (smtp_transport_options_block *) tp->options_block; + dnssec_domains = &ob->dnssec; + } + (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL, - &canonical_name, NULL); + dnssec_domains, NULL, NULL); + } } } } @@ -1885,7 +2262,7 @@ while (addr_new != NULL) /* If we have carried on to verify a child address, we want the value of $address_data to be that of the child */ - vaddr->p.address_data = addr->p.address_data; + vaddr->prop.address_data = addr->prop.address_data; yield = OK; goto out; } @@ -1908,17 +2285,18 @@ if (allok && addr_local == NULL && addr_remote == NULL) } for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) - { - while (addr_list != NULL) + while (addr_list) { address_item *addr = addr_list; address_item *p = addr->parent; + transport_instance * tp = addr->transport; + addr_list = addr->next; fprintf(f, "%s", CS addr->address); #ifdef EXPERIMENTAL_SRS - if(addr->p.srs_sender) - fprintf(f, " [srs = %s]", addr->p.srs_sender); + if(addr->prop.srs_sender) + fprintf(f, " [srs = %s]", addr->prop.srs_sender); #endif /* If the address is a duplicate, show something about it. */ @@ -1926,80 +2304,63 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) if (!testflag(addr, af_pfr)) { tree_node *tnode; - if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL) + if ((tnode = tree_search(tree_duplicates, addr->unique))) fprintf(f, " [duplicate, would not be delivered]"); else tree_add_duplicate(addr->unique, addr); } /* Now show its parents */ - while (p != NULL) - { + for (p = addr->parent; p; p = p->parent) fprintf(f, "\n <-- %s", p->address); - p = p->parent; - } fprintf(f, "\n "); /* Show router, and transport */ - fprintf(f, "router = %s, ", addr->router->name); - fprintf(f, "transport = %s\n", (addr->transport == NULL)? US"unset" : - addr->transport->name); + fprintf(f, "router = %s, transport = %s\n", + addr->router->name, tp ? tp->name : US"unset"); /* Show any hosts that are set up by a router unless the transport is going to override them; fiddle a bit to get a nice format. */ - if (addr->host_list != NULL && addr->transport != NULL && - !addr->transport->overrides_hosts) + if (addr->host_list && tp && !tp->overrides_hosts) { host_item *h; int maxlen = 0; int maxaddlen = 0; - for (h = addr->host_list; h != NULL; h = h->next) - { + for (h = addr->host_list; h; h = h->next) + { /* get max lengths of host names, addrs */ int len = Ustrlen(h->name); if (len > maxlen) maxlen = len; - len = (h->address != NULL)? Ustrlen(h->address) : 7; + len = h->address ? Ustrlen(h->address) : 7; if (len > maxaddlen) maxaddlen = len; } - for (h = addr->host_list; h != NULL; h = h->next) - { - int len = Ustrlen(h->name); - fprintf(f, " host %s ", h->name); - while (len++ < maxlen) fprintf(f, " "); - if (h->address != NULL) - { - fprintf(f, "[%s] ", h->address); - len = Ustrlen(h->address); - } - else if (!addr->transport->info->local) /* Omit [unknown] for local */ - { - fprintf(f, "[unknown] "); - len = 7; - } - else len = -3; - while (len++ < maxaddlen) fprintf(f," "); - if (h->mx >= 0) fprintf(f, "MX=%d", h->mx); + for (h = addr->host_list; h; h = h->next) + { + fprintf(f, " host %-*s ", maxlen, h->name); + + if (h->address) + fprintf(f, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']'); + else if (tp->info->local) + fprintf(f, " %-*s ", maxaddlen, ""); /* Omit [unknown] for local */ + else + fprintf(f, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']'); + + if (h->mx >= 0) fprintf(f, " MX=%d", h->mx); if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port); - if (h->status == hstatus_unusable) fprintf(f, " ** unusable **"); - fprintf(f, "\n"); + if (running_in_test_harness && h->dnssec == DS_YES) fputs(" AD", f); + if (h->status == hstatus_unusable) fputs(" ** unusable **", f); + fputc('\n', f); } } } - } /* Yield will be DEFER or FAIL if any one address has, only for full_info (which is the -bv or -bt case). */ out: - -modify_variable(US"tls_bits", &tls_in.bits); -modify_variable(US"tls_certificate_verified", &tls_in.certificate_verified); -modify_variable(US"tls_cipher", &tls_in.cipher); -modify_variable(US"tls_peerdn", &tls_in.peerdn); -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) -modify_variable(US"tls_sni", &tls_in.sni); -#endif +verify_mode = NULL; +tls_modify_variables(&tls_in); return yield; } @@ -2107,7 +2468,8 @@ for (h = header_list; h != NULL && yield == OK; h = h->next) verb = US"begins"; } - *msgptr = string_printing( + /* deconst cast ok as we're passing a non-const to string_printing() */ + *msgptr = US string_printing( string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s", errmess, tt - h->text, h->text, verb, len, s)); @@ -2129,6 +2491,41 @@ return yield; } +/************************************************* +* Check header names for 8-bit characters * +*************************************************/ + +/* This function checks for invalid charcters in header names. See +RFC 5322, 2.2. and RFC 6532, 3. + +Arguments: + msgptr where to put an error message + +Returns: OK + FAIL +*/ + +int +verify_check_header_names_ascii(uschar **msgptr) +{ +header_line *h; +uschar *colon, *s; + +for (h = header_list; h != NULL; 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; + } + } + } +return OK; +} /************************************************* * Check for blind recipients * @@ -2499,7 +2896,7 @@ if (ip_bind(sock, host_af, interface_address, 0) < 0) if (ip_connect(sock, host_af, sender_host_address, port, rfc1413_query_timeout) < 0) { - if (errno == ETIMEDOUT && (log_extra_selector & LX_ident_timeout) != 0) + if (errno == ETIMEDOUT && LOGGING(ident_timeout)) { log_write(0, LOG_MAIN, "ident connection to %s timed out", sender_host_address); @@ -2593,9 +2990,9 @@ if (*p == 0) goto END_OFF; /* The rest of the line is the data we want. We turn it into printing characters when we save it, so that it cannot mess up the format of any logging or Received: lines into which it gets inserted. We keep a maximum of 127 -characters. */ +characters. The deconst cast is ok as we fed a nonconst to string_printing() */ -sender_ident = string_printing(string_copyn(p, 127)); +sender_ident = US string_printing(string_copyn(p, 127)); DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident); END_OFF: @@ -2640,7 +3037,7 @@ Returns: OK matched */ int -check_host(void *arg, uschar *ss, uschar **valueptr, uschar **error) +check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error) { check_host_block *cb = (check_host_block *)arg; int mlen = -1; @@ -2648,7 +3045,7 @@ int maskoffset; BOOL iplookup = FALSE; BOOL isquery = FALSE; BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0; -uschar *t; +const uschar *t; uschar *semicolon; uschar **aliases; @@ -2789,9 +3186,9 @@ if (iplookup) /* Now do the actual lookup; note that there is no search_close() because of the caching arrangements. */ - handle = search_open(filename, search_type, 0, NULL, NULL); - if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", - search_error_message); + if (!(handle = search_open(filename, search_type, 0, NULL, NULL))) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message); + result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL); if (valueptr != NULL) *valueptr = result; return (result != NULL)? OK : search_find_defer? DEFER: FAIL; @@ -2829,6 +3226,10 @@ if (*t == 0) h.address = NULL; h.mx = MX_NONE; + /* Using byname rather than bydns here means we cannot determine dnssec + status. On the other hand it is unclear how that could be either + propagated up or enforced. */ + rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) { @@ -2861,7 +3262,7 @@ on spec. */ if ((semicolon = Ustrchr(ss, ';')) != NULL) { - uschar *affix; + const uschar *affix; int partial, affixlen, starflags, id; *semicolon = 0; @@ -2962,12 +3363,12 @@ determined from the IP address, the result is FAIL unless the item "+allow_unknown" was met earlier in the list, in which case OK is returned. */ int -verify_check_this_host(uschar **listptr, unsigned int *cache_bits, - uschar *host_name, uschar *host_address, uschar **valueptr) +verify_check_this_host(const uschar **listptr, unsigned int *cache_bits, + const uschar *host_name, const uschar *host_address, const uschar **valueptr) { int rc; unsigned int *local_cache_bits = cache_bits; -uschar *save_host_address = deliver_host_address; +const uschar *save_host_address = deliver_host_address; check_host_block cb; cb.host_name = host_name; cb.host_address = host_address; @@ -3006,6 +3407,15 @@ return rc; +/************************************************* +* Check the given host item matches a list * +*************************************************/ +int +verify_check_given_host(uschar **listptr, host_item *host) +{ +return verify_check_this_host(CUSS listptr, NULL, host->name, host->address, NULL); +} + /************************************************* * Check the remote host matches a list * *************************************************/ @@ -3025,7 +3435,7 @@ Returns: the yield of verify_check_this_host(), int verify_check_host(uschar **listptr) { -return verify_check_this_host(listptr, sender_host_cache, NULL, +return verify_check_this_host(CUSS listptr, sender_host_cache, NULL, (sender_host_address == NULL)? US"" : sender_host_address, NULL); } @@ -3154,21 +3564,37 @@ if (!string_format(query, sizeof(query), "%s.%s", prepend, domain)) /* Look for this query in the cache. */ -t = tree_search(dnsbl_cache, query); +if ( (t = tree_search(dnsbl_cache, query)) + && (cb = t->data.ptr)->expiry > time(NULL) + ) + +/* Previous lookup was cached */ + + { + HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n"); + } /* If not cached from a previous lookup, we must do a DNS lookup, and cache the result in permanent memory. */ -if (t == NULL) +else { + uint ttl = 3600; + store_pool = POOL_PERM; - /* Set up a tree entry to cache the lookup */ + if (t) + { + HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; "); + } - t = store_get(sizeof(tree_node) + Ustrlen(query)); - Ustrcpy(t->name, query); - t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block)); - (void)tree_insertnode(&dnsbl_cache, t); + else + { /* Set up a tree entry to cache the lookup */ + t = store_get(sizeof(tree_node) + Ustrlen(query)); + Ustrcpy(t->name, query); + t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block)); + (void)tree_insertnode(&dnsbl_cache, t); + } /* Do the DNS loopup . */ @@ -3186,24 +3612,28 @@ if (t == NULL) Quite apart from one A6 RR generating multiple addresses, there are DNS lists that return more than one A record, so we must handle multiple - addresses generated in that way as well. */ + addresses generated in that way as well. + + Mark the cache entry with the "now" plus the minimum of the address TTLs, + or some suitably far-future time if none were found. */ if (cb->rc == DNS_SUCCEED) { dns_record *rr; dns_address **addrp = &(cb->rhs); for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); - rr != NULL; + rr; rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) { if (rr->type == T_A) { dns_address *da = dns_address_from_rr(&dnsa, rr); - if (da != NULL) + if (da) { *addrp = da; while (da->next != NULL) da = da->next; addrp = &(da->next); + if (ttl > rr->ttl) ttl = rr->ttl; } } } @@ -3215,17 +3645,10 @@ if (t == NULL) if (cb->rhs == NULL) cb->rc = DNS_NODATA; } + cb->expiry = time(NULL)+ttl; store_pool = old_pool; } -/* Previous lookup was cached */ - -else - { - HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n"); - cb = t->data.ptr; - } - /* We now have the result of the DNS lookup, either newly done, or cached from a previous call. If the lookup succeeded, check against the address list if there is one. This may be a positive equality list (introduced by @@ -3256,7 +3679,7 @@ if (cb->rc == DNS_SUCCEED) { int ipsep = ','; uschar ip[46]; - uschar *ptr = iplist; + const uschar *ptr = iplist; uschar *res; /* Handle exact matching */ @@ -3449,7 +3872,9 @@ Note: an address for testing DUL is 192.203.178.4 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org Arguments: + where the acl type listptr the domain/address/data list + log_msgptr log message on error Returns: OK successful lookup (i.e. the address is on the list), or lookup deferred after +include_unknown @@ -3459,11 +3884,11 @@ Returns: OK successful lookup (i.e. the address is on the list), or */ int -verify_check_dnsbl(uschar **listptr) +verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr) { int sep = 0; int defer_return = FAIL; -uschar *list = *listptr; +const uschar *list = *listptr; uschar *domain; uschar *s; uschar buffer[1024]; @@ -3475,7 +3900,7 @@ revadd[0] = 0; /* In case this is the first time the DNS resolver is being used. */ -dns_init(FALSE, FALSE); +dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */ /* Loop through all the domains supplied, until something matches */ @@ -3506,21 +3931,19 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL /* See if there's explicit data to be looked up */ - key = Ustrchr(domain, '/'); - if (key != NULL) *key++ = 0; + if ((key = Ustrchr(domain, '/'))) *key++ = 0; /* See if there's a list of addresses supplied after the domain name. This is introduced by an = or a & character; if preceded by = we require all matches and if preceded by ! we invert the result. */ - iplist = Ustrchr(domain, '='); - if (iplist == NULL) + if (!(iplist = Ustrchr(domain, '='))) { bitmask = TRUE; iplist = Ustrchr(domain, '&'); } - if (iplist != NULL) /* Found either = or & */ + if (iplist) /* Found either = or & */ { if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */ { @@ -3539,6 +3962,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL } } + /* If there is a comma in the domain, it indicates that a second domain for looking up TXT records is provided, before the main domain. Otherwise we must set domain_txt == domain. */ @@ -3584,6 +4008,13 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL if (key == NULL) { + if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP) + { + *log_msgptr = string_sprintf + ("cannot test auto-keyed dnslists condition in %s ACL", + acl_wherenames[where]); + return ERROR; + } if (sender_host_address == NULL) return FAIL; /* can never match */ if (revadd[0] == 0) invert_address(revadd, sender_host_address); rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd, @@ -3609,7 +4040,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL uschar keybuffer[256]; uschar keyrevadd[128]; - while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, + while ((keydomain = string_nextinlist(CUSS &key, &keysep, keybuffer, sizeof(keybuffer))) != NULL) { uschar *prepend = keydomain; @@ -3646,4 +4077,6 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL return FAIL; } +/* vi: aw ai sw=2 +*/ /* End of verify.c */