X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fverify.c;h=8564aacc2c5bb6bbef7391f9d8b95da3c62d97ef;hp=ef7ab8d226b2c3d19bdc7f82bb083eb4b3b91d65;hb=a7538db17824b7fd70c12ef7561a67b85d6f247e;hpb=1688f43b3071b3b4d7d3a88a6ccf28c1bc3272e0 diff --git a/src/src/verify.c b/src/src/verify.c index ef7ab8d22..8564aacc2 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/verify.c,v 1.32 2006/02/13 11:13:37 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2006 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with verifying things. The original code for callout @@ -12,6 +10,13 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */ #include "exim.h" +#include "transports/smtp.h" + +#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]; /* Structure for caching DNSBL lookups */ @@ -29,6 +34,12 @@ typedef struct dnsbl_cache_block { static tree_node *dnsbl_cache = NULL; +/* Bits for match_type in one_check_dnsbl() */ + +#define MT_NOT 1 +#define MT_ALL 2 + + /************************************************* * Retrieve a callout cache record * @@ -148,6 +159,7 @@ BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0; BOOL callout_random = (options & vopt_callout_random) != 0; int yield = OK; +int old_domain_cache_result = ccache_accept; BOOL done = FALSE; uschar *address_key; uschar *from_address; @@ -228,10 +240,18 @@ if (dbm_file != NULL) if (cache_record != NULL) { - /* If an early command (up to and including MAIL FROM:<>) was rejected, - there is no point carrying on. The callout fails. */ - - if (cache_record->result == ccache_reject) + /* In most cases, if an early command (up to and including MAIL FROM:<>) + was rejected, there is no point carrying on. The callout fails. However, if + we are doing a recipient verification with use_sender or use_postmaster + set, a previous failure of MAIL FROM:<> doesn't count, because this time we + will be using a non-empty sender. We have to remember this situation so as + not to disturb the cached domain value if this whole verification succeeds + (we don't want it turning into "accept"). */ + + old_domain_cache_result = cache_record->result; + + if (cache_record->result == ccache_reject || + (*from_address == 0 && cache_record->result == ccache_reject_mfnull)) { setflag(addr, af_verify_nsfail); HDEBUG(D_verify) @@ -349,333 +369,664 @@ if (dbm_file != NULL) dbm_file = NULL; } -/* 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, -or unless we have a previously cached negative random result. If we are to test -with a random local part, ensure that such a local part is available. If not, -log the fact, but carry on without randomming. */ - -if (callout_random && callout_random_local_part != NULL) +if (!addr->transport) { - random_local_part = expand_string(callout_random_local_part); - if (random_local_part == NULL) - log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " - "callout_random_local_part: %s", expand_string_message); + 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; -/* Default the connect and overall callout timeouts if not set, and record the -time we are starting so that we can enforce it. */ - -if (callout_overall < 0) callout_overall = 4 * callout; -if (callout_connect < 0) callout_connect = callout; -callout_start_time = time(NULL); - -/* Now make connections to the hosts and do real callouts. The list of hosts -is passed in as an argument. */ + /* 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, + or unless we have a previously cached negative random result. If we are to test + with a random local part, ensure that such a local part is available. If not, + log the fact, but carry on without randomming. */ -for (host = host_list; host != NULL && !done; host = host->next) - { - smtp_inblock inblock; - smtp_outblock outblock; - int host_af; - int port = 25; - BOOL send_quit = TRUE; - uschar *helo = US"HELO"; - uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ - uschar inbuffer[4096]; - uschar outbuffer[1024]; - 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 == NULL) + if (callout_random && callout_random_local_part != NULL) { - DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n", - host->name); - continue; + random_local_part = expand_string(callout_random_local_part); + if (random_local_part == NULL) + log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " + "callout_random_local_part: %s", expand_string_message); } - /* Check the overall callout timeout */ + /* Default the connect and overall callout timeouts if not set, and record the + time we are starting so that we can enforce it. */ - if (time(NULL) - callout_start_time >= callout_overall) - { - HDEBUG(D_verify) debug_printf("overall timeout for callout exceeded\n"); - break; - } + if (callout_overall < 0) callout_overall = 4 * callout; + if (callout_connect < 0) callout_connect = callout; + callout_start_time = time(NULL); - /* Set IPv4 or IPv6 */ + /* Before doing a real callout, if this is an SMTP connection, flush the SMTP + output because a callout might take some time. When PIPELINING is active and + there are many recipients, the total time for doing lots of callouts can add up + and cause the client to time out. So in this case we forgo the PIPELINING + optimization. */ - host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6; + if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush(); - /* 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). - This has to be delayed till now, because they may expand differently for - different hosts. If there's a failure, log it, but carry on with the - defaults. */ + /* Now make connections to the hosts and do real callouts. The list of hosts + is passed in as an argument. */ - deliver_host = host->name; - deliver_host_address = host->address; - deliver_domain = addr->domain; + for (host = host_list; host != NULL && !done; host = host->next) + { + smtp_inblock inblock; + smtp_outblock outblock; + int host_af; + int port = 25; + BOOL send_quit = TRUE; + uschar *active_hostname = smtp_active_hostname; + BOOL lmtp; + BOOL smtps; + BOOL esmtp; + BOOL suppress_tls = FALSE; + uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ + uschar inbuffer[4096]; + uschar outbuffer[1024]; + 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 == NULL) + { + DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n", + host->name); + continue; + } - if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &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); + /* Check the overall callout timeout */ - deliver_host = deliver_host_address = NULL; - deliver_domain = save_deliver_domain; + if (time(NULL) - callout_start_time >= callout_overall) + { + HDEBUG(D_verify) debug_printf("overall timeout for callout exceeded\n"); + break; + } - /* Set HELO string according to the protocol */ + /* Set IPv4 or IPv6 */ - if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO"; + host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6; - HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port); + /* 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). + This has to be delayed till now, because they may expand differently for + different hosts. If there's a failure, log it, but carry on with the + defaults. */ - /* Set up the buffer for reading SMTP response packets. */ + deliver_host = host->name; + deliver_host_address = host->address; + deliver_host_port = host->port; + deliver_domain = addr->domain; - inblock.buffer = inbuffer; - inblock.buffersize = sizeof(inbuffer); - inblock.ptr = inbuffer; - inblock.ptrend = inbuffer; + if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &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); - /* Set up the buffer for holding SMTP commands while pipelining */ + /* Set HELO string according to the protocol */ + lmtp= Ustrcmp(tf->protocol, "lmtp") == 0; + smtps= Ustrcmp(tf->protocol, "smtps") == 0; - outblock.buffer = outbuffer; - outblock.buffersize = sizeof(outbuffer); - outblock.ptr = outbuffer; - outblock.cmd_count = 0; - outblock.authenticating = FALSE; - /* 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. */ + HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port); - inblock.sock = outblock.sock = - smtp_connect(host, host_af, port, interface, callout_connect, TRUE); - if (inblock.sock < 0) - { - addr->message = string_sprintf("could not connect to %s [%s]: %s", - host->name, host->address, strerror(errno)); - continue; - } + /* Set up the buffer for reading SMTP response packets. */ - /* Wait for initial response, and then run the initial SMTP commands. 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. */ + inblock.buffer = inbuffer; + inblock.buffersize = sizeof(inbuffer); + inblock.ptr = inbuffer; + inblock.ptrend = inbuffer; - Ustrcpy(big_buffer, "initial connection"); + /* Set up the buffer for holding SMTP commands while pipelining */ - done = - smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout) && + outblock.buffer = outbuffer; + outblock.buffersize = sizeof(outbuffer); + outblock.ptr = outbuffer; + outblock.cmd_count = 0; + outblock.authenticating = FALSE; - smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo, - smtp_active_hostname) >= 0 && - smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout) && + /* Reset the parameters of a TLS session */ + tls_out.cipher = tls_out.peerdn = NULL; - smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", - from_address) >= 0 && - smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + /* 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. */ - /* If the host gave an initial error, or does not accept HELO or MAIL - FROM:<>, arrange to cache this information, but don't record anything for an - I/O error or a defer. Do not cache rejections when a non-empty sender has - been used, because that blocks the whole domain for all senders. */ + tls_retry_connection: - if (!done) - { - *failure_ptr = US"mail"; - if (errno == 0 && responsebuffer[0] == '5') + inblock.sock = outblock.sock = + smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL +#ifdef EXPERIMENTAL_TPDA + /*XXX tpda action? NULL for now. */ + , NULL +#endif + ); + /* reconsider DSCP here */ + if (inblock.sock < 0) { - setflag(addr, af_verify_nsfail); - if (from_address[0] == 0) new_domain_record.result = ccache_reject; + addr->message = string_sprintf("could not connect to %s [%s]: %s", + host->name, host->address, strerror(errno)); + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; + continue; } - } - /* 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:<>. */ + /* Expand the helo_data string to find the host name to use. */ - else - { - new_domain_record.result = ccache_accept; + if (tf->helo_data != NULL) + { + uschar *s = expand_string(tf->helo_data); + if (s == NULL) + log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's " + "helo_data value for callout: %s", addr->address, + expand_string_message); + else active_hostname = s; + } - /* Do the random local part check first */ + /* 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. */ - if (random_local_part != NULL) + Ustrcpy(big_buffer, "initial connection"); + + /* Unless ssl-on-connect, wait for the initial greeting */ + smtps_redo_greeting: + +#ifdef SUPPORT_TLS + if (!smtps || (smtps && tls_out.active >= 0)) +#endif { - uschar randombuffer[1024]; - BOOL random_ok = - smtp_write_command(&outblock, FALSE, - "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part, - addr->domain) >= 0 && - smtp_read_response(&inblock, randombuffer, - sizeof(randombuffer), '2', callout); + if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))) + goto RESPONSE_FAILED; + +#ifdef EXPERIMENTAL_TPDA + if (tpda_raise_event(addr->transport->tpda_event_action, + US"smtp:connect", responsebuffer) == DEFER) + { + /* Logging? Debug? */ + goto RESPONSE_FAILED; + } +#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)) + DEBUG(D_transport) + debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n"); - /* Remember when we last did a random test */ + tls_redo_helo: - new_domain_record.random_stamp = time(NULL); +#ifdef SUPPORT_TLS + if (smtps && tls_out.active < 0) /* ssl-on-connect, first pass */ + { + tls_offered = TRUE; + ob->tls_tempfail_tryclear = FALSE; + } + else /* all other cases */ +#endif - /* If accepted, we aren't going to do any further tests below. */ + { esmtp_retry: - if (random_ok) + if (!(done= smtp_write_command(&outblock, FALSE, "%s %s\r\n", + !esmtp? "HELO" : lmtp? "LHLO" : "EHLO", active_hostname) >= 0)) + goto SEND_FAILED; + if (!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)) { - new_domain_record.random_result = ccache_accept; + if (errno != 0 || responsebuffer[0] == 0 || lmtp || !esmtp || tls_out.active >= 0) + { + done= FALSE; + goto RESPONSE_FAILED; + } +#ifdef SUPPORT_TLS + tls_offered = FALSE; +#endif + esmtp = FALSE; + goto esmtp_retry; /* fallback to HELO */ } - /* 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. */ + /* 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); - else if (errno == 0) + tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer, + Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0; + } + else + tls_offered = FALSE; +#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 + use a separate buffer for reading the response to STARTTLS so that if it is + negative, the original EHLO data is available for subsequent analysis, should + 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 + ) + { + uschar buffer2[4096]; + if ( !smtps + && !(done= smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") >= 0)) + goto SEND_FAILED; + + /* If there is an I/O error, transmission of this message is deferred. If + there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is + false, we also defer. However, if there is a temporary rejection of STARTTLS + and tls_tempfail_tryclear is true, or if there is an outright rejection of + STARTTLS, we carry on. This means we will try to send the message in clear, + unless the host is in hosts_require_tls (tested below). */ + + if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2', + ob->command_timeout)) { - if (randombuffer[0] == '5') - new_domain_record.random_result = ccache_reject; + if (errno != 0 || buffer2[0] == 0 || + (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)) + { + Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer)); + done= FALSE; + goto RESPONSE_FAILED; + } + } - done = - smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 && - smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout) && + /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */ + else + { + int oldtimeout = ob->command_timeout; + int rc; - smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", - from_address) >= 0 && - smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + ob->command_timeout = callout; + rc = tls_client_start(inblock.sock, host, addr, addr->transport); + 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); +#ifdef EXPERIMENTAL_TPDA + (void) tpda_raise_event(addr->transport->tpda_event_action, + US"tcp:close", NULL); +#endif + 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; + addr->peerdn = tls_out.peerdn; + + /* For SMTPS we need to wait for the initial OK response, then do HELO. */ + if (smtps) + goto smtps_redo_greeting; + + /* For STARTTLS we need to redo EHLO */ + goto tls_redo_helo; } - else done = FALSE; /* Some timeout/connection problem */ - } /* Random check */ + } - /* 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 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) + { + /*save_errno = ERRNO_TLSREQUIRED;*/ + log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s", + host->name, host->address, + tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support"); + done= FALSE; + goto TLS_FAILED; + } + + #endif /*SUPPORT_TLS*/ + + done = TRUE; /* so far so good; have response to HELO */ - if (new_domain_record.random_result != ccache_accept && done) + /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */ + + /* For now, transport_filter by cutthrough-delivery is not supported */ + /* Need proper integration with the proper transport mechanism. */ + if (cutthrough_delivery) { - /* Get the rcpt_include_affixes flag from the transport if there is one, - but assume FALSE if there is not. */ - - 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 && - smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); - - if (done) - new_address_record.result = ccache_accept; - else if (errno == 0 && responsebuffer[0] == '5') + 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) { - *failure_ptr = US"recipient"; - new_address_record.result = ccache_reject; + cutthrough_delivery= FALSE; + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n"); } +#endif + } + + SEND_FAILED: + RESPONSE_FAILED: + TLS_FAILED: + ; + /* Clear down of the TLS, SMTP and TCP layers on error is handled below. */ - /* Do postmaster check if requested; if a full check is required, we - check for RCPT TO: (no domain) in accordance with RFC 821. */ + /* Failure to accept HELO is cached; this blocks the whole domain for all + senders. I/O errors and defer responses are not cached. */ - if (done && pm_mailfrom != NULL) + if (!done) + { + *failure_ptr = US"mail"; /* At or before MAIL */ + if (errno == 0 && responsebuffer[0] == '5') { - done = - smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 && - smtp_read_response(&inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) && + setflag(addr, af_verify_nsfail); + new_domain_record.result = ccache_reject; + } + } - smtp_write_command(&outblock, FALSE, - "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 && - smtp_read_response(&inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) && + /* If we haven't authenticated, but are required to, give up. */ + /* Try to AUTH */ - /* First try using the current domain */ + else done = smtp_auth(responsebuffer, sizeof(responsebuffer), + addr, host, ob, esmtp, &inblock, &outblock) == OK && - (( - smtp_write_command(&outblock, FALSE, - "RCPT TO:\r\n", addr->domain) >= 0 && - smtp_read_response(&inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) - ) + /* 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, "MAIL FROM:<%s>%s\r\n", + from_address, responsebuffer) >= 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 + that blocks the whole domain for all senders. */ + + 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 + { + new_domain_record.result = + (old_domain_cache_result == ccache_reject_mfnull)? + ccache_reject_mfnull: ccache_accept; - /* If that doesn't work, and a full check is requested, - try without the domain. */ + /* Do the random local part check first */ - ( - (options & vopt_callout_fullpm) != 0 && + if (random_local_part != NULL) + { + uschar randombuffer[1024]; + BOOL random_ok = smtp_write_command(&outblock, FALSE, - "RCPT TO:\r\n") >= 0 && - smtp_read_response(&inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) - )); + "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part, + addr->domain) >= 0 && + smtp_read_response(&inblock, randombuffer, + sizeof(randombuffer), '2', callout); + + /* Remember when we last did a random test */ + + new_domain_record.random_stamp = time(NULL); - /* Sort out the cache record */ + /* If accepted, we aren't going to do any further tests below. */ + + if (random_ok) + { + new_domain_record.random_result = ccache_accept; + } - new_domain_record.postmaster_stamp = time(NULL); + /* 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. */ + + else if (errno == 0) + { + if (randombuffer[0] == '5') + new_domain_record.random_result = ccache_reject; + + done = + smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 && + smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), + '2', callout) && + + smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", + from_address) >= 0 && + smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), + '2', callout); + } + else done = FALSE; /* Some timeout/connection problem */ + } /* Random check */ + + /* 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. */ + + 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 && + smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), + '2', callout); if (done) - new_domain_record.postmaster_result = ccache_accept; + new_address_record.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; + *failure_ptr = US"recipient"; + new_address_record.result = ccache_reject; } - } - } /* Random not accepted */ - } /* MAIL FROM: accepted */ - /* 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 + /* Do postmaster check if requested; if a full check is required, we + check for RCPT TO: (no domain) in accordance with RFC 821. */ - Set up different error texts for logging and for sending back to the caller - as an SMTP response. Log in all cases, using a one-line format. For sender - callouts, give a full response to the caller, but for recipient callouts, - don't give the IP address because this may be an internal host whose identity - is not to be widely broadcast. */ + if (done && pm_mailfrom != NULL) + { + /*XXX not suitable for cutthrough - sequencing problems */ + cutthrough_delivery= FALSE; + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n"); - if (!done) - { - if (errno == ETIMEDOUT) - { - HDEBUG(D_verify) debug_printf("SMTP timeout\n"); - send_quit = FALSE; - } - else if (errno == 0) - { - if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped"); + done = + smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 && + smtp_read_response(&inblock, responsebuffer, + sizeof(responsebuffer), '2', callout) && - addr->message = - string_sprintf("response to \"%s\" from %s [%s] was: %s", - big_buffer, host->name, host->address, - string_printing(responsebuffer)); + smtp_write_command(&outblock, FALSE, + "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 && + smtp_read_response(&inblock, responsebuffer, + sizeof(responsebuffer), '2', callout) && - addr->user_message = is_recipient? - string_sprintf("Callout verification failed:\n%s", responsebuffer) - : - string_sprintf("Called: %s\nSent: %s\nResponse: %s", - host->address, big_buffer, responsebuffer); + /* First try using the current domain */ + + (( + smtp_write_command(&outblock, FALSE, + "RCPT TO:\r\n", addr->domain) >= 0 && + smtp_read_response(&inblock, responsebuffer, + sizeof(responsebuffer), '2', callout) + ) + + || - /* Hard rejection ends the process */ + /* If that doesn't work, and a full check is requested, + try without the domain. */ - if (responsebuffer[0] == '5') /* Address rejected */ + ( + (options & vopt_callout_fullpm) != 0 && + smtp_write_command(&outblock, FALSE, + "RCPT TO:\r\n") >= 0 && + smtp_read_response(&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 */ + + /* 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 + + Set up different error texts for logging and for sending back to the caller + as an SMTP response. Log in all cases, using a one-line format. For sender + callouts, give a full response to the caller, but for recipient callouts, + don't give the IP address because this may be an internal host whose identity + is not to be widely broadcast. */ + + if (!done) + { + if (errno == ETIMEDOUT) { - yield = FAIL; - done = TRUE; + HDEBUG(D_verify) debug_printf("SMTP timeout\n"); + send_quit = FALSE; + } + else if (errno == 0) + { + if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped"); + + addr->message = + string_sprintf("response to \"%s\" from %s [%s] was: %s", + big_buffer, host->name, host->address, + string_printing(responsebuffer)); + + addr->user_message = is_recipient? + string_sprintf("Callout verification failed:\n%s", responsebuffer) + : + string_sprintf("Called: %s\nSent: %s\nResponse: %s", + host->address, big_buffer, responsebuffer); + + /* Hard rejection ends the process */ + + if (responsebuffer[0] == '5') /* Address rejected */ + { + yield = FAIL; + done = TRUE; + } } } - } - /* End the SMTP conversation and close the connection. */ + /* 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 + here is where we want to leave the conn open */ + if ( cutthrough_delivery + && done + && yield == OK + && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender + && !random_local_part + && !pm_mailfrom + && cutthrough_fd < 0 + ) + { + 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.next = NULL; + 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; + if (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; + } + else + { + /* Ensure no cutthrough on multiple address verifies */ + if (options & vopt_callout_recipsender) + cancel_cutthrough_connection("multiple verify calls"); + if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); + +#ifdef SUPPORT_TLS + tls_close(FALSE, TRUE); +#endif + (void)close(inblock.sock); +#ifdef EXPERIMENTAL_TPDA + (void) tpda_raise_event(addr->transport->tpda_event_action, + US"tcp:close", NULL); +#endif + } - if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); - (void)close(inblock.sock); - } /* Loop through all hosts, while !done */ + } /* Loop through all hosts, while !done */ + } /* If we get here with done == TRUE, a successful callout happened, and yield will be set OK or FAIL according to the response to the RCPT command. @@ -685,7 +1036,7 @@ However, there may be domain-specific information to cache in both cases. The value of the result field in the new_domain record is ccache_unknown if there was an error before or with MAIL FROM:, and errno was not zero, implying some kind of I/O error. We don't want to write the cache in that case. -Otherwise the value is ccache_accept or ccache_reject. */ +Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */ if (!callout_no_cache && new_domain_record.result != ccache_unknown) { @@ -767,6 +1118,268 @@ 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 +open_cutthrough_connection( address_item * addr ) +{ +address_item addr2; + +/* 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, + 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; +} + + + +/* Send given number of bytes from the buffer */ +static BOOL +cutthrough_send(int n) +{ +if(cutthrough_fd < 0) + return TRUE; + +if( +#ifdef SUPPORT_TLS + (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) : +#endif + send(cutthrough_fd, ctblock.buffer, n, 0) > 0 + ) +{ + transport_count += n; + ctblock.ptr= ctblock.buffer; + return TRUE; +} + +HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno)); +return FALSE; +} + + + +static BOOL +_cutthrough_puts(uschar * cp, int n) +{ +while(n--) + { + if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize) + if(!cutthrough_send(ctblock.buffersize)) + return FALSE; + + *ctblock.ptr++ = *cp++; + } +return TRUE; +} + +/* Buffered output of counted data block. Return boolean success */ +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"); +return FALSE; +} + + +static BOOL +_cutthrough_flush_send( void ) +{ +int n= ctblock.ptr-ctblock.buffer; + +if(n>0) + if(!cutthrough_send(n)) + return FALSE; +return TRUE; +} + + +/* Send out any bufferred output. Return boolean success. */ +BOOL +cutthrough_flush_send( void ) +{ +if (_cutthrough_flush_send()) return TRUE; +cancel_cutthrough_connection("transmit failed"); +return FALSE; +} + + +BOOL +cutthrough_put_nl( void ) +{ +return cutthrough_puts(US"\r\n", 2); +} + + +/* Get and check response from cutthrough target */ +static uschar +cutthrough_response(char expect, uschar ** copy) +{ +smtp_inblock inblock; +uschar inbuffer[4096]; +uschar responsebuffer[4096]; + +inblock.buffer = inbuffer; +inblock.buffersize = sizeof(inbuffer); +inblock.ptr = inbuffer; +inblock.ptrend = inbuffer; +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"); + +if(copy != NULL) + { + uschar * cp; + *copy= cp= string_copy(responsebuffer); + /* Trim the trailing end of line */ + cp += Ustrlen(responsebuffer); + if(cp > *copy && cp[-1] == '\n') *--cp = '\0'; + if(cp > *copy && cp[-1] == '\r') *--cp = '\0'; + } + +return responsebuffer[0]; +} + + +/* Negotiate dataphase with the cutthrough target, returning success boolean */ +BOOL +cutthrough_predata( void ) +{ +if(cutthrough_fd < 0) + return FALSE; + +HDEBUG(D_transport|D_acl|D_v) debug_printf(" 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) == '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 ) +{ +if(cutthrough_fd < 0) + 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_acl) debug_printf("----------- done cutthrough headers send ------------\n"); +return TRUE; +} + + +static void +close_cutthrough_connection( const char * why ) +{ +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 + conn before the final dot. + */ + ctblock.ptr = ctbuffer; + HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n"); + _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */ + _cutthrough_flush_send(); + /* No wait for response */ + + #ifdef SUPPORT_TLS + tls_close(FALSE, TRUE); + #endif + (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 ) +{ +close_cutthrough_connection(why); +cutthrough_delivery= FALSE; +} + + + + +/* Have senders final-dot. Send one to cutthrough target, and grab the response. + Log an OK response as a transmission. + Close the connection. + Return smtp response-class digit. +*/ +uschar * +cutthrough_finaldot( void ) +{ +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; + +switch(cutthrough_response('2', &cutthrough_addr.message)) + { + case '2': + delivery_log(LOG_MAIN, &cutthrough_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 '5': + delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:"); + break; + + default: + break; + } + return cutthrough_addr.message; +} + + + /************************************************* * Copy error to toplevel address * *************************************************/ @@ -793,6 +1406,8 @@ 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; + copyflag(vaddr, addr, af_pass_message); } return yield; } @@ -800,6 +1415,42 @@ return yield; +/************************************************** +* printf that automatically handles TLS if needed * +***************************************************/ + +/* This function is used by verify_address() as a substitute for all fprintf() +calls; a direct fprintf() will not produce output in a TLS SMTP session, such +as a response to an EXPN command. smtp_in.c makes smtp_printf available but +that assumes that we always use the smtp_out FILE* when not using TLS or the +ssl buffer when we are. Instead we take a FILE* parameter and check to see if +that is smtp_out; if so, smtp_printf() with TLS support, otherwise regular +fprintf(). + +Arguments: + f the candidate FILE* to write to + format format string + ... optional arguments + +Returns: + nothing +*/ + +static void PRINTF_FUNCTION(2,3) +respond_printf(FILE *f, const char *format, ...) +{ +va_list ap; + +va_start(ap, format); +if (smtp_out && (f == smtp_out)) + smtp_vprintf(format, ap); +else + vfprintf(f, format, ap); +va_end(ap); +} + + + /************************************************* * Verify an email address * *************************************************/ @@ -899,8 +1550,8 @@ if (parse_find_at(address) == NULL) if ((options & vopt_qualify) == 0) { if (f != NULL) - fprintf(f, "%sA domain is required for \"%s\"%s\n", ko_prefix, address, - cr); + respond_printf(f, "%sA domain is required for \"%s\"%s\n", + ko_prefix, address, cr); *failure_ptr = US"qualify"; return FAIL; } @@ -941,6 +1592,12 @@ addresses, such rewriting fails. */ 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. */ + +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). */ @@ -1040,10 +1697,21 @@ while (addr_new != NULL) { host_item *host_list = addr->host_list; - /* Default, if no remote transport, to NULL for the interface (=> any), - "smtp" for the port, and "smtp" for the protocol. */ - - transport_feedback tf = { NULL, US"smtp", US"smtp", NULL, FALSE, FALSE }; + /* Make up some data for use in the case where there is no remote + transport. */ + + transport_feedback tf = { + NULL, /* interface (=> any) */ + US"smtp", /* port */ + US"smtp", /* protocol */ + NULL, /* hosts */ + US"$smtp_active_hostname", /* helo_data */ + FALSE, /* hosts_override */ + FALSE, /* hosts_randomize */ + FALSE, /* gethostbyname */ + TRUE, /* qualify_single */ + FALSE /* search_parents */ + }; /* If verification yielded a remote transport, we want to use that transport's options, so as to mimic what would happen if we were really @@ -1051,7 +1719,7 @@ while (addr_new != NULL) if (addr->transport != NULL && !addr->transport->info->local) { - (void)(addr->transport->setup)(addr->transport, addr, &tf, NULL); + (void)(addr->transport->setup)(addr->transport, 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 @@ -1079,6 +1747,7 @@ while (addr_new != NULL) } else { + int flags; uschar *canonical_name; host_item *host, *nexthost; host_build_hostlist(&host_list, s, tf.hosts_randomize); @@ -1089,20 +1758,31 @@ while (addr_new != NULL) additional host items being inserted into the chain. Hence we must save the next host first. */ + flags = HOST_FIND_BY_A; + if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; + if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; + for (host = host_list; host != NULL; host = nexthost) { nexthost = host->next; if (tf.gethostbyname || string_is_ip_address(host->name, NULL) != 0) - (void)host_find_byname(host, NULL, &canonical_name, TRUE); + (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE); else - { - int flags = HOST_FIND_BY_A; - if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; - if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; + { + uschar * d_request = NULL, * d_require = NULL; + if (Ustrcmp(addr->transport->driver_name, "smtp") == 0) + { + smtp_transport_options_block * ob = + (smtp_transport_options_block *) + addr->transport->options_block; + d_request = ob->dnssec_request_domains; + d_require = ob->dnssec_require_domains; + } + (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL, - &canonical_name, NULL); - } + d_request, d_require, &canonical_name, NULL); + } } } } @@ -1122,6 +1802,9 @@ while (addr_new != NULL) } else { +#ifdef SUPPORT_TLS + deliver_set_expansions(addr); +#endif rc = do_callout(addr, host_list, &tf, callout, callout_overall, callout_connect, options, se_mailfrom, pm_mailfrom); } @@ -1151,20 +1834,36 @@ while (addr_new != NULL) allok = FALSE; if (f != NULL) { - fprintf(f, "%s%s %s", ko_prefix, address, + address_item *p = addr->parent; + + respond_printf(f, "%s%s %s", ko_prefix, + full_info? addr->address : address, address_test_mode? "is undeliverable" : "failed to verify"); if (!expn && admin_user) { if (addr->basic_errno > 0) - fprintf(f, ": %s", strerror(addr->basic_errno)); + respond_printf(f, ": %s", strerror(addr->basic_errno)); if (addr->message != NULL) - fprintf(f, ":\n %s", addr->message); + respond_printf(f, ": %s", addr->message); + } + + /* Show parents iff doing full info */ + + if (full_info) while (p != NULL) + { + respond_printf(f, "%s\n <-- %s", cr, p->address); + p = p->parent; } - fprintf(f, "%s\n", cr); + respond_printf(f, "%s\n", cr); } + cancel_cutthrough_connection("routing hard fail"); - if (!full_info) return copy_error(vaddr, addr, FAIL); - else yield = FAIL; + if (!full_info) + { + yield = copy_error(vaddr, addr, FAIL); + goto out; + } + else yield = FAIL; } /* Soft failure */ @@ -1174,25 +1873,40 @@ while (addr_new != NULL) allok = FALSE; if (f != NULL) { - fprintf(f, "%s%s cannot be resolved at this time", ko_prefix, address); + address_item *p = addr->parent; + respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix, + full_info? addr->address : address); if (!expn && admin_user) { if (addr->basic_errno > 0) - fprintf(f, ":\n %s", strerror(addr->basic_errno)); + respond_printf(f, ": %s", strerror(addr->basic_errno)); if (addr->message != NULL) - fprintf(f, ":\n %s", addr->message); + respond_printf(f, ": %s", addr->message); else if (addr->basic_errno <= 0) - fprintf(f, ":\n unknown error"); + respond_printf(f, ": unknown error"); } - fprintf(f, "%s\n", cr); + /* Show parents iff doing full info */ + + if (full_info) while (p != NULL) + { + respond_printf(f, "%s\n <-- %s", cr, p->address); + p = p->parent; + } + respond_printf(f, "%s\n", cr); } - if (!full_info) return copy_error(vaddr, addr, DEFER); - else if (yield == OK) yield = DEFER; + cancel_cutthrough_connection("routing soft fail"); + + if (!full_info) + { + yield = copy_error(vaddr, addr, DEFER); + goto out; + } + else if (yield == OK) yield = DEFER; } /* If we are handling EXPN, we do not want to continue to route beyond - the top level. */ + the top level (whose address is in "address"). */ else if (expn) { @@ -1200,18 +1914,19 @@ while (addr_new != NULL) if (addr_new == NULL) { if (addr_local == NULL && addr_remote == NULL) - fprintf(f, "250 mail to <%s> is discarded\r\n", address); + respond_printf(f, "250 mail to <%s> is discarded\r\n", address); else - fprintf(f, "250 <%s>\r\n", address); + respond_printf(f, "250 <%s>\r\n", address); } else while (addr_new != NULL) { address_item *addr2 = addr_new; addr_new = addr2->next; if (addr_new == NULL) ok_prefix = US"250 "; - fprintf(f, "%s<%s>\r\n", ok_prefix, addr2->address); + respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address); } - return OK; + yield = OK; + goto out; } /* Successful routing other than EXPN. */ @@ -1246,7 +1961,8 @@ while (addr_new != NULL) of $address_data to be that of the child */ vaddr->p.address_data = addr->p.address_data; - return OK; + yield = OK; + goto out; } } } /* Loop for generated addresses */ @@ -1261,9 +1977,12 @@ or autoreplies, and there were no errors or deferments, the message is to be discarded, usually because of the use of :blackhole: in an alias file. */ if (allok && addr_local == NULL && addr_remote == NULL) + { fprintf(f, "mail to %s is discarded\n", address); + goto out; + } -else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) +for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) { while (addr_list != NULL) { @@ -1276,6 +1995,19 @@ else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) if(addr->p.srs_sender) fprintf(f, " [srs = %s]", addr->p.srs_sender); #endif + + /* If the address is a duplicate, show something about it. */ + + if (!testflag(addr, af_pfr)) + { + tree_node *tnode; + if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL) + fprintf(f, " [duplicate, would not be delivered]"); + else tree_add_duplicate(addr->unique, addr); + } + + /* Now show its parents */ + while (p != NULL) { fprintf(f, "\n <-- %s", p->address); @@ -1331,9 +2063,12 @@ else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) } } -/* Will be DEFER or FAIL if any one address has, only for full_info (which is +/* Yield will be DEFER or FAIL if any one address has, only for full_info (which is the -bv or -bt case). */ +out: +tls_modify_variables(&tls_in); + return yield; } @@ -1359,8 +2094,9 @@ verify_check_headers(uschar **msgptr) { header_line *h; uschar *colon, *s; +int yield = OK; -for (h = header_list; h != NULL; h = h->next) +for (h = header_list; h != NULL && yield == OK; h = h->next) { if (h->type != htype_from && h->type != htype_reply_to && @@ -1374,9 +2110,10 @@ for (h = header_list; h != NULL; h = h->next) s = colon + 1; while (isspace(*s)) s++; - parse_allow_group = TRUE; /* Allow group syntax */ + /* Loop for multiple addresses in the header, enabling group syntax. Note + that we have to reset this after the header has been scanned. */ - /* Loop for multiple addresses in the header */ + parse_allow_group = TRUE; while (*s != 0) { @@ -1386,7 +2123,7 @@ for (h = header_list; h != NULL; h = h->next) int start, end, domain; /* Temporarily terminate the string at this point, and extract the - operative address within. */ + operative address within, allowing group syntax. */ *ss = 0; recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); @@ -1415,14 +2152,16 @@ for (h = header_list; h != NULL; h = h->next) { uschar *verb = US"is"; uschar *t = ss; + uschar *tt = colon; int len; /* Arrange not to include any white space at the end in the - error message. */ + error message or the header name. */ while (t > s && isspace(t[-1])) t--; + while (tt > h->text && isspace(tt[-1])) tt--; - /* Add the address which failed to the error message, since in a + /* Add the address that failed to the error message, since in a header with very many addresses it is sometimes hard to spot which one is at fault. However, limit the amount of address to quote - cases have been seen where, for example, a missing double @@ -1437,10 +2176,11 @@ for (h = header_list; h != NULL; h = h->next) } *msgptr = string_printing( - string_sprintf("%s: failing address in \"%.*s\" header %s: %.*s", - errmess, colon - h->text, h->text, verb, len, s)); + string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s", + errmess, tt - h->text, h->text, verb, len, s)); - return FAIL; + yield = FAIL; + break; /* Out of address loop */ } /* Advance to the next address */ @@ -1448,12 +2188,50 @@ for (h = header_list; h != NULL; h = h->next) s = ss + (terminator? 1:0); while (isspace(*s)) s++; } /* Next address */ - } /* Next header */ -return OK; + parse_allow_group = FALSE; + parse_found_group = FALSE; + } /* Next header unless yield has been set FALSE */ + +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 * @@ -1493,9 +2271,10 @@ for (i = 0; i < recipients_count; i++) s = colon + 1; while (isspace(*s)) s++; - parse_allow_group = TRUE; /* Allow group syntax */ + /* Loop for multiple addresses in the header, enabling group syntax. Note + that we have to reset this after the header has been scanned. */ - /* Loop for multiple addresses in the header */ + parse_allow_group = TRUE; while (*s != 0) { @@ -1505,7 +2284,7 @@ for (i = 0; i < recipients_count; i++) int start, end, domain; /* Temporarily terminate the string at this point, and extract the - operative address within. */ + operative address within, allowing group syntax. */ *ss = 0; recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); @@ -1529,6 +2308,9 @@ for (i = 0; i < recipients_count; i++) s = ss + (terminator? 1:0); while (isspace(*s)) s++; } /* Next address */ + + parse_allow_group = FALSE; + parse_found_group = FALSE; } /* Next header (if found is false) */ if (!found) return FAIL; @@ -1611,13 +2393,14 @@ verify_check_header_address(uschar **user_msgptr, uschar **log_msgptr, uschar *pm_mailfrom, int options, int *verrno) { static int header_types[] = { htype_sender, htype_reply_to, htype_from }; +BOOL done = FALSE; int yield = FAIL; int i; -for (i = 0; i < 3; i++) +for (i = 0; i < 3 && !done; i++) { header_line *h; - for (h = header_list; h != NULL; h = h->next) + for (h = header_list; h != NULL && !done; h = h->next) { int terminator, new_ok; uschar *s, *ss, *endname; @@ -1625,6 +2408,11 @@ for (i = 0; i < 3; i++) if (h->type != header_types[i]) continue; s = endname = Ustrchr(h->text, ':') + 1; + /* Scan the addresses in the header, enabling group syntax. Note that we + have to reset this after the header has been scanned. */ + + parse_allow_group = TRUE; + while (*s != 0) { address_item *vaddr; @@ -1667,11 +2455,21 @@ for (i = 0; i < 3; i++) else { int start, end, domain; - uschar *address = parse_extract_address(s, log_msgptr, &start, - &end, &domain, FALSE); + uschar *address = parse_extract_address(s, log_msgptr, &start, &end, + &domain, FALSE); *ss = terminator; + /* If we found an empty address, just carry on with the next one, but + kill the message. */ + + if (address == NULL && Ustrcmp(*log_msgptr, "empty address") == 0) + { + *log_msgptr = NULL; + s = ss; + continue; + } + /* If verification failed because of a syntax error, fail this function, and ensure that the failing address gets added to the error message. */ @@ -1679,14 +2477,13 @@ for (i = 0; i < 3; i++) if (address == NULL) { new_ok = FAIL; - if (*log_msgptr != NULL) - { - while (ss > s && isspace(ss[-1])) ss--; - *log_msgptr = string_sprintf("syntax error in '%.*s' header when " - "scanning for sender: %s in \"%.*s\"", - endname - h->text, h->text, *log_msgptr, ss - s, s); - return FAIL; - } + while (ss > s && isspace(ss[-1])) ss--; + *log_msgptr = string_sprintf("syntax error in '%.*s' header when " + "scanning for sender: %s in \"%.*s\"", + endname - h->text, h->text, *log_msgptr, ss - s, s); + yield = FAIL; + done = TRUE; + break; } /* Else go ahead with the sender verification. But it isn't *the* @@ -1720,15 +2517,24 @@ for (i = 0; i < 3; i++) /* Success or defer */ - if (new_ok == OK) return OK; + if (new_ok == OK) + { + yield = OK; + done = TRUE; + break; + } + if (new_ok == DEFER) yield = DEFER; /* Move on to any more addresses in the header */ s = ss; - } - } - } + } /* Next address */ + + parse_allow_group = FALSE; + parse_found_group = FALSE; + } /* Next header, unless done */ + } /* Next header type unless done */ if (yield == FAIL && *log_msgptr == NULL) *log_msgptr = US"there is no valid sender in any header line"; @@ -2056,7 +2862,8 @@ if (iplookup) name, we have to fish the file off the start of the query. For a single-key lookup, the key is the current IP address, masked appropriately, and reconverted to text form, with the mask appended. For IPv6 addresses, specify - dot separators instead of colons. */ + dot separators instead of colons, except when the lookup type is "iplsearch". + */ if (mac_islookup(search_type, lookup_absfilequery)) { @@ -2071,11 +2878,13 @@ if (iplookup) filename = NULL; key = semicolon + 1; } - else + else /* Single-key style */ { + int sep = (Ustrcmp(lookup_list[search_type]->name, "iplsearch") == 0)? + ':' : '.'; insize = host_aton(cb->host_address, incoming); host_mask(insize, incoming, mlen); - (void)host_nmtoa(insize, incoming, mlen, buffer, '.'); + (void)host_nmtoa(insize, incoming, mlen, buffer, sep); key = buffer; filename = semicolon + 1; } @@ -2122,7 +2931,8 @@ if (*t == 0) h.name = ss; h.address = NULL; h.mx = MX_NONE; - rc = host_find_byname(&h, NULL, NULL, FALSE); + + rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) { host_item *hh; @@ -2327,16 +3137,18 @@ return verify_check_this_host(listptr, sender_host_cache, NULL, /************************************************* -* Invert an IP address for a DNS black list * +* Invert an IP address * *************************************************/ -/* +/* Originally just used for DNS xBL lists, now also used for the +reverse_ip expansion operator. + Arguments: buffer where to put the answer address the address to invert */ -static void +void invert_address(uschar *buffer, uschar *address) { int bin[4]; @@ -2382,6 +3194,12 @@ else } } #endif + +/* Remove trailing period -- this is needed so that both arbitrary +dnsbl keydomains and inverted addresses may be combined with the +same format string, "%s.%s" */ + +*(--bptr) = 0; } @@ -2390,15 +3208,26 @@ else * Perform a single dnsbl lookup * *************************************************/ -/* This function is called from verify_check_dnsbl() below. +/* This function is called from verify_check_dnsbl() below. It is also called +recursively from within itself when domain and domain_txt are different +pointers, in order to get the TXT record from the alternate domain. Arguments: - domain the outer dnsbl domain (for debug message) + domain the outer dnsbl domain + domain_txt alternate domain to lookup TXT record on success; when the + same domain is to be used, domain_txt == domain (that is, + the pointers must be identical, not just the text) keydomain the current keydomain (for debug message) - query the domain to be looked up - iplist the list of matching IP addresses + prepend subdomain to lookup (like keydomain, but + reversed if IP address) + iplist the list of matching IP addresses, or NULL for "any" bitmask true if bitmask matching is wanted - invert_result true if result to be inverted + match_type condition for 'succeed' result + 0 => Any RR in iplist (=) + 1 => No RR in iplist (!=) + 2 => All RRs in iplist (==) + 3 => Some RRs not in iplist (!==) + the two bits are defined as MT_NOT and MT_ALL defer_return what to return for a defer Returns: OK if lookup succeeded @@ -2406,14 +3235,25 @@ Returns: OK if lookup succeeded */ static int -one_check_dnsbl(uschar *domain, uschar *keydomain, uschar *query, - uschar *iplist, BOOL bitmask, BOOL invert_result, int defer_return) +one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain, + uschar *prepend, uschar *iplist, BOOL bitmask, int match_type, + int defer_return) { dns_answer dnsa; dns_scan dnss; tree_node *t; dnsbl_cache_block *cb; int old_pool = store_pool; +uschar query[256]; /* DNS domain max length */ + +/* Construct the specific query domainname */ + +if (!string_format(query, sizeof(query), "%s.%s", prepend, domain)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " + "(ignored): %s...", query); + return FAIL; + } /* Look for this query in the cache. */ @@ -2515,21 +3355,25 @@ if (cb->rc == DNS_SUCCEED) if (iplist != NULL) { - int ipsep = ','; - uschar ip[46]; - uschar *ptr = iplist; - - while (string_nextinlist(&ptr, &ipsep, ip, sizeof(ip)) != NULL) + for (da = cb->rhs; da != NULL; da = da->next) { + int ipsep = ','; + uschar ip[46]; + uschar *ptr = iplist; + uschar *res; + /* Handle exact matching */ + if (!bitmask) { - for (da = cb->rhs; da != NULL; da = da->next) + while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL) { if (Ustrcmp(CS da->address, ip) == 0) break; } } + /* Handle bitmask matching */ + else { int address[4]; @@ -2542,44 +3386,77 @@ if (cb->rc == DNS_SUCCEED) ignore IPv6 addresses. The default mask is 0, which always matches. We change this only for IPv4 addresses in the list. */ - if (host_aton(ip, address) == 1) mask = address[0]; + if (host_aton(da->address, address) == 1) mask = address[0]; /* Scan the returned addresses, skipping any that are IPv6 */ - for (da = cb->rhs; da != NULL; da = da->next) + while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL) { - if (host_aton(da->address, address) != 1) continue; - if ((address[0] & mask) == mask) break; + if (host_aton(ip, address) != 1) continue; + if ((address[0] & mask) == address[0]) break; } } - /* Break out if a match has been found */ + /* If either + + (a) An IP address in an any ('=') list matched, or + (b) No IP address in an all ('==') list matched + + then we're done searching. */ - if (da != NULL) break; + if (((match_type & MT_ALL) != 0) == (res == NULL)) break; } - /* If either + /* If da == NULL, either - (a) No IP address in a positive list matched, or - (b) An IP address in a negative list did match + (a) No IP address in an any ('=') list matched, or + (b) An IP address in an all ('==') list didn't match - then behave as if the DNSBL lookup had not succeeded, i.e. the host is - not on the list. */ + so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on + the list. */ - if (invert_result != (da == NULL)) + if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL)) { HDEBUG(D_dnsbl) { + uschar *res = NULL; + switch(match_type) + { + case 0: + res = US"was no match"; + break; + case MT_NOT: + res = US"was an exclude match"; + break; + case MT_ALL: + res = US"was an IP address that did not match"; + break; + case MT_NOT|MT_ALL: + res = US"were no IP addresses that did not match"; + break; + } debug_printf("=> but we are not accepting this block class because\n"); - debug_printf("=> there was %s match for %c%s\n", - invert_result? "an exclude":"no", bitmask? '&' : '=', iplist); + debug_printf("=> there %s for %s%c%s\n", + res, + ((match_type & MT_ALL) == 0)? "" : "=", + bitmask? '&' : '=', iplist); } return FAIL; } } - /* Either there was no IP list, or the record matched. Look up a TXT record - if it hasn't previously been done. */ + /* Either there was no IP list, or the record matched, implying that the + domain is on the list. We now want to find a corresponding TXT record. If an + alternate domain is specified for the TXT record, call this function + recursively to look that up; this has the side effect of re-checking that + there is indeed an A record at the alternate domain. */ + + if (domain_txt != domain) + return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL, + FALSE, match_type, defer_return); + + /* If there is no alternate domain, look up a TXT record in the main domain + if it has not previously been cached. */ if (!cb->text_set) { @@ -2650,7 +3527,7 @@ given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2. If no key is given, what is looked up in the domain is the inverted IP address of the current client host. If a key is given, it is used to construct the -domain for the lookup. For example, +domain for the lookup. For example: dsn.rfc-ignorant.org/$sender_address_domain @@ -2659,6 +3536,17 @@ then we check for a TXT record for an error message, and if found, save its value in $dnslist_text. We also cache everything in a tree, to optimize multiple lookups. +The TXT record is normally looked up in the same domain as the A record, but +when many lists are combined in a single DNS domain, this will not be a very +specific message. It is possible to specify a different domain for looking up +TXT records; this is given before the main domain, comma-separated. For +example: + + dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \ + socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 + +The caching ensures that only one lookup in dnsbl.sorbs.net is done. + Note: an address for testing RBL is 192.203.178.39 Note: an address for testing DUL is 192.203.178.4 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org @@ -2678,12 +3566,10 @@ verify_check_dnsbl(uschar **listptr) { int sep = 0; int defer_return = FAIL; -BOOL invert_result = FALSE; uschar *list = *listptr; uschar *domain; uschar *s; uschar buffer[1024]; -uschar query[256]; /* DNS domain max length */ uschar revadd[128]; /* Long enough for IPv6 address */ /* Indicate that the inverted IP address is not yet set up */ @@ -2692,15 +3578,17 @@ 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 */ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int rc; - BOOL frc; BOOL bitmask = FALSE; + int match_type = 0; + uschar *domain_txt; + uschar *comma; uschar *iplist; uschar *key; @@ -2725,8 +3613,8 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL if (key != NULL) *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 invert the result. - */ + 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) @@ -2735,14 +3623,35 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL iplist = Ustrchr(domain, '&'); } - if (iplist != NULL) + if (iplist != NULL) /* Found either = or & */ { - if (iplist > domain && iplist[-1] == '!') + if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */ { - invert_result = TRUE; + match_type |= MT_NOT; iplist[-1] = 0; } - *iplist++ = 0; + + *iplist++ = 0; /* Terminate domain, move on */ + + /* If we found = (bitmask == FALSE), check for == or =& */ + + if (!bitmask && (*iplist == '=' || *iplist == '&')) + { + bitmask = *iplist++ == '&'; + match_type |= MT_ALL; + } + } + + /* 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. */ + + domain_txt = domain; + comma = Ustrchr(domain, ','); + if (comma != NULL) + { + *comma++ = 0; + domain = comma; } /* Check that what we have left is a sensible domain name. There is no reason @@ -2753,7 +3662,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL for (s = domain; *s != 0; s++) { - if (!isalnum(*s) && *s != '-' && *s != '.') + if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') { log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " "strange characters - is this right?", domain); @@ -2761,6 +3670,18 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL } } + /* Check the alternate domain if present */ + + if (domain_txt != domain) for (s = domain_txt; *s != 0; s++) + { + if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') + { + log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " + "strange characters - is this right?", domain_txt); + break; + } + } + /* If there is no key string, construct the query by adding the domain name onto the inverted host address, and perform a single DNS lookup. */ @@ -2768,25 +3689,15 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL { if (sender_host_address == NULL) return FAIL; /* can never match */ if (revadd[0] == 0) invert_address(revadd, sender_host_address); - frc = string_format(query, sizeof(query), "%s%s", revadd, domain); - - if (!frc) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " - "(ignored): %s...", query); - continue; - } - - rc = one_check_dnsbl(domain, sender_host_address, query, iplist, bitmask, - invert_result, defer_return); - + rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd, + iplist, bitmask, match_type, defer_return); if (rc == OK) { - dnslist_domain = string_copy(domain); + dnslist_domain = string_copy(domain_txt); + dnslist_matched = string_copy(sender_host_address); HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - sender_host_address, domain); + sender_host_address, dnslist_domain); } - if (rc != FAIL) return rc; /* OK or DEFER */ } @@ -2799,36 +3710,28 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL BOOL defer = FALSE; uschar *keydomain; uschar keybuffer[256]; + uschar keyrevadd[128]; while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, sizeof(keybuffer))) != NULL) { + uschar *prepend = keydomain; + if (string_is_ip_address(keydomain, NULL) != 0) { - uschar keyrevadd[128]; invert_address(keyrevadd, keydomain); - frc = string_format(query, sizeof(query), "%s%s", keyrevadd, domain); - } - else - { - frc = string_format(query, sizeof(query), "%s.%s", keydomain, domain); - } - - if (!frc) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " - "(ignored): %s...", query); - continue; + prepend = keyrevadd; } - rc = one_check_dnsbl(domain, keydomain, query, iplist, bitmask, - invert_result, defer_return); + rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist, + bitmask, match_type, defer_return); if (rc == OK) { - dnslist_domain = string_copy(domain); + dnslist_domain = string_copy(domain_txt); + dnslist_matched = string_copy(keydomain); HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - keydomain, domain); + keydomain, dnslist_domain); return OK; } @@ -2846,4 +3749,6 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL return FAIL; } +/* vi: aw ai sw=2 +*/ /* End of verify.c */