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