* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* 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
#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];
#define MT_NOT 1
#define MT_ALL 2
+static uschar cutthrough_response(char, uschar **);
/*************************************************
*/
static dbdata_callout_cache *
-get_callout_cache_record(open_db *dbm_file, uschar *key, uschar *type,
+get_callout_cache_record(open_db *dbm_file, const uschar *key, uschar *type,
int positive_expire, int negative_expire)
{
BOOL negative;
if (cache_record == NULL)
{
- HDEBUG(D_verify) debug_printf("callout cache: no %s record found\n", type);
+ HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
return NULL;
}
if (now - cache_record->time_stamp > expire)
{
- HDEBUG(D_verify) debug_printf("callout cache: %s record expired\n", type);
+ HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key);
return NULL;
}
cache_record->random_result = ccache_unknown;
}
-HDEBUG(D_verify) debug_printf("callout cache: found %s record\n", type);
+HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key);
return cache_record;
}
uschar *address_key;
uschar *from_address;
uschar *random_local_part = NULL;
-uschar *save_deliver_domain = deliver_domain;
+const uschar *save_deliver_domain = deliver_domain;
uschar **failure_ptr = is_recipient?
&recipient_verify_failure : &sender_verify_failure;
open_db dbblock;
dbdata_callout_cache_address new_address_record;
host_item *host;
time_t callout_start_time;
+#ifdef EXPERIMENTAL_INTERNATIONAL
+BOOL utf8_offered = FALSE;
+#endif
new_domain_record.result = ccache_unknown;
new_domain_record.postmaster_result = ccache_unknown;
if (is_recipient)
{
- if ((options & vopt_callout_recipsender) != 0)
+ if (options & vopt_callout_recipsender)
{
address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
from_address = sender_address;
}
- else if ((options & vopt_callout_recippmaster) != 0)
+ else if (options & vopt_callout_recippmaster)
{
address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
qualify_domain_sender);
{
HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
}
+else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
+ addr->transport->name, addr->transport->driver_name);
else
{
smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(addr->transport->options_block);
+ (smtp_transport_options_block *)addr->transport->options_block;
/* The information wasn't available in the cache, so we have to do a real
callout and save the result in the cache for next time, unless no_cache is set,
log the fact, but carry on without randomming. */
if (callout_random && callout_random_local_part != NULL)
- {
- random_local_part = expand_string(callout_random_local_part);
- if (random_local_part == NULL)
+ if (!(random_local_part = expand_string(callout_random_local_part)))
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
"callout_random_local_part: %s", expand_string_message);
- }
/* Default the connect and overall callout timeouts if not set, and record the
time we are starting so that we can enforce it. */
if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
+/* cutthrough-multi: if a nonfirst rcpt has the same routing as the first,
+and we are holding a cutthrough conn open, we can just append the rcpt to
+that conn for verification purposes (and later delivery also). Simplest
+coding means skipping this whole loop and doing the append separately.
+
+We will need to remember it has been appended so that rcpt-acl tail code
+can do it there for the non-rcpt-verify case. For this we keep an addresscount.
+*/
+
+ /* Can we re-use an open cutthrough connection? */
+ if ( cutthrough.fd >= 0
+ && (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
+ == vopt_callout_recipsender
+ && !random_local_part
+ && !pm_mailfrom
+ )
+ {
+ if (addr->transport == cutthrough.addr.transport)
+ for (host = host_list; host; host = host->next)
+ if (Ustrcmp(host->address, cutthrough.host.address) == 0)
+ {
+ int host_af;
+ uschar *interface = NULL; /* Outgoing interface to use; NULL => any */
+ int port = 25;
+
+ deliver_host = host->name;
+ deliver_host_address = host->address;
+ deliver_host_port = host->port;
+ deliver_domain = addr->domain;
+ transport_name = addr->transport->name;
+
+ host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+
+ if (!smtp_get_interface(tf->interface, host_af, addr, 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);
+
+ if ( ( interface == cutthrough.interface
+ || ( interface
+ && cutthrough.interface
+ && Ustrcmp(interface, cutthrough.interface) == 0
+ ) )
+ && port == cutthrough.host.port
+ )
+ {
+ uschar * resp;
+
+ /* Match! Send the RCPT TO, append the addr, set done */
+ done =
+ smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+ transport_rcpt_address(addr,
+ (addr->transport == NULL)? FALSE :
+ addr->transport->rcpt_include_affixes)) >= 0 &&
+ cutthrough_response('2', &resp) == '2';
+
+ /* This would go horribly wrong if a callout fail was ignored by ACL.
+ We punt by abandoning cutthrough on a reject, like the
+ first-rcpt does. */
+
+ if (done)
+ {
+ address_item * na = store_get(sizeof(address_item));
+ *na = cutthrough.addr;
+ cutthrough.addr = *addr;
+ cutthrough.addr.host_used = &cutthrough.host;
+ cutthrough.addr.next = na;
+
+ cutthrough.nrcpt++;
+ }
+ else
+ {
+ cancel_cutthrough_connection("recipient rejected");
+ if (errno == ETIMEDOUT)
+ {
+ HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+ }
+ else if (errno == 0)
+ {
+ if (*resp == 0)
+ Ustrcpy(resp, US"connection dropped");
+
+ addr->message =
+ string_sprintf("response to \"%s\" from %s [%s] was: %s",
+ big_buffer, host->name, host->address,
+ string_printing(resp));
+
+ addr->user_message =
+ string_sprintf("Callout verification failed:\n%s", resp);
+
+ /* Hard rejection ends the process */
+
+ if (resp[0] == '5') /* Address rejected */
+ {
+ yield = FAIL;
+ done = TRUE;
+ }
+ }
+ }
+ }
+ break;
+ }
+ if (!done)
+ cancel_cutthrough_connection("incompatible connection");
+ }
+
/* Now make connections to the hosts and do real callouts. The list of hosts
is passed in as an argument. */
BOOL esmtp;
BOOL suppress_tls = FALSE;
uschar *interface = NULL; /* Outgoing interface to use; NULL => any */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+ BOOL dane = FALSE;
+ BOOL dane_required;
+ dns_answer tlsa_dnsa;
+#endif
uschar inbuffer[4096];
uschar outbuffer[1024];
uschar responsebuffer[4096];
deliver_host = host->name;
deliver_host_address = host->address;
+ deliver_host_port = host->port;
deliver_domain = addr->domain;
+ transport_name = addr->transport->name;
- if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
- US"callout") ||
- !smtp_get_port(tf->port, addr, &port, US"callout"))
+ if ( !smtp_get_interface(tf->interface, host_af, addr, 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);
outblock.cmd_count = 0;
outblock.authenticating = FALSE;
- /* Reset the parameters of a TLS session */
- tls_out.cipher = tls_out.peerdn = NULL;
-
/* Connect to the host; on failure, just loop for the next one, but we
set the error for the last one. Use the callout_connect timeout. */
tls_retry_connection:
+ /* Reset the parameters of a TLS session */
+ tls_out.cipher = tls_out.peerdn = tls_out.peercert = NULL;
+
inblock.sock = outblock.sock =
- smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
- /* reconsider DSCP here */
+ smtp_connect(host, host_af, port, interface, callout_connect,
+ addr->transport);
if (inblock.sock < 0)
{
addr->message = string_sprintf("could not connect to %s [%s]: %s",
host->name, host->address, strerror(errno));
+ transport_name = NULL;
deliver_host = deliver_host_address = NULL;
deliver_domain = save_deliver_domain;
continue;
}
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+ {
+ int rc;
+
+ tls_out.dane_verified = FALSE;
+ tls_out.tlsa_usage = 0;
+
+ dane_required =
+ verify_check_given_host(&ob->hosts_require_dane, host) == OK;
+
+ if (host->dnssec == DS_YES)
+ {
+ if( ( dane_required
+ || verify_check_given_host(&ob->hosts_try_dane, host) == OK
+ )
+ && (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK
+ )
+ return rc;
+ }
+ else if (dane_required)
+ {
+ log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name);
+ return FAIL;
+ }
+
+ if (dane)
+ ob->tls_tempfail_tryclear = FALSE;
+ }
+#endif /*DANE*/
+
/* Expand the helo_data string to find the host name to use. */
if (tf->helo_data != NULL)
else active_hostname = s;
}
- deliver_host = deliver_host_address = NULL;
- deliver_domain = save_deliver_domain;
-
/* Wait for initial response, and send HELO. The smtp_write_command()
function leaves its command in big_buffer. This is used in error responses.
Initialize it in case the connection is rejected. */
/* Unless ssl-on-connect, wait for the initial greeting */
smtps_redo_greeting:
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
if (!smtps || (smtps && tls_out.active >= 0))
- #endif
+#endif
+ {
if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
goto RESPONSE_FAILED;
-
+
+#ifdef EXPERIMENTAL_EVENT
+ lookup_dnssec_authenticated = host->dnssec==DS_YES ? US"yes"
+ : host->dnssec==DS_NO ? US"no" : NULL;
+ if (event_raise(addr->transport->event_action,
+ US"smtp:connect", responsebuffer))
+ {
+ lookup_dnssec_authenticated = NULL;
+ /* Logging? Debug? */
+ goto RESPONSE_FAILED;
+ }
+ lookup_dnssec_authenticated = NULL;
+#endif
+ }
+
/* Not worth checking greeting line for ESMTP support */
- if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
- host->name, host->address, NULL) != OK))
+ if (!(esmtp = verify_check_given_host(&ob->hosts_avoid_esmtp, host) != OK))
DEBUG(D_transport)
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
tls_redo_helo:
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
if (smtps && tls_out.active < 0) /* ssl-on-connect, first pass */
{
tls_offered = TRUE;
ob->tls_tempfail_tryclear = FALSE;
}
- else /* all other cases */
- #endif
+ else /* all other cases */
+#endif
{ esmtp_retry:
done= FALSE;
goto RESPONSE_FAILED;
}
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
tls_offered = FALSE;
- #endif
+#endif
esmtp = FALSE;
goto esmtp_retry; /* fallback to HELO */
}
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
- #ifdef SUPPORT_TLS
+#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);
+ {
+ if (regex_STARTTLS == NULL) regex_STARTTLS =
+ regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
- tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
- Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
+ tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
+ Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
}
else
tls_offered = FALSE;
- #endif
+#endif
}
/* If TLS is available on this connection attempt to
the client not be required to use TLS. If the response is bad, copy the buffer
for error analysis. */
- #ifdef SUPPORT_TLS
- if (tls_offered &&
- verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
- host->address, NULL) != OK &&
- verify_check_this_host(&(ob->hosts_verify_avoid_tls), NULL, host->name,
- host->address, NULL) != OK
+#ifdef SUPPORT_TLS
+ if ( tls_offered
+ && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK
+ && verify_check_given_host(&ob->hosts_verify_avoid_tls, host) != OK
)
{
uschar buffer2[4096];
{
if (errno != 0 || buffer2[0] == 0 ||
(buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
- {
- Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
- done= FALSE;
- goto RESPONSE_FAILED;
- }
+ {
+ Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
+ done= FALSE;
+ goto RESPONSE_FAILED;
+ }
}
/* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
else
{
- int rc = tls_client_start(inblock.sock, host, addr,
- 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. */
+ int oldtimeout = ob->command_timeout;
+ int rc;
+
+ tls_negotiate:
+ ob->command_timeout = callout;
+ rc = tls_client_start(inblock.sock, host, addr, addr->transport
+# ifdef EXPERIMENTAL_DANE
+ , dane ? &tlsa_dnsa : NULL
+# endif
+ );
+ ob->command_timeout = oldtimeout;
+
+ /* TLS negotiation failed; give an error. Try in clear on a new
+ connection, if the options permit it for this host. */
if (rc != OK)
{
- if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
- verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) != OK)
- {
- (void)close(inblock.sock);
- log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
- "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
- suppress_tls = TRUE;
- goto tls_retry_connection;
- }
- /*save_errno = ERRNO_TLSFAILURE;*/
- /*message = US"failure while setting up TLS session";*/
- send_quit = FALSE;
- done= FALSE;
- goto TLS_FAILED;
- }
+ if (rc == DEFER)
+ {
+ (void)close(inblock.sock);
+# ifdef EXPERIMENTAL_EVENT
+ (void) event_raise(addr->transport->event_action,
+ US"tcp:close", NULL);
+# endif
+# ifdef EXPERIMENTAL_DANE
+ if (dane)
+ {
+ if (!dane_required)
+ {
+ log_write(0, LOG_MAIN, "DANE attempt failed;"
+ " trying CA-root TLS to %s [%s] (not in hosts_require_dane)",
+ host->name, host->address);
+ dane = FALSE;
+ goto tls_negotiate;
+ }
+ }
+ else
+# endif
+ if ( ob->tls_tempfail_tryclear
+ && !smtps
+ && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+ )
+ {
+ log_write(0, LOG_MAIN, "TLS session failure:"
+ " delivering unencrypted to %s [%s] (not in hosts_require_tls)",
+ host->name, host->address);
+ suppress_tls = TRUE;
+ goto tls_retry_connection;
+ }
+ }
+
+ /*save_errno = ERRNO_TLSFAILURE;*/
+ /*message = US"failure while setting up TLS session";*/
+ send_quit = FALSE;
+ done= FALSE;
+ goto TLS_FAILED;
+ }
/* TLS session is set up. Copy info for logging. */
addr->cipher = tls_out.cipher;
/* For SMTPS we need to wait for the initial OK response, then do HELO. */
if (smtps)
- goto smtps_redo_greeting;
+ goto smtps_redo_greeting;
/* For STARTTLS we need to redo EHLO */
goto tls_redo_helo;
/* If the host is required to use a secure channel, ensure that we have one. */
if (tls_out.active < 0)
- if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) == OK)
+ if (
+# ifdef EXPERIMENTAL_DANE
+ dane ||
+# endif
+ verify_check_given_host(&ob->hosts_require_tls, host) == OK
+ )
{
/*save_errno = ERRNO_TLSREQUIRED;*/
- log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
+ log_write(0, LOG_MAIN,
+ "H=%s [%s]: a TLS session is required for this host, but %s",
host->name, host->address,
- tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
+ tls_offered ? "an attempt to start TLS failed"
+ : "the server did not offer TLS support");
done= FALSE;
goto TLS_FAILED;
}
- #endif /*SUPPORT_TLS*/
+#endif /*SUPPORT_TLS*/
done = TRUE; /* so far so good; have response to HELO */
- /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING, AUTH */
- /* If we haven't authenticated, but are required to, give up. */
+ /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */
- /*XXX "filter command specified for this transport" ??? */
- /* for now, transport_filter by cutthrough-delivery is not supported */
+ /* For now, transport_filter by cutthrough-delivery is not supported */
/* Need proper integration with the proper transport mechanism. */
-
+ if (cutthrough.delivery)
+ {
+ if (addr->transport->filter_command)
+ {
+ cutthrough.delivery = FALSE;
+ HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
+ }
+#ifndef DISABLE_DKIM
+ if (ob->dkim_domain)
+ {
+ cutthrough.delivery = FALSE;
+ HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
+ }
+#endif
+ }
SEND_FAILED:
RESPONSE_FAILED:
;
/* 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. */
}
}
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ else if ( addr->prop.utf8_msg
+ && !addr->prop.utf8_downcvt
+ && !( esmtp
+ && ( regex_UTF8
+ || ( (regex_UTF8 = regex_must_compile(
+ US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE)),
+ TRUE
+ ) )
+ && ( (utf8_offered = pcre_exec(regex_UTF8, NULL,
+ CS responsebuffer, Ustrlen(responsebuffer),
+ 0, PCRE_EOPT, NULL, 0) >= 0)
+ || addr->prop.utf8_downcvt_maybe
+ ) ) )
+ {
+ HDEBUG(D_acl|D_v) debug_printf("utf8 required but not offered\n");
+ errno = ERRNO_UTF8_FWD;
+ setflag(addr, af_verify_nsfail);
+ done = FALSE;
+ }
+ else if ( addr->prop.utf8_msg
+ && (addr->prop.utf8_downcvt || !utf8_offered)
+ && (setflag(addr, af_utf8_downcvt),
+ from_address = string_address_utf8_to_alabel(from_address,
+ &addr->message),
+ addr->message
+ ) )
+ {
+ errno = ERRNO_EXPANDFAIL;
+ setflag(addr, af_verify_nsfail);
+ done = FALSE;
+ }
+#endif
+
+ /* If we haven't authenticated, but are required to, give up. */
+ /* Try to AUTH */
+
+ else done = smtp_auth(responsebuffer, sizeof(responsebuffer),
+ addr, host, ob, esmtp, &inblock, &outblock) == OK &&
+
+ /* Copy AUTH info for logging */
+ ( (addr->authenticator = client_authenticator),
+ (addr->auth_id = client_authenticated_id),
+
+ /* Build a mail-AUTH string (re-using responsebuffer for convenience */
+ !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob)
+ ) &&
+
+ ( (addr->auth_sndr = client_authenticated_sender),
+
/* Send the MAIL command */
+ (smtp_write_command(&outblock, FALSE,
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ addr->prop.utf8_msg && !addr->prop.utf8_downcvt
+ ? "MAIL FROM:<%s>%s SMTPUTF8\r\n"
+ :
+#endif
+ "MAIL FROM:<%s>%s\r\n",
+ from_address, responsebuffer) >= 0)
+ ) &&
- else done =
- smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
- from_address) >= 0 &&
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
'2', callout);
+ deliver_host = deliver_host_address = NULL;
+ deliver_domain = save_deliver_domain;
+
/* If the host does not accept MAIL FROM:<>, arrange to cache this
information, but again, don't record anything for an I/O error or a defer. Do
not cache rejections of MAIL when a non-empty sender has been used, because
else
{
+ const uschar * rcpt_domain = addr->domain;
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ uschar * errstr = NULL;
+ if ( testflag(addr, af_utf8_downcvt)
+ && (rcpt_domain = string_domain_utf8_to_alabel(rcpt_domain,
+ &errstr), errstr)
+ )
+ {
+ addr->message = errstr;
+ errno = ERRNO_EXPANDFAIL;
+ setflag(addr, af_verify_nsfail);
+ done = FALSE;
+ rcpt_domain = US""; /*XXX errorhandling! */
+ }
+#endif
+
new_domain_record.result =
(old_domain_cache_result == ccache_reject_mfnull)?
ccache_reject_mfnull: ccache_accept;
BOOL random_ok =
smtp_write_command(&outblock, FALSE,
"RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
- addr->domain) >= 0 &&
+ rcpt_domain) >= 0 &&
smtp_read_response(&inblock, randombuffer,
sizeof(randombuffer), '2', callout);
/* If accepted, we aren't going to do any further tests below. */
if (random_ok)
- {
new_domain_record.random_result = ccache_accept;
- }
/* Otherwise, cache a real negative response, and get back to the right
state to send RCPT. Unless there's some problem such as a dropped
- connection, we expect to succeed, because the commands succeeded above. */
+ connection, we expect to succeed, because the commands succeeded above.
+ However, some servers drop the connection after responding to an
+ invalid recipient, so on (any) error we drop and remake the connection.
+ */
else if (errno == 0)
{
+ /* This would be ok for 1st rcpt a cutthrough, but no way to
+ handle a subsequent. So refuse to support any */
+ cancel_cutthrough_connection("random-recipient");
+
if (randombuffer[0] == '5')
new_domain_record.random_result = ccache_reject;
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
'2', callout) &&
- smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
+ smtp_write_command(&outblock, FALSE,
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ addr->prop.utf8_msg && !addr->prop.utf8_downcvt
+ ? "MAIL FROM:<%s> SMTPUTF8\r\n"
+ :
+#endif
+ "MAIL FROM:<%s>\r\n",
from_address) >= 0 &&
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
'2', callout);
+
+ if (!done)
+ {
+ HDEBUG(D_acl|D_v)
+ debug_printf("problem after random/rset/mfrom; reopen conn\n");
+ random_local_part = NULL;
+#ifdef SUPPORT_TLS
+ tls_close(FALSE, TRUE);
+#endif
+ (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_EVENT
+ (void) event_raise(addr->transport->event_action,
+ US"tcp:close", NULL);
+#endif
+ goto tls_retry_connection;
+ }
}
else done = FALSE; /* Some timeout/connection problem */
} /* Random check */
/* Get the rcpt_include_affixes flag from the transport if there is one,
but assume FALSE if there is not. */
+ uschar * rcpt = transport_rcpt_address(addr,
+ addr->transport ? addr->transport->rcpt_include_affixes : FALSE);
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ /*XXX should the conversion be moved into transport_rcpt_address() ? */
+ uschar * dummy_errstr = NULL;
+ if ( testflag(addr, af_utf8_downcvt)
+ && (rcpt = string_address_utf8_to_alabel(rcpt, &dummy_errstr),
+ dummy_errstr
+ ) )
+ {
+ errno = ERRNO_EXPANDFAIL;
+ *failure_ptr = US"recipient";
+ done = FALSE;
+ }
+ else
+#endif
+
done =
smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
- transport_rcpt_address(addr,
- (addr->transport == NULL)? FALSE :
- addr->transport->rcpt_include_affixes)) >= 0 &&
+ rcpt) >= 0 &&
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
'2', callout);
if (done && pm_mailfrom != NULL)
{
- /*XXX not suitable for cutthrough - sequencing problems */
- cutthrough_delivery= FALSE;
+ /* Could possibly shift before main verify, just above, and be ok
+ for cutthrough. But no way to handle a subsequent rcpt, so just
+ refuse any */
+ cancel_cutthrough_connection("postmaster verify");
HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
done =
((
smtp_write_command(&outblock, FALSE,
- "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
+ "RCPT TO:<postmaster@%.1000s>\r\n", rcpt_domain) >= 0 &&
smtp_read_response(&inblock, responsebuffer,
sizeof(responsebuffer), '2', callout)
)
HDEBUG(D_verify) debug_printf("SMTP timeout\n");
send_quit = FALSE;
}
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ else if (errno == ERRNO_UTF8_FWD)
+ {
+ extern int acl_where; /* src/acl.c */
+ errno = 0;
+ addr->message = string_sprintf(
+ "response to \"%s\" from %s [%s] did not include SMTPUTF8",
+ big_buffer, host->name, host->address);
+ addr->user_message = acl_where == ACL_WHERE_RCPT
+ ? US"533 mailbox name not allowed"
+ : US"550 mailbox unavailable";
+ yield = FAIL;
+ done = TRUE;
+ }
+#endif
else if (errno == 0)
{
if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped");
/* End the SMTP conversation and close the connection. */
- /* Cutthrough - on a successfull connect and recipient-verify with use-sender
- and we have no cutthrough conn so far
+ /* Cutthrough - on a successfull connect and recipient-verify with
+ use-sender and we are 1st rcpt and have no cutthrough conn so far
here is where we want to leave the conn open */
- if ( cutthrough_delivery
+ if ( cutthrough.delivery
+ && rcpt_count == 1
&& done
&& yield == OK
&& (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
&& !random_local_part
&& !pm_mailfrom
- && cutthrough_fd < 0
+ && cutthrough.fd < 0
+ && !lmtp
)
{
- cutthrough_fd= outblock.sock; /* We assume no buffer in use in the outblock */
- cutthrough_addr = *addr; /* Save the address_item for later logging */
- cutthrough_addr.host_used = store_get(sizeof(host_item));
- cutthrough_addr.host_used->name = host->name;
- cutthrough_addr.host_used->address = host->address;
- cutthrough_addr.host_used->port = port;
+ cutthrough.fd = outblock.sock; /* We assume no buffer in use in the outblock */
+ cutthrough.nrcpt = 1;
+ cutthrough.interface = interface;
+ cutthrough.host = *host;
+ cutthrough.addr = *addr; /* Save the address_item for later logging */
+ cutthrough.addr.next = NULL;
+ cutthrough.addr.host_used = &cutthrough.host;
if (addr->parent)
- *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+ *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
+ *addr->parent;
ctblock.buffer = ctbuffer;
ctblock.buffersize = sizeof(ctbuffer);
ctblock.ptr = ctbuffer;
/* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
- ctblock.sock = cutthrough_fd;
+ ctblock.sock = cutthrough.fd;
}
else
{
cancel_cutthrough_connection("multiple verify calls");
if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
tls_close(FALSE, TRUE);
- #endif
+#endif
(void)close(inblock.sock);
+#ifdef EXPERIMENTAL_EVENT
+ (void) event_raise(addr->transport->event_action,
+ US"tcp:close", NULL);
+#endif
}
} /* Loop through all hosts, while !done */
get rewritten. */
addr2 = *addr;
-HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
+HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n",
+ rcpt_count > 1 ? "more" : "start");
(void) verify_address(&addr2, NULL,
vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
CUTTHROUGH_CMD_TIMEOUT, -1, -1,
static BOOL
cutthrough_send(int n)
{
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
return TRUE;
if(
#ifdef SUPPORT_TLS
- (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+ (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
#endif
- send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+ send(cutthrough.fd, ctblock.buffer, n, 0) > 0
)
{
transport_count += n;
BOOL
cutthrough_puts(uschar * cp, int n)
{
-if (cutthrough_fd < 0) return TRUE;
+if (cutthrough.fd < 0) return TRUE;
if (_cutthrough_puts(cp, n)) return TRUE;
cancel_cutthrough_connection("transmit failed");
return FALSE;
static BOOL
-_cutthrough_flush_send( void )
+_cutthrough_flush_send(void)
{
int n= ctblock.ptr-ctblock.buffer;
/* Send out any bufferred output. Return boolean success. */
BOOL
-cutthrough_flush_send( void )
+cutthrough_flush_send(void)
{
if (_cutthrough_flush_send()) return TRUE;
cancel_cutthrough_connection("transmit failed");
BOOL
-cutthrough_put_nl( void )
+cutthrough_put_nl(void)
{
return cutthrough_puts(US"\r\n", 2);
}
inblock.buffersize = sizeof(inbuffer);
inblock.ptr = inbuffer;
inblock.ptrend = inbuffer;
-inblock.sock = cutthrough_fd;
+inblock.sock = cutthrough.fd;
/* this relies on (inblock.sock == tls_out.active) */
if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
cancel_cutthrough_connection("target timeout on read");
if(copy != NULL)
{
uschar * cp;
- *copy= cp= string_copy(responsebuffer);
+ *copy = cp = string_copy(responsebuffer);
/* Trim the trailing end of line */
cp += Ustrlen(responsebuffer);
if(cp > *copy && cp[-1] == '\n') *--cp = '\0';
/* Negotiate dataphase with the cutthrough target, returning success boolean */
BOOL
-cutthrough_predata( void )
+cutthrough_predata(void)
{
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
return FALSE;
HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n");
}
+/* fd and use_crlf args only to match write_chunk() */
+static BOOL
+cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf)
+{
+uschar * s2;
+while(s && (s2 = Ustrchr(s, '\n')))
+ {
+ if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl())
+ return FALSE;
+ s = s2+1;
+ }
+return TRUE;
+}
+
+
/* Buffered send of headers. Return success boolean. */
/* Expands newlines to wire format (CR,NL). */
/* Also sends header-terminating blank line. */
BOOL
-cutthrough_headers_send( void )
+cutthrough_headers_send(void)
{
-header_line * h;
-uschar * cp1, * cp2;
-
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
return FALSE;
-for(h= header_list; h != NULL; h= h->next)
- if(h->type != htype_old && h->text != NULL)
- for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1)
- if( !cutthrough_puts(cp1, cp2-cp1)
- || !cutthrough_put_nl())
- return FALSE;
+/* We share a routine with the mainline transport to handle header add/remove/rewrites,
+ but having a separate buffered-output function (for now)
+*/
+HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");
+
+if (!transport_headers_send(&cutthrough.addr, cutthrough.fd,
+ cutthrough.addr.transport->add_headers,
+ cutthrough.addr.transport->remove_headers,
+ &cutthrough_write_chunk, TRUE,
+ cutthrough.addr.transport->rewrite_rules,
+ cutthrough.addr.transport->rewrite_existflags))
+ return FALSE;
-HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n");
-return cutthrough_put_nl();
+HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
+return TRUE;
}
static void
-close_cutthrough_connection( const char * why )
+close_cutthrough_connection(const char * why)
{
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
{
/* We could be sending this after a bunch of data, but that is ok as
the only way to cancel the transfer in dataphase is to drop the tcp
#ifdef SUPPORT_TLS
tls_close(FALSE, TRUE);
#endif
- (void)close(cutthrough_fd);
- cutthrough_fd= -1;
+ (void)close(cutthrough.fd);
+ cutthrough.fd = -1;
HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
}
ctblock.ptr = ctbuffer;
}
void
-cancel_cutthrough_connection( const char * why )
+cancel_cutthrough_connection(const char * why)
{
close_cutthrough_connection(why);
-cutthrough_delivery= FALSE;
+cutthrough.delivery = FALSE;
}
Return smtp response-class digit.
*/
uschar *
-cutthrough_finaldot( void )
+cutthrough_finaldot(void)
{
+uschar res;
+address_item * addr;
HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n");
/* Assume data finshed with new-line */
-if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
- return cutthrough_addr.message;
+if( !cutthrough_puts(US".", 1)
+ || !cutthrough_put_nl()
+ || !cutthrough_flush_send()
+ )
+ return cutthrough.addr.message;
-switch(cutthrough_response('2', &cutthrough_addr.message))
+res = cutthrough_response('2', &cutthrough.addr.message);
+for (addr = &cutthrough.addr; addr; addr = addr->next)
{
- case '2':
- delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
- close_cutthrough_connection("delivered");
- break;
+ addr->message = cutthrough.addr.message;
+ switch(res)
+ {
+ case '2':
+ delivery_log(LOG_MAIN, addr, (int)'>', NULL);
+ close_cutthrough_connection("delivered");
+ break;
- case '4':
- delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
- break;
+ case '4':
+ delivery_log(LOG_MAIN, addr, 0,
+ US"tmp-reject from cutthrough after DATA:");
+ break;
- case '5':
- delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
- break;
+ case '5':
+ delivery_log(LOG_MAIN|LOG_REJECT, addr, 0,
+ US"rejected after DATA:");
+ break;
- default:
- break;
+ default:
+ break;
+ }
}
- return cutthrough_addr.message;
+return cutthrough.addr.message;
}
vaddr->user_message = addr->user_message;
vaddr->basic_errno = addr->basic_errno;
vaddr->more_errno = addr->more_errno;
- vaddr->p.address_data = addr->p.address_data;
+ vaddr->prop.address_data = addr->prop.address_data;
copyflag(vaddr, addr, af_pass_message);
}
return yield;
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
+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). */
/* Just in case some router parameter refers to it. */
- return_path = (addr->p.errors_address != NULL)?
- addr->p.errors_address : sender_address;
+ return_path = (addr->prop.errors_address != NULL)?
+ addr->prop.errors_address : sender_address;
/* Split the address into domain and local part, handling the %-hack if
necessary, and then route it. While routing a sender address, set
if (tf.hosts != NULL && (host_list == NULL || tf.hosts_override))
{
uschar *s;
- uschar *save_deliver_domain = deliver_domain;
+ const uschar *save_deliver_domain = deliver_domain;
uschar *save_deliver_localpart = deliver_localpart;
host_list = NULL; /* Ignore the router's hosts */
else
{
int flags;
- uschar *canonical_name;
host_item *host, *nexthost;
host_build_hostlist(&host_list, s, tf.hosts_randomize);
nexthost = host->next;
if (tf.gethostbyname ||
string_is_ip_address(host->name, NULL) != 0)
- (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
+ (void)host_find_byname(host, NULL, flags, NULL, TRUE);
else
+ {
+ 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, NULL, NULL);
+ }
}
}
}
#ifdef SUPPORT_TLS
deliver_set_expansions(addr);
#endif
+ verify_mode = is_recipient ? US"R" : US"S";
rc = do_callout(addr, host_list, &tf, callout, callout_overall,
callout_connect, options, se_mailfrom, pm_mailfrom);
+ verify_mode = NULL;
}
}
else
/* If we have carried on to verify a child address, we want the value
of $address_data to be that of the child */
- vaddr->p.address_data = addr->p.address_data;
+ vaddr->prop.address_data = addr->prop.address_data;
yield = OK;
goto out;
}
fprintf(f, "%s", CS addr->address);
#ifdef EXPERIMENTAL_SRS
- if(addr->p.srs_sender)
- fprintf(f, " [srs = %s]", addr->p.srs_sender);
+ if(addr->prop.srs_sender)
+ fprintf(f, " [srs = %s]", addr->prop.srs_sender);
#endif
/* If the address is a duplicate, show something about it. */
while (len++ < maxaddlen) fprintf(f," ");
if (h->mx >= 0) fprintf(f, "MX=%d", h->mx);
if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port);
+ if (running_in_test_harness)
+#ifndef DISABLE_DNSSEC
+ fprintf(f, " ad=%s", h->dnssec==DS_YES ? "yes" : "no");
+#else
+ fprintf(f, " ad=no");
+#endif
if (h->status == hstatus_unusable) fprintf(f, " ** unusable **");
fprintf(f, "\n");
}
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
+tls_modify_variables(&tls_in);
return yield;
}
verb = US"begins";
}
- *msgptr = string_printing(
+ /* deconst cast ok as we're passing a non-const to string_printing() */
+ *msgptr = US string_printing(
string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
errmess, tt - h->text, h->text, verb, len, s));
}
+/*************************************************
+* 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 *
/* The rest of the line is the data we want. We turn it into printing
characters when we save it, so that it cannot mess up the format of any logging
or Received: lines into which it gets inserted. We keep a maximum of 127
-characters. */
+characters. The deconst cast is ok as we fed a nonconst to string_printing() */
-sender_ident = string_printing(string_copyn(p, 127));
+sender_ident = US string_printing(string_copyn(p, 127));
DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
END_OFF:
*/
int
-check_host(void *arg, uschar *ss, uschar **valueptr, uschar **error)
+check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error)
{
check_host_block *cb = (check_host_block *)arg;
int mlen = -1;
BOOL iplookup = FALSE;
BOOL isquery = FALSE;
BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
-uschar *t;
+const uschar *t;
uschar *semicolon;
uschar **aliases;
if ((semicolon = Ustrchr(ss, ';')) != NULL)
{
- uschar *affix;
+ const uschar *affix;
int partial, affixlen, starflags, id;
*semicolon = 0;
"+allow_unknown" was met earlier in the list, in which case OK is returned. */
int
-verify_check_this_host(uschar **listptr, unsigned int *cache_bits,
- uschar *host_name, uschar *host_address, uschar **valueptr)
+verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
+ const uschar *host_name, const uschar *host_address, const uschar **valueptr)
{
int rc;
unsigned int *local_cache_bits = cache_bits;
-uschar *save_host_address = deliver_host_address;
+const uschar *save_host_address = deliver_host_address;
check_host_block cb;
cb.host_name = host_name;
cb.host_address = host_address;
+/*************************************************
+* Check the given host item matches a list *
+*************************************************/
+int
+verify_check_given_host(uschar **listptr, host_item *host)
+{
+return verify_check_this_host(CUSS listptr, NULL, host->name, host->address, NULL);
+}
+
/*************************************************
* Check the remote host matches a list *
*************************************************/
int
verify_check_host(uschar **listptr)
{
-return verify_check_this_host(listptr, sender_host_cache, NULL,
+return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
(sender_host_address == NULL)? US"" : sender_host_address, NULL);
}
{
int ipsep = ',';
uschar ip[46];
- uschar *ptr = iplist;
+ const uschar *ptr = iplist;
uschar *res;
/* Handle exact matching */
*/
int
-verify_check_dnsbl(uschar **listptr)
+verify_check_dnsbl(const uschar **listptr)
{
int sep = 0;
int defer_return = FAIL;
-uschar *list = *listptr;
+const uschar *list = *listptr;
uschar *domain;
uschar *s;
uschar buffer[1024];
/* 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 */
uschar keybuffer[256];
uschar keyrevadd[128];
- while ((keydomain = string_nextinlist(&key, &keysep, keybuffer,
+ while ((keydomain = string_nextinlist(CUSS &key, &keysep, keybuffer,
sizeof(keybuffer))) != NULL)
{
uschar *prepend = keydomain;
return FAIL;
}
+/* vi: aw ai sw=2
+*/
/* End of verify.c */