1 #include <u.h> 2 #include <libc.h> 3 #include <auth.h> 4 #include <fcall.h> 5 #include <bio.h> 6 #include <ctype.h> 7 #include <ip.h> 8 #include <pool.h> 9 #include "dns.h" 10 11 enum 12 { 13 Maxrequest= 1024, 14 Maxreply= 8192, /* was 512 */ 15 Maxrrr= 32, /* was 16 */ 16 Maxfdata= 8192, 17 18 Defmaxage= 60*60, /* default domain name max. age */ 19 20 Qdir= 0, 21 Qdns= 1, 22 }; 23 24 typedef struct Mfile Mfile; 25 typedef struct Job Job; 26 typedef struct Network Network; 27 28 int vers; /* incremented each clone/attach */ 29 30 struct Mfile 31 { 32 Mfile *next; /* next free mfile */ 33 int ref; 34 35 char *user; 36 Qid qid; 37 int fid; 38 39 int type; /* reply type */ 40 char reply[Maxreply]; 41 ushort rr[Maxrrr]; /* offset of rr's */ 42 ushort nrr; /* number of rr's */ 43 }; 44 45 /* 46 * active local requests 47 */ 48 struct Job 49 { 50 Job *next; 51 int flushed; 52 Fcall request; 53 Fcall reply; 54 }; 55 Lock joblock; 56 Job *joblist; 57 58 struct { 59 Lock; 60 Mfile *inuse; /* active mfile's */ 61 } mfalloc; 62 63 Cfg cfg; 64 int debug; 65 uchar ipaddr[IPaddrlen]; /* my ip address */ 66 int maxage = Defmaxage; 67 int mfd[2]; 68 int needrefresh; 69 ulong now; 70 vlong nowns; 71 int sendnotifies; 72 int testing; 73 char *trace; 74 int traceactivity; 75 char *zonerefreshprogram; 76 77 char *logfile = "dns"; /* or "dns.test" */ 78 char *dbfile; 79 char mntpt[Maxpath]; 80 81 int fillreply(Mfile*, int); 82 void freejob(Job*); 83 void io(void); 84 void mountinit(char*, char*); 85 Job* newjob(void); 86 void rattach(Job*, Mfile*); 87 void rauth(Job*); 88 void rclunk(Job*, Mfile*); 89 void rcreate(Job*, Mfile*); 90 void rflush(Job*); 91 void ropen(Job*, Mfile*); 92 void rread(Job*, Mfile*); 93 void rremove(Job*, Mfile*); 94 void rstat(Job*, Mfile*); 95 void rversion(Job*); 96 char* rwalk(Job*, Mfile*); 97 void rwrite(Job*, Mfile*, Request*); 98 void rwstat(Job*, Mfile*); 99 void sendmsg(Job*, char*); 100 void setext(char*, int, char*); 101 102 void 103 usage(void) 104 { 105 fprint(2, "usage: %s [-FnorRst] [-a maxage] [-f ndb-file] [-N target] " 106 "[-x netmtpt] [-z refreshprog]\n", argv0); 107 exits("usage"); 108 } 109 110 void 111 main(int argc, char *argv[]) 112 { 113 char servefile[Maxpath], ext[Maxpath]; 114 115 setnetmtpt(mntpt, sizeof mntpt, nil); 116 ext[0] = 0; 117 ARGBEGIN{ 118 case 'a': 119 maxage = atol(EARGF(usage())); 120 if (maxage <= 0) 121 maxage = Defmaxage; 122 break; 123 case 'd': 124 debug = 1; 125 traceactivity = 1; 126 break; 127 case 'f': 128 dbfile = EARGF(usage()); 129 break; 130 case 'F': 131 cfg.justforw = cfg.resolver = 1; 132 break; 133 case 'n': 134 sendnotifies = 1; 135 break; 136 case 'N': 137 target = atol(EARGF(usage())); 138 if (target < 1000) 139 target = 1000; 140 break; 141 case 'o': 142 cfg.straddle = 1; /* straddle inside & outside networks */ 143 break; 144 case 'r': 145 cfg.resolver = 1; 146 break; 147 case 'R': 148 norecursion = 1; 149 break; 150 case 's': 151 cfg.serve = 1; /* serve network */ 152 cfg.cachedb = 1; 153 break; 154 case 't': 155 testing = 1; 156 break; 157 case 'x': 158 setnetmtpt(mntpt, sizeof mntpt, EARGF(usage())); 159 setext(ext, sizeof ext, mntpt); 160 break; 161 case 'z': 162 zonerefreshprogram = EARGF(usage()); 163 break; 164 }ARGEND 165 USED(argc); 166 USED(argv); 167 168 if(testing) 169 mainmem->flags |= POOL_NOREUSE | POOL_ANTAGONISM; 170 // mainmem->flags |= POOL_ANTAGONISM; 171 rfork(RFREND|RFNOTEG); 172 173 cfg.inside = (*mntpt == '\0' || strcmp(mntpt, "/net") == 0); 174 175 /* start syslog before we fork */ 176 fmtinstall('F', fcallfmt); 177 dninit(); 178 /* this really shouldn't be fatal */ 179 if(myipaddr(ipaddr, mntpt) < 0) 180 sysfatal("can't read my ip address"); 181 dnslog("starting %s%sdns %s%s%son %I's %s", 182 (cfg.straddle? "straddling ": ""), 183 (cfg.cachedb? "caching ": ""), 184 (cfg.serve? "udp server ": ""), 185 (cfg.justforw? "forwarding-only ": ""), 186 (cfg.resolver? "resolver ": ""), ipaddr, mntpt); 187 188 opendatabase(); 189 now = time(nil); /* open time files before we fork */ 190 nowns = nsec(); 191 192 snprint(servefile, sizeof servefile, "#s/dns%s", ext); 193 unmount(servefile, mntpt); 194 remove(servefile); 195 mountinit(servefile, mntpt); /* forks */ 196 197 srand(now*getpid()); 198 db2cache(1); 199 dnagenever(); 200 201 if (cfg.straddle && !seerootns()) 202 dnslog("straddle server misconfigured; can't see root name servers"); 203 if(cfg.serve) 204 dnudpserver(mntpt); 205 if(sendnotifies) 206 notifyproc(); 207 208 io(); 209 dnslog("io returned, exiting"); 210 exits(0); 211 } 212 213 /* 214 * if a mount point is specified, set the cs extension to be the mount point 215 * with '_'s replacing '/'s 216 */ 217 void 218 setext(char *ext, int n, char *p) 219 { 220 int i, c; 221 222 n--; 223 for(i = 0; i < n; i++){ 224 c = p[i]; 225 if(c == 0) 226 break; 227 if(c == '/') 228 c = '_'; 229 ext[i] = c; 230 } 231 ext[i] = 0; 232 } 233 234 void 235 mountinit(char *service, char *mntpt) 236 { 237 int f; 238 int p[2]; 239 char buf[32]; 240 241 if(pipe(p) < 0) 242 abort(); /* "pipe failed" */; 243 switch(rfork(RFFDG|RFPROC|RFNAMEG)){ 244 case 0: 245 close(p[1]); 246 procsetname("main"); 247 break; 248 case -1: 249 abort(); /* "fork failed\n" */; 250 default: 251 close(p[0]); 252 253 /* 254 * make a /srv/dns 255 */ 256 f = create(service, 1, 0666); 257 if(f < 0) 258 abort(); /* service */; 259 snprint(buf, sizeof buf, "%d", p[1]); 260 if(write(f, buf, strlen(buf)) != strlen(buf)) 261 abort(); /* "write %s", service */; 262 close(f); 263 264 /* 265 * put ourselves into the file system 266 */ 267 if(mount(p[1], -1, mntpt, MAFTER, "") < 0) 268 fprint(2, "dns mount failed: %r\n"); 269 _exits(0); 270 } 271 mfd[0] = mfd[1] = p[0]; 272 } 273 274 Mfile* 275 newfid(int fid, int needunused) 276 { 277 Mfile *mf; 278 279 lock(&mfalloc); 280 for(mf = mfalloc.inuse; mf != nil; mf = mf->next) 281 if(mf->fid == fid){ 282 unlock(&mfalloc); 283 if(needunused) 284 return nil; 285 return mf; 286 } 287 mf = emalloc(sizeof(*mf)); 288 if(mf == nil) 289 sysfatal("out of memory"); 290 mf->fid = fid; 291 mf->next = mfalloc.inuse; 292 mfalloc.inuse = mf; 293 unlock(&mfalloc); 294 return mf; 295 } 296 297 void 298 freefid(Mfile *mf) 299 { 300 Mfile **l; 301 302 lock(&mfalloc); 303 for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next) 304 if(*l == mf){ 305 *l = mf->next; 306 if(mf->user) 307 free(mf->user); 308 memset(mf, 0, sizeof *mf); /* cause trouble */ 309 free(mf); 310 unlock(&mfalloc); 311 return; 312 } 313 sysfatal("freeing unused fid"); 314 } 315 316 Mfile* 317 copyfid(Mfile *mf, int fid) 318 { 319 Mfile *nmf; 320 321 nmf = newfid(fid, 1); 322 if(nmf == nil) 323 return nil; 324 nmf->fid = fid; 325 nmf->user = estrdup(mf->user); 326 nmf->qid.type = mf->qid.type; 327 nmf->qid.path = mf->qid.path; 328 nmf->qid.vers = vers++; 329 return nmf; 330 } 331 332 Job* 333 newjob(void) 334 { 335 Job *job; 336 337 job = emalloc(sizeof *job); 338 lock(&joblock); 339 job->next = joblist; 340 joblist = job; 341 job->request.tag = -1; 342 unlock(&joblock); 343 return job; 344 } 345 346 void 347 freejob(Job *job) 348 { 349 Job **l; 350 351 lock(&joblock); 352 for(l = &joblist; *l; l = &(*l)->next) 353 if(*l == job){ 354 *l = job->next; 355 memset(job, 0, sizeof *job); /* cause trouble */ 356 free(job); 357 break; 358 } 359 unlock(&joblock); 360 } 361 362 void 363 flushjob(int tag) 364 { 365 Job *job; 366 367 lock(&joblock); 368 for(job = joblist; job; job = job->next) 369 if(job->request.tag == tag && job->request.type != Tflush){ 370 job->flushed = 1; 371 break; 372 } 373 unlock(&joblock); 374 } 375 376 void 377 io(void) 378 { 379 long n; 380 Mfile *mf; 381 uchar mdata[IOHDRSZ + Maxfdata]; 382 Request req; 383 Job *job; 384 385 memset(&req, 0, sizeof req); 386 /* 387 * a slave process is sometimes forked to wait for replies from other 388 * servers. The master process returns immediately via a longjmp 389 * through 'mret'. 390 */ 391 if(setjmp(req.mret)) 392 putactivity(0); 393 procsetname("main 9p reading loop"); 394 req.isslave = 0; 395 for(;;){ 396 n = read9pmsg(mfd[0], mdata, sizeof mdata); 397 if(n<=0){ 398 dnslog("error reading mntpt: %r"); 399 exits(0); 400 } 401 402 job = newjob(); 403 if(convM2S(mdata, n, &job->request) != n){ 404 freejob(job); 405 continue; 406 } 407 mf = newfid(job->request.fid, 0); 408 if(debug) 409 dnslog("%F", &job->request); 410 411 getactivity(&req, 0); 412 req.aborttime = now + Maxreqtm; 413 req.from = "9p"; 414 415 switch(job->request.type){ 416 default: 417 warning("unknown request type %d", job->request.type); 418 break; 419 case Tversion: 420 rversion(job); 421 break; 422 case Tauth: 423 rauth(job); 424 break; 425 case Tflush: 426 rflush(job); 427 break; 428 case Tattach: 429 rattach(job, mf); 430 break; 431 case Twalk: 432 rwalk(job, mf); 433 break; 434 case Topen: 435 ropen(job, mf); 436 break; 437 case Tcreate: 438 rcreate(job, mf); 439 break; 440 case Tread: 441 rread(job, mf); 442 break; 443 case Twrite: 444 rwrite(job, mf, &req); 445 break; 446 case Tclunk: 447 rclunk(job, mf); 448 break; 449 case Tremove: 450 rremove(job, mf); 451 break; 452 case Tstat: 453 rstat(job, mf); 454 break; 455 case Twstat: 456 rwstat(job, mf); 457 break; 458 } 459 460 freejob(job); 461 462 /* 463 * slave processes die after replying 464 */ 465 if(req.isslave){ 466 putactivity(0); 467 _exits(0); 468 } 469 470 putactivity(0); 471 } 472 } 473 474 void 475 rversion(Job *job) 476 { 477 if(job->request.msize > IOHDRSZ + Maxfdata) 478 job->reply.msize = IOHDRSZ + Maxfdata; 479 else 480 job->reply.msize = job->request.msize; 481 if(strncmp(job->request.version, "9P2000", 6) != 0) 482 sendmsg(job, "unknown 9P version"); 483 else{ 484 job->reply.version = "9P2000"; 485 sendmsg(job, 0); 486 } 487 } 488 489 void 490 rauth(Job *job) 491 { 492 sendmsg(job, "dns: authentication not required"); 493 } 494 495 /* 496 * don't flush till all the slaves are done 497 */ 498 void 499 rflush(Job *job) 500 { 501 flushjob(job->request.oldtag); 502 sendmsg(job, 0); 503 } 504 505 void 506 rattach(Job *job, Mfile *mf) 507 { 508 if(mf->user != nil) 509 free(mf->user); 510 mf->user = estrdup(job->request.uname); 511 mf->qid.vers = vers++; 512 mf->qid.type = QTDIR; 513 mf->qid.path = 0LL; 514 job->reply.qid = mf->qid; 515 sendmsg(job, 0); 516 } 517 518 char* 519 rwalk(Job *job, Mfile *mf) 520 { 521 int i, nelems; 522 char *err; 523 char **elems; 524 Mfile *nmf; 525 Qid qid; 526 527 err = 0; 528 nmf = nil; 529 elems = job->request.wname; 530 nelems = job->request.nwname; 531 job->reply.nwqid = 0; 532 533 if(job->request.newfid != job->request.fid){ 534 /* clone fid */ 535 nmf = copyfid(mf, job->request.newfid); 536 if(nmf == nil){ 537 err = "clone bad newfid"; 538 goto send; 539 } 540 mf = nmf; 541 } 542 /* else nmf will be nil */ 543 544 qid = mf->qid; 545 if(nelems > 0) 546 /* walk fid */ 547 for(i=0; i<nelems && i<MAXWELEM; i++){ 548 if((qid.type & QTDIR) == 0){ 549 err = "not a directory"; 550 break; 551 } 552 if (strcmp(elems[i], "..") == 0 || 553 strcmp(elems[i], ".") == 0){ 554 qid.type = QTDIR; 555 qid.path = Qdir; 556 Found: 557 job->reply.wqid[i] = qid; 558 job->reply.nwqid++; 559 continue; 560 } 561 if(strcmp(elems[i], "dns") == 0){ 562 qid.type = QTFILE; 563 qid.path = Qdns; 564 goto Found; 565 } 566 err = "file does not exist"; 567 break; 568 } 569 570 send: 571 if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)) 572 freefid(nmf); 573 if(err == nil) 574 mf->qid = qid; 575 sendmsg(job, err); 576 return err; 577 } 578 579 void 580 ropen(Job *job, Mfile *mf) 581 { 582 int mode; 583 char *err; 584 585 err = 0; 586 mode = job->request.mode; 587 if(mf->qid.type & QTDIR) 588 if(mode) 589 err = "permission denied"; 590 job->reply.qid = mf->qid; 591 job->reply.iounit = 0; 592 sendmsg(job, err); 593 } 594 595 void 596 rcreate(Job *job, Mfile *mf) 597 { 598 USED(mf); 599 sendmsg(job, "creation permission denied"); 600 } 601 602 void 603 rread(Job *job, Mfile *mf) 604 { 605 int i, n; 606 long clock; 607 ulong cnt; 608 vlong off; 609 char *err; 610 uchar buf[Maxfdata]; 611 Dir dir; 612 613 n = 0; 614 err = nil; 615 off = job->request.offset; 616 cnt = job->request.count; 617 *buf = '\0'; 618 job->reply.data = (char*)buf; 619 if(mf->qid.type & QTDIR){ 620 clock = time(nil); 621 if(off == 0){ 622 dir.name = "dns"; 623 dir.qid.type = QTFILE; 624 dir.qid.vers = vers; 625 dir.qid.path = Qdns; 626 dir.mode = 0666; 627 dir.length = 0; 628 dir.uid = dir.gid = dir.muid = mf->user; 629 dir.atime = dir.mtime = clock; /* wrong */ 630 n = convD2M(&dir, buf, sizeof buf); 631 } 632 } else if (off < 0) 633 err = "negative read offset"; 634 else { 635 /* first offset will always be zero */ 636 for(i = 1; i <= mf->nrr; i++) 637 if(mf->rr[i] > off) 638 break; 639 if(i <= mf->nrr) { 640 if(off + cnt > mf->rr[i]) 641 n = mf->rr[i] - off; 642 else 643 n = cnt; 644 assert(n >= 0); 645 job->reply.data = mf->reply + off; 646 } 647 } 648 job->reply.count = n; 649 sendmsg(job, err); 650 } 651 652 void 653 rwrite(Job *job, Mfile *mf, Request *req) 654 { 655 int rooted, status, wantsav; 656 long n; 657 ulong cnt; 658 char *err, *p, *atype; 659 RR *rp, *tp, *neg; 660 661 err = nil; 662 cnt = job->request.count; 663 if(mf->qid.type & QTDIR){ 664 err = "can't write directory"; 665 goto send; 666 } 667 if (job->request.offset != 0) { 668 err = "writing at non-zero offset"; 669 goto send; 670 } 671 if(cnt >= Maxrequest){ 672 err = "request too long"; 673 goto send; 674 } 675 job->request.data[cnt] = 0; 676 if(cnt > 0 && job->request.data[cnt-1] == '\n') 677 job->request.data[cnt-1] = 0; 678 679 /* 680 * special commands 681 */ 682 if(strcmp(job->request.data, "debug")==0){ 683 debug ^= 1; 684 goto send; 685 } else if(strcmp(job->request.data, "dump")==0){ 686 dndump("/lib/ndb/dnsdump"); 687 goto send; 688 } else if(strcmp(job->request.data, "refresh")==0){ 689 needrefresh = 1; 690 goto send; 691 } else if(strcmp(job->request.data, "poolcheck")==0){ 692 poolcheck(mainmem); 693 goto send; 694 } else if(strncmp(job->request.data, "target", 6)==0){ 695 target = atol(job->request.data + 6); 696 dnslog("target set to %ld", target); 697 goto send; 698 } else if(strcmp(job->request.data, "age")==0){ 699 dnslog("dump, age & dump forced"); 700 dndump("/lib/ndb/dnsdump1"); 701 dnforceage(); 702 dndump("/lib/ndb/dnsdump2"); 703 goto send; 704 } 705 706 /* 707 * kill previous reply 708 */ 709 mf->nrr = 0; 710 mf->rr[0] = 0; 711 712 /* 713 * break up request (into a name and a type) 714 */ 715 atype = strchr(job->request.data, ' '); 716 if(atype == 0){ 717 err = "illegal request"; 718 goto send; 719 } else 720 *atype++ = 0; 721 722 /* 723 * tracing request 724 */ 725 if(strcmp(atype, "trace") == 0){ 726 if(trace) 727 free(trace); 728 if(*job->request.data) 729 trace = estrdup(job->request.data); 730 else 731 trace = 0; 732 goto send; 733 } 734 735 /* normal request: domain [type] */ 736 stats.qrecvd9p++; 737 mf->type = rrtype(atype); 738 if(mf->type < 0){ 739 err = "unknown type"; 740 goto send; 741 } 742 743 p = atype - 2; 744 if(p >= job->request.data && *p == '.'){ 745 rooted = 1; 746 *p = 0; 747 } else 748 rooted = 0; 749 750 p = job->request.data; 751 if(*p == '!'){ 752 wantsav = 1; 753 p++; 754 } else 755 wantsav = 0; 756 757 dncheck(0, 1); 758 status = 0; 759 rp = dnresolve(p, Cin, mf->type, req, 0, 0, Recurse, rooted, &status); 760 761 dncheck(0, 1); 762 neg = rrremneg(&rp); 763 if(neg){ 764 status = neg->negrcode; 765 rrfreelist(neg); 766 } 767 768 if(rp == nil) 769 switch(status){ 770 case Rname: 771 err = "name does not exist"; 772 break; 773 case Rserver: 774 err = "dns failure"; 775 break; 776 default: 777 err = "resource does not exist"; 778 break; 779 } 780 else { 781 lock(&joblock); 782 if(!job->flushed){ 783 /* format data to be read later */ 784 n = 0; 785 mf->nrr = 0; 786 for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp && 787 tsame(mf->type, tp->type); tp = tp->next){ 788 mf->rr[mf->nrr++] = n; 789 if(wantsav) 790 n += snprint(mf->reply+n, Maxreply-n, 791 "%Q", tp); 792 else 793 n += snprint(mf->reply+n, Maxreply-n, 794 "%R", tp); 795 } 796 mf->rr[mf->nrr] = n; 797 } 798 unlock(&joblock); 799 rrfreelist(rp); 800 } 801 send: 802 dncheck(0, 1); 803 job->reply.count = cnt; 804 sendmsg(job, err); 805 } 806 807 void 808 rclunk(Job *job, Mfile *mf) 809 { 810 freefid(mf); 811 sendmsg(job, 0); 812 } 813 814 void 815 rremove(Job *job, Mfile *mf) 816 { 817 USED(mf); 818 sendmsg(job, "remove permission denied"); 819 } 820 821 void 822 rstat(Job *job, Mfile *mf) 823 { 824 Dir dir; 825 uchar buf[IOHDRSZ+Maxfdata]; 826 827 if(mf->qid.type & QTDIR){ 828 dir.name = "."; 829 dir.mode = DMDIR|0555; 830 } else { 831 dir.name = "dns"; 832 dir.mode = 0666; 833 } 834 dir.qid = mf->qid; 835 dir.length = 0; 836 dir.uid = dir.gid = dir.muid = mf->user; 837 dir.atime = dir.mtime = time(nil); 838 job->reply.nstat = convD2M(&dir, buf, sizeof buf); 839 job->reply.stat = buf; 840 sendmsg(job, 0); 841 } 842 843 void 844 rwstat(Job *job, Mfile *mf) 845 { 846 USED(mf); 847 sendmsg(job, "wstat permission denied"); 848 } 849 850 void 851 sendmsg(Job *job, char *err) 852 { 853 int n; 854 uchar mdata[IOHDRSZ + Maxfdata]; 855 char ename[ERRMAX]; 856 857 if(err){ 858 job->reply.type = Rerror; 859 snprint(ename, sizeof ename, "dns: %s", err); 860 job->reply.ename = ename; 861 }else 862 job->reply.type = job->request.type+1; 863 job->reply.tag = job->request.tag; 864 n = convS2M(&job->reply, mdata, sizeof mdata); 865 if(n == 0){ 866 warning("sendmsg convS2M of %F returns 0", &job->reply); 867 abort(); 868 } 869 lock(&joblock); 870 if(job->flushed == 0) 871 if(write(mfd[1], mdata, n)!=n) 872 sysfatal("mount write"); 873 unlock(&joblock); 874 if(debug) 875 dnslog("%F %d", &job->reply, n); 876 } 877 878 /* 879 * the following varies between dnsdebug and dns 880 */ 881 void 882 logreply(int id, uchar *addr, DNSmsg *mp) 883 { 884 RR *rp; 885 886 dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr, 887 mp->flags & Fauth? " auth": "", 888 mp->flags & Ftrunc? " trunc": "", 889 mp->flags & Frecurse? " rd": "", 890 mp->flags & Fcanrec? " ra": "", 891 (mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": ""); 892 for(rp = mp->qd; rp != nil; rp = rp->next) 893 dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name); 894 for(rp = mp->an; rp != nil; rp = rp->next) 895 dnslog("%d: rcvd %I an %R", id, addr, rp); 896 for(rp = mp->ns; rp != nil; rp = rp->next) 897 dnslog("%d: rcvd %I ns %R", id, addr, rp); 898 for(rp = mp->ar; rp != nil; rp = rp->next) 899 dnslog("%d: rcvd %I ar %R", id, addr, rp); 900 } 901 902 void 903 logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type) 904 { 905 char buf[12]; 906 907 dnslog("[%d] %d.%d: sending to %I/%s %s %s", 908 getpid(), id, subid, addr, sname, rname, 909 rrname(type, buf, sizeof buf)); 910 } 911 912 RR* 913 getdnsservers(int class) 914 { 915 return dnsservers(class); 916 } 917