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