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