Additions to dnsdb lookups: (a) list of domains (b) change output
[exim.git] / src / src / lookups / dnsdb.c
1 /* $Cambridge: exim/src/src/lookups/dnsdb.c,v 1.3 2004/11/19 15:18:57 ph10 Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2004 */
8 /* See the file NOTICE for conditions of use and distribution. */
9
10 #include "../exim.h"
11 #include "lf_functions.h"
12 #include "dnsdb.h"
13
14
15
16 /* Ancient systems (e.g. SunOS4) don't appear to have T_TXT defined in their
17 header files. */
18
19 #ifndef T_TXT
20 #define T_TXT 16
21 #endif
22
23 /* Table of recognized DNS record types and their integer values. */
24
25 static char *type_names[] = {
26   "a",
27 #if HAVE_IPV6
28   "aaaa",
29   #ifdef SUPPORT_A6
30   "a6",
31   #endif
32 #endif
33   "cname",
34   "mx",
35   "ns",
36   "ptr",
37   "srv",
38   "txt",
39   "zns" 
40 };
41
42 static int type_values[] = {
43   T_A,
44 #if HAVE_IPV6
45   T_AAAA,
46   #ifdef SUPPORT_A6
47   T_A6,
48   #endif
49 #endif
50   T_CNAME,
51   T_MX,
52   T_NS,
53   T_PTR,
54   T_SRV,
55   T_TXT,
56   T_ZNS      /* Private type for "zone nameservers" */
57 };
58
59
60 /*************************************************
61 *              Open entry point                  *
62 *************************************************/
63
64 /* See local README for interface description. */
65
66 void *
67 dnsdb_open(uschar *filename, uschar **errmsg)
68 {
69 filename = filename;   /* Keep picky compilers happy */
70 errmsg = errmsg;       /* Ditto */
71 return (void *)(-1);   /* Any non-0 value */
72 }
73
74
75
76 /*************************************************
77 *           Find entry point for dnsdb           *
78 *************************************************/
79
80 /* See local README for interface description. The query in the "keystring" may
81 consist of a number of parts.
82
83 (a) If the first significant character is '>' then the next character is the 
84 separator character that is used when multiple records are found. The default 
85 separator is newline.
86
87 (b) If the next sequence of characters is a sequence of letters and digits 
88 followed by '=', it is interpreted as the name of the DNS record type. The 
89 default is "A".
90
91 (c) Then there follows list of domain names. This is a generalized Exim list, 
92 which may start with '<' in order to set a specific separator. The default 
93 separator, as always, is colon. */
94
95 int
96 dnsdb_find(void *handle, uschar *filename, uschar *keystring, int length,
97   uschar **result, uschar **errmsg, BOOL *do_cache)
98 {
99 int rc;
100 int size = 256;
101 int ptr = 0;
102 int sep = 0;
103 int type = T_TXT;
104 uschar *outsep = US"\n";
105 uschar *equals, *domain;
106 uschar buffer[256];
107
108 /* Because we're the working in the search pool, we try to reclaim as much
109 store as possible later, so we preallocate the result here */
110
111 uschar *yield = store_get(size);
112
113 dns_record *rr;
114 dns_answer dnsa;
115 dns_scan dnss;
116
117 handle = handle;           /* Keep picky compilers happy */
118 filename = filename;
119 length = length;
120 do_cache = do_cache;
121
122 /* If the string starts with '>' we change the output separator */
123
124 while (isspace(*keystring)) keystring++;
125 if (*keystring == '>')
126   {
127   outsep = keystring + 1;
128   keystring += 2; 
129   while (isspace(*keystring)) keystring++;
130   } 
131
132 /* If the keystring contains an = this must be preceded by a valid type name. */
133
134 if ((equals = Ustrchr(keystring, '=')) != NULL)
135   {
136   int i, len;
137   uschar *tend = equals;
138    
139   while (tend > keystring && isspace(tend[-1])) tend--; 
140   len = tend - keystring; 
141  
142   for (i = 0; i < sizeof(type_names)/sizeof(uschar *); i++)
143     {
144     if (len == Ustrlen(type_names[i]) &&
145         strncmpic(keystring, US type_names[i], len) == 0)
146       {
147       type = type_values[i];
148       break;
149       }
150     }
151      
152   if (i >= sizeof(type_names)/sizeof(uschar *))
153     {
154     *errmsg = US"unsupported DNS record type";
155     return DEFER;
156     }
157      
158   keystring = equals + 1;
159   while (isspace(*keystring)) keystring++;
160   }
161   
162 /* Initialize the resolver in case this is the first time it has been used. */
163
164 dns_init(FALSE, FALSE);
165
166 /* The remainder of the string must be a list of domains. As long as the lookup 
167 for at least one of them succeeds, we return success. Failure means that none 
168 of them were found. 
169
170 The original implementation did not support a list of domains. Adding the list 
171 feature is compatible, except in one case: when PTR records are being looked up 
172 for a single IPv6 address. Fortunately, we can hack in a compatibility feature 
173 here: If the type is PTR and no list separator is specified, and the entire 
174 remaining string is valid as an IP address, set an impossible separator so that 
175 it is treated as one item. */
176
177 if (type == T_PTR && keystring[0] != '<' &&
178     string_is_ip_address(keystring, NULL) > 0) 
179   sep = -1;
180
181 /* Now scan the list and do a lookup for each item */
182
183 while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer))) 
184         != NULL)
185   {       
186   uschar rbuffer[256];
187
188   /* If the type is PTR, we have to construct the relevant magic lookup
189   key. This code is now in a separate function. */
190   
191   if (type == T_PTR)
192     {
193     dns_build_reverse(domain, rbuffer);
194     domain = rbuffer;
195     }
196   
197   DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain);
198   
199   /* Do the lookup and sort out the result. We use the special 
200   lookup function that knows about pseudo types like "zns". If the lookup 
201   fails, continue with the next domain. */
202   
203   rc = dns_special_lookup(&dnsa, domain, type, NULL);
204   
205   if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue;
206   if (rc != DNS_SUCCEED) return DEFER;
207   
208   /* If the lookup was a pseudo-type, change it to the correct type for
209   searching the returned records; then search for them. */
210   
211   if (type == T_ZNS) type = T_NS;
212   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
213        rr != NULL;
214        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
215     {
216     if (rr->type != type) continue;
217   
218     /* There may be several addresses from an A6 record. Put the configured 
219     separator between them, just as for between several records. However, A6 
220     support is not normally configured these days. */
221   
222     if (type == T_A ||
223         #ifdef SUPPORT_A6
224         type == T_A6 ||
225         #endif
226         type == T_AAAA)
227       {
228       dns_address *da;
229       for (da = dns_address_from_rr(&dnsa, rr); da != NULL; da = da->next)
230         {
231         if (ptr != 0) yield = string_cat(yield, &size, &ptr, outsep, 1);
232         yield = string_cat(yield, &size, &ptr, da->address, 
233           Ustrlen(da->address));
234         }
235       continue;
236       }
237   
238     /* Other kinds of record just have one piece of data each, but there may be 
239     several of them, of course. */
240   
241     if (ptr != 0) yield = string_cat(yield, &size, &ptr, outsep, 1);
242   
243     if (type == T_TXT)
244       {
245       yield = string_cat(yield, &size, &ptr, (uschar *)(rr->data+1),
246         (rr->data)[0]);
247       }
248     else   /* T_CNAME, T_MX, T_NS, T_SRV, T_PTR */
249       {
250       uschar s[264];
251       uschar *p = (uschar *)(rr->data);
252       if (type == T_MX)
253         {
254         int num;
255         GETSHORT(num, p);            /* pointer is advanced */
256         sprintf(CS s, "%d ", num);
257         yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
258         }
259       else if (type == T_SRV)
260         {
261         int num, weight, port;
262         GETSHORT(num, p);            /* pointer is advanced */
263         GETSHORT(weight, p);
264         GETSHORT(port, p);
265         sprintf(CS s, "%d %d %d ", num, weight, port);
266         yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
267         }
268       rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
269         (DN_EXPAND_ARG4_TYPE)(s), sizeof(s));
270   
271       /* If an overlong response was received, the data will have been
272       truncated and dn_expand may fail. */
273   
274       if (rc < 0)
275         {
276         log_write(0, LOG_MAIN, "host name alias list truncated: type=%s "
277           "domain=%s", dns_text_type(type), domain);
278         break;
279         }
280       else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
281       }
282     }    /* Loop for list of returned records */
283   }      /* Loop for list of domains */
284
285 /* Reclaim unused memory */
286
287 store_reset(yield + ptr + 1);
288
289 /* If ptr == 0 we have not found anything. Otherwise, insert the terminating 
290 zero and return the result. */
291
292 if (ptr == 0) return FAIL;
293 yield[ptr] = 0;
294 *result = yield;
295 return OK;
296 }
297
298 /* End of lookups/dnsdb.c */