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 */