1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 #include "lf_functions.h"
13 /* Ancient systems (e.g. SunOS4) don't appear to have T_TXT defined in their
20 /* Table of recognized DNS record types and their integer values. */
22 static const char *type_names[] = {
41 static int type_values[] = {
50 T_CSA, /* Private type for "Client SMTP Authorization". */
52 T_MXH, /* Private type for "MX hostnames" */
57 T_ZNS /* Private type for "zone nameservers" */
61 /*************************************************
63 *************************************************/
65 /* See local README for interface description. */
68 dnsdb_open(uschar *filename, uschar **errmsg)
70 filename = filename; /* Keep picky compilers happy */
71 errmsg = errmsg; /* Ditto */
72 return (void *)(-1); /* Any non-0 value */
77 /*************************************************
78 * Find entry point for dnsdb *
79 *************************************************/
81 /* See local README for interface description. The query in the "keystring" may
82 consist of a number of parts.
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
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
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'.
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
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. */
109 dnsdb_find(void *handle, uschar *filename, uschar *keystring, int length,
110 uschar **result, uschar **errmsg, BOOL *do_cache)
116 int defer_mode = PASS;
119 uschar *outsep = US"\n";
120 uschar *outsep2 = NULL;
121 uschar *equals, *domain, *found;
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 */
127 uschar *yield = store_get(size);
133 handle = handle; /* Keep picky compilers happy */
138 /* If the string starts with '>' we change the output separator.
139 If it's followed by ';' or ',' we set the TXT output separator. */
141 while (isspace(*keystring)) keystring++;
142 if (*keystring == '>')
144 outsep = keystring + 1;
146 if (*keystring == ',')
148 outsep2 = keystring + 1;
151 else if (*keystring == ';')
156 while (isspace(*keystring)) keystring++;
159 /* Check for a defer behaviour keyword. */
161 if (strncmpic(keystring, US"defer_", 6) == 0)
164 if (strncmpic(keystring, US"strict", 6) == 0)
169 else if (strncmpic(keystring, US"lax", 3) == 0)
174 else if (strncmpic(keystring, US"never", 5) == 0)
181 *errmsg = US"unsupported dnsdb defer behaviour";
184 while (isspace(*keystring)) keystring++;
185 if (*keystring++ != ',')
187 *errmsg = US"dnsdb defer behaviour syntax error";
190 while (isspace(*keystring)) keystring++;
193 /* If the keystring contains an = this must be preceded by a valid type name. */
195 if ((equals = Ustrchr(keystring, '=')) != NULL)
198 uschar *tend = equals;
200 while (tend > keystring && isspace(tend[-1])) tend--;
201 len = tend - keystring;
203 for (i = 0; i < sizeof(type_names)/sizeof(uschar *); i++)
205 if (len == Ustrlen(type_names[i]) &&
206 strncmpic(keystring, US type_names[i], len) == 0)
208 type = type_values[i];
213 if (i >= sizeof(type_names)/sizeof(uschar *))
215 *errmsg = US"unsupported DNS record type";
219 keystring = equals + 1;
220 while (isspace(*keystring)) keystring++;
223 /* Initialize the resolver in case this is the first time it has been used. */
225 dns_init(FALSE, FALSE);
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
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. */
238 if (type == T_PTR && keystring[0] != '<' &&
239 string_is_ip_address(keystring, NULL) != 0)
242 /* Now scan the list and do a lookup for each item */
244 while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
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;
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. */
258 if ((type == T_PTR || type == T_CSA) &&
259 string_is_ip_address(domain, NULL) != 0)
261 dns_build_reverse(domain, rbuffer);
265 DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain);
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. */
276 rc = dns_special_lookup(&dnsa, domain, type, &found);
278 if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue;
279 if (rc != DNS_SUCCEED)
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 */
286 /* Search the returned records */
288 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
290 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
292 if (rr->type != searchtype) continue;
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. */
305 for (da = dns_address_from_rr(&dnsa, rr); da != NULL; da = da->next)
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));
314 /* Other kinds of record just have one piece of data each, but there may be
315 several of them, of course. */
317 if (ptr != 0) yield = string_cat(yield, &size, &ptr, outsep, 1);
323 /* output only the first item of data */
324 yield = string_cat(yield, &size, &ptr, (uschar *)(rr->data+1),
329 /* output all items */
331 while (data_offset < rr->size)
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;
342 else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SRV */
344 int priority, weight, port;
346 uschar *p = (uschar *)(rr->data);
350 /* mxh ignores the priority number and includes only the hostnames */
351 GETSHORT(priority, p);
353 else if (type == T_MX)
355 GETSHORT(priority, p);
356 sprintf(CS s, "%d ", priority);
357 yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
359 else if (type == T_SRV)
361 GETSHORT(priority, p);
364 sprintf(CS s, "%d %d %d ", priority, weight, port);
365 yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
367 else if (type == T_CSA)
369 /* See acl_verify_csa() for more comments about CSA. */
371 GETSHORT(priority, p);
375 if (priority != 1) continue; /* CSA version must be 1 */
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. */
383 if (port & 1) *s = 'X'; /* explicit authorization required */
384 else *s = '?'; /* no subdomain assertions here */
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 */
395 yield = string_cat(yield, &size, &ptr, s, 2);
398 /* GETSHORT() has advanced the pointer to the target domain. */
400 rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
401 (DN_EXPAND_ARG4_TYPE)(s), sizeof(s));
403 /* If an overlong response was received, the data will have been
404 truncated and dn_expand may fail. */
408 log_write(0, LOG_MAIN, "host name alias list truncated: type=%s "
409 "domain=%s", dns_text_type(type), domain);
412 else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
414 } /* Loop for list of returned records */
415 } /* Loop for list of domains */
417 /* Reclaim unused memory */
419 store_reset(yield + ptr + 1);
421 /* If ptr == 0 we have not found anything. Otherwise, insert the terminating
422 zero and return the result. */
424 if (ptr == 0) return failrc;
432 /*************************************************
433 * Version reporting entry point *
434 *************************************************/
436 /* See local README for interface description. */
438 #include "../version.h"
441 dnsdb_version_report(FILE *f)
444 fprintf(f, "Library version: DNSDB: Exim version %s\n", EXIM_VERSION_STR);
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 */
462 #define dnsdb_lookup_module_info _lookup_module_info
465 static lookup_info *_lookup_list[] = { &_lookup_info };
466 lookup_module_info dnsdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
468 /* End of lookups/dnsdb.c */