1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
9 /* Functions concerned with dnsbls */
14 /* Structure for caching DNSBL lookups */
16 typedef struct dnsbl_cache_block {
25 /* Anchor for DNSBL cache */
27 static tree_node *dnsbl_cache = NULL;
30 /* Bits for match_type in one_check_dnsbl() */
36 /*************************************************
37 * Perform a single dnsbl lookup *
38 *************************************************/
40 /* This function is called from verify_check_dnsbl() below. It is also called
41 recursively from within itself when domain and domain_txt are different
42 pointers, in order to get the TXT record from the alternate domain.
45 domain the outer dnsbl domain
46 domain_txt alternate domain to lookup TXT record on success; when the
47 same domain is to be used, domain_txt == domain (that is,
48 the pointers must be identical, not just the text)
49 keydomain the current keydomain (for debug message)
50 prepend subdomain to lookup (like keydomain, but
51 reversed if IP address)
52 iplist the list of matching IP addresses, or NULL for "any"
53 bitmask true if bitmask matching is wanted
54 match_type condition for 'succeed' result
55 0 => Any RR in iplist (=)
56 1 => No RR in iplist (!=)
57 2 => All RRs in iplist (==)
58 3 => Some RRs not in iplist (!==)
59 the two bits are defined as MT_NOT and MT_ALL
60 defer_return what to return for a defer
62 Returns: OK if lookup succeeded
67 one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
68 uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
71 dns_answer * dnsa = store_get_dns_answer();
74 dnsbl_cache_block *cb;
75 int old_pool = store_pool;
79 /* Construct the specific query domainname */
81 query = string_sprintf("%s.%s", prepend, domain);
82 if ((qlen = Ustrlen(query)) >= 256)
84 log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
85 "(ignored): %s...", query);
89 /* Look for this query in the cache. */
91 if ( (t = tree_search(dnsbl_cache, query))
92 && (cb = t->data.ptr)->expiry > time(NULL)
95 /* Previous lookup was cached */
98 HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
101 /* If not cached from a previous lookup, we must do a DNS lookup, and
102 cache the result in permanent memory. */
106 uint ttl = 3600; /* max TTL for positive cache entries */
108 store_pool = POOL_PERM;
112 HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
116 { /* Set up a tree entry to cache the lookup */
117 t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query));
118 Ustrcpy(t->name, query);
119 t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
120 (void)tree_insertnode(&dnsbl_cache, t);
123 /* Do the DNS lookup . */
125 HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
126 cb->rc = dns_basic_lookup(dnsa, query, T_A);
127 cb->text_set = FALSE;
131 /* If the lookup succeeded, cache the RHS address. The code allows for
132 more than one address - this was for complete generality and the possible
133 use of A6 records. However, A6 records are no longer supported. Leave the code
136 Quite apart from one A6 RR generating multiple addresses, there are DNS
137 lists that return more than one A record, so we must handle multiple
138 addresses generated in that way as well.
140 Mark the cache entry with the "now" plus the minimum of the address TTLs,
141 or the RFC 2308 negative-cache value from the SOA if none were found. */
147 dns_address ** addrp = &cb->rhs;
149 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
150 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
151 if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr)))
154 while (da->next) da = da->next;
156 if (ttl > rr->ttl) ttl = rr->ttl;
161 cb->expiry = time(NULL) + ttl;
165 /* If we didn't find any A records, change the return code. This can
166 happen when there is a CNAME record but there are no A records for what
176 /* Although there already is a neg-cache layer maintained by
177 dns_basic_lookup(), we have a dnslist cache entry allocated and
178 tree-inserted. So we may as well use it. */
180 time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
181 cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
186 cb->expiry = time(NULL) + ttl;
190 store_pool = old_pool;
191 HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
192 (int)(cb->expiry - time(NULL)));
195 /* We now have the result of the DNS lookup, either newly done, or cached
196 from a previous call. If the lookup succeeded, check against the address
197 list if there is one. This may be a positive equality list (introduced by
198 "="), a negative equality list (introduced by "!="), a positive bitmask
199 list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
201 if (cb->rc == DNS_SUCCEED)
203 dns_address * da = NULL;
204 uschar *addlist = cb->rhs->address;
206 /* For A and AAAA records, there may be multiple addresses from multiple
207 records. For A6 records (currently not expected to be used) there may be
208 multiple addresses from a single record. */
210 for (da = cb->rhs->next; da; da = da->next)
211 addlist = string_sprintf("%s, %s", addlist, da->address);
213 HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
216 /* Address list check; this can be either for equality, or via a bitmask.
217 In the latter case, all the bits must match. */
221 for (da = cb->rhs; da; da = da->next)
224 const uschar *ptr = iplist;
227 /* Handle exact matching */
231 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
232 if (Ustrcmp(CS da->address, res) == 0)
236 /* Handle bitmask matching */
243 /* At present, all known DNS blocking lists use A records, with
244 IPv4 addresses on the RHS encoding the information they return. I
245 wonder if this will linger on as the last vestige of IPv4 when IPv6
246 is ubiquitous? Anyway, for now we use paranoia code to completely
247 ignore IPv6 addresses. The default mask is 0, which always matches.
248 We change this only for IPv4 addresses in the list. */
250 if (host_aton(da->address, address) == 1) mask = address[0];
252 /* Scan the returned addresses, skipping any that are IPv6 */
254 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
255 if (host_aton(res, address) == 1)
256 if ((address[0] & mask) == address[0])
262 (a) An IP address in an any ('=') list matched, or
263 (b) No IP address in an all ('==') list matched
265 then we're done searching. */
267 if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
270 /* If da == NULL, either
272 (a) No IP address in an any ('=') list matched, or
273 (b) An IP address in an all ('==') list didn't match
275 so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
278 if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
286 res = US"was no match"; break;
288 res = US"was an exclude match"; break;
290 res = US"was an IP address that did not match"; break;
292 res = US"were no IP addresses that did not match"; break;
294 debug_printf("=> but we are not accepting this block class because\n");
295 debug_printf("=> there %s for %s%c%s\n",
297 match_type & MT_ALL ? "=" : "",
298 bitmask ? '&' : '=', iplist);
304 /* Either there was no IP list, or the record matched, implying that the
305 domain is on the list. We now want to find a corresponding TXT record. If an
306 alternate domain is specified for the TXT record, call this function
307 recursively to look that up; this has the side effect of re-checking that
308 there is indeed an A record at the alternate domain. */
310 if (domain_txt != domain)
311 return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
312 FALSE, match_type, defer_return);
314 /* If there is no alternate domain, look up a TXT record in the main domain
315 if it has not previously been cached. */
320 if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
321 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
322 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
323 if (rr->type == T_TXT)
325 int len = (rr->data)[0];
326 if (len > 511) len = 127;
327 store_pool = POOL_PERM;
328 cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
329 store_pool = old_pool;
334 dnslist_value = addlist;
335 dnslist_text = cb->text;
339 /* There was a problem with the DNS lookup */
341 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
343 log_write(L_dnslist_defer, LOG_MAIN,
344 "DNS list lookup defer (probably timeout) for %s: %s", query,
345 defer_return == OK ? US"assumed in list" :
346 defer_return == FAIL ? US"assumed not in list" :
351 /* No entry was found in the DNS; continue for next domain */
355 debug_printf("DNS lookup for %s failed\n", query);
356 debug_printf("=> that means %s is not listed at %s\n",
366 /*************************************************
367 * Check host against DNS black lists *
368 *************************************************/
370 /* This function runs checks against a list of DNS black lists, until one
371 matches. Each item on the list can be of the form
373 domain=ip-address/key
375 The domain is the right-most domain that is used for the query, for example,
376 blackholes.mail-abuse.org. If the IP address is present, there is a match only
377 if the DNS lookup returns a matching IP address. Several addresses may be
378 given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2.
380 If no key is given, what is looked up in the domain is the inverted IP address
381 of the current client host. If a key is given, it is used to construct the
382 domain for the lookup. For example:
384 dsn.rfc-ignorant.org/$sender_address_domain
386 After finding a match in the DNS, the domain is placed in $dnslist_domain, and
387 then we check for a TXT record for an error message, and if found, save its
388 value in $dnslist_text. We also cache everything in a tree, to optimize
391 The TXT record is normally looked up in the same domain as the A record, but
392 when many lists are combined in a single DNS domain, this will not be a very
393 specific message. It is possible to specify a different domain for looking up
394 TXT records; this is given before the main domain, comma-separated. For
397 dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
398 socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3
400 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
402 Note: an address for testing RBL is 192.203.178.39
403 Note: an address for testing DUL is 192.203.178.4
404 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
408 listptr the domain/address/data list
409 log_msgptr log message on error
411 Returns: OK successful lookup (i.e. the address is on the list), or
412 lookup deferred after +include_unknown
413 FAIL name not found, or no data found for the given type, or
414 lookup deferred after +exclude_unknown (default)
415 DEFER lookup failure, if +defer_unknown was set
419 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
422 int defer_return = FAIL;
423 const uschar *list = *listptr;
425 uschar revadd[128]; /* Long enough for IPv6 address */
427 /* Indicate that the inverted IP address is not yet set up */
431 /* In case this is the first time the DNS resolver is being used. */
433 dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
435 /* Loop through all the domains supplied, until something matches */
437 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
440 BOOL bitmask = FALSE;
447 HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
449 /* Deal with special values that change the behaviour on defer */
451 if (domain[0] == '+')
453 if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
454 else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
455 else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER;
457 log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
462 /* See if there's explicit data to be looked up */
464 if ((key = Ustrchr(domain, '/'))) *key++ = 0;
466 /* See if there's a list of addresses supplied after the domain name. This is
467 introduced by an = or a & character; if preceded by = we require all matches
468 and if preceded by ! we invert the result. */
470 if (!(iplist = Ustrchr(domain, '=')))
473 iplist = Ustrchr(domain, '&');
476 if (iplist) /* Found either = or & */
478 if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */
480 match_type |= MT_NOT;
484 *iplist++ = 0; /* Terminate domain, move on */
486 /* If we found = (bitmask == FALSE), check for == or =& */
488 if (!bitmask && (*iplist == '=' || *iplist == '&'))
490 bitmask = *iplist++ == '&';
491 match_type |= MT_ALL;
496 /* If there is a comma in the domain, it indicates that a second domain for
497 looking up TXT records is provided, before the main domain. Otherwise we must
498 set domain_txt == domain. */
501 if ((comma = Ustrchr(domain, ',')))
507 /* Check that what we have left is a sensible domain name. There is no reason
508 why these domains should in fact use the same syntax as hosts and email
509 domains, but in practice they seem to. However, there is little point in
510 actually causing an error here, because that would no doubt hold up incoming
511 mail. Instead, I'll just log it. */
513 for (uschar * s = domain; *s; s++)
514 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
516 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
517 "strange characters - is this right?", domain);
521 /* Check the alternate domain if present */
523 if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
524 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
526 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
527 "strange characters - is this right?", domain_txt);
531 /* If there is no key string, construct the query by adding the domain name
532 onto the inverted host address, and perform a single DNS lookup. */
536 if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
538 *log_msgptr = string_sprintf
539 ("cannot test auto-keyed dnslists condition in %s ACL",
540 acl_wherenames[where]);
543 if (!sender_host_address) return FAIL; /* can never match */
544 if (revadd[0] == 0) invert_address(revadd, sender_host_address);
545 rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
546 iplist, bitmask, match_type, defer_return);
549 dnslist_domain = string_copy(domain_txt);
550 dnslist_matched = string_copy(sender_host_address);
551 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
552 sender_host_address, dnslist_domain);
554 if (rc != FAIL) return rc; /* OK or DEFER */
557 /* If there is a key string, it can be a list of domains or IP addresses to
558 be concatenated with the main domain. */
565 uschar keyrevadd[128];
567 while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
569 uschar *prepend = keydomain;
571 if (string_is_ip_address(keydomain, NULL) != 0)
573 invert_address(keyrevadd, keydomain);
577 rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
578 bitmask, match_type, defer_return);
581 dnslist_domain = string_copy(domain_txt);
582 dnslist_matched = string_copy(keydomain);
583 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
584 keydomain, dnslist_domain);
588 /* If the lookup deferred, remember this fact. We keep trying the rest
589 of the list to see if we get a useful result, and if we don't, we return
592 if (rc == DEFER) defer = TRUE;
593 } /* continue with next keystring domain/address */
595 if (defer) return DEFER;
597 } /* continue with next dnsdb outer domain */
604 /* End of dnsbl.c.c */