xref: /freebsd/crypto/krb5/src/lib/krb5/os/dnsglue.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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