1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/dnsglue.c */
3 /*
4 * Copyright 2004, 2009 by the Massachusetts Institute of Technology.
5 * All Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 #include "k5-int.h"
28 #include "os-proto.h"
29
30 #ifdef KRB5_DNS_LOOKUP
31
32 #ifndef _WIN32
33
34 #include "dnsglue.h"
35 #ifdef __APPLE__
36 #include <dns.h>
37 #endif
38
39 /*
40 * Only use res_ninit() if there's also a res_ndestroy(), to avoid
41 * memory leaks (Linux & Solaris) and outright corruption (AIX 4.x,
42 * 5.x). While we're at it, make sure res_nsearch() is there too.
43 *
44 * In any case, it is probable that platforms having broken
45 * res_ninit() will have thread safety hacks for res_init() and _res.
46 */
47
48 /*
49 * Opaque handle
50 */
51 struct krb5int_dns_state {
52 int nclass;
53 int ntype;
54 void *ansp;
55 int anslen;
56 int ansmax;
57 #if HAVE_NS_INITPARSE
58 int cur_ans;
59 ns_msg msg;
60 #else
61 unsigned char *ptr;
62 unsigned short nanswers;
63 #endif
64 };
65
66 #if !HAVE_NS_INITPARSE
67 static int initparse(struct krb5int_dns_state *);
68 #endif
69
70 /*
71 * Define macros to use the best available DNS search functions. INIT_HANDLE()
72 * returns true if handle initialization is successful, false if it is not.
73 * SEARCH() returns the length of the response or -1 on error.
74 * PRIMARY_DOMAIN() returns the first search domain in allocated memory.
75 * DECLARE_HANDLE() must be used last in the declaration list since it may
76 * evaluate to nothing.
77 */
78
79 #if defined(__APPLE__)
80
81 /* Use the macOS interfaces dns_open, dns_search, and dns_free. */
82 #define DECLARE_HANDLE(h) dns_handle_t h
83 #define INIT_HANDLE(h) ((h = dns_open(NULL)) != NULL)
84 #define SEARCH(h, n, c, t, a, l) dns_search(h, n, c, t, a, l, NULL, NULL)
85 #define PRIMARY_DOMAIN(h) dns_search_list_domain(h, 0)
86 #define DESTROY_HANDLE(h) dns_free(h)
87
88 #elif HAVE_RES_NINIT && HAVE_RES_NSEARCH
89
90 /* Use res_ninit, res_nsearch, and res_ndestroy or res_nclose. */
91 #define DECLARE_HANDLE(h) struct __res_state h
92 #define INIT_HANDLE(h) (memset(&h, 0, sizeof(h)), res_ninit(&h) == 0)
93 #define SEARCH(h, n, c, t, a, l) res_nsearch(&h, n, c, t, a, l)
94 #define PRIMARY_DOMAIN(h) ((h.dnsrch[0] == NULL) ? NULL : strdup(h.dnsrch[0]))
95 #if HAVE_RES_NDESTROY
96 #define DESTROY_HANDLE(h) res_ndestroy(&h)
97 #else
98 #define DESTROY_HANDLE(h) res_nclose(&h)
99 #endif
100
101 #else
102
103 /* Use res_init and res_search. */
104 #define DECLARE_HANDLE(h)
105 #define INIT_HANDLE(h) (res_init() == 0)
106 #define SEARCH(h, n, c, t, a, l) res_search(n, c, t, a, l)
107 #define PRIMARY_DOMAIN(h) \
108 ((_res.defdname == NULL) ? NULL : strdup(_res.defdname))
109 #define DESTROY_HANDLE(h)
110
111 #endif
112
113 /*
114 * krb5int_dns_init()
115 *
116 * Initialize an opaque handle. Do name lookup and initial parsing of
117 * reply, skipping question section. Prepare to iterate over answer
118 * section. Returns -1 on error, 0 on success.
119 */
120 int
krb5int_dns_init(struct krb5int_dns_state ** dsp,char * host,int nclass,int ntype)121 krb5int_dns_init(struct krb5int_dns_state **dsp,
122 char *host, int nclass, int ntype)
123 {
124 struct krb5int_dns_state *ds;
125 int len, ret;
126 size_t nextincr, maxincr;
127 unsigned char *p;
128 DECLARE_HANDLE(h);
129
130 *dsp = ds = malloc(sizeof(*ds));
131 if (ds == NULL)
132 return -1;
133
134 ret = -1;
135 ds->nclass = nclass;
136 ds->ntype = ntype;
137 ds->ansp = NULL;
138 ds->anslen = 0;
139 ds->ansmax = 0;
140 nextincr = 4096;
141 maxincr = INT_MAX;
142
143 #if HAVE_NS_INITPARSE
144 ds->cur_ans = 0;
145 #endif
146
147 if (!INIT_HANDLE(h))
148 return -1;
149
150 do {
151 p = (ds->ansp == NULL)
152 ? malloc(nextincr) : realloc(ds->ansp, nextincr);
153
154 if (p == NULL) {
155 ret = -1;
156 goto errout;
157 }
158 ds->ansp = p;
159 ds->ansmax = nextincr;
160
161 len = SEARCH(h, host, ds->nclass, ds->ntype, ds->ansp, ds->ansmax);
162 if ((size_t) len > maxincr) {
163 ret = -1;
164 goto errout;
165 }
166 while (nextincr < (size_t) len)
167 nextincr *= 2;
168 if (len < 0 || nextincr > maxincr) {
169 ret = -1;
170 goto errout;
171 }
172 } while (len > ds->ansmax);
173
174 ds->anslen = len;
175 #if HAVE_NS_INITPARSE
176 ret = ns_initparse(ds->ansp, ds->anslen, &ds->msg);
177 #else
178 ret = initparse(ds);
179 #endif
180 if (ret < 0)
181 goto errout;
182
183 ret = 0;
184
185 errout:
186 DESTROY_HANDLE(h);
187 if (ret < 0) {
188 if (ds->ansp != NULL) {
189 free(ds->ansp);
190 ds->ansp = NULL;
191 }
192 }
193
194 return ret;
195 }
196
197 #if HAVE_NS_INITPARSE
198 /*
199 * krb5int_dns_nextans - get next matching answer record
200 *
201 * Sets pp to NULL if no more records. Returns -1 on error, 0 on
202 * success.
203 */
204 int
krb5int_dns_nextans(struct krb5int_dns_state * ds,const unsigned char ** pp,int * lenp)205 krb5int_dns_nextans(struct krb5int_dns_state *ds,
206 const unsigned char **pp, int *lenp)
207 {
208 int len;
209 ns_rr rr;
210
211 *pp = NULL;
212 *lenp = 0;
213 while (ds->cur_ans < ns_msg_count(ds->msg, ns_s_an)) {
214 len = ns_parserr(&ds->msg, ns_s_an, ds->cur_ans, &rr);
215 if (len < 0)
216 return -1;
217 ds->cur_ans++;
218 if (ds->nclass == (int)ns_rr_class(rr)
219 && ds->ntype == (int)ns_rr_type(rr)) {
220 *pp = ns_rr_rdata(rr);
221 *lenp = ns_rr_rdlen(rr);
222 return 0;
223 }
224 }
225 return 0;
226 }
227 #endif
228
229 /*
230 * krb5int_dns_expand - wrapper for dn_expand()
231 */
232 int
krb5int_dns_expand(struct krb5int_dns_state * ds,const unsigned char * p,char * buf,int len)233 krb5int_dns_expand(struct krb5int_dns_state *ds, const unsigned char *p,
234 char *buf, int len)
235 {
236
237 #if HAVE_NS_NAME_UNCOMPRESS
238 return ns_name_uncompress(ds->ansp,
239 (unsigned char *)ds->ansp + ds->anslen,
240 p, buf, (size_t)len);
241 #else
242 return dn_expand(ds->ansp,
243 (unsigned char *)ds->ansp + ds->anslen,
244 p, buf, len);
245 #endif
246 }
247
248 /*
249 * Free stuff.
250 */
251 void
krb5int_dns_fini(struct krb5int_dns_state * ds)252 krb5int_dns_fini(struct krb5int_dns_state *ds)
253 {
254 if (ds == NULL)
255 return;
256 if (ds->ansp != NULL)
257 free(ds->ansp);
258 free(ds);
259 }
260
261 /*
262 * Compat routines for BIND 4
263 */
264 #if !HAVE_NS_INITPARSE
265
266 /*
267 * initparse
268 *
269 * Skip header and question section of reply. Set a pointer to the
270 * beginning of the answer section, and prepare to iterate over
271 * answer records.
272 */
273 static int
initparse(struct krb5int_dns_state * ds)274 initparse(struct krb5int_dns_state *ds)
275 {
276 HEADER *hdr;
277 unsigned char *p;
278 unsigned short nqueries, nanswers;
279 int len;
280 #if !HAVE_DN_SKIPNAME
281 char host[MAXDNAME];
282 #endif
283
284 if ((size_t) ds->anslen < sizeof(HEADER))
285 return -1;
286
287 hdr = (HEADER *)ds->ansp;
288 p = ds->ansp;
289 nqueries = ntohs((unsigned short)hdr->qdcount);
290 nanswers = ntohs((unsigned short)hdr->ancount);
291 p += sizeof(HEADER);
292
293 /*
294 * Skip query records.
295 */
296 while (nqueries--) {
297 #if HAVE_DN_SKIPNAME
298 len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen);
299 #else
300 len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen,
301 p, host, sizeof(host));
302 #endif
303 if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len + 4))
304 return -1;
305 p += len + 4;
306 }
307 ds->ptr = p;
308 ds->nanswers = nanswers;
309 return 0;
310 }
311
312 /*
313 * krb5int_dns_nextans() - get next answer record
314 *
315 * Sets pp to NULL if no more records.
316 */
317 int
krb5int_dns_nextans(struct krb5int_dns_state * ds,const unsigned char ** pp,int * lenp)318 krb5int_dns_nextans(struct krb5int_dns_state *ds,
319 const unsigned char **pp, int *lenp)
320 {
321 int len;
322 unsigned char *p;
323 unsigned short ntype, nclass, rdlen;
324 #if !HAVE_DN_SKIPNAME
325 char host[MAXDNAME];
326 #endif
327
328 *pp = NULL;
329 *lenp = 0;
330 p = ds->ptr;
331
332 while (ds->nanswers--) {
333 #if HAVE_DN_SKIPNAME
334 len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen);
335 #else
336 len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen,
337 p, host, sizeof(host));
338 #endif
339 if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len))
340 return -1;
341 p += len;
342 SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, ntype, out);
343 /* Also skip 4 bytes of TTL */
344 SAFE_GETUINT16(ds->ansp, ds->anslen, p, 6, nclass, out);
345 SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, rdlen, out);
346
347 if (!INCR_OK(ds->ansp, ds->anslen, p, rdlen))
348 return -1;
349 if (rdlen > INT_MAX)
350 return -1;
351 if (nclass == ds->nclass && ntype == ds->ntype) {
352 *pp = p;
353 *lenp = rdlen;
354 ds->ptr = p + rdlen;
355 return 0;
356 }
357 p += rdlen;
358 }
359 return 0;
360 out:
361 return -1;
362 }
363
364 #endif /* !HAVE_NS_INITPARSE */
365 #endif /* not _WIN32 */
366
367 /* Construct a DNS label of the form "prefix[.name.]". name may be NULL. */
368 static char *
txt_lookup_name(const char * prefix,const char * name)369 txt_lookup_name(const char *prefix, const char *name)
370 {
371 struct k5buf buf;
372
373 k5_buf_init_dynamic(&buf);
374
375 if (name == NULL || name[0] == '\0') {
376 k5_buf_add(&buf, prefix);
377 } else {
378 k5_buf_add_fmt(&buf, "%s.%s", prefix, name);
379
380 /*
381 * Realm names don't (normally) end with ".", but if the query doesn't
382 * end with "." and doesn't get an answer as is, the resolv code will
383 * try appending the local domain. Since the realm names are
384 * absolutes, let's stop that.
385 *
386 * But only if a name has been specified. If we are performing a
387 * search on the prefix alone then the intention is to allow the local
388 * domain or domain search lists to be expanded.
389 */
390
391 if (buf.len > 0 && ((char *)buf.data)[buf.len - 1] != '.')
392 k5_buf_add(&buf, ".");
393 }
394
395 return k5_buf_cstring(&buf);
396 }
397
398 /*
399 * Try to look up a TXT record pointing to a Kerberos realm
400 */
401
402 #ifdef _WIN32
403
404 #include <windns.h>
405
406 krb5_error_code
k5_try_realm_txt_rr(krb5_context context,const char * prefix,const char * name,char ** realm)407 k5_try_realm_txt_rr(krb5_context context, const char *prefix, const char *name,
408 char **realm)
409 {
410 krb5_error_code ret = 0;
411 char *txtname = NULL;
412 PDNS_RECORD rr = NULL;
413 DNS_STATUS st;
414
415 *realm = NULL;
416
417 txtname = txt_lookup_name(prefix, name);
418 if (txtname == NULL)
419 return ENOMEM;
420
421 st = DnsQuery_UTF8(txtname, DNS_TYPE_TEXT, DNS_QUERY_STANDARD, NULL,
422 &rr, NULL);
423 if (st != ERROR_SUCCESS || rr == NULL) {
424 TRACE_TXT_LOOKUP_NOTFOUND(context, txtname);
425 ret = KRB5_ERR_HOST_REALM_UNKNOWN;
426 goto cleanup;
427 }
428
429 *realm = strdup(rr->Data.TXT.pStringArray[0]);
430 if (*realm == NULL)
431 ret = ENOMEM;
432 TRACE_TXT_LOOKUP_SUCCESS(context, txtname, *realm);
433
434 cleanup:
435 free(txtname);
436 if (rr != NULL)
437 DnsRecordListFree(rr, DnsFreeRecordList);
438 return ret;
439 }
440
441 char *
k5_primary_domain()442 k5_primary_domain()
443 {
444 return NULL;
445 }
446
447 #else /* _WIN32 */
448
449 krb5_error_code
k5_try_realm_txt_rr(krb5_context context,const char * prefix,const char * name,char ** realm)450 k5_try_realm_txt_rr(krb5_context context, const char *prefix, const char *name,
451 char **realm)
452 {
453 krb5_error_code retval = KRB5_ERR_HOST_REALM_UNKNOWN;
454 const unsigned char *p, *base;
455 char *txtname = NULL;
456 int ret, rdlen, len;
457 struct krb5int_dns_state *ds = NULL;
458
459 /*
460 * Form our query, and send it via DNS
461 */
462
463 txtname = txt_lookup_name(prefix, name);
464 if (txtname == NULL)
465 return ENOMEM;
466 ret = krb5int_dns_init(&ds, txtname, C_IN, T_TXT);
467 if (ret < 0) {
468 TRACE_TXT_LOOKUP_NOTFOUND(context, txtname);
469 goto errout;
470 }
471
472 ret = krb5int_dns_nextans(ds, &base, &rdlen);
473 if (ret < 0 || base == NULL)
474 goto errout;
475
476 p = base;
477 if (!INCR_OK(base, rdlen, p, 1))
478 goto errout;
479 len = *p++;
480 *realm = malloc((size_t)len + 1);
481 if (*realm == NULL) {
482 retval = ENOMEM;
483 goto errout;
484 }
485 strncpy(*realm, (const char *)p, (size_t)len);
486 (*realm)[len] = '\0';
487 /* Avoid a common error. */
488 if ( (*realm)[len-1] == '.' )
489 (*realm)[len-1] = '\0';
490 retval = 0;
491 TRACE_TXT_LOOKUP_SUCCESS(context, txtname, *realm);
492
493 errout:
494 krb5int_dns_fini(ds);
495 free(txtname);
496 return retval;
497 }
498
499 char *
k5_primary_domain()500 k5_primary_domain()
501 {
502 char *domain;
503 DECLARE_HANDLE(h);
504
505 if (!INIT_HANDLE(h))
506 return NULL;
507 domain = PRIMARY_DOMAIN(h);
508 DESTROY_HANDLE(h);
509 return domain;
510 }
511
512 #endif /* not _WIN32 */
513 #endif /* KRB5_DNS_LOOKUP */
514