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)
251 if ((address[0] & 0xff000000) != 0x7f000000) /* 127.0.0.0/8 */
252 log_write(0, LOG_MAIN,
253 "DNS list lookup for %s at %s returned %s;"
254 " not in 127.0/8 and discarded",
255 keydomain, domain, da->address);
260 /* Scan the returned addresses, skipping any that are IPv6 */
262 while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
263 if (host_aton(res, address) == 1)
264 if ((address[0] & mask) == address[0])
270 (a) An IP address in an any ('=') list matched, or
271 (b) No IP address in an all ('==') list matched
273 then we're done searching. */
275 if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
278 /* If da == NULL, either
280 (a) No IP address in an any ('=') list matched, or
281 (b) An IP address in an all ('==') list didn't match
283 so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
286 if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
294 res = US"was no match"; break;
296 res = US"was an exclude match"; break;
298 res = US"was an IP address that did not match"; break;
300 res = US"were no IP addresses that did not match"; break;
302 debug_printf("=> but we are not accepting this block class because\n");
303 debug_printf("=> there %s for %s%c%s\n",
305 match_type & MT_ALL ? "=" : "",
306 bitmask ? '&' : '=', iplist);
312 /* No address list check; discard any illegal returns and give up if
318 for (da = cb->rhs; da; da = da->next)
322 if ( host_aton(da->address, address) == 1 /* ipv4 */
323 && (address[0] & 0xff000000) == 0x7f000000 /* 127.0.0.0/8 */
327 log_write(0, LOG_MAIN,
328 "DNS list lookup for %s at %s returned %s;"
329 " not in 127.0/8 and discarded",
330 keydomain, domain, da->address);
332 if (!ok) return FAIL;
335 /* Either there was no IP list, or the record matched, implying that the
336 domain is on the list. We now want to find a corresponding TXT record. If an
337 alternate domain is specified for the TXT record, call this function
338 recursively to look that up; this has the side effect of re-checking that
339 there is indeed an A record at the alternate domain. */
341 if (domain_txt != domain)
342 return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
343 FALSE, match_type, defer_return);
345 /* If there is no alternate domain, look up a TXT record in the main domain
346 if it has not previously been cached. */
351 if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
352 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
353 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
354 if (rr->type == T_TXT)
356 int len = (rr->data)[0];
357 if (len > 511) len = 127;
358 store_pool = POOL_PERM;
359 cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
360 store_pool = old_pool;
365 dnslist_value = addlist;
366 dnslist_text = cb->text;
370 /* There was a problem with the DNS lookup */
372 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
374 log_write(L_dnslist_defer, LOG_MAIN,
375 "DNS list lookup defer (probably timeout) for %s: %s", query,
376 defer_return == OK ? US"assumed in list" :
377 defer_return == FAIL ? US"assumed not in list" :
382 /* No entry was found in the DNS; continue for next domain */
386 debug_printf("DNS lookup for %s failed\n", query);
387 debug_printf("=> that means %s is not listed at %s\n",
397 /*************************************************
398 * Check host against DNS black lists *
399 *************************************************/
401 /* This function runs checks against a list of DNS black lists, until one
402 matches. Each item on the list can be of the form
404 domain=ip-address/key
406 The domain is the right-most domain that is used for the query, for example,
407 blackholes.mail-abuse.org. If the IP address is present, there is a match only
408 if the DNS lookup returns a matching IP address. Several addresses may be
409 given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2.
411 If no key is given, what is looked up in the domain is the inverted IP address
412 of the current client host. If a key is given, it is used to construct the
413 domain for the lookup. For example:
415 dsn.rfc-ignorant.org/$sender_address_domain
417 After finding a match in the DNS, the domain is placed in $dnslist_domain, and
418 then we check for a TXT record for an error message, and if found, save its
419 value in $dnslist_text. We also cache everything in a tree, to optimize
422 The TXT record is normally looked up in the same domain as the A record, but
423 when many lists are combined in a single DNS domain, this will not be a very
424 specific message. It is possible to specify a different domain for looking up
425 TXT records; this is given before the main domain, comma-separated. For
428 dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
429 socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3
431 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
433 Note: an address for testing RBL is 192.203.178.39
434 Note: an address for testing DUL is 192.203.178.4
435 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
439 listptr the domain/address/data list
440 log_msgptr log message on error
442 Returns: OK successful lookup (i.e. the address is on the list), or
443 lookup deferred after +include_unknown
444 FAIL name not found, or no data found for the given type, or
445 lookup deferred after +exclude_unknown (default)
446 DEFER lookup failure, if +defer_unknown was set
450 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
453 int defer_return = FAIL;
454 const uschar *list = *listptr;
456 uschar revadd[128]; /* Long enough for IPv6 address */
458 /* Indicate that the inverted IP address is not yet set up */
462 /* In case this is the first time the DNS resolver is being used. */
464 dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
466 /* Loop through all the domains supplied, until something matches */
468 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
471 BOOL bitmask = FALSE;
478 HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
480 /* Deal with special values that change the behaviour on defer */
482 if (domain[0] == '+')
484 if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
485 else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
486 else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER;
488 log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
493 /* See if there's explicit data to be looked up */
495 if ((key = Ustrchr(domain, '/'))) *key++ = 0;
497 /* See if there's a list of addresses supplied after the domain name. This is
498 introduced by an = or a & character; if preceded by = we require all matches
499 and if preceded by ! we invert the result. */
501 if (!(iplist = Ustrchr(domain, '=')))
504 iplist = Ustrchr(domain, '&');
507 if (iplist) /* Found either = or & */
509 if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */
511 match_type |= MT_NOT;
515 *iplist++ = 0; /* Terminate domain, move on */
517 /* If we found = (bitmask == FALSE), check for == or =& */
519 if (!bitmask && (*iplist == '=' || *iplist == '&'))
521 bitmask = *iplist++ == '&';
522 match_type |= MT_ALL;
527 /* If there is a comma in the domain, it indicates that a second domain for
528 looking up TXT records is provided, before the main domain. Otherwise we must
529 set domain_txt == domain. */
532 if ((comma = Ustrchr(domain, ',')))
538 /* Check that what we have left is a sensible domain name. There is no reason
539 why these domains should in fact use the same syntax as hosts and email
540 domains, but in practice they seem to. However, there is little point in
541 actually causing an error here, because that would no doubt hold up incoming
542 mail. Instead, I'll just log it. */
544 for (uschar * s = domain; *s; s++)
545 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
547 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
548 "strange characters - is this right?", domain);
552 /* Check the alternate domain if present */
554 if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
555 if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
557 log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
558 "strange characters - is this right?", domain_txt);
562 /* If there is no key string, construct the query by adding the domain name
563 onto the inverted host address, and perform a single DNS lookup. */
567 if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
569 *log_msgptr = string_sprintf
570 ("cannot test auto-keyed dnslists condition in %s ACL",
571 acl_wherenames[where]);
574 if (!sender_host_address) return FAIL; /* can never match */
575 if (revadd[0] == 0) invert_address(revadd, sender_host_address);
576 rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
577 iplist, bitmask, match_type, defer_return);
580 dnslist_domain = string_copy(domain_txt);
581 dnslist_matched = string_copy(sender_host_address);
582 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
583 sender_host_address, dnslist_domain);
585 if (rc != FAIL) return rc; /* OK or DEFER */
588 /* If there is a key string, it can be a list of domains or IP addresses to
589 be concatenated with the main domain. */
596 uschar keyrevadd[128];
598 while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
600 uschar *prepend = keydomain;
602 if (string_is_ip_address(keydomain, NULL) != 0)
604 invert_address(keyrevadd, keydomain);
608 rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
609 bitmask, match_type, defer_return);
612 dnslist_domain = string_copy(domain_txt);
613 dnslist_matched = string_copy(keydomain);
614 HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
615 keydomain, dnslist_domain);
619 /* If the lookup deferred, remember this fact. We keep trying the rest
620 of the list to see if we get a useful result, and if we don't, we return
623 if (rc == DEFER) defer = TRUE;
624 } /* continue with next keystring domain/address */
626 if (defer) return DEFER;
628 } /* continue with next dnsdb outer domain */
635 /* End of dnsbl.c.c */