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