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