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