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