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