#include #include #include #include #include #include #include #include <9p.h> #include #include "x10.h" static X10* x; static char myhc; /* * 0 -> cm11 * 1..n -> devices */ enum { Qdir = 0, Qcm11= 256, // 1..n -> devices Qauth= 128, // 128..m -> auth files Nauth = 2, }; static char Eperm[] = "permission denied"; static char Egone[] = "device is gone"; static char Enotexist[] = "file does not exist"; static char Ebadctl[] = "unknown control message"; static char Eattach[] = "invalid attach specifier"; static char Ex10[]= "X10 request failed"; static char Eauth[]= "authentication failed"; static char* uids[Ndevs+1]; static char* gids[Ndevs+1]; static int modes[Ndevs+1]; static char* names[Ndevs+1]; static int vers; static AuthRpc* authf[Nauth]; static int authok[Nauth]; static char* authu[Nauth]; /* * simplistic permission checking. assume that * each user is the leader of her own group. */ static int allowed(int qid, char *uid, int p) { static char* elf; int m; int mode; char* g; char* u; if (elf == nil){ elf=getenv("user"); u = strchr(elf, '\n'); if (u != nil) *u = 0; } switch(qid){ case Qdir: mode = 0775; break; case Qcm11: qid = 0; // and fall... default: if (modes[qid]) mode = modes[qid]; else mode = 0664; } if (qid >= 0 && gids[qid]) g = gids[qid]; else g = "sys"; if (qid >= 0 && uids[qid]) u = uids[qid]; else u = "sys"; m = mode & 7; /* other */ if((p & m) == p) return 1; if(strcmp(u, uid) == 0 || strcmp(uid, elf) == 0) { m |= (mode>>6) & 7; if((p & m) == p) return 1; } if(strcmp(g, uid) == 0) { m |= (mode>>3) & 7; if((p & m) == p) return 1; } return 0; } static int dirgen(int i, Dir *d, void*) { int n; Dev* devs; char name[40]; memset(d, 0, sizeof *d); d->uid = estrdup9p("sys"); d->gid = estrdup9p("sys"); d->length = 0; d->atime = d->mtime = time(nil); switch(i){ case -1: d->name = estrdup9p("/"); d->qid.type = QTDIR; d->qid.path = Qdir; d->mode = DMDIR|0775; break; case 0: d->name = estrdup9p("cm11"); d->qid.type = 0; d->qid.path = Qcm11; d->qid.vers = vers; d->mode = 0664; if (uids[0] != nil){ free(d->uid); d->uid = estrdup9p(uids[0]); } if (gids[0] != nil){ free(d->gid); d->gid = estrdup9p(gids[0]); } if (modes[0] != 0) d->mode = modes[0]&0777; break; default: devs = x10devs(x); for (n = 0; n < Ndevs; n++) if (devs[n].hc !=0 && --i == 0) break; if (n == Ndevs) return -1; if (names[n+1] == nil){ name[0] = hctochr(devs[n].hc); seprint(name+1, name+sizeof(name), "%d", dctoint(devs[n].dc)); d->name = estrdup9p(name); } else d->name = estrdup9p(names[n+1]); d->qid.type = 0; d->qid.path = n+1; d->qid.vers = vers; d->mode = 0664; if (uids[n+1] != nil){ free(d->uid); d->uid = estrdup9p(uids[n+1]); } if (gids[n+1] != nil){ free(d->gid); d->gid = estrdup9p(gids[n+1]); } if (modes[n+1] != 0) d->mode = modes[n+1]&0777; } return 0; } static char *keyspec = "proto=p9any role=server"; static void fsauth(Req* r) { int i; int afd; int fd; for (i = 0; i < Nauth; i++) if (authf[i] == nil) break; if (i == Nauth){ respond(r, "No more auth files"); return; } r->afid->qid.path = Qauth+i; r->afid->qid.type = QTAUTH; r->afid->omode = ORDWR; r->ofcall.qid = r->afid->qid; if(access("/mnt/factotum", 0) < 0) if((fd = open("/srv/factotum", ORDWR)) >= 0) mount(fd, -1, "/mnt", MBEFORE, ""); afd = open("/mnt/factotum/rpc", ORDWR); if (afd < 0) goto fail; authf[i] = auth_allocrpc(afd); authok[i] = 0; if (authf[i] == nil) goto fail; if(auth_rpc(authf[i], "start", keyspec, strlen(keyspec)) != ARok) goto fail; if (r->ifcall.uname == nil || r->ifcall.uname[0] == 0) goto fail; authu[i] = estrdup9p(r->ifcall.uname); respond(r, nil); return; fail: if (afd >= 0) close(afd); free(authf[i]); authf[i] = nil; respond(r, Eauth); } static void clrauth(int i) { if (authf[i] == nil) return; free(authu[i]); close(authf[i]->afd); free(authf[i]); authu[i] = nil; authf[i] = nil; authok[i] = 0; } static long _authread(Fid* fid, void* data, long count) { int i; long n; int rr; AuthInfo*ai; if (fid->qid.type != QTAUTH) return -1; i = fid->qid.path - Qauth; if (i < 0 || i >= Nauth || authf[i] == nil) return -1; rr = auth_rpc(authf[i], "read", nil, 0); n = 0; switch(rr){ case ARdone: ai = auth_getinfo(authf[i]); if(ai == nil){ clrauth(i); return -1; } auth_freeAI(ai); if (debug) fprint(2, "user %s authenticated\n", fid->uid); authok[i] = 1; break; case ARok: if(count < authf[i]->narg){ clrauth(i); return -1; } memmove(data, authf[i]->arg, authf[i]->narg); n = authf[i]->narg; break; case ARphase: default: clrauth(i); return -1; } return n; } static void fsauthread(Req* r) { int n; n = _authread(r->fid, r->ofcall.data, r->ifcall.count); if (n < 0) respond(r, Eauth); r->ofcall.count = n; respond(r, nil); } static void fsauthwrite(Req* r) { int ret; int i; i = r->fid->qid.path - Qauth; if (i < 0 || i >= Nauth || authf[i] == nil){ respond(r, Enotexist); return; } ret = auth_rpc(authf[i], "write", r->ifcall.data, r->ifcall.count); if(ret != ARok){ clrauth(i); respond(r, Eauth); return; } r->ofcall.count = r->ifcall.count; respond(r, nil); } static void fsattach(Req *r) { char *spec; int i; uchar buf[1]; if (r->afid == nil){ respond(r, Eauth); return; } i = r->afid->qid.path - Qauth; if (i < 0 || i >= Nauth || authf[i] == nil){ respond(r, Eauth); return; } if (!authok[i] && _authread(r->afid, buf, 0) != 0){ clrauth(i); respond(r, Eauth); return; } authok[i] = 1; if (strcmp(authu[i], r->fid->uid) != 0){ clrauth(i); respond(r, Eauth); return; } clrauth(i); x10reqsts(x); vers++; spec = r->ifcall.aname; if(spec != nil && spec[0] != 0){ respond(r, Eattach); return; } r->ofcall.qid = (Qid){0, 0, QTDIR}; r->fid->qid = r->ofcall.qid; respond(r, nil); } static void fsopen(Req *r) { ulong qid; int mode; r->ifcall.mode &= 3; if (r->fid->qid.type == QTAUTH){ r->fid->omode = r->ifcall.mode; respond(r, nil); return; } switch(r->ifcall.mode){ case OREAD: mode = AREAD; break; case OWRITE: mode = AWRITE; break; case ORDWR: mode = AREAD|AWRITE; break; default: respond(r, Eperm); return; } qid = r->fid->qid.path; if (qid >= 0 && qid <= Ndevs){ r->fid->qid.vers = vers; r->ofcall.qid = r->fid->qid; } if (!allowed(qid, r->fid->uid, mode) || (r->ifcall.mode != OREAD && qid == Qdir) ){ respond(r, Eperm); return; } r->fid->omode = r->ifcall.mode; respond(r, nil); } static void fsread(Req *r) { static char null[] = ""; static char* ons[] = {"off", "on"}; Dev* devs; ulong qid; int o; char buf[80]; static ulong laststs; ulong t; qid = r->fid->qid.path; if (r->fid->qid.type == QTAUTH){ if (debug) fprint(2, "auth read\n"); fsauthread(r); return; } o = r->fid->omode & 3; if (o != OREAD && o != ORDWR){ respond(r, Eperm); return; } switch(qid){ case Qdir: x10reqsts(x); vers++; dirread9p(r, dirgen, nil); break; case Qcm11: cm11sprint(x, buf, sizeof(buf)); readstr(r, buf); break; default: if (!laststs) laststs = time(nil); t = time(nil); if (t - laststs >= 10){ if (x10reqsts(x) < 0){ sleep(500); if (x10reqsts(x) < 0){ respond(r, Ex10); return; } } laststs = time(nil); } devs = x10devs(x); if (qid <= 0 || qid > Ndevs || devs[qid-1].hc ==0){ respond(r, Egone); return; } readstr(r, ons[devs[qid-1].on]); } respond(r, nil); } /* * Process command as-is */ static char* cm11cmd(char* cmd) { char* args[10]; int nargs; int r; char saved[40]; int attempts; if (debug) fprint(2, "cm11cmd %s ", cmd); strecpy(saved, saved+sizeof(saved), cmd); nargs = tokenize(cmd, args, nelem(args)); if (nargs == 0){ if (debug) fprint(2, "ignored\n"); return nil; } if (nargs == nelem(args)) sysfatal("bug: sh: fixme: Nargs overflow"); if (*args[1] != myhc){ if (debug) fprint(2, "ignored\n"); return nil; } if (debug) fprint(2, "\n"); args[nargs] = nil; attempts = 0; again: r = runfunc(x, nargs, args); if (r == 0){ syslog(0, logf, "cm11cmd %s: bad ctl", saved); return Ebadctl; } if (r < 0){ if (attempts++ < 10){ sleep(1000); goto again; } syslog(0, logf, "cm11cmd %s failed", saved); return Ex10; } syslog(0, logf, "cm11cmd %s", saved); return nil; } /* * process "first word" + house + device + "rest" */ static char* devcmd(char* cmd, uchar hc, uchar dc) { char ncmd[50]; char* s; s = strchr(cmd, ' '); if (s == nil) s = ""; else *s++= 0; seprint(ncmd, ncmd+sizeof(ncmd), "%s %c %d %s", cmd, hctochr(hc), dctoint(dc), s); return cm11cmd(ncmd); } static void fswrite(Req *r) { static char buf[30]; Dev* devs; ulong qid; char* e; int l; ulong o; if (r->fid->qid.type == QTAUTH){ fsauthwrite(r); return; } o = r->fid->omode & 3; if (o != OWRITE && o != ORDWR){ respond(r, Eperm); return; } r->ofcall.count = r->ifcall.count; if (r->ofcall.count > sizeof(buf)-1) r->ofcall.count = sizeof(buf)-1; memmove(buf, r->ifcall.data, r->ofcall.count); buf[r->ofcall.count] = 0; l = strlen(buf); if (l > 0 && buf[l-1] == '\n') buf[l-1] = 0; qid = r->fid->qid.path; switch(qid){ case Qdir: respond(r, Eperm); return; case Qcm11: e = cm11cmd(buf); if (e != nil){ respond(r, e); return; } break; default: devs = x10devs(x); if (qid <= 0 || qid > Ndevs || devs[qid-1].hc ==0){ respond(r, Egone); return; } // BUG: should handle 'on ' besides 'on ' e = devcmd(buf, devs[qid-1].hc, devs[qid-1].dc); if (e != nil){ respond(r, e); return; } break; } respond(r, nil); // and update our status now that it changed x10reqsts(x); vers++; } static void fsstat(Req *r) { ulong qid; qid = r->fid->qid.path; dirgen(qid == Qdir ? -1 : qid, &r->d, nil); respond(r, nil); } static char* fswalk1(Fid *fid, char *name, void*) { int n; uchar hc; uchar dc; Dev* devs; int i; if(strcmp(name, "..") == 0) // we're in / return nil; if (strcmp(name, "cm11") == 0){ fid->qid.type = 0; fid->qid.path = Qcm11; return nil; } for (i = 0; i < Ndevs; i++) if (names[i] != nil && strcmp(names[i], name) == 0){ fid->qid.type = 0; fid->qid.path = i; return nil; } if (name[0] >= 'a' && name[0] <= 'p' ){ hc = chrtohc(name[0]); n = atoi(name+1); dc = inttodc(n); devs = x10devs(x); for (i = 0; i < Ndevs; i++) if (devs[i].hc == hc && devs[i].dc == dc){ fid->qid.type = 0; fid->qid.path = i+1; return nil; } } return Enotexist; } static char* fsclone(Fid *, Fid*, void*) { return nil; } static void fswalk(Req *r) { //x10reqsts(x); walkandclone(r, fswalk1, fsclone, nil); } static void fswstat(Req* r) { ulong qid; Dev* devs; qid = r->fid->qid.path; switch(qid){ case Qdir: respond(r, Eperm); return; case Qcm11: qid = 0; goto chperm; default: devs = x10devs(x); if (qid <= 0 || qid > Ndevs || devs[qid-1].hc ==0){ respond(r, Egone); return; } chperm: if (r->d.uid && r->d.uid[0]){ if (uids[qid] != nil && strcmp(uids[qid], r->d.uid) != 0 ){ respond(r, Eperm); return; } uids[qid] = strdup(r->d.uid); } if (r->d.gid && r->d.gid[0]){ if (gids[qid] != nil){ respond(r, Eperm); return; } gids[qid] = strdup(r->d.gid); } if (~(ulong)r->d.mode){ if (!uids[qid] && modes[qid] != 0){ respond(r, Eperm); return; } if (uids[qid] && strcmp(r->fid->uid, uids[qid])){ respond(r, Eperm); return; } modes[qid] = r->d.mode; } } respond(r, nil); } static Channel* reqc; static Channel* waitc; static void fssend(Req* r) { sendp(reqc, r); recvp(waitc); } static void shutdown(Srv* p) { close(p->infd); free(p); threadexits("done"); } static void fsproc(void*) { Req* r; threadsetname("fsproc"); for(;;){ r = recvp(reqc); switch(r->ifcall.type){ case Tauth: fsauth(r); break; case Tattach: fsattach(r); break; case Topen: fsopen(r); break; case Tread: fsread(r); break; case Twrite: fswrite(r); break; case Tstat: fsstat(r); break; case Twstat: fswstat(r); break; case Twalk: fswalk(r); break; default: respond(r, "bug in fsproc: bad ifcall type"); break; } sendp(waitc, nil); } } static Srv sfs= { .auth= fssend, .attach=fssend, .open= fssend, .read= fssend, .write= fssend, .stat= fssend, .wstat= fssend, .walk= fssend, .end= shutdown, }; static void perm(char* ln) { char* args[10]; int nargs; int n; // perm name owner group mode nargs = tokenize(ln, args, nelem(args)); if (nargs != 5) return; if (*args[1] != myhc) return; n = atoi(args[1]+1); if (n <0 || n >= Ndevs) return; uids[n] = strdup(args[2]); gids[n] = strdup(args[3]); modes[n]= strtol(args[4], nil, 0); } static void setname(char* ln) { char* args[10]; int nargs; int n; // name a1 str nargs = tokenize(ln, args, nelem(args)); if (nargs != 3) return; if (*args[1] != myhc) return; n = atoi(args[1]+1); if (n <0 || n >= Ndevs) return; names[n] = estrdup9p(args[2]); } static void config(char* conf) { Biobuf* bc; char* ln; char* c; if (conf == nil) return; bc = Bopen(conf, OREAD); if (bc == nil) return; while(ln = Brdstr(bc, '\n', 1)){ c = strchr(ln, '#'); if (c != nil) *c = 0; if (ln[0] == '\n' || ln[0] == '#' || ln[0] == 0) continue; if (strncmp("perm", ln, 4) == 0) perm(ln); else if (strncmp("name", ln, 4) == 0) setname(ln); else cm11cmd(ln); } Bterm(bc); } typedef struct PArg PArg; struct PArg { int lfd; char ldir[40]; }; static void srvproc(void* a) { PArg* p; Srv* msrv; int dfd; p = a; threadsetname("srvproc"); dfd = accept(p->lfd, p->ldir); if (dfd < 0){ free(p); threadexits(nil); } close(p->lfd); free(p); msrv = emalloc9p(sizeof(Srv)); *msrv = sfs; msrv->infd = dfd; msrv->outfd= dfd; msrv->srvfd= -1; msrv->nopipe= 1; rfork(RFNOTEG); srv(msrv); threadexits(nil); } static void listener(void *) { int afd, lfd; char adir[40]; char ldir[40]; PArg* p; threadsetname("listener"); afd = announce("tcp!*!19000", adir); if (afd < 0) sysfatal("can't announce: %r"); for(;;){ lfd = listen(adir, ldir); if (lfd < 0) sysfatal("can't listen: %r"); p = emalloc9p(sizeof(PArg)); p->lfd = lfd; strcpy(p->ldir, ldir); proccreate(srvproc, p, 32*1024); } } static void announceproc(void*) { int afd = -1; char* addr; addr = smprint("tcp!%s!19000 ", sysname()); for(;;){ afd = announcevol(afd, addr, "/devs/x10", nil); sleep(10 * 1000); } } void pfs(X10* p, char hc, char* conf) { x = p; myhc = hc; config(conf); x10reqsts(x); reqc = chancreate(sizeof(void*), 0); waitc = chancreate(sizeof(void*), 0); if (proccreate(announceproc, nil, 8*1024) < 0) sysfatal("procrfork: %r"); if (proccreate(listener, nil, 32*1024) < 0) sysfatal("procrfork: %r"); proccreate(fsproc, nil, 32*1024); syslog(0, logf, "X10 house %c started", hc); }