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