| #include <u.h> |
| #include <libc.h> |
| #include <auth.h> |
| #include <fcall.h> |
| #include <bio.h> |
| #include <ctype.h> |
| #include <ip.h> |
| #include <ndb.h> |
| #include <thread.h> |
| #include "dns.h" |
| |
| enum |
| { |
| Maxrequest= 1024, |
| Ncache= 8, |
| Maxpath= 128, |
| Maxreply= 512, |
| Maxrrr= 16, |
| Maxfdata= 8192, |
| |
| Qdir= 0, |
| Qdns= 1 |
| }; |
| |
| typedef struct Mfile Mfile; |
| typedef struct Job Job; |
| typedef struct Network Network; |
| |
| int vers; /* incremented each clone/attach */ |
| |
| struct Mfile |
| { |
| Mfile *next; /* next free mfile */ |
| int ref; |
| |
| char *user; |
| Qid qid; |
| int fid; |
| |
| int type; /* reply type */ |
| char reply[Maxreply]; |
| ushort rr[Maxrrr]; /* offset of rr's */ |
| ushort nrr; /* number of rr's */ |
| }; |
| |
| /* |
| * active local requests |
| */ |
| struct Job |
| { |
| Job *next; |
| int flushed; |
| Fcall request; |
| Fcall reply; |
| }; |
| Lock joblock; |
| Job *joblist; |
| |
| struct { |
| Lock lk; |
| Mfile *inuse; /* active mfile's */ |
| } mfalloc; |
| |
| int mfd[2]; |
| int debug; |
| int traceactivity; |
| int cachedb; |
| ulong now; |
| int testing; |
| char *trace; |
| int needrefresh; |
| int resolver; |
| uchar ipaddr[IPaddrlen]; /* my ip address */ |
| int maxage; |
| char *zonerefreshprogram; |
| int sendnotifies; |
| |
| void rversion(Job*); |
| void rauth(Job*); |
| void rflush(Job*); |
| void rattach(Job*, Mfile*); |
| char* rwalk(Job*, Mfile*); |
| void ropen(Job*, Mfile*); |
| void rcreate(Job*, Mfile*); |
| void rread(Job*, Mfile*); |
| void rwrite(Job*, Mfile*, Request*); |
| void rclunk(Job*, Mfile*); |
| void rremove(Job*, Mfile*); |
| void rstat(Job*, Mfile*); |
| void rwstat(Job*, Mfile*); |
| void sendmsg(Job*, char*); |
| void mountinit(char*); |
| void io(void); |
| int fillreply(Mfile*, int); |
| Job* newjob(void); |
| void freejob(Job*); |
| void setext(char*, int, char*); |
| |
| char *tcpaddr = "tcp!*!domain"; |
| char *udpaddr = "udp!*!domain"; |
| char *logfile = "dns"; |
| char *dbfile; |
| char mntpt[Maxpath]; |
| char *LOG; |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: dns [-dnrst] [-a maxage] [-f ndb-file] [-T tcpaddr] [-U udpaddr] [-x service] [-z zoneprog]\n"); |
| threadexitsall("usage"); |
| } |
| |
| void |
| checkaddress(void) |
| { |
| char *u, *t; |
| |
| u = strchr(udpaddr, '!'); |
| t = strchr(tcpaddr, '!'); |
| if(u && t && strcmp(u, t) != 0) |
| fprint(2, "warning: announce mismatch %s %s\n", udpaddr, tcpaddr); |
| } |
| |
| void |
| threadmain(int argc, char *argv[]) |
| { |
| int serveudp, servetcp; |
| char *service; |
| |
| serveudp = 0; |
| servetcp = 0; |
| service = "dns"; |
| ARGBEGIN{ |
| case 'd': |
| debug = 1; |
| traceactivity = 1; |
| break; |
| case 'f': |
| dbfile = EARGF(usage()); |
| break; |
| case 'x': |
| service = EARGF(usage()); |
| break; |
| case 'r': |
| resolver = 1; |
| break; |
| case 's': |
| serveudp = 1; |
| cachedb = 1; |
| break; |
| case 't': |
| servetcp = 1; |
| cachedb = 1; |
| break; |
| case 'a': |
| maxage = atoi(EARGF(usage())); |
| break; |
| case 'z': |
| zonerefreshprogram = EARGF(usage()); |
| break; |
| case 'n': |
| sendnotifies = 1; |
| break; |
| case 'U': |
| udpaddr = estrdup(netmkaddr(EARGF(usage()), "udp", "domain")); |
| break; |
| case 'T': |
| tcpaddr = estrdup(netmkaddr(EARGF(usage()), "tcp", "domain")); |
| break; |
| default: |
| usage(); |
| }ARGEND |
| |
| if(argc) |
| usage(); |
| if(serveudp && servetcp) |
| checkaddress(); |
| |
| rfork(RFNOTEG); |
| |
| /* start syslog before we fork */ |
| fmtinstall('F', fcallfmt); |
| dninit(); |
| if(myipaddr(ipaddr, mntpt) < 0) |
| sysfatal("can't read my ip address"); |
| |
| syslog(0, logfile, "starting dns on %I", ipaddr); |
| |
| opendatabase(); |
| |
| mountinit(service); |
| |
| now = time(0); |
| srand(now*getpid()); |
| db2cache(1); |
| |
| if(serveudp) |
| proccreate(dnudpserver, nil, STACK); |
| if(servetcp) |
| proccreate(dntcpserver, nil, STACK); |
| if(sendnotifies) |
| proccreate(notifyproc, nil, STACK); |
| |
| io(); |
| } |
| |
| /* |
| * if a mount point is specified, set the cs extention to be the mount point |
| * with '_'s replacing '/'s |
| */ |
| void |
| setext(char *ext, int n, char *p) |
| { |
| int i, c; |
| |
| n--; |
| for(i = 0; i < n; i++){ |
| c = p[i]; |
| if(c == 0) |
| break; |
| if(c == '/') |
| c = '_'; |
| ext[i] = c; |
| } |
| ext[i] = 0; |
| } |
| |
| void |
| mountinit(char *service) |
| { |
| int p[2]; |
| |
| if(pipe(p) < 0) |
| abort(); /* "pipe failed" */; |
| if(post9pservice(p[1], service, nil) < 0) |
| fprint(2, "post9pservice dns: %r\n"); |
| close(p[1]); |
| mfd[0] = mfd[1] = p[0]; |
| } |
| |
| Mfile* |
| newfid(int fid, int needunused) |
| { |
| Mfile *mf; |
| |
| lock(&mfalloc.lk); |
| for(mf = mfalloc.inuse; mf != nil; mf = mf->next){ |
| if(mf->fid == fid){ |
| unlock(&mfalloc.lk); |
| if(needunused) |
| return nil; |
| return mf; |
| } |
| } |
| if(!needunused){ |
| unlock(&mfalloc.lk); |
| return nil; |
| } |
| mf = emalloc(sizeof(*mf)); |
| if(mf == nil) |
| sysfatal("out of memory"); |
| mf->fid = fid; |
| mf->next = mfalloc.inuse; |
| mfalloc.inuse = mf; |
| unlock(&mfalloc.lk); |
| return mf; |
| } |
| |
| void |
| freefid(Mfile *mf) |
| { |
| Mfile **l; |
| |
| lock(&mfalloc.lk); |
| for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next){ |
| if(*l == mf){ |
| *l = mf->next; |
| if(mf->user) |
| free(mf->user); |
| free(mf); |
| unlock(&mfalloc.lk); |
| return; |
| } |
| } |
| sysfatal("freeing unused fid"); |
| } |
| |
| Mfile* |
| copyfid(Mfile *mf, int fid) |
| { |
| Mfile *nmf; |
| |
| nmf = newfid(fid, 1); |
| if(nmf == nil) |
| return nil; |
| nmf->fid = fid; |
| nmf->user = estrdup(mf->user); |
| nmf->qid.type = mf->qid.type; |
| nmf->qid.path = mf->qid.path; |
| nmf->qid.vers = vers++; |
| return nmf; |
| } |
| |
| Job* |
| newjob(void) |
| { |
| Job *job; |
| |
| job = emalloc(sizeof(*job)); |
| lock(&joblock); |
| job->next = joblist; |
| joblist = job; |
| job->request.tag = -1; |
| unlock(&joblock); |
| return job; |
| } |
| |
| void |
| freejob(Job *job) |
| { |
| Job **l; |
| |
| lock(&joblock); |
| for(l = &joblist; *l; l = &(*l)->next){ |
| if((*l) == job){ |
| *l = job->next; |
| free(job); |
| break; |
| } |
| } |
| unlock(&joblock); |
| } |
| |
| void |
| flushjob(int tag) |
| { |
| Job *job; |
| |
| lock(&joblock); |
| for(job = joblist; job; job = job->next){ |
| if(job->request.tag == tag && job->request.type != Tflush){ |
| job->flushed = 1; |
| break; |
| } |
| } |
| unlock(&joblock); |
| } |
| |
| void |
| ioproc0(void *v) |
| { |
| long n; |
| Mfile *mf; |
| uchar mdata[IOHDRSZ + Maxfdata]; |
| Request req; |
| Job *job; |
| |
| USED(v); |
| |
| for(;;){ |
| n = read9pmsg(mfd[0], mdata, sizeof mdata); |
| if(n <= 0){ |
| syslog(0, logfile, "error reading mntpt: %r"); |
| break; |
| } |
| job = newjob(); |
| if(convM2S(mdata, n, &job->request) != n){ |
| freejob(job); |
| continue; |
| } |
| if(debug) |
| syslog(0, logfile, "%F", &job->request); |
| |
| getactivity(&req); |
| req.aborttime = now + 60; /* don't spend more than 60 seconds */ |
| |
| mf = nil; |
| switch(job->request.type){ |
| case Tversion: |
| case Tauth: |
| case Tflush: |
| break; |
| case Tattach: |
| mf = newfid(job->request.fid, 1); |
| if(mf == nil){ |
| sendmsg(job, "fid in use"); |
| goto skip; |
| } |
| break; |
| default: |
| mf = newfid(job->request.fid, 0); |
| if(mf == nil){ |
| sendmsg(job, "unknown fid"); |
| goto skip; |
| } |
| break; |
| } |
| |
| switch(job->request.type){ |
| default: |
| syslog(1, logfile, "unknown request type %d", job->request.type); |
| break; |
| case Tversion: |
| rversion(job); |
| break; |
| case Tauth: |
| rauth(job); |
| break; |
| case Tflush: |
| rflush(job); |
| break; |
| case Tattach: |
| rattach(job, mf); |
| break; |
| case Twalk: |
| rwalk(job, mf); |
| break; |
| case Topen: |
| ropen(job, mf); |
| break; |
| case Tcreate: |
| rcreate(job, mf); |
| break; |
| case Tread: |
| rread(job, mf); |
| break; |
| case Twrite: |
| rwrite(job, mf, &req); |
| break; |
| case Tclunk: |
| rclunk(job, mf); |
| break; |
| case Tremove: |
| rremove(job, mf); |
| break; |
| case Tstat: |
| rstat(job, mf); |
| break; |
| case Twstat: |
| rwstat(job, mf); |
| break; |
| } |
| skip: |
| freejob(job); |
| putactivity(); |
| } |
| } |
| |
| void |
| io(void) |
| { |
| int i; |
| |
| for(i=0; i<Maxactive; i++) |
| proccreate(ioproc0, 0, STACK); |
| } |
| |
| void |
| rversion(Job *job) |
| { |
| if(job->request.msize > IOHDRSZ + Maxfdata) |
| job->reply.msize = IOHDRSZ + Maxfdata; |
| else |
| job->reply.msize = job->request.msize; |
| if(strncmp(job->request.version, "9P2000", 6) != 0) |
| sendmsg(job, "unknown 9P version"); |
| else{ |
| job->reply.version = "9P2000"; |
| sendmsg(job, 0); |
| } |
| } |
| |
| void |
| rauth(Job *job) |
| { |
| sendmsg(job, "dns: authentication not required"); |
| } |
| |
| /* |
| * don't flush till all the slaves are done |
| */ |
| void |
| rflush(Job *job) |
| { |
| flushjob(job->request.oldtag); |
| sendmsg(job, 0); |
| } |
| |
| void |
| rattach(Job *job, Mfile *mf) |
| { |
| if(mf->user != nil) |
| free(mf->user); |
| mf->user = estrdup(job->request.uname); |
| mf->qid.vers = vers++; |
| mf->qid.type = QTDIR; |
| mf->qid.path = 0LL; |
| job->reply.qid = mf->qid; |
| sendmsg(job, 0); |
| } |
| |
| char* |
| rwalk(Job *job, Mfile *mf) |
| { |
| char *err; |
| char **elems; |
| int nelems; |
| int i; |
| Mfile *nmf; |
| Qid qid; |
| |
| err = 0; |
| nmf = nil; |
| elems = job->request.wname; |
| nelems = job->request.nwname; |
| job->reply.nwqid = 0; |
| |
| if(job->request.newfid != job->request.fid){ |
| /* clone fid */ |
| if(job->request.newfid<0){ |
| err = "clone newfid out of range"; |
| goto send; |
| } |
| nmf = copyfid(mf, job->request.newfid); |
| if(nmf == nil){ |
| err = "clone bad newfid"; |
| goto send; |
| } |
| mf = nmf; |
| } |
| /* else nmf will be nil */ |
| |
| qid = mf->qid; |
| if(nelems > 0){ |
| /* walk fid */ |
| for(i=0; i<nelems && i<MAXWELEM; i++){ |
| if((qid.type & QTDIR) == 0){ |
| err = "not a directory"; |
| break; |
| } |
| if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){ |
| qid.type = QTDIR; |
| qid.path = Qdir; |
| Found: |
| job->reply.wqid[i] = qid; |
| job->reply.nwqid++; |
| continue; |
| } |
| if(strcmp(elems[i], "dns") == 0){ |
| qid.type = QTFILE; |
| qid.path = Qdns; |
| goto Found; |
| } |
| err = "file does not exist"; |
| break; |
| } |
| } |
| |
| send: |
| if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)) |
| freefid(nmf); |
| if(err == nil) |
| mf->qid = qid; |
| sendmsg(job, err); |
| return err; |
| } |
| |
| void |
| ropen(Job *job, Mfile *mf) |
| { |
| int mode; |
| char *err; |
| |
| err = 0; |
| mode = job->request.mode; |
| if(mf->qid.type & QTDIR){ |
| if(mode) |
| err = "permission denied"; |
| } |
| job->reply.qid = mf->qid; |
| job->reply.iounit = 0; |
| sendmsg(job, err); |
| } |
| |
| void |
| rcreate(Job *job, Mfile *mf) |
| { |
| USED(mf); |
| sendmsg(job, "creation permission denied"); |
| } |
| |
| void |
| rread(Job *job, Mfile *mf) |
| { |
| int i, n, cnt; |
| long off; |
| Dir dir; |
| uchar buf[Maxfdata]; |
| char *err; |
| long clock; |
| |
| n = 0; |
| err = 0; |
| off = job->request.offset; |
| cnt = job->request.count; |
| if(mf->qid.type & QTDIR){ |
| clock = time(0); |
| if(off == 0){ |
| dir.name = "dns"; |
| dir.qid.type = QTFILE; |
| dir.qid.vers = vers; |
| dir.qid.path = Qdns; |
| dir.mode = 0666; |
| dir.length = 0; |
| dir.uid = mf->user; |
| dir.gid = mf->user; |
| dir.muid = mf->user; |
| dir.atime = clock; /* wrong */ |
| dir.mtime = clock; /* wrong */ |
| n = convD2M(&dir, buf, sizeof buf); |
| } |
| job->reply.data = (char*)buf; |
| } else { |
| for(i = 1; i <= mf->nrr; i++) |
| if(mf->rr[i] > off) |
| break; |
| if(i > mf->nrr) |
| goto send; |
| if(off + cnt > mf->rr[i]) |
| n = mf->rr[i] - off; |
| else |
| n = cnt; |
| job->reply.data = mf->reply + off; |
| } |
| send: |
| job->reply.count = n; |
| sendmsg(job, err); |
| } |
| |
| void |
| rwrite(Job *job, Mfile *mf, Request *req) |
| { |
| int cnt, rooted, status; |
| long n; |
| char *err, *p, *atype; |
| RR *rp, *tp, *neg; |
| int wantsav; |
| static char *dumpfile; |
| |
| err = 0; |
| cnt = job->request.count; |
| if(mf->qid.type & QTDIR){ |
| err = "can't write directory"; |
| goto send; |
| } |
| if(cnt >= Maxrequest){ |
| err = "request too long"; |
| goto send; |
| } |
| job->request.data[cnt] = 0; |
| if(cnt > 0 && job->request.data[cnt-1] == '\n') |
| job->request.data[cnt-1] = 0; |
| |
| /* |
| * special commands |
| */ |
| p = job->request.data; |
| if(strcmp(p, "debug")==0){ |
| debug ^= 1; |
| goto send; |
| } else if(strcmp(p, "dump")==0){ |
| if(dumpfile == nil) |
| dumpfile = unsharp("#9/ndb/dnsdump"); |
| dndump(dumpfile); |
| goto send; |
| } else if(strncmp(p, "dump ", 5) == 0){ |
| if(*(p+5)) |
| dndump(p+5); |
| else |
| err = "bad filename"; |
| goto send; |
| } else if(strcmp(p, "refresh")==0){ |
| needrefresh = 1; |
| goto send; |
| } |
| |
| /* |
| * kill previous reply |
| */ |
| mf->nrr = 0; |
| mf->rr[0] = 0; |
| |
| /* |
| * break up request (into a name and a type) |
| */ |
| atype = strchr(job->request.data, ' '); |
| if(atype == 0){ |
| err = "illegal request"; |
| goto send; |
| } else |
| *atype++ = 0; |
| |
| /* |
| * tracing request |
| */ |
| if(strcmp(atype, "trace") == 0){ |
| if(trace) |
| free(trace); |
| if(*job->request.data) |
| trace = estrdup(job->request.data); |
| else |
| trace = 0; |
| goto send; |
| } |
| |
| mf->type = rrtype(atype); |
| if(mf->type < 0){ |
| err = "unknown type"; |
| goto send; |
| } |
| |
| p = atype - 2; |
| if(p >= job->request.data && *p == '.'){ |
| rooted = 1; |
| *p = 0; |
| } else |
| rooted = 0; |
| |
| p = job->request.data; |
| if(*p == '!'){ |
| wantsav = 1; |
| p++; |
| } else |
| wantsav = 0; |
| dncheck(0, 1); |
| rp = dnresolve(p, Cin, mf->type, req, 0, 0, Recurse, rooted, &status); |
| dncheck(0, 1); |
| neg = rrremneg(&rp); |
| if(neg){ |
| status = neg->negrcode; |
| rrfreelist(neg); |
| } |
| if(rp == 0){ |
| switch(status){ |
| case Rname: |
| err = "name does not exist"; |
| break; |
| case Rserver: |
| err = "dns failure"; |
| break; |
| default: |
| err = "resource does not exist"; |
| break; |
| } |
| } else { |
| lock(&joblock); |
| if(!job->flushed){ |
| /* format data to be read later */ |
| n = 0; |
| mf->nrr = 0; |
| for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp && |
| tsame(mf->type, tp->type); tp = tp->next){ |
| mf->rr[mf->nrr++] = n; |
| if(wantsav) |
| n += snprint(mf->reply+n, Maxreply-n, "%Q", tp); |
| else |
| n += snprint(mf->reply+n, Maxreply-n, "%R", tp); |
| } |
| mf->rr[mf->nrr] = n; |
| } |
| unlock(&joblock); |
| rrfreelist(rp); |
| } |
| |
| send: |
| dncheck(0, 1); |
| job->reply.count = cnt; |
| sendmsg(job, err); |
| } |
| |
| void |
| rclunk(Job *job, Mfile *mf) |
| { |
| freefid(mf); |
| sendmsg(job, 0); |
| } |
| |
| void |
| rremove(Job *job, Mfile *mf) |
| { |
| USED(mf); |
| sendmsg(job, "remove permission denied"); |
| } |
| |
| void |
| rstat(Job *job, Mfile *mf) |
| { |
| Dir dir; |
| uchar buf[IOHDRSZ+Maxfdata]; |
| |
| if(mf->qid.type & QTDIR){ |
| dir.name = "."; |
| dir.mode = DMDIR|0555; |
| } else { |
| dir.name = "dns"; |
| dir.mode = 0666; |
| } |
| dir.qid = mf->qid; |
| dir.length = 0; |
| dir.uid = mf->user; |
| dir.gid = mf->user; |
| dir.muid = mf->user; |
| dir.atime = dir.mtime = time(0); |
| job->reply.nstat = convD2M(&dir, buf, sizeof buf); |
| job->reply.stat = buf; |
| sendmsg(job, 0); |
| } |
| |
| void |
| rwstat(Job *job, Mfile *mf) |
| { |
| USED(mf); |
| sendmsg(job, "wstat permission denied"); |
| } |
| |
| void |
| sendmsg(Job *job, char *err) |
| { |
| int n; |
| uchar mdata[IOHDRSZ + Maxfdata]; |
| char ename[ERRMAX]; |
| |
| if(err){ |
| job->reply.type = Rerror; |
| snprint(ename, sizeof(ename), "dns: %s", err); |
| job->reply.ename = ename; |
| }else{ |
| job->reply.type = job->request.type+1; |
| } |
| job->reply.tag = job->request.tag; |
| n = convS2M(&job->reply, mdata, sizeof mdata); |
| if(n == 0){ |
| syslog(1, logfile, "sendmsg convS2M of %F returns 0", &job->reply); |
| abort(); |
| } |
| lock(&joblock); |
| if(job->flushed == 0) |
| if(write(mfd[1], mdata, n)!=n) |
| sysfatal("mount write"); |
| unlock(&joblock); |
| if(debug) |
| syslog(0, logfile, "%F %d", &job->reply, n); |
| } |
| |
| /* |
| * the following varies between dnsdebug and dns |
| */ |
| void |
| logreply(int id, uchar *addr, DNSmsg *mp) |
| { |
| RR *rp; |
| |
| syslog(0, LOG, "%d: rcvd %I flags:%s%s%s%s%s", id, addr, |
| mp->flags & Fauth ? " auth" : "", |
| mp->flags & Ftrunc ? " trunc" : "", |
| mp->flags & Frecurse ? " rd" : "", |
| mp->flags & Fcanrec ? " ra" : "", |
| mp->flags & (Fauth|Rname) == (Fauth|Rname) ? |
| " nx" : ""); |
| for(rp = mp->qd; rp != nil; rp = rp->next) |
| syslog(0, LOG, "%d: rcvd %I qd %s", id, addr, rp->owner->name); |
| for(rp = mp->an; rp != nil; rp = rp->next) |
| syslog(0, LOG, "%d: rcvd %I an %R", id, addr, rp); |
| for(rp = mp->ns; rp != nil; rp = rp->next) |
| syslog(0, LOG, "%d: rcvd %I ns %R", id, addr, rp); |
| for(rp = mp->ar; rp != nil; rp = rp->next) |
| syslog(0, LOG, "%d: rcvd %I ar %R", id, addr, rp); |
| } |
| |
| void |
| logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type) |
| { |
| char buf[12]; |
| |
| syslog(0, LOG, "%d.%d: sending to %I/%s %s %s", |
| id, subid, addr, sname, rname, rrname(type, buf, sizeof buf)); |
| } |
| |
| RR* |
| getdnsservers(int class) |
| { |
| return dnsservers(class); |
| } |