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