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