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
49static 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
68struct 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
76static int	  read_config_file(struct hesiod_p *, const char *);
77static char	**get_txt_records(int, const char *);
78static int	  init_context(void);
79static void	  translate_errors(void);
80
81
82/*
83 * hesiod_init --
84 *	initialize a hesiod_p.
85 */
86int
87hesiod_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 */
141void
142hesiod_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 */
158char *
159hesiod_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 */
232char **
233hesiod_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*/
254void
255hesiod_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 */
274static int
275read_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 */
368static char **
369get_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
490static int	  inited = 0;
491static void	 *context;
492static int	  errval = HES_ER_UNINIT;
493
494int
495hes_init()
496{
497	init_context();
498	return errval;
499}
500
501char *
502hes_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
517char **
518hes_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
540int
541hes_error()
542{
543	return errval;
544}
545
546void
547hes_free(hp)
548	char **hp;
549{
550	hesiod_free_list(context, hp);
551}
552
553static int
554init_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
567static void
568translate_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