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