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