1 /* $OpenBSD: asr.c,v 1.41 2015/09/14 11:52:49 guenther 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/socket.h> 20 #include <sys/stat.h> 21 #include <netinet/in.h> 22 #include <arpa/inet.h> 23 #include <arpa/nameser.h> 24 #include <netdb.h> 25 26 #include <asr.h> 27 #include <err.h> 28 #include <errno.h> 29 #include <fcntl.h> 30 #include <resolv.h> 31 #include <poll.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 #include <limits.h> 37 38 #include "asr_private.h" 39 40 #ifndef ASR_OPT_THREADSAFE 41 #define ASR_OPT_THREADSAFE 1 42 #endif 43 #ifndef ASR_OPT_HOSTALIASES 44 #define ASR_OPT_HOSTALIASES 1 45 #endif 46 #ifndef ASR_OPT_ENVOPTS 47 #define ASR_OPT_ENVOPTS 1 48 #endif 49 #ifndef ASR_OPT_RELOADCONF 50 #define ASR_OPT_RELOADCONF 1 51 #endif 52 #ifndef ASR_OPT_ALTCONF 53 #define ASR_OPT_ALTCONF 1 54 #endif 55 56 #if ASR_OPT_THREADSAFE 57 #include "thread_private.h" 58 #endif 59 60 #define DEFAULT_CONFFILE _PATH_RESCONF 61 #define DEFAULT_CONF "lookup file\n" 62 #define DEFAULT_LOOKUP "lookup bind file" 63 64 #define RELOAD_DELAY 15 /* seconds */ 65 66 static void asr_check_reload(struct asr *); 67 static struct asr_ctx *asr_ctx_create(void); 68 static void asr_ctx_ref(struct asr_ctx *); 69 static void asr_ctx_free(struct asr_ctx *); 70 static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *); 71 static int asr_ctx_from_file(struct asr_ctx *, const char *); 72 static int asr_ctx_from_string(struct asr_ctx *, const char *); 73 static int asr_ctx_parse(struct asr_ctx *, const char *); 74 static int asr_parse_nameserver(struct sockaddr *, const char *); 75 static int asr_ndots(const char *); 76 static void pass0(char **, int, struct asr_ctx *); 77 static int strsplit(char *, char **, int); 78 #if ASR_OPT_ENVOPTS 79 static void asr_ctx_envopts(struct asr_ctx *); 80 #endif 81 #if ASR_OPT_THREADSAFE 82 static void *__THREAD_NAME(_asr); 83 #else 84 # define _THREAD_PRIVATE(a, b, c) (c) 85 #endif 86 87 static struct asr *_asr = NULL; 88 89 /* Allocate and configure an async "resolver". */ 90 void * 91 _asr_resolver(const char *conf) 92 { 93 static int init = 0; 94 struct asr *asr; 95 96 if (init == 0) { 97 #ifdef DEBUG 98 if (getenv("ASR_DEBUG")) 99 asr_debug = stderr; 100 #endif 101 init = 1; 102 } 103 104 if ((asr = calloc(1, sizeof(*asr))) == NULL) 105 goto fail; 106 107 #if ASR_OPT_ALTCONF 108 /* If not setuid/setgid, allow to use an alternate config. */ 109 if (conf == NULL && !issetugid()) 110 conf = getenv("ASR_CONFIG"); 111 #endif 112 113 if (conf == NULL) 114 conf = DEFAULT_CONFFILE; 115 116 if (conf[0] == '!') { 117 /* Use the rest of the string as config file */ 118 if ((asr->a_ctx = asr_ctx_create()) == NULL) 119 goto fail; 120 if (asr_ctx_from_string(asr->a_ctx, conf + 1) == -1) 121 goto fail; 122 } else { 123 /* Use the given config file */ 124 asr->a_path = strdup(conf); 125 if (asr->a_path == NULL) 126 goto fail; 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->a_path); 149 free(asr); 150 } 151 152 return (NULL); 153 } 154 155 /* 156 * Free the "asr" async resolver (or the thread-local resolver if NULL). 157 * Drop the reference to the current context. 158 */ 159 void 160 _asr_resolver_done(void *arg) 161 { 162 struct asr *asr = arg; 163 struct asr **priv; 164 165 if (asr == NULL) { 166 priv = _THREAD_PRIVATE(_asr, _asr, &_asr); 167 if (*priv == NULL) 168 return; 169 asr = *priv; 170 *priv = NULL; 171 } 172 173 _asr_ctx_unref(asr->a_ctx); 174 free(asr->a_path); 175 free(asr); 176 } 177 178 /* 179 * Cancel an async query. 180 */ 181 void 182 asr_abort(struct asr_query *as) 183 { 184 _asr_async_free(as); 185 } 186 187 /* 188 * Resume the "as" async query resolution. Return one of ASYNC_COND, 189 * or ASYNC_DONE and put query-specific return values in the user-allocated 190 * memory at "ar". 191 */ 192 int 193 asr_run(struct asr_query *as, struct asr_result *ar) 194 { 195 int r, saved_errno = errno; 196 197 DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar, 198 asr_querystr(as->as_type), as->as_ctx); 199 r = as->as_run(as, ar); 200 201 DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, asr_transitionstr(r)); 202 #ifdef DEBUG 203 if (r == ASYNC_COND) 204 #endif 205 DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout); 206 DPRINT("\n"); 207 if (r == ASYNC_DONE) 208 _asr_async_free(as); 209 210 errno = saved_errno; 211 212 return (r); 213 } 214 DEF_WEAK(asr_run); 215 216 /* 217 * Same as above, but run in a loop that handles the fd conditions result. 218 */ 219 int 220 asr_run_sync(struct asr_query *as, struct asr_result *ar) 221 { 222 struct pollfd fds[1]; 223 int r, saved_errno = errno; 224 225 while ((r = asr_run(as, ar)) == ASYNC_COND) { 226 fds[0].fd = ar->ar_fd; 227 fds[0].events = (ar->ar_cond == ASR_WANT_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 asr_run() 234 * catch the failure. 235 */ 236 } 237 238 errno = saved_errno; 239 240 return (r); 241 } 242 DEF_WEAK(asr_run_sync); 243 244 /* 245 * Create a new async request of the given "type" on the async context "ac". 246 * Take a reference on it so it does not gets deleted while the async query 247 * is running. 248 */ 249 struct asr_query * 250 _asr_async_new(struct asr_ctx *ac, int type) 251 { 252 struct asr_query *as; 253 254 DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type, 255 ac ? ac->ac_refcount : 0); 256 if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL) 257 return (NULL); 258 259 ac->ac_refcount += 1; 260 as->as_ctx = ac; 261 as->as_fd = -1; 262 as->as_type = type; 263 as->as_state = ASR_STATE_INIT; 264 265 return (as); 266 } 267 268 /* 269 * Free an async query and unref the associated context. 270 */ 271 void 272 _asr_async_free(struct asr_query *as) 273 { 274 DPRINT("asr: asr_async_free(%p)\n", as); 275 switch (as->as_type) { 276 case ASR_SEND: 277 if (as->as_fd != -1) 278 close(as->as_fd); 279 if (as->as.dns.obuf && !(as->as.dns.flags & ASYNC_EXTOBUF)) 280 free(as->as.dns.obuf); 281 if (as->as.dns.ibuf) 282 free(as->as.dns.ibuf); 283 if (as->as.dns.dname) 284 free(as->as.dns.dname); 285 break; 286 287 case ASR_SEARCH: 288 if (as->as.search.subq) 289 _asr_async_free(as->as.search.subq); 290 if (as->as.search.name) 291 free(as->as.search.name); 292 break; 293 294 case ASR_GETRRSETBYNAME: 295 if (as->as.rrset.subq) 296 _asr_async_free(as->as.rrset.subq); 297 if (as->as.rrset.name) 298 free(as->as.rrset.name); 299 break; 300 301 case ASR_GETHOSTBYNAME: 302 case ASR_GETHOSTBYADDR: 303 if (as->as.hostnamadr.subq) 304 _asr_async_free(as->as.hostnamadr.subq); 305 if (as->as.hostnamadr.name) 306 free(as->as.hostnamadr.name); 307 break; 308 309 case ASR_GETNETBYNAME: 310 case ASR_GETNETBYADDR: 311 if (as->as.netnamadr.subq) 312 _asr_async_free(as->as.netnamadr.subq); 313 if (as->as.netnamadr.name) 314 free(as->as.netnamadr.name); 315 break; 316 317 case ASR_GETADDRINFO: 318 if (as->as.ai.subq) 319 _asr_async_free(as->as.ai.subq); 320 if (as->as.ai.aifirst) 321 freeaddrinfo(as->as.ai.aifirst); 322 if (as->as.ai.hostname) 323 free(as->as.ai.hostname); 324 if (as->as.ai.servname) 325 free(as->as.ai.servname); 326 if (as->as.ai.fqdn) 327 free(as->as.ai.fqdn); 328 break; 329 330 case ASR_GETNAMEINFO: 331 if (as->as.ni.subq) 332 _asr_async_free(as->as.ni.subq); 333 break; 334 } 335 336 _asr_ctx_unref(as->as_ctx); 337 free(as); 338 } 339 340 /* 341 * Get a context from the given resolver. This takes a new reference to 342 * the returned context, which *must* be explicitely dropped when done 343 * using this context. 344 */ 345 struct asr_ctx * 346 _asr_use_resolver(void *arg) 347 { 348 struct asr *asr = arg; 349 struct asr **priv; 350 351 if (asr == NULL) { 352 DPRINT("using thread-local resolver\n"); 353 priv = _THREAD_PRIVATE(_asr, _asr, &_asr); 354 if (*priv == NULL) { 355 DPRINT("setting up thread-local resolver\n"); 356 *priv = _asr_resolver(NULL); 357 } 358 asr = *priv; 359 } 360 if (asr != NULL) { 361 asr_check_reload(asr); 362 asr_ctx_ref(asr->a_ctx); 363 return (asr->a_ctx); 364 } 365 return (NULL); 366 } 367 368 static void 369 asr_ctx_ref(struct asr_ctx *ac) 370 { 371 DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount); 372 ac->ac_refcount += 1; 373 } 374 375 /* 376 * Drop a reference to an async context, freeing it if the reference 377 * count drops to 0. 378 */ 379 void 380 _asr_ctx_unref(struct asr_ctx *ac) 381 { 382 DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac, 383 ac ? ac->ac_refcount : 0); 384 if (ac == NULL) 385 return; 386 if (--ac->ac_refcount) 387 return; 388 389 asr_ctx_free(ac); 390 } 391 392 static void 393 asr_ctx_free(struct asr_ctx *ac) 394 { 395 int i; 396 397 if (ac->ac_domain) 398 free(ac->ac_domain); 399 for (i = 0; i < ASR_MAXNS; i++) 400 free(ac->ac_ns[i]); 401 for (i = 0; i < ASR_MAXDOM; i++) 402 free(ac->ac_dom[i]); 403 404 free(ac); 405 } 406 407 /* 408 * Reload the configuration file if it has changed on disk. 409 */ 410 static void 411 asr_check_reload(struct asr *asr) 412 { 413 struct asr_ctx *ac; 414 #if ASR_OPT_RELOADCONF 415 struct stat st; 416 struct timespec ts; 417 pid_t pid; 418 #endif 419 420 if (asr->a_path == NULL) 421 return; 422 423 #if ASR_OPT_RELOADCONF 424 425 pid = getpid(); 426 if (pid != asr->a_pid) { 427 asr->a_pid = pid; 428 asr->a_rtime = 0; 429 } 430 431 if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) 432 return; 433 434 if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0) 435 return; 436 asr->a_rtime = ts.tv_sec; 437 438 DPRINT("asr: checking for update of \"%s\"\n", asr->a_path); 439 if (stat(asr->a_path, &st) == -1 || 440 asr->a_mtime == st.st_mtime || 441 (ac = asr_ctx_create()) == NULL) 442 return; 443 asr->a_mtime = st.st_mtime; 444 #else 445 if ((ac = asr_ctx_create()) == NULL) 446 return; 447 #endif 448 449 DPRINT("asr: reloading config file\n"); 450 if (asr_ctx_from_file(ac, asr->a_path) == -1) { 451 asr_ctx_free(ac); 452 return; 453 } 454 455 #if ASR_OPT_ENVOPTS 456 asr_ctx_envopts(ac); 457 #endif 458 if (asr->a_ctx) 459 _asr_ctx_unref(asr->a_ctx); 460 asr->a_ctx = ac; 461 } 462 463 /* 464 * Construct a fully-qualified domain name for the given name and domain. 465 * If "name" ends with a '.' it is considered as a FQDN by itself. 466 * Otherwise, the domain, which must be a FQDN, is appended to "name" (it 467 * may have a leading dot which would be ignored). If the domain is null, 468 * then "." is used. Return the length of the constructed FQDN or (0) on 469 * error. 470 */ 471 size_t 472 _asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen) 473 { 474 size_t len; 475 476 if (domain == NULL) 477 domain = "."; 478 else if ((len = strlen(domain)) == 0) 479 return (0); 480 else if (domain[len -1] != '.') 481 return (0); 482 483 len = strlen(name); 484 if (len == 0) { 485 if (strlcpy(buf, domain, buflen) >= buflen) 486 return (0); 487 } else if (name[len - 1] != '.') { 488 if (domain[0] == '.') 489 domain += 1; 490 if (strlcpy(buf, name, buflen) >= buflen || 491 strlcat(buf, ".", buflen) >= buflen || 492 strlcat(buf, domain, buflen) >= buflen) 493 return (0); 494 } else { 495 if (strlcpy(buf, name, buflen) >= buflen) 496 return (0); 497 } 498 499 return (strlen(buf)); 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_nscount = 0; 536 ac->ac_nstimeout = 5; 537 ac->ac_nsretries = 4; 538 539 return (ac); 540 } 541 542 /* 543 * Add a search domain to the async context. 544 */ 545 static int 546 asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain) 547 { 548 char buf[MAXDNAME]; 549 550 if (ac->ac_domcount == ASR_MAXDOM) 551 return (-1); 552 553 if (_asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0) 554 return (-1); 555 556 if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL) 557 return (0); 558 559 ac->ac_domcount += 1; 560 561 return (1); 562 } 563 564 static int 565 strsplit(char *line, char **tokens, int ntokens) 566 { 567 int ntok; 568 char *cp, **tp; 569 570 for (cp = line, tp = tokens, ntok = 0; 571 ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; ) 572 if (**tp != '\0') { 573 tp++; 574 ntok++; 575 } 576 577 return (ntok); 578 } 579 580 /* 581 * Pass on a split config line. 582 */ 583 static void 584 pass0(char **tok, int n, struct asr_ctx *ac) 585 { 586 int i, j, d; 587 const char *e; 588 struct sockaddr_storage ss; 589 590 if (!strcmp(tok[0], "nameserver")) { 591 if (ac->ac_nscount == ASR_MAXNS) 592 return; 593 if (n != 2) 594 return; 595 if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1])) 596 return; 597 if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL) 598 return; 599 memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len); 600 ac->ac_nscount += 1; 601 602 } else if (!strcmp(tok[0], "domain")) { 603 if (n != 2) 604 return; 605 if (ac->ac_domain) 606 return; 607 ac->ac_domain = strdup(tok[1]); 608 609 } else if (!strcmp(tok[0], "lookup")) { 610 /* ensure that each lookup is only given once */ 611 for (i = 1; i < n; i++) 612 for (j = i + 1; j < n; j++) 613 if (!strcmp(tok[i], tok[j])) 614 return; 615 ac->ac_dbcount = 0; 616 for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) { 617 if (!strcmp(tok[i], "yp")) 618 ac->ac_db[ac->ac_dbcount++] = ASR_DB_YP; 619 else if (!strcmp(tok[i], "bind")) 620 ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS; 621 else if (!strcmp(tok[i], "file")) 622 ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE; 623 } 624 } else if (!strcmp(tok[0], "search")) { 625 /* resolv.conf says the last line wins */ 626 for (i = 0; i < ASR_MAXDOM; i++) 627 free(ac->ac_dom[i]); 628 ac->ac_domcount = 0; 629 for (i = 1; i < n; i++) 630 asr_ctx_add_searchdomain(ac, tok[i]); 631 632 } else if (!strcmp(tok[0], "family")) { 633 if (n == 1 || n > 3) 634 return; 635 for (i = 1; i < n; i++) 636 if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) 637 return; 638 for (i = 1; i < n; i++) 639 ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \ 640 AF_INET6 : AF_INET; 641 ac->ac_family[i - 1] = -1; 642 643 } else if (!strcmp(tok[0], "options")) { 644 for (i = 1; i < n; i++) { 645 if (!strcmp(tok[i], "tcp")) 646 ac->ac_options |= RES_USEVC; 647 else if ((!strncmp(tok[i], "ndots:", 6))) { 648 e = NULL; 649 d = strtonum(tok[i] + 6, 1, 16, &e); 650 if (e == NULL) 651 ac->ac_ndots = d; 652 } 653 } 654 } 655 } 656 657 /* 658 * Setup an async context with the config specified in the string "str". 659 */ 660 static int 661 asr_ctx_from_string(struct asr_ctx *ac, const char *str) 662 { 663 char buf[512], *ch; 664 665 asr_ctx_parse(ac, str); 666 667 if (ac->ac_dbcount == 0) { 668 /* No lookup directive */ 669 asr_ctx_parse(ac, DEFAULT_LOOKUP); 670 } 671 672 if (ac->ac_nscount == 0) 673 asr_ctx_parse(ac, "nameserver 127.0.0.1"); 674 675 if (ac->ac_domain == NULL) 676 if (gethostname(buf, sizeof buf) == 0) { 677 ch = strchr(buf, '.'); 678 if (ch) 679 ac->ac_domain = strdup(ch + 1); 680 else /* Assume root. see resolv.conf(5) */ 681 ac->ac_domain = strdup(""); 682 } 683 684 /* If no search domain was specified, use the local subdomains */ 685 if (ac->ac_domcount == 0) 686 for (ch = ac->ac_domain; ch; ) { 687 asr_ctx_add_searchdomain(ac, ch); 688 ch = strchr(ch, '.'); 689 if (ch && asr_ndots(++ch) == 0) 690 break; 691 } 692 693 return (0); 694 } 695 696 /* 697 * Setup the "ac" async context from the file at location "path". 698 */ 699 static int 700 asr_ctx_from_file(struct asr_ctx *ac, const char *path) 701 { 702 FILE *cf; 703 char buf[4096]; 704 ssize_t r; 705 706 cf = fopen(path, "re"); 707 if (cf == NULL) 708 return (-1); 709 710 r = fread(buf, 1, sizeof buf - 1, cf); 711 if (feof(cf) == 0) { 712 DPRINT("asr: config file too long: \"%s\"\n", path); 713 r = -1; 714 } 715 fclose(cf); 716 if (r == -1) 717 return (-1); 718 buf[r] = '\0'; 719 720 return asr_ctx_from_string(ac, buf); 721 } 722 723 /* 724 * Parse lines in the configuration string. For each one, split it into 725 * tokens and pass them to "pass0" for processing. 726 */ 727 static int 728 asr_ctx_parse(struct asr_ctx *ac, const char *str) 729 { 730 size_t len; 731 const char *line; 732 char buf[1024]; 733 char *tok[10]; 734 int ntok; 735 736 line = str; 737 while (*line) { 738 len = strcspn(line, "\n\0"); 739 if (len < sizeof buf) { 740 memmove(buf, line, len); 741 buf[len] = '\0'; 742 } else 743 buf[0] = '\0'; 744 line += len; 745 if (*line == '\n') 746 line++; 747 buf[strcspn(buf, ";#")] = '\0'; 748 if ((ntok = strsplit(buf, tok, 10)) == 0) 749 continue; 750 751 pass0(tok, ntok, ac); 752 } 753 754 return (0); 755 } 756 757 #if ASR_OPT_ENVOPTS 758 /* 759 * Check for environment variables altering the configuration as described 760 * in resolv.conf(5). Altough not documented there, this feature is disabled 761 * for setuid/setgid programs. 762 */ 763 static void 764 asr_ctx_envopts(struct asr_ctx *ac) 765 { 766 char buf[4096], *e; 767 size_t s; 768 769 if (issetugid()) { 770 ac->ac_options |= RES_NOALIASES; 771 return; 772 } 773 774 if ((e = getenv("RES_OPTIONS")) != NULL) { 775 strlcpy(buf, "options ", sizeof buf); 776 strlcat(buf, e, sizeof buf); 777 s = strlcat(buf, "\n", sizeof buf); 778 s = strlcat(buf, "\n", sizeof buf); 779 if (s < sizeof buf) 780 asr_ctx_parse(ac, buf); 781 } 782 783 if ((e = getenv("LOCALDOMAIN")) != NULL) { 784 strlcpy(buf, "search ", sizeof buf); 785 strlcat(buf, e, sizeof buf); 786 s = strlcat(buf, "\n", sizeof buf); 787 if (s < sizeof buf) 788 asr_ctx_parse(ac, buf); 789 } 790 } 791 #endif 792 793 /* 794 * Parse a resolv.conf(5) nameserver string into a sockaddr. 795 */ 796 static int 797 asr_parse_nameserver(struct sockaddr *sa, const char *s) 798 { 799 const char *estr; 800 char buf[256]; 801 char *port = NULL; 802 in_port_t portno = 53; 803 804 if (*s == '[') { 805 strlcpy(buf, s + 1, sizeof buf); 806 s = buf; 807 port = strchr(buf, ']'); 808 if (port == NULL) 809 return (-1); 810 *port++ = '\0'; 811 if (*port != ':') 812 return (-1); 813 port++; 814 } 815 816 if (port) { 817 portno = strtonum(port, 1, USHRT_MAX, &estr); 818 if (estr) 819 return (-1); 820 } 821 822 if (_asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1) 823 return (-1); 824 825 if (sa->sa_family == PF_INET) 826 ((struct sockaddr_in *)sa)->sin_port = htons(portno); 827 else if (sa->sa_family == PF_INET6) 828 ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); 829 830 return (0); 831 } 832 833 /* 834 * Turn a (uncompressed) DNS domain name into a regular nul-terminated string 835 * where labels are separated by dots. The result is put into the "buf" buffer, 836 * truncated if it exceeds "max" chars. The function returns "buf". 837 */ 838 char * 839 _asr_strdname(const char *_dname, char *buf, size_t max) 840 { 841 const unsigned char *dname = _dname; 842 char *res; 843 size_t left, n, count; 844 845 if (_dname[0] == 0) { 846 strlcpy(buf, ".", max); 847 return buf; 848 } 849 850 res = buf; 851 left = max - 1; 852 for (n = 0; dname[0] && left; n += dname[0]) { 853 count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); 854 memmove(buf, dname + 1, count); 855 dname += dname[0] + 1; 856 left -= count; 857 buf += count; 858 if (left) { 859 left -= 1; 860 *buf++ = '.'; 861 } 862 } 863 buf[0] = 0; 864 865 return (res); 866 } 867 868 /* 869 * Read and split the next line from the given namedb file. 870 * Return -1 on error, or put the result in the "tokens" array of 871 * size "ntoken" and returns the number of token on the line. 872 */ 873 int 874 _asr_parse_namedb_line(FILE *file, char **tokens, int ntoken, char *lbuf, size_t sz) 875 { 876 size_t len; 877 char *buf; 878 int ntok; 879 880 again: 881 if ((buf = fgetln(file, &len)) == NULL) 882 return (-1); 883 884 if (len >= sz) 885 goto again; 886 887 if (buf[len - 1] == '\n') 888 len--; 889 else { 890 memcpy(lbuf, buf, len); 891 buf = lbuf; 892 } 893 894 buf[len] = '\0'; 895 buf[strcspn(buf, "#")] = '\0'; 896 if ((ntok = strsplit(buf, tokens, ntoken)) == 0) 897 goto again; 898 899 return (ntok); 900 } 901 902 /* 903 * Update the async context so that it uses the next configured DB. 904 * Return 0 on success, or -1 if no more DBs is available. 905 */ 906 int 907 _asr_iter_db(struct asr_query *as) 908 { 909 if (as->as_db_idx >= as->as_ctx->ac_dbcount) { 910 DPRINT("asr_iter_db: done\n"); 911 return (-1); 912 } 913 914 as->as_db_idx += 1; 915 DPRINT("asr_iter_db: %i\n", as->as_db_idx); 916 917 return (0); 918 } 919 920 /* 921 * Check if the hostname "name" is a user-defined alias as per hostname(7). 922 * If so, copies the result in the buffer "abuf" of size "abufsz" and 923 * return "abuf". Otherwise return NULL. 924 */ 925 char * 926 _asr_hostalias(struct asr_ctx *ac, const char *name, char *abuf, size_t abufsz) 927 { 928 #if ASR_OPT_HOSTALIASES 929 FILE *fp; 930 size_t len; 931 char *file, *buf, *tokens[2]; 932 int ntok; 933 934 if (ac->ac_options & RES_NOALIASES || 935 asr_ndots(name) != 0 || 936 issetugid() || 937 (file = getenv("HOSTALIASES")) == NULL || 938 (fp = fopen(file, "re")) == NULL) 939 return (NULL); 940 941 DPRINT("asr: looking up aliases in \"%s\"\n", file); 942 943 while ((buf = fgetln(fp, &len)) != NULL) { 944 if (buf[len - 1] == '\n') 945 len--; 946 buf[len] = '\0'; 947 if ((ntok = strsplit(buf, tokens, 2)) != 2) 948 continue; 949 if (!strcasecmp(tokens[0], name)) { 950 if (strlcpy(abuf, tokens[1], abufsz) > abufsz) 951 continue; 952 DPRINT("asr: found alias \"%s\"\n", abuf); 953 fclose(fp); 954 return (abuf); 955 } 956 } 957 958 fclose(fp); 959 #endif 960 return (NULL); 961 } 962