xref: /dragonfly/lib/libc/net/hesiod.c (revision 5dcdf778)
1 /* Copyright (c) 1996 by Internet Software Consortium.
2  *
3  * Permission to use, copy, modify, and distribute this software for any
4  * purpose with or without fee is hereby granted, provided that the above
5  * copyright notice and this permission notice appear in all copies.
6  *
7  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
8  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
9  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
10  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
11  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
12  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
14  * SOFTWARE.
15  */
16 
17 /* Copyright 1996 by the Massachusetts Institute of Technology.
18  *
19  * Permission to use, copy, modify, and distribute this
20  * software and its documentation for any purpose and without
21  * fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright
23  * notice and this permission notice appear in supporting
24  * documentation, and that the name of M.I.T. not be used in
25  * advertising or publicity pertaining to distribution of the
26  * software without specific, written prior permission.
27  * M.I.T. makes no representations about the suitability of
28  * this software for any purpose.  It is provided "as is"
29  * without express or implied warranty.
30  */
31 
32 /* This file is part of the hesiod library.  It implements the core
33  * portion of the hesiod resolver.
34  *
35  * This file is loosely based on an interim version of hesiod.c from
36  * the BIND IRS library, which was in turn based on an earlier version
37  * of this file.  Extensive changes have been made on each step of the
38  * path.
39  *
40  * This implementation is not truly thread-safe at the moment because
41  * it uses res_send() and accesses _res.
42  *
43  * $NetBSD: hesiod.c,v 1.9 1999/02/11 06:16:38 simonb Exp $
44  * $FreeBSD: src/lib/libc/net/hesiod.c,v 1.9 2003/05/01 19:03:14 nectar Exp $
45  */
46 
47 #include <sys/types.h>
48 #include <sys/param.h>
49 #include <netinet/in.h>
50 #include <arpa/nameser.h>
51 
52 #include <ctype.h>
53 #include <errno.h>
54 #include <hesiod.h>
55 #include <resolv.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 
61 struct hesiod_p {
62 	char	*lhs;			/* normally ".ns" */
63 	char	*rhs;			/* AKA the default hesiod domain */
64 	int	 classes[2];		/* The class search order. */
65 };
66 
67 #define	MAX_HESRESP	1024
68 
69 static int	  read_config_file(struct hesiod_p *, const char *);
70 static char	**get_txt_records(int, const char *);
71 static int	  init_context(void);
72 static void	  translate_errors(void);
73 
74 
75 /*
76  * hesiod_init --
77  *	initialize a hesiod_p.
78  */
79 int
hesiod_init(void ** context)80 hesiod_init(void **context)
81 {
82 	struct hesiod_p	*ctx;
83 	const char	*p, *configname;
84 
85 	ctx = malloc(sizeof(struct hesiod_p));
86 	if (ctx) {
87 		*context = ctx;
88 		if (!issetugid())
89 			configname = getenv("HESIOD_CONFIG");
90 		else
91 			configname = NULL;
92 		if (!configname)
93 			configname = _PATH_HESIOD_CONF;
94 		if (read_config_file(ctx, configname) >= 0) {
95 			/*
96 			 * The default rhs can be overridden by an
97 			 * environment variable.
98 			 */
99 			if (!issetugid())
100 				p = getenv("HES_DOMAIN");
101 			else
102 				p = NULL;
103 			if (p) {
104 				if (ctx->rhs)
105 					free(ctx->rhs);
106 				ctx->rhs = malloc(strlen(p) + 2);
107 				if (ctx->rhs) {
108 					*ctx->rhs = '.';
109 					strcpy(ctx->rhs + 1,
110 					    (*p == '.') ? p + 1 : p);
111 					return 0;
112 				} else
113 					errno = ENOMEM;
114 			} else
115 				return 0;
116 		}
117 	} else
118 		errno = ENOMEM;
119 
120 	if (ctx->lhs)
121 		free(ctx->lhs);
122 	if (ctx->rhs)
123 		free(ctx->rhs);
124 	if (ctx)
125 		free(ctx);
126 	return -1;
127 }
128 
129 /*
130  * hesiod_end --
131  *	Deallocates the hesiod_p.
132  */
133 void
hesiod_end(void * context)134 hesiod_end(void *context)
135 {
136 	struct hesiod_p *ctx = (struct hesiod_p *) context;
137 
138 	free(ctx->rhs);
139 	if (ctx->lhs)
140 		free(ctx->lhs);
141 	free(ctx);
142 }
143 
144 /*
145  * hesiod_to_bind --
146  * 	takes a hesiod (name, type) and returns a DNS
147  *	name which is to be resolved.
148  */
149 char *
hesiod_to_bind(void * context,const char * name,const char * type)150 hesiod_to_bind(void *context, const char *name, const char *type)
151 {
152 	struct hesiod_p *ctx = (struct hesiod_p *) context;
153 	char		 bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
154 	const char	*rhs;
155 	int		 len;
156 
157 	if (strlcpy(bindname, name, sizeof(bindname)) >= sizeof(bindname)) {
158 		errno = EMSGSIZE;
159 		return NULL;
160 	}
161 
162 		/*
163 		 * Find the right right hand side to use, possibly
164 		 * truncating bindname.
165 		 */
166 	p = strchr(bindname, '@');
167 	if (p) {
168 		*p++ = 0;
169 		if (strchr(p, '.'))
170 			rhs = name + (p - bindname);
171 		else {
172 			rhs_list = hesiod_resolve(context, p, "rhs-extension");
173 			if (rhs_list)
174 				rhs = *rhs_list;
175 			else {
176 				errno = ENOENT;
177 				return NULL;
178 			}
179 		}
180 	} else
181 		rhs = ctx->rhs;
182 
183 		/* See if we have enough room. */
184 	len = strlen(bindname) + 1 + strlen(type);
185 	if (ctx->lhs)
186 		len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
187 	len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
188 	if (len > sizeof(bindname) - 1) {
189 		if (rhs_list)
190 			hesiod_free_list(context, rhs_list);
191 		errno = EMSGSIZE;
192 		return NULL;
193 	}
194 		/* Put together the rest of the domain. */
195 	strcat(bindname, ".");
196 	strcat(bindname, type);
197 		/* Only append lhs if it isn't empty. */
198 	if (ctx->lhs && ctx->lhs[0] != '\0' ) {
199 		if (ctx->lhs[0] != '.')
200 			strcat(bindname, ".");
201 		strcat(bindname, ctx->lhs);
202 	}
203 	if (rhs[0] != '.')
204 		strcat(bindname, ".");
205 	strcat(bindname, rhs);
206 
207 		/* rhs_list is no longer needed, since we're done with rhs. */
208 	if (rhs_list)
209 		hesiod_free_list(context, rhs_list);
210 
211 		/* Make a copy of the result and return it to the caller. */
212 	ret = strdup(bindname);
213 	if (!ret)
214 		errno = ENOMEM;
215 	return ret;
216 }
217 
218 /*
219  * hesiod_resolve --
220  *	Given a hesiod name and type, return an array of strings returned
221  *	by the resolver.
222  */
223 char **
hesiod_resolve(void * context,const char * name,const char * type)224 hesiod_resolve(void *context, const char *name, const char *type)
225 {
226 	struct hesiod_p	*ctx = (struct hesiod_p *) context;
227 	char		*bindname, **retvec;
228 
229 	bindname = hesiod_to_bind(context, name, type);
230 	if (!bindname)
231 		return NULL;
232 
233 	retvec = get_txt_records(ctx->classes[0], bindname);
234 	if (retvec == NULL && errno == ENOENT && ctx->classes[1])
235 		retvec = get_txt_records(ctx->classes[1], bindname);
236 
237 	free(bindname);
238 	return retvec;
239 }
240 
241 /*ARGSUSED*/
242 void
hesiod_free_list(void * context __unused,char ** list)243 hesiod_free_list(void *context __unused, char **list)
244 {
245 	char  **p;
246 
247 	if (list == NULL)
248 		return;
249 	for (p = list; *p; p++)
250 		free(*p);
251 	free(list);
252 }
253 
254 
255 /* read_config_file --
256  *	Parse the /etc/hesiod.conf file.  Returns 0 on success,
257  *	-1 on failure.  On failure, it might leave values in ctx->lhs
258  *	or ctx->rhs which need to be freed by the caller.
259  */
260 static int
read_config_file(struct hesiod_p * ctx,const char * filename)261 read_config_file(struct hesiod_p *ctx, const char *filename)
262 {
263 	char	*key, *data, *p, **which;
264 	char	 buf[MAXDNAME + 7];
265 	int	 n;
266 	FILE	*fp;
267 
268 		/* Set default query classes. */
269 	ctx->classes[0] = C_IN;
270 	ctx->classes[1] = C_HS;
271 
272 		/* Try to open the configuration file. */
273 	fp = fopen(filename, "r");
274 	if (!fp) {
275 		/* Use compiled in default domain names. */
276 		ctx->lhs = strdup(DEF_LHS);
277 		ctx->rhs = strdup(DEF_RHS);
278 		if (ctx->lhs && ctx->rhs)
279 			return 0;
280 		else {
281 			errno = ENOMEM;
282 			return -1;
283 		}
284 	}
285 	ctx->lhs = NULL;
286 	ctx->rhs = NULL;
287 	while (fgets(buf, sizeof(buf), fp) != NULL) {
288 		p = buf;
289 		if (*p == '#' || *p == '\n' || *p == '\r')
290 			continue;
291 		while (*p == ' ' || *p == '\t')
292 			p++;
293 		key = p;
294 		while (*p != ' ' && *p != '\t' && *p != '=')
295 			p++;
296 		*p++ = 0;
297 
298 		while (isspace(*p) || *p == '=')
299 			p++;
300 		data = p;
301 		while (!isspace(*p))
302 			p++;
303 		*p = 0;
304 
305 		if (strcasecmp(key, "lhs") == 0 ||
306 		    strcasecmp(key, "rhs") == 0) {
307 			which = (strcasecmp(key, "lhs") == 0)
308 			    ? &ctx->lhs : &ctx->rhs;
309 			*which = strdup(data);
310 			if (!*which) {
311 				errno = ENOMEM;
312 				return -1;
313 			}
314 		} else {
315 			if (strcasecmp(key, "classes") == 0) {
316 				n = 0;
317 				while (*data && n < 2) {
318 					p = data;
319 					while (*p && *p != ',')
320 						p++;
321 					if (*p)
322 						*p++ = 0;
323 					if (strcasecmp(data, "IN") == 0)
324 						ctx->classes[n++] = C_IN;
325 					else
326 						if (strcasecmp(data, "HS") == 0)
327 							ctx->classes[n++] =
328 							    C_HS;
329 					data = p;
330 				}
331 				while (n < 2)
332 					ctx->classes[n++] = 0;
333 			}
334 		}
335 	}
336 	fclose(fp);
337 
338 	if (!ctx->rhs || ctx->classes[0] == 0 ||
339 	    ctx->classes[0] == ctx->classes[1]) {
340 		errno = ENOEXEC;
341 		return -1;
342 	}
343 	return 0;
344 }
345 
346 /*
347  * get_txt_records --
348  *	Given a DNS class and a DNS name, do a lookup for TXT records, and
349  *	return a list of them.
350  */
351 static char **
get_txt_records(int qclass,const char * name)352 get_txt_records(int qclass, const char *name)
353 {
354 	HEADER		*hp;
355 	unsigned char	 qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
356 	char		*dst, **list;
357 	int		 ancount, qdcount, i, j, n, skip, type, class, len;
358 
359 		/* Make sure the resolver is initialized. */
360 	if ((_res.options & RES_INIT) == 0 && res_init() == -1)
361 		return NULL;
362 
363 		/* Construct the query. */
364 	n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
365 	    NULL, qbuf, PACKETSZ);
366 	if (n < 0)
367 		return NULL;
368 
369 		/* Send the query. */
370 	n = res_send(qbuf, n, abuf, MAX_HESRESP);
371 	if (n < 0 || n > MAX_HESRESP) {
372 		errno = ECONNREFUSED; /* XXX */
373 		return NULL;
374 	}
375 		/* Parse the header of the result. */
376 	hp = (HEADER *) (void *) abuf;
377 	ancount = ntohs(hp->ancount);
378 	qdcount = ntohs(hp->qdcount);
379 	p = abuf + sizeof(HEADER);
380 	eom = abuf + n;
381 
382 		/*
383 		 * Skip questions, trying to get to the answer section
384 		 * which follows.
385 		 */
386 	for (i = 0; i < qdcount; i++) {
387 		skip = dn_skipname(p, eom);
388 		if (skip < 0 || p + skip + QFIXEDSZ > eom) {
389 			errno = EMSGSIZE;
390 			return NULL;
391 		}
392 		p += skip + QFIXEDSZ;
393 	}
394 
395 		/* Allocate space for the text record answers. */
396 	list = malloc((ancount + 1) * sizeof(char *));
397 	if (!list) {
398 		errno = ENOMEM;
399 		return NULL;
400 	}
401 		/* Parse the answers. */
402 	j = 0;
403 	for (i = 0; i < ancount; i++) {
404 		/* Parse the header of this answer. */
405 		skip = dn_skipname(p, eom);
406 		if (skip < 0 || p + skip + 10 > eom)
407 			break;
408 		type = p[skip + 0] << 8 | p[skip + 1];
409 		class = p[skip + 2] << 8 | p[skip + 3];
410 		len = p[skip + 8] << 8 | p[skip + 9];
411 		p += skip + 10;
412 		if (p + len > eom) {
413 			errno = EMSGSIZE;
414 			break;
415 		}
416 		/* Skip entries of the wrong class and type. */
417 		if (class != qclass || type != T_TXT) {
418 			p += len;
419 			continue;
420 		}
421 		/* Allocate space for this answer. */
422 		list[j] = malloc((size_t)len);
423 		if (!list[j]) {
424 			errno = ENOMEM;
425 			break;
426 		}
427 		dst = list[j++];
428 
429 		/* Copy answer data into the allocated area. */
430 		eor = p + len;
431 		while (p < eor) {
432 			n = (unsigned char) *p++;
433 			if (p + n > eor) {
434 				errno = EMSGSIZE;
435 				break;
436 			}
437 			memcpy(dst, p, (size_t)n);
438 			p += n;
439 			dst += n;
440 		}
441 		if (p < eor) {
442 			errno = EMSGSIZE;
443 			break;
444 		}
445 		*dst = 0;
446 	}
447 
448 		/*
449 		 * If we didn't terminate the loop normally, something
450 		 * went wrong.
451 		 */
452 	if (i < ancount) {
453 		for (i = 0; i < j; i++)
454 			free(list[i]);
455 		free(list);
456 		return NULL;
457 	}
458 	if (j == 0) {
459 		errno = ENOENT;
460 		free(list);
461 		return NULL;
462 	}
463 	list[j] = NULL;
464 	return list;
465 }
466 
467 		/*
468 		 *	COMPATIBILITY FUNCTIONS
469 		 */
470 
471 static int	  inited = 0;
472 static void	 *context;
473 static int	  errval = HES_ER_UNINIT;
474 
475 int
hes_init(void)476 hes_init(void)
477 {
478 	init_context();
479 	return errval;
480 }
481 
482 char *
hes_to_bind(const char * name,const char * type)483 hes_to_bind(const char *name, const char *type)
484 {
485 	static	char	*bindname;
486 	if (init_context() < 0)
487 		return NULL;
488 	if (bindname)
489 		free(bindname);
490 	bindname = hesiod_to_bind(context, name, type);
491 	if (!bindname)
492 		translate_errors();
493 	return bindname;
494 }
495 
496 char **
hes_resolve(const char * name,const char * type)497 hes_resolve(const char *name, const char *type)
498 {
499 	static char	**list;
500 
501 	if (init_context() < 0)
502 		return NULL;
503 
504 	/*
505 	 * In the old Hesiod interface, the caller was responsible for
506 	 * freeing the returned strings but not the vector of strings itself.
507 	 */
508 	if (list)
509 		free(list);
510 
511 	list = hesiod_resolve(context, name, type);
512 	if (!list)
513 		translate_errors();
514 	return list;
515 }
516 
517 int
hes_error(void)518 hes_error(void)
519 {
520 	return errval;
521 }
522 
523 void
hes_free(char ** hp)524 hes_free(char **hp)
525 {
526 	hesiod_free_list(context, hp);
527 }
528 
529 static int
init_context(void)530 init_context(void)
531 {
532 	if (!inited) {
533 		inited = 1;
534 		if (hesiod_init(&context) < 0) {
535 			errval = HES_ER_CONFIG;
536 			return -1;
537 		}
538 		errval = HES_ER_OK;
539 	}
540 	return 0;
541 }
542 
543 static void
translate_errors(void)544 translate_errors(void)
545 {
546 	switch (errno) {
547 	case ENOENT:
548 		errval = HES_ER_NOTFOUND;
549 		break;
550 	case ECONNREFUSED:
551 	case EMSGSIZE:
552 		errval = HES_ER_NET;
553 		break;
554 	case ENOMEM:
555 	default:
556 		/* Not a good match, but the best we can do. */
557 		errval = HES_ER_CONFIG;
558 		break;
559 	}
560 }
561