dnslists: hardwired return value check. Bug 2631
[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)
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);
256
257           else
258             mask = address[0];
259
260         /* Scan the returned addresses, skipping any that are IPv6 */
261
262         while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
263           if (host_aton(res, address) == 1)
264             if ((address[0] & mask) == address[0])
265               break;
266         }
267
268       /* If either
269
270          (a) An IP address in an any ('=') list matched, or
271          (b) No IP address in an all ('==') list matched
272
273       then we're done searching. */
274
275       if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
276       }
277
278     /* If da == NULL, either
279
280        (a) No IP address in an any ('=') list matched, or
281        (b) An IP address in an all ('==') list didn't match
282
283     so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
284     the list. */
285
286     if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
287       {
288       HDEBUG(D_dnsbl)
289         {
290         uschar *res = NULL;
291         switch(match_type)
292           {
293           case 0:
294             res = US"was no match"; break;
295           case MT_NOT:
296             res = US"was an exclude match"; break;
297           case MT_ALL:
298             res = US"was an IP address that did not match"; break;
299           case MT_NOT|MT_ALL:
300             res = US"were no IP addresses that did not match"; break;
301           }
302         debug_printf("=> but we are not accepting this block class because\n");
303         debug_printf("=> there %s for %s%c%s\n",
304           res,
305           match_type & MT_ALL ? "=" : "",
306           bitmask ? '&' : '=', iplist);
307         }
308       return FAIL;
309       }
310     }
311
312   /* No address list check; discard any illegal returns and give up if
313   none remain. */
314
315   else
316     {
317     BOOL ok = FALSE;
318     for (da = cb->rhs; da; da = da->next)
319       {
320       int address[4];
321
322       if (  host_aton(da->address, address) == 1                /* ipv4 */
323          && (address[0] & 0xff000000) == 0x7f000000     /* 127.0.0.0/8 */
324          )
325         ok = TRUE;
326       else
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);
331       }
332     if (!ok) return FAIL;
333     }
334
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. */
340
341   if (domain_txt != domain)
342     return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
343       FALSE, match_type, defer_return);
344
345   /* If there is no alternate domain, look up a TXT record in the main domain
346   if it has not previously been cached. */
347
348   if (!cb->text_set)
349     {
350     cb->text_set = TRUE;
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)
355           {
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;
361           break;
362           }
363     }
364
365   dnslist_value = addlist;
366   dnslist_text = cb->text;
367   return OK;
368   }
369
370 /* There was a problem with the DNS lookup */
371
372 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
373   {
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" :
378                             US"returned DEFER");
379   return defer_return;
380   }
381
382 /* No entry was found in the DNS; continue for next domain */
383
384 HDEBUG(D_dnsbl)
385   {
386   debug_printf("DNS lookup for %s failed\n", query);
387   debug_printf("=> that means %s is not listed at %s\n",
388      keydomain, domain);
389   }
390
391 return FAIL;
392 }
393
394
395
396
397 /*************************************************
398 *        Check host against DNS black lists      *
399 *************************************************/
400
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
403
404   domain=ip-address/key
405
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.
410
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:
414
415   dsn.rfc-ignorant.org/$sender_address_domain
416
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
420 multiple lookups.
421
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
426 example:
427
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
430
431 The caching ensures that only one lookup in dnsbl.sorbs.net is done.
432
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
436
437 Arguments:
438   where        the acl type
439   listptr      the domain/address/data list
440   log_msgptr   log message on error
441
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
447 */
448
449 int
450 verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
451 {
452 int sep = 0;
453 int defer_return = FAIL;
454 const uschar *list = *listptr;
455 uschar *domain;
456 uschar revadd[128];        /* Long enough for IPv6 address */
457
458 /* Indicate that the inverted IP address is not yet set up */
459
460 revadd[0] = 0;
461
462 /* In case this is the first time the DNS resolver is being used. */
463
464 dns_init(FALSE, FALSE, FALSE);  /*XXX dnssec? */
465
466 /* Loop through all the domains supplied, until something matches */
467
468 while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
469   {
470   int rc;
471   BOOL bitmask = FALSE;
472   int match_type = 0;
473   uschar *domain_txt;
474   uschar *comma;
475   uschar *iplist;
476   uschar *key;
477
478   HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
479
480   /* Deal with special values that change the behaviour on defer */
481
482   if (domain[0] == '+')
483     {
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;
487     else
488       log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
489         domain);
490     continue;
491     }
492
493   /* See if there's explicit data to be looked up */
494
495   if ((key = Ustrchr(domain, '/'))) *key++ = 0;
496
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. */
500
501   if (!(iplist = Ustrchr(domain, '=')))
502     {
503     bitmask = TRUE;
504     iplist = Ustrchr(domain, '&');
505     }
506
507   if (iplist)                                  /* Found either = or & */
508     {
509     if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
510       {
511       match_type |= MT_NOT;
512       iplist[-1] = 0;
513       }
514
515     *iplist++ = 0;                             /* Terminate domain, move on */
516
517     /* If we found = (bitmask == FALSE), check for == or =& */
518
519     if (!bitmask && (*iplist == '=' || *iplist == '&'))
520       {
521       bitmask = *iplist++ == '&';
522       match_type |= MT_ALL;
523       }
524     }
525
526
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. */
530
531   domain_txt = domain;
532   if ((comma = Ustrchr(domain, ',')))
533     {
534     *comma++ = 0;
535     domain = comma;
536     }
537
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. */
543
544   for (uschar * s = domain; *s; s++)
545     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
546       {
547       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
548         "strange characters - is this right?", domain);
549       break;
550       }
551
552   /* Check the alternate domain if present */
553
554   if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
555     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
556       {
557       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
558         "strange characters - is this right?", domain_txt);
559       break;
560       }
561
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. */
564
565   if (!key)
566     {
567     if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
568       {
569       *log_msgptr = string_sprintf
570         ("cannot test auto-keyed dnslists condition in %s ACL",
571           acl_wherenames[where]);
572       return ERROR;
573       }
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);
578     if (rc == OK)
579       {
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);
584       }
585     if (rc != FAIL) return rc;     /* OK or DEFER */
586     }
587
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. */
590
591   else
592     {
593     int keysep = 0;
594     BOOL defer = FALSE;
595     uschar *keydomain;
596     uschar keyrevadd[128];
597
598     while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
599       {
600       uschar *prepend = keydomain;
601
602       if (string_is_ip_address(keydomain, NULL) != 0)
603         {
604         invert_address(keyrevadd, keydomain);
605         prepend = keyrevadd;
606         }
607
608       rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
609         bitmask, match_type, defer_return);
610       if (rc == OK)
611         {
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);
616         return OK;
617         }
618
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
621       DEFER at the end. */
622
623       if (rc == DEFER) defer = TRUE;
624       }    /* continue with next keystring domain/address */
625
626     if (defer) return DEFER;
627     }
628   }        /* continue with next dnsdb outer domain */
629
630 return FAIL;
631 }
632
633 /* vi: aw ai sw=2
634 */
635 /* End of dnsbl.c.c */