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