Copyright updates:
[exim.git] / src / src / lookups / dnsdb.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 #include "../exim.h"
10 #include "lf_functions.h"
11
12
13
14 /* Ancient systems (e.g. SunOS4) don't appear to have T_TXT defined in their
15 header files. */
16
17 #ifndef T_TXT
18 # define T_TXT 16
19 #endif
20
21 /* Many systems do not have T_SPF. */
22 #ifndef T_SPF
23 # define T_SPF 99
24 #endif
25
26 /* New TLSA record for DANE */
27 #ifndef T_TLSA
28 # define T_TLSA 52
29 #endif
30
31 /* Table of recognized DNS record types and their integer values. */
32
33 static const char *type_names[] = {
34   "a",
35 #if HAVE_IPV6
36   "a+",
37   "aaaa",
38 #endif
39   "cname",
40   "csa",
41   "mx",
42   "mxh",
43   "ns",
44   "ptr",
45   "soa",
46   "spf",
47   "srv",
48   "tlsa",
49   "txt",
50   "zns"
51 };
52
53 static int type_values[] = {
54   T_A,
55 #if HAVE_IPV6
56   T_ADDRESSES,     /* Private type for AAAA + A */
57   T_AAAA,
58 #endif
59   T_CNAME,
60   T_CSA,     /* Private type for "Client SMTP Authorization". */
61   T_MX,
62   T_MXH,     /* Private type for "MX hostnames" */
63   T_NS,
64   T_PTR,
65   T_SOA,
66   T_SPF,
67   T_SRV,
68   T_TLSA,
69   T_TXT,
70   T_ZNS      /* Private type for "zone nameservers" */
71 };
72
73
74 /*************************************************
75 *              Open entry point                  *
76 *************************************************/
77
78 /* See local README for interface description. */
79
80 static void *
81 dnsdb_open(const uschar * filename, uschar **errmsg)
82 {
83 filename = filename;   /* Keep picky compilers happy */
84 errmsg = errmsg;       /* Ditto */
85 return (void *)(-1);   /* Any non-0 value */
86 }
87
88
89
90 /*************************************************
91 *           Find entry point for dnsdb           *
92 *************************************************/
93
94 /* See local README for interface description. The query in the "keystring" may
95 consist of a number of parts.
96
97 (a) If the first significant character is '>' then the next character is the
98 separator character that is used when multiple records are found. The default
99 separator is newline.
100
101 (b) If the next character is ',' then the next character is the separator
102 character used for multiple items of text in "TXT" records. Alternatively,
103 if the next character is ';' then these multiple items are concatenated with
104 no separator. With neither of these options specified, only the first item
105 is output.  Similarly for "SPF" records, but the default for joining multiple
106 items in one SPF record is the empty string, for direct concatenation.
107
108 (c) Options, all comma-terminated, in any order.  Any unrecognised option
109 terminates option processing.  Recognised options are:
110
111 - 'defer_FOO':  set the defer behaviour to FOO.  The possible behaviours are:
112 'strict', where any defer causes the whole lookup to defer; 'lax', where a defer
113 causes the whole lookup to defer only if none of the DNS queries succeeds; and
114 'never', where all defers are as if the lookup failed. The default is 'lax'.
115
116 - 'dnssec_FOO', with 'strict', 'lax' (default), and 'never'.  The meanings are
117 require, try and don't-try dnssec respectively.
118
119 - 'retrans_VAL', set the timeout value.  VAL is an Exim time specification
120 (eg "5s").  The default is set by the main configuration option 'dns_retrans'.
121
122 - 'retry_VAL', set the retry count on timeouts.  VAL is an integer.  The
123 default is set by the main configuration option "dns_retry".
124
125 (d) If the next sequence of characters is a sequence of letters and digits
126 followed by '=', it is interpreted as the name of the DNS record type. The
127 default is "TXT".
128
129 (e) Then there follows list of domain names. This is a generalized Exim list,
130 which may start with '<' in order to set a specific separator. The default
131 separator, as always, is colon. */
132
133 static int
134 dnsdb_find(void * handle, const uschar * filename, const uschar * keystring,
135  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
136  const uschar * opts)
137 {
138 int rc;
139 int sep = 0;
140 int defer_mode = PASS;
141 int dnssec_mode = PASS;
142 int save_retrans = dns_retrans;
143 int save_retry =   dns_retry;
144 int type;
145 int failrc = FAIL;
146 const uschar *outsep = CUS"\n";
147 const uschar *outsep2 = NULL;
148 uschar *equals, *domain, *found;
149
150 dns_answer * dnsa = store_get_dns_answer();
151 dns_scan dnss;
152
153 /* Because we're working in the search pool, we try to reclaim as much
154 store as possible later, so we preallocate the result here */
155
156 gstring * yield = string_get(256);
157
158 handle = handle;           /* Keep picky compilers happy */
159 filename = filename;
160 length = length;
161 do_cache = do_cache;
162
163 /* If the string starts with '>' we change the output separator.
164 If it's followed by ';' or ',' we set the TXT output separator. */
165
166 while (isspace(*keystring)) keystring++;
167 if (*keystring == '>')
168   {
169   outsep = keystring + 1;
170   keystring += 2;
171   if (*keystring == ',')
172     {
173     outsep2 = keystring + 1;
174     keystring += 2;
175     }
176   else if (*keystring == ';')
177     {
178     outsep2 = US"";
179     keystring++;
180     }
181   while (isspace(*keystring)) keystring++;
182   }
183
184 /* Check for a modifier keyword. */
185
186 for (;;)
187   {
188   if (strncmpic(keystring, US"defer_", 6) == 0)
189     {
190     keystring += 6;
191     if (strncmpic(keystring, US"strict", 6) == 0)
192       { defer_mode = DEFER; keystring += 6; }
193     else if (strncmpic(keystring, US"lax", 3) == 0)
194       { defer_mode = PASS; keystring += 3; }
195     else if (strncmpic(keystring, US"never", 5) == 0)
196       { defer_mode = OK; keystring += 5; }
197     else
198       {
199       *errmsg = US"unsupported dnsdb defer behaviour";
200       return DEFER;
201       }
202     }
203   else if (strncmpic(keystring, US"dnssec_", 7) == 0)
204     {
205     keystring += 7;
206     if (strncmpic(keystring, US"strict", 6) == 0)
207       { dnssec_mode = DEFER; keystring += 6; }
208     else if (strncmpic(keystring, US"lax", 3) == 0)
209       { dnssec_mode = PASS; keystring += 3; }
210     else if (strncmpic(keystring, US"never", 5) == 0)
211       { dnssec_mode = OK; keystring += 5; }
212     else
213       {
214       *errmsg = US"unsupported dnsdb dnssec behaviour";
215       return DEFER;
216       }
217     }
218   else if (strncmpic(keystring, US"retrans_", 8) == 0)
219     {
220     int timeout_sec;
221     if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0)
222       {
223       *errmsg = US"unsupported dnsdb timeout value";
224       return DEFER;
225       }
226     dns_retrans = timeout_sec;
227     while (*keystring != ',') keystring++;
228     }
229   else if (strncmpic(keystring, US"retry_", 6) == 0)
230     {
231     int retries;
232     if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0)
233       {
234       *errmsg = US"unsupported dnsdb retry count";
235       return DEFER;
236       }
237     dns_retry = retries;
238     }
239   else
240     break;
241
242   while (isspace(*keystring)) keystring++;
243   if (*keystring++ != ',')
244     {
245     *errmsg = US"dnsdb modifier syntax error";
246     return DEFER;
247     }
248   while (isspace(*keystring)) keystring++;
249   }
250
251 /* Figure out the "type" value if it is not T_TXT.
252 If the keystring contains an = this must be preceded by a valid type name. */
253
254 type = T_TXT;
255 if ((equals = Ustrchr(keystring, '=')) != NULL)
256   {
257   int i, len;
258   uschar *tend = equals;
259
260   while (tend > keystring && isspace(tend[-1])) tend--;
261   len = tend - keystring;
262
263   for (i = 0; i < nelem(type_names); i++)
264     if (len == Ustrlen(type_names[i]) &&
265         strncmpic(keystring, US type_names[i], len) == 0)
266       {
267       type = type_values[i];
268       break;
269       }
270
271   if (i >= nelem(type_names))
272     {
273     *errmsg = US"unsupported DNS record type";
274     return DEFER;
275     }
276
277   keystring = equals + 1;
278   while (isspace(*keystring)) keystring++;
279   }
280
281 /* Initialize the resolver in case this is the first time it has been used. */
282
283 dns_init(FALSE, FALSE, dnssec_mode != OK);
284
285 /* The remainder of the string must be a list of domains. As long as the lookup
286 for at least one of them succeeds, we return success. Failure means that none
287 of them were found.
288
289 The original implementation did not support a list of domains. Adding the list
290 feature is compatible, except in one case: when PTR records are being looked up
291 for a single IPv6 address. Fortunately, we can hack in a compatibility feature
292 here: If the type is PTR and no list separator is specified, and the entire
293 remaining string is valid as an IP address, set an impossible separator so that
294 it is treated as one item. */
295
296 if (type == T_PTR && keystring[0] != '<' &&
297     string_is_ip_address(keystring, NULL) != 0)
298   sep = -1;
299
300 /* SPF strings should be concatenated without a separator, thus make
301 it the default if not defined (see RFC 4408 section 3.1.3).
302 Multiple SPF records are forbidden (section 3.1.2) but are currently
303 not handled specially, thus they are concatenated with \n by default.
304 MX priority and value are space-separated by default.
305 SRV and TLSA record parts are space-separated by default. */
306
307 if (!outsep2) switch(type)
308   {
309   case T_SPF:                         outsep2 = US"";  break;
310   case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break;
311   }
312
313 /* Now scan the list and do a lookup for each item */
314
315 while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
316   {
317   int searchtype = type == T_CSA ? T_SRV :         /* record type we want */
318                    type == T_MXH ? T_MX :
319                    type == T_ZNS ? T_NS : type;
320
321   /* If the type is PTR or CSA, we have to construct the relevant magic lookup
322   key if the original is an IP address (some experimental protocols are using
323   PTR records for different purposes where the key string is a host name, and
324   Exim's extended CSA can be keyed by domains or IP addresses). This code for
325   doing the reversal is now in a separate function. */
326
327   if ((type == T_PTR || type == T_CSA) &&
328       string_is_ip_address(domain, NULL) != 0)
329     domain = dns_build_reverse(domain);
330
331   do
332     {
333     DEBUG(D_lookup) debug_printf_indent("dnsdb key: %s\n", domain);
334
335     /* Do the lookup and sort out the result. There are four special types that
336     are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH.
337     The first two are handled in a special lookup function so that the facility
338     could be used from other parts of the Exim code. T_ADDRESSES is handled by looping
339     over the types of A lookup.  T_MXH affects only what happens later on in
340     this function, but for tidiness it is handled by the "special". If the
341     lookup fails, continue with the next domain. In the case of DEFER, adjust
342     the final "nothing found" result, but carry on to the next domain. */
343
344     found = domain;
345 #if HAVE_IPV6
346     if (type == T_ADDRESSES)            /* NB cannot happen unless HAVE_IPV6 */
347       {
348       if (searchtype == T_ADDRESSES) searchtype = T_AAAA;
349       else if (searchtype == T_AAAA) searchtype = T_A;
350       rc = dns_special_lookup(dnsa, domain, searchtype, CUSS &found);
351       }
352     else
353 #endif
354       rc = dns_special_lookup(dnsa, domain, type, CUSS &found);
355
356     lookup_dnssec_authenticated = dnssec_mode==OK ? NULL
357       : dns_is_secure(dnsa) ? US"yes" : US"no";
358
359     if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue;
360     if (  rc != DNS_SUCCEED
361        || (dnssec_mode == DEFER && !dns_is_secure(dnsa))
362        )
363       {
364       if (defer_mode == DEFER)
365         {
366         dns_retrans = save_retrans;
367         dns_retry = save_retry;
368         dns_init(FALSE, FALSE, FALSE);                  /* clr dnssec bit */
369         return DEFER;                                   /* always defer */
370         }
371       if (defer_mode == PASS) failrc = DEFER;         /* defer only if all do */
372       continue;                                       /* treat defer as fail */
373       }
374
375
376     /* Search the returned records */
377
378     for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
379          rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == searchtype)
380       {
381       if (*do_cache > rr->ttl)
382         *do_cache = rr->ttl;
383
384       if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
385         {
386         for (dns_address * da = dns_address_from_rr(dnsa, rr); da; da = da->next)
387           {
388           if (yield->ptr) yield = string_catn(yield, outsep, 1);
389           yield = string_cat(yield, da->address);
390           }
391         continue;
392         }
393
394       /* Other kinds of record just have one piece of data each, but there may be
395       several of them, of course. */
396
397       if (yield->ptr) yield = string_catn(yield, outsep, 1);
398
399       if (type == T_TXT || type == T_SPF)
400         {
401         if (outsep2 == NULL)    /* output only the first item of data */
402           yield = string_catn(yield, US (rr->data+1), (rr->data)[0]);
403         else
404           {
405           /* output all items */
406           int data_offset = 0;
407           while (data_offset < rr->size)
408             {
409             uschar chunk_len = (rr->data)[data_offset++];
410             if (outsep2[0] != '\0' && data_offset != 1)
411               yield = string_catn(yield, outsep2, 1);
412             yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len);
413             data_offset += chunk_len;
414             }
415           }
416         }
417       else if (type == T_TLSA)
418         {
419         uint8_t usage, selector, matching_type;
420         uint16_t payload_length;
421         uschar s[MAX_TLSA_EXPANDED_SIZE];
422         uschar * sp = s;
423         uschar * p = US rr->data;
424
425         usage = *p++;
426         selector = *p++;
427         matching_type = *p++;
428         /* What's left after removing the first 3 bytes above */
429         payload_length = rr->size - 3;
430         sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
431                 selector, *outsep2, matching_type, *outsep2);
432         /* Now append the cert/identifier, one hex char at a time */
433         while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
434           sp += sprintf(CS sp, "%02x", *p++);
435
436         yield = string_cat(yield, s);
437         }
438       else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
439         {
440         int priority, weight, port;
441         uschar s[264];
442         uschar * p = US rr->data;
443
444         switch (type)
445           {
446           case T_MXH:
447             /* mxh ignores the priority number and includes only the hostnames */
448             GETSHORT(priority, p);
449             break;
450
451           case T_MX:
452             GETSHORT(priority, p);
453             sprintf(CS s, "%d%c", priority, *outsep2);
454             yield = string_cat(yield, s);
455             break;
456
457           case T_SRV:
458             GETSHORT(priority, p);
459             GETSHORT(weight, p);
460             GETSHORT(port, p);
461             sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
462                               weight, *outsep2, port, *outsep2);
463             yield = string_cat(yield, s);
464             break;
465
466           case T_CSA:
467             /* See acl_verify_csa() for more comments about CSA. */
468             GETSHORT(priority, p);
469             GETSHORT(weight, p);
470             GETSHORT(port, p);
471
472             if (priority != 1) continue;      /* CSA version must be 1 */
473
474             /* If the CSA record we found is not the one we asked for, analyse
475             the subdomain assertions in the port field, else analyse the direct
476             authorization status in the weight field. */
477
478             if (Ustrcmp(found, domain) != 0)
479               {
480               if (port & 1) *s = 'X';         /* explicit authorization required */
481               else *s = '?';                  /* no subdomain assertions here */
482               }
483             else
484               {
485               if (weight < 2) *s = 'N';       /* not authorized */
486               else if (weight == 2) *s = 'Y'; /* authorized */
487               else if (weight == 3) *s = '?'; /* unauthorizable */
488               else continue;                  /* invalid */
489               }
490
491             s[1] = ' ';
492             yield = string_catn(yield, s, 2);
493             break;
494
495           default:
496             break;
497           }
498
499         /* GETSHORT() has advanced the pointer to the target domain. */
500
501         rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
502           (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
503
504         /* If an overlong response was received, the data will have been
505         truncated and dn_expand may fail. */
506
507         if (rc < 0)
508           {
509           log_write(0, LOG_MAIN, "host name alias list truncated: type=%s "
510             "domain=%s", dns_text_type(type), domain);
511           break;
512           }
513         else yield = string_cat(yield, s);
514
515         if (type == T_SOA && outsep2 != NULL)
516           {
517           unsigned long serial, refresh, retry, expire, minimum;
518
519           p += rc;
520           yield = string_catn(yield, outsep2, 1);
521
522           rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
523             (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
524           if (rc < 0)
525             {
526             log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
527               "domain=%s", dns_text_type(type), domain);
528             break;
529             }
530           else yield = string_cat(yield, s);
531
532           p += rc;
533           GETLONG(serial, p); GETLONG(refresh, p);
534           GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
535           sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
536             *outsep2, serial, *outsep2, refresh,
537             *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
538           yield = string_cat(yield, s);
539           }
540         }
541       }    /* Loop for list of returned records */
542
543            /* Loop for set of A-lookup types */
544     } while (type == T_ADDRESSES && searchtype != T_A);
545
546   }        /* Loop for list of domains */
547
548 /* Reclaim unused memory */
549
550 gstring_release_unused(yield);
551
552 /* If yield NULL we have not found anything. Otherwise, insert the terminating
553 zero and return the result. */
554
555 dns_retrans = save_retrans;
556 dns_retry = save_retry;
557 dns_init(FALSE, FALSE, FALSE);  /* clear the dnssec bit for getaddrbyname */
558
559 if (!yield || !yield->ptr) return failrc;
560
561 *result = string_from_gstring(yield);
562 return OK;
563 }
564
565
566
567 /*************************************************
568 *         Version reporting entry point          *
569 *************************************************/
570
571 /* See local README for interface description. */
572
573 #include "../version.h"
574
575 void
576 dnsdb_version_report(FILE *f)
577 {
578 #ifdef DYNLOOKUP
579 fprintf(f, "Library version: DNSDB: Exim version %s\n", EXIM_VERSION_STR);
580 #endif
581 }
582
583
584 static lookup_info _lookup_info = {
585   .name = US"dnsdb",                    /* lookup name */
586   .type = lookup_querystyle,            /* query style */
587   .open = dnsdb_open,                   /* open function */
588   .check = NULL,                        /* check function */
589   .find = dnsdb_find,                   /* find function */
590   .close = NULL,                        /* no close function */
591   .tidy = NULL,                         /* no tidy function */
592   .quote = NULL,                        /* no quoting function */
593   .version_report = dnsdb_version_report           /* version reporting */
594 };
595
596 #ifdef DYNLOOKUP
597 #define dnsdb_lookup_module_info _lookup_module_info
598 #endif
599
600 static lookup_info *_lookup_list[] = { &_lookup_info };
601 lookup_module_info dnsdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
602
603 /* vi: aw ai sw=2
604 */
605 /* End of lookups/dnsdb.c */