Fix small typo
[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, yield;
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   yield = FAIL;
87   goto out;
88   }
89
90 /* Look for this query in the cache. */
91
92 if (  (t = tree_search(dnsbl_cache, query))
93    && (cb = t->data.ptr)->expiry > time(NULL)
94    )
95
96 /* Previous lookup was cached */
97
98   {
99   HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
100   }
101
102 /* If not cached from a previous lookup, we must do a DNS lookup, and
103 cache the result in permanent memory. */
104
105 else
106   {
107   uint ttl = 3600;      /* max TTL for positive cache entries */
108
109   store_pool = POOL_PERM;
110
111   if (t)
112     {
113     HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
114     }
115
116   else
117     {   /* Set up a tree entry to cache the lookup */
118     t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query));
119     Ustrcpy(t->name, query);
120     t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
121     (void)tree_insertnode(&dnsbl_cache, t);
122     }
123
124   /* Do the DNS lookup . */
125
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;
129   cb->text = NULL;
130   cb->rhs = NULL;
131
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
135   here, just in case.
136
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.
140
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. */
143
144   switch (cb->rc)
145     {
146     case DNS_SUCCEED:
147       {
148       dns_address ** addrp = &cb->rhs;
149       dns_address * da;
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)))
153           {
154           *addrp = da;
155           while (da->next) da = da->next;
156           addrp = &da->next;
157           if (ttl > rr->ttl) ttl = rr->ttl;
158           }
159
160       if (cb->rhs)
161         {
162         cb->expiry = time(NULL) + ttl;
163         break;
164         }
165
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
168       it points to. */
169
170       cb->rc = DNS_NODATA;
171       }
172       /*FALLTHROUGH*/
173
174     case DNS_NOMATCH:
175     case DNS_NODATA:
176       {
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. */
180
181       time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
182       cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
183       break;
184       }
185
186     default:
187       cb->expiry = time(NULL) + ttl;
188       break;
189     }
190
191   store_pool = old_pool;
192   HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
193     (int)(cb->expiry - time(NULL)));
194   }
195
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 "!&").*/
201
202 if (cb->rc == DNS_SUCCEED)
203   {
204   dns_address * da = NULL;
205   uschar *addlist = cb->rhs->address;
206
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. */
210
211   for (da = cb->rhs->next; da; da = da->next)
212     addlist = string_sprintf("%s, %s", addlist, da->address);
213
214   HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
215     query, addlist);
216
217   /* Address list check; this can be either for equality, or via a bitmask.
218   In the latter case, all the bits must match. */
219
220   if (iplist)
221     {
222     for (da = cb->rhs; da; da = da->next)
223       {
224       int ipsep = ',';
225       const uschar *ptr = iplist;
226       uschar *res;
227
228       /* Handle exact matching */
229
230       if (!bitmask)
231         {
232         while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
233           if (Ustrcmp(CS da->address, res) == 0)
234             break;
235         }
236
237       /* Handle bitmask matching */
238
239       else
240         {
241         int address[4];
242         int mask = 0;
243
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. */
250
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);
257
258           else
259             mask = address[0];
260
261         /* Scan the returned addresses, skipping any that are IPv6 */
262
263         while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
264           if (host_aton(res, address) == 1)
265             if ((address[0] & mask) == address[0])
266               break;
267         }
268
269       /* If either
270
271          (a) An IP address in an any ('=') list matched, or
272          (b) No IP address in an all ('==') list matched
273
274       then we're done searching. */
275
276       if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
277       }
278
279     /* If da == NULL, either
280
281        (a) No IP address in an any ('=') list matched, or
282        (b) An IP address in an all ('==') list didn't match
283
284     so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
285     the list. */
286
287     if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
288       {
289       HDEBUG(D_dnsbl)
290         {
291         uschar *res = NULL;
292         switch(match_type)
293           {
294           case 0:
295             res = US"was no match"; break;
296           case MT_NOT:
297             res = US"was an exclude match"; break;
298           case MT_ALL:
299             res = US"was an IP address that did not match"; break;
300           case MT_NOT|MT_ALL:
301             res = US"were no IP addresses that did not match"; break;
302           }
303         debug_printf("=> but we are not accepting this block class because\n");
304         debug_printf("=> there %s for %s%c%s\n",
305           res,
306           match_type & MT_ALL ? "=" : "",
307           bitmask ? '&' : '=', iplist);
308         }
309       yield = FAIL;
310       goto out;
311       }
312     }
313
314   /* No address list check; discard any illegal returns and give up if
315   none remain. */
316
317   else
318     {
319     BOOL ok = FALSE;
320     for (da = cb->rhs; da; da = da->next)
321       {
322       int address[4];
323
324       if (  host_aton(da->address, address) == 1                /* ipv4 */
325          && (address[0] & 0xff000000) == 0x7f000000     /* 127.0.0.0/8 */
326          )
327         ok = TRUE;
328       else
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);
333       }
334     if (!ok)
335       {
336       yield = FAIL;
337       goto out;
338       }
339     }
340
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. */
346
347   if (domain_txt != domain)
348     {
349     yield = one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
350       FALSE, match_type, defer_return);
351     goto out;
352     }
353
354   /* If there is no alternate domain, look up a TXT record in the main domain
355   if it has not previously been cached. */
356
357   if (!cb->text_set)
358     {
359     cb->text_set = TRUE;
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)
364           {
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, TRUE);
369           store_pool = old_pool;
370           break;
371           }
372     }
373
374   dnslist_value = addlist;
375   dnslist_text = cb->text;
376   yield = OK;
377   goto out;
378   }
379
380 /* There was a problem with the DNS lookup */
381
382 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
383   {
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" :
388                             US"returned DEFER");
389   yield = defer_return;
390   goto out;
391   }
392
393 /* No entry was found in the DNS; continue for next domain */
394
395 HDEBUG(D_dnsbl)
396   {
397   debug_printf("DNS lookup for %s failed\n", query);
398   debug_printf("=> that means %s is not listed at %s\n",
399      keydomain, domain);
400   }
401
402 yield = FAIL;
403
404 out:
405
406 store_free_dns_answer(dnsa);
407 return yield;
408 }
409
410
411
412
413 /*************************************************
414 *        Check host against DNS black lists      *
415 *************************************************/
416
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
419
420   domain=ip-address/key
421
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.
426
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:
430
431   dsn.rfc-ignorant.org/$sender_address_domain
432
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
436 multiple lookups.
437
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
442 example:
443
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
446
447 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
448
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
452
453 Arguments:
454   where        the acl type
455   listptr      the domain/address/data list
456   log_msgptr   log message on error
457
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
463 */
464
465 int
466 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
467 {
468 int sep = 0;
469 int defer_return = FAIL;
470 const uschar *list = *listptr;
471 uschar *domain;
472 uschar revadd[128];        /* Long enough for IPv6 address */
473
474 /* Indicate that the inverted IP address is not yet set up */
475
476 revadd[0] = 0;
477
478 /* In case this is the first time the DNS resolver is being used. */
479
480 dns_init(FALSE, FALSE, FALSE);  /*XXX dnssec? */
481
482 /* Loop through all the domains supplied, until something matches */
483
484 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
485   {
486   int rc;
487   BOOL bitmask = FALSE;
488   int match_type = 0;
489   uschar *domain_txt;
490   uschar *comma;
491   uschar *iplist;
492   uschar *key;
493
494   HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
495
496   /* Deal with special values that change the behaviour on defer */
497
498   if (domain[0] == '+')
499     {
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;
503     else
504       log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
505         domain);
506     continue;
507     }
508
509   /* See if there's explicit data to be looked up */
510
511   if ((key = Ustrchr(domain, '/'))) *key++ = 0;
512
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. */
516
517   if (!(iplist = Ustrchr(domain, '=')))
518     {
519     bitmask = TRUE;
520     iplist = Ustrchr(domain, '&');
521     }
522
523   if (iplist)                                  /* Found either = or & */
524     {
525     if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
526       {
527       match_type |= MT_NOT;
528       iplist[-1] = 0;
529       }
530
531     *iplist++ = 0;                             /* Terminate domain, move on */
532
533     /* If we found = (bitmask == FALSE), check for == or =& */
534
535     if (!bitmask && (*iplist == '=' || *iplist == '&'))
536       {
537       bitmask = *iplist++ == '&';
538       match_type |= MT_ALL;
539       }
540     }
541
542
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. */
546
547   domain_txt = domain;
548   if ((comma = Ustrchr(domain, ',')))
549     {
550     *comma++ = 0;
551     domain = comma;
552     }
553
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. */
559
560   for (uschar * s = domain; *s; s++)
561     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
562       {
563       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
564         "strange characters - is this right?", domain);
565       break;
566       }
567
568   /* Check the alternate domain if present */
569
570   if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
571     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
572       {
573       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
574         "strange characters - is this right?", domain_txt);
575       break;
576       }
577
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. */
580
581   if (!key)
582     {
583     if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
584       {
585       *log_msgptr = string_sprintf
586         ("cannot test auto-keyed dnslists condition in %s ACL",
587           acl_wherenames[where]);
588       return ERROR;
589       }
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);
594     if (rc == OK)
595       {
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);
600       }
601     if (rc != FAIL) return rc;     /* OK or DEFER */
602     }
603
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. */
606
607   else
608     {
609     int keysep = 0;
610     BOOL defer = FALSE;
611     uschar *keydomain;
612     uschar keyrevadd[128];
613
614     while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
615       {
616       uschar *prepend = keydomain;
617
618       if (string_is_ip_address(keydomain, NULL) != 0)
619         {
620         invert_address(keyrevadd, keydomain);
621         prepend = keyrevadd;
622         }
623
624       rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
625         bitmask, match_type, defer_return);
626       if (rc == OK)
627         {
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);
632         return OK;
633         }
634
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
637       DEFER at the end. */
638
639       if (rc == DEFER) defer = TRUE;
640       }    /* continue with next keystring domain/address */
641
642     if (defer) return DEFER;
643     }
644   }        /* continue with next dnsdb outer domain */
645
646 return FAIL;
647 }
648
649 /* vi: aw ai sw=2
650 */
651 /* End of dnsbl.c.c */