1 /* 2 * Copyright (c) 1989 Jan-Simon Pendry 3 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine 4 * Copyright (c) 1989 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Jan-Simon Pendry at Imperial College, London. 9 * 10 * %sccs.include.redist.c% 11 * 12 * @(#)info_hes.c 5.4 (Berkeley) 02/09/92 13 * 14 * $Id: info_hes.c,v 5.2.2.1 1992/02/09 15:08:29 jsp beta $ 15 * 16 */ 17 18 /* 19 * Get info from Hesiod 20 * 21 * Zone transfer code from Bruce Cole <cole@cs.wisc.edu> 22 */ 23 24 #include "am.h" 25 26 #ifdef HAS_HESIOD_MAPS 27 #include <hesiod.h> 28 29 #define HES_PREFIX "hesiod." 30 #define HES_PREFLEN 7 31 32 #ifdef HAS_HESIOD_RELOAD 33 #include <arpa/nameser.h> 34 #include <resolv.h> 35 #include <sys/uio.h> 36 #include <netdb.h> 37 38 /* 39 * Patch up broken system include files 40 */ 41 #ifndef C_HS 42 #define C_HS 4 43 #endif 44 #ifndef T_TXT 45 #define T_TXT 16 46 #endif 47 48 static int soacnt; 49 static struct timeval hs_timeout; 50 static int servernum; 51 #endif /* HAS_HESIOD_RELOAD */ 52 53 /* 54 * No easy way to probe the server - check the map name begins with "hesiod." 55 */ 56 int hesiod_init P((char *map, time_t *tp)); 57 int hesiod_init(map, tp) 58 char *map; 59 time_t *tp; 60 { 61 #ifdef DEBUG 62 dlog("hesiod_init(%s)", map); 63 #endif 64 *tp = 0; 65 return strncmp(map, HES_PREFIX, HES_PREFLEN) == 0 ? 0 : ENOENT; 66 } 67 68 69 /* 70 * Make Hesiod name. Skip past the "hesiod." 71 * at the start of the map name and append 72 * ".automount". The net effect is that a lookup 73 * of /defaults in hesiod.home will result in a 74 * call to hes_resolve("/defaults", "home.automount"); 75 */ 76 #ifdef notdef 77 #define MAKE_HES_NAME(dest, src) sprintf(dest, "%s%s", src + HES_PREFLEN, ".automount") 78 #endif 79 80 /* 81 * Do a Hesiod nameserver call. 82 * Modify time is ignored by Hesiod - XXX 83 */ 84 int hesiod_search P((mnt_map *m, char *map, char **pval, time_t *tp)); 85 int hesiod_search(m, map, key, pval, tp) 86 mnt_map *m; 87 char *map; 88 char *key; 89 char **pval; 90 time_t *tp; 91 { 92 int error; 93 char hes_key[MAXPATHLEN]; 94 char **rvec; 95 #ifdef DEBUG 96 dlog("hesiod_search(m=%x, map=%s, key=%s, pval=%x tp=%x)", m, map, key, pval, tp); 97 #endif 98 /*MAKE_HES_NAME(hes_map, map);*/ 99 sprintf(hes_key, "%s.%s", key, map+HES_PREFLEN); 100 101 /* 102 * Call the resolver 103 */ 104 #ifdef DEBUG 105 dlog("hesiod_search: hes_resolve(%s, %s)", hes_key, "automount"); 106 #ifdef HAS_HESIOD_RELOAD 107 if (debug_flags & D_FULL) 108 _res.options |= RES_DEBUG; 109 #endif 110 #endif 111 rvec = hes_resolve(hes_key, "automount"); 112 /* 113 * If a reply was forthcoming then return 114 * it (and free subsequent replies) 115 */ 116 if (rvec && *rvec) { 117 *pval = *rvec; 118 while (*++rvec) 119 free(*rvec); 120 return 0; 121 } 122 123 /* 124 * Otherwise reflect the hesiod error into a Un*x error 125 */ 126 #ifdef DEBUG 127 dlog("hesiod_search: Error: %d", hes_error()); 128 #endif 129 switch (hes_error()) { 130 case HES_ER_NOTFOUND: error = ENOENT; break; 131 case HES_ER_CONFIG: error = EIO; break; 132 case HES_ER_NET: error = ETIMEDOUT; break; 133 default: error = EINVAL; break; 134 } 135 #ifdef DEBUG 136 dlog("hesiod_search: Returning: %d", error); 137 #endif 138 return error; 139 } 140 141 #ifdef HAS_HESIOD_RELOAD 142 /* 143 * Zone transfer... 144 */ 145 146 #define MAXHSNS 8 147 #define MAX_NSADDR 16 148 149 static char *hs_domain; 150 static mnt_map *hs_map; 151 static int hs_nscount; 152 static char nsaddr_list[MAX_NSADDR][sizeof(struct in_addr)]; 153 154 int hesiod_reload P((mnt_map *m, char *map, void (*fn)())); 155 int hesiod_reload(m, map, fn) 156 mnt_map *m; 157 char *map; 158 void (*fn)(); 159 { 160 char *zone_name, *cp; 161 short domainlen; 162 int status; 163 164 #ifdef DEBUG 165 dlog("hesiod_reload (%x %s %x)", m, map, fn); 166 #endif DEBUG 167 if (status = res_init()) { 168 #ifdef DEBUG 169 dlog("hesiod_reload: res_init failed with %d", status); 170 #endif 171 return(status); 172 } 173 _res.retrans = 90; 174 hs_map = m; 175 domainlen = strlen(hostdomain); 176 zone_name = hes_to_bind(map+HES_PREFLEN, "automount"); 177 if (*zone_name == '.') 178 zone_name++; 179 hs_domain = zone_name; 180 /* Traverse the DNS tree until we find an SOA we can transfer from. 181 (Our initial zone_name is likely to just be a subtree of a 182 real zone). */ 183 do { 184 /* If we can't find any NS records, go up a level in the 185 DNS tree */ 186 if (hs_get_ns_list(zone_name) == 0 && 187 hs_zone_transfer(zone_name) == 0) 188 return(0); 189 /* Move up DNS tree by one component */ 190 if (cp = strchr(zone_name, '.')) 191 zone_name = ++cp; 192 else 193 break; 194 } while (strlen(zone_name) >= domainlen); 195 #ifdef DEBUG 196 dlog("hesiod_reload: Giving up on %s", hs_domain); 197 #endif 198 return(-1); 199 } 200 201 hs_zone_transfer(domain) 202 char *domain; 203 { 204 int status, len; 205 char buf[PACKETSZ]; 206 /* Want to make sure ansbuf is well alligned */ 207 long ansbuf[PACKETSZ/sizeof(long)]; 208 209 #ifdef DEBUG 210 dlog("hs_zone_transfer (%s)", domain); 211 #endif 212 if ((len = res_mkquery(QUERY, domain, C_HS, T_AXFR, 213 (char *)NULL, 0, NULL, buf, PACKETSZ)) == -1) { 214 #ifdef DEBUG 215 dlog("hs_zone_transfer: res_mkquery failed"); 216 #endif 217 errno = 0; 218 return(-1); 219 } 220 if ((status = hs_res_send(buf, len, (char *)ansbuf, PACKETSZ)) == -1) { 221 #ifdef DEBUG 222 dlog("hs_zone_transfer: hs_res_send failed. status %d errno %d", 223 status, errno); 224 #endif 225 errno = 0; 226 return(-1); 227 } 228 return(0); 229 } 230 231 #define hs_server_addr(ns) ((struct in_addr *) nsaddr_list[ns]) 232 233 hs_res_send(buf, buflen, answer, anslen) 234 char *buf; 235 int buflen; 236 char *answer; 237 int anslen; 238 { 239 int retry, ns; 240 u_short id, len; 241 HEADER *hp = (HEADER *) buf; 242 struct iovec iov[2]; 243 static int s = -1; 244 int status; 245 struct sockaddr_in server; 246 247 soacnt = 0; 248 id = hp->id; 249 /* 250 * Send request, RETRY times, or until successful 251 */ 252 for (retry = _res.retry; retry > 0; retry--) { 253 for (ns = 0; ns < hs_nscount; ns++) { 254 hs_timeout.tv_sec = 255 (_res.retrans << (_res.retry - retry)) 256 / hs_nscount; 257 if (hs_timeout.tv_sec <= 0) 258 hs_timeout.tv_sec = 1; 259 hs_timeout.tv_usec = 0; 260 if (s < 0) { 261 s = socket(AF_INET, SOCK_STREAM, 0); 262 if (s < 0) { 263 continue; 264 } 265 servernum = ns; 266 bcopy(hs_server_addr(ns), &server.sin_addr, 267 sizeof(struct in_addr)); 268 server.sin_family = AF_INET; 269 server.sin_port = htons(NAMESERVER_PORT); 270 271 if (connect(s, &server, 272 sizeof(struct sockaddr)) < 0) { 273 (void) close(s); 274 s = -1; 275 continue; 276 } 277 } 278 /* 279 * Send length & message 280 */ 281 len = htons((u_short)buflen); 282 iov[0].iov_base = (caddr_t)&len; 283 iov[0].iov_len = sizeof(len); 284 iov[1].iov_base = buf; 285 iov[1].iov_len = buflen; 286 if (writev(s, iov, 2) != sizeof(len) + buflen) { 287 (void) close(s); 288 s = -1; 289 continue; 290 } 291 status = 0; 292 while (s != -1 && soacnt < 2 && status != -2) { 293 if ((status = 294 hs_readresp(s, answer, anslen)) == -1) { 295 (void) close(s); 296 s = -1; 297 continue; 298 } 299 } 300 if (status == -2) { 301 /* There was a permanent error transfering this 302 zone. Give up. */ 303 if (s != -1) { 304 (void) close(s); 305 s = -1; 306 } 307 return(-1); 308 } 309 if (s == -1) 310 continue; 311 return (0); 312 } 313 } 314 if (errno == 0) 315 errno = ETIMEDOUT; 316 return (-1); 317 } 318 319 /* Returns: 320 0: Success 321 -1: Error 322 -2: Permanent failure 323 */ 324 hs_readresp(s, answer, anslen) 325 int s; 326 char *answer; 327 int anslen; 328 { 329 register int len, n; 330 char *cp; 331 332 cp = answer; 333 len = sizeof(short); 334 while (len != 0 && 335 (n = hs_res_vcread(s, (char *)cp, (int)len, &hs_timeout)) > 0) { 336 cp += n; 337 len -= n; 338 } 339 if (n <= 0) 340 return(-1); 341 cp = answer; 342 if ((len = _getshort(cp)) > anslen) { 343 #ifdef DEBUG 344 dlog("hs_readresp: response too long: %d", len); 345 #endif 346 return(-1); 347 } 348 while (len != 0 && 349 (n = hs_res_vcread(s, (char *)cp, (int)len, &hs_timeout)) > 0) { 350 cp += n; 351 len -= n; 352 } 353 if (n <= 0) 354 return(-1); 355 return(hs_parse(answer, answer+PACKETSZ)); 356 } 357 358 hs_res_vcread(sock, buf, buflen, timeout) 359 int sock, buflen; 360 char *buf; 361 struct timeval *timeout; 362 { 363 register int n; 364 365 if ((n = hs_res_selwait(sock, timeout)) > 0) 366 return(read(sock, buf, buflen)); 367 else 368 return(n); 369 } 370 371 hs_res_selwait(sock, timeout) 372 int sock; 373 struct timeval *timeout; 374 { 375 fd_set dsmask; 376 register int n; 377 378 /* 379 * Wait for reply 380 */ 381 FD_ZERO(&dsmask); 382 FD_SET(sock, &dsmask); 383 n = select(sock+1, &dsmask, (fd_set *)NULL, 384 (fd_set *)NULL, timeout); 385 return(n); 386 } 387 388 /* Returns: 389 0: Success 390 -1: Error 391 -2: Permanent failure 392 */ 393 hs_parse(msg, eom) 394 char *msg, *eom; 395 { 396 register char *cp; 397 register HEADER *hp; 398 register int n, len; 399 int qdcount, ancount; 400 char key[PACKETSZ]; 401 char *key_cpy, *value, *hs_make_value(); 402 short type; 403 404 hp = (HEADER *)msg; 405 if (hp->rcode != NOERROR || hp->opcode != QUERY) { 406 char dq[20]; 407 #ifdef DEBUG 408 dlog("Bad response (%d) from nameserver %s", hp->rcode, inet_dquad(dq, hs_server_addr(servernum)->s_addr)); 409 #endif DEBUG 410 return(-1); 411 } 412 cp = msg + sizeof(HEADER); 413 ancount = ntohs(hp->ancount); 414 qdcount = ntohs(hp->qdcount); 415 while (qdcount-- > 0) 416 cp += dn_skipname(cp, eom) + QFIXEDSZ; 417 if (soacnt == 0 && ancount == 0) { 418 /* XXX We should look for NS records to find SOA */ 419 #ifdef DEBUG 420 dlog("No SOA found"); 421 #endif 422 return(-2); 423 } 424 while (ancount-- > 0 && cp < eom) { 425 if ((n = dn_expand(msg, eom, cp, key, PACKETSZ)) < 0) 426 break; 427 cp += n; 428 if ((type = _getshort(cp)) == T_SOA) { 429 soacnt++; 430 } 431 cp += 2*sizeof(u_short) + sizeof(u_long); 432 len = _getshort(cp); 433 cp += sizeof(u_short); 434 /* Check to see if key is in our domain */ 435 if (type == T_TXT && hs_strip_our_domain(key)) { 436 value = hs_make_value(cp, len); 437 if (value == NULL) 438 return(-1); 439 key_cpy = strdup(key); 440 #ifdef DEBUG 441 dlog("hs_parse: Parsed key: %s, value: %s", key, 442 value); 443 #endif 444 mapc_add_kv(hs_map, key_cpy, value); 445 } 446 cp += len; 447 errno = 0; 448 } 449 return(0); 450 } 451 452 /* Check to see if the domain name in the supplied argument matches 453 hs_domain. Strip hs_domain from supplied argument if so. */ 454 hs_strip_our_domain(name) 455 char *name; 456 { 457 char *end_pos; 458 short targ_len, cur_len; 459 460 targ_len = strlen(hs_domain); 461 cur_len = strlen(name); 462 if (cur_len <= targ_len) 463 return(0); 464 end_pos = &name[cur_len - targ_len]; 465 if (strcmp(end_pos, hs_domain) != 0) 466 return(0); 467 if (*--end_pos != '.') 468 return(0); 469 *end_pos = '\0'; 470 return(1); 471 } 472 473 #define MAXDATA 8*1024 474 475 char * 476 hs_make_value(cp, len) 477 char *cp; 478 int len; 479 { 480 char *value, *cpcpy, *valuep; 481 int cnt, nextcnt, totalcnt, lencpy; 482 #ifdef DEBUG 483 char *dbgname; 484 485 dbgname = &cp[1]; 486 #endif DEBUG 487 488 lencpy = len; 489 cpcpy = cp; 490 totalcnt = 0; 491 cnt = *cpcpy++; 492 while (cnt) { 493 totalcnt += cnt; 494 lencpy -= cnt+1; 495 if (lencpy == 0) 496 break; 497 nextcnt = cpcpy[cnt]; 498 cpcpy = &cpcpy[cnt+1]; 499 cnt = nextcnt; 500 } 501 if (totalcnt < 1 || totalcnt > MAXDATA || totalcnt > len) { 502 #ifdef DEBUG 503 dlog("TXT RR not of expected length (%d %d): %s", totalcnt, 504 len, dbgname); 505 #endif DEBUG 506 return(NULL); 507 } 508 /* Allocate null terminated string */ 509 value = (char *) xmalloc(totalcnt+1); 510 value[totalcnt] = '\0'; 511 cnt = *cp++; 512 valuep = value; 513 while (cnt) { 514 bcopy(cp, valuep, cnt); 515 len -= cnt+1; 516 if (len == 0) 517 break; 518 valuep = &valuep[cnt]; 519 nextcnt = cp[cnt]; 520 cp = &cp[cnt+1]; 521 cnt = nextcnt; 522 } 523 return(value); 524 } 525 526 hs_make_ns_query(domain, ansbuf) 527 char *domain; 528 char *ansbuf; 529 { 530 int status, len; 531 char buf[PACKETSZ]; 532 533 if ((len = res_mkquery(QUERY, domain, C_HS, T_NS, 534 (char *)NULL, 0, NULL, buf, PACKETSZ)) == -1) { 535 #ifdef DEBUG 536 dlog("hs_get_ns_list: res_mkquery failed"); 537 #endif 538 errno = 0; 539 return(-1); 540 } 541 if ((status = res_send(buf, len, (char *)ansbuf, PACKETSZ)) == -1) { 542 #ifdef DEBUG 543 dlog("hs_get_ns_list: res_send failed. status %d errno %d", 544 status, errno); 545 #endif 546 errno = 0; 547 return(-1); 548 } 549 return(0); 550 } 551 552 static void 553 add_address(addr) 554 struct in_addr *addr; 555 { 556 char dq[20]; 557 bcopy((char *)addr, nsaddr_list[hs_nscount++], sizeof(struct in_addr)); 558 #ifdef DEBUG 559 dlog("Adding NS address %s", inet_dquad(dq, addr->s_addr)); 560 #endif DEBUG 561 } 562 563 hs_get_ns_list(domain) 564 char *domain; 565 { 566 register HEADER *hp; 567 int qdcount, nscount; 568 register char *cp; 569 register int n, len; 570 char key[PACKETSZ], name[PACKETSZ], msg[PACKETSZ], *eom; 571 register long **hptr; 572 struct hostent *ghp; 573 int numns; 574 char nsname[MAXHSNS][MAXDATA]; 575 int nshaveaddr[MAXHSNS], i; 576 short type; 577 578 if (hs_make_ns_query(domain, msg) == -1) 579 return(-1); 580 numns = hs_nscount = 0; 581 eom = &msg[PACKETSZ]; 582 bzero(nsname, sizeof(nsname)); 583 hp = (HEADER *)msg; 584 if (hp->rcode != NOERROR || hp->opcode != QUERY) { 585 #ifdef DEBUG 586 dlog("Bad response (%d) from nameserver %#x", hp->rcode, 587 hs_server_addr(servernum)->s_addr); 588 #endif DEBUG 589 return(-1); 590 } 591 cp = msg + sizeof(HEADER); 592 qdcount = ntohs(hp->qdcount); 593 while (qdcount-- > 0) 594 cp += dn_skipname(cp, eom) + QFIXEDSZ; 595 nscount = ntohs(hp->ancount) + ntohs(hp->nscount) + ntohs(hp->arcount); 596 #ifdef DEBUG 597 dlog("hs_get_ns_list: Processing %d response records", nscount); 598 #endif 599 for (;nscount; nscount--) { 600 if ((n = dn_expand(msg, eom, cp, key, PACKETSZ)) < 0) 601 break; 602 cp += n; 603 type = _getshort(cp); 604 cp += 2*sizeof(u_short) + sizeof(u_long); 605 len = _getshort(cp); 606 cp += sizeof(u_short); 607 #ifdef DEBUG 608 dlog("hs_get_ns_list: Record type: %d", type); 609 #endif 610 switch (type) { 611 case T_NS: 612 if (numns >= MAXHSNS || strcasecmp(domain, key) != 0) 613 break; 614 if ((n = dn_expand(msg, eom, cp, name, PACKETSZ)) < 0) 615 break; 616 #ifdef DEBUG 617 dlog("hs_get_ns_list: NS name: %s", name); 618 #endif 619 for (i = 0; i < numns; i++) 620 if (strcasecmp(nsname[i], name) == 0) 621 break; 622 if (i == numns) { 623 #ifdef DEBUG 624 dlog("hs_get_ns_list: Saving name %s", name); 625 #endif 626 strncpy(nsname[numns], name, MAXDATA); 627 nshaveaddr[numns] = 0; 628 numns++; 629 } 630 break; 631 case T_A: 632 if (hs_nscount == MAX_NSADDR) 633 break; 634 for (i = 0; i < numns; i++) { 635 if (strcasecmp(nsname[i], domain) == 0) { 636 nshaveaddr[i]++; 637 add_address((struct in_addr *) cp); 638 break; 639 } 640 } 641 break; 642 default: 643 break; 644 } 645 if (hs_nscount == MAX_NSADDR) 646 break; 647 cp += len; 648 errno = 0; 649 } 650 #ifdef DEBUG 651 dlog("hs_get_ns_list: Found %d NS records", numns); 652 #endif 653 for (i = 0; i < numns; i++) { 654 if (nshaveaddr[i]) 655 continue; 656 if ((ghp = gethostbyname(nsname[i])) == 0) 657 continue; 658 for (hptr = (long **)ghp->h_addr_list; 659 *hptr && hs_nscount < MAX_NSADDR; hptr++) { 660 add_address((struct in_addr *) *hptr); 661 } 662 } 663 if (hs_nscount) 664 return(0); 665 #ifdef DEBUG 666 dlog("No NS records found for %s", domain); 667 return(-1); 668 #endif DEBUG 669 } 670 #endif /* HAS_HESIOD_RELOAD */ 671 #endif /* HAS_HESIOD_MAPS */ 672