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