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