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/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
67struct 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
75static int	  read_config_file(struct hesiod_p *, const char *);
76static char	**get_txt_records(int, const char *);
77static int	  init_context(void);
78static void	  translate_errors(void);
79
80
81/*
82 * hesiod_init --
83 *	initialize a hesiod_p.
84 */
85int
86hesiod_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 */
140void
141hesiod_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 */
157char *
158hesiod_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 */
231char **
232hesiod_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*/
253void
254hesiod_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 */
273static int
274read_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, "re");
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				fclose(fp);
327				errno = ENOMEM;
328				return -1;
329			}
330		} else {
331			if (strcasecmp(key, "classes") == 0) {
332				n = 0;
333				while (*data && n < 2) {
334					p = data;
335					while (*p && *p != ',')
336						p++;
337					if (*p)
338						*p++ = 0;
339					if (strcasecmp(data, "IN") == 0)
340						ctx->classes[n++] = C_IN;
341					else
342						if (strcasecmp(data, "HS") == 0)
343							ctx->classes[n++] =
344							    C_HS;
345					data = p;
346				}
347				while (n < 2)
348					ctx->classes[n++] = 0;
349			}
350		}
351	}
352	fclose(fp);
353
354	if (!ctx->rhs || ctx->classes[0] == 0 ||
355	    ctx->classes[0] == ctx->classes[1]) {
356		errno = ENOEXEC;
357		return -1;
358	}
359	return 0;
360}
361
362/*
363 * get_txt_records --
364 *	Given a DNS class and a DNS name, do a lookup for TXT records, and
365 *	return a list of them.
366 */
367static char **
368get_txt_records(qclass, name)
369	int		 qclass;
370	const char	*name;
371{
372	HEADER		*hp;
373	unsigned char	 qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
374	char		*dst, **list;
375	int		 ancount, qdcount, i, j, n, skip, type, class, len;
376
377		/* Make sure the resolver is initialized. */
378	if ((_res.options & RES_INIT) == 0 && res_init() == -1)
379		return NULL;
380
381		/* Construct the query. */
382	n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
383	    NULL, qbuf, PACKETSZ);
384	if (n < 0)
385		return NULL;
386
387		/* Send the query. */
388	n = res_send(qbuf, n, abuf, MAX_HESRESP);
389	if (n < 0 || n > MAX_HESRESP) {
390		errno = ECONNREFUSED; /* XXX */
391		return NULL;
392	}
393		/* Parse the header of the result. */
394	hp = (HEADER *) (void *) abuf;
395	ancount = ntohs(hp->ancount);
396	qdcount = ntohs(hp->qdcount);
397	p = abuf + sizeof(HEADER);
398	eom = abuf + n;
399
400		/*
401		 * Skip questions, trying to get to the answer section
402		 * which follows.
403		 */
404	for (i = 0; i < qdcount; i++) {
405		skip = dn_skipname(p, eom);
406		if (skip < 0 || p + skip + QFIXEDSZ > eom) {
407			errno = EMSGSIZE;
408			return NULL;
409		}
410		p += skip + QFIXEDSZ;
411	}
412
413		/* Allocate space for the text record answers. */
414	list = malloc((ancount + 1) * sizeof(char *));
415	if (!list) {
416		errno = ENOMEM;
417		return NULL;
418	}
419		/* Parse the answers. */
420	j = 0;
421	for (i = 0; i < ancount; i++) {
422		/* Parse the header of this answer. */
423		skip = dn_skipname(p, eom);
424		if (skip < 0 || p + skip + 10 > eom)
425			break;
426		type = p[skip + 0] << 8 | p[skip + 1];
427		class = p[skip + 2] << 8 | p[skip + 3];
428		len = p[skip + 8] << 8 | p[skip + 9];
429		p += skip + 10;
430		if (p + len > eom) {
431			errno = EMSGSIZE;
432			break;
433		}
434		/* Skip entries of the wrong class and type. */
435		if (class != qclass || type != T_TXT) {
436			p += len;
437			continue;
438		}
439		/* Allocate space for this answer. */
440		list[j] = malloc((size_t)len);
441		if (!list[j]) {
442			errno = ENOMEM;
443			break;
444		}
445		dst = list[j++];
446
447		/* Copy answer data into the allocated area. */
448		eor = p + len;
449		while (p < eor) {
450			n = (unsigned char) *p++;
451			if (p + n > eor) {
452				errno = EMSGSIZE;
453				break;
454			}
455			memcpy(dst, p, (size_t)n);
456			p += n;
457			dst += n;
458		}
459		if (p < eor) {
460			errno = EMSGSIZE;
461			break;
462		}
463		*dst = 0;
464	}
465
466		/*
467		 * If we didn't terminate the loop normally, something
468		 * went wrong.
469		 */
470	if (i < ancount) {
471		for (i = 0; i < j; i++)
472			free(list[i]);
473		free(list);
474		return NULL;
475	}
476	if (j == 0) {
477		errno = ENOENT;
478		free(list);
479		return NULL;
480	}
481	list[j] = NULL;
482	return list;
483}
484
485		/*
486		 *	COMPATIBILITY FUNCTIONS
487		 */
488
489static int	  inited = 0;
490static void	 *context;
491static int	  errval = HES_ER_UNINIT;
492
493int
494hes_init()
495{
496	init_context();
497	return errval;
498}
499
500char *
501hes_to_bind(name, type)
502	const char	*name;
503	const char	*type;
504{
505	static	char	*bindname;
506	if (init_context() < 0)
507		return NULL;
508	if (bindname)
509		free(bindname);
510	bindname = hesiod_to_bind(context, name, type);
511	if (!bindname)
512		translate_errors();
513	return bindname;
514}
515
516char **
517hes_resolve(name, type)
518	const char	*name;
519	const char	*type;
520{
521	static char	**list;
522
523	if (init_context() < 0)
524		return NULL;
525
526	/*
527	 * In the old Hesiod interface, the caller was responsible for
528	 * freeing the returned strings but not the vector of strings itself.
529	 */
530	if (list)
531		free(list);
532
533	list = hesiod_resolve(context, name, type);
534	if (!list)
535		translate_errors();
536	return list;
537}
538
539int
540hes_error()
541{
542	return errval;
543}
544
545void
546hes_free(hp)
547	char **hp;
548{
549	hesiod_free_list(context, hp);
550}
551
552static int
553init_context()
554{
555	if (!inited) {
556		inited = 1;
557		if (hesiod_init(&context) < 0) {
558			errval = HES_ER_CONFIG;
559			return -1;
560		}
561		errval = HES_ER_OK;
562	}
563	return 0;
564}
565
566static void
567translate_errors()
568{
569	switch (errno) {
570	case ENOENT:
571		errval = HES_ER_NOTFOUND;
572		break;
573	case ECONNREFUSED:
574	case EMSGSIZE:
575		errval = HES_ER_NET;
576		break;
577	case ENOMEM:
578	default:
579		/* Not a good match, but the best we can do. */
580		errval = HES_ER_CONFIG;
581		break;
582	}
583}
584