1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
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);
90 /* Look for this query in the cache. */
92 if ( (t = tree_search(dnsbl_cache, query))
93 && (cb = t->data.ptr)->expiry > time(NULL)
96 /* Previous lookup was cached */
99 HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
102 /* If not cached from a previous lookup, we must do a DNS lookup, and
103 cache the result in permanent memory. */
107 uint ttl = 3600; /* max TTL for positive cache entries */
109 store_pool = POOL_PERM;
113 HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
117 { /* Set up a tree entry to cache the lookup */
118 t = store_get(sizeof(tree_node) + qlen + 1 + 1, query);
119 Ustrcpy(t->name, query);
120 t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), GET_UNTAINTED);
121 (void)tree_insertnode(&dnsbl_cache, t);
124 /* Do the DNS lookup . */
126 HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
127 cb->rc = dns_basic_lookup(dnsa, query, T_A);
128 cb->text_set = FALSE;
132 /* If the lookup succeeded, cache the RHS address. The code allows for
133 more than one address - this was for complete generality and the possible
134 use of A6 records. However, A6 records are no longer supported. Leave the code
137 Quite apart from one A6 RR generating multiple addresses, there are DNS
138 lists that return more than one A record, so we must handle multiple
139 addresses generated in that way as well.
141 Mark the cache entry with the "now" plus the minimum of the address TTLs,
142 or the RFC 2308 negative-cache value from the SOA if none were found. */
148 dns_address ** addrp = &cb->rhs;
150 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
151 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
152 if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr)))
155 while (da->next) da = da->next;
157 if (ttl > rr->ttl) ttl = rr->ttl;
162 cb->expiry = time(NULL) + ttl;
166 /* If we didn't find any A records, change the return code. This can
167 happen when there is a CNAME record but there are no A records for what
177 /* Although there already is a neg-cache layer maintained by
178 dns_basic_lookup(), we have a dnslist cache entry allocated and
179 tree-inserted. So we may as well use it. */
181 time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
182 cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
187 cb->expiry = time(NULL) + ttl;
191 store_pool = old_pool;
192 HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
193 (int)(cb->expiry - time(NULL)));
196 /* We now have the result of the DNS lookup, either newly done, or cached
197 from a previous call. If the lookup succeeded, check against the address
198 list if there is one. This may be a positive equality list (introduced by
199 "="), a negative equality list (introduced by "!="), a positive bitmask
200 list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
202 if (cb->rc == DNS_SUCCEED)
204 dns_address * da = NULL;
205 uschar *addlist = cb->rhs->address;
207 /* For A and AAAA records, there may be multiple addresses from multiple
208 records. For A6 records (currently not expected to be used) there may be
209 multiple addresses from a single record. */
211 for (da = cb->rhs->next; da; da = da->next)
212 addlist = string_sprintf("%s, %s", addlist, da->address);
214 HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
217 /* Address list check; this can be either for equality, or via a bitmask.
218 In the latter case, all the bits must match. */
222 for (da = cb->rhs; da; da = da->next)
225 const uschar *ptr = iplist;
228 /* Handle exact matching */
232 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
233 if (Ustrcmp(CS da->address, res) == 0)
237 /* Handle bitmask matching */
244 /* At present, all known DNS blocking lists use A records, with
245 IPv4 addresses on the RHS encoding the information they return. I
246 wonder if this will linger on as the last vestige of IPv4 when IPv6
247 is ubiquitous? Anyway, for now we use paranoia code to completely
248 ignore IPv6 addresses. The default mask is 0, which always matches.
249 We change this only for IPv4 addresses in the list. */
251 if (host_aton(da->address, address) == 1)
252 if ((address[0] & 0xff000000) != 0x7f000000) /* 127.0.0.0/8 */
253 log_write(0, LOG_MAIN,
254 "DNS list lookup for %s at %s returned %s;"
255 " not in 127.0/8 and discarded",
256 keydomain, domain, da->address);
261 /* Scan the returned addresses, skipping any that are IPv6 */
263 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
264 if (host_aton(res, address) == 1)
265 if ((address[0] & mask) == address[0])
271 (a) An IP address in an any ('=') list matched, or
272 (b) No IP address in an all ('==') list matched
274 then we're done searching. */
276 if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
279 /* If da == NULL, either
281 (a) No IP address in an any ('=') list matched, or
282 (b) An IP address in an all ('==') list didn't match
284 so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
287 if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
295 res = US"was no match"; break;
297 res = US"was an exclude match"; break;
299 res = US"was an IP address that did not match"; break;
301 res = US"were no IP addresses that did not match"; break;
303 debug_printf("=> but we are not accepting this block class because\n");
304 debug_printf("=> there %s for %s%c%s\n",
306 match_type & MT_ALL ? "=" : "",
307 bitmask ? '&' : '=', iplist);
314 /* No address list check; discard any illegal returns and give up if
320 for (da = cb->rhs; da; da = da->next)
324 if ( host_aton(da->address, address) == 1 /* ipv4 */
325 && (address[0] & 0xff000000) == 0x7f000000 /* 127.0.0.0/8 */
329 log_write(0, LOG_MAIN,
330 "DNS list lookup for %s at %s returned %s;"
331 " not in 127.0/8 and discarded",
332 keydomain, domain, da->address);
341 /* Either there was no IP list, or the record matched, implying that the
342 domain is on the list. We now want to find a corresponding TXT record. If an
343 alternate domain is specified for the TXT record, call this function
344 recursively to look that up; this has the side effect of re-checking that
345 there is indeed an A record at the alternate domain. */
347 if (domain_txt != domain)
349 yield = one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
350 FALSE, match_type, defer_return);
354 /* If there is no alternate domain, look up a TXT record in the main domain
355 if it has not previously been cached. */
360 if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
361 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
362 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
363 if (rr->type == T_TXT)
365 int len = (rr->data)[0];
366 if (len > 511) len = 127;
367 store_pool = POOL_PERM;
368 cb->text = string_copyn_taint(CUS (rr->data+1), len, GET_TAINTED);
369 store_pool = old_pool;
374 dnslist_value = addlist;
375 dnslist_text = cb->text;
380 /* There was a problem with the DNS lookup */
382 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
384 log_write(L_dnslist_defer, LOG_MAIN,
385 "DNS list lookup defer (probably timeout) for %s: %s", query,
386 defer_return == OK ? US"assumed in list" :
387 defer_return == FAIL ? US"assumed not in list" :
389 yield = defer_return;
393 /* No entry was found in the DNS; continue for next domain */
397 debug_printf("DNS lookup for %s failed\n", query);
398 debug_printf("=> that means %s is not listed at %s\n",
406 store_free_dns_answer(dnsa);
413 /*************************************************
414 * Check host against DNS black lists *
415 *************************************************/
417 /* This function runs checks against a list of DNS black lists, until one
418 matches. Each item on the list can be of the form
420 domain=ip-address/key
422 The domain is the right-most domain that is used for the query, for example,
423 blackholes.mail-abuse.org. If the IP address is present, there is a match only
424 if the DNS lookup returns a matching IP address. Several addresses may be
425 given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2.
427 If no key is given, what is looked up in the domain is the inverted IP address
428 of the current client host. If a key is given, it is used to construct the
429 domain for the lookup. For example:
431 dsn.rfc-ignorant.org/$sender_address_domain
433 After finding a match in the DNS, the domain is placed in $dnslist_domain, and
434 then we check for a TXT record for an error message, and if found, save its
435 value in $dnslist_text. We also cache everything in a tree, to optimize
438 The TXT record is normally looked up in the same domain as the A record, but
439 when many lists are combined in a single DNS domain, this will not be a very
440 specific message. It is possible to specify a different domain for looking up
441 TXT records; this is given before the main domain, comma-separated. For
444 dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
445 socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3
447 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
449 Note: an address for testing RBL is 192.203.178.39
450 Note: an address for testing DUL is 192.203.178.4
451 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
455 listptr the domain/address/data list
456 log_msgptr log message on error
458 Returns: OK successful lookup (i.e. the address is on the list), or
459 lookup deferred after +include_unknown
460 FAIL name not found, or no data found for the given type, or
461 lookup deferred after +exclude_unknown (default)
462 DEFER lookup failure, if +defer_unknown was set
466 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
469 int defer_return = FAIL;
470 const uschar *list = *listptr;
472 uschar revadd[128]; /* Long enough for IPv6 address */
474 /* Indicate that the inverted IP address is not yet set up */
478 /* In case this is the first time the DNS resolver is being used. */
480 dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
482 /* Loop through all the domains supplied, until something matches */
484 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
487 BOOL bitmask = FALSE;
494 HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
496 /* Deal with special values that change the behaviour on defer */
498 if (domain[0] == '+')
500 if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
501 else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
502 else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER;
504 log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
509 /* See if there's explicit data to be looked up */
511 if ((key = Ustrchr(domain, '/'))) *key++ = 0;
513 /* See if there's a list of addresses supplied after the domain name. This is
514 introduced by an = or a & character; if preceded by = we require all matches
515 and if preceded by ! we invert the result. */
517 if (!(iplist = Ustrchr(domain, '=')))
520 iplist = Ustrchr(domain, '&');
523 if (iplist) /* Found either = or & */
525 if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */
527 match_type |= MT_NOT;
531 *iplist++ = 0; /* Terminate domain, move on */
533 /* If we found = (bitmask == FALSE), check for == or =& */
535 if (!bitmask && (*iplist == '=' || *iplist == '&'))
537 bitmask = *iplist++ == '&';
538 match_type |= MT_ALL;
543 /* If there is a comma in the domain, it indicates that a second domain for
544 looking up TXT records is provided, before the main domain. Otherwise we must
545 set domain_txt == domain. */
548 if ((comma = Ustrchr(domain, ',')))
554 /* Check that what we have left is a sensible domain name. There is no reason
555 why these domains should in fact use the same syntax as hosts and email
556 domains, but in practice they seem to. However, there is little point in
557 actually causing an error here, because that would no doubt hold up incoming
558 mail. Instead, I'll just log it. */
560 for (uschar * s = domain; *s; s++)
561 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
563 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
564 "strange characters - is this right?", domain);
568 /* Check the alternate domain if present */
570 if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
571 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
573 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
574 "strange characters - is this right?", domain_txt);
578 /* If there is no key string, construct the query by adding the domain name
579 onto the inverted host address, and perform a single DNS lookup. */
583 if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
585 *log_msgptr = string_sprintf
586 ("cannot test auto-keyed dnslists condition in %s ACL",
587 acl_wherenames[where]);
590 if (!sender_host_address) return FAIL; /* can never match */
591 if (revadd[0] == 0) invert_address(revadd, sender_host_address);
592 rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
593 iplist, bitmask, match_type, defer_return);
596 dnslist_domain = string_copy(domain_txt);
597 dnslist_matched = string_copy(sender_host_address);
598 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
599 sender_host_address, dnslist_domain);
601 if (rc != FAIL) return rc; /* OK or DEFER */
604 /* If there is a key string, it can be a list of domains or IP addresses to
605 be concatenated with the main domain. */
612 uschar keyrevadd[128];
614 while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
616 uschar *prepend = keydomain;
618 if (string_is_ip_address(keydomain, NULL) != 0)
620 invert_address(keyrevadd, keydomain);
624 rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
625 bitmask, match_type, defer_return);
628 dnslist_domain = string_copy(domain_txt);
629 dnslist_matched = string_copy(keydomain);
630 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
631 keydomain, dnslist_domain);
635 /* If the lookup deferred, remember this fact. We keep trying the rest
636 of the list to see if we get a useful result, and if we don't, we return
639 if (rc == DEFER) defer = TRUE;
640 } /* continue with next keystring domain/address */
642 if (defer) return DEFER;
644 } /* continue with next dnsdb outer domain */
651 /* End of dnsbl.c.c */