xref: /netbsd-src/external/bsd/libbind/dist/irs/hesiod.c (revision 5bbd2a12505d72a8177929a37b5cee489d0a1cfd)
1 /*	$NetBSD: hesiod.c,v 1.1.1.2 2012/09/09 16:07:51 christos Exp $	*/
2 
3 #if defined(LIBC_SCCS) && !defined(lint)
4 static const char rcsid[] = "Id: hesiod.c,v 1.7 2005/07/28 06:51:48 marka Exp ";
5 #endif
6 
7 /*
8  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9  * Copyright (c) 1996,1999 by Internet Software Consortium.
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 
24 
25 /*! \file
26  * \brief
27  * hesiod.c --- the core portion of the hesiod resolver.
28  *
29  * This file is derived from the hesiod library from Project Athena;
30  * It has been extensively rewritten by Theodore Ts'o to have a more
31  * thread-safe interface.
32  * \author
33  * This file is primarily maintained by <tytso@mit.edu> and <ghudson@mit.edu>.
34  */
35 
36 /* Imports */
37 
38 #include "port_before.h"
39 
40 #include <sys/types.h>
41 #include <netinet/in.h>
42 #include <arpa/nameser.h>
43 
44 #include <errno.h>
45 #include <netdb.h>
46 #include <resolv.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 
51 #include "port_after.h"
52 
53 #include "pathnames.h"
54 #include "hesiod.h"
55 #include "hesiod_p.h"
56 
57 /* Forward */
58 
59 int		hesiod_init(void **context);
60 void		hesiod_end(void *context);
61 char *		hesiod_to_bind(void *context, const char *name,
62 			       const char *type);
63 char **		hesiod_resolve(void *context, const char *name,
64 			       const char *type);
65 void		hesiod_free_list(void *context, char **list);
66 
67 static int	parse_config_file(struct hesiod_p *ctx, const char *filename);
68 static char **	get_txt_records(struct hesiod_p *ctx, int class,
69 				const char *name);
70 static int	init(struct hesiod_p *ctx);
71 
72 /* Public */
73 
74 /*%
75  * This function is called to initialize a hesiod_p.
76  */
77 int
hesiod_init(void ** context)78 hesiod_init(void **context) {
79 	struct hesiod_p *ctx;
80 	char *cp;
81 
82 	ctx = malloc(sizeof(struct hesiod_p));
83 	if (ctx == 0) {
84 		errno = ENOMEM;
85 		return (-1);
86 	}
87 
88 	memset(ctx, 0, sizeof (*ctx));
89 
90 	if (parse_config_file(ctx, _PATH_HESIOD_CONF) < 0) {
91 #ifdef DEF_RHS
92 		/*
93 		 * Use compiled in defaults.
94 		 */
95 		ctx->LHS = malloc(strlen(DEF_LHS) + 1);
96 		ctx->RHS = malloc(strlen(DEF_RHS) + 1);
97 		if (ctx->LHS == NULL || ctx->RHS == NULL) {
98 			errno = ENOMEM;
99 			goto cleanup;
100 		}
101 		strcpy(ctx->LHS, DEF_LHS);	/* (checked) */
102 		strcpy(ctx->RHS, DEF_RHS);	/* (checked) */
103 #else
104 		goto cleanup;
105 #endif
106 	}
107 	/*
108 	 * The default RHS can be overridden by an environment
109 	 * variable.
110 	 */
111 	if ((cp = getenv("HES_DOMAIN")) != NULL) {
112 		size_t RHSlen = strlen(cp) + 2;
113 		if (ctx->RHS)
114 			free(ctx->RHS);
115 		ctx->RHS = malloc(RHSlen);
116 		if (!ctx->RHS) {
117 			errno = ENOMEM;
118 			goto cleanup;
119 		}
120 		if (cp[0] == '.') {
121 			strcpy(ctx->RHS, cp);	/* (checked) */
122 		} else {
123 			strcpy(ctx->RHS, ".");	/* (checked) */
124 			strcat(ctx->RHS, cp);	/* (checked) */
125 		}
126 	}
127 
128 	/*
129 	 * If there is no default hesiod realm set, we return an
130 	 * error.
131 	 */
132 	if (!ctx->RHS) {
133 		errno = ENOEXEC;
134 		goto cleanup;
135 	}
136 
137 #if 0
138 	if (res_ninit(ctx->res) < 0)
139 		goto cleanup;
140 #endif
141 
142 	*context = ctx;
143 	return (0);
144 
145  cleanup:
146 	hesiod_end(ctx);
147 	return (-1);
148 }
149 
150 /*%
151  * This function deallocates the hesiod_p
152  */
153 void
hesiod_end(void * context)154 hesiod_end(void *context) {
155 	struct hesiod_p *ctx = (struct hesiod_p *) context;
156 	int save_errno = errno;
157 
158 	if (ctx->res)
159 		res_nclose(ctx->res);
160 	if (ctx->RHS)
161 		free(ctx->RHS);
162 	if (ctx->LHS)
163 		free(ctx->LHS);
164 	if (ctx->res && ctx->free_res)
165 		(*ctx->free_res)(ctx->res);
166 	free(ctx);
167 	errno = save_errno;
168 }
169 
170 /*%
171  * This function takes a hesiod (name, type) and returns a DNS
172  * name which is to be resolved.
173  */
174 char *
hesiod_to_bind(void * context,const char * name,const char * type)175 hesiod_to_bind(void *context, const char *name, const char *type) {
176 	struct hesiod_p *ctx = (struct hesiod_p *) context;
177 	char *bindname;
178 	char **rhs_list = NULL;
179 	const char *RHS, *cp;
180 
181 	/* Decide what our RHS is, and set cp to the end of the actual name. */
182 	if ((cp = strchr(name, '@')) != NULL) {
183 		if (strchr(cp + 1, '.'))
184 			RHS = cp + 1;
185 		else if ((rhs_list = hesiod_resolve(context, cp + 1,
186 		    "rhs-extension")) != NULL)
187 			RHS = *rhs_list;
188 		else {
189 			errno = ENOENT;
190 			return (NULL);
191 		}
192 	} else {
193 		RHS = ctx->RHS;
194 		cp = name + strlen(name);
195 	}
196 
197 	/*
198 	 * Allocate the space we need, including up to three periods and
199 	 * the terminating NUL.
200 	 */
201 	if ((bindname = malloc((cp - name) + strlen(type) + strlen(RHS) +
202 	    (ctx->LHS ? strlen(ctx->LHS) : 0) + 4)) == NULL) {
203 		errno = ENOMEM;
204 		if (rhs_list)
205 			hesiod_free_list(context, rhs_list);
206 		return NULL;
207 	}
208 
209 	/* Now put together the DNS name. */
210 	memcpy(bindname, name, cp - name);
211 	bindname[cp - name] = '\0';
212 	strcat(bindname, ".");
213 	strcat(bindname, type);
214 	if (ctx->LHS) {
215 		if (ctx->LHS[0] != '.')
216 			strcat(bindname, ".");
217 		strcat(bindname, ctx->LHS);
218 	}
219 	if (RHS[0] != '.')
220 		strcat(bindname, ".");
221 	strcat(bindname, RHS);
222 
223 	if (rhs_list)
224 		hesiod_free_list(context, rhs_list);
225 
226 	return (bindname);
227 }
228 
229 /*%
230  * This is the core function.  Given a hesiod (name, type), it
231  * returns an array of strings returned by the resolver.
232  */
233 char **
hesiod_resolve(void * context,const char * name,const char * type)234 hesiod_resolve(void *context, const char *name, const char *type) {
235 	struct hesiod_p *ctx = (struct hesiod_p *) context;
236 	char *bindname = hesiod_to_bind(context, name, type);
237 	char **retvec;
238 
239 	if (bindname == NULL)
240 		return (NULL);
241 	if (init(ctx) == -1) {
242 		free(bindname);
243 		return (NULL);
244 	}
245 
246 	if ((retvec = get_txt_records(ctx, C_IN, bindname))) {
247 		free(bindname);
248 		return (retvec);
249 	}
250 
251 	if (errno != ENOENT)
252 		return (NULL);
253 
254 	retvec = get_txt_records(ctx, C_HS, bindname);
255 	free(bindname);
256 	return (retvec);
257 }
258 
259 void
hesiod_free_list(void * context,char ** list)260 hesiod_free_list(void *context, char **list) {
261 	char **p;
262 
263 	UNUSED(context);
264 
265 	for (p = list; *p; p++)
266 		free(*p);
267 	free(list);
268 }
269 
270 /*%
271  * This function parses the /etc/hesiod.conf file
272  */
273 static int
parse_config_file(struct hesiod_p * ctx,const char * filename)274 parse_config_file(struct hesiod_p *ctx, const char *filename) {
275 	char *key, *data, *cp, **cpp;
276 	char buf[MAXDNAME+7];
277 	FILE *fp;
278 
279 	/*
280 	 * Clear the existing configuration variable, just in case
281 	 * they're set.
282 	 */
283 	if (ctx->RHS)
284 		free(ctx->RHS);
285 	if (ctx->LHS)
286 		free(ctx->LHS);
287 	ctx->RHS = ctx->LHS = 0;
288 
289 	/*
290 	 * Now open and parse the file...
291 	 */
292 	if (!(fp = fopen(filename, "r")))
293 		return (-1);
294 
295 	while (fgets(buf, sizeof(buf), fp) != NULL) {
296 		cp = buf;
297 		if (*cp == '#' || *cp == '\n' || *cp == '\r')
298 			continue;
299 		while(*cp == ' ' || *cp == '\t')
300 			cp++;
301 		key = cp;
302 		while(*cp != ' ' && *cp != '\t' && *cp != '=')
303 			cp++;
304 		*cp++ = '\0';
305 
306 		while(*cp == ' ' || *cp == '\t' || *cp == '=')
307 			cp++;
308 		data = cp;
309 		while(*cp != ' ' && *cp != '\n' && *cp != '\r')
310 			cp++;
311 		*cp++ = '\0';
312 
313 		if (strcmp(key, "lhs") == 0)
314 			cpp = &ctx->LHS;
315 		else if (strcmp(key, "rhs") == 0)
316 			cpp = &ctx->RHS;
317 		else
318 			continue;
319 
320 		*cpp = malloc(strlen(data) + 1);
321 		if (!*cpp) {
322 			errno = ENOMEM;
323 			goto cleanup;
324 		}
325 		strcpy(*cpp, data);
326 	}
327 	fclose(fp);
328 	return (0);
329 
330  cleanup:
331 	fclose(fp);
332 	if (ctx->RHS)
333 		free(ctx->RHS);
334 	if (ctx->LHS)
335 		free(ctx->LHS);
336 	ctx->RHS = ctx->LHS = 0;
337 	return (-1);
338 }
339 
340 /*%
341  * Given a DNS class and a DNS name, do a lookup for TXT records, and
342  * return a list of them.
343  */
344 static char **
get_txt_records(struct hesiod_p * ctx,int class,const char * name)345 get_txt_records(struct hesiod_p *ctx, int class, const char *name) {
346 	struct {
347 		int type;		/*%< RR type */
348 		int class;		/*%< RR class */
349 		int dlen;		/*%< len of data section */
350 		u_char *data;		/*%< pointer to data */
351 	} rr;
352 	HEADER *hp;
353 	u_char qbuf[MAX_HESRESP], abuf[MAX_HESRESP];
354 	u_char *cp, *erdata, *eom;
355 	char *dst, *edst, **list;
356 	int ancount, qdcount;
357 	int i, j, n, skip;
358 
359 	/*
360 	 * Construct the query and send it.
361 	 */
362 	n = res_nmkquery(ctx->res, QUERY, name, class, T_TXT, NULL, 0,
363 			 NULL, qbuf, MAX_HESRESP);
364 	if (n < 0) {
365 		errno = EMSGSIZE;
366 		return (NULL);
367 	}
368 	n = res_nsend(ctx->res, qbuf, n, abuf, MAX_HESRESP);
369 	if (n < 0) {
370 		errno = ECONNREFUSED;
371 		return (NULL);
372 	}
373 	if (n < HFIXEDSZ) {
374 		errno = EMSGSIZE;
375 		return (NULL);
376 	}
377 
378 	/*
379 	 * OK, parse the result.
380 	 */
381 	hp = (HEADER *) abuf;
382 	ancount = ntohs(hp->ancount);
383 	qdcount = ntohs(hp->qdcount);
384 	cp = abuf + sizeof(HEADER);
385 	eom = abuf + n;
386 
387 	/* Skip query, trying to get to the answer section which follows. */
388 	for (i = 0; i < qdcount; i++) {
389 		skip = dn_skipname(cp, eom);
390 		if (skip < 0 || cp + skip + QFIXEDSZ > eom) {
391 			errno = EMSGSIZE;
392 			return (NULL);
393 		}
394 		cp += skip + QFIXEDSZ;
395 	}
396 
397 	list = malloc((ancount + 1) * sizeof(char *));
398 	if (!list) {
399 		errno = ENOMEM;
400 		return (NULL);
401 	}
402 	j = 0;
403 	for (i = 0; i < ancount; i++) {
404 		skip = dn_skipname(cp, eom);
405 		if (skip < 0) {
406 			errno = EMSGSIZE;
407 			goto cleanup;
408 		}
409 		cp += skip;
410 		if (cp + 3 * INT16SZ + INT32SZ > eom) {
411 			errno = EMSGSIZE;
412 			goto cleanup;
413 		}
414 		rr.type = ns_get16(cp);
415 		cp += INT16SZ;
416 		rr.class = ns_get16(cp);
417 		cp += INT16SZ + INT32SZ;	/*%< skip the ttl, too */
418 		rr.dlen = ns_get16(cp);
419 		cp += INT16SZ;
420 		if (cp + rr.dlen > eom) {
421 			errno = EMSGSIZE;
422 			goto cleanup;
423 		}
424 		rr.data = cp;
425 		cp += rr.dlen;
426 		if (rr.class != class || rr.type != T_TXT)
427 			continue;
428 		if (!(list[j] = malloc(rr.dlen)))
429 			goto cleanup;
430 		dst = list[j++];
431 		edst = dst + rr.dlen;
432 		erdata = rr.data + rr.dlen;
433 		cp = rr.data;
434 		while (cp < erdata) {
435 			n = (unsigned char) *cp++;
436 			if (cp + n > eom || dst + n > edst) {
437 				errno = EMSGSIZE;
438 				goto cleanup;
439 			}
440 			memcpy(dst, cp, n);
441 			cp += n;
442 			dst += n;
443 		}
444 		if (cp != erdata) {
445 			errno = EMSGSIZE;
446 			goto cleanup;
447 		}
448 		*dst = '\0';
449 	}
450 	list[j] = NULL;
451 	if (j == 0) {
452 		errno = ENOENT;
453 		goto cleanup;
454 	}
455 	return (list);
456 
457  cleanup:
458 	for (i = 0; i < j; i++)
459 		free(list[i]);
460 	free(list);
461 	return (NULL);
462 }
463 
464 struct __res_state *
__hesiod_res_get(void * context)465 __hesiod_res_get(void *context) {
466 	struct hesiod_p *ctx = context;
467 
468 	if (!ctx->res) {
469 		struct __res_state *res;
470 		res = (struct __res_state *)malloc(sizeof *res);
471 		if (res == NULL) {
472 			errno = ENOMEM;
473 			return (NULL);
474 		}
475 		memset(res, 0, sizeof *res);
476 		__hesiod_res_set(ctx, res, free);
477 	}
478 
479 	return (ctx->res);
480 }
481 
482 void
__hesiod_res_set(void * context,struct __res_state * res,void (* free_res)(void *))483 __hesiod_res_set(void *context, struct __res_state *res,
484 	         void (*free_res)(void *)) {
485 	struct hesiod_p *ctx = context;
486 
487 	if (ctx->res && ctx->free_res) {
488 		res_nclose(ctx->res);
489 		(*ctx->free_res)(ctx->res);
490 	}
491 
492 	ctx->res = res;
493 	ctx->free_res = free_res;
494 }
495 
496 static int
init(struct hesiod_p * ctx)497 init(struct hesiod_p *ctx) {
498 
499 	if (!ctx->res && !__hesiod_res_get(ctx))
500 		return (-1);
501 
502 	if (((ctx->res->options & RES_INIT) == 0U) &&
503 	    (res_ninit(ctx->res) == -1))
504 		return (-1);
505 
506 	return (0);
507 }
508