Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / os / dnsglue.c
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 "autoconf.h"
28 #ifdef KRB5_DNS_LOOKUP
29
30 #include "dnsglue.h"
31 #ifdef __APPLE__
32 #include <dns.h>
33 #endif
34
35 /*
36  * Only use res_ninit() if there's also a res_ndestroy(), to avoid
37  * memory leaks (Linux & Solaris) and outright corruption (AIX 4.x,
38  * 5.x).  While we're at it, make sure res_nsearch() is there too.
39  *
40  * In any case, it is probable that platforms having broken
41  * res_ninit() will have thread safety hacks for res_init() and _res.
42  */
43
44 /*
45  * Opaque handle
46  */
47 struct krb5int_dns_state {
48     int nclass;
49     int ntype;
50     void *ansp;
51     int anslen;
52     int ansmax;
53 #if HAVE_NS_INITPARSE
54     int cur_ans;
55     ns_msg msg;
56 #else
57     unsigned char *ptr;
58     unsigned short nanswers;
59 #endif
60 };
61
62 #if !HAVE_NS_INITPARSE
63 static int initparse(struct krb5int_dns_state *);
64 #endif
65
66 /*
67  * Define macros to use the best available DNS search functions.  INIT_HANDLE()
68  * returns true if handle initialization is successful, false if it is not.
69  * SEARCH() returns the length of the response or -1 on error.
70  * DECLARE_HANDLE() must be used last in the declaration list since it may
71  * evaluate to nothing.
72  */
73
74 #if defined(__APPLE__)
75
76 /* Use the OS X interfaces dns_open, dns_search, and dns_free. */
77 #define DECLARE_HANDLE(h) dns_handle_t h
78 #define INIT_HANDLE(h) ((h = dns_open(NULL)) != NULL)
79 #define SEARCH(h, n, c, t, a, l) dns_search(h, n, c, t, a, l, NULL, NULL)
80 #define DESTROY_HANDLE(h) dns_free(h)
81
82 #elif HAVE_RES_NINIT && HAVE_RES_NSEARCH
83
84 /* Use res_ninit, res_nsearch, and res_ndestroy or res_nclose. */
85 #define DECLARE_HANDLE(h) struct __res_state h
86 #define INIT_HANDLE(h) (memset(&h, 0, sizeof(h)), res_ninit(&h) == 0)
87 #define SEARCH(h, n, c, t, a, l) res_nsearch(&h, n, c, t, a, l)
88 #if HAVE_RES_NDESTROY
89 #define DESTROY_HANDLE(h) res_ndestroy(&h)
90 #else
91 #define DESTROY_HANDLE(h) res_nclose(&h)
92 #endif
93
94 #else
95
96 /* Use res_init and res_search. */
97 #define DECLARE_HANDLE(h)
98 #define INIT_HANDLE(h) (res_init() == 0)
99 #define SEARCH(h, n, c, t, a, l) res_search(n, c, t, a, l)
100 #define DESTROY_HANDLE(h)
101
102 #endif
103
104 /*
105  * krb5int_dns_init()
106  *
107  * Initialize an opaque handle.  Do name lookup and initial parsing of
108  * reply, skipping question section.  Prepare to iterate over answer
109  * section.  Returns -1 on error, 0 on success.
110  */
111 int
112 krb5int_dns_init(struct krb5int_dns_state **dsp,
113                  char *host, int nclass, int ntype)
114 {
115     struct krb5int_dns_state *ds;
116     int len, ret;
117     size_t nextincr, maxincr;
118     unsigned char *p;
119     DECLARE_HANDLE(h);
120
121     *dsp = ds = malloc(sizeof(*ds));
122     if (ds == NULL)
123         return -1;
124
125     ret = -1;
126     ds->nclass = nclass;
127     ds->ntype = ntype;
128     ds->ansp = NULL;
129     ds->anslen = 0;
130     ds->ansmax = 0;
131     nextincr = 4096;
132     maxincr = INT_MAX;
133
134 #if HAVE_NS_INITPARSE
135     ds->cur_ans = 0;
136 #endif
137
138     if (!INIT_HANDLE(h))
139         return -1;
140
141     do {
142         p = (ds->ansp == NULL)
143             ? malloc(nextincr) : realloc(ds->ansp, nextincr);
144
145         if (p == NULL) {
146             ret = -1;
147             goto errout;
148         }
149         ds->ansp = p;
150         ds->ansmax = nextincr;
151
152         len = SEARCH(h, host, ds->nclass, ds->ntype, ds->ansp, ds->ansmax);
153         if ((size_t) len > maxincr) {
154             ret = -1;
155             goto errout;
156         }
157         while (nextincr < (size_t) len)
158             nextincr *= 2;
159         if (len < 0 || nextincr > maxincr) {
160             ret = -1;
161             goto errout;
162         }
163     } while (len > ds->ansmax);
164
165     ds->anslen = len;
166 #if HAVE_NS_INITPARSE
167     ret = ns_initparse(ds->ansp, ds->anslen, &ds->msg);
168 #else
169     ret = initparse(ds);
170 #endif
171     if (ret < 0)
172         goto errout;
173
174     ret = 0;
175
176 errout:
177     DESTROY_HANDLE(h);
178     if (ret < 0) {
179         if (ds->ansp != NULL) {
180             free(ds->ansp);
181             ds->ansp = NULL;
182         }
183     }
184
185     return ret;
186 }
187
188 #if HAVE_NS_INITPARSE
189 /*
190  * krb5int_dns_nextans - get next matching answer record
191  *
192  * Sets pp to NULL if no more records.  Returns -1 on error, 0 on
193  * success.
194  */
195 int
196 krb5int_dns_nextans(struct krb5int_dns_state *ds,
197                     const unsigned char **pp, int *lenp)
198 {
199     int len;
200     ns_rr rr;
201
202     *pp = NULL;
203     *lenp = 0;
204     while (ds->cur_ans < ns_msg_count(ds->msg, ns_s_an)) {
205         len = ns_parserr(&ds->msg, ns_s_an, ds->cur_ans, &rr);
206         if (len < 0)
207             return -1;
208         ds->cur_ans++;
209         if (ds->nclass == (int)ns_rr_class(rr)
210             && ds->ntype == (int)ns_rr_type(rr)) {
211             *pp = ns_rr_rdata(rr);
212             *lenp = ns_rr_rdlen(rr);
213             return 0;
214         }
215     }
216     return 0;
217 }
218 #endif
219
220 /*
221  * krb5int_dns_expand - wrapper for dn_expand()
222  */
223 int
224 krb5int_dns_expand(struct krb5int_dns_state *ds, const unsigned char *p,
225                    char *buf, int len)
226 {
227
228 #if HAVE_NS_NAME_UNCOMPRESS
229     return ns_name_uncompress(ds->ansp,
230                               (unsigned char *)ds->ansp + ds->anslen,
231                               p, buf, (size_t)len);
232 #else
233     return dn_expand(ds->ansp,
234                      (unsigned char *)ds->ansp + ds->anslen,
235                      p, buf, len);
236 #endif
237 }
238
239 /*
240  * Free stuff.
241  */
242 void
243 krb5int_dns_fini(struct krb5int_dns_state *ds)
244 {
245     if (ds == NULL)
246         return;
247     if (ds->ansp != NULL)
248         free(ds->ansp);
249     free(ds);
250 }
251
252 /*
253  * Compat routines for BIND 4
254  */
255 #if !HAVE_NS_INITPARSE
256
257 /*
258  * initparse
259  *
260  * Skip header and question section of reply.  Set a pointer to the
261  * beginning of the answer section, and prepare to iterate over
262  * answer records.
263  */
264 static int
265 initparse(struct krb5int_dns_state *ds)
266 {
267     HEADER *hdr;
268     unsigned char *p;
269     unsigned short nqueries, nanswers;
270     int len;
271 #if !HAVE_DN_SKIPNAME
272     char host[MAXDNAME];
273 #endif
274
275     if ((size_t) ds->anslen < sizeof(HEADER))
276         return -1;
277
278     hdr = (HEADER *)ds->ansp;
279     p = ds->ansp;
280     nqueries = ntohs((unsigned short)hdr->qdcount);
281     nanswers = ntohs((unsigned short)hdr->ancount);
282     p += sizeof(HEADER);
283
284     /*
285      * Skip query records.
286      */
287     while (nqueries--) {
288 #if HAVE_DN_SKIPNAME
289         len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen);
290 #else
291         len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen,
292                         p, host, sizeof(host));
293 #endif
294         if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len + 4))
295             return -1;
296         p += len + 4;
297     }
298     ds->ptr = p;
299     ds->nanswers = nanswers;
300     return 0;
301 }
302
303 /*
304  * krb5int_dns_nextans() - get next answer record
305  *
306  * Sets pp to NULL if no more records.
307  */
308 int
309 krb5int_dns_nextans(struct krb5int_dns_state *ds,
310                     const unsigned char **pp, int *lenp)
311 {
312     int len;
313     unsigned char *p;
314     unsigned short ntype, nclass, rdlen;
315 #if !HAVE_DN_SKIPNAME
316     char host[MAXDNAME];
317 #endif
318
319     *pp = NULL;
320     *lenp = 0;
321     p = ds->ptr;
322
323     while (ds->nanswers--) {
324 #if HAVE_DN_SKIPNAME
325         len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen);
326 #else
327         len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen,
328                         p, host, sizeof(host));
329 #endif
330         if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len))
331             return -1;
332         p += len;
333         SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, ntype, out);
334         /* Also skip 4 bytes of TTL */
335         SAFE_GETUINT16(ds->ansp, ds->anslen, p, 6, nclass, out);
336         SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, rdlen, out);
337
338         if (!INCR_OK(ds->ansp, ds->anslen, p, rdlen))
339             return -1;
340         if (rdlen > INT_MAX)
341             return -1;
342         if (nclass == ds->nclass && ntype == ds->ntype) {
343             *pp = p;
344             *lenp = rdlen;
345             ds->ptr = p + rdlen;
346             return 0;
347         }
348         p += rdlen;
349     }
350     return 0;
351 out:
352     return -1;
353 }
354
355 #endif
356
357 /*
358  * Try to look up a TXT record pointing to a Kerberos realm
359  */
360
361 krb5_error_code
362 k5_try_realm_txt_rr(krb5_context context, const char *prefix, const char *name,
363                     char **realm)
364 {
365     krb5_error_code retval = KRB5_ERR_HOST_REALM_UNKNOWN;
366     const unsigned char *p, *base;
367     char host[MAXDNAME];
368     int ret, rdlen, len;
369     struct krb5int_dns_state *ds = NULL;
370     struct k5buf buf;
371
372     /*
373      * Form our query, and send it via DNS
374      */
375
376     k5_buf_init_fixed(&buf, host, sizeof(host));
377     if (name == NULL || name[0] == '\0') {
378         k5_buf_add(&buf, prefix);
379     } else {
380         k5_buf_add_fmt(&buf, "%s.%s", prefix, name);
381
382         /* Realm names don't (normally) end with ".", but if the query
383            doesn't end with "." and doesn't get an answer as is, the
384            resolv code will try appending the local domain.  Since the
385            realm names are absolutes, let's stop that.
386
387            But only if a name has been specified.  If we are performing
388            a search on the prefix alone then the intention is to allow
389            the local domain or domain search lists to be expanded.
390         */
391
392         if (buf.len > 0 && host[buf.len - 1] != '.')
393             k5_buf_add(&buf, ".");
394     }
395     if (k5_buf_status(&buf) != 0)
396         return KRB5_ERR_HOST_REALM_UNKNOWN;
397     ret = krb5int_dns_init(&ds, host, C_IN, T_TXT);
398     if (ret < 0) {
399         TRACE_TXT_LOOKUP_NOTFOUND(context, host);
400         goto errout;
401     }
402
403     ret = krb5int_dns_nextans(ds, &base, &rdlen);
404     if (ret < 0 || base == NULL)
405         goto errout;
406
407     p = base;
408     if (!INCR_OK(base, rdlen, p, 1))
409         goto errout;
410     len = *p++;
411     *realm = malloc((size_t)len + 1);
412     if (*realm == NULL) {
413         retval = ENOMEM;
414         goto errout;
415     }
416     strncpy(*realm, (const char *)p, (size_t)len);
417     (*realm)[len] = '\0';
418     /* Avoid a common error. */
419     if ( (*realm)[len-1] == '.' )
420         (*realm)[len-1] = '\0';
421     retval = 0;
422     TRACE_TXT_LOOKUP_SUCCESS(context, host, *realm);
423
424 errout:
425     if (ds != NULL) {
426         krb5int_dns_fini(ds);
427         ds = NULL;
428     }
429     return retval;
430 }
431
432 #endif /* KRB5_DNS_LOOKUP */