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