-/* $Cambridge: exim/src/src/verify.c,v 1.42 2006/10/09 14:36:25 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with verifying things. The original code for callout
#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 */
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 *
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
+ {
+ 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 *active_hostname = smtp_active_hostname;
- 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 */
- /* Expand the helo_data string to find the host name to use. */
+ if (time(NULL) - callout_start_time >= callout_overall)
+ {
+ HDEBUG(D_verify) debug_printf("overall timeout for callout exceeded\n");
+ break;
+ }
- if (tf->helo_data != NULL)
- {
- uschar *s = expand_string(tf->helo_data);
- if (active_hostname == NULL)
- log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
- "helo_data value for callout: %s", expand_string_message);
- else active_hostname = s;
- }
+ /* Set IPv4 or IPv6 */
- deliver_host = deliver_host_address = NULL;
- deliver_domain = save_deliver_domain;
+ host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
- /* Set HELO string according to the protocol */
+ /* 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. */
- if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO";
+ deliver_host = host->name;
+ deliver_host_address = host->address;
+ deliver_domain = addr->domain;
- HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
+ 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 reading SMTP response packets. */
+ /* Set HELO string according to the protocol */
+ lmtp= Ustrcmp(tf->protocol, "lmtp") == 0;
+ smtps= Ustrcmp(tf->protocol, "smtps") == 0;
- inblock.buffer = inbuffer;
- inblock.buffersize = sizeof(inbuffer);
- inblock.ptr = inbuffer;
- inblock.ptrend = inbuffer;
- /* Set up the buffer for holding SMTP commands while pipelining */
+ HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
- outblock.buffer = outbuffer;
- outblock.buffersize = sizeof(outbuffer);
- outblock.ptr = outbuffer;
- outblock.cmd_count = 0;
- outblock.authenticating = FALSE;
+ /* Set up the buffer for reading SMTP response packets. */
- /* 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. */
+ inblock.buffer = inbuffer;
+ inblock.buffersize = sizeof(inbuffer);
+ inblock.ptr = inbuffer;
+ inblock.ptrend = inbuffer;
- 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 holding SMTP commands while pipelining */
- /* 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. */
+ outblock.buffer = outbuffer;
+ outblock.buffersize = sizeof(outbuffer);
+ outblock.ptr = outbuffer;
+ outblock.cmd_count = 0;
+ outblock.authenticating = FALSE;
- Ustrcpy(big_buffer, "initial connection");
+ /* Reset the parameters of a TLS session */
+ tls_out.cipher = tls_out.peerdn = NULL;
- done =
- smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
- '2', callout) &&
- smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo,
- active_hostname) >= 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. */
- /* Failure to accept HELO is cached; this blocks the whole domain for all
- senders. I/O errors and defer responses are not cached. */
+ tls_retry_connection:
- if (!done)
- {
- *failure_ptr = US"mail"; /* At or before MAIL */
- if (errno == 0 && responsebuffer[0] == '5')
+ inblock.sock = outblock.sock =
+ smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
+ /* reconsider DSCP here */
+ if (inblock.sock < 0)
{
- setflag(addr, af_verify_nsfail);
- 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;
}
- }
-
- /* Send the MAIL command */
- else done =
- smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
- from_address) >= 0 &&
- smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
- '2', callout);
+ /* Expand the helo_data string to find the host name to use. */
- /* 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')
+ if (tf->helo_data != NULL)
{
- setflag(addr, af_verify_nsfail);
- if (from_address[0] == 0)
- new_domain_record.result = ccache_reject_mfnull;
+ 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;
}
- }
- /* 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:<>.
+ deliver_host = deliver_host_address = NULL;
+ deliver_domain = save_deliver_domain;
- 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. */
+ /* 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. */
- else
- {
- new_domain_record.result =
- (old_domain_cache_result == ccache_reject_mfnull)?
- ccache_reject_mfnull: ccache_accept;
+ Ustrcpy(big_buffer, "initial connection");
- /* Do the random local part check first */
+ /* Unless ssl-on-connect, wait for the initial greeting */
+ smtps_redo_greeting:
- if (random_local_part != NULL)
- {
- 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);
+ #ifdef SUPPORT_TLS
+ if (!smtps || (smtps && tls_out.active >= 0))
+ #endif
+ if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
+ goto RESPONSE_FAILED;
+
+ /* 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 rc = tls_client_start(inblock.sock, host, addr,
+ NULL, /* No DH param */
+ ob->tls_certificate, ob->tls_privatekey,
+ ob->tls_sni,
+ ob->tls_verify_certificates, ob->tls_crl,
+ ob->tls_require_ciphers, 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. */
+ 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;
+ }
+
+ /* 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;
+ }
+ }
- smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
- from_address) >= 0 &&
- smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
- '2', callout);
+ /* 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;
}
- 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. */
+ #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 */
+ /* Need proper integration with the proper transport mechanism. */
+
+
+ SEND_FAILED:
+ RESPONSE_FAILED:
+ TLS_FAILED:
+ ;
+ /* 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. */
- if (new_domain_record.random_result != ccache_accept && done)
+ if (!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_address_record.result = ccache_accept;
- else if (errno == 0 && responsebuffer[0] == '5')
+ *failure_ptr = US"mail"; /* At or before MAIL */
+ if (errno == 0 && responsebuffer[0] == '5')
{
- *failure_ptr = US"recipient";
- new_address_record.result = ccache_reject;
+ setflag(addr, af_verify_nsfail);
+ new_domain_record.result = ccache_reject;
}
+ }
+
+ /* Send the MAIL command */
- /* Do postmaster check if requested; if a full check is required, we
- check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
+ else done =
+ smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
+ from_address) >= 0 &&
+ smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
+ '2', callout);
- if (done && pm_mailfrom != NULL)
+ /* 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')
{
- done =
- smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
- smtp_read_response(&inblock, responsebuffer,
- sizeof(responsebuffer), '2', callout) &&
+ setflag(addr, af_verify_nsfail);
+ if (from_address[0] == 0)
+ new_domain_record.result = ccache_reject_mfnull;
+ }
+ }
- smtp_write_command(&outblock, FALSE,
- "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 &&
- smtp_read_response(&inblock, responsebuffer,
- sizeof(responsebuffer), '2', callout) &&
+ /* 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:<>.
- /* First try using the current domain */
+ 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;
+
+ /* Do the random local part check first */
+
+ if (random_local_part != NULL)
+ {
+ uschar randombuffer[1024];
+ BOOL random_ok =
smtp_write_command(&outblock, FALSE,
- "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 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 */
- /* If that doesn't work, and a full check is requested,
- try without the domain. */
+ new_domain_record.random_stamp = time(NULL);
- (
- (options & vopt_callout_fullpm) != 0 &&
- smtp_write_command(&outblock, FALSE,
- "RCPT TO:<postmaster>\r\n") >= 0 &&
- smtp_read_response(&inblock, responsebuffer,
- sizeof(responsebuffer), '2', callout)
- ));
+ /* If accepted, we aren't going to do any further tests below. */
+
+ if (random_ok)
+ {
+ new_domain_record.random_result = ccache_accept;
+ }
- /* Sort out the cache record */
+ /* 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. */
- new_domain_record.postmaster_stamp = time(NULL);
+ 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:<postmaster> (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:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
+ smtp_read_response(&inblock, responsebuffer,
+ sizeof(responsebuffer), '2', callout)
+ )
+
+ ||
+
+ /* If that doesn't work, and a full check is requested,
+ try without the domain. */
+
+ (
+ (options & vopt_callout_fullpm) != 0 &&
+ smtp_write_command(&outblock, FALSE,
+ "RCPT TO:<postmaster>\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
- /* Hard rejection ends the process */
+ 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 (responsebuffer[0] == '5') /* Address rejected */
+ 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.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);
+ }
- 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.
+/* 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';
+}
+
+
+/* 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 )
+{
+header_line * h;
+uschar * cp1, * cp2;
+
+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;
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n");
+return cutthrough_put_nl();
+}
+
+
+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 *
*************************************************/
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;
}
+/**************************************************
+* 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 *
*************************************************/
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;
}
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
+
/* 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). */
}
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);
}
{
address_item *p = addr->parent;
- fprintf(f, "%s%s %s", ko_prefix, full_info? addr->address : address,
+ 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, ": %s", addr->message);
+ respond_printf(f, ": %s", addr->message);
}
/* Show parents iff doing full info */
if (full_info) while (p != NULL)
{
- fprintf(f, "%s\n <-- %s", cr, p->address);
+ 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 */
if (f != NULL)
{
address_item *p = addr->parent;
- fprintf(f, "%s%s cannot be resolved at this time", ko_prefix,
+ 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, ": %s", strerror(addr->basic_errno));
+ respond_printf(f, ": %s", strerror(addr->basic_errno));
if (addr->message != NULL)
- fprintf(f, ": %s", addr->message);
+ respond_printf(f, ": %s", addr->message);
else if (addr->basic_errno <= 0)
- fprintf(f, ": unknown error");
+ respond_printf(f, ": unknown error");
}
/* Show parents iff doing full info */
if (full_info) while (p != NULL)
{
- fprintf(f, "%s\n <-- %s", cr, p->address);
+ 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 soft fail");
- if (!full_info) return copy_error(vaddr, addr, DEFER);
- else if (yield == OK) yield = DEFER;
+ 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
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. */
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 */
if (allok && addr_local == NULL && addr_remote == NULL)
{
fprintf(f, "mail to %s is discarded\n", address);
- return yield;
+ goto out;
}
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:
+
+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
+
return yield;
}
{
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 &&
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)
{
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);
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 */
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;
}
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)
{
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);
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;
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;
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;
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. */
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*
/* 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";
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))
{
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;
}
/*************************************************
-* 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];
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
static int
one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
- uschar *prepend, uschar *iplist, BOOL bitmask, BOOL invert_result,
+ uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
int defer_return)
{
dns_answer dnsa;
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];
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
- if (da != NULL) break;
+ then we're done searching. */
+
+ 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;
}
if (domain_txt != domain)
return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
- FALSE, invert_result, defer_return);
+ 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. */
{
int sep = 0;
int defer_return = FAIL;
-BOOL invert_result = FALSE;
uschar *list = *listptr;
uschar *domain;
uschar *s;
{
int rc;
BOOL bitmask = FALSE;
+ int match_type = 0;
uschar *domain_txt;
uschar *comma;
uschar *iplist;
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)
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
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);
if (domain_txt != domain) for (s = domain_txt; *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_txt);
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,
- iplist, bitmask, invert_result, defer_return);
+ iplist, bitmask, match_type, defer_return);
if (rc == OK)
{
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, dnslist_domain);
}
}
rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
- bitmask, invert_result, defer_return);
+ bitmask, match_type, defer_return);
if (rc == OK)
{
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, dnslist_domain);
return OK;