* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with verifying things. The original code for callout
/* Structure for caching DNSBL lookups */
typedef struct dnsbl_cache_block {
+ time_t expiry;
dns_address *rhs;
uschar *text;
int rc;
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;
}
dbdata_callout_cache_address new_address_record;
host_item *host;
time_t callout_start_time;
-#ifdef EXPERIMENTAL_INTERNATIONAL
-BOOL utf8_offered = FALSE;
-#endif
+uschar peer_offered = 0;
new_domain_record.result = ccache_unknown;
new_domain_record.postmaster_result = ccache_unknown;
host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
- if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
+ if (!smtp_get_interface(tf->interface, host_af, addr, &interface,
US"callout") ||
!smtp_get_port(tf->port, addr, &port, US"callout"))
log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
uschar inbuffer[4096];
uschar outbuffer[1024];
uschar responsebuffer[4096];
+ uschar * size_str;
clearflag(addr, af_verify_pmfail); /* postmaster callout flag */
clearflag(addr, af_verify_nsfail); /* null sender callout flag */
deliver_domain = addr->domain;
transport_name = addr->transport->name;
- if ( !smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
+ if ( !smtp_get_interface(tf->interface, host_af, addr, &interface,
US"callout")
|| !smtp_get_port(tf->port, addr, &port, US"callout")
)
if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
goto RESPONSE_FAILED;
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
lookup_dnssec_authenticated = host->dnssec==DS_YES ? US"yes"
: host->dnssec==DS_NO ? US"no" : NULL;
if (event_raise(addr->transport->event_action,
#ifdef SUPPORT_TLS
if (smtps && tls_out.active < 0) /* ssl-on-connect, first pass */
{
- tls_offered = TRUE;
+ peer_offered &= ~PEER_OFFERED_TLS;
ob->tls_tempfail_tryclear = FALSE;
}
else /* all other cases */
goto RESPONSE_FAILED;
}
#ifdef SUPPORT_TLS
- tls_offered = FALSE;
+ peer_offered &= ~PEER_OFFERED_TLS;
#endif
esmtp = FALSE;
goto esmtp_retry; /* fallback to HELO */
}
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
-#ifdef SUPPORT_TLS
- if (esmtp && !suppress_tls && tls_out.active < 0)
- {
- if (regex_STARTTLS == NULL) regex_STARTTLS =
- regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
- tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
- Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
- }
- else
- tls_offered = FALSE;
+ peer_offered = esmtp
+ ? ehlo_response(responsebuffer, sizeof(responsebuffer),
+ (!suppress_tls && tls_out.active < 0 ? PEER_OFFERED_TLS : 0)
+ | 0 /* no IGNQ */
+ | 0 /* no PRDR */
+#ifdef SUPPORT_I18N
+ | (addr->prop.utf8_msg && !addr->prop.utf8_downcvt
+ ? PEER_OFFERED_UTF8 : 0)
#endif
+ | 0 /* no DSN */
+ | 0 /* no PIPE */
+
+ /* only care about SIZE if we have size from inbound */
+ | (message_size > 0 && ob->size_addition >= 0
+ ? PEER_OFFERED_SIZE : 0)
+ )
+ : 0;
}
+ size_str = peer_offered & PEER_OFFERED_SIZE
+ ? string_sprintf(" SIZE=%d", message_size + ob->size_addition) : US"";
+
+#ifdef SUPPORT_TLS
+ tls_offered = !!(peer_offered & PEER_OFFERED_TLS);
+#endif
+
/* If TLS is available on this connection attempt to
start up a TLS session, unless the host is in hosts_avoid_tls. If successful,
send another EHLO - the server may give a different answer in secure mode. We
for error analysis. */
#ifdef SUPPORT_TLS
- if ( tls_offered
+ if ( peer_offered & PEER_OFFERED_TLS
&& verify_check_given_host(&ob->hosts_avoid_tls, host) != OK
&& verify_check_given_host(&ob->hosts_verify_avoid_tls, host) != OK
)
if (rc == DEFER)
{
(void)close(inblock.sock);
-# ifdef EXPERIMENTAL_EVENT
+# ifndef DISABLE_EVENT
(void) event_raise(addr->transport->event_action,
US"tcp:close", NULL);
# endif
log_write(0, LOG_MAIN,
"H=%s [%s]: a TLS session is required for this host, but %s",
host->name, host->address,
- tls_offered ? "an attempt to start TLS failed"
- : "the server did not offer TLS support");
+ peer_offered & PEER_OFFERED_TLS
+ ? "an attempt to start TLS failed"
+ : "the server did not offer TLS support");
done= FALSE;
goto TLS_FAILED;
}
done = TRUE; /* so far so good; have response to HELO */
- /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */
-
/* For now, transport_filter by cutthrough-delivery is not supported */
/* Need proper integration with the proper transport mechanism. */
if (cutthrough.delivery)
}
}
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
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
- ) ) )
+ && !(peer_offered & PEER_OFFERED_UTF8)
+ )
{
HDEBUG(D_acl|D_v) debug_printf("utf8 required but not offered\n");
errno = ERRNO_UTF8_FWD;
done = FALSE;
}
else if ( addr->prop.utf8_msg
- && (addr->prop.utf8_downcvt || !utf8_offered)
- && (from_address = string_address_utf8_to_alabel(from_address,
- &addr->message), addr->message)
- )
+ && (addr->prop.utf8_downcvt || !(peer_offered & PEER_OFFERED_UTF8))
+ && (setflag(addr, af_utf8_downcvt),
+ from_address = string_address_utf8_to_alabel(from_address,
+ &addr->message),
+ addr->message
+ ) )
{
errno = ERRNO_EXPANDFAIL;
setflag(addr, af_verify_nsfail);
/* Send the MAIL command */
(smtp_write_command(&outblock, FALSE,
-#ifdef EXPERIMENTAL_INTERNATIONAL
- addr->prop.utf8_msg
- ? "MAIL FROM:<%s>%s SMTPUTF8\r\n"
+#ifdef SUPPORT_I18N
+ addr->prop.utf8_msg && !addr->prop.utf8_downcvt
+ ? "MAIL FROM:<%s>%s%s SMTPUTF8\r\n"
:
#endif
- "MAIL FROM:<%s>%s\r\n",
- from_address, responsebuffer) >= 0)
+ "MAIL FROM:<%s>%s%s\r\n",
+ from_address, responsebuffer, size_str) >= 0)
) &&
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
else
{
+ const uschar * rcpt_domain = addr->domain;
+
+#ifdef SUPPORT_I18N
+ uschar * errstr = NULL;
+ if ( testflag(addr, af_utf8_downcvt)
+ && (rcpt_domain = string_domain_utf8_to_alabel(rcpt_domain,
+ &errstr), errstr)
+ )
+ {
+ addr->message = errstr;
+ errno = ERRNO_EXPANDFAIL;
+ setflag(addr, af_verify_nsfail);
+ done = FALSE;
+ rcpt_domain = US""; /*XXX errorhandling! */
+ }
+#endif
+
new_domain_record.result =
(old_domain_cache_result == ccache_reject_mfnull)?
ccache_reject_mfnull: ccache_accept;
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);
'2', callout) &&
smtp_write_command(&outblock, FALSE,
-#ifdef EXPERIMENTAL_INTERNATIONAL
- addr->prop.utf8_msg
+#ifdef SUPPORT_I18N
+ addr->prop.utf8_msg && !addr->prop.utf8_downcvt
? "MAIL FROM:<%s> SMTPUTF8\r\n"
:
#endif
tls_close(FALSE, TRUE);
#endif
(void)close(inblock.sock);
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
(void) event_raise(addr->transport->event_action,
US"tcp:close", NULL);
#endif
/* Get the rcpt_include_affixes flag from the transport if there is one,
but assume FALSE if there is not. */
+ uschar * rcpt = transport_rcpt_address(addr,
+ addr->transport ? addr->transport->rcpt_include_affixes : FALSE);
+
+#ifdef SUPPORT_I18N
+ /*XXX should the conversion be moved into transport_rcpt_address() ? */
+ uschar * dummy_errstr = NULL;
+ if ( testflag(addr, af_utf8_downcvt)
+ && (rcpt = string_address_utf8_to_alabel(rcpt, &dummy_errstr),
+ dummy_errstr
+ ) )
+ {
+ errno = ERRNO_EXPANDFAIL;
+ *failure_ptr = US"recipient";
+ done = FALSE;
+ }
+ else
+#endif
+
done =
smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
- transport_rcpt_address(addr,
- (addr->transport == NULL)? FALSE :
- addr->transport->rcpt_include_affixes)) >= 0 &&
+ rcpt) >= 0 &&
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
'2', callout);
((
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
+#ifdef SUPPORT_I18N
else if (errno == ERRNO_UTF8_FWD)
{
extern int acl_where; /* src/acl.c */
tls_close(FALSE, TRUE);
#endif
(void)close(inblock.sock);
-#ifdef EXPERIMENTAL_EVENT
- (void) event_raise(addr->transport->event_action,
- US"tcp:close", NULL);
+#ifndef DISABLE_EVENT
+ (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
#endif
}
if (callout > 0)
{
host_item *host_list = addr->host_list;
+ transport_instance * tp;
/* Make up some data for use in the case where there is no remote
transport. */
transport's options, so as to mimic what would happen if we were really
sending a message to this address. */
- if (addr->transport != NULL && !addr->transport->info->local)
+ if ((tp = addr->transport) && !tp->info->local)
{
- (void)(addr->transport->setup)(addr->transport, addr, &tf, 0, 0, NULL);
+ (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL);
/* If the transport has hosts and the router does not, or if the
transport is configured to override the router's hosts, we must build a
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
"\"%s\" in %s transport for callout: %s", tf.hosts,
- addr->transport->name, expand_string_message);
+ tp->name, expand_string_message);
}
else
{
(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)
+ dnssec_domains * dnssec_domains = NULL;
+ if (Ustrcmp(tp->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;
+ (smtp_transport_options_block *) tp->options_block;
+ dnssec_domains = &ob->dnssec;
}
(void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
- d_request, d_require, NULL, NULL);
+ dnssec_domains, NULL, NULL);
}
}
}
}
for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
- {
- while (addr_list != NULL)
+ while (addr_list)
{
address_item *addr = addr_list;
address_item *p = addr->parent;
+ transport_instance * tp = addr->transport;
+
addr_list = addr->next;
fprintf(f, "%s", CS addr->address);
if (!testflag(addr, af_pfr))
{
tree_node *tnode;
- if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+ if ((tnode = tree_search(tree_duplicates, addr->unique)))
fprintf(f, " [duplicate, would not be delivered]");
else tree_add_duplicate(addr->unique, addr);
}
/* Now show its parents */
- while (p != NULL)
- {
+ for (p = addr->parent; p; p = p->parent)
fprintf(f, "\n <-- %s", p->address);
- p = p->parent;
- }
fprintf(f, "\n ");
/* Show router, and transport */
- fprintf(f, "router = %s, ", addr->router->name);
- fprintf(f, "transport = %s\n", (addr->transport == NULL)? US"unset" :
- addr->transport->name);
+ fprintf(f, "router = %s, transport = %s\n",
+ addr->router->name, tp ? tp->name : US"unset");
/* Show any hosts that are set up by a router unless the transport
is going to override them; fiddle a bit to get a nice format. */
- if (addr->host_list != NULL && addr->transport != NULL &&
- !addr->transport->overrides_hosts)
+ if (addr->host_list && tp && !tp->overrides_hosts)
{
host_item *h;
int maxlen = 0;
int maxaddlen = 0;
- for (h = addr->host_list; h != NULL; h = h->next)
- {
+ for (h = addr->host_list; h; h = h->next)
+ { /* get max lengths of host names, addrs */
int len = Ustrlen(h->name);
if (len > maxlen) maxlen = len;
- len = (h->address != NULL)? Ustrlen(h->address) : 7;
+ len = h->address ? Ustrlen(h->address) : 7;
if (len > maxaddlen) maxaddlen = len;
}
- for (h = addr->host_list; h != NULL; h = h->next)
- {
- int len = Ustrlen(h->name);
- fprintf(f, " host %s ", h->name);
- while (len++ < maxlen) fprintf(f, " ");
- if (h->address != NULL)
- {
- fprintf(f, "[%s] ", h->address);
- len = Ustrlen(h->address);
- }
- else if (!addr->transport->info->local) /* Omit [unknown] for local */
- {
- fprintf(f, "[unknown] ");
- len = 7;
- }
- else len = -3;
- while (len++ < maxaddlen) fprintf(f," ");
- if (h->mx >= 0) fprintf(f, "MX=%d", h->mx);
+ for (h = addr->host_list; h; h = h->next)
+ {
+ fprintf(f, " host %-*s ", maxlen, h->name);
+
+ if (h->address)
+ fprintf(f, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
+ else if (tp->info->local)
+ fprintf(f, " %-*s ", maxaddlen, ""); /* Omit [unknown] for local */
+ else
+ fprintf(f, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
+
+ if (h->mx >= 0) fprintf(f, " MX=%d", h->mx);
if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port);
- if (h->status == hstatus_unusable) fprintf(f, " ** unusable **");
- fprintf(f, "\n");
+ if (running_in_test_harness && h->dnssec == DS_YES) fputs(" AD", f);
+ if (h->status == hstatus_unusable) fputs(" ** unusable **", f);
+ fputc('\n', f);
}
}
}
- }
/* Yield will be DEFER or FAIL if any one address has, only for full_info (which is
the -bv or -bt case). */
if (ip_connect(sock, host_af, sender_host_address, port, rfc1413_query_timeout)
< 0)
{
- if (errno == ETIMEDOUT && (log_extra_selector & LX_ident_timeout) != 0)
+ if (errno == ETIMEDOUT && LOGGING(ident_timeout))
{
log_write(0, LOG_MAIN, "ident connection to %s timed out",
sender_host_address);
h.address = NULL;
h.mx = MX_NONE;
+ /* Using byname rather than bydns here means we cannot determine dnssec
+ status. On the other hand it is unclear how that could be either
+ propagated up or enforced. */
+
rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
{
/* Look for this query in the cache. */
-t = tree_search(dnsbl_cache, query);
+if ( (t = tree_search(dnsbl_cache, query))
+ && (cb = t->data.ptr)->expiry > time(NULL)
+ )
+
+/* Previous lookup was cached */
+
+ {
+ HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
+ }
/* If not cached from a previous lookup, we must do a DNS lookup, and
cache the result in permanent memory. */
-if (t == NULL)
+else
{
+ uint ttl = 3600;
+
store_pool = POOL_PERM;
- /* Set up a tree entry to cache the lookup */
+ if (t)
+ {
+ HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
+ }
- t = store_get(sizeof(tree_node) + Ustrlen(query));
- Ustrcpy(t->name, query);
- t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
- (void)tree_insertnode(&dnsbl_cache, t);
+ else
+ { /* Set up a tree entry to cache the lookup */
+ t = store_get(sizeof(tree_node) + Ustrlen(query));
+ Ustrcpy(t->name, query);
+ t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
+ (void)tree_insertnode(&dnsbl_cache, t);
+ }
/* Do the DNS loopup . */
Quite apart from one A6 RR generating multiple addresses, there are DNS
lists that return more than one A record, so we must handle multiple
- addresses generated in that way as well. */
+ addresses generated in that way as well.
+
+ Mark the cache entry with the "now" plus the minimum of the address TTLs,
+ or some suitably far-future time if none were found. */
if (cb->rc == DNS_SUCCEED)
{
dns_record *rr;
dns_address **addrp = &(cb->rhs);
for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
- rr != NULL;
+ rr;
rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
{
if (rr->type == T_A)
{
dns_address *da = dns_address_from_rr(&dnsa, rr);
- if (da != NULL)
+ if (da)
{
*addrp = da;
while (da->next != NULL) da = da->next;
addrp = &(da->next);
+ if (ttl > rr->ttl) ttl = rr->ttl;
}
}
}
if (cb->rhs == NULL) cb->rc = DNS_NODATA;
}
+ cb->expiry = time(NULL)+ttl;
store_pool = old_pool;
}
-/* Previous lookup was cached */
-
-else
- {
- HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
- cb = t->data.ptr;
- }
-
/* We now have the result of the DNS lookup, either newly done, or cached
from a previous call. If the lookup succeeded, check against the address
list if there is one. This may be a positive equality list (introduced by
Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
Arguments:
+ where the acl type
listptr the domain/address/data list
+ log_msgptr log message on error
Returns: OK successful lookup (i.e. the address is on the list), or
lookup deferred after +include_unknown
*/
int
-verify_check_dnsbl(const uschar **listptr)
+verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
{
int sep = 0;
int defer_return = FAIL;
/* See if there's explicit data to be looked up */
- key = Ustrchr(domain, '/');
- if (key != NULL) *key++ = 0;
+ if ((key = Ustrchr(domain, '/'))) *key++ = 0;
/* See if there's a list of addresses supplied after the domain name. This is
introduced by an = or a & character; if preceded by = we require all matches
and if preceded by ! we invert the result. */
- iplist = Ustrchr(domain, '=');
- if (iplist == NULL)
+ if (!(iplist = Ustrchr(domain, '=')))
{
bitmask = TRUE;
iplist = Ustrchr(domain, '&');
}
- if (iplist != NULL) /* Found either = or & */
+ if (iplist) /* Found either = or & */
{
if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */
{
}
}
+
/* If there is a comma in the domain, it indicates that a second domain for
looking up TXT records is provided, before the main domain. Otherwise we must
set domain_txt == domain. */
if (key == NULL)
{
+ if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
+ {
+ *log_msgptr = string_sprintf
+ ("cannot test auto-keyed dnslists condition in %s ACL",
+ acl_wherenames[where]);
+ return ERROR;
+ }
if (sender_host_address == NULL) return FAIL; /* can never match */
if (revadd[0] == 0) invert_address(revadd, sender_host_address);
rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,