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