xref: /freebsd/lib/libc/net/hesiod.c (revision 5723e501abb675701352eb3e9fd58bb3cc2c649d)
1 /*	$NetBSD: hesiod.c,v 1.9 1999/02/11 06:16:38 simonb Exp $	*/
2 
3 /* Copyright (c) 1996 by Internet Software Consortium.
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
10  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
11  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
12  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
14  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
15  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
16  * SOFTWARE.
17  */
18 
19 /* Copyright 1996 by the Massachusetts Institute of Technology.
20  *
21  * Permission to use, copy, modify, and distribute this
22  * software and its documentation for any purpose and without
23  * fee is hereby granted, provided that the above copyright
24  * notice appear in all copies and that both that copyright
25  * notice and this permission notice appear in supporting
26  * documentation, and that the name of M.I.T. not be used in
27  * advertising or publicity pertaining to distribution of the
28  * software without specific, written prior permission.
29  * M.I.T. makes no representations about the suitability of
30  * this software for any purpose.  It is provided "as is"
31  * without express or implied warranty.
32  */
33 
34 /* This file is part of the hesiod library.  It implements the core
35  * portion of the hesiod resolver.
36  *
37  * This file is loosely based on an interim version of hesiod.c from
38  * the BIND IRS library, which was in turn based on an earlier version
39  * of this file.  Extensive changes have been made on each step of the
40  * path.
41  *
42  * This implementation is not truly thread-safe at the moment because
43  * it uses res_send() and accesses _res.
44  */
45 
46 #include <sys/cdefs.h>
47 
48 #if 0
49 static char *orig_rcsid = "$NetBSD: hesiod.c,v 1.9 1999/02/11 06:16:38 simonb Exp $";
50 #endif
51 #include <sys/cdefs.h>
52 __FBSDID("$FreeBSD$");
53 
54 #include "namespace.h"
55 #include <sys/types.h>
56 #include <sys/param.h>
57 #include <netinet/in.h>
58 #include <arpa/nameser.h>
59 
60 #include <ctype.h>
61 #include <errno.h>
62 #include <hesiod.h>
63 #include <resolv.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <unistd.h>
68 #include "un-namespace.h"
69 
70 struct hesiod_p {
71 	char	*lhs;			/* normally ".ns" */
72 	char	*rhs;			/* AKA the default hesiod domain */
73 	int	 classes[2];		/* The class search order. */
74 };
75 
76 #define	MAX_HESRESP	1024
77 
78 static int	  read_config_file(struct hesiod_p *, const char *);
79 static char	**get_txt_records(int, const char *);
80 static int	  init_context(void);
81 static void	  translate_errors(void);
82 
83 
84 /*
85  * hesiod_init --
86  *	initialize a hesiod_p.
87  */
88 int
89 hesiod_init(context)
90 	void	**context;
91 {
92 	struct hesiod_p	*ctx;
93 	const char	*p, *configname;
94 
95 	ctx = malloc(sizeof(struct hesiod_p));
96 	if (ctx) {
97 		*context = ctx;
98 		if (!issetugid())
99 			configname = getenv("HESIOD_CONFIG");
100 		else
101 			configname = NULL;
102 		if (!configname)
103 			configname = _PATH_HESIOD_CONF;
104 		if (read_config_file(ctx, configname) >= 0) {
105 			/*
106 			 * The default rhs can be overridden by an
107 			 * environment variable.
108 			 */
109 			if (!issetugid())
110 				p = getenv("HES_DOMAIN");
111 			else
112 				p = NULL;
113 			if (p) {
114 				if (ctx->rhs)
115 					free(ctx->rhs);
116 				ctx->rhs = malloc(strlen(p) + 2);
117 				if (ctx->rhs) {
118 					*ctx->rhs = '.';
119 					strcpy(ctx->rhs + 1,
120 					    (*p == '.') ? p + 1 : p);
121 					return 0;
122 				} else
123 					errno = ENOMEM;
124 			} else
125 				return 0;
126 		}
127 	} else
128 		errno = ENOMEM;
129 
130 	if (ctx->lhs)
131 		free(ctx->lhs);
132 	if (ctx->rhs)
133 		free(ctx->rhs);
134 	if (ctx)
135 		free(ctx);
136 	return -1;
137 }
138 
139 /*
140  * hesiod_end --
141  *	Deallocates the hesiod_p.
142  */
143 void
144 hesiod_end(context)
145 	void	*context;
146 {
147 	struct hesiod_p *ctx = (struct hesiod_p *) context;
148 
149 	free(ctx->rhs);
150 	if (ctx->lhs)
151 		free(ctx->lhs);
152 	free(ctx);
153 }
154 
155 /*
156  * hesiod_to_bind --
157  * 	takes a hesiod (name, type) and returns a DNS
158  *	name which is to be resolved.
159  */
160 char *
161 hesiod_to_bind(void *context, const char *name, const char *type)
162 {
163 	struct hesiod_p *ctx = (struct hesiod_p *) context;
164 	char		 bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
165 	const char	*rhs;
166 	int		 len;
167 
168 	if (_strlcpy(bindname, name, sizeof(bindname)) >= sizeof(bindname)) {
169 		errno = EMSGSIZE;
170 		return NULL;
171 	}
172 
173 		/*
174 		 * Find the right right hand side to use, possibly
175 		 * truncating bindname.
176 		 */
177 	p = strchr(bindname, '@');
178 	if (p) {
179 		*p++ = 0;
180 		if (strchr(p, '.'))
181 			rhs = name + (p - bindname);
182 		else {
183 			rhs_list = hesiod_resolve(context, p, "rhs-extension");
184 			if (rhs_list)
185 				rhs = *rhs_list;
186 			else {
187 				errno = ENOENT;
188 				return NULL;
189 			}
190 		}
191 	} else
192 		rhs = ctx->rhs;
193 
194 		/* See if we have enough room. */
195 	len = strlen(bindname) + 1 + strlen(type);
196 	if (ctx->lhs)
197 		len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
198 	len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
199 	if (len > sizeof(bindname) - 1) {
200 		if (rhs_list)
201 			hesiod_free_list(context, rhs_list);
202 		errno = EMSGSIZE;
203 		return NULL;
204 	}
205 		/* Put together the rest of the domain. */
206 	strcat(bindname, ".");
207 	strcat(bindname, type);
208 		/* Only append lhs if it isn't empty. */
209 	if (ctx->lhs && ctx->lhs[0] != '\0' ) {
210 		if (ctx->lhs[0] != '.')
211 			strcat(bindname, ".");
212 		strcat(bindname, ctx->lhs);
213 	}
214 	if (rhs[0] != '.')
215 		strcat(bindname, ".");
216 	strcat(bindname, rhs);
217 
218 		/* rhs_list is no longer needed, since we're done with rhs. */
219 	if (rhs_list)
220 		hesiod_free_list(context, rhs_list);
221 
222 		/* Make a copy of the result and return it to the caller. */
223 	ret = strdup(bindname);
224 	if (!ret)
225 		errno = ENOMEM;
226 	return ret;
227 }
228 
229 /*
230  * hesiod_resolve --
231  *	Given a hesiod name and type, return an array of strings returned
232  *	by the resolver.
233  */
234 char **
235 hesiod_resolve(context, name, type)
236 	void		*context;
237 	const char	*name;
238 	const char	*type;
239 {
240 	struct hesiod_p	*ctx = (struct hesiod_p *) context;
241 	char		*bindname, **retvec;
242 
243 	bindname = hesiod_to_bind(context, name, type);
244 	if (!bindname)
245 		return NULL;
246 
247 	retvec = get_txt_records(ctx->classes[0], bindname);
248 	if (retvec == NULL && errno == ENOENT && ctx->classes[1])
249 		retvec = get_txt_records(ctx->classes[1], bindname);
250 
251 	free(bindname);
252 	return retvec;
253 }
254 
255 /*ARGSUSED*/
256 void
257 hesiod_free_list(context, list)
258 	void	 *context;
259 	char	**list;
260 {
261 	char  **p;
262 
263 	if (list == NULL)
264 		return;
265 	for (p = list; *p; p++)
266 		free(*p);
267 	free(list);
268 }
269 
270 
271 /* read_config_file --
272  *	Parse the /etc/hesiod.conf file.  Returns 0 on success,
273  *	-1 on failure.  On failure, it might leave values in ctx->lhs
274  *	or ctx->rhs which need to be freed by the caller.
275  */
276 static int
277 read_config_file(ctx, filename)
278 	struct hesiod_p	*ctx;
279 	const char	*filename;
280 {
281 	char	*key, *data, *p, **which;
282 	char	 buf[MAXDNAME + 7];
283 	int	 n;
284 	FILE	*fp;
285 
286 		/* Set default query classes. */
287 	ctx->classes[0] = C_IN;
288 	ctx->classes[1] = C_HS;
289 
290 		/* Try to open the configuration file. */
291 	fp = fopen(filename, "r");
292 	if (!fp) {
293 		/* Use compiled in default domain names. */
294 		ctx->lhs = strdup(DEF_LHS);
295 		ctx->rhs = strdup(DEF_RHS);
296 		if (ctx->lhs && ctx->rhs)
297 			return 0;
298 		else {
299 			errno = ENOMEM;
300 			return -1;
301 		}
302 	}
303 	ctx->lhs = NULL;
304 	ctx->rhs = NULL;
305 	while (fgets(buf, sizeof(buf), fp) != NULL) {
306 		p = buf;
307 		if (*p == '#' || *p == '\n' || *p == '\r')
308 			continue;
309 		while (*p == ' ' || *p == '\t')
310 			p++;
311 		key = p;
312 		while (*p != ' ' && *p != '\t' && *p != '=')
313 			p++;
314 		*p++ = 0;
315 
316 		while (isspace(*p) || *p == '=')
317 			p++;
318 		data = p;
319 		while (!isspace(*p))
320 			p++;
321 		*p = 0;
322 
323 		if (strcasecmp(key, "lhs") == 0 ||
324 		    strcasecmp(key, "rhs") == 0) {
325 			which = (strcasecmp(key, "lhs") == 0)
326 			    ? &ctx->lhs : &ctx->rhs;
327 			*which = strdup(data);
328 			if (!*which) {
329 				errno = ENOMEM;
330 				return -1;
331 			}
332 		} else {
333 			if (strcasecmp(key, "classes") == 0) {
334 				n = 0;
335 				while (*data && n < 2) {
336 					p = data;
337 					while (*p && *p != ',')
338 						p++;
339 					if (*p)
340 						*p++ = 0;
341 					if (strcasecmp(data, "IN") == 0)
342 						ctx->classes[n++] = C_IN;
343 					else
344 						if (strcasecmp(data, "HS") == 0)
345 							ctx->classes[n++] =
346 							    C_HS;
347 					data = p;
348 				}
349 				while (n < 2)
350 					ctx->classes[n++] = 0;
351 			}
352 		}
353 	}
354 	fclose(fp);
355 
356 	if (!ctx->rhs || ctx->classes[0] == 0 ||
357 	    ctx->classes[0] == ctx->classes[1]) {
358 		errno = ENOEXEC;
359 		return -1;
360 	}
361 	return 0;
362 }
363 
364 /*
365  * get_txt_records --
366  *	Given a DNS class and a DNS name, do a lookup for TXT records, and
367  *	return a list of them.
368  */
369 static char **
370 get_txt_records(qclass, name)
371 	int		 qclass;
372 	const char	*name;
373 {
374 	HEADER		*hp;
375 	unsigned char	 qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
376 	char		*dst, **list;
377 	int		 ancount, qdcount, i, j, n, skip, type, class, len;
378 
379 		/* Make sure the resolver is initialized. */
380 	if ((_res.options & RES_INIT) == 0 && res_init() == -1)
381 		return NULL;
382 
383 		/* Construct the query. */
384 	n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
385 	    NULL, qbuf, PACKETSZ);
386 	if (n < 0)
387 		return NULL;
388 
389 		/* Send the query. */
390 	n = res_send(qbuf, n, abuf, MAX_HESRESP);
391 	if (n < 0 || n > MAX_HESRESP) {
392 		errno = ECONNREFUSED; /* XXX */
393 		return NULL;
394 	}
395 		/* Parse the header of the result. */
396 	hp = (HEADER *) (void *) abuf;
397 	ancount = ntohs(hp->ancount);
398 	qdcount = ntohs(hp->qdcount);
399 	p = abuf + sizeof(HEADER);
400 	eom = abuf + n;
401 
402 		/*
403 		 * Skip questions, trying to get to the answer section
404 		 * which follows.
405 		 */
406 	for (i = 0; i < qdcount; i++) {
407 		skip = dn_skipname(p, eom);
408 		if (skip < 0 || p + skip + QFIXEDSZ > eom) {
409 			errno = EMSGSIZE;
410 			return NULL;
411 		}
412 		p += skip + QFIXEDSZ;
413 	}
414 
415 		/* Allocate space for the text record answers. */
416 	list = malloc((ancount + 1) * sizeof(char *));
417 	if (!list) {
418 		errno = ENOMEM;
419 		return NULL;
420 	}
421 		/* Parse the answers. */
422 	j = 0;
423 	for (i = 0; i < ancount; i++) {
424 		/* Parse the header of this answer. */
425 		skip = dn_skipname(p, eom);
426 		if (skip < 0 || p + skip + 10 > eom)
427 			break;
428 		type = p[skip + 0] << 8 | p[skip + 1];
429 		class = p[skip + 2] << 8 | p[skip + 3];
430 		len = p[skip + 8] << 8 | p[skip + 9];
431 		p += skip + 10;
432 		if (p + len > eom) {
433 			errno = EMSGSIZE;
434 			break;
435 		}
436 		/* Skip entries of the wrong class and type. */
437 		if (class != qclass || type != T_TXT) {
438 			p += len;
439 			continue;
440 		}
441 		/* Allocate space for this answer. */
442 		list[j] = malloc((size_t)len);
443 		if (!list[j]) {
444 			errno = ENOMEM;
445 			break;
446 		}
447 		dst = list[j++];
448 
449 		/* Copy answer data into the allocated area. */
450 		eor = p + len;
451 		while (p < eor) {
452 			n = (unsigned char) *p++;
453 			if (p + n > eor) {
454 				errno = EMSGSIZE;
455 				break;
456 			}
457 			memcpy(dst, p, (size_t)n);
458 			p += n;
459 			dst += n;
460 		}
461 		if (p < eor) {
462 			errno = EMSGSIZE;
463 			break;
464 		}
465 		*dst = 0;
466 	}
467 
468 		/*
469 		 * If we didn't terminate the loop normally, something
470 		 * went wrong.
471 		 */
472 	if (i < ancount) {
473 		for (i = 0; i < j; i++)
474 			free(list[i]);
475 		free(list);
476 		return NULL;
477 	}
478 	if (j == 0) {
479 		errno = ENOENT;
480 		free(list);
481 		return NULL;
482 	}
483 	list[j] = NULL;
484 	return list;
485 }
486 
487 		/*
488 		 *	COMPATIBILITY FUNCTIONS
489 		 */
490 
491 static int	  inited = 0;
492 static void	 *context;
493 static int	  errval = HES_ER_UNINIT;
494 
495 int
496 hes_init()
497 {
498 	init_context();
499 	return errval;
500 }
501 
502 char *
503 hes_to_bind(name, type)
504 	const char	*name;
505 	const char	*type;
506 {
507 	static	char	*bindname;
508 	if (init_context() < 0)
509 		return NULL;
510 	if (bindname)
511 		free(bindname);
512 	bindname = hesiod_to_bind(context, name, type);
513 	if (!bindname)
514 		translate_errors();
515 	return bindname;
516 }
517 
518 char **
519 hes_resolve(name, type)
520 	const char	*name;
521 	const char	*type;
522 {
523 	static char	**list;
524 
525 	if (init_context() < 0)
526 		return NULL;
527 
528 	/*
529 	 * In the old Hesiod interface, the caller was responsible for
530 	 * freeing the returned strings but not the vector of strings itself.
531 	 */
532 	if (list)
533 		free(list);
534 
535 	list = hesiod_resolve(context, name, type);
536 	if (!list)
537 		translate_errors();
538 	return list;
539 }
540 
541 int
542 hes_error()
543 {
544 	return errval;
545 }
546 
547 void
548 hes_free(hp)
549 	char **hp;
550 {
551 	hesiod_free_list(context, hp);
552 }
553 
554 static int
555 init_context()
556 {
557 	if (!inited) {
558 		inited = 1;
559 		if (hesiod_init(&context) < 0) {
560 			errval = HES_ER_CONFIG;
561 			return -1;
562 		}
563 		errval = HES_ER_OK;
564 	}
565 	return 0;
566 }
567 
568 static void
569 translate_errors()
570 {
571 	switch (errno) {
572 	case ENOENT:
573 		errval = HES_ER_NOTFOUND;
574 		break;
575 	case ECONNREFUSED:
576 	case EMSGSIZE:
577 		errval = HES_ER_NET;
578 		break;
579 	case ENOMEM:
580 	default:
581 		/* Not a good match, but the best we can do. */
582 		errval = HES_ER_CONFIG;
583 		break;
584 	}
585 }
586