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