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