-/* $Cambridge: exim/src/src/verify.c,v 1.32 2006/02/13 11:13:37 ph10 Exp $ */
+/* $Cambridge: exim/src/src/verify.c,v 1.51 2007/06/14 14:18:19 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with verifying things. The original code for callout
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 *
BOOL callout_random = (options & vopt_callout_random) != 0;
int yield = OK;
+int old_domain_cache_result = ccache_accept;
BOOL done = FALSE;
uschar *address_key;
uschar *from_address;
if (cache_record != NULL)
{
- /* If an early command (up to and including MAIL FROM:<>) was rejected,
- there is no point carrying on. The callout fails. */
-
- if (cache_record->result == ccache_reject)
+ /* In most cases, if an early command (up to and including MAIL FROM:<>)
+ was rejected, there is no point carrying on. The callout fails. However, if
+ we are doing a recipient verification with use_sender or use_postmaster
+ set, a previous failure of MAIL FROM:<> doesn't count, because this time we
+ will be using a non-empty sender. We have to remember this situation so as
+ not to disturb the cached domain value if this whole verification succeeds
+ (we don't want it turning into "accept"). */
+
+ old_domain_cache_result = cache_record->result;
+
+ if (cache_record->result == ccache_reject ||
+ (*from_address == 0 && cache_record->result == ccache_reject_mfnull))
{
setflag(addr, af_verify_nsfail);
HDEBUG(D_verify)
if (callout_connect < 0) callout_connect = callout;
callout_start_time = time(NULL);
+/* 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. */
+
+if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
+
/* Now make connections to the hosts and do real callouts. The list of hosts
is passed in as an argument. */
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];
log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
addr->message);
- deliver_host = deliver_host_address = NULL;
- deliver_domain = save_deliver_domain;
-
/* Set HELO string according to the protocol */
if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO";
{
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;
}
- /* Wait for initial response, and then run the initial SMTP commands. The
- smtp_write_command() function leaves its command in big_buffer. This is
- used in error responses. Initialize it in case the connection is
- rejected. */
+ /* Expand the helo_data string to find the host name to use. */
+
+ if (tf->helo_data != NULL)
+ {
+ uschar *s = expand_string(tf->helo_data);
+ if (s == NULL)
+ log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
+ "helo_data value for callout: %s", addr->address,
+ expand_string_message);
+ else active_hostname = s;
+ }
+
+ 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. */
Ustrcpy(big_buffer, "initial connection");
done =
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
'2', callout) &&
-
smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo,
- smtp_active_hostname) >= 0 &&
+ active_hostname) >= 0 &&
smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
- '2', callout) &&
+ '2', callout);
+
+ /* Failure to accept HELO is cached; this blocks the whole domain for all
+ senders. I/O errors and defer responses are not cached. */
+
+ if (!done)
+ {
+ *failure_ptr = US"mail"; /* At or before MAIL */
+ if (errno == 0 && responsebuffer[0] == '5')
+ {
+ setflag(addr, af_verify_nsfail);
+ new_domain_record.result = ccache_reject;
+ }
+ }
+
+ /* 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);
- /* If the host gave an initial error, or does not accept HELO or MAIL
- FROM:<>, arrange to cache this information, but don't record anything for an
- I/O error or a defer. Do not cache rejections when a non-empty sender has
- been used, because that blocks the whole domain for all senders. */
+ /* 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";
+ *failure_ptr = US"mail"; /* At or before MAIL */
if (errno == 0 && responsebuffer[0] == '5')
{
setflag(addr, af_verify_nsfail);
- if (from_address[0] == 0) new_domain_record.result = ccache_reject;
+ if (from_address[0] == 0)
+ new_domain_record.result = ccache_reject_mfnull;
}
}
/* Otherwise, proceed to check a "random" address (if required), then the
given address, and the postmaster address (if required). Between each check,
issue RSET, because some servers accept only one recipient after MAIL
- FROM:<>. */
+ FROM:<>.
+
+ Before doing this, set the result in the domain cache record to "accept",
+ unless its previous value was ccache_reject_mfnull. In that case, the domain
+ rejects MAIL FROM:<> and we want to continue to remember that. When that is
+ the case, we have got here only in the case of a recipient verification with
+ a non-null sender. */
else
{
- new_domain_record.result = ccache_accept;
+ new_domain_record.result =
+ (old_domain_cache_result == ccache_reject_mfnull)?
+ ccache_reject_mfnull: ccache_accept;
/* Do the random local part check first */
The value of the result field in the new_domain record is ccache_unknown if
there was an error before or with MAIL FROM:, and errno was not zero,
implying some kind of I/O error. We don't want to write the cache in that case.
-Otherwise the value is ccache_accept or ccache_reject. */
+Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
if (!callout_no_cache && new_domain_record.result != ccache_unknown)
{
vaddr->user_message = addr->user_message;
vaddr->basic_errno = addr->basic_errno;
vaddr->more_errno = addr->more_errno;
+ vaddr->p.address_data = addr->p.address_data;
+ copyflag(vaddr, addr, af_pass_message);
}
return yield;
}
{
host_item *host_list = addr->host_list;
- /* Default, if no remote transport, to NULL for the interface (=> any),
- "smtp" for the port, and "smtp" for the protocol. */
-
- transport_feedback tf = { NULL, US"smtp", US"smtp", NULL, FALSE, FALSE };
+ /* Make up some data for use in the case where there is no remote
+ transport. */
+
+ transport_feedback tf = {
+ NULL, /* interface (=> any) */
+ US"smtp", /* port */
+ US"smtp", /* protocol */
+ NULL, /* hosts */
+ US"$smtp_active_hostname", /* helo_data */
+ FALSE, /* hosts_override */
+ FALSE, /* hosts_randomize */
+ FALSE, /* gethostbyname */
+ TRUE, /* qualify_single */
+ FALSE /* search_parents */
+ };
/* If verification yielded a remote transport, we want to use that
transport's options, so as to mimic what would happen if we were really
if (addr->transport != NULL && !addr->transport->info->local)
{
- (void)(addr->transport->setup)(addr->transport, addr, &tf, NULL);
+ (void)(addr->transport->setup)(addr->transport, addr, &tf, 0, 0, NULL);
/* If the transport has hosts and the router does not, or if the
transport is configured to override the router's hosts, we must build a
}
else
{
+ int flags;
uschar *canonical_name;
host_item *host, *nexthost;
host_build_hostlist(&host_list, s, tf.hosts_randomize);
additional host items being inserted into the chain. Hence we must
save the next host first. */
+ flags = HOST_FIND_BY_A;
+ if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
+ if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
+
for (host = host_list; host != NULL; host = nexthost)
{
nexthost = host->next;
if (tf.gethostbyname ||
string_is_ip_address(host->name, NULL) != 0)
- (void)host_find_byname(host, NULL, &canonical_name, TRUE);
+ (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
else
- {
- int flags = HOST_FIND_BY_A;
- if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
- if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
(void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
&canonical_name, NULL);
- }
}
}
}
allok = FALSE;
if (f != NULL)
{
- fprintf(f, "%s%s %s", ko_prefix, address,
+ address_item *p = addr->parent;
+
+ fprintf(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));
if (addr->message != NULL)
- fprintf(f, ":\n %s", addr->message);
+ fprintf(f, ": %s", addr->message);
+ }
+
+ /* Show parents iff doing full info */
+
+ if (full_info) while (p != NULL)
+ {
+ fprintf(f, "%s\n <-- %s", cr, p->address);
+ p = p->parent;
}
fprintf(f, "%s\n", cr);
}
allok = FALSE;
if (f != NULL)
{
- fprintf(f, "%s%s cannot be resolved at this time", ko_prefix, address);
+ address_item *p = addr->parent;
+ fprintf(f, "%s%s cannot be resolved at this time", ko_prefix,
+ full_info? addr->address : address);
if (!expn && admin_user)
{
if (addr->basic_errno > 0)
- fprintf(f, ":\n %s", strerror(addr->basic_errno));
+ fprintf(f, ": %s", strerror(addr->basic_errno));
if (addr->message != NULL)
- fprintf(f, ":\n %s", addr->message);
+ fprintf(f, ": %s", addr->message);
else if (addr->basic_errno <= 0)
- fprintf(f, ":\n unknown error");
+ fprintf(f, ": unknown error");
}
+ /* Show parents iff doing full info */
+
+ if (full_info) while (p != NULL)
+ {
+ fprintf(f, "%s\n <-- %s", cr, p->address);
+ p = p->parent;
+ }
fprintf(f, "%s\n", cr);
}
if (!full_info) return copy_error(vaddr, addr, DEFER);
}
/* If we are handling EXPN, we do not want to continue to route beyond
- the top level. */
+ the top level (whose address is in "address"). */
else if (expn)
{
discarded, usually because of the use of :blackhole: in an alias file. */
if (allok && addr_local == NULL && addr_remote == NULL)
+ {
fprintf(f, "mail to %s is discarded\n", address);
+ return yield;
+ }
-else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
+for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
{
while (addr_list != NULL)
{
if(addr->p.srs_sender)
fprintf(f, " [srs = %s]", addr->p.srs_sender);
#endif
+
+ /* If the address is a duplicate, show something about it. */
+
+ if (!testflag(addr, af_pfr))
+ {
+ tree_node *tnode;
+ if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+ fprintf(f, " [duplicate, would not be delivered]");
+ else tree_add_duplicate(addr->unique, addr);
+ }
+
+ /* Now show its parents */
+
while (p != NULL)
{
fprintf(f, "\n <-- %s", p->address);
{
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);
{
uschar *verb = US"is";
uschar *t = ss;
+ uschar *tt = colon;
int len;
/* Arrange not to include any white space at the end in the
- error message. */
+ error message or the header name. */
while (t > s && isspace(t[-1])) t--;
+ while (tt > h->text && isspace(tt[-1])) tt--;
- /* Add the address which failed to the error message, since in a
+ /* Add the address that failed to the error message, since in a
header with very many addresses it is sometimes hard to spot
which one is at fault. However, limit the amount of address to
quote - cases have been seen where, for example, a missing double
}
*msgptr = string_printing(
- string_sprintf("%s: failing address in \"%.*s\" header %s: %.*s",
- errmess, colon - h->text, h->text, verb, len, s));
+ string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
+ errmess, tt - h->text, h->text, verb, len, s));
- return FAIL;
+ yield = FAIL;
+ break; /* Out of address loop */
}
/* Advance to the next address */
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;
}
h.name = ss;
h.address = NULL;
h.mx = MX_NONE;
- rc = host_find_byname(&h, NULL, NULL, FALSE);
+
+ rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
{
host_item *hh;
}
}
#endif
+
+/* Remove trailing period -- this is needed so that both arbitrary
+dnsbl keydomains and inverted addresses may be combined with the
+same format string, "%s.%s" */
+
+*(--bptr) = 0;
}
* Perform a single dnsbl lookup *
*************************************************/
-/* This function is called from verify_check_dnsbl() below.
+/* This function is called from verify_check_dnsbl() below. It is also called
+recursively from within itself when domain and domain_txt are different
+pointers, in order to get the TXT record from the alternate domain.
Arguments:
- domain the outer dnsbl domain (for debug message)
+ domain the outer dnsbl domain
+ domain_txt alternate domain to lookup TXT record on success; when the
+ same domain is to be used, domain_txt == domain (that is,
+ the pointers must be identical, not just the text)
keydomain the current keydomain (for debug message)
- query the domain to be looked up
- iplist the list of matching IP addresses
+ prepend subdomain to lookup (like keydomain, but
+ reversed if IP address)
+ iplist the list of matching IP addresses, or NULL for "any"
bitmask true if bitmask matching is wanted
- invert_result true if result to be inverted
+ match_type condition for 'succeed' result
+ 0 => Any RR in iplist (=)
+ 1 => No RR in iplist (!=)
+ 2 => All RRs in iplist (==)
+ 3 => Some RRs not in iplist (!==)
+ the two bits are defined as MT_NOT and MT_ALL
defer_return what to return for a defer
Returns: OK if lookup succeeded
*/
static int
-one_check_dnsbl(uschar *domain, uschar *keydomain, uschar *query,
- uschar *iplist, BOOL bitmask, BOOL invert_result, int defer_return)
+one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
+ uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
+ int defer_return)
{
dns_answer dnsa;
dns_scan dnss;
tree_node *t;
dnsbl_cache_block *cb;
int old_pool = store_pool;
+uschar query[256]; /* DNS domain max length */
+
+/* Construct the specific query domainname */
+
+if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
+ "(ignored): %s...", query);
+ return FAIL;
+ }
/* Look for this query in the cache. */
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;
}
}
- /* Either there was no IP list, or the record matched. Look up a TXT record
- if it hasn't previously been done. */
+ /* Either there was no IP list, or the record matched, implying that the
+ domain is on the list. We now want to find a corresponding TXT record. If an
+ alternate domain is specified for the TXT record, call this function
+ recursively to look that up; this has the side effect of re-checking that
+ there is indeed an A record at the alternate domain. */
+
+ if (domain_txt != domain)
+ return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
+ FALSE, match_type, defer_return);
+
+ /* If there is no alternate domain, look up a TXT record in the main domain
+ if it has not previously been cached. */
if (!cb->text_set)
{
If no key is given, what is looked up in the domain is the inverted IP address
of the current client host. If a key is given, it is used to construct the
-domain for the lookup. For example,
+domain for the lookup. For example:
dsn.rfc-ignorant.org/$sender_address_domain
value in $dnslist_text. We also cache everything in a tree, to optimize
multiple lookups.
+The TXT record is normally looked up in the same domain as the A record, but
+when many lists are combined in a single DNS domain, this will not be a very
+specific message. It is possible to specify a different domain for looking up
+TXT records; this is given before the main domain, comma-separated. For
+example:
+
+ dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
+ socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3
+
+The caching ensures that only one lookup in dnsbl.sorbs.net is done.
+
Note: an address for testing RBL is 192.203.178.39
Note: an address for testing DUL is 192.203.178.4
Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
{
int sep = 0;
int defer_return = FAIL;
-BOOL invert_result = FALSE;
uschar *list = *listptr;
uschar *domain;
uschar *s;
uschar buffer[1024];
-uschar query[256]; /* DNS domain max length */
uschar revadd[128]; /* Long enough for IPv6 address */
/* Indicate that the inverted IP address is not yet set up */
while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
{
int rc;
- BOOL frc;
BOOL bitmask = FALSE;
+ int match_type = 0;
+ uschar *domain_txt;
+ uschar *comma;
uschar *iplist;
uschar *key;
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
+ looking up TXT records is provided, before the main domain. Otherwise we must
+ set domain_txt == domain. */
+
+ domain_txt = domain;
+ comma = Ustrchr(domain, ',');
+ if (comma != NULL)
+ {
+ *comma++ = 0;
+ domain = comma;
}
/* Check that what we have left is a sensible domain name. There is no reason
}
}
+ /* Check the alternate domain if present */
+
+ if (domain_txt != domain) for (s = domain_txt; *s != 0; s++)
+ {
+ if (!isalnum(*s) && *s != '-' && *s != '.')
+ {
+ log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
+ "strange characters - is this right?", domain_txt);
+ break;
+ }
+ }
+
/* If there is no key string, construct the query by adding the domain name
onto the inverted host address, and perform a single DNS lookup. */
{
if (sender_host_address == NULL) return FAIL; /* can never match */
if (revadd[0] == 0) invert_address(revadd, sender_host_address);
- frc = string_format(query, sizeof(query), "%s%s", revadd, domain);
-
- if (!frc)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
- "(ignored): %s...", query);
- continue;
- }
-
- rc = one_check_dnsbl(domain, sender_host_address, query, iplist, bitmask,
- invert_result, defer_return);
-
+ rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
+ iplist, bitmask, match_type, defer_return);
if (rc == OK)
{
- dnslist_domain = string_copy(domain);
+ dnslist_domain = string_copy(domain_txt);
+ dnslist_matched = string_copy(sender_host_address);
HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
- sender_host_address, domain);
+ sender_host_address, dnslist_domain);
}
-
if (rc != FAIL) return rc; /* OK or DEFER */
}
BOOL defer = FALSE;
uschar *keydomain;
uschar keybuffer[256];
+ uschar keyrevadd[128];
while ((keydomain = string_nextinlist(&key, &keysep, keybuffer,
sizeof(keybuffer))) != NULL)
{
+ uschar *prepend = keydomain;
+
if (string_is_ip_address(keydomain, NULL) != 0)
{
- uschar keyrevadd[128];
invert_address(keyrevadd, keydomain);
- frc = string_format(query, sizeof(query), "%s%s", keyrevadd, domain);
- }
- else
- {
- frc = string_format(query, sizeof(query), "%s.%s", keydomain, domain);
- }
-
- if (!frc)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
- "(ignored): %s...", query);
- continue;
+ prepend = keyrevadd;
}
- rc = one_check_dnsbl(domain, keydomain, query, iplist, bitmask,
- invert_result, defer_return);
+ rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
+ bitmask, match_type, defer_return);
if (rc == OK)
{
- dnslist_domain = string_copy(domain);
+ dnslist_domain = string_copy(domain_txt);
+ dnslist_matched = string_copy(keydomain);
HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
- keydomain, domain);
+ keydomain, dnslist_domain);
return OK;
}