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. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
10 /* Functions concerned with dnsbls */
15 /* Structure for caching DNSBL lookups */
17 typedef struct dnsbl_cache_block {
26 /* Anchor for DNSBL cache */
28 static tree_node *dnsbl_cache = NULL;
31 /* Bits for match_type in one_check_dnsbl() */
37 /*************************************************
38 * Perform a single dnsbl lookup *
39 *************************************************/
41 /* This function is called from verify_check_dnsbl() below. It is also called
42 recursively from within itself when domain and domain_txt are different
43 pointers, in order to get the TXT record from the alternate domain.
46 domain the outer dnsbl domain
47 domain_txt alternate domain to lookup TXT record on success; when the
48 same domain is to be used, domain_txt == domain (that is,
49 the pointers must be identical, not just the text)
50 keydomain the current keydomain (for debug message)
51 prepend subdomain to lookup (like keydomain, but
52 reversed if IP address)
53 iplist the list of matching IP addresses, or NULL for "any"
54 bitmask true if bitmask matching is wanted
55 match_type condition for 'succeed' result
56 0 => Any RR in iplist (=)
57 1 => No RR in iplist (!=)
58 2 => All RRs in iplist (==)
59 3 => Some RRs not in iplist (!==)
60 the two bits are defined as MT_NOT and MT_ALL
61 defer_return what to return for a defer
63 Returns: OK if lookup succeeded
68 one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
69 uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
72 dns_answer * dnsa = store_get_dns_answer();
75 dnsbl_cache_block *cb;
76 int old_pool = store_pool;
80 /* Construct the specific query domainname */
82 query = string_sprintf("%s.%s", prepend, domain);
83 if ((qlen = Ustrlen(query)) >= 256)
85 log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
86 "(ignored): %s...", query);
91 /* Look for this query in the cache. */
93 if ( (t = tree_search(dnsbl_cache, query))
94 && (cb = t->data.ptr)->expiry > time(NULL)
97 /* Previous lookup was cached */
100 HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
103 /* If not cached from a previous lookup, we must do a DNS lookup, and
104 cache the result in permanent memory. */
108 uint ttl = 3600; /* max TTL for positive cache entries */
110 store_pool = POOL_PERM;
114 HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
118 { /* Set up a tree entry to cache the lookup */
119 t = store_get(sizeof(tree_node) + qlen + 1 + 1, query);
120 Ustrcpy(t->name, query);
121 t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), GET_UNTAINTED);
122 (void)tree_insertnode(&dnsbl_cache, t);
125 /* Do the DNS lookup . */
127 HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
128 cb->rc = dns_basic_lookup(dnsa, query, T_A);
129 cb->text_set = FALSE;
133 /* If the lookup succeeded, cache the RHS address. The code allows for
134 more than one address - this was for complete generality and the possible
135 use of A6 records. However, A6 records are no longer supported. Leave the code
138 Quite apart from one A6 RR generating multiple addresses, there are DNS
139 lists that return more than one A record, so we must handle multiple
140 addresses generated in that way as well.
142 Mark the cache entry with the "now" plus the minimum of the address TTLs,
143 or the RFC 2308 negative-cache value from the SOA if none were found. */
149 dns_address ** addrp = &cb->rhs;
151 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
152 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
153 if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr)))
156 while (da->next) da = da->next;
158 if (ttl > rr->ttl) ttl = rr->ttl;
163 cb->expiry = time(NULL) + ttl;
167 /* If we didn't find any A records, change the return code. This can
168 happen when there is a CNAME record but there are no A records for what
178 /* Although there already is a neg-cache layer maintained by
179 dns_basic_lookup(), we have a dnslist cache entry allocated and
180 tree-inserted. So we may as well use it. */
182 time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
183 cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
188 cb->expiry = time(NULL) + ttl;
192 store_pool = old_pool;
193 HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
194 (int)(cb->expiry - time(NULL)));
197 /* We now have the result of the DNS lookup, either newly done, or cached
198 from a previous call. If the lookup succeeded, check against the address
199 list if there is one. This may be a positive equality list (introduced by
200 "="), a negative equality list (introduced by "!="), a positive bitmask
201 list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
203 if (cb->rc == DNS_SUCCEED)
205 dns_address * da = NULL;
206 uschar *addlist = cb->rhs->address;
208 /* For A and AAAA records, there may be multiple addresses from multiple
209 records. For A6 records (currently not expected to be used) there may be
210 multiple addresses from a single record. */
212 for (da = cb->rhs->next; da; da = da->next)
213 addlist = string_sprintf("%s, %s", addlist, da->address);
215 HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
218 /* Address list check; this can be either for equality, or via a bitmask.
219 In the latter case, all the bits must match. */
223 for (da = cb->rhs; da; da = da->next)
226 const uschar *ptr = iplist;
229 /* Handle exact matching */
233 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
234 if (Ustrcmp(CS da->address, res) == 0)
238 /* Handle bitmask matching */
245 /* At present, all known DNS blocking lists use A records, with
246 IPv4 addresses on the RHS encoding the information they return. I
247 wonder if this will linger on as the last vestige of IPv4 when IPv6
248 is ubiquitous? Anyway, for now we use paranoia code to completely
249 ignore IPv6 addresses. The default mask is 0, which always matches.
250 We change this only for IPv4 addresses in the list. */
252 if (host_aton(da->address, address) == 1)
253 if ((address[0] & 0xff000000) != 0x7f000000) /* 127.0.0.0/8 */
254 log_write(0, LOG_MAIN,
255 "DNS list lookup for %s at %s returned %s;"
256 " not in 127.0/8 and discarded",
257 keydomain, domain, da->address);
262 /* Scan the returned addresses, skipping any that are IPv6 */
264 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
265 if (host_aton(res, address) == 1)
266 if ((address[0] & mask) == address[0])
272 (a) An IP address in an any ('=') list matched, or
273 (b) No IP address in an all ('==') list matched
275 then we're done searching. */
277 if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
280 /* If da == NULL, either
282 (a) No IP address in an any ('=') list matched, or
283 (b) An IP address in an all ('==') list didn't match
285 so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
288 if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
296 res = US"was no match"; break;
298 res = US"was an exclude match"; break;
300 res = US"was an IP address that did not match"; break;
302 res = US"were no IP addresses that did not match"; break;
304 debug_printf("=> but we are not accepting this block class because\n");
305 debug_printf("=> there %s for %s%c%s\n",
307 match_type & MT_ALL ? "=" : "",
308 bitmask ? '&' : '=', iplist);
315 /* No address list check; discard any illegal returns and give up if
321 for (da = cb->rhs; da; da = da->next)
325 if ( host_aton(da->address, address) == 1 /* ipv4 */
326 && (address[0] & 0xff000000) == 0x7f000000 /* 127.0.0.0/8 */
330 log_write(0, LOG_MAIN,
331 "DNS list lookup for %s at %s returned %s;"
332 " not in 127.0/8 and discarded",
333 keydomain, domain, da->address);
342 /* Either there was no IP list, or the record matched, implying that the
343 domain is on the list. We now want to find a corresponding TXT record. If an
344 alternate domain is specified for the TXT record, call this function
345 recursively to look that up; this has the side effect of re-checking that
346 there is indeed an A record at the alternate domain. */
348 if (domain_txt != domain)
350 yield = one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
351 FALSE, match_type, defer_return);
355 /* If there is no alternate domain, look up a TXT record in the main domain
356 if it has not previously been cached. */
361 if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
362 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
363 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
364 if (rr->type == T_TXT)
366 int len = (rr->data)[0];
367 if (len > 511) len = 127;
368 store_pool = POOL_PERM;
369 cb->text = string_copyn_taint(CUS (rr->data+1), len, GET_TAINTED);
370 store_pool = old_pool;
375 dnslist_value = addlist;
376 dnslist_text = cb->text;
381 /* There was a problem with the DNS lookup */
383 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
385 log_write(L_dnslist_defer, LOG_MAIN,
386 "DNS list lookup defer (probably timeout) for %s: %s", query,
387 defer_return == OK ? US"assumed in list" :
388 defer_return == FAIL ? US"assumed not in list" :
390 yield = defer_return;
394 /* No entry was found in the DNS; continue for next domain */
398 debug_printf("DNS lookup for %s failed\n", query);
399 debug_printf("=> that means %s is not listed at %s\n",
407 store_free_dns_answer(dnsa);
414 /*************************************************
415 * Check host against DNS black lists *
416 *************************************************/
418 /* This function runs checks against a list of DNS black lists, until one
419 matches. Each item on the list can be of the form
421 domain=ip-address/key
423 The domain is the right-most domain that is used for the query, for example,
424 blackholes.mail-abuse.org. If the IP address is present, there is a match only
425 if the DNS lookup returns a matching IP address. Several addresses may be
426 given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2.
428 If no key is given, what is looked up in the domain is the inverted IP address
429 of the current client host. If a key is given, it is used to construct the
430 domain for the lookup. For example:
432 dsn.rfc-ignorant.org/$sender_address_domain
434 After finding a match in the DNS, the domain is placed in $dnslist_domain, and
435 then we check for a TXT record for an error message, and if found, save its
436 value in $dnslist_text. We also cache everything in a tree, to optimize
439 The TXT record is normally looked up in the same domain as the A record, but
440 when many lists are combined in a single DNS domain, this will not be a very
441 specific message. It is possible to specify a different domain for looking up
442 TXT records; this is given before the main domain, comma-separated. For
445 dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
446 socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3
448 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
450 Note: an address for testing RBL is 192.203.178.39
451 Note: an address for testing DUL is 192.203.178.4
452 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
456 listptr the domain/address/data list
457 log_msgptr log message on error
459 Returns: OK successful lookup (i.e. the address is on the list), or
460 lookup deferred after +include_unknown
461 FAIL name not found, or no data found for the given type, or
462 lookup deferred after +exclude_unknown (default)
463 DEFER lookup failure, if +defer_unknown was set
467 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
470 int defer_return = FAIL;
471 const uschar *list = *listptr;
473 uschar revadd[128]; /* Long enough for IPv6 address */
475 /* Indicate that the inverted IP address is not yet set up */
479 /* In case this is the first time the DNS resolver is being used. */
481 dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
483 /* Loop through all the domains supplied, until something matches */
485 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
488 BOOL bitmask = FALSE;
495 HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
497 /* Deal with special values that change the behaviour on defer */
499 if (domain[0] == '+')
501 if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
502 else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
503 else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER;
505 log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
510 /* See if there's explicit data to be looked up */
512 if ((key = Ustrchr(domain, '/'))) *key++ = 0;
514 /* See if there's a list of addresses supplied after the domain name. This is
515 introduced by an = or a & character; if preceded by = we require all matches
516 and if preceded by ! we invert the result. */
518 if (!(iplist = Ustrchr(domain, '=')))
521 iplist = Ustrchr(domain, '&');
524 if (iplist) /* Found either = or & */
526 if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */
528 match_type |= MT_NOT;
532 *iplist++ = 0; /* Terminate domain, move on */
534 /* If we found = (bitmask == FALSE), check for == or =& */
536 if (!bitmask && (*iplist == '=' || *iplist == '&'))
538 bitmask = *iplist++ == '&';
539 match_type |= MT_ALL;
544 /* If there is a comma in the domain, it indicates that a second domain for
545 looking up TXT records is provided, before the main domain. Otherwise we must
546 set domain_txt == domain. */
549 if ((comma = Ustrchr(domain, ',')))
555 /* Check that what we have left is a sensible domain name. There is no reason
556 why these domains should in fact use the same syntax as hosts and email
557 domains, but in practice they seem to. However, there is little point in
558 actually causing an error here, because that would no doubt hold up incoming
559 mail. Instead, I'll just log it. */
561 for (uschar * s = domain; *s; s++)
562 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
564 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
565 "strange characters - is this right?", domain);
569 /* Check the alternate domain if present */
571 if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
572 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
574 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
575 "strange characters - is this right?", domain_txt);
579 /* If there is no key string, construct the query by adding the domain name
580 onto the inverted host address, and perform a single DNS lookup. */
584 if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
586 *log_msgptr = string_sprintf
587 ("cannot test auto-keyed dnslists condition in %s ACL",
588 acl_wherenames[where]);
591 if (!sender_host_address) return FAIL; /* can never match */
592 if (revadd[0] == 0) invert_address(revadd, sender_host_address);
593 rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
594 iplist, bitmask, match_type, defer_return);
597 dnslist_domain = string_copy(domain_txt);
598 dnslist_matched = string_copy(sender_host_address);
599 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
600 sender_host_address, dnslist_domain);
602 if (rc != FAIL) return rc; /* OK or DEFER */
605 /* If there is a key string, it can be a list of domains or IP addresses to
606 be concatenated with the main domain. */
613 uschar keyrevadd[128];
615 while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
617 uschar *prepend = keydomain;
619 if (string_is_ip_address(keydomain, NULL) != 0)
621 invert_address(keyrevadd, keydomain);
625 rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
626 bitmask, match_type, defer_return);
629 dnslist_domain = string_copy(domain_txt);
630 dnslist_matched = string_copy(keydomain);
631 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
632 keydomain, dnslist_domain);
636 /* If the lookup deferred, remember this fact. We keep trying the rest
637 of the list to see if we get a useful result, and if we don't, we return
640 if (rc == DEFER) defer = TRUE;
641 } /* continue with next keystring domain/address */
643 if (defer) return DEFER;
645 } /* continue with next dnsdb outer domain */
652 /* End of dnsbl.c.c */