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  */
52 struct 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
68 static 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  */
78 int
krb5int_dns_init(struct krb5int_dns_state ** dsp,char * host,int nclass,int ntype)79 krb5int_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 
157 errout:
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  */
178 int
krb5int_dns_nextans(struct krb5int_dns_state * ds,const unsigned char ** pp,int * lenp)179 krb5int_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  */
krb5int_dns_expand(struct krb5int_dns_state * ds,const unsigned char * p,char * buf,int len)206 int 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  */
225 void
krb5int_dns_fini(struct krb5int_dns_state * ds)226 krb5int_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  */
247 static int
initparse(struct krb5int_dns_state * ds)248 initparse(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  */
291 int
krb5int_dns_nextans(struct krb5int_dns_state * ds,const unsigned char ** pp,int * lenp)292 krb5int_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;
337 out:
338     return -1;
339 }
340 
341 #endif
342 
343 #endif /* KRB5_DNS_LOOKUP */
344