1 /* $OpenBSD: asr.c,v 1.26 2013/05/27 17:31:01 eric Exp $ */ 2 /* 3 * Copyright (c) 2010-2012 Eric Faurot <eric@openbsd.org> 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 THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <netinet/in.h> 21 #include <arpa/inet.h> 22 #include <arpa/nameser.h> 23 24 #include <err.h> 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <netdb.h> 28 #include <resolv.h> 29 #include <poll.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include "asr.h" 36 #include "asr_private.h" 37 38 #ifndef ASR_OPT_THREADSAFE 39 #define ASR_OPT_THREADSAFE 1 40 #endif 41 #ifndef ASR_OPT_HOSTALIASES 42 #define ASR_OPT_HOSTALIASES 1 43 #endif 44 #ifndef ASR_OPT_ENVOPTS 45 #define ASR_OPT_ENVOPTS 1 46 #endif 47 #ifndef ASR_OPT_RELOADCONF 48 #define ASR_OPT_RELOADCONF 1 49 #endif 50 #ifndef ASR_OPT_ALTCONF 51 #define ASR_OPT_ALTCONF 1 52 #endif 53 54 #if ASR_OPT_THREADSAFE 55 #include "thread_private.h" 56 #endif 57 58 #define DEFAULT_CONFFILE "/etc/resolv.conf" 59 #define DEFAULT_HOSTFILE "/etc/hosts" 60 #define DEFAULT_CONF "lookup file\n" 61 #define DEFAULT_LOOKUP "lookup bind file" 62 63 #define RELOAD_DELAY 15 /* seconds */ 64 65 static void asr_check_reload(struct asr *); 66 static struct asr_ctx *asr_ctx_create(void); 67 static void asr_ctx_ref(struct asr_ctx *); 68 static void asr_ctx_free(struct asr_ctx *); 69 static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *); 70 static int asr_ctx_from_file(struct asr_ctx *, const char *); 71 static int asr_ctx_from_string(struct asr_ctx *, const char *); 72 static int asr_ctx_parse(struct asr_ctx *, const char *); 73 static int asr_parse_nameserver(struct sockaddr *, const char *); 74 static int asr_ndots(const char *); 75 static void pass0(char **, int, struct asr_ctx *); 76 static int strsplit(char *, char **, int); 77 #if ASR_OPT_HOSTALIASES 78 static char *asr_hostalias(const char *, char *, size_t); 79 #endif 80 #if ASR_OPT_ENVOPTS 81 static void asr_ctx_envopts(struct asr_ctx *); 82 #endif 83 #if ASR_OPT_THREADSAFE 84 static void *__THREAD_NAME(_asr); 85 #else 86 # define _THREAD_PRIVATE(a, b, c) (c) 87 #endif 88 89 static struct asr *_asr = NULL; 90 91 /* Allocate and configure an async "resolver". */ 92 struct asr * 93 async_resolver(const char *conf) 94 { 95 static int init = 0; 96 struct asr *asr; 97 98 if (init == 0) { 99 #ifdef DEBUG 100 if (getenv("ASR_DEBUG")) 101 asr_debug = stderr; 102 #endif 103 init = 1; 104 } 105 106 if ((asr = calloc(1, sizeof(*asr))) == NULL) 107 goto fail; 108 109 #if ASR_OPT_ALTCONF 110 /* If not setuid/setgid, allow to use an alternate config. */ 111 if (conf == NULL && !issetugid()) 112 conf = getenv("ASR_CONFIG"); 113 #endif 114 115 if (conf == NULL) 116 conf = DEFAULT_CONFFILE; 117 118 if (conf[0] == '!') { 119 /* Use the rest of the string as config file */ 120 if ((asr->a_ctx = asr_ctx_create()) == NULL) 121 goto fail; 122 if (asr_ctx_from_string(asr->a_ctx, conf + 1) == -1) 123 goto fail; 124 } else { 125 /* Use the given config file */ 126 asr->a_path = strdup(conf); 127 if (asr->a_path == NULL) 128 goto fail; 129 asr_check_reload(asr); 130 if (asr->a_ctx == NULL) { 131 if ((asr->a_ctx = asr_ctx_create()) == NULL) 132 goto fail; 133 if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1) 134 goto fail; 135 #if ASR_OPT_ENVOPTS 136 asr_ctx_envopts(asr->a_ctx); 137 #endif 138 } 139 } 140 141 #ifdef DEBUG 142 asr_dump_config(asr_debug, asr); 143 #endif 144 return (asr); 145 146 fail: 147 if (asr) { 148 if (asr->a_ctx) 149 asr_ctx_free(asr->a_ctx); 150 free(asr->a_path); 151 free(asr); 152 } 153 154 return (NULL); 155 } 156 157 /* 158 * Free the "asr" async resolver (or the thread-local resolver if NULL). 159 * Drop the reference to the current context. 160 */ 161 void 162 async_resolver_done(struct asr *asr) 163 { 164 struct asr **priv; 165 166 if (asr == NULL) { 167 priv = _THREAD_PRIVATE(_asr, _asr, &_asr); 168 if (*priv == NULL) 169 return; 170 asr = *priv; 171 *priv = NULL; 172 } 173 174 asr_ctx_unref(asr->a_ctx); 175 free(asr->a_path); 176 free(asr); 177 } 178 179 /* 180 * Cancel an async query. 181 */ 182 void 183 async_abort(struct async *as) 184 { 185 async_free(as); 186 } 187 188 /* 189 * Resume the "as" async query resolution. Return one of ASYNC_COND, 190 * ASYNC_YIELD or ASYNC_DONE and put query-specific return values in 191 * the user-allocated memory at "ar". 192 */ 193 int 194 async_run(struct async *as, struct async_res *ar) 195 { 196 int r, saved_errno = errno; 197 198 DPRINT("asr: async_run(%p, %p) %s ctx=[%p]\n", as, ar, 199 asr_querystr(as->as_type), as->as_ctx); 200 r = as->as_run(as, ar); 201 202 DPRINT("asr: async_run(%p, %p) -> %s", as, ar, asr_transitionstr(r)); 203 #ifdef DEBUG 204 if (r == ASYNC_COND) 205 #endif 206 DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout); 207 DPRINT("\n"); 208 if (r == ASYNC_DONE) 209 async_free(as); 210 211 errno = saved_errno; 212 213 return (r); 214 } 215 216 /* 217 * Same as above, but run in a loop that handles the fd conditions result. 218 */ 219 int 220 async_run_sync(struct async *as, struct async_res *ar) 221 { 222 struct pollfd fds[1]; 223 int r, saved_errno = errno; 224 225 while ((r = async_run(as, ar)) == ASYNC_COND) { 226 fds[0].fd = ar->ar_fd; 227 fds[0].events = (ar->ar_cond == ASYNC_READ) ? POLLIN : POLLOUT; 228 again: 229 r = poll(fds, 1, ar->ar_timeout); 230 if (r == -1 && errno == EINTR) 231 goto again; 232 /* 233 * Otherwise, just ignore the error and let async_run() 234 * catch the failure. 235 */ 236 } 237 238 errno = saved_errno; 239 240 return (r); 241 } 242 243 /* 244 * Create a new async request of the given "type" on the async context "ac". 245 * Take a reference on it so it does not gets deleted while the async query 246 * is running. 247 */ 248 struct async * 249 async_new(struct asr_ctx *ac, int type) 250 { 251 struct async *as; 252 253 DPRINT("asr: async_new(ctx=%p) type=%i refcount=%i\n", ac, type, 254 ac ? ac->ac_refcount : 0); 255 if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL) 256 return (NULL); 257 258 ac->ac_refcount += 1; 259 as->as_ctx = ac; 260 as->as_fd = -1; 261 as->as_type = type; 262 as->as_state = ASR_STATE_INIT; 263 264 return (as); 265 } 266 267 /* 268 * Free an async query and unref the associated context. 269 */ 270 void 271 async_free(struct async *as) 272 { 273 DPRINT("asr: async_free(%p)\n", as); 274 switch (as->as_type) { 275 case ASR_SEND: 276 if (as->as_fd != -1) 277 close(as->as_fd); 278 if (as->as.dns.obuf && !(as->as.dns.flags & ASYNC_EXTOBUF)) 279 free(as->as.dns.obuf); 280 if (as->as.dns.ibuf) 281 free(as->as.dns.ibuf); 282 if (as->as.dns.dname) 283 free(as->as.dns.dname); 284 break; 285 286 case ASR_SEARCH: 287 if (as->as.search.subq) 288 async_free(as->as.search.subq); 289 if (as->as.search.name) 290 free(as->as.search.name); 291 break; 292 293 case ASR_GETRRSETBYNAME: 294 if (as->as.rrset.subq) 295 async_free(as->as.rrset.subq); 296 if (as->as.rrset.name) 297 free(as->as.rrset.name); 298 break; 299 300 case ASR_GETHOSTBYNAME: 301 case ASR_GETHOSTBYADDR: 302 if (as->as.hostnamadr.subq) 303 async_free(as->as.hostnamadr.subq); 304 if (as->as.hostnamadr.name) 305 free(as->as.hostnamadr.name); 306 break; 307 308 case ASR_GETNETBYNAME: 309 case ASR_GETNETBYADDR: 310 if (as->as.netnamadr.subq) 311 async_free(as->as.netnamadr.subq); 312 if (as->as.netnamadr.name) 313 free(as->as.netnamadr.name); 314 break; 315 316 case ASR_GETADDRINFO: 317 if (as->as.ai.subq) 318 async_free(as->as.ai.subq); 319 if (as->as.ai.aifirst) 320 freeaddrinfo(as->as.ai.aifirst); 321 if (as->as.ai.hostname) 322 free(as->as.ai.hostname); 323 if (as->as.ai.servname) 324 free(as->as.ai.servname); 325 if (as->as.ai.fqdn) 326 free(as->as.ai.fqdn); 327 break; 328 329 case ASR_GETNAMEINFO: 330 if (as->as.ni.subq) 331 async_free(as->as.ni.subq); 332 break; 333 } 334 335 asr_ctx_unref(as->as_ctx); 336 free(as); 337 } 338 339 /* 340 * Get a context from the given resolver. This takes a new reference to 341 * the returned context, which *must* be explicitely dropped when done 342 * using this context. 343 */ 344 struct asr_ctx * 345 asr_use_resolver(struct asr *asr) 346 { 347 struct asr **priv; 348 349 if (asr == NULL) { 350 DPRINT("using thread-local resolver\n"); 351 priv = _THREAD_PRIVATE(_asr, _asr, &_asr); 352 if (*priv == NULL) { 353 DPRINT("setting up thread-local resolver\n"); 354 *priv = async_resolver(NULL); 355 } 356 asr = *priv; 357 } 358 if (asr != NULL) { 359 asr_check_reload(asr); 360 asr_ctx_ref(asr->a_ctx); 361 return (asr->a_ctx); 362 } 363 return (NULL); 364 } 365 366 static void 367 asr_ctx_ref(struct asr_ctx *ac) 368 { 369 DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount); 370 ac->ac_refcount += 1; 371 } 372 373 /* 374 * Drop a reference to an async context, freeing it if the reference 375 * count drops to 0. 376 */ 377 void 378 asr_ctx_unref(struct asr_ctx *ac) 379 { 380 DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac, 381 ac ? ac->ac_refcount : 0); 382 if (ac == NULL) 383 return; 384 if (--ac->ac_refcount) 385 return; 386 387 asr_ctx_free(ac); 388 } 389 390 static void 391 asr_ctx_free(struct asr_ctx *ac) 392 { 393 int i; 394 395 if (ac->ac_domain) 396 free(ac->ac_domain); 397 for (i = 0; i < ASR_MAXNS; i++) 398 free(ac->ac_ns[i]); 399 for (i = 0; i < ASR_MAXDOM; i++) 400 free(ac->ac_dom[i]); 401 402 free(ac); 403 } 404 405 /* 406 * Reload the configuration file if it has changed on disk. 407 */ 408 static void 409 asr_check_reload(struct asr *asr) 410 { 411 struct asr_ctx *ac; 412 #if ASR_OPT_RELOADCONF 413 struct stat st; 414 struct timespec ts; 415 #endif 416 417 if (asr->a_path == NULL) 418 return; 419 420 #if ASR_OPT_RELOADCONF 421 if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) 422 return; 423 424 if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0) 425 return; 426 asr->a_rtime = ts.tv_sec; 427 428 DPRINT("asr: checking for update of \"%s\"\n", asr->a_path); 429 if (stat(asr->a_path, &st) == -1 || 430 asr->a_mtime == st.st_mtime || 431 (ac = asr_ctx_create()) == NULL) 432 return; 433 asr->a_mtime = st.st_mtime; 434 #else 435 if ((ac = asr_ctx_create()) == NULL) 436 return; 437 #endif 438 439 DPRINT("asr: reloading config file\n"); 440 if (asr_ctx_from_file(ac, asr->a_path) == -1) { 441 asr_ctx_free(ac); 442 return; 443 } 444 445 #if ASR_OPT_ENVOPTS 446 asr_ctx_envopts(ac); 447 #endif 448 if (asr->a_ctx) 449 asr_ctx_unref(asr->a_ctx); 450 asr->a_ctx = ac; 451 } 452 453 /* 454 * Construct a fully-qualified domain name for the given name and domain. 455 * If "name" ends with a '.' it is considered as a FQDN by itself. 456 * Otherwise, the domain, which must be a FQDN, is appended to "name" (it 457 * may have a leading dot which would be ignored). If the domain is null, 458 * then "." is used. Return the length of the constructed FQDN or (0) on 459 * error. 460 */ 461 size_t 462 asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen) 463 { 464 size_t len; 465 466 if (domain == NULL) 467 domain = "."; 468 else if ((len = strlen(domain)) == 0) 469 return (0); 470 else if (domain[len -1] != '.') 471 return (0); 472 473 len = strlen(name); 474 if (len == 0) { 475 if (strlcpy(buf, domain, buflen) >= buflen) 476 return (0); 477 } else if (name[len - 1] != '.') { 478 if (domain[0] == '.') 479 domain += 1; 480 if (strlcpy(buf, name, buflen) >= buflen || 481 strlcat(buf, ".", buflen) >= buflen || 482 strlcat(buf, domain, buflen) >= buflen) 483 return (0); 484 } else { 485 if (strlcpy(buf, name, buflen) >= buflen) 486 return (0); 487 } 488 489 return (strlen(buf)); 490 } 491 492 /* 493 * Concatenate a name and a domain name. The result has no trailing dot. 494 * Return the resulting string length, or 0 in case of error. 495 */ 496 size_t 497 asr_domcat(const char *name, const char *domain, char *buf, size_t buflen) 498 { 499 size_t r; 500 501 r = asr_make_fqdn(name, domain, buf, buflen); 502 if (r == 0) 503 return (0); 504 buf[r - 1] = '\0'; 505 506 return (r - 1); 507 } 508 509 /* 510 * Count the dots in a string. 511 */ 512 static int 513 asr_ndots(const char *s) 514 { 515 int n; 516 517 for (n = 0; *s; s++) 518 if (*s == '.') 519 n += 1; 520 521 return (n); 522 } 523 524 /* 525 * Allocate a new empty context. 526 */ 527 static struct asr_ctx * 528 asr_ctx_create(void) 529 { 530 struct asr_ctx *ac; 531 532 if ((ac = calloc(1, sizeof(*ac))) == NULL) 533 return (NULL); 534 535 ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH; 536 ac->ac_refcount = 1; 537 ac->ac_ndots = 1; 538 ac->ac_family[0] = AF_INET; 539 ac->ac_family[1] = AF_INET6; 540 ac->ac_family[2] = -1; 541 542 ac->ac_hostfile = DEFAULT_HOSTFILE; 543 544 ac->ac_nscount = 0; 545 ac->ac_nstimeout = 5; 546 ac->ac_nsretries = 4; 547 548 return (ac); 549 } 550 551 /* 552 * Add a search domain to the async context. 553 */ 554 static int 555 asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain) 556 { 557 char buf[MAXDNAME]; 558 559 if (ac->ac_domcount == ASR_MAXDOM) 560 return (-1); 561 562 if (asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0) 563 return (-1); 564 565 if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL) 566 return (0); 567 568 ac->ac_domcount += 1; 569 570 return (1); 571 } 572 573 static int 574 strsplit(char *line, char **tokens, int ntokens) 575 { 576 int ntok; 577 char *cp, **tp; 578 579 for (cp = line, tp = tokens, ntok = 0; 580 ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; ) 581 if (**tp != '\0') { 582 tp++; 583 ntok++; 584 } 585 586 return (ntok); 587 } 588 589 /* 590 * Pass on a split config line. 591 */ 592 static void 593 pass0(char **tok, int n, struct asr_ctx *ac) 594 { 595 int i, j, d; 596 const char *e; 597 struct sockaddr_storage ss; 598 599 if (!strcmp(tok[0], "nameserver")) { 600 if (ac->ac_nscount == ASR_MAXNS) 601 return; 602 if (n != 2) 603 return; 604 if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1])) 605 return; 606 if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL) 607 return; 608 memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len); 609 ac->ac_nscount += 1; 610 611 } else if (!strcmp(tok[0], "domain")) { 612 if (n != 2) 613 return; 614 if (ac->ac_domain) 615 return; 616 ac->ac_domain = strdup(tok[1]); 617 618 } else if (!strcmp(tok[0], "lookup")) { 619 /* ensure that each lookup is only given once */ 620 for (i = 1; i < n; i++) 621 for (j = i + 1; j < n; j++) 622 if (!strcmp(tok[i], tok[j])) 623 return; 624 ac->ac_dbcount = 0; 625 for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) { 626 if (!strcmp(tok[i], "yp")) 627 ac->ac_db[ac->ac_dbcount++] = ASR_DB_YP; 628 else if (!strcmp(tok[i], "bind")) 629 ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS; 630 else if (!strcmp(tok[i], "file")) 631 ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE; 632 } 633 } else if (!strcmp(tok[0], "search")) { 634 /* resolv.conf says the last line wins */ 635 for (i = 0; i < ASR_MAXDOM; i++) 636 free(ac->ac_dom[i]); 637 ac->ac_domcount = 0; 638 for (i = 1; i < n; i++) 639 asr_ctx_add_searchdomain(ac, tok[i]); 640 641 } else if (!strcmp(tok[0], "family")) { 642 if (n == 1 || n > 3) 643 return; 644 for (i = 1; i < n; i++) 645 if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) 646 return; 647 for (i = 1; i < n; i++) 648 ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \ 649 AF_INET6 : AF_INET; 650 ac->ac_family[i - 1] = -1; 651 652 } else if (!strcmp(tok[0], "options")) { 653 for (i = 1; i < n; i++) { 654 if (!strcmp(tok[i], "tcp")) 655 ac->ac_options |= RES_USEVC; 656 else if ((!strncmp(tok[i], "ndots:", 6))) { 657 e = NULL; 658 d = strtonum(tok[i] + 6, 1, 16, &e); 659 if (e == NULL) 660 ac->ac_ndots = d; 661 } 662 } 663 } 664 } 665 666 /* 667 * Setup an async context with the config specified in the string "str". 668 */ 669 static int 670 asr_ctx_from_string(struct asr_ctx *ac, const char *str) 671 { 672 char buf[512], *ch; 673 674 asr_ctx_parse(ac, str); 675 676 if (ac->ac_dbcount == 0) { 677 /* No lookup directive */ 678 asr_ctx_parse(ac, DEFAULT_LOOKUP); 679 } 680 681 if (ac->ac_nscount == 0) 682 asr_ctx_parse(ac, "nameserver 127.0.0.1"); 683 684 if (ac->ac_domain == NULL) 685 if (gethostname(buf, sizeof buf) == 0) { 686 ch = strchr(buf, '.'); 687 if (ch) 688 ac->ac_domain = strdup(ch + 1); 689 else /* Assume root. see resolv.conf(5) */ 690 ac->ac_domain = strdup(""); 691 } 692 693 /* If no search domain was specified, use the local subdomains */ 694 if (ac->ac_domcount == 0) 695 for (ch = ac->ac_domain; ch; ) { 696 asr_ctx_add_searchdomain(ac, ch); 697 ch = strchr(ch, '.'); 698 if (ch && asr_ndots(++ch) == 0) 699 break; 700 } 701 702 return (0); 703 } 704 705 /* 706 * Setup the "ac" async context from the file at location "path". 707 */ 708 static int 709 asr_ctx_from_file(struct asr_ctx *ac, const char *path) 710 { 711 FILE *cf; 712 char buf[4096]; 713 ssize_t r; 714 715 cf = fopen(path, "r"); 716 if (cf == NULL) 717 return (-1); 718 719 r = fread(buf, 1, sizeof buf - 1, cf); 720 if (feof(cf) == 0) { 721 DPRINT("asr: config file too long: \"%s\"\n", path); 722 r = -1; 723 } 724 fclose(cf); 725 if (r == -1) 726 return (-1); 727 buf[r] = '\0'; 728 729 return asr_ctx_from_string(ac, buf); 730 } 731 732 /* 733 * Parse lines in the configuration string. For each one, split it into 734 * tokens and pass them to "pass0" for processing. 735 */ 736 static int 737 asr_ctx_parse(struct asr_ctx *ac, const char *str) 738 { 739 size_t len; 740 const char *line; 741 char buf[1024]; 742 char *tok[10]; 743 int ntok; 744 745 line = str; 746 while (*line) { 747 len = strcspn(line, "\n\0"); 748 if (len < sizeof buf) { 749 memmove(buf, line, len); 750 buf[len] = '\0'; 751 } else 752 buf[0] = '\0'; 753 line += len; 754 if (*line == '\n') 755 line++; 756 buf[strcspn(buf, ";#")] = '\0'; 757 if ((ntok = strsplit(buf, tok, 10)) == 0) 758 continue; 759 760 pass0(tok, ntok, ac); 761 } 762 763 return (0); 764 } 765 766 #if ASR_OPT_ENVOPTS 767 /* 768 * Check for environment variables altering the configuration as described 769 * in resolv.conf(5). Altough not documented there, this feature is disabled 770 * for setuid/setgid programs. 771 */ 772 static void 773 asr_ctx_envopts(struct asr_ctx *ac) 774 { 775 char buf[4096], *e; 776 size_t s; 777 778 if (issetugid()) { 779 ac->ac_options |= RES_NOALIASES; 780 return; 781 } 782 783 if ((e = getenv("RES_OPTIONS")) != NULL) { 784 strlcpy(buf, "options ", sizeof buf); 785 strlcat(buf, e, sizeof buf); 786 s = strlcat(buf, "\n", sizeof buf); 787 s = strlcat(buf, "\n", sizeof buf); 788 if (s < sizeof buf) 789 asr_ctx_parse(ac, buf); 790 } 791 792 if ((e = getenv("LOCALDOMAIN")) != NULL) { 793 strlcpy(buf, "search ", sizeof buf); 794 strlcat(buf, e, sizeof buf); 795 s = strlcat(buf, "\n", sizeof buf); 796 if (s < sizeof buf) 797 asr_ctx_parse(ac, buf); 798 } 799 } 800 #endif 801 802 /* 803 * Parse a resolv.conf(5) nameserver string into a sockaddr. 804 */ 805 static int 806 asr_parse_nameserver(struct sockaddr *sa, const char *s) 807 { 808 const char *estr; 809 char buf[256]; 810 char *port = NULL; 811 in_port_t portno = 53; 812 813 if (*s == '[') { 814 strlcpy(buf, s + 1, sizeof buf); 815 s = buf; 816 port = strchr(buf, ']'); 817 if (port == NULL) 818 return (-1); 819 *port++ = '\0'; 820 if (*port != ':') 821 return (-1); 822 port++; 823 } 824 825 if (port) { 826 portno = strtonum(port, 1, USHRT_MAX, &estr); 827 if (estr) 828 return (-1); 829 } 830 831 if (sockaddr_from_str(sa, PF_UNSPEC, s) == -1) 832 return (-1); 833 834 if (sa->sa_family == PF_INET) 835 ((struct sockaddr_in *)sa)->sin_port = htons(portno); 836 else if (sa->sa_family == PF_INET6) 837 ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); 838 839 return (0); 840 } 841 842 /* 843 * Turn a (uncompressed) DNS domain name into a regular nul-terminated string 844 * where labels are separated by dots. The result is put into the "buf" buffer, 845 * truncated if it exceeds "max" chars. The function returns "buf". 846 */ 847 char * 848 asr_strdname(const char *_dname, char *buf, size_t max) 849 { 850 const unsigned char *dname = _dname; 851 char *res; 852 size_t left, n, count; 853 854 if (_dname[0] == 0) { 855 strlcpy(buf, ".", max); 856 return buf; 857 } 858 859 res = buf; 860 left = max - 1; 861 for (n = 0; dname[0] && left; n += dname[0]) { 862 count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); 863 memmove(buf, dname + 1, count); 864 dname += dname[0] + 1; 865 left -= count; 866 buf += count; 867 if (left) { 868 left -= 1; 869 *buf++ = '.'; 870 } 871 } 872 buf[0] = 0; 873 874 return (res); 875 } 876 877 /* 878 * Read and split the next line from the given namedb file. 879 * Return -1 on error, or put the result in the "tokens" array of 880 * size "ntoken" and returns the number of token on the line. 881 */ 882 int 883 asr_parse_namedb_line(FILE *file, char **tokens, int ntoken) 884 { 885 size_t len; 886 char *buf; 887 int ntok; 888 889 again: 890 if ((buf = fgetln(file, &len)) == NULL) 891 return (-1); 892 893 if (buf[len - 1] == '\n') 894 len--; 895 896 buf[len] = '\0'; 897 buf[strcspn(buf, "#")] = '\0'; 898 if ((ntok = strsplit(buf, tokens, ntoken)) == 0) 899 goto again; 900 901 return (ntok); 902 } 903 904 /* 905 * Update the async context so that it uses the next configured DB. 906 * Return 0 on success, or -1 if no more DBs is available. 907 */ 908 int 909 asr_iter_db(struct async *as) 910 { 911 if (as->as_db_idx >= as->as_ctx->ac_dbcount) { 912 DPRINT("asr_iter_db: done\n"); 913 return (-1); 914 } 915 916 as->as_db_idx += 1; 917 as->as_ns_idx = 0; 918 DPRINT("asr_iter_db: %i\n", as->as_db_idx); 919 920 return (0); 921 } 922 923 /* 924 * Set the async context nameserver index to the next nameserver of the 925 * currently used DB (assuming it is DNS), cycling over the list until the 926 * maximum retry counter is reached. Return 0 on success, or -1 if all 927 * nameservers were used. 928 */ 929 int 930 asr_iter_ns(struct async *as) 931 { 932 for (;;) { 933 if (as->as_ns_cycles >= as->as_ctx->ac_nsretries) 934 return (-1); 935 936 as->as_ns_idx += 1; 937 if (as->as_ns_idx <= as->as_ctx->ac_nscount) 938 break; 939 as->as_ns_idx = 0; 940 as->as_ns_cycles++; 941 DPRINT("asr: asr_iter_ns(): cycle %i\n", as->as_ns_cycles); 942 } 943 944 as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as_ns_cycles); 945 if (as->as_ns_cycles > 0) 946 as->as_timeout /= as->as_ctx->ac_nscount; 947 if (as->as_timeout < 1000) 948 as->as_timeout = 1000; 949 950 return (0); 951 } 952 953 enum { 954 DOM_INIT, 955 DOM_DOMAIN, 956 DOM_DONE 957 }; 958 959 /* 960 * Implement the search domain strategy. 961 * 962 * This function works as a generator that constructs complete domains in 963 * buffer "buf" of size "len" for the given host name "name", according to the 964 * search rules defined by the resolving context. It is supposed to be called 965 * multiple times (with the same name) to generate the next possible domain 966 * name, if any. 967 * 968 * It returns -1 if all possibilities have been exhausted, 0 if there was an 969 * error generating the next name, or the resulting name length. 970 */ 971 int 972 asr_iter_domain(struct async *as, const char *name, char * buf, size_t len) 973 { 974 #if ASR_OPT_HOSTALIASES 975 char *alias; 976 #endif 977 978 switch (as->as_dom_step) { 979 980 case DOM_INIT: 981 /* First call */ 982 983 /* 984 * If "name" is an FQDN, that's the only result and we 985 * don't try anything else. 986 */ 987 if (strlen(name) && name[strlen(name) - 1] == '.') { 988 DPRINT("asr: asr_iter_domain(\"%s\") fqdn\n", name); 989 as->as_dom_flags |= ASYNC_DOM_FQDN; 990 as->as_dom_step = DOM_DONE; 991 return (asr_domcat(name, NULL, buf, len)); 992 } 993 994 #if ASR_OPT_HOSTALIASES 995 /* 996 * If "name" has no dots, it might be an alias. If so, 997 * That's also the only result. 998 */ 999 if ((as->as_ctx->ac_options & RES_NOALIASES) == 0 && 1000 asr_ndots(name) == 0 && 1001 (alias = asr_hostalias(name, buf, len)) != NULL) { 1002 DPRINT("asr: asr_iter_domain(\"%s\") is alias \"%s\"\n", 1003 name, alias); 1004 as->as_dom_flags |= ASYNC_DOM_HOSTALIAS; 1005 as->as_dom_step = DOM_DONE; 1006 return (asr_domcat(alias, NULL, buf, len)); 1007 } 1008 #endif 1009 1010 /* 1011 * Otherwise, we iterate through the specified search domains. 1012 */ 1013 as->as_dom_step = DOM_DOMAIN; 1014 as->as_dom_idx = 0; 1015 1016 /* 1017 * If "name" as enough dots, use it as-is first, as indicated 1018 * in resolv.conf(5). 1019 */ 1020 if ((asr_ndots(name)) >= as->as_ctx->ac_ndots) { 1021 DPRINT("asr: asr_iter_domain(\"%s\") ndots\n", name); 1022 as->as_dom_flags |= ASYNC_DOM_NDOTS; 1023 if (strlcpy(buf, name, len) >= len) 1024 return (0); 1025 return (strlen(buf)); 1026 } 1027 /* Otherwise, starts using the search domains */ 1028 /* FALLTHROUGH */ 1029 1030 case DOM_DOMAIN: 1031 if (as->as_dom_idx < as->as_ctx->ac_domcount) { 1032 DPRINT("asr: asr_iter_domain(\"%s\") domain \"%s\"\n", 1033 name, as->as_ctx->ac_dom[as->as_dom_idx]); 1034 as->as_dom_flags |= ASYNC_DOM_DOMAIN; 1035 return (asr_domcat(name, 1036 as->as_ctx->ac_dom[as->as_dom_idx++], buf, len)); 1037 } 1038 1039 /* No more domain to try. */ 1040 1041 as->as_dom_step = DOM_DONE; 1042 1043 /* 1044 * If the name was not tried as an absolute name before, 1045 * do it now. 1046 */ 1047 if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) { 1048 DPRINT("asr: asr_iter_domain(\"%s\") as is\n", name); 1049 as->as_dom_flags |= ASYNC_DOM_ASIS; 1050 if (strlcpy(buf, name, len) >= len) 1051 return (0); 1052 return (strlen(buf)); 1053 } 1054 /* Otherwise, we are done. */ 1055 1056 case DOM_DONE: 1057 default: 1058 DPRINT("asr: asr_iter_domain(\"%s\") done\n", name); 1059 return (-1); 1060 } 1061 } 1062 1063 #if ASR_OPT_HOSTALIASES 1064 /* 1065 * Check if the hostname "name" is a user-defined alias as per hostname(7). 1066 * If so, copies the result in the buffer "abuf" of size "abufsz" and 1067 * return "abuf". Otherwise return NULL. 1068 */ 1069 static char * 1070 asr_hostalias(const char *name, char *abuf, size_t abufsz) 1071 { 1072 FILE *fp; 1073 size_t len; 1074 char *file, *buf, *tokens[2]; 1075 int ntok; 1076 1077 file = getenv("HOSTALIASES"); 1078 if (file == NULL || issetugid() != 0 || (fp = fopen(file, "r")) == NULL) 1079 return (NULL); 1080 1081 DPRINT("asr: looking up aliases in \"%s\"\n", file); 1082 1083 while ((buf = fgetln(fp, &len)) != NULL) { 1084 if (buf[len - 1] == '\n') 1085 len--; 1086 buf[len] = '\0'; 1087 if ((ntok = strsplit(buf, tokens, 2)) != 2) 1088 continue; 1089 if (!strcasecmp(tokens[0], name)) { 1090 if (strlcpy(abuf, tokens[1], abufsz) > abufsz) 1091 continue; 1092 DPRINT("asr: found alias \"%s\"\n", abuf); 1093 fclose(fp); 1094 return (abuf); 1095 } 1096 } 1097 1098 fclose(fp); 1099 return (NULL); 1100 } 1101 #endif 1102