diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ratfs |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ratfs')
-rwxr-xr-x | sys/src/cmd/ratfs/ctlfiles.c | 397 | ||||
-rwxr-xr-x | sys/src/cmd/ratfs/main.c | 318 | ||||
-rwxr-xr-x | sys/src/cmd/ratfs/misc.c | 467 | ||||
-rwxr-xr-x | sys/src/cmd/ratfs/mkfile | 12 | ||||
-rwxr-xr-x | sys/src/cmd/ratfs/proto.c | 512 | ||||
-rwxr-xr-x | sys/src/cmd/ratfs/ratfs.h | 118 |
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*); + |