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