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