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