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