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