1e8df3b169e51d13dc610b15574dc625bc86dd1d
[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 */