From: Jeremy Harris Date: Mon, 10 Aug 2020 18:51:18 +0000 (+0100) Subject: Build: Split out dnsbl code X-Git-Tag: exim-4.95-RC0~274 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/d4095f83496094d7d8649cc412536f69d1cfcb6a Build: Split out dnsbl code --- diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 9eed7b0b9..77fb34742 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -484,7 +484,7 @@ OBJ_LOOKUPS = lookups/lf_quote.o lookups/lf_check_file.o lookups/lf_sqlperform.o OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \ directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \ - filtertest.o globals.o dkim.o dkim_transport.o hash.o \ + filtertest.o globals.o dkim.o dkim_transport.o dnsbl.o hash.o \ header.o host.o ip.o log.o lss.o match.o md5.o moan.o \ os.o parse.o queue.o \ rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \ @@ -774,6 +774,7 @@ debug.o: $(HDRS) debug.c deliver.o: $(HDRS) transports/smtp.h deliver.c directory.o: $(HDRS) directory.c dns.o: $(HDRS) dns.c +dnsbl.o: $(HDRS) dnsbl.c enq.o: $(HDRS) enq.c exim.o: $(HDRS) exim.c expand.o: $(HDRS) expand.c diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 784247d99..54ca7fb61 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -100,7 +100,7 @@ for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \ macros.h mytypes.h osfunctions.h store.h structs.h lookupapi.h sha_ver.h \ \ acl.c buildconfig.c base64.c child.c crypt16.c daemon.c dbfn.c debug.c \ - deliver.c directory.c dns.c drtables.c dummies.c enq.c exim.c \ + deliver.c directory.c dns.c dnsbl.c drtables.c dummies.c enq.c exim.c \ exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c \ globals.c hash.c header.c host.c ip.c log.c lss.c match.c md5.c moan.c \ parse.c perl.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \ diff --git a/src/src/dnsbl.c b/src/src/dnsbl.c new file mode 100644 index 000000000..d3afd5cf8 --- /dev/null +++ b/src/src/dnsbl.c @@ -0,0 +1,604 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Functions concerned with dnsbls */ + + +#include "exim.h" + +/* Structure for caching DNSBL lookups */ + +typedef struct dnsbl_cache_block { + time_t expiry; + dns_address *rhs; + uschar *text; + int rc; + BOOL text_set; +} dnsbl_cache_block; + + +/* Anchor for DNSBL cache */ + +static tree_node *dnsbl_cache = NULL; + + +/* Bits for match_type in one_check_dnsbl() */ + +#define MT_NOT 1 +#define MT_ALL 2 + + +/************************************************* +* Perform a single dnsbl lookup * +*************************************************/ + +/* 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 + 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) + 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 + 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 + FAIL if not +*/ + +static int +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 = store_get_dns_answer(); +dns_scan dnss; +tree_node *t; +dnsbl_cache_block *cb; +int old_pool = store_pool; +uschar * query; +int qlen; + +/* Construct the specific query domainname */ + +query = string_sprintf("%s.%s", prepend, domain); +if ((qlen = Ustrlen(query)) >= 256) + { + 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 ( (t = tree_search(dnsbl_cache, query)) + && (cb = t->data.ptr)->expiry > time(NULL) + ) + +/* Previous lookup was cached */ + + { + HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n"); + } + +/* If not cached from a previous lookup, we must do a DNS lookup, and +cache the result in permanent memory. */ + +else + { + uint ttl = 3600; /* max TTL for positive cache entries */ + + store_pool = POOL_PERM; + + if (t) + { + HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; "); + } + + else + { /* Set up a tree entry to cache the lookup */ + t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query)); + Ustrcpy(t->name, query); + t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE); + (void)tree_insertnode(&dnsbl_cache, t); + } + + /* Do the DNS lookup . */ + + HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query); + cb->rc = dns_basic_lookup(dnsa, query, T_A); + cb->text_set = FALSE; + cb->text = NULL; + cb->rhs = NULL; + + /* If the lookup succeeded, cache the RHS address. The code allows for + more than one address - this was for complete generality and the possible + use of A6 records. However, A6 records are no longer supported. Leave the code + here, just in case. + + Quite apart from one A6 RR generating multiple addresses, there are DNS + lists that return more than one A record, so we must handle multiple + addresses generated in that way as well. + + Mark the cache entry with the "now" plus the minimum of the address TTLs, + or the RFC 2308 negative-cache value from the SOA if none were found. */ + + switch (cb->rc) + { + case DNS_SUCCEED: + { + dns_address ** addrp = &cb->rhs; + dns_address * da; + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr))) + { + *addrp = da; + while (da->next) da = da->next; + addrp = &da->next; + if (ttl > rr->ttl) ttl = rr->ttl; + } + + if (cb->rhs) + { + cb->expiry = time(NULL) + ttl; + break; + } + + /* If we didn't find any A records, change the return code. This can + happen when there is a CNAME record but there are no A records for what + it points to. */ + + cb->rc = DNS_NODATA; + } + /*FALLTHROUGH*/ + + case DNS_NOMATCH: + case DNS_NODATA: + { + /* Although there already is a neg-cache layer maintained by + dns_basic_lookup(), we have a dnslist cache entry allocated and + tree-inserted. So we may as well use it. */ + + time_t soa_negttl = dns_expire_from_soa(dnsa, T_A); + cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl; + break; + } + + default: + cb->expiry = time(NULL) + ttl; + break; + } + + store_pool = old_pool; + HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n", + (int)(cb->expiry - time(NULL))); + } + +/* We now have the result of the DNS lookup, either newly done, or cached +from a previous call. If the lookup succeeded, check against the address +list if there is one. This may be a positive equality list (introduced by +"="), a negative equality list (introduced by "!="), a positive bitmask +list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/ + +if (cb->rc == DNS_SUCCEED) + { + dns_address * da = NULL; + uschar *addlist = cb->rhs->address; + + /* For A and AAAA records, there may be multiple addresses from multiple + records. For A6 records (currently not expected to be used) there may be + multiple addresses from a single record. */ + + for (da = cb->rhs->next; da; da = da->next) + addlist = string_sprintf("%s, %s", addlist, da->address); + + HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n", + query, addlist); + + /* Address list check; this can be either for equality, or via a bitmask. + In the latter case, all the bits must match. */ + + if (iplist) + { + for (da = cb->rhs; da; da = da->next) + { + int ipsep = ','; + const uschar *ptr = iplist; + uschar *res; + + /* Handle exact matching */ + + if (!bitmask) + { + while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0))) + if (Ustrcmp(CS da->address, res) == 0) + break; + } + + /* Handle bitmask matching */ + + else + { + int address[4]; + int mask = 0; + + /* At present, all known DNS blocking lists use A records, with + IPv4 addresses on the RHS encoding the information they return. I + wonder if this will linger on as the last vestige of IPv4 when IPv6 + is ubiquitous? Anyway, for now we use paranoia code to completely + ignore IPv6 addresses. The default mask is 0, which always matches. + We change this only for IPv4 addresses in the list. */ + + if (host_aton(da->address, address) == 1) mask = address[0]; + + /* Scan the returned addresses, skipping any that are IPv6 */ + + while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0))) + if (host_aton(res, address) == 1) + if ((address[0] & mask) == address[0]) + break; + } + + /* If either + + (a) An IP address in an any ('=') list matched, or + (b) No IP address in an all ('==') list matched + + then we're done searching. */ + + if (((match_type & MT_ALL) != 0) == (res == NULL)) break; + } + + /* If da == NULL, either + + (a) No IP address in an any ('=') list matched, or + (b) An IP address in an all ('==') list didn't match + + so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on + the list. */ + + 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 %s for %s%c%s\n", + res, + match_type & MT_ALL ? "=" : "", + bitmask ? '&' : '=', iplist); + } + return FAIL; + } + } + + /* 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) + { + cb->text_set = TRUE; + if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED) + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + if (rr->type == T_TXT) + { + int len = (rr->data)[0]; + if (len > 511) len = 127; + store_pool = POOL_PERM; + cb->text = string_sprintf("%.*s", len, CUS (rr->data+1)); + store_pool = old_pool; + break; + } + } + + dnslist_value = addlist; + dnslist_text = cb->text; + return OK; + } + +/* There was a problem with the DNS lookup */ + +if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA) + { + log_write(L_dnslist_defer, LOG_MAIN, + "DNS list lookup defer (probably timeout) for %s: %s", query, + defer_return == OK ? US"assumed in list" : + defer_return == FAIL ? US"assumed not in list" : + US"returned DEFER"); + return defer_return; + } + +/* No entry was found in the DNS; continue for next domain */ + +HDEBUG(D_dnsbl) + { + debug_printf("DNS lookup for %s failed\n", query); + debug_printf("=> that means %s is not listed at %s\n", + keydomain, domain); + } + +return FAIL; +} + + + + +/************************************************* +* Check host against DNS black lists * +*************************************************/ + +/* This function runs checks against a list of DNS black lists, until one +matches. Each item on the list can be of the form + + domain=ip-address/key + +The domain is the right-most domain that is used for the query, for example, +blackholes.mail-abuse.org. If the IP address is present, there is a match only +if the DNS lookup returns a matching IP address. Several addresses may be +given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2. + +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: + + dsn.rfc-ignorant.org/$sender_address_domain + +After finding a match in the DNS, the domain is placed in $dnslist_domain, and +then we check for a TXT record for an error message, and if found, save its +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 + +Arguments: + where the acl type + listptr the domain/address/data list + log_msgptr log message on error + +Returns: OK successful lookup (i.e. the address is on the list), or + lookup deferred after +include_unknown + FAIL name not found, or no data found for the given type, or + lookup deferred after +exclude_unknown (default) + DEFER lookup failure, if +defer_unknown was set +*/ + +int +verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr) +{ +int sep = 0; +int defer_return = FAIL; +const uschar *list = *listptr; +uschar *domain; +uschar revadd[128]; /* Long enough for IPv6 address */ + +/* Indicate that the inverted IP address is not yet set up */ + +revadd[0] = 0; + +/* In case this is the first time the DNS resolver is being used. */ + +dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */ + +/* Loop through all the domains supplied, until something matches */ + +while ((domain = string_nextinlist(&list, &sep, NULL, 0))) + { + int rc; + BOOL bitmask = FALSE; + int match_type = 0; + uschar *domain_txt; + uschar *comma; + uschar *iplist; + uschar *key; + + HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain); + + /* Deal with special values that change the behaviour on defer */ + + if (domain[0] == '+') + { + if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK; + else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL; + else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER; + else + log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s", + domain); + continue; + } + + /* See if there's explicit data to be looked up */ + + if ((key = Ustrchr(domain, '/'))) *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 require all matches + and if preceded by ! we invert the result. */ + + if (!(iplist = Ustrchr(domain, '='))) + { + bitmask = TRUE; + iplist = Ustrchr(domain, '&'); + } + + if (iplist) /* Found either = or & */ + { + if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */ + { + match_type |= MT_NOT; + iplist[-1] = 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; + if ((comma = Ustrchr(domain, ','))) + { + *comma++ = 0; + domain = comma; + } + + /* Check that what we have left is a sensible domain name. There is no reason + why these domains should in fact use the same syntax as hosts and email + domains, but in practice they seem to. However, there is little point in + actually causing an error here, because that would no doubt hold up incoming + mail. Instead, I'll just log it. */ + + for (uschar * s = domain; *s; s++) + if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') + { + log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " + "strange characters - is this right?", domain); + break; + } + + /* Check the alternate domain if present */ + + if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++) + if (!isalnum(*s) && *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 (!key) + { + if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP) + { + *log_msgptr = string_sprintf + ("cannot test auto-keyed dnslists condition in %s ACL", + acl_wherenames[where]); + return ERROR; + } + if (!sender_host_address) return FAIL; /* can never match */ + if (revadd[0] == 0) invert_address(revadd, sender_host_address); + 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_txt); + dnslist_matched = string_copy(sender_host_address); + HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", + sender_host_address, dnslist_domain); + } + if (rc != FAIL) return rc; /* OK or DEFER */ + } + + /* If there is a key string, it can be a list of domains or IP addresses to + be concatenated with the main domain. */ + + else + { + int keysep = 0; + BOOL defer = FALSE; + uschar *keydomain; + uschar keyrevadd[128]; + + while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0))) + { + uschar *prepend = keydomain; + + if (string_is_ip_address(keydomain, NULL) != 0) + { + invert_address(keyrevadd, keydomain); + prepend = keyrevadd; + } + + rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist, + bitmask, match_type, defer_return); + if (rc == OK) + { + 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, dnslist_domain); + return OK; + } + + /* If the lookup deferred, remember this fact. We keep trying the rest + of the list to see if we get a useful result, and if we don't, we return + DEFER at the end. */ + + if (rc == DEFER) defer = TRUE; + } /* continue with next keystring domain/address */ + + if (defer) return DEFER; + } + } /* continue with next dnsdb outer domain */ + +return FAIL; +} + +/* vi: aw ai sw=2 +*/ +/* End of dnsbl.c.c */ diff --git a/src/src/verify.c b/src/src/verify.c index a0ab1b873..efc05fcf1 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -19,27 +19,6 @@ static smtp_context ctctx; uschar ctbuffer[8192]; -/* Structure for caching DNSBL lookups */ - -typedef struct dnsbl_cache_block { - time_t expiry; - dns_address *rhs; - uschar *text; - int rc; - BOOL text_set; -} dnsbl_cache_block; - - -/* Anchor for DNSBL cache */ - -static tree_node *dnsbl_cache = NULL; - - -/* Bits for match_type in one_check_dnsbl() */ - -#define MT_NOT 1 -#define MT_ALL 2 - static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int); @@ -3365,575 +3344,6 @@ same format string, "%s.%s" */ -/************************************************* -* Perform a single dnsbl lookup * -*************************************************/ - -/* 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 - 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) - 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 - 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 - FAIL if not -*/ - -static int -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 = store_get_dns_answer(); -dns_scan dnss; -tree_node *t; -dnsbl_cache_block *cb; -int old_pool = store_pool; -uschar * query; -int qlen; - -/* Construct the specific query domainname */ - -query = string_sprintf("%s.%s", prepend, domain); -if ((qlen = Ustrlen(query)) >= 256) - { - 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 ( (t = tree_search(dnsbl_cache, query)) - && (cb = t->data.ptr)->expiry > time(NULL) - ) - -/* Previous lookup was cached */ - - { - HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n"); - } - -/* If not cached from a previous lookup, we must do a DNS lookup, and -cache the result in permanent memory. */ - -else - { - uint ttl = 3600; /* max TTL for positive cache entries */ - - store_pool = POOL_PERM; - - if (t) - { - HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; "); - } - - else - { /* Set up a tree entry to cache the lookup */ - t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query)); - Ustrcpy(t->name, query); - t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE); - (void)tree_insertnode(&dnsbl_cache, t); - } - - /* Do the DNS lookup . */ - - HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query); - cb->rc = dns_basic_lookup(dnsa, query, T_A); - cb->text_set = FALSE; - cb->text = NULL; - cb->rhs = NULL; - - /* If the lookup succeeded, cache the RHS address. The code allows for - more than one address - this was for complete generality and the possible - use of A6 records. However, A6 records are no longer supported. Leave the code - here, just in case. - - Quite apart from one A6 RR generating multiple addresses, there are DNS - lists that return more than one A record, so we must handle multiple - addresses generated in that way as well. - - Mark the cache entry with the "now" plus the minimum of the address TTLs, - or the RFC 2308 negative-cache value from the SOA if none were found. */ - - switch (cb->rc) - { - case DNS_SUCCEED: - { - dns_address ** addrp = &cb->rhs; - dns_address * da; - for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr))) - { - *addrp = da; - while (da->next) da = da->next; - addrp = &da->next; - if (ttl > rr->ttl) ttl = rr->ttl; - } - - if (cb->rhs) - { - cb->expiry = time(NULL) + ttl; - break; - } - - /* If we didn't find any A records, change the return code. This can - happen when there is a CNAME record but there are no A records for what - it points to. */ - - cb->rc = DNS_NODATA; - } - /*FALLTHROUGH*/ - - case DNS_NOMATCH: - case DNS_NODATA: - { - /* Although there already is a neg-cache layer maintained by - dns_basic_lookup(), we have a dnslist cache entry allocated and - tree-inserted. So we may as well use it. */ - - time_t soa_negttl = dns_expire_from_soa(dnsa, T_A); - cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl; - break; - } - - default: - cb->expiry = time(NULL) + ttl; - break; - } - - store_pool = old_pool; - HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n", - (int)(cb->expiry - time(NULL))); - } - -/* We now have the result of the DNS lookup, either newly done, or cached -from a previous call. If the lookup succeeded, check against the address -list if there is one. This may be a positive equality list (introduced by -"="), a negative equality list (introduced by "!="), a positive bitmask -list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/ - -if (cb->rc == DNS_SUCCEED) - { - dns_address * da = NULL; - uschar *addlist = cb->rhs->address; - - /* For A and AAAA records, there may be multiple addresses from multiple - records. For A6 records (currently not expected to be used) there may be - multiple addresses from a single record. */ - - for (da = cb->rhs->next; da; da = da->next) - addlist = string_sprintf("%s, %s", addlist, da->address); - - HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n", - query, addlist); - - /* Address list check; this can be either for equality, or via a bitmask. - In the latter case, all the bits must match. */ - - if (iplist) - { - for (da = cb->rhs; da; da = da->next) - { - int ipsep = ','; - const uschar *ptr = iplist; - uschar *res; - - /* Handle exact matching */ - - if (!bitmask) - { - while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0))) - if (Ustrcmp(CS da->address, res) == 0) - break; - } - - /* Handle bitmask matching */ - - else - { - int address[4]; - int mask = 0; - - /* At present, all known DNS blocking lists use A records, with - IPv4 addresses on the RHS encoding the information they return. I - wonder if this will linger on as the last vestige of IPv4 when IPv6 - is ubiquitous? Anyway, for now we use paranoia code to completely - ignore IPv6 addresses. The default mask is 0, which always matches. - We change this only for IPv4 addresses in the list. */ - - if (host_aton(da->address, address) == 1) mask = address[0]; - - /* Scan the returned addresses, skipping any that are IPv6 */ - - while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0))) - { - if (host_aton(res, address) != 1) continue; - if ((address[0] & mask) == address[0]) break; - } - } - - /* If either - - (a) An IP address in an any ('=') list matched, or - (b) No IP address in an all ('==') list matched - - then we're done searching. */ - - if (((match_type & MT_ALL) != 0) == (res == NULL)) break; - } - - /* If da == NULL, either - - (a) No IP address in an any ('=') list matched, or - (b) An IP address in an all ('==') list didn't match - - so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on - the list. */ - - 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 %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, 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) - { - cb->text_set = TRUE; - if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED) - for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - if (rr->type == T_TXT) - { - int len = (rr->data)[0]; - if (len > 511) len = 127; - store_pool = POOL_PERM; - cb->text = string_sprintf("%.*s", len, CUS (rr->data+1)); - store_pool = old_pool; - break; - } - } - - dnslist_value = addlist; - dnslist_text = cb->text; - return OK; - } - -/* There was a problem with the DNS lookup */ - -if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA) - { - log_write(L_dnslist_defer, LOG_MAIN, - "DNS list lookup defer (probably timeout) for %s: %s", query, - (defer_return == OK)? US"assumed in list" : - (defer_return == FAIL)? US"assumed not in list" : - US"returned DEFER"); - return defer_return; - } - -/* No entry was found in the DNS; continue for next domain */ - -HDEBUG(D_dnsbl) - { - debug_printf("DNS lookup for %s failed\n", query); - debug_printf("=> that means %s is not listed at %s\n", - keydomain, domain); - } - -return FAIL; -} - - - - -/************************************************* -* Check host against DNS black lists * -*************************************************/ - -/* This function runs checks against a list of DNS black lists, until one -matches. Each item on the list can be of the form - - domain=ip-address/key - -The domain is the right-most domain that is used for the query, for example, -blackholes.mail-abuse.org. If the IP address is present, there is a match only -if the DNS lookup returns a matching IP address. Several addresses may be -given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2. - -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: - - dsn.rfc-ignorant.org/$sender_address_domain - -After finding a match in the DNS, the domain is placed in $dnslist_domain, and -then we check for a TXT record for an error message, and if found, save its -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 - -Arguments: - where the acl type - listptr the domain/address/data list - log_msgptr log message on error - -Returns: OK successful lookup (i.e. the address is on the list), or - lookup deferred after +include_unknown - FAIL name not found, or no data found for the given type, or - lookup deferred after +exclude_unknown (default) - DEFER lookup failure, if +defer_unknown was set -*/ - -int -verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr) -{ -int sep = 0; -int defer_return = FAIL; -const uschar *list = *listptr; -uschar *domain; -uschar revadd[128]; /* Long enough for IPv6 address */ - -/* Indicate that the inverted IP address is not yet set up */ - -revadd[0] = 0; - -/* In case this is the first time the DNS resolver is being used. */ - -dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */ - -/* Loop through all the domains supplied, until something matches */ - -while ((domain = string_nextinlist(&list, &sep, NULL, 0))) - { - int rc; - BOOL bitmask = FALSE; - int match_type = 0; - uschar *domain_txt; - uschar *comma; - uschar *iplist; - uschar *key; - - HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain); - - /* Deal with special values that change the behaviour on defer */ - - if (domain[0] == '+') - { - if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK; - else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL; - else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER; - else - log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s", - domain); - continue; - } - - /* See if there's explicit data to be looked up */ - - if ((key = Ustrchr(domain, '/'))) *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 require all matches - and if preceded by ! we invert the result. */ - - if (!(iplist = Ustrchr(domain, '='))) - { - bitmask = TRUE; - iplist = Ustrchr(domain, '&'); - } - - if (iplist) /* Found either = or & */ - { - if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */ - { - match_type |= MT_NOT; - iplist[-1] = 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; - if ((comma = Ustrchr(domain, ','))) - { - *comma++ = 0; - domain = comma; - } - - /* Check that what we have left is a sensible domain name. There is no reason - why these domains should in fact use the same syntax as hosts and email - domains, but in practice they seem to. However, there is little point in - actually causing an error here, because that would no doubt hold up incoming - mail. Instead, I'll just log it. */ - - for (uschar * s = domain; *s; s++) - if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') - { - log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " - "strange characters - is this right?", domain); - break; - } - - /* Check the alternate domain if present */ - - if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++) - if (!isalnum(*s) && *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 (!key) - { - if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP) - { - *log_msgptr = string_sprintf - ("cannot test auto-keyed dnslists condition in %s ACL", - acl_wherenames[where]); - return ERROR; - } - if (!sender_host_address) return FAIL; /* can never match */ - if (revadd[0] == 0) invert_address(revadd, sender_host_address); - 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_txt); - dnslist_matched = string_copy(sender_host_address); - HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - sender_host_address, dnslist_domain); - } - if (rc != FAIL) return rc; /* OK or DEFER */ - } - - /* If there is a key string, it can be a list of domains or IP addresses to - be concatenated with the main domain. */ - - else - { - int keysep = 0; - BOOL defer = FALSE; - uschar *keydomain; - uschar keyrevadd[128]; - - while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0))) - { - uschar *prepend = keydomain; - - if (string_is_ip_address(keydomain, NULL) != 0) - { - invert_address(keyrevadd, keydomain); - prepend = keyrevadd; - } - - rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist, - bitmask, match_type, defer_return); - if (rc == OK) - { - 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, dnslist_domain); - return OK; - } - - /* If the lookup deferred, remember this fact. We keep trying the rest - of the list to see if we get a useful result, and if we don't, we return - DEFER at the end. */ - - if (rc == DEFER) defer = TRUE; - } /* continue with next keystring domain/address */ - - if (defer) return DEFER; - } - } /* continue with next dnsdb outer domain */ - -return FAIL; -} - - - /**************************************************** Verify a local user account for quota sufficiency ****************************************************/