Build: Split out dnsbl code
[exim.git] / src / src / dnsbl.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
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. */
8
9 /* Functions concerned with dnsbls */
10
11
12 #include "exim.h"
13
14 /* Structure for caching DNSBL lookups */
15
16 typedef struct dnsbl_cache_block {
17   time_t expiry;
18   dns_address *rhs;
19   uschar *text;
20   int rc;
21   BOOL text_set;
22 } dnsbl_cache_block;
23
24
25 /* Anchor for DNSBL cache */
26
27 static tree_node *dnsbl_cache = NULL;
28
29
30 /* Bits for match_type in one_check_dnsbl() */
31
32 #define MT_NOT 1
33 #define MT_ALL 2
34
35
36 /*************************************************
37 *          Perform a single dnsbl lookup         *
38 *************************************************/
39
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.
43
44 Arguments:
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
61
62 Returns:         OK if lookup succeeded
63                  FAIL if not
64 */
65
66 static int
67 one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
68   uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
69   int defer_return)
70 {
71 dns_answer * dnsa = store_get_dns_answer();
72 dns_scan dnss;
73 tree_node *t;
74 dnsbl_cache_block *cb;
75 int old_pool = store_pool;
76 uschar * query;
77 int qlen;
78
79 /* Construct the specific query domainname */
80
81 query = string_sprintf("%s.%s", prepend, domain);
82 if ((qlen = Ustrlen(query)) >= 256)
83   {
84   log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
85     "(ignored): %s...", query);
86   return FAIL;
87   }
88
89 /* Look for this query in the cache. */
90
91 if (  (t = tree_search(dnsbl_cache, query))
92    && (cb = t->data.ptr)->expiry > time(NULL)
93    )
94
95 /* Previous lookup was cached */
96
97   {
98   HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
99   }
100
101 /* If not cached from a previous lookup, we must do a DNS lookup, and
102 cache the result in permanent memory. */
103
104 else
105   {
106   uint ttl = 3600;      /* max TTL for positive cache entries */
107
108   store_pool = POOL_PERM;
109
110   if (t)
111     {
112     HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
113     }
114
115   else
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);
121     }
122
123   /* Do the DNS lookup . */
124
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;
128   cb->text = NULL;
129   cb->rhs = NULL;
130
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
134   here, just in case.
135
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.
139
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. */
142
143   switch (cb->rc)
144     {
145     case DNS_SUCCEED:
146       {
147       dns_address ** addrp = &cb->rhs;
148       dns_address * da;
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)))
152           {
153           *addrp = da;
154           while (da->next) da = da->next;
155           addrp = &da->next;
156           if (ttl > rr->ttl) ttl = rr->ttl;
157           }
158
159       if (cb->rhs)
160         {
161         cb->expiry = time(NULL) + ttl;
162         break;
163         }
164
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
167       it points to. */
168
169       cb->rc = DNS_NODATA;
170       }
171       /*FALLTHROUGH*/
172
173     case DNS_NOMATCH:
174     case DNS_NODATA:
175       {
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. */
179
180       time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
181       cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
182       break;
183       }
184
185     default:
186       cb->expiry = time(NULL) + ttl;
187       break;
188     }
189
190   store_pool = old_pool;
191   HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
192     (int)(cb->expiry - time(NULL)));
193   }
194
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 "!&").*/
200
201 if (cb->rc == DNS_SUCCEED)
202   {
203   dns_address * da = NULL;
204   uschar *addlist = cb->rhs->address;
205
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. */
209
210   for (da = cb->rhs->next; da; da = da->next)
211     addlist = string_sprintf("%s, %s", addlist, da->address);
212
213   HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
214     query, addlist);
215
216   /* Address list check; this can be either for equality, or via a bitmask.
217   In the latter case, all the bits must match. */
218
219   if (iplist)
220     {
221     for (da = cb->rhs; da; da = da->next)
222       {
223       int ipsep = ',';
224       const uschar *ptr = iplist;
225       uschar *res;
226
227       /* Handle exact matching */
228
229       if (!bitmask)
230         {
231         while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
232           if (Ustrcmp(CS da->address, res) == 0)
233             break;
234         }
235
236       /* Handle bitmask matching */
237
238       else
239         {
240         int address[4];
241         int mask = 0;
242
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. */
249
250         if (host_aton(da->address, address) == 1) mask = address[0];
251
252         /* Scan the returned addresses, skipping any that are IPv6 */
253
254         while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
255           if (host_aton(res, address) == 1)
256             if ((address[0] & mask) == address[0])
257               break;
258         }
259
260       /* If either
261
262          (a) An IP address in an any ('=') list matched, or
263          (b) No IP address in an all ('==') list matched
264
265       then we're done searching. */
266
267       if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
268       }
269
270     /* If da == NULL, either
271
272        (a) No IP address in an any ('=') list matched, or
273        (b) An IP address in an all ('==') list didn't match
274
275     so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
276     the list. */
277
278     if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
279       {
280       HDEBUG(D_dnsbl)
281         {
282         uschar *res = NULL;
283         switch(match_type)
284           {
285           case 0:
286             res = US"was no match"; break;
287           case MT_NOT:
288             res = US"was an exclude match"; break;
289           case MT_ALL:
290             res = US"was an IP address that did not match"; break;
291           case MT_NOT|MT_ALL:
292             res = US"were no IP addresses that did not match"; break;
293           }
294         debug_printf("=> but we are not accepting this block class because\n");
295         debug_printf("=> there %s for %s%c%s\n",
296           res,
297           match_type & MT_ALL ? "=" : "",
298           bitmask ? '&' : '=', iplist);
299         }
300       return FAIL;
301       }
302     }
303
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. */
309
310   if (domain_txt != domain)
311     return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
312       FALSE, match_type, defer_return);
313
314   /* If there is no alternate domain, look up a TXT record in the main domain
315   if it has not previously been cached. */
316
317   if (!cb->text_set)
318     {
319     cb->text_set = TRUE;
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)
324           {
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;
330           break;
331           }
332     }
333
334   dnslist_value = addlist;
335   dnslist_text = cb->text;
336   return OK;
337   }
338
339 /* There was a problem with the DNS lookup */
340
341 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
342   {
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" :
347                             US"returned DEFER");
348   return defer_return;
349   }
350
351 /* No entry was found in the DNS; continue for next domain */
352
353 HDEBUG(D_dnsbl)
354   {
355   debug_printf("DNS lookup for %s failed\n", query);
356   debug_printf("=> that means %s is not listed at %s\n",
357      keydomain, domain);
358   }
359
360 return FAIL;
361 }
362
363
364
365
366 /*************************************************
367 *        Check host against DNS black lists      *
368 *************************************************/
369
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
372
373   domain=ip-address/key
374
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.
379
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:
383
384   dsn.rfc-ignorant.org/$sender_address_domain
385
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
389 multiple lookups.
390
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
395 example:
396
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
399
400 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
401
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
405
406 Arguments:
407   where        the acl type
408   listptr      the domain/address/data list
409   log_msgptr   log message on error
410
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
416 */
417
418 int
419 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
420 {
421 int sep = 0;
422 int defer_return = FAIL;
423 const uschar *list = *listptr;
424 uschar *domain;
425 uschar revadd[128];        /* Long enough for IPv6 address */
426
427 /* Indicate that the inverted IP address is not yet set up */
428
429 revadd[0] = 0;
430
431 /* In case this is the first time the DNS resolver is being used. */
432
433 dns_init(FALSE, FALSE, FALSE);  /*XXX dnssec? */
434
435 /* Loop through all the domains supplied, until something matches */
436
437 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
438   {
439   int rc;
440   BOOL bitmask = FALSE;
441   int match_type = 0;
442   uschar *domain_txt;
443   uschar *comma;
444   uschar *iplist;
445   uschar *key;
446
447   HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
448
449   /* Deal with special values that change the behaviour on defer */
450
451   if (domain[0] == '+')
452     {
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;
456     else
457       log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
458         domain);
459     continue;
460     }
461
462   /* See if there's explicit data to be looked up */
463
464   if ((key = Ustrchr(domain, '/'))) *key++ = 0;
465
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. */
469
470   if (!(iplist = Ustrchr(domain, '=')))
471     {
472     bitmask = TRUE;
473     iplist = Ustrchr(domain, '&');
474     }
475
476   if (iplist)                                  /* Found either = or & */
477     {
478     if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
479       {
480       match_type |= MT_NOT;
481       iplist[-1] = 0;
482       }
483
484     *iplist++ = 0;                             /* Terminate domain, move on */
485
486     /* If we found = (bitmask == FALSE), check for == or =& */
487
488     if (!bitmask && (*iplist == '=' || *iplist == '&'))
489       {
490       bitmask = *iplist++ == '&';
491       match_type |= MT_ALL;
492       }
493     }
494
495
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. */
499
500   domain_txt = domain;
501   if ((comma = Ustrchr(domain, ',')))
502     {
503     *comma++ = 0;
504     domain = comma;
505     }
506
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. */
512
513   for (uschar * s = domain; *s; s++)
514     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
515       {
516       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
517         "strange characters - is this right?", domain);
518       break;
519       }
520
521   /* Check the alternate domain if present */
522
523   if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
524     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
525       {
526       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
527         "strange characters - is this right?", domain_txt);
528       break;
529       }
530
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. */
533
534   if (!key)
535     {
536     if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
537       {
538       *log_msgptr = string_sprintf
539         ("cannot test auto-keyed dnslists condition in %s ACL",
540           acl_wherenames[where]);
541       return ERROR;
542       }
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);
547     if (rc == OK)
548       {
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);
553       }
554     if (rc != FAIL) return rc;     /* OK or DEFER */
555     }
556
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. */
559
560   else
561     {
562     int keysep = 0;
563     BOOL defer = FALSE;
564     uschar *keydomain;
565     uschar keyrevadd[128];
566
567     while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
568       {
569       uschar *prepend = keydomain;
570
571       if (string_is_ip_address(keydomain, NULL) != 0)
572         {
573         invert_address(keyrevadd, keydomain);
574         prepend = keyrevadd;
575         }
576
577       rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
578         bitmask, match_type, defer_return);
579       if (rc == OK)
580         {
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);
585         return OK;
586         }
587
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
590       DEFER at the end. */
591
592       if (rc == DEFER) defer = TRUE;
593       }    /* continue with next keystring domain/address */
594
595     if (defer) return DEFER;
596     }
597   }        /* continue with next dnsdb outer domain */
598
599 return FAIL;
600 }
601
602 /* vi: aw ai sw=2
603 */
604 /* End of dnsbl.c.c */