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