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