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