summaryrefslogtreecommitdiff
path: root/sys/src/cmd/ndb/cs.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ndb/cs.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ndb/cs.c')
-rwxr-xr-xsys/src/cmd/ndb/cs.c1904
1 files changed, 1904 insertions, 0 deletions
diff --git a/sys/src/cmd/ndb/cs.c b/sys/src/cmd/ndb/cs.c
new file mode 100755
index 000000000..10ecee536
--- /dev/null
+++ b/sys/src/cmd/ndb/cs.c
@@ -0,0 +1,1904 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include <ip.h>
+#include <String.h>
+
+enum
+{
+ Nreply= 20,
+ Maxreply= 256,
+ Maxrequest= 128,
+ Maxpath= 128,
+ Maxfdata= 8192,
+ Maxhost= 64, /* maximum host name size */
+ Maxservice= 64, /* maximum service name size */
+
+ Qdir= 0,
+ Qcs= 1,
+};
+
+typedef struct Mfile Mfile;
+typedef struct Mlist Mlist;
+typedef struct Network Network;
+typedef struct Flushreq Flushreq;
+typedef struct Job Job;
+
+int vers; /* incremented each clone/attach */
+
+struct Mfile
+{
+ int busy;
+
+ char *user;
+ Qid qid;
+ int fid;
+
+ /*
+ * current request
+ */
+ char *net;
+ char *host;
+ char *serv;
+ char *rem;
+
+ /*
+ * result of the last lookup
+ */
+ Network *nextnet;
+ int nreply;
+ char *reply[Nreply];
+ int replylen[Nreply];
+};
+
+struct Mlist
+{
+ Mlist *next;
+ Mfile mf;
+};
+
+
+/*
+ * active requests
+ */
+struct Job
+{
+ Job *next;
+ int flushed;
+ Fcall request;
+ Fcall reply;
+};
+Lock joblock;
+Job *joblist;
+
+Mlist *mlist;
+int mfd[2];
+int debug;
+int paranoia;
+int ipv6lookups = 1;
+jmp_buf masterjmp; /* return through here after a slave process has been created */
+int *isslave; /* *isslave non-zero means this is a slave process */
+char *dbfile;
+Ndb *db, *netdb;
+
+void rversion(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*);
+void rclunk(Job*, Mfile*);
+void rremove(Job*, Mfile*);
+void rstat(Job*, Mfile*);
+void rwstat(Job*, Mfile*);
+void rauth(Job*);
+void sendmsg(Job*, char*);
+void error(char*);
+void mountinit(char*, char*);
+void io(void);
+void ndbinit(void);
+void netinit(int);
+void netadd(char*);
+char *genquery(Mfile*, char*);
+char* ipinfoquery(Mfile*, char**, int);
+int needproto(Network*, Ndbtuple*);
+int lookup(Mfile*);
+Ndbtuple* reorder(Ndbtuple*, Ndbtuple*);
+void ipid(void);
+void readipinterfaces(void);
+void* emalloc(int);
+char* estrdup(char*);
+Job* newjob(void);
+void freejob(Job*);
+void setext(char*, int, char*);
+void cleanmf(Mfile*);
+
+extern void paralloc(void);
+
+Lock dblock; /* mutex on database operations */
+Lock netlock; /* mutex for netinit() */
+
+char *logfile = "cs";
+char *paranoiafile = "cs.paranoia";
+
+char mntpt[Maxpath];
+char netndb[Maxpath];
+
+/*
+ * Network specific translators
+ */
+Ndbtuple* iplookup(Network*, char*, char*, int);
+char* iptrans(Ndbtuple*, Network*, char*, char*, int);
+Ndbtuple* telcolookup(Network*, char*, char*, int);
+char* telcotrans(Ndbtuple*, Network*, char*, char*, int);
+Ndbtuple* dnsiplookup(char*, Ndbs*);
+
+struct Network
+{
+ char *net;
+ Ndbtuple *(*lookup)(Network*, char*, char*, int);
+ char *(*trans)(Ndbtuple*, Network*, char*, char*, int);
+ int considered; /* flag: ignored for "net!"? */
+ int fasttimeouthack; /* flag. was for IL */
+ Network *next;
+};
+
+enum
+{
+ Ntcp = 0,
+};
+
+/*
+ * net doesn't apply to (r)udp, icmp(v6), or telco (for speed).
+ */
+Network network[] = {
+[Ntcp] { "tcp", iplookup, iptrans, 0 },
+ { "udp", iplookup, iptrans, 1 },
+ { "icmp", iplookup, iptrans, 1 },
+ { "icmpv6", iplookup, iptrans, 1 },
+ { "rudp", iplookup, iptrans, 1 },
+ { "telco", telcolookup, telcotrans, 1 },
+ { 0 },
+};
+
+Lock ipifclock;
+Ipifc *ipifcs;
+
+char eaddr[16]; /* ascii ethernet address */
+char ipaddr[64]; /* ascii internet address */
+uchar ipa[IPaddrlen]; /* binary internet address */
+char *mysysname;
+
+Network *netlist; /* networks ordered by preference */
+Network *last;
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+ strncpy(to, from, len);
+ to[len-1] = 0;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-dn] [-f ndb-file] [-x netmtpt]\n", argv0);
+ exits("usage");
+}
+
+/*
+ * based on libthread's threadsetname, but drags in less library code.
+ * actually just sets the arguments displayed.
+ */
+void
+procsetname(char *fmt, ...)
+{
+ int fd;
+ char *cmdname;
+ char buf[128];
+ va_list arg;
+
+ va_start(arg, fmt);
+ cmdname = vsmprint(fmt, arg);
+ va_end(arg);
+ if (cmdname == nil)
+ return;
+ snprint(buf, sizeof buf, "#p/%d/args", getpid());
+ if((fd = open(buf, OWRITE)) >= 0){
+ write(fd, cmdname, strlen(cmdname)+1);
+ close(fd);
+ }
+ free(cmdname);
+}
+
+void
+main(int argc, char *argv[])
+{
+ int justsetname;
+ char ext[Maxpath], servefile[Maxpath];
+
+ justsetname = 0;
+ setnetmtpt(mntpt, sizeof(mntpt), nil);
+ ext[0] = 0;
+ ARGBEGIN{
+ case '4':
+ ipv6lookups = 0;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'n':
+ justsetname = 1;
+ break;
+ case 'x':
+ setnetmtpt(mntpt, sizeof(mntpt), EARGF(usage()));
+ setext(ext, sizeof(ext), mntpt);
+ break;
+ }ARGEND
+ USED(argc);
+ USED(argv);
+
+ rfork(RFREND|RFNOTEG);
+
+ snprint(servefile, sizeof(servefile), "#s/cs%s", ext);
+ snprint(netndb, sizeof(netndb), "%s/ndb", mntpt);
+ unmount(servefile, mntpt);
+ remove(servefile);
+
+ fmtinstall('E', eipfmt);
+ fmtinstall('I', eipfmt);
+ fmtinstall('M', eipfmt);
+ fmtinstall('F', fcallfmt);
+
+ ndbinit();
+ netinit(0);
+
+ if(!justsetname){
+ mountinit(servefile, mntpt);
+ io();
+ }
+ exits(0);
+}
+
+/*
+ * 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, char *mntpt)
+{
+ int f;
+ int p[2];
+ char buf[32];
+
+ if(pipe(p) < 0)
+ error("pipe failed");
+
+ /*
+ * make a /srv/cs
+ */
+ f = create(service, OWRITE|ORCLOSE, 0666);
+ if(f < 0)
+ error(service);
+ snprint(buf, sizeof(buf), "%d", p[1]);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ error("write /srv/cs");
+
+ switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+ case 0:
+ close(p[1]);
+ procsetname("%s", mntpt);
+ break;
+ case -1:
+ error("fork failed\n");
+ default:
+ /*
+ * put ourselves into the file system
+ */
+ close(p[0]);
+ if(mount(p[1], -1, mntpt, MAFTER, "") < 0)
+ error("mount failed\n");
+ _exits(0);
+ }
+ mfd[0] = mfd[1] = p[0];
+}
+
+void
+ndbinit(void)
+{
+ db = ndbopen(dbfile);
+ if(db == nil)
+ error("can't open network database");
+
+ netdb = ndbopen(netndb);
+ if(netdb != nil){
+ netdb->nohash = 1;
+ db = ndbcat(netdb, db);
+ }
+}
+
+Mfile*
+newfid(int fid)
+{
+ Mlist *f, *ff;
+ Mfile *mf;
+
+ ff = 0;
+ for(f = mlist; f; f = f->next)
+ if(f->mf.busy && f->mf.fid == fid)
+ return &f->mf;
+ else if(!ff && !f->mf.busy)
+ ff = f;
+ if(ff == 0){
+ ff = emalloc(sizeof *f);
+ ff->next = mlist;
+ mlist = ff;
+ }
+ mf = &ff->mf;
+ memset(mf, 0, sizeof *mf);
+ mf->fid = fid;
+ return mf;
+}
+
+Job*
+newjob(void)
+{
+ Job *job;
+
+ job = mallocz(sizeof(Job), 1);
+ 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
+io(void)
+{
+ long n;
+ Mfile *mf;
+ int slaveflag;
+ uchar mdata[IOHDRSZ + Maxfdata];
+ Job *job;
+
+ /*
+ * if we ask dns to fulfill requests,
+ * a slave process is created to wait for replies. The
+ * master process returns immediately via a longjmp
+ * through 'masterjmp'.
+ *
+ * *isslave is a pointer into the call stack to a variable
+ * that tells whether or not the current process is a slave.
+ */
+ slaveflag = 0; /* init slave variable */
+ isslave = &slaveflag;
+ setjmp(masterjmp);
+
+ for(;;){
+ n = read9pmsg(mfd[0], mdata, sizeof mdata);
+ if(n<=0)
+ error("mount read");
+ job = newjob();
+ if(convM2S(mdata, n, &job->request) != n){
+ syslog(1, logfile, "format error %ux %ux %ux %ux %ux",
+ mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+ freejob(job);
+ continue;
+ }
+ lock(&dblock);
+ mf = newfid(job->request.fid);
+ if(debug)
+ syslog(0, logfile, "%F", &job->request);
+
+
+ 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);
+ 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;
+ }
+ unlock(&dblock);
+
+ freejob(job);
+
+ /*
+ * slave processes die after replying
+ */
+ if(*isslave){
+ if(debug)
+ syslog(0, logfile, "slave death %d", getpid());
+ _exits(0);
+ }
+ }
+}
+
+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, "cs: 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->busy == 0){
+ mf->busy = 1;
+ 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 */
+ nmf = newfid(job->request.newfid);
+ if(nmf->busy){
+ nmf = nil;
+ err = "clone to used channel";
+ goto send;
+ }
+ *nmf = *mf;
+ nmf->user = estrdup(mf->user);
+ nmf->fid = job->request.newfid;
+ nmf->qid.vers = vers++;
+ 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], "cs") == 0){
+ qid.type = QTFILE;
+ qid.path = Qcs;
+ goto Found;
+ }
+ err = "file does not exist";
+ break;
+ }
+ }
+
+ send:
+ if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)){
+ cleanmf(nmf);
+ free(nmf->user);
+ nmf->user = 0;
+ nmf->busy = 0;
+ nmf->fid = 0;
+ }
+ 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, toff, clock;
+ Dir dir;
+ uchar buf[Maxfdata];
+ char *err;
+
+ n = 0;
+ err = 0;
+ off = job->request.offset;
+ cnt = job->request.count;
+ if(mf->qid.type & QTDIR){
+ clock = time(0);
+ if(off == 0){
+ memset(&dir, 0, sizeof dir);
+ dir.name = "cs";
+ dir.qid.type = QTFILE;
+ dir.qid.vers = vers;
+ dir.qid.path = Qcs;
+ 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(;;){
+ /* look for an answer at the right offset */
+ toff = 0;
+ for(i = 0; mf->reply[i] && i < mf->nreply; i++){
+ n = mf->replylen[i];
+ if(off < toff + n)
+ break;
+ toff += n;
+ }
+ if(i < mf->nreply)
+ break; /* got something to return */
+
+ /* try looking up more answers */
+ if(lookup(mf) == 0){
+ /* no more */
+ n = 0;
+ goto send;
+ }
+ }
+
+ /* give back a single reply (or part of one) */
+ job->reply.data = mf->reply[i] + (off - toff);
+ if(cnt > toff - off + n)
+ n = toff - off + n;
+ else
+ n = cnt;
+ }
+send:
+ job->reply.count = n;
+ sendmsg(job, err);
+}
+void
+cleanmf(Mfile *mf)
+{
+ int i;
+
+ if(mf->net != nil){
+ free(mf->net);
+ mf->net = nil;
+ }
+ if(mf->host != nil){
+ free(mf->host);
+ mf->host = nil;
+ }
+ if(mf->serv != nil){
+ free(mf->serv);
+ mf->serv = nil;
+ }
+ if(mf->rem != nil){
+ free(mf->rem);
+ mf->rem = nil;
+ }
+ for(i = 0; i < mf->nreply; i++){
+ free(mf->reply[i]);
+ mf->reply[i] = nil;
+ mf->replylen[i] = 0;
+ }
+ mf->nreply = 0;
+ mf->nextnet = netlist;
+}
+
+void
+rwrite(Job *job, Mfile *mf)
+{
+ int cnt, n;
+ char *err;
+ char *field[4];
+ char curerr[64];
+
+ 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;
+
+ /*
+ * toggle debugging
+ */
+ if(strncmp(job->request.data, "debug", 5)==0){
+ debug ^= 1;
+ syslog(1, logfile, "debug %d", debug);
+ goto send;
+ }
+
+ /*
+ * toggle ipv6 lookups
+ */
+ if(strncmp(job->request.data, "ipv6", 4)==0){
+ ipv6lookups ^= 1;
+ syslog(1, logfile, "ipv6lookups %d", ipv6lookups);
+ goto send;
+ }
+
+ /*
+ * toggle debugging
+ */
+ if(strncmp(job->request.data, "paranoia", 8)==0){
+ paranoia ^= 1;
+ syslog(1, logfile, "paranoia %d", paranoia);
+ goto send;
+ }
+
+ /*
+ * add networks to the default list
+ */
+ if(strncmp(job->request.data, "add ", 4)==0){
+ if(job->request.data[cnt-1] == '\n')
+ job->request.data[cnt-1] = 0;
+ netadd(job->request.data+4);
+ readipinterfaces();
+ goto send;
+ }
+
+ /*
+ * refresh all state
+ */
+ if(strncmp(job->request.data, "refresh", 7)==0){
+ netinit(1);
+ goto send;
+ }
+
+ /* start transaction with a clean slate */
+ cleanmf(mf);
+
+ /*
+ * look for a general query
+ */
+ if(*job->request.data == '!'){
+ err = genquery(mf, job->request.data+1);
+ goto send;
+ }
+
+ if(debug)
+ syslog(0, logfile, "write %s", job->request.data);
+ if(paranoia)
+ syslog(0, paranoiafile, "write %s by %s", job->request.data, mf->user);
+
+ /*
+ * break up name
+ */
+ n = getfields(job->request.data, field, 4, 1, "!");
+ switch(n){
+ case 1:
+ mf->net = strdup("net");
+ mf->host = strdup(field[0]);
+ break;
+ case 4:
+ mf->rem = strdup(field[3]);
+ /* fall through */
+ case 3:
+ mf->serv = strdup(field[2]);
+ /* fall through */
+ case 2:
+ mf->host = strdup(field[1]);
+ mf->net = strdup(field[0]);
+ break;
+ }
+
+ /*
+ * do the first net worth of lookup
+ */
+ if(lookup(mf) == 0){
+ rerrstr(curerr, sizeof curerr);
+ err = curerr;
+ }
+send:
+ job->reply.count = cnt;
+ sendmsg(job, err);
+}
+
+void
+rclunk(Job *job, Mfile *mf)
+{
+ cleanmf(mf);
+ free(mf->user);
+ mf->user = 0;
+ mf->busy = 0;
+ mf->fid = 0;
+ 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];
+
+ memset(&dir, 0, sizeof dir);
+ if(mf->qid.type & QTDIR){
+ dir.name = ".";
+ dir.mode = DMDIR|0555;
+ } else {
+ dir.name = "cs";
+ 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), "cs: %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)
+ error("mount write");
+ unlock(&joblock);
+ if(debug)
+ syslog(0, logfile, "%F %d", &job->reply, n);
+}
+
+void
+error(char *s)
+{
+ syslog(1, "cs", "%s: %r", s);
+ _exits(0);
+}
+
+static int
+isvalidip(uchar *ip)
+{
+ return ipcmp(ip, IPnoaddr) != 0 && ipcmp(ip, v4prefix) != 0;
+}
+
+static uchar loopbacknet[IPaddrlen] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0xff, 0xff,
+ 127, 0, 0, 0
+};
+static uchar loopbackmask[IPaddrlen] = {
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0, 0, 0
+};
+
+void
+readipinterfaces(void)
+{
+ if(myipaddr(ipa, mntpt) != 0)
+ ipmove(ipa, IPnoaddr);
+ sprint(ipaddr, "%I", ipa);
+ if (debug)
+ syslog(0, "dns", "ipaddr is %s\n", ipaddr);
+}
+
+/*
+ * get the system name
+ */
+void
+ipid(void)
+{
+ uchar addr[6];
+ Ndbtuple *t, *tt;
+ char *p, *attr;
+ Ndbs s;
+ int f;
+ char buf[Maxpath];
+
+ /* use environment, ether addr, or ipaddr to get system name */
+ if(mysysname == 0){
+ /*
+ * environment has priority.
+ *
+ * on the sgi power the default system name
+ * is the ip address. ignore that.
+ *
+ */
+ p = getenv("sysname");
+ if(p && *p){
+ attr = ipattr(p);
+ if(strcmp(attr, "ip") != 0)
+ mysysname = strdup(p);
+ }
+
+ /*
+ * the /net/ndb contains what the network
+ * figured out from DHCP. use that name if
+ * there is one.
+ */
+ if(mysysname == 0 && netdb != nil){
+ ndbreopen(netdb);
+ for(tt = t = ndbparse(netdb); t != nil; t = t->entry){
+ if(strcmp(t->attr, "sys") == 0){
+ mysysname = strdup(t->val);
+ break;
+ }
+ }
+ ndbfree(tt);
+ }
+
+ /* next network database, ip address, and ether address to find a name */
+ if(mysysname == 0){
+ t = nil;
+ if(isvalidip(ipa))
+ free(ndbgetvalue(db, &s, "ip", ipaddr, "sys", &t));
+ if(t == nil){
+ for(f = 0; f < 3; f++){
+ snprint(buf, sizeof buf, "%s/ether%d", mntpt, f);
+ if(myetheraddr(addr, buf) >= 0){
+ snprint(eaddr, sizeof(eaddr), "%E", addr);
+ free(ndbgetvalue(db, &s, "ether", eaddr, "sys", &t));
+ if(t != nil)
+ break;
+ }
+ }
+ }
+ for(tt = t; tt != nil; tt = tt->entry){
+ if(strcmp(tt->attr, "sys") == 0){
+ mysysname = strdup(tt->val);
+ break;
+ }
+ }
+ ndbfree(t);
+ }
+
+ /* nothing else worked, use the ip address */
+ if(mysysname == 0 && isvalidip(ipa))
+ mysysname = strdup(ipaddr);
+
+
+ /* set /dev/sysname if we now know it */
+ if(mysysname){
+ f = open("/dev/sysname", OWRITE);
+ if(f >= 0){
+ write(f, mysysname, strlen(mysysname));
+ close(f);
+ }
+ }
+ }
+}
+
+/*
+ * Set up a list of default networks by looking for
+ * /net/^*^/clone.
+ */
+void
+netinit(int background)
+{
+ char clone[Maxpath];
+ Network *np;
+ static int working;
+
+ if(background){
+ switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+ case 0:
+ break;
+ default:
+ return;
+ }
+ lock(&netlock);
+ }
+
+ /* add the mounted networks to the default list */
+ for(np = network; np->net; np++){
+ if(np->considered)
+ continue;
+ snprint(clone, sizeof(clone), "%s/%s/clone", mntpt, np->net);
+ if(access(clone, AEXIST) < 0)
+ continue;
+ if(netlist)
+ last->next = np;
+ else
+ netlist = np;
+ last = np;
+ np->next = 0;
+ np->considered = 1;
+ }
+
+ /* find out what our ip address is */
+ readipinterfaces();
+
+ /* set the system name if we need to, these days ip is all we have */
+ ipid();
+
+ if(debug)
+ syslog(0, logfile, "mysysname %s eaddr %s ipaddr %s ipa %I\n",
+ mysysname?mysysname:"???", eaddr, ipaddr, ipa);
+
+ if(background){
+ unlock(&netlock);
+ _exits(0);
+ }
+}
+
+/*
+ * add networks to the standard list
+ */
+void
+netadd(char *p)
+{
+ Network *np;
+ char *field[12];
+ int i, n;
+
+ n = getfields(p, field, 12, 1, " ");
+ for(i = 0; i < n; i++){
+ for(np = network; np->net; np++){
+ if(strcmp(field[i], np->net) != 0)
+ continue;
+ if(np->considered)
+ break;
+ if(netlist)
+ last->next = np;
+ else
+ netlist = np;
+ last = np;
+ np->next = 0;
+ np->considered = 1;
+ }
+ }
+}
+
+int
+lookforproto(Ndbtuple *t, char *proto)
+{
+ for(; t != nil; t = t->entry)
+ if(strcmp(t->attr, "proto") == 0 && strcmp(t->val, proto) == 0)
+ return 1;
+ return 0;
+}
+
+/*
+ * lookup a request. the network "net" means we should pick the
+ * best network to get there.
+ */
+int
+lookup(Mfile *mf)
+{
+ Network *np;
+ char *cp;
+ Ndbtuple *nt, *t;
+ char reply[Maxreply];
+ int i, rv;
+ int hack;
+
+ /* open up the standard db files */
+ if(db == 0)
+ ndbinit();
+ if(db == 0)
+ error("can't open mf->network database\n");
+
+ rv = 0;
+
+ if(mf->net == nil)
+ return 0; /* must have been a genquery */
+
+ if(strcmp(mf->net, "net") == 0){
+ /*
+ * go through set of default nets
+ */
+ for(np = mf->nextnet; np; np = np->next){
+ nt = (*np->lookup)(np, mf->host, mf->serv, 1);
+ if(nt == nil)
+ continue;
+ hack = np->fasttimeouthack && !lookforproto(nt, np->net);
+ for(t = nt; mf->nreply < Nreply && t; t = t->entry){
+ cp = (*np->trans)(t, np, mf->serv, mf->rem, hack);
+ if(cp){
+ /* avoid duplicates */
+ for(i = 0; i < mf->nreply; i++)
+ if(strcmp(mf->reply[i], cp) == 0)
+ break;
+ if(i == mf->nreply){
+ /* save the reply */
+ mf->replylen[mf->nreply] = strlen(cp);
+ mf->reply[mf->nreply++] = cp;
+ rv++;
+ }
+ }
+ }
+ ndbfree(nt);
+ np = np->next;
+ break;
+ }
+ mf->nextnet = np;
+ return rv;
+ }
+
+ /*
+ * if not /net, we only get one lookup
+ */
+ if(mf->nreply != 0)
+ return 0;
+ /*
+ * look for a specific network
+ */
+ for(np = netlist; np && np->net != nil; np++){
+ if(np->fasttimeouthack)
+ continue;
+ if(strcmp(np->net, mf->net) == 0)
+ break;
+ }
+
+ if(np && np->net != nil){
+ /*
+ * known network
+ */
+ nt = (*np->lookup)(np, mf->host, mf->serv, 1);
+ for(t = nt; mf->nreply < Nreply && t; t = t->entry){
+ cp = (*np->trans)(t, np, mf->serv, mf->rem, 0);
+ if(cp){
+ mf->replylen[mf->nreply] = strlen(cp);
+ mf->reply[mf->nreply++] = cp;
+ rv++;
+ }
+ }
+ ndbfree(nt);
+ return rv;
+ } else {
+ /*
+ * not a known network, don't translate host or service
+ */
+ if(mf->serv)
+ snprint(reply, sizeof(reply), "%s/%s/clone %s!%s",
+ mntpt, mf->net, mf->host, mf->serv);
+ else
+ snprint(reply, sizeof(reply), "%s/%s/clone %s",
+ mntpt, mf->net, mf->host);
+ mf->reply[0] = strdup(reply);
+ mf->replylen[0] = strlen(reply);
+ mf->nreply = 1;
+ return 1;
+ }
+}
+
+/*
+ * translate an ip service name into a port number. If it's a numeric port
+ * number, look for restricted access.
+ *
+ * the service '*' needs no translation.
+ */
+char*
+ipserv(Network *np, char *name, char *buf, int blen)
+{
+ char *p;
+ int alpha = 0;
+ int restr = 0;
+ char port[10];
+ Ndbtuple *t, *nt;
+ Ndbs s;
+
+ /* '*' means any service */
+ if(strcmp(name, "*")==0){
+ strcpy(buf, name);
+ return buf;
+ }
+
+ /* see if it's numeric or symbolic */
+ port[0] = 0;
+ for(p = name; *p; p++){
+ if(isdigit(*p))
+ {}
+ else if(isalpha(*p) || *p == '-' || *p == '$')
+ alpha = 1;
+ else
+ return 0;
+ }
+ t = nil;
+ p = nil;
+ if(alpha){
+ p = ndbgetvalue(db, &s, np->net, name, "port", &t);
+ if(p == nil)
+ return 0;
+ } else {
+ /* look up only for tcp ports < 1024 to get the restricted
+ * attribute
+ */
+ if(atoi(name) < 1024 && strcmp(np->net, "tcp") == 0)
+ p = ndbgetvalue(db, &s, "port", name, "port", &t);
+ if(p == nil)
+ p = strdup(name);
+ }
+
+ if(t){
+ for(nt = t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "restricted") == 0)
+ restr = 1;
+ ndbfree(t);
+ }
+ snprint(buf, blen, "%s%s", p, restr ? "!r" : "");
+ free(p);
+
+ return buf;
+}
+
+/*
+ * lookup an ip attribute
+ */
+int
+ipattrlookup(Ndb *db, char *ipa, char *attr, char *val, int vlen)
+{
+
+ Ndbtuple *t, *nt;
+ char *alist[2];
+
+ alist[0] = attr;
+ t = ndbipinfo(db, "ip", ipa, alist, 1);
+ if(t == nil)
+ return 0;
+ for(nt = t; nt != nil; nt = nt->entry){
+ if(strcmp(nt->attr, attr) == 0){
+ nstrcpy(val, nt->val, vlen);
+ ndbfree(t);
+ return 1;
+ }
+ }
+
+ /* we shouldn't get here */
+ ndbfree(t);
+ return 0;
+}
+
+/*
+ * lookup (and translate) an ip destination
+ */
+Ndbtuple*
+iplookup(Network *np, char *host, char *serv, int nolookup)
+{
+ char *attr, *dnsname;
+ Ndbtuple *t, *nt;
+ Ndbs s;
+ char ts[Maxservice];
+ char dollar[Maxhost];
+ uchar ip[IPaddrlen];
+ uchar net[IPaddrlen];
+ uchar tnet[IPaddrlen];
+ Ipifc *ifc;
+ Iplifc *lifc;
+
+ USED(nolookup);
+
+ /*
+ * start with the service since it's the most likely to fail
+ * and costs the least
+ */
+ werrstr("can't translate address");
+ if(serv==0 || ipserv(np, serv, ts, sizeof ts) == 0){
+ werrstr("can't translate service");
+ return 0;
+ }
+
+ /* for dial strings with no host */
+ if(strcmp(host, "*") == 0)
+ return ndbnew("ip", "*");
+
+ /*
+ * hack till we go v6 :: = 0.0.0.0
+ */
+ if(strcmp("::", host) == 0)
+ return ndbnew("ip", "*");
+
+ /*
+ * '$' means the rest of the name is an attribute that we
+ * need to search for
+ */
+ if(*host == '$'){
+ if(ipattrlookup(db, ipaddr, host+1, dollar, sizeof dollar))
+ host = dollar;
+ }
+
+ /*
+ * turn '[ip address]' into just 'ip address'
+ */
+ if(*host == '[' && host[strlen(host)-1] == ']'){
+ host++;
+ host[strlen(host)-1] = 0;
+ }
+
+ /*
+ * just accept addresses
+ */
+ attr = ipattr(host);
+ if(strcmp(attr, "ip") == 0)
+ return ndbnew("ip", host);
+
+ /*
+ * give the domain name server the first opportunity to
+ * resolve domain names. if that fails try the database.
+ */
+ t = 0;
+ werrstr("can't translate address");
+ if(strcmp(attr, "dom") == 0)
+ t = dnsiplookup(host, &s);
+ if(t == 0)
+ free(ndbgetvalue(db, &s, attr, host, "ip", &t));
+ if(t == 0){
+ dnsname = ndbgetvalue(db, &s, attr, host, "dom", nil);
+ if(dnsname){
+ t = dnsiplookup(dnsname, &s);
+ free(dnsname);
+ }
+ }
+ if(t == 0)
+ t = dnsiplookup(host, &s);
+ if(t == 0)
+ return 0;
+
+ /*
+ * reorder the tuple to have the matched line first and
+ * save that in the request structure.
+ */
+ t = reorder(t, s.t);
+
+ /*
+ * reorder according to our interfaces
+ */
+ lock(&ipifclock);
+ for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ maskip(lifc->ip, lifc->mask, net);
+ for(nt = t; nt; nt = nt->entry){
+ if(strcmp(nt->attr, "ip") != 0)
+ continue;
+ parseip(ip, nt->val);
+ maskip(ip, lifc->mask, tnet);
+ if(memcmp(net, tnet, IPaddrlen) == 0){
+ t = reorder(t, nt);
+ unlock(&ipifclock);
+ return t;
+ }
+ }
+ }
+ }
+ unlock(&ipifclock);
+
+ return t;
+}
+
+/*
+ * translate an ip address
+ */
+char*
+iptrans(Ndbtuple *t, Network *np, char *serv, char *rem, int hack)
+{
+ char ts[Maxservice];
+ char reply[Maxreply];
+ char x[Maxservice];
+
+ if(strcmp(t->attr, "ip") != 0)
+ return 0;
+
+ if(serv == 0 || ipserv(np, serv, ts, sizeof ts) == 0){
+ werrstr("can't translate service");
+ return 0;
+ }
+ if(rem != nil)
+ snprint(x, sizeof(x), "!%s", rem);
+ else
+ *x = 0;
+
+ if(*t->val == '*')
+ snprint(reply, sizeof(reply), "%s/%s/clone %s%s",
+ mntpt, np->net, ts, x);
+ else
+ snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s%s",
+ mntpt, np->net, t->val, ts, x, hack? "!fasttimeout": "");
+
+ return strdup(reply);
+}
+
+/*
+ * lookup a telephone number
+ */
+Ndbtuple*
+telcolookup(Network *np, char *host, char *serv, int nolookup)
+{
+ Ndbtuple *t;
+ Ndbs s;
+
+ USED(np, nolookup, serv);
+
+ werrstr("can't translate address");
+ free(ndbgetvalue(db, &s, "sys", host, "telco", &t));
+ if(t == 0)
+ return ndbnew("telco", host);
+
+ return reorder(t, s.t);
+}
+
+/*
+ * translate a telephone address
+ */
+char*
+telcotrans(Ndbtuple *t, Network *np, char *serv, char *rem, int)
+{
+ char reply[Maxreply];
+ char x[Maxservice];
+
+ if(strcmp(t->attr, "telco") != 0)
+ return 0;
+
+ if(rem != nil)
+ snprint(x, sizeof(x), "!%s", rem);
+ else
+ *x = 0;
+ if(serv)
+ snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
+ t->val, serv, x);
+ else
+ snprint(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
+ t->val, x);
+ return strdup(reply);
+}
+
+/*
+ * reorder the tuple to put x's line first in the entry
+ */
+Ndbtuple*
+reorder(Ndbtuple *t, Ndbtuple *x)
+{
+ Ndbtuple *nt;
+ Ndbtuple *line;
+
+ /* find start of this entry's line */
+ for(line = x; line->entry == line->line; line = line->line)
+ ;
+ line = line->line;
+ if(line == t)
+ return t; /* already the first line */
+
+ /* remove this line and everything after it from the entry */
+ for(nt = t; nt->entry != line; nt = nt->entry)
+ ;
+ nt->entry = 0;
+
+ /* make that the start of the entry */
+ for(nt = line; nt->entry; nt = nt->entry)
+ ;
+ nt->entry = t;
+ return line;
+}
+
+/*
+ * create a slave process to handle a request to avoid one request blocking
+ * another. parent returns to job loop.
+ */
+void
+slave(char *host)
+{
+ if(*isslave)
+ return; /* we're already a slave process */
+
+ switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+ case -1:
+ break;
+ case 0:
+ if(debug)
+ syslog(0, logfile, "slave %d", getpid());
+ procsetname("%s", host);
+ *isslave = 1;
+ break;
+ default:
+ longjmp(masterjmp, 1);
+ }
+
+}
+
+static Ndbtuple*
+dnsip6lookup(char *mntpt, char *buf, Ndbtuple *t)
+{
+ Ndbtuple *t6, *tt;
+
+ t6 = dnsquery(mntpt, buf, "ipv6"); /* lookup AAAA dns RRs */
+ if (t6 == nil)
+ return t;
+
+ /* convert ipv6 attr to ip */
+ for (tt = t6; tt != nil; tt = tt->entry)
+ if (strcmp(tt->attr, "ipv6") == 0)
+ strncpy(tt->attr, "ip", sizeof tt->attr - 1);
+
+ if (t == nil)
+ return t6;
+
+ /* append t6 list to t list */
+ for (tt = t; tt->entry != nil; tt = tt->entry)
+ ;
+ tt->entry = t6;
+ return t;
+}
+
+/*
+ * call the dns process and have it try to translate a name
+ */
+Ndbtuple*
+dnsiplookup(char *host, Ndbs *s)
+{
+ char buf[Maxreply];
+ Ndbtuple *t;
+
+ unlock(&dblock);
+
+ /* save the name before starting a slave */
+ snprint(buf, sizeof(buf), "%s", host);
+
+ slave(host);
+
+ if(strcmp(ipattr(buf), "ip") == 0)
+ t = dnsquery(mntpt, buf, "ptr");
+ else {
+ t = dnsquery(mntpt, buf, "ip");
+ /* special case: query ipv6 (AAAA dns RR) too */
+ if (ipv6lookups)
+ t = dnsip6lookup(mntpt, buf, t);
+ }
+ s->t = t;
+
+ if(t == nil){
+ rerrstr(buf, sizeof buf);
+ if(strstr(buf, "exist"))
+ werrstr("can't translate address: %s", buf);
+ else if(strstr(buf, "dns failure"))
+ werrstr("temporary problem: %s", buf);
+ }
+
+ lock(&dblock);
+ return t;
+}
+
+int
+qmatch(Ndbtuple *t, char **attr, char **val, int n)
+{
+ int i, found;
+ Ndbtuple *nt;
+
+ for(i = 1; i < n; i++){
+ found = 0;
+ for(nt = t; nt; nt = nt->entry)
+ if(strcmp(attr[i], nt->attr) == 0)
+ if(strcmp(val[i], "*") == 0
+ || strcmp(val[i], nt->val) == 0){
+ found = 1;
+ break;
+ }
+ if(found == 0)
+ break;
+ }
+ return i == n;
+}
+
+void
+qreply(Mfile *mf, Ndbtuple *t)
+{
+ Ndbtuple *nt;
+ String *s;
+
+ s = s_new();
+ for(nt = t; mf->nreply < Nreply && nt; nt = nt->entry){
+ s_append(s, nt->attr);
+ s_append(s, "=");
+ s_append(s, nt->val);
+
+ if(nt->line != nt->entry){
+ mf->replylen[mf->nreply] = s_len(s);
+ mf->reply[mf->nreply++] = strdup(s_to_c(s));
+ s_restart(s);
+ } else
+ s_append(s, " ");
+ }
+ s_free(s);
+}
+
+enum
+{
+ Maxattr= 32,
+};
+
+/*
+ * generic query lookup. The query is of one of the following
+ * forms:
+ *
+ * attr1=val1 attr2=val2 attr3=val3 ...
+ *
+ * returns the matching tuple
+ *
+ * ipinfo attr=val attr1 attr2 attr3 ...
+ *
+ * is like ipinfo and returns the attr{1-n}
+ * associated with the ip address.
+ */
+char*
+genquery(Mfile *mf, char *query)
+{
+ int i, n;
+ char *p;
+ char *attr[Maxattr];
+ char *val[Maxattr];
+ Ndbtuple *t;
+ Ndbs s;
+
+ n = getfields(query, attr, nelem(attr), 1, " ");
+ if(n == 0)
+ return "bad query";
+
+ if(strcmp(attr[0], "ipinfo") == 0)
+ return ipinfoquery(mf, attr, n);
+
+ /* parse pairs */
+ for(i = 0; i < n; i++){
+ p = strchr(attr[i], '=');
+ if(p == 0)
+ return "bad query";
+ *p++ = 0;
+ val[i] = p;
+ }
+
+ /* give dns a chance */
+ if((strcmp(attr[0], "dom") == 0 || strcmp(attr[0], "ip") == 0) && val[0]){
+ t = dnsiplookup(val[0], &s);
+ if(t){
+ if(qmatch(t, attr, val, n)){
+ qreply(mf, t);
+ ndbfree(t);
+ return 0;
+ }
+ ndbfree(t);
+ }
+ }
+
+ /* first pair is always the key. It can't be a '*' */
+ t = ndbsearch(db, &s, attr[0], val[0]);
+
+ /* search is the and of all the pairs */
+ while(t){
+ if(qmatch(t, attr, val, n)){
+ qreply(mf, t);
+ ndbfree(t);
+ return 0;
+ }
+
+ ndbfree(t);
+ t = ndbsnext(&s, attr[0], val[0]);
+ }
+
+ return "no match";
+}
+
+/*
+ * resolve an ip address
+ */
+static Ndbtuple*
+ipresolve(char *attr, char *host)
+{
+ Ndbtuple *t, *nt, **l;
+
+ t = iplookup(&network[Ntcp], host, "*", 0);
+ for(l = &t; *l != nil; ){
+ nt = *l;
+ if(strcmp(nt->attr, "ip") != 0){
+ *l = nt->entry;
+ nt->entry = nil;
+ ndbfree(nt);
+ continue;
+ }
+ strcpy(nt->attr, attr);
+ l = &nt->entry;
+ }
+ return t;
+}
+
+char*
+ipinfoquery(Mfile *mf, char **list, int n)
+{
+ int i, nresolve;
+ int resolve[Maxattr];
+ Ndbtuple *t, *nt, **l;
+ char *attr, *val;
+
+ /* skip 'ipinfo' */
+ list++; n--;
+
+ if(n < 1)
+ return "bad query";
+
+ /* get search attribute=value, or assume ip=myipaddr */
+ attr = *list;
+ if((val = strchr(attr, '=')) != nil){
+ *val++ = 0;
+ list++;
+ n--;
+ }else{
+ attr = "ip";
+ val = ipaddr;
+ }
+
+ if(n < 1)
+ return "bad query";
+
+ /*
+ * don't let ndbipinfo resolve the addresses, we're
+ * better at it.
+ */
+ nresolve = 0;
+ for(i = 0; i < n; i++)
+ if(*list[i] == '@'){ /* @attr=val ? */
+ list[i]++;
+ resolve[i] = 1; /* we'll resolve it */
+ nresolve++;
+ } else
+ resolve[i] = 0;
+
+ t = ndbipinfo(db, attr, val, list, n);
+ if(t == nil)
+ return "no match";
+
+ if(nresolve != 0){
+ for(l = &t; *l != nil;){
+ nt = *l;
+
+ /* already an address? */
+ if(strcmp(ipattr(nt->val), "ip") == 0){
+ l = &(*l)->entry;
+ continue;
+ }
+
+ /* user wants it resolved? */
+ for(i = 0; i < n; i++)
+ if(strcmp(list[i], nt->attr) == 0)
+ break;
+ if(i >= n || resolve[i] == 0){
+ l = &(*l)->entry;
+ continue;
+ }
+
+ /* resolve address and replace entry */
+ *l = ipresolve(nt->attr, nt->val);
+ while(*l != nil)
+ l = &(*l)->entry;
+ *l = nt->entry;
+
+ nt->entry = nil;
+ ndbfree(nt);
+ }
+ }
+
+ /* make it all one line */
+ for(nt = t; nt != nil; nt = nt->entry){
+ if(nt->entry == nil)
+ nt->line = t;
+ else
+ nt->line = nt->entry;
+ }
+
+ qreply(mf, t);
+
+ return nil;
+}
+
+void*
+emalloc(int size)
+{
+ void *x;
+
+ x = malloc(size);
+ if(x == nil)
+ abort();
+ memset(x, 0, size);
+ return x;
+}
+
+char*
+estrdup(char *s)
+{
+ int size;
+ char *p;
+
+ size = strlen(s)+1;
+ p = malloc(size);
+ if(p == nil)
+ abort();
+ memmove(p, s, size);
+ return p;
+}