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