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