summaryrefslogtreecommitdiff
path: root/sys/src/cmd/ratfs
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/ratfs
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ratfs')
-rwxr-xr-xsys/src/cmd/ratfs/ctlfiles.c397
-rwxr-xr-xsys/src/cmd/ratfs/main.c318
-rwxr-xr-xsys/src/cmd/ratfs/misc.c467
-rwxr-xr-xsys/src/cmd/ratfs/mkfile12
-rwxr-xr-xsys/src/cmd/ratfs/proto.c512
-rwxr-xr-xsys/src/cmd/ratfs/ratfs.h118
6 files changed, 1824 insertions, 0 deletions
diff --git a/sys/src/cmd/ratfs/ctlfiles.c b/sys/src/cmd/ratfs/ctlfiles.c
new file mode 100755
index 000000000..a9f863029
--- /dev/null
+++ b/sys/src/cmd/ratfs/ctlfiles.c
@@ -0,0 +1,397 @@
+#include "ratfs.h"
+#include <ip.h>
+
+enum {
+ ACCEPT = 0, /* verbs in control file */
+ REFUSED,
+ DENIED,
+ DIALUP,
+ BLOCKED,
+ DELAY,
+ NONE,
+
+ Subchar = '#', /* character substituted for '/' in file names */
+};
+
+static Keyword actions[] = {
+ "allow", ACCEPT,
+ "accept", ACCEPT,
+ "block", BLOCKED,
+ "deny", DENIED,
+ "dial", DIALUP,
+ "relay", DELAY,
+ "delay", DELAY,
+ 0, NONE,
+};
+
+static void acctinsert(Node*, char*);
+static char* getline(Biobuf*);
+static void ipinsert(Node*, char*);
+static void ipsort(void);
+
+/*
+ * Input the configuration file
+ * Currently we only process the "ournets"
+ * specification.
+ */
+void
+getconf(void)
+{
+ Biobuf *bp;
+ char *cp;
+ Node *np, *dir, **l;
+
+ if(debugfd >= 0)
+ fprint(debugfd, "loading %s\n", conffile);
+
+ bp = Bopen(conffile, OREAD);
+ if(bp == 0)
+ return;
+
+ dir = finddir(Trusted);
+ if(dir == 0)
+ return;
+
+ /*
+ * if this isn't the first time, purge permanent entries
+ */
+ trustedqid = Qtrustedfile;
+ if(lastconftime){
+ l = &dir->children;
+ for(np = dir->children; np; np = *l){
+ if(np->d.type == Trustedperm){
+ *l = np->sibs;
+ free(np);
+ } else {
+ np->d.qid.path = trustedqid++;
+ l = &np->sibs;
+ }
+ }
+ dir->count = 0;
+ }
+
+ for(;;){
+ cp = getline(bp);
+ if(cp == 0)
+ break;
+ if (strcmp(cp, "ournets") == 0){
+ for(cp += strlen(cp)+1; cp && *cp; cp += strlen(cp)+1){
+ np = newnode(dir, cp, Trustedperm, 0111, trustedqid++);
+ cidrparse(&np->ip, cp);
+ subslash(cp);
+ np->d.name = atom(cp);
+ }
+ }
+ }
+ Bterm(bp);
+ lastconftime = time(0);
+}
+
+/*
+ * Reload the control file, if necessary
+ */
+void
+reload(void)
+{
+ int type, action;
+ Biobuf *bp;
+ char *cp;
+ Node *np, *dir;
+
+ if(debugfd >= 0)
+ fprint(debugfd,"loading %s\n", ctlfile);
+
+ bp = Bopen(ctlfile, OREAD);
+ if(bp == 0)
+ return;
+
+ if(lastctltime){
+ for(dir = root->children; dir; dir = dir->sibs){
+ if (dir->d.type != Addrdir)
+ continue;
+ for(np = dir->children; np; np = np->sibs)
+ np->count = 0;
+ }
+ }
+
+ for(;;){
+ cp = getline(bp);
+ if(cp == 0)
+ break;
+ type = *cp;
+ if(type == '*'){
+ cp++;
+ if(*cp == 0) /* space before keyword */
+ cp++;
+ }
+ action = findkey(cp, actions);
+ if (action == NONE)
+ continue;
+ if (action == ACCEPT)
+ dir = dirwalk("allow", root);
+ else
+ if (action == DELAY)
+ dir = dirwalk("delay", root);
+ else
+ dir = dirwalk(cp, root);
+ if(dir == 0)
+ continue;
+
+ for(cp += strlen(cp)+1; cp && *cp; cp += strlen(cp)+1){
+ if(type == '*')
+ acctinsert(dir, cp);
+ else
+ ipinsert(dir, cp);
+ }
+ }
+ Bterm(bp);
+ ipsort();
+ dummy.d.mtime = dummy.d.atime = lastctltime = time(0);
+}
+
+/*
+ * get a canonicalized line: a string of null-terminated lower-case
+ * tokens with a two null bytes at the end.
+ */
+static char*
+getline(Biobuf *bp)
+{
+ char c, *cp, *p, *q;
+ int n;
+
+ static char *buf;
+ static int bufsize;
+
+ for(;;){
+ cp = Brdline(bp, '\n');
+ if(cp == 0)
+ return 0;
+ n = Blinelen(bp);
+ cp[n-1] = 0;
+ if(buf == 0 || bufsize < n+1){
+ bufsize += 512;
+ if(bufsize < n+1)
+ bufsize = n+1;
+ buf = realloc(buf, bufsize);
+ if(buf == 0)
+ break;
+ }
+ q = buf;
+ for (p = cp; *p; p++){
+ c = *p;
+ if(c == '\\' && p[1]) /* we don't allow \<newline> */
+ c = *++p;
+ else
+ if(c == '#')
+ break;
+ else
+ if(c == ' ' || c == '\t' || c == ',')
+ if(q == buf || q[-1] == 0)
+ continue;
+ else
+ c = 0;
+ *q++ = tolower(c);
+ }
+ if(q != buf){
+ if(q[-1])
+ *q++ = 0;
+ *q = 0;
+ break;
+ }
+ }
+ return buf;
+}
+
+/*
+ * Match a keyword
+ */
+int
+findkey(char *val, Keyword *p)
+{
+
+ for(; p->name; p++)
+ if(strcmp(val, p->name) == 0)
+ break;
+ return p->code;
+}
+
+/*
+ * parse a cidr specification in either IP/mask or IP#mask format
+ */
+void
+cidrparse(Cidraddr *cidr, char *cp)
+{
+
+ char *p, *slash;
+ int c;
+ ulong a, m;
+ uchar addr[IPv4addrlen];
+ uchar mask[IPv4addrlen];
+ char buf[64];
+
+ /*
+ * find '/' or '#' character in the cidr specification
+ */
+ slash = 0;
+ for(p = buf; p < buf+sizeof(buf)-1 && *cp; p++) {
+ c = *cp++;
+ switch(c) {
+ case Subchar:
+ c = '/';
+ slash = p;
+ break;
+ case '/':
+ slash = p;
+ break;
+ default:
+ break;
+ }
+ *p = c;
+ }
+ *p = 0;
+
+ v4parsecidr(addr, mask, buf);
+ a = nhgetl(addr);
+ m = nhgetl(mask);
+ /*
+ * if a mask isn't specified, we build a minimal mask
+ * instead of using the default mask for that net. in this
+ * case we never allow a class A mask (0xff000000).
+ */
+ if(slash == 0){
+ m = 0xff000000;
+ p = buf;
+ for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
+ m = (m>>8)|0xff000000;
+
+ /* force at least a class B */
+ m |= 0xffff0000;
+ }
+ cidr->ipaddr = a;
+ cidr->mask = m;
+}
+
+/*
+ * Substitute Subchar ('#') for '/'
+ */
+char*
+subslash(char *os)
+{
+ char *s;
+
+ for(s=os; *s; s++)
+ if(*s == '/')
+ *s = Subchar;
+ return os;
+}
+
+/*
+ * Insert an account pseudo-file in a directory
+ */
+static void
+acctinsert(Node *np, char *cp)
+{
+ int i;
+ char *tmp;
+ Address *ap;
+
+ static char *dangerous[] = { "*", "!", "*!", "!*", "*!*", 0 };
+
+ if(cp == 0 || *cp == 0)
+ return;
+
+ /* rule out dangerous patterns */
+ for (i = 0; dangerous[i]; i++)
+ if(strcmp(cp, dangerous[i])== 0)
+ return;
+
+ np = dirwalk("account", np);
+ if(np == 0)
+ return;
+
+ i = np->count++;
+ if(i >= np->allocated){
+ np->allocated = np->count;
+ np->addrs = realloc(np->addrs, np->allocated*sizeof(Address));
+ if(np->addrs == 0)
+ fatal("out of memory");
+ }
+
+ ap = &np->addrs[i]; /* new entry on end */
+ tmp = strdup(cp);
+ if(tmp == nil)
+ fatal("out of memory");
+ subslash(tmp);
+ ap->name = atom(tmp);
+ free(tmp);
+}
+
+/*
+ * Insert an IP address pseudo-file in a directory
+ */
+static void
+ipinsert(Node *np, char *cp)
+{
+ char *tmp;
+ int i;
+ Address *ap;
+ if(cp == 0 || *cp == 0)
+ return;
+
+ np = dirwalk("ip", np);
+ if(np == 0)
+ return;
+
+ i = np->count++;
+ if(i >= np->allocated){
+ np->allocated = np->count;
+ np->addrs = realloc(np->addrs, np->allocated*sizeof(Address));
+ if(np->addrs == 0)
+ fatal("out of memory");
+ }
+
+ ap = &np->addrs[i]; /* new entry on end */
+ tmp = strdup(cp);
+ if(tmp == nil)
+ fatal("out of memory");
+ subslash(tmp);
+ ap->name = atom(tmp);
+ free(tmp);
+ cidrparse(&ap->ip, cp);
+}
+
+int
+ipcomp(void *a, void *b)
+{
+ ulong aip, bip;
+
+ aip = ((Address*)a)->ip.ipaddr;
+ bip = ((Address*)b)->ip.ipaddr;
+ if(aip > bip)
+ return 1;
+ if(aip < bip)
+ return -1;
+ return 0;
+}
+
+/*
+ * Sort a directory of IP addresses
+ */
+static void
+ipsort(void)
+{
+ int base;
+ Node *dir, *np;
+
+ base = Qaddrfile;
+ for(dir = root->children; dir; dir = dir->sibs){
+ if (dir->d.type != Addrdir)
+ continue;
+ for(np = dir->children; np; np = np->sibs){
+ if(np->d.type == IPaddr && np->count && np->addrs)
+ qsort(np->addrs, np->count, sizeof(Address), ipcomp);
+ np->baseqid = base;
+ base += np->count;
+ }
+ }
+}
diff --git a/sys/src/cmd/ratfs/main.c b/sys/src/cmd/ratfs/main.c
new file mode 100755
index 000000000..7c83c892d
--- /dev/null
+++ b/sys/src/cmd/ratfs/main.c
@@ -0,0 +1,318 @@
+#include "ratfs.h"
+
+#define SRVFILE "/srv/ratify"
+#define MOUNTPOINT "/mail/ratify"
+#define CTLFILE "/mail/lib/blocked"
+#define CONFFILE "/mail/lib/smtpd.conf.ext"
+
+typedef struct Filetree Filetree;
+
+ /* prototype file tree */
+struct Filetree
+{
+ int level;
+ char *name;
+ ushort type;
+ int mode;
+ ulong qid;
+};
+
+ /* names of first-level directories - must be in order of level*/
+Filetree filetree[] =
+{
+ 0, "/", Directory, 0555|DMDIR, Qroot,
+ 1, "allow", Addrdir, 0555|DMDIR, Qallow,
+ 1, "delay", Addrdir, 0555|DMDIR, Qdelay,
+ 1, "block", Addrdir, 0555|DMDIR, Qblock,
+ 1, "dial", Addrdir, 0555|DMDIR, Qdial,
+ 1, "deny", Addrdir, 0555|DMDIR, Qdeny,
+ 1, "trusted", Trusted, 0777|DMDIR, Qtrusted, /* creation allowed */
+ 1, "ctl", Ctlfile, 0222, Qctl,
+ 2, "ip", IPaddr, 0555|DMDIR, Qaddr,
+ 2, "account", Acctaddr, 0555|DMDIR, Qaddr,
+ 0, 0, 0, 0, 0,
+
+};
+
+int debugfd = -1;
+int trustedqid = Qtrustedfile;
+char *ctlfile = CTLFILE;
+char *conffile = CONFFILE;
+
+#pragma varargck type "I" Cidraddr*
+
+static int ipconv(Fmt*);
+static void post(int, char*);
+static void setroot(void);
+
+void
+usage(void)
+{
+ fprint(2, "ratfs [-d] [-c conffile] [-f ctlfile] [-m mountpoint]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *mountpoint = MOUNTPOINT;
+ int p[2];
+
+ ARGBEGIN {
+ case 'c':
+ conffile = ARGF();
+ break;
+ case 'd':
+ debugfd = 2; /* stderr*/
+ break;
+ case 'f':
+ ctlfile = ARGF();
+ break;
+ case 'm':
+ mountpoint = ARGF();
+ break;
+ } ARGEND
+ if(argc != 0)
+ usage();
+
+ fmtinstall('I', ipconv);
+ setroot();
+ getconf();
+ reload();
+
+ /* get a pipe and mount it in /srv */
+ if(pipe(p) < 0)
+ fatal("pipe failed: %r");
+ srvfd = p[0];
+ post(p[1], mountpoint);
+
+ /* start the 9fs protocol */
+ switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){
+ case -1:
+ fatal("fork: %r");
+ case 0:
+ /* seal off standard input/output */
+ close(0);
+ open("/dev/null", OREAD);
+ close(1);
+ open("/dev/null", OWRITE);
+
+ close(p[1]);
+ fmtinstall('F', fcallfmt); /* debugging */
+ io();
+ fprint(2, "ratfs dying\n");
+ break;
+ default:
+ close(p[0]);
+ if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0)
+ fatal("mount failed: %r");
+ }
+ exits(0);
+}
+
+static void
+setroot(void)
+{
+ Filetree *fp;
+ Node *np;
+ int qid;
+
+ root = 0;
+ qid = Qaddr;
+ for(fp = filetree; fp->name; fp++) {
+ switch(fp->level) {
+ case 0: /* root */
+ case 1: /* second level directory */
+ newnode(root, fp->name, fp->type, fp->mode, fp->qid);
+ break;
+ case 2: /* lay down the Ipaddr and Acctaddr subdirectories */
+ for (np = root->children; np; np = np->sibs){
+ if(np->d.type == Addrdir)
+ newnode(np, fp->name, fp->type, fp->mode, qid++);
+ }
+ break;
+ default:
+ fatal("bad filetree");
+ }
+ }
+ dummy.d.type = Dummynode;
+ dummy.d.mode = 0444;
+ dummy.d.uid = "upas";
+ dummy.d.gid = "upas";
+ dummy.d.atime = dummy.d.mtime = time(0);
+ dummy.d.qid.path = Qdummy; /* for now */
+}
+
+static void
+post(int fd, char *mountpoint)
+{
+
+ int f;
+ char buf[128];
+
+ if(access(SRVFILE,0) >= 0){
+ /*
+ * If we can open and mount the /srv node,
+ * another server is already running, so just exit.
+ */
+ f = open(SRVFILE, ORDWR);
+ if(f >= 0 && mount(f, -1, mountpoint, MREPL|MCREATE, "") >= 0){
+ unmount(0, mountpoint);
+ close(f);
+ exits(0);
+ }
+ remove(SRVFILE);
+ }
+
+ /*
+ * create the server node and post our pipe to it
+ */
+ f = create(SRVFILE, OWRITE, 0666);
+ if(f < 0)
+ fatal("can't create %s", SRVFILE);
+
+ sprint(buf, "%d", fd);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ fatal("can't write %s", SRVFILE);
+
+ close(f);
+}
+
+/*
+ * print message and die
+ */
+void
+fatal(char *fmt, ...)
+{
+ va_list arg;
+ char buf[8*1024];
+
+ va_start(arg, fmt);
+ vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
+ va_end(arg);
+
+ fprint(2, "%s: %s\n", argv0, buf);
+ exits(buf);
+}
+
+/*
+ * create a new directory node
+ */
+Node*
+newnode(Node *parent, char *name, ushort type, int mode, ulong qid)
+{
+ Node *np;
+
+ np = mallocz(sizeof(Node), 1);
+ if(np == 0)
+ fatal("out of memory");
+ np->d.name = atom(name);
+ np->d.type = type;
+ np->d.mode = mode;
+ np->d.mtime = np->d.atime = time(0);
+ np->d.uid = atom("upas");
+ np->d.gid = atom("upas");
+ np->d.muid = atom("upas");
+ if(np->d.mode&DMDIR)
+ np->d.qid.type = QTDIR;
+ np->d.qid.path = qid;
+ np->d.qid.vers = 0;
+ if(parent){
+ np->parent = parent;
+ np->sibs = parent->children;
+ parent->children = np;
+ parent->count++;
+ } else {
+ /* the root node */
+ root = np;
+ np->parent = np;
+ np->children = 0;
+ np->sibs = 0;
+ }
+ return np;
+}
+
+void
+printnode(Node *np)
+{
+ fprint(debugfd, "Node at %p: %s (%s %s)", np, np->d.name, np->d.uid, np->d.gid);
+ if(np->d.qid.type&QTDIR)
+ fprint(debugfd, " QTDIR");
+ fprint(debugfd, "\n");
+ fprint(debugfd,"\tQID: %llud.%lud Mode: %lo Type: %d\n", np->d.qid.path,
+ np->d.qid.vers, np->d.mode, np->d.type);
+ fprint(debugfd, "\tMod: %.15s Acc: %.15s Count: %d\n", ctime(np->d.mtime)+4,
+ ctime(np->d.atime)+4, np->count);
+ switch(np->d.type)
+ {
+ case Directory:
+ fprint(debugfd, "\tDirectory Child: %p", np->children);
+ break;
+ case Addrdir:
+ fprint(debugfd, "\tAddrdir Child: %p", np->children);
+ break;
+ case IPaddr:
+ fprint(debugfd, "\tIPaddr Base: %p Alloc: %d BaseQid %lud", np->addrs,
+ np->allocated, np->baseqid);
+ break;
+ case Acctaddr:
+ fprint(debugfd, "\tAcctaddr Base: %p Alloc: %d BaseQid %lud", np->addrs,
+ np->allocated, np->baseqid);
+ break;
+ case Trusted:
+ fprint(debugfd, "\tTrusted Child: %p", np->children);
+ break;
+ case Trustedperm:
+ fprint(debugfd, "\tPerm Trustedfile: %I", &np->ip);
+ break;
+ case Trustedtemp:
+ fprint(debugfd, "\tTemp Trustedfile: %I", &np->ip);
+ break;
+ case Ctlfile:
+ fprint(debugfd, "\tCtlfile");
+ break;
+ case Dummynode:
+ fprint(debugfd, "\tDummynode");
+ break;
+ default:
+ fprint(debugfd, "\tUnknown Node Type\n\n");
+ return;
+ }
+ fprint(debugfd, " Parent %p Sib: %p\n\n", np->parent, np->sibs);
+}
+
+void
+printfid(Fid *fp)
+{
+ fprint(debugfd, "FID: %d (%s %s) Busy: %d Open: %d\n", fp->fid, fp->name,
+ fp->uid, fp->busy, fp->open);
+ printnode(fp->node);
+}
+
+void
+printtree(Node *np)
+{
+ printnode(np);
+ if(np->d.type == IPaddr
+ || np->d.type == Acctaddr
+ || np->d.type == Trustedperm
+ || np->d.type == Trustedtemp)
+ return;
+ for (np = np->children; np; np = np->sibs)
+ printtree(np);
+}
+
+static int
+ipconv(Fmt *f)
+{
+ Cidraddr *ip;
+ int i, j;
+ char *p;
+
+ ip = va_arg(f->args, Cidraddr*);
+ p = (char*)&ip->ipaddr;
+ i = 0;
+ for (j = ip->mask; j; j <<= 1)
+ i++;
+ return fmtprint(f, "%d.%d.%d.%d/%d", p[3]&0xff, p[2]&0xff, p[1]&0xff, p[0]&0xff, i);
+}
diff --git a/sys/src/cmd/ratfs/misc.c b/sys/src/cmd/ratfs/misc.c
new file mode 100755
index 000000000..ae2b4bb94
--- /dev/null
+++ b/sys/src/cmd/ratfs/misc.c
@@ -0,0 +1,467 @@
+#include "ratfs.h"
+#include <ip.h>
+
+enum {
+ Maxdoms = 10, /* max domains in a path */
+ Timeout = 2*60*60, /* seconds until temporarily trusted addr times out */
+};
+
+static int accountmatch(char*, char**, int, char*);
+static Node* acctwalk(char*, Node*);
+static int dommatch(char*, char*);
+static Address* ipsearch(ulong, Address*, int);
+static Node* ipwalk(char*, Node*);
+static Node* trwalk(char*, Node*);
+static int usermatch(char*, char*);
+
+/*
+ * Do a walk
+ */
+char*
+walk(char *name, Fid *fidp)
+{
+ Node *np;
+
+ if((fidp->node->d.mode & DMDIR) == 0)
+ return "not a directory";
+
+ if(strcmp(name, ".") == 0)
+ return 0;
+ if(strcmp(name, "..") == 0){
+ fidp->node = fidp->node->parent;
+ fidp->name = 0;
+ return 0;
+ }
+
+ switch(fidp->node->d.type){
+ case Directory:
+ case Addrdir:
+ np = dirwalk(name, fidp->node);
+ break;
+ case Trusted:
+ np = trwalk(name, fidp->node);
+ break;
+ case IPaddr:
+ np = ipwalk(name, fidp->node);
+ break;
+ case Acctaddr:
+ np = acctwalk(name, fidp->node);
+ break;
+ default:
+ return "directory botch in walk";
+ }
+ if(np) {
+ fidp->node = np;
+ fidp->name = np->d.name;
+ return 0;
+ }
+ return "file does not exist";
+}
+
+/*
+ * Walk to a subdirectory
+ */
+Node*
+dirwalk(char *name, Node *np)
+{
+ Node *p;
+
+ for(p = np->children; p; p = p->sibs)
+ if(strcmp(name, p->d.name) == 0)
+ break;
+ return p;
+}
+
+/*
+ * Walk the directory of trusted files
+ */
+static Node*
+trwalk(char *name, Node *np)
+{
+ Node *p;
+ ulong peerip;
+ uchar addr[IPv4addrlen];
+
+ v4parseip(addr, name);
+ peerip = nhgetl(addr);
+
+ for(p = np->children; p; p = p->sibs)
+ if((peerip&p->ip.mask) == p->ip.ipaddr)
+ break;
+ return p;
+}
+
+/*
+ * Walk a directory of IP addresses
+ */
+static Node*
+ipwalk(char *name, Node *np)
+{
+ Address *ap;
+ ulong peerip;
+ uchar addr[IPv4addrlen];
+
+ v4parseip(addr, name);
+ peerip = nhgetl(addr);
+
+ if(debugfd >= 0)
+ fprint(debugfd, "%d.%d.%d.%d - ", addr[0]&0xff, addr[1]&0xff,
+ addr[2]&0xff, addr[3]&0xff);
+ ap = ipsearch(peerip, np->addrs, np->count);
+ if(ap == 0)
+ return 0;
+
+ dummy.d.name = ap->name;
+ return &dummy;
+}
+
+/*
+ * Walk a directory of account names
+ */
+static Node*
+acctwalk(char *name, Node *np)
+{
+ int i, n;
+ Address *ap;
+ char *p, *cp, *user;
+ char buf[512];
+ char *doms[Maxdoms];
+
+ strecpy(buf, buf+sizeof buf, name);
+ subslash(buf);
+
+ p = buf;
+ for(n = 0; n < Maxdoms; n++) {
+ cp = strchr(p, '!');
+ if(cp == 0)
+ break;
+ *cp = 0;
+ doms[n] = p;
+ p = cp+1;
+ }
+ user = p;
+
+ for(i = 0; i < np->count; i++){
+ ap = &np->addrs[i];
+ if (accountmatch(ap->name, doms, n, user)) {
+ dummy.d.name = ap->name;
+ return &dummy;
+ }
+ }
+ return 0;
+}
+
+/*
+ * binary search sorted IP address list
+ */
+
+static Address*
+ipsearch(ulong addr, Address *base, int n)
+{
+ ulong top, bot, mid;
+ Address *ap;
+
+ bot = 0;
+ top = n;
+ for (mid = (bot+top)/2; mid < top; mid = (bot+top)/2) {
+ ap = &base[mid];
+ if((addr&ap->ip.mask) == ap->ip.ipaddr)
+ return ap;
+ if(addr < ap->ip.ipaddr)
+ top = mid;
+ else if(mid != n-1 && addr >= base[mid+1].ip.ipaddr)
+ bot = mid;
+ else
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Read a directory
+ */
+int
+dread(Fid *fidp, int cnt)
+{
+ uchar *q, *eq, *oq;
+ int n, skip;
+ Node *np;
+
+ if(debugfd >= 0)
+ fprint(debugfd, "dread %d\n", cnt);
+
+ np = fidp->node;
+ oq = q = rbuf+IOHDRSZ;
+ eq = q+cnt;
+ if(fidp->dirindex >= np->count)
+ return 0;
+
+ skip = fidp->dirindex;
+ for(np = np->children; skip > 0 && np; np = np->sibs)
+ skip--;
+ if(np == 0)
+ return 0;
+
+ for(; q < eq && np; np = np->sibs){
+ if(debugfd >= 0)
+ printnode(np);
+ if((n=convD2M(&np->d, q, eq-q)) <= BIT16SZ)
+ break;
+ q += n;
+ fidp->dirindex++;
+ }
+ return q - oq;
+}
+
+/*
+ * Read a directory of IP addresses or account names
+ */
+int
+hread(Fid *fidp, int cnt)
+{
+ uchar *q, *eq, *oq;
+ int i, n, path;
+ Address *p;
+ Node *np;
+
+ if(debugfd >= 0)
+ fprint(debugfd, "hread %d\n", cnt);
+
+ np = fidp->node;
+ oq = q = rbuf+IOHDRSZ;
+ eq = q+cnt;
+ if(fidp->dirindex >= np->count)
+ return 0;
+
+ path = np->baseqid;
+ for(i = fidp->dirindex; q < eq && i < np->count; i++){
+ p = &np->addrs[i];
+ dummy.d.name = p->name;
+ dummy.d.qid.path = path++;
+ if((n=convD2M(&dummy.d, q, eq-q)) <= BIT16SZ)
+ break;
+ q += n;
+ }
+ fidp->dirindex = i;
+ return q - oq;
+}
+
+/*
+ * Find a directory node by type
+ */
+Node*
+finddir(int type)
+{
+ Node *np;
+
+ for(np = root->children; np; np = np->sibs)
+ if (np->d.type == type)
+ return np;
+ return 0;
+}
+
+/*
+ * Remove temporary pseudo-files that have timed-out
+ * from the trusted directory
+ */
+void
+cleantrusted(void)
+{
+ Node *np, **l;
+ ulong t;
+
+ np = finddir(Trusted);
+ if (np == 0)
+ return;
+
+ t = time(0)-Timeout;
+ l = &np->children;
+ for (np = np->children; np; np = *l) {
+ if(np->d.type == Trustedtemp && t >= np->d.mtime) {
+ *l = np->sibs;
+ if(debugfd >= 0)
+ fprint(debugfd, "Deleting %s\n", np->d.name);
+ np->parent->count--;
+ free(np);
+ } else
+ l = &np->sibs;
+ }
+}
+
+/*
+ * match path components to prohibited domain & user specifications. patterns include:
+ * domain, domain! or domain!* - all users in domain
+ * *.domain, *.domain! or *.domain!* - all users in domain and its subdomains
+ * !user or *!user - user in all domains
+ * domain!user - user in domain
+ * *.domain!user - user in domain and its subdomains
+ *
+ * if "user" has a trailing '*', it matches all user names beginning with "user"
+ *
+ * there are special semantics for the "domain, domain! or domain!*" specifications:
+ * the first two forms match when the domain is anywhere in at list of source-routed
+ * domains while the latter matches only when the domain is the last hop. the same is
+ * true for the *.domain!* form of the pattern.
+ */
+static int
+accountmatch(char *spec, char **doms, int ndoms, char *user)
+{
+ char *cp, *userp;
+ int i, ret;
+
+ userp = 0;
+ ret = 0;
+ cp = strchr(spec, '!');
+ if(cp){
+ *cp++ = 0; /* restored below */
+ if(*cp)
+ if(strcmp(cp, "*")) /* "!*" is the same as no user field */
+ userp = cp; /* there is a user name */
+ }
+
+ if(userp == 0){ /* no user field - domain match only */
+ for(i = 0; i < ndoms && doms[i]; i++)
+ if(dommatch(doms[i], spec) == 0)
+ ret = 1;
+ } else {
+ /* check for "!user", "*!user" or "domain!user" */
+ if(usermatch(user, userp) == 0){
+ if(*spec == 0 || strcmp(spec, "*") == 0)
+ ret = 1;
+ else if(ndoms > 0 && dommatch(doms[ndoms-1], spec) == 0)
+ ret = 1;
+ }
+ }
+ if(cp)
+ cp[-1] = '!';
+ return ret;
+}
+
+/*
+ * match a user name. the only meta-char is '*' which matches all
+ * characters. we only allow it as "*", which matches anything or
+ * an * at the end of the name (e.g., "username*") which matches
+ * trailing characters.
+ */
+static int
+usermatch(char *pathuser, char *specuser)
+{
+ int n;
+
+ n = strlen(specuser)-1;
+ if(specuser[n] == '*'){
+ if(n == 0) /* match everything */
+ return 0;
+ return strncmp(pathuser, specuser, n);
+ }
+ return strcmp(pathuser, specuser);
+}
+
+/*
+ * Match a domain specification
+ */
+static int
+dommatch(char *pathdom, char *specdom)
+{
+ int n;
+
+ if (*specdom == '*'){
+ if (specdom[1] == '.' && specdom[2]){
+ specdom += 2;
+ n = strlen(pathdom)-strlen(specdom);
+ if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
+ return strcmp(pathdom+n, specdom);
+ return n;
+ }
+ }
+ return strcmp(pathdom, specdom);
+}
+
+/*
+ * Custom allocators to avoid malloc overheads on small objects.
+ * We never free these. (See below.)
+ */
+typedef struct Stringtab Stringtab;
+struct Stringtab {
+ Stringtab *link;
+ char *str;
+};
+static Stringtab*
+taballoc(void)
+{
+ static Stringtab *t;
+ static uint nt;
+
+ if(nt == 0){
+ t = malloc(64*sizeof(Stringtab));
+ if(t == 0)
+ fatal("out of memory");
+ nt = 64;
+ }
+ nt--;
+ return t++;
+}
+
+static char*
+xstrdup(char *s)
+{
+ char *r;
+ int len;
+ static char *t;
+ static int nt;
+
+ len = strlen(s)+1;
+ if(len >= 8192)
+ fatal("strdup big string");
+
+ if(nt < len){
+ t = malloc(8192);
+ if(t == 0)
+ fatal("out of memory");
+ nt = 8192;
+ }
+ r = t;
+ t += len;
+ nt -= len;
+ strcpy(r, s);
+ return r;
+}
+
+/*
+ * Return a uniquely allocated copy of a string.
+ * Don't free these -- they stay in the table for the
+ * next caller who wants that particular string.
+ * String comparison can be done with pointer comparison
+ * if you know both strings are atoms.
+ */
+static Stringtab *stab[1024];
+
+static uint
+hash(char *s)
+{
+ uint h;
+ uchar *p;
+
+ h = 0;
+ for(p=(uchar*)s; *p; p++)
+ h = h*37 + *p;
+ return h;
+}
+
+char*
+atom(char *str)
+{
+ uint h;
+ Stringtab *tab;
+
+ h = hash(str) % nelem(stab);
+ for(tab=stab[h]; tab; tab=tab->link)
+ if(strcmp(str, tab->str) == 0)
+ return tab->str;
+
+ tab = taballoc();
+ tab->str = xstrdup(str);
+ tab->link = stab[h];
+ stab[h] = tab;
+ return tab->str;
+}
diff --git a/sys/src/cmd/ratfs/mkfile b/sys/src/cmd/ratfs/mkfile
new file mode 100755
index 000000000..d4c273a67
--- /dev/null
+++ b/sys/src/cmd/ratfs/mkfile
@@ -0,0 +1,12 @@
+</$objtype/mkfile
+
+TARG=ratfs
+OFILES=main.$O\
+ proto.$O\
+ misc.$O\
+ ctlfiles.$O\
+
+HFILES=ratfs.h\
+
+BIN=/$objtype/bin/upas
+</sys/src/cmd/mkone
diff --git a/sys/src/cmd/ratfs/proto.c b/sys/src/cmd/ratfs/proto.c
new file mode 100755
index 000000000..7ed046fdd
--- /dev/null
+++ b/sys/src/cmd/ratfs/proto.c
@@ -0,0 +1,512 @@
+#include "ratfs.h"
+
+/*
+ * 9P protocol interface
+ */
+
+enum {
+ RELOAD = 0, /* commands written to ctl file */
+ RDEBUG,
+ RNODEBUG,
+ RNONE,
+};
+
+static void rflush(Fcall*), rnop(Fcall*),
+ rauth(Fcall*), rattach(Fcall*),
+ rclone(Fcall*), rwalk(Fcall*),
+ rclwalk(Fcall*), ropen(Fcall*),
+ rcreate(Fcall*), rread(Fcall*),
+ rwrite(Fcall*), rclunk(Fcall*),
+ rremove(Fcall*), rstat(Fcall*),
+ rwstat(Fcall*), rversion(Fcall*);
+
+static Fid* newfid(int);
+static void reply(Fcall*, char*);
+
+static void (*fcalls[])(Fcall*) = {
+ [Tversion] rversion,
+ [Tflush] rflush,
+ [Tauth] rauth,
+ [Tattach] rattach,
+ [Twalk] rwalk,
+ [Topen] ropen,
+ [Tcreate] rcreate,
+ [Tread] rread,
+ [Twrite] rwrite,
+ [Tclunk] rclunk,
+ [Tremove] rremove,
+ [Tstat] rstat,
+ [Twstat] rwstat,
+};
+
+
+static Keyword cmds[] = {
+ "reload", RELOAD,
+ "debug", RDEBUG,
+ "nodebug", RNODEBUG,
+ 0, RNONE,
+};
+
+/*
+ * Main protocol loop
+ */
+void
+io(void)
+{
+ Fcall rhdr;
+ int n;
+
+ for(;;){
+ n = read9pmsg(srvfd, rbuf, sizeof rbuf-1);
+ if(n <= 0)
+ fatal("mount read");
+ if(convM2S(rbuf, n, &rhdr) == 0){
+ if(debugfd >= 0)
+ fprint(2, "%s: malformed message\n", argv0);
+ continue;
+ }
+
+ if(debugfd >= 0)
+ fprint(debugfd, "<-%F\n", &rhdr);/**/
+
+ if(!fcalls[rhdr.type])
+ reply(&rhdr, "bad fcall type");
+ else
+ (*fcalls[rhdr.type])(&rhdr);
+ }
+}
+
+/*
+ * write a protocol reply to the client
+ */
+static void
+reply(Fcall *r, char *error)
+{
+ int n;
+
+ if(error == nil)
+ r->type++;
+ else {
+ r->type = Rerror;
+ r->ename = error;
+ }
+ if(debugfd >= 0)
+ fprint(debugfd, "->%F\n", r);/**/
+ n = convS2M(r, rbuf, sizeof rbuf);
+ if(n == 0)
+ sysfatal("convS2M: %r");
+ if(write(srvfd, rbuf, n) < 0)
+ sysfatal("reply: %r");
+}
+
+
+/*
+ * lookup a fid. if not found, create a new one.
+ */
+
+static Fid*
+newfid(int fid)
+{
+ Fid *f, *ff;
+
+ static Fid *fids;
+
+ ff = 0;
+ for(f = fids; f; f = f->next){
+ if(f->fid == fid){
+ if(!f->busy)
+ f->node = 0;
+ return f;
+ } else if(!ff && !f->busy)
+ ff = f;
+ }
+ if(ff == 0){
+ ff = mallocz(sizeof(*f), 1);
+ ff->next = fids;
+ fids = ff;
+ }
+ ff->node = 0;
+ ff->fid = fid;
+ return ff;
+}
+
+static void
+rversion(Fcall *f)
+{
+ f->version = "9P2000";
+ if(f->msize > MAXRPC)
+ f->msize = MAXRPC;
+ reply(f, 0);
+}
+
+static void
+rauth(Fcall *f)
+{
+ reply(f, "ratfs: authentication not required");
+}
+
+static void
+rflush(Fcall *f)
+{
+ reply(f, 0);
+}
+
+static void
+rattach(Fcall *f)
+{
+ Fid *fidp;
+ Dir *d;
+
+ if((d=dirstat(conffile)) != nil && d->mtime > lastconftime)
+ getconf();
+ free(d);
+ if((d=dirstat(ctlfile)) != nil && d->mtime > lastctltime)
+ reload();
+ free(d);
+ cleantrusted();
+
+ fidp = newfid(f->fid);
+ fidp->busy = 1;
+ fidp->node = root;
+ fidp->name = root->d.name;
+ fidp->uid = atom(f->uname);
+ f->qid = root->d.qid;
+ reply(f,0);
+}
+
+static void
+rclone(Fcall *f)
+{
+ Fid *fidp, *nf;
+
+ fidp = newfid(f->fid);
+ if(fidp->node && fidp->node->d.type == Dummynode){
+ reply(f, "can't clone an address");
+ return;
+ }
+ nf = newfid(f->newfid);
+ nf->busy = 1;
+ nf->node = fidp->node;
+ nf->uid = fidp->uid;
+ nf->name = fidp->name;
+ if(debugfd >= 0)
+ printfid(nf);
+ reply(f,0);
+}
+
+static void
+rwalk(Fcall *f)
+{
+ int i, j;
+ Fcall r;
+ Fid *fidp, *nf;
+ char *err;
+
+ fidp = newfid(f->fid);
+ if(fidp->node && fidp->node->d.type == Dummynode){
+ reply(f, "can't walk an address node");
+ return;
+ }
+ if(f->fid == f->newfid)
+ nf = fidp;
+ else{
+ nf = newfid(f->newfid);
+ nf->busy = 1;
+ nf->node = fidp->node;
+ nf->uid = fidp->uid;
+ nf->name = fidp->name;
+ if(debugfd >= 0)
+ printfid(nf);
+ }
+
+ err = nil;
+ for(i=0; i<f->nwname; i++){
+ err = walk(f->wname[i], nf);
+ if(err)
+ break;
+ r.wqid[i] = nf->node->d.qid;
+ }
+
+
+ if(i < f->nwname && f->fid != f->newfid){
+ nf->busy = 0;
+ nf->node = 0;
+ nf->name = 0;
+ nf->uid = 0;
+ }
+ if(i > 0 && i < f->nwname && f->fid == f->newfid){
+ /*
+ * try to put things back;
+ * we never get this sort of call from the kernel
+ */
+ for(j=0; j<i; j++)
+ walk("..", nf);
+ }
+ memmove(f->wqid, r.wqid, sizeof f->wqid);
+ f->nwqid = i;
+ if(err && i==0)
+ reply(f, err);
+ else
+ reply(f, 0);
+}
+
+/*
+ * We don't have to do full permission checking because most files
+ * have restricted semantics:
+ * The ctl file is only writable
+ * All others, including directories, are only readable
+ */
+static void
+ropen(Fcall *f)
+{
+ Fid *fidp;
+ int mode;
+
+ fidp = newfid(f->fid);
+
+ if(debugfd >= 0)
+ printfid(fidp);
+
+ mode = f->mode&(OREAD|OWRITE|ORDWR);
+ if(fidp->node->d.type == Ctlfile) {
+ if(mode != OWRITE) {
+ reply(f, "permission denied");
+ return;
+ }
+ } else
+ if (mode != OREAD) {
+ reply(f, "permission denied or operation not supported");
+ return;
+ }
+
+ f->qid = fidp->node->d.qid;
+ fidp->open = 1;
+ reply(f, 0);
+}
+
+static int
+permitted(Fid *fp, Node *np, int mask)
+{
+ int mode;
+
+ mode = np->d.mode;
+ return (fp->uid==np->d.uid && (mode&(mask<<6)))
+ || (fp->uid==np->d.gid && (mode&(mask<<3)))
+ || (mode&mask);
+}
+
+/*
+ * creates are only allowed in the "trusted" subdirectory
+ * we also assume that the groupid == the uid
+ */
+static void
+rcreate(Fcall *f)
+{
+ Fid *fidp;
+ Node *np;
+
+ fidp = newfid(f->fid);
+ np = fidp->node;
+ if((np->d.mode&DMDIR) == 0){
+ reply(f, "not a directory");
+ return;
+ }
+
+ if(!permitted(fidp, np, AWRITE)) {
+ reply(f, "permission denied");
+ return;
+ }
+
+ /* Ignore the supplied mode and force it to be non-writable */
+
+ np = newnode(np, f->name, Trustedtemp, 0444, trustedqid++);
+ if(trustedqid >= Qaddrfile) /* wrap QIDs */
+ trustedqid = Qtrustedfile;
+ cidrparse(&np->ip, f->name);
+ f->qid = np->d.qid;
+ np->d.uid = fidp->uid;
+ np->d.gid = np->d.uid;
+ np->d.muid = np->d.muid;
+ fidp->node = np;
+ fidp->open = 1;
+ reply(f, 0);
+ return;
+}
+
+/*
+ * only directories can be read. everthing else returns EOF.
+ */
+static void
+rread(Fcall *f)
+{
+ long cnt;
+ Fid *fidp;
+
+ cnt = f->count;
+ f->count = 0;
+ fidp = newfid(f->fid);
+ f->data = (char*)rbuf+IOHDRSZ;
+ if(fidp->open == 0) {
+ reply(f, "file not open");
+ return;
+ }
+ if ((fidp->node->d.mode&DMDIR) == 0){
+ reply(f, 0); /*EOF*/
+ return;
+ }
+ if(cnt > MAXRPC)
+ cnt = MAXRPC;
+
+ if(f->offset == 0)
+ fidp->dirindex = 0;
+
+ switch(fidp->node->d.type) {
+ case Directory:
+ case Addrdir:
+ case Trusted:
+ f->count = dread(fidp, cnt);
+ break;
+ case IPaddr:
+ case Acctaddr:
+ f->count = hread(fidp, cnt);
+ break;
+ default:
+ reply(f, "can't read this type of file");
+ return;
+ }
+ reply(f, 0);
+}
+
+
+/*
+ * only the 'ctl' file in the top level directory is writable
+ */
+
+static void
+rwrite(Fcall *f)
+{
+ Fid *fidp;
+ int n;
+ char *err, *argv[10];
+
+ fidp = newfid(f->fid);
+ if(fidp->node->d.mode & DMDIR){
+ reply(f, "directories are not writable");
+ return;
+ }
+ if(fidp->open == 0) {
+ reply(f, "file not open");
+ return;
+ }
+
+ if (!permitted(fidp, fidp->node, AWRITE)) {
+ reply(f, "permission denied");
+ return;
+ }
+
+ f->data[f->count] = 0; /* the extra byte in rbuf leaves room */
+ n = tokenize(f->data, argv, 10);
+ err = 0;
+ switch(findkey(argv[0], cmds)){
+ case RELOAD:
+ getconf();
+ reload();
+ break;
+ case RDEBUG:
+ if(n > 1){
+ debugfd = create(argv[1], OWRITE, 0666);
+ if(debugfd < 0)
+ err = "create failed";
+ } else
+ debugfd = 2;
+ break;
+ case RNODEBUG:
+ if(debugfd >= 0)
+ close(debugfd);
+ debugfd = -1;
+ break;
+ default:
+ err = "unknown command";
+ break;
+ }
+ reply(f, err);
+}
+
+static void
+rclunk(Fcall *f)
+{
+ Fid *fidp;
+
+ fidp = newfid(f->fid);
+ fidp->open = 0;
+ fidp->busy = 0;
+ fidp->node = 0;
+ fidp->name = 0;
+ fidp->uid = 0;
+ reply(f, 0);
+}
+
+/*
+ * no files or directories are removable; this becomes clunk;
+ */
+static void
+rremove(Fcall *f)
+{
+ Fid *fidp;
+ Node *dir, *np;
+
+ fidp = newfid(f->fid);
+
+ /*
+ * only trusted temporary files can be removed
+ * and only by their owner.
+ */
+ if(fidp->node->d.type != Trustedtemp){
+ reply(f, "can't be removed");
+ return;
+ }
+ if(fidp->uid != fidp->node->d.uid){
+ reply(f, "permission denied");
+ return;
+ }
+ dir = fidp->node->parent;
+ for(np = dir->children; np; np = np->sibs)
+ if(np->sibs == fidp->node)
+ break;
+ if(np)
+ np->sibs = fidp->node->sibs;
+ else
+ dir->children = fidp->node->sibs;
+ dir->count--;
+ free(fidp->node);
+ fidp->node = 0;
+ fidp->open = 0;
+ fidp->busy = 0;
+ fidp->name = 0;
+ fidp->uid = 0;
+ reply(f, 0);
+}
+
+static void
+rstat(Fcall *f)
+{
+ Fid *fidp;
+
+ fidp = newfid(f->fid);
+ if (fidp->node->d.type == Dummynode)
+ dummy.d.name = fidp->name;
+ f->stat = (uchar*)rbuf+4+1+2+2; /* knows about stat(5) */
+ f->nstat = convD2M(&fidp->node->d, f->stat, MAXRPC);
+ if(f->nstat <= BIT16SZ)
+ reply(f, "ratfs: convD2M");
+ else
+ reply(f, 0);
+ return;
+}
+
+static void
+rwstat(Fcall *f)
+{
+ reply(f, "wstat not implemented");
+}
+
diff --git a/sys/src/cmd/ratfs/ratfs.h b/sys/src/cmd/ratfs/ratfs.h
new file mode 100755
index 000000000..ddf6f710c
--- /dev/null
+++ b/sys/src/cmd/ratfs/ratfs.h
@@ -0,0 +1,118 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+
+enum {
+ MAXRPC = 8192,
+
+ Qroot = 1, /* fixed QID's */
+ Qallow,
+ Qdelay,
+ Qblock,
+ Qdial,
+ Qdeny,
+ Qtrusted,
+ Qctl,
+ Qdummy,
+ Qaddr, /* Qid's for "ip" & "account" subdirs (Qaddr-99) */
+
+ Qtrustedfile = 100, /* Qid's for trusted files (100-999)*/
+ Qaddrfile = 1000, /* Qid's for address files (> 1000) */
+
+ /* type codes in node.d.type */
+ Directory = 0, /* normal directory */
+ Addrdir, /* contains "ip" and "account" directories */
+ IPaddr, /* contains IP address "files" */
+ Acctaddr, /* contains Account address "files" */
+ Trusted, /* contains trusted IP files */
+ Trustedperm, /* permanently trusted IP pseudo-file */
+ Trustedtemp, /* temporarily trusted IP pseudo-file */
+ Ctlfile, /* ctl file under root */
+ Dummynode, /* place holder for Address pseudo-files */
+};
+
+typedef struct Fid Fid;
+typedef struct Node Node;
+typedef struct Address Address;
+typedef struct Cidraddr Cidraddr;
+typedef struct Keyword Keyword;
+
+ /* an active fid */
+struct Fid
+{
+ int fid;
+ int dirindex;
+ Node *node; /* current position in path */
+ int busy;
+ int open; /* directories only */
+ char *name;
+ char *uid;
+ Fid *next;
+};
+
+struct Cidraddr
+{
+ ulong ipaddr; /* CIDR base addr */
+ ulong mask; /* CIDR mask */
+};
+
+ /* an address is either an account name (domain!user) or Ip address */
+struct Address
+{
+ char *name; /* from the control file */
+ Cidraddr ip; /* CIDR Address */
+};
+
+/* Fids point to either a directory or pseudo-file */
+struct Node
+{
+ Dir d; /* d.name, d.uid, d.gid, d.muid are atoms */
+ int count;
+ int allocated; /* number of Address structs allocated */
+ ulong baseqid; /* base of Qid's in this set */
+ Node *parent; /* points to self in root node*/
+ Node *sibs; /* 0 in Ipaddr and Acctaddr dirs */
+ union {
+ Node *children; /* type == Directory || Addrdir || Trusted */
+ Address *addrs; /* type == Ipaddr || Acctaddr */
+ Cidraddr ip; /* type == Trustedfile */
+ };
+};
+
+struct Keyword {
+ char *name;
+ int code;
+};
+
+Node *root; /* root of directory tree */
+Node dummy; /* dummy node for fid's pointing to an Address */
+int srvfd; /* fd for 9fs */
+uchar rbuf[IOHDRSZ+MAXRPC+1];
+int debugfd;
+char *ctlfile;
+char *conffile;
+long lastconftime;
+long lastctltime;
+int trustedqid;
+
+char* atom(char*);
+void cidrparse(Cidraddr*, char*);
+void cleantrusted(void);
+Node* dirwalk(char*, Node*);
+int dread(Fid*, int);
+void fatal(char*, ...);
+Node* finddir(int);
+int findkey(char*, Keyword*);
+void getconf(void);
+int hread(Fid*, int);
+void io(void);
+Node* newnode(Node*, char*, ushort, int, ulong);
+void printfid(Fid*);
+void printnode(Node*);
+void printtree(Node*);
+void reload(void);
+char* subslash(char*);
+char* walk(char*, Fid*);
+