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