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