1 /*
2  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #if defined(LIBC_SCCS) && !defined(lint)
7 static const char rcsid[] = "$Id: hesiod.c,v 1.23 2002/07/18 02:07:45 marka Exp $";
8 #endif
9 
10 /*
11  * Copyright (c) 1996,1999 by Internet Software Consortium.
12  *
13  * Permission to use, copy, modify, and distribute this software for any
14  * purpose with or without fee is hereby granted, provided that the above
15  * copyright notice and this permission notice appear in all copies.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
18  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
20  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
21  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
22  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
23  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
24  * SOFTWARE.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This file is primarily maintained by <tytso@mit.edu> and <ghudson@mit.edu>.
31  */
32 
33 /*
34  * hesiod.c --- the core portion of the hesiod resolver.
35  *
36  * This file is derived from the hesiod library from Project Athena;
37  * It has been extensively rewritten by Theodore Ts'o to have a more
38  * thread-safe interface.
39  */
40 
41 /* Imports */
42 
43 #include "port_before.h"
44 
45 #include <sys/types.h>
46 #include <netinet/in.h>
47 #include <arpa/nameser.h>
48 
49 #include <errno.h>
50 #include <netdb.h>
51 #include <resolv.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 
56 #include "port_after.h"
57 
58 #include "pathnames.h"
59 #include "hesiod.h"
60 #include "hesiod_p.h"
61 
62 /* Forward */
63 
64 int		hesiod_init(void **context);
65 void		hesiod_end(void *context);
66 char *		hesiod_to_bind(void *context, const char *name,
67 			       const char *type);
68 char **		hesiod_resolve(void *context, const char *name,
69 			       const char *type);
70 void		hesiod_free_list(void *context, char **list);
71 
72 static int	parse_config_file(struct hesiod_p *ctx, const char *filename);
73 static char **	get_txt_records(struct hesiod_p *ctx, int class,
74 				const char *name);
75 static int	init(struct hesiod_p *ctx);
76 
77 /* Public */
78 
79 /*
80  * This function is called to initialize a hesiod_p.
81  */
82 int
83 hesiod_init(void **context) {
84 	struct hesiod_p *ctx;
85 	char *cp;
86 
87 	ctx = malloc(sizeof(struct hesiod_p));
88 	if (ctx == 0) {
89 		errno = ENOMEM;
90 		return (-1);
91 	}
92 
93 #ifdef	ORIGINAL_ISC_CODE
94 	ctx->LHS = NULL;
95 	ctx->RHS = NULL;
96 	ctx->res = NULL;
97 #else
98 	memset(ctx, 0, sizeof (*ctx));
99 #endif	/* ORIGINAL_ISC_CODE */
100 
101 	if (parse_config_file(ctx, _PATH_HESIOD_CONF) < 0) {
102 #ifdef DEF_RHS
103 		/*
104 		 * Use compiled in defaults.
105 		 */
106 		ctx->LHS = malloc(strlen(DEF_LHS)+1);
107 		ctx->RHS = malloc(strlen(DEF_RHS)+1);
108 		if (ctx->LHS == 0 || ctx->RHS == 0) {
109 			errno = ENOMEM;
110 			goto cleanup;
111 		}
112 #ifdef HAVE_STRLCPY
113 		strlcpy(ctx->LHS, DEF_LHS, strlen(DEF_LHS) + 1);
114 		strlcpy(ctx->RHS, DEF_RHS, strlen(DEF_RHS) + 1);
115 #else
116 		strcpy(ctx->LHS, DEF_LHS);
117 		strcpy(ctx->RHS, DEF_RHS);
118 #endif
119 #else
120 		goto cleanup;
121 #endif
122 	}
123 	/*
124 	 * The default RHS can be overridden by an environment
125 	 * variable.
126 	 */
127 	if ((cp = getenv("HES_DOMAIN")) != NULL) {
128 		size_t RHSlen = strlen(cp) + 2;
129 		if (ctx->RHS)
130 			free(ctx->RHS);
131 		ctx->RHS = malloc(RHSlen);
132 		if (!ctx->RHS) {
133 			errno = ENOMEM;
134 			goto cleanup;
135 		}
136 		if (cp[0] == '.') {
137 #ifdef HAVE_STRLCPY
138 			strlcpy(ctx->RHS, cp, RHSlen);
139 #else
140 			strcpy(ctx->RHS, cp);
141 #endif
142 		} else {
143 #ifdef HAVE_STRLCPY
144 			strlcpy(ctx->RHS, ".", RHSlen);
145 #else
146 			strcpy(ctx->RHS, ".");
147 #endif
148 #ifdef HAVE_STRLCAT
149 			strlcat(ctx->RHS, cp, RHSlen);
150 #else
151 			strcat(ctx->RHS, cp);
152 #endif
153 		}
154 	}
155 
156 	/*
157 	 * If there is no default hesiod realm set, we return an
158 	 * error.
159 	 */
160 	if (!ctx->RHS) {
161 		errno = ENOEXEC;
162 		goto cleanup;
163 	}
164 
165 #if 0
166 	if (res_ninit(ctx->res) < 0)
167 		goto cleanup;
168 #endif
169 
170 	*context = ctx;
171 	return (0);
172 
173  cleanup:
174 	hesiod_end(ctx);
175 	return (-1);
176 }
177 
178 /*
179  * This function deallocates the hesiod_p
180  */
181 void
182 hesiod_end(void *context) {
183 	struct hesiod_p *ctx = (struct hesiod_p *) context;
184 	int save_errno = errno;
185 
186 	if (ctx->res)
187 		res_nclose(ctx->res);
188 	if (ctx->RHS)
189 		free(ctx->RHS);
190 	if (ctx->LHS)
191 		free(ctx->LHS);
192 	if (ctx->res && ctx->free_res)
193 		(*ctx->free_res)(ctx->res);
194 	free(ctx);
195 	errno = save_errno;
196 }
197 
198 /*
199  * This function takes a hesiod (name, type) and returns a DNS
200  * name which is to be resolved.
201  */
202 char *
203 hesiod_to_bind(void *context, const char *name, const char *type) {
204 	struct hesiod_p *ctx = (struct hesiod_p *) context;
205 	char *bindname;
206 	char **rhs_list = NULL;
207 	const char *RHS, *cp;
208 
209 	/* Decide what our RHS is, and set cp to the end of the actual name. */
210 	if ((cp = strchr(name, '@')) != NULL) {
211 		if (strchr(cp + 1, '.'))
212 			RHS = cp + 1;
213 		else if ((rhs_list = hesiod_resolve(context, cp + 1,
214 		    "rhs-extension")) != NULL)
215 			RHS = *rhs_list;
216 		else {
217 			errno = ENOENT;
218 			return (NULL);
219 		}
220 	} else {
221 		RHS = ctx->RHS;
222 		cp = name + strlen(name);
223 	}
224 
225 	/*
226 	 * Allocate the space we need, including up to three periods and
227 	 * the terminating NUL.
228 	 */
229 	if ((bindname = malloc((cp - name) + strlen(type) + strlen(RHS) +
230 	    (ctx->LHS ? strlen(ctx->LHS) : 0) + 4)) == NULL) {
231 		errno = ENOMEM;
232 		if (rhs_list)
233 			hesiod_free_list(context, rhs_list);
234 		return NULL;
235 	}
236 
237 	/* Now put together the DNS name. */
238 	memcpy(bindname, name, cp - name);
239 	bindname[cp - name] = '\0';
240 	strcat(bindname, ".");
241 	strcat(bindname, type);
242 	if (ctx->LHS) {
243 		if (ctx->LHS[0] != '.')
244 			strcat(bindname, ".");
245 		strcat(bindname, ctx->LHS);
246 	}
247 	if (RHS[0] != '.')
248 		strcat(bindname, ".");
249 	strcat(bindname, RHS);
250 
251 	if (rhs_list)
252 		hesiod_free_list(context, rhs_list);
253 
254 	return (bindname);
255 }
256 
257 /*
258  * This is the core function.  Given a hesiod (name, type), it
259  * returns an array of strings returned by the resolver.
260  */
261 char **
262 hesiod_resolve(void *context, const char *name, const char *type) {
263 	struct hesiod_p *ctx = (struct hesiod_p *) context;
264 	char *bindname = hesiod_to_bind(context, name, type);
265 	char **retvec;
266 
267 	if (bindname == NULL)
268 		return (NULL);
269 	if (init(ctx) == -1) {
270 		free(bindname);
271 		return (NULL);
272 	}
273 
274 	if ((retvec = get_txt_records(ctx, C_IN, bindname))) {
275 		free(bindname);
276 		return (retvec);
277 	}
278 
279 	if (errno != ENOENT)
280 		return (NULL);
281 
282 	retvec = get_txt_records(ctx, C_HS, bindname);
283 	free(bindname);
284 	return (retvec);
285 }
286 
287 void
288 hesiod_free_list(void *context, char **list) {
289 	char **p;
290 
291 	UNUSED(context);
292 
293 	for (p = list; *p; p++)
294 		free(*p);
295 	free(list);
296 }
297 
298 /*
299  * This function parses the /etc/hesiod.conf file
300  */
301 static int
302 parse_config_file(struct hesiod_p *ctx, const char *filename) {
303 	char *key, *data, *cp, **cpp;
304 	char buf[MAXDNAME+7];
305 	FILE *fp;
306 
307 	/*
308 	 * Clear the existing configuration variable, just in case
309 	 * they're set.
310 	 */
311 	if (ctx->RHS)
312 		free(ctx->RHS);
313 	if (ctx->LHS)
314 		free(ctx->LHS);
315 	ctx->RHS = ctx->LHS = 0;
316 
317 	/*
318 	 * Now open and parse the file...
319 	 */
320 	if (!(fp = fopen(filename, "r")))
321 		return (-1);
322 
323 	while (fgets(buf, sizeof(buf), fp) != NULL) {
324 		cp = buf;
325 		if (*cp == '#' || *cp == '\n' || *cp == '\r')
326 			continue;
327 		while(*cp == ' ' || *cp == '\t')
328 			cp++;
329 		key = cp;
330 		while(*cp != ' ' && *cp != '\t' && *cp != '=')
331 			cp++;
332 		*cp++ = '\0';
333 
334 		while(*cp == ' ' || *cp == '\t' || *cp == '=')
335 			cp++;
336 		data = cp;
337 		while(*cp != ' ' && *cp != '\n' && *cp != '\r')
338 			cp++;
339 		*cp++ = '\0';
340 
341 		if (strcmp(key, "lhs") == 0)
342 			cpp = &ctx->LHS;
343 		else if (strcmp(key, "rhs") == 0)
344 			cpp = &ctx->RHS;
345 		else
346 			continue;
347 
348 		*cpp = malloc(strlen(data) + 1);
349 		if (!*cpp) {
350 			errno = ENOMEM;
351 			goto cleanup;
352 		}
353 		strcpy(*cpp, data);
354 	}
355 	fclose(fp);
356 	return (0);
357 
358  cleanup:
359 	fclose(fp);
360 	if (ctx->RHS)
361 		free(ctx->RHS);
362 	if (ctx->LHS)
363 		free(ctx->LHS);
364 	ctx->RHS = ctx->LHS = 0;
365 	return (-1);
366 }
367 
368 /*
369  * Given a DNS class and a DNS name, do a lookup for TXT records, and
370  * return a list of them.
371  */
372 static char **
373 get_txt_records(struct hesiod_p *ctx, int class, const char *name) {
374 	struct {
375 		int type;		/* RR type */
376 		int class;		/* RR class */
377 		int dlen;		/* len of data section */
378 		u_char *data;		/* pointer to data */
379 	} rr;
380 	HEADER *hp;
381 	u_char qbuf[MAX_HESRESP], abuf[MAX_HESRESP];
382 	u_char *cp, *erdata, *eom;
383 	char *dst, *edst, **list;
384 	int ancount, qdcount;
385 	int i, j, n, skip;
386 
387 	/*
388 	 * Construct the query and send it.
389 	 */
390 	n = res_nmkquery(ctx->res, QUERY, name, class, T_TXT, NULL, 0,
391 			 NULL, qbuf, MAX_HESRESP);
392 	if (n < 0) {
393 		errno = EMSGSIZE;
394 		return (NULL);
395 	}
396 	n = res_nsend(ctx->res, qbuf, n, abuf, MAX_HESRESP);
397 	if (n < 0) {
398 		errno = ECONNREFUSED;
399 		return (NULL);
400 	}
401 	if (n < HFIXEDSZ) {
402 		errno = EMSGSIZE;
403 		return (NULL);
404 	}
405 
406 	/*
407 	 * OK, parse the result.
408 	 */
409 	hp = (HEADER *) abuf;
410 	ancount = ntohs(hp->ancount);
411 	qdcount = ntohs(hp->qdcount);
412 	cp = abuf + sizeof(HEADER);
413 	eom = abuf + n;
414 
415 	/* Skip query, trying to get to the answer section which follows. */
416 	for (i = 0; i < qdcount; i++) {
417 		skip = dn_skipname(cp, eom);
418 		if (skip < 0 || cp + skip + QFIXEDSZ > eom) {
419 			errno = EMSGSIZE;
420 			return (NULL);
421 		}
422 		cp += skip + QFIXEDSZ;
423 	}
424 
425 	list = malloc((ancount + 1) * sizeof(char *));
426 	if (!list) {
427 		errno = ENOMEM;
428 		return (NULL);
429 	}
430 	j = 0;
431 	for (i = 0; i < ancount; i++) {
432 		skip = dn_skipname(cp, eom);
433 		if (skip < 0) {
434 			errno = EMSGSIZE;
435 			goto cleanup;
436 		}
437 		cp += skip;
438 		if (cp + 3 * INT16SZ + INT32SZ > eom) {
439 			errno = EMSGSIZE;
440 			goto cleanup;
441 		}
442 		rr.type = ns_get16(cp);
443 		cp += INT16SZ;
444 		rr.class = ns_get16(cp);
445 		cp += INT16SZ + INT32SZ;	/* skip the ttl, too */
446 		rr.dlen = ns_get16(cp);
447 		cp += INT16SZ;
448 		if (cp + rr.dlen > eom) {
449 			errno = EMSGSIZE;
450 			goto cleanup;
451 		}
452 		rr.data = cp;
453 		cp += rr.dlen;
454 		if (rr.class != class || rr.type != T_TXT)
455 			continue;
456 		if (!(list[j] = malloc(rr.dlen)))
457 			goto cleanup;
458 		dst = list[j++];
459 		edst = dst + rr.dlen;
460 		erdata = rr.data + rr.dlen;
461 		cp = rr.data;
462 		while (cp < erdata) {
463 			n = (unsigned char) *cp++;
464 			if (cp + n > eom || dst + n > edst) {
465 				errno = EMSGSIZE;
466 				goto cleanup;
467 			}
468 			memcpy(dst, cp, n);
469 			cp += n;
470 			dst += n;
471 		}
472 		if (cp != erdata) {
473 			errno = EMSGSIZE;
474 			goto cleanup;
475 		}
476 		*dst = '\0';
477 	}
478 	list[j] = NULL;
479 	if (j == 0) {
480 		errno = ENOENT;
481 		goto cleanup;
482 	}
483 	return (list);
484 
485  cleanup:
486 	for (i = 0; i < j; i++)
487 		free(list[i]);
488 	free(list);
489 	return (NULL);
490 }
491 
492 struct __res_state *
493 __hesiod_res_get(void *context) {
494 	struct hesiod_p *ctx = context;
495 
496 	if (!ctx->res) {
497 		struct __res_state *res;
498 		res = (struct __res_state *)malloc(sizeof *res);
499 		if (res == NULL) {
500 			errno = ENOMEM;
501 			return (NULL);
502 		}
503 		memset(res, 0, sizeof *res);
504 		__hesiod_res_set(ctx, res, free);
505 	}
506 
507 	return (ctx->res);
508 }
509 
510 void
511 __hesiod_res_set(void *context, struct __res_state *res,
512 	         void (*free_res)(void *)) {
513 	struct hesiod_p *ctx = context;
514 
515 	if (ctx->res && ctx->free_res) {
516 		res_nclose(ctx->res);
517 		(*ctx->free_res)(ctx->res);
518 	}
519 
520 	ctx->res = res;
521 	ctx->free_res = free_res;
522 }
523 
524 static int
525 init(struct hesiod_p *ctx) {
526 
527 	if (!ctx->res && !__hesiod_res_get(ctx))
528 		return (-1);
529 
530 	if (((ctx->res->options & RES_INIT) == 0) &&
531 	    (res_ninit(ctx->res) == -1))
532 		return (-1);
533 
534 	return (0);
535 }
536