1/*
2 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6/*
7 * lib/krb5/os/dnsglue.c
8 *
9 * Copyright 2004 by the Massachusetts Institute of Technology.
10 * All Rights Reserved.
11 *
12 * Export of this software from the United States of America may
13 *   require a specific license from the United States Government.
14 *   It is the responsibility of any person or organization contemplating
15 *   export to obtain such a license before exporting.
16 *
17 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
18 * distribute this software and its documentation for any purpose and
19 * without fee is hereby granted, provided that the above copyright
20 * notice appear in all copies and that both that copyright notice and
21 * this permission notice appear in supporting documentation, and that
22 * the name of M.I.T. not be used in advertising or publicity pertaining
23 * to distribution of the software without specific, written prior
24 * permission.  Furthermore if you modify this software you must label
25 * your software as modified software and not distribute it in such a
26 * fashion that it might be confused with the original M.I.T. software.
27 * M.I.T. makes no representations about the suitability of
28 * this software for any purpose.  It is provided "as is" without express
29 * or implied warranty.
30 *
31 */
32#include "autoconf.h"
33#ifdef KRB5_DNS_LOOKUP
34
35#include "dnsglue.h"
36
37/*
38 * Only use res_ninit() if there's also a res_ndestroy(), to avoid
39 * memory leaks (Linux & Solaris) and outright corruption (AIX 4.x,
40 * 5.x).  While we're at it, make sure res_nsearch() is there too.
41 *
42 * In any case, it is probable that platforms having broken
43 * res_ninit() will have thread safety hacks for res_init() and _res.
44 */
45#if HAVE_RES_NINIT && HAVE_RES_NDESTROY && HAVE_RES_NSEARCH
46#define USE_RES_NINIT 1
47#endif
48
49/*
50 * Opaque handle
51 */
52struct krb5int_dns_state {
53    int nclass;
54    int ntype;
55    void *ansp;
56    int anslen;
57    int ansmax;
58#if HAVE_NS_INITPARSE
59    int cur_ans;
60    ns_msg msg;
61#else
62    unsigned char *ptr;
63    unsigned short nanswers;
64#endif
65};
66
67#if !HAVE_NS_INITPARSE
68static int initparse(struct krb5int_dns_state *);
69#endif
70
71/*
72 * krb5int_dns_init()
73 *
74 * Initialize an opaque handle.  Do name lookup and initial parsing of
75 * reply, skipping question section.  Prepare to iterate over answer
76 * section.  Returns -1 on error, 0 on success.
77 */
78int
79krb5int_dns_init(struct krb5int_dns_state **dsp,
80		 char *host, int nclass, int ntype)
81{
82#if USE_RES_NINIT
83    struct __res_state statbuf;
84#endif
85    struct krb5int_dns_state *ds;
86    int len, ret;
87    size_t nextincr, maxincr;
88    unsigned char *p;
89
90    *dsp = ds = malloc(sizeof(*ds));
91    if (ds == NULL)
92	return -1;
93
94    ret = -1;
95    ds->nclass = nclass;
96    ds->ntype = ntype;
97    ds->ansp = NULL;
98    ds->anslen = 0;
99    ds->ansmax = 0;
100    nextincr = 2048;
101    maxincr = INT_MAX;
102
103#if HAVE_NS_INITPARSE
104    ds->cur_ans = 0;
105#endif
106
107#if USE_RES_NINIT
108    memset(&statbuf, 0, sizeof(statbuf));
109    ret = res_ninit(&statbuf);
110#else
111    ret = res_init();
112#endif
113    if (ret < 0)
114	return -1;
115
116    do {
117	p = (ds->ansp == NULL)
118	    ? malloc(nextincr) : realloc(ds->ansp, nextincr);
119
120	if (p == NULL && ds->ansp != NULL) {
121	    ret = -1;
122	    goto errout;
123	}
124	ds->ansp = p;
125	ds->ansmax = nextincr;
126
127#if USE_RES_NINIT
128	len = res_nsearch(&statbuf, host, ds->nclass, ds->ntype,
129			  ds->ansp, ds->ansmax);
130#else
131	len = res_search(host, ds->nclass, ds->ntype,
132			 ds->ansp, ds->ansmax);
133#endif
134	if (len > maxincr) {
135	    ret = -1;
136	    goto errout;
137	}
138	while (nextincr < len)
139	    nextincr *= 2;
140	if (len < 0 || nextincr > maxincr) {
141	    ret = -1;
142	    goto errout;
143	}
144    } while (len > ds->ansmax);
145
146    ds->anslen = len;
147#if HAVE_NS_INITPARSE
148    ret = ns_initparse(ds->ansp, ds->anslen, &ds->msg);
149#else
150    ret = initparse(ds);
151#endif
152    if (ret < 0)
153	goto errout;
154
155    ret = 0;
156
157errout:
158#if USE_RES_NINIT
159    res_ndestroy(&statbuf);
160#endif
161    if (ret < 0) {
162	if (ds->ansp != NULL) {
163	    free(ds->ansp);
164	    ds->ansp = NULL;
165	}
166    }
167
168    return ret;
169}
170
171#if HAVE_NS_INITPARSE
172/*
173 * krb5int_dns_nextans - get next matching answer record
174 *
175 * Sets pp to NULL if no more records.  Returns -1 on error, 0 on
176 * success.
177 */
178int
179krb5int_dns_nextans(struct krb5int_dns_state *ds,
180		    const unsigned char **pp, int *lenp)
181{
182    int len;
183    ns_rr rr;
184
185    *pp = NULL;
186    *lenp = 0;
187    while (ds->cur_ans < ns_msg_count(ds->msg, ns_s_an)) {
188	len = ns_parserr(&ds->msg, ns_s_an, ds->cur_ans, &rr);
189	if (len < 0)
190	    return -1;
191	ds->cur_ans++;
192	if (ds->nclass == ns_rr_class(rr)
193	    && ds->ntype == ns_rr_type(rr)) {
194	    *pp = ns_rr_rdata(rr);
195	    *lenp = ns_rr_rdlen(rr);
196	    return 0;
197	}
198    }
199    return 0;
200}
201#endif
202
203/*
204 * krb5int_dns_expand - wrapper for dn_expand()
205 */
206int krb5int_dns_expand(struct krb5int_dns_state *ds,
207		       const unsigned char *p,
208		       char *buf, int len)
209{
210
211#if HAVE_NS_NAME_UNCOMPRESS
212    return ns_name_uncompress(ds->ansp,
213			      (unsigned char *)ds->ansp + ds->anslen,
214			      p, buf, (size_t)len);
215#else
216    return dn_expand(ds->ansp,
217		     (unsigned char *)ds->ansp + ds->anslen,
218		     p, buf, len);
219#endif
220}
221
222/*
223 * Free stuff.
224 */
225void
226krb5int_dns_fini(struct krb5int_dns_state *ds)
227{
228    if (ds == NULL)
229	return;
230    if (ds->ansp != NULL)
231	free(ds->ansp);
232    free(ds);
233}
234
235/*
236 * Compat routines for BIND 4
237 */
238#if !HAVE_NS_INITPARSE
239
240/*
241 * initparse
242 *
243 * Skip header and question section of reply.  Set a pointer to the
244 * beginning of the answer section, and prepare to iterate over
245 * answer records.
246 */
247static int
248initparse(struct krb5int_dns_state *ds)
249{
250    HEADER *hdr;
251    unsigned char *p;
252    unsigned short nqueries, nanswers;
253    int len;
254#if !HAVE_DN_SKIPNAME
255    char host[MAXDNAME];
256#endif
257
258    if (ds->anslen < sizeof(HEADER))
259	return -1;
260
261    hdr = (HEADER *)ds->ansp;
262    p = ds->ansp;
263    nqueries = ntohs((unsigned short)hdr->qdcount);
264    nanswers = ntohs((unsigned short)hdr->ancount);
265    p += sizeof(HEADER);
266
267    /*
268     * Skip query records.
269     */
270    while (nqueries--) {
271#if HAVE_DN_SKIPNAME
272	len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen);
273#else
274	len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen,
275			p, host, sizeof(host));
276#endif
277	if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len + 4))
278	    return -1;
279	p += len + 4;
280    }
281    ds->ptr = p;
282    ds->nanswers = nanswers;
283    return 0;
284}
285
286/*
287 * krb5int_dns_nextans() - get next answer record
288 *
289 * Sets pp to NULL if no more records.
290 */
291int
292krb5int_dns_nextans(struct krb5int_dns_state *ds,
293		    const unsigned char **pp, int *lenp)
294{
295    int len;
296    unsigned char *p;
297    unsigned short ntype, nclass, rdlen;
298#if !HAVE_DN_SKIPNAME
299    char host[MAXDNAME];
300#endif
301
302    *pp = NULL;
303    *lenp = 0;
304    p = ds->ptr;
305
306    while (ds->nanswers--) {
307#if HAVE_DN_SKIPNAME
308	len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen);
309#else
310	len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen,
311			p, host, sizeof(host));
312#endif
313	if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len))
314	    return -1;
315	p += len;
316	SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, ntype, out);
317	/* Also skip 4 bytes of TTL */
318	SAFE_GETUINT16(ds->ansp, ds->anslen, p, 6, nclass, out);
319	SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, rdlen, out);
320
321	if (!INCR_OK(ds->ansp, ds->anslen, p, rdlen))
322	    return -1;
323/* Solaris Kerberos - resync */
324#if 0
325	if (rdlen > INT_MAX)
326	    return -1;
327#endif
328	if (nclass == ds->nclass && ntype == ds->ntype) {
329	    *pp = p;
330	    *lenp = rdlen;
331	    ds->ptr = p + rdlen;
332	    return 0;
333	}
334	p += rdlen;
335    }
336    return 0;
337out:
338    return -1;
339}
340
341#endif
342
343#endif /* KRB5_DNS_LOOKUP */
344