/* already in plan9.h #include *//* for struct passwd, struct group, struct stat ... */ /* plan9.h is first to get the large file support definitions as early as possible */ #include #include /* for stat, umask */ #include /* for malloc */ #include /* for strcpy, memmove */ #include /* for getpwnam, getpwuid */ #include /* for getgrnam, getgrgid */ #include /* for gethostname, pread, pwrite, read, write */ #include /* for utime */ #include /* for readdir */ #include /* for errno */ #include /* for remove [sic] */ #include /* for O_RDONLY, etc. */ #include /* various networking crud */ #include #include #include #include #include /* #ifndef because can be given in makefile */ #ifndef DEFAULTLOG #define DEFAULTLOG "/tmp/u9fs.log" #endif char *logfile = DEFAULTLOG; #define S_ISSPECIAL(m) (S_ISCHR(m) || S_ISBLK(m) || S_ISFIFO(m)) enum { Tdot = 1, Tdotdot }; enum { P9P1, P9P2000 }; typedef struct User User; struct User { int id; gid_t defaultgid; char *name; char **mem; /* group members */ int nmem; User *next; }; struct Fid { int fid; char *path; struct stat st; User *u; int omode; DIR *dir; int diroffset; int fd; struct dirent *dirent; int direof; Fid *next; Fid *prev; int auth; void *authmagic; }; void* emalloc(size_t); void* erealloc(void*, size_t); char* estrdup(char*); char* estrpath(char*, char*, int); void sysfatal(char*, ...); int okuser(char*); void rversion(Fcall*, Fcall*); void rauth(Fcall*, Fcall*); void rattach(Fcall*, Fcall*); void rflush(Fcall*, Fcall*); void rclone(Fcall*, Fcall*); void rwalk(Fcall*, Fcall*); void ropen(Fcall*, Fcall*); void rcreate(Fcall*, Fcall*); void rread(Fcall*, Fcall*); void rwrite(Fcall*, Fcall*); void rclunk(Fcall*, Fcall*); void rstat(Fcall*, Fcall*); void rwstat(Fcall*, Fcall*); void rclwalk(Fcall*, Fcall*); void rremove(Fcall*, Fcall*); User* uname2user(char*); User* gname2user(char*); User* uid2user(int); User* gid2user(int); Fid* newfid(int, char**); Fid* oldfidex(int, int, char**); Fid* oldfid(int, char**); int fidstat(Fid*, char**); void freefid(Fid*); int userchange(User*, char**); int userwalk(User*, char**, char*, Qid*, char**); int useropen(Fid*, int, char**); int usercreate(Fid*, char*, int, long, char**); int userremove(Fid*, char**); int userperm(User*, char*, int, int); int useringroup(User*, User*); Qid stat2qid(struct stat*); void getfcallold(int, Fcall*, int); void putfcallold(int, Fcall*); char Eauth[] = "authentication failed"; char Ebadfid[] = "fid unknown or out of range"; char Ebadoffset[] = "bad offset in directory read"; char Ebadusefid[] = "bad use of fid"; char Edirchange[] = "wstat can't convert between files and directories"; char Eexist[] = "file or directory already exists"; char Efidactive[] = "fid already in use"; char Enotdir[] = "not a directory"; char Enotingroup[] = "not a member of proposed group"; char Enotowner[] = "only owner can change group in wstat"; char Eperm[] = "permission denied"; char Especial0[] = "already attached without access to special files"; char Especial1[] = "already attached with access to special files"; char Especial[] = "no access to special file"; char Etoolarge[] = "i/o count too large"; char Eunknowngroup[] = "unknown group"; char Eunknownuser[] = "unknown user"; char Ewstatbuffer[] = "bogus wstat buffer"; ulong msize = IOHDRSZ+8192; uchar* rxbuf; uchar* txbuf; void* databuf; int connected; int devallowed; char* autharg; char* defaultuser; char hostname[256]; char remotehostname[256]; int chatty9p = 0; int network = 1; int old9p = -1; int authed; User* none; Auth *authmethods[] = { /* first is default */ &authrhosts, &authp9any, &authnone, }; Auth *auth; /* * frogs: characters not valid in plan9 * filenames, keep this list in sync with * /sys/src/9/port/chan.c:1656 */ char isfrog[256]={ /*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1, /*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1, /*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1, /*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1, ['/'] 1, [0x7f] 1, }; void getfcallnew(int fd, Fcall *fc, int have) { int len; if(have > BIT32SZ) sysfatal("cannot happen"); if(have < BIT32SZ && readn(fd, rxbuf+have, BIT32SZ-have) != BIT32SZ-have) sysfatal("couldn't read message"); len = GBIT32(rxbuf); if(len <= BIT32SZ) sysfatal("bogus message"); len -= BIT32SZ; if(readn(fd, rxbuf+BIT32SZ, len) != len) sysfatal("short message"); if(convM2S(rxbuf, len+BIT32SZ, fc) != len+BIT32SZ) sysfatal("badly sized message type %d", rxbuf[0]); } void getfcallold(int fd, Fcall *fc, int have) { int len, n; if(have > 3) sysfatal("cannot happen"); if(have < 3 && readn(fd, rxbuf, 3-have) != 3-have) sysfatal("couldn't read message"); len = oldhdrsize(rxbuf[0]); if(len < 3) sysfatal("bad message %d", rxbuf[0]); if(len > 3 && readn(fd, rxbuf+3, len-3) != len-3) sysfatal("couldn't read message"); n = iosize(rxbuf); if(readn(fd, rxbuf+len, n) != n) sysfatal("couldn't read message"); len += n; if(convM2Sold(rxbuf, len, fc) != len) sysfatal("badly sized message type %d", rxbuf[0]); } void putfcallnew(int wfd, Fcall *tx) { uint n; if((n = convS2M(tx, txbuf, msize)) == 0) sysfatal("couldn't format message type %d", tx->type); if(write(wfd, txbuf, n) != n) sysfatal("couldn't send message"); } void putfcallold(int wfd, Fcall *tx) { uint n; if((n = convS2Mold(tx, txbuf, msize)) == 0) sysfatal("couldn't format message type %d", tx->type); if(write(wfd, txbuf, n) != n) sysfatal("couldn't send message"); } void getfcall(int fd, Fcall *fc) { if(old9p == 1){ getfcallold(fd, fc, 0); return; } if(old9p == 0){ getfcallnew(fd, fc, 0); return; } /* auto-detect */ if(readn(fd, rxbuf, 3) != 3) sysfatal("couldn't read message"); /* is it an old (9P1) message? */ if(50 <= rxbuf[0] && rxbuf[0] <= 87 && (rxbuf[0]&1)==0 && GBIT16(rxbuf+1) == 0xFFFF){ old9p = 1; getfcallold(fd, fc, 3); return; } getfcallnew(fd, fc, 3); old9p = 0; } void seterror(Fcall *f, char *error) { f->type = Rerror; f->ename = error ? error : "programmer error"; } int isowner(User *u, Fid *f) { return u->id == f->st.st_uid; } void serve(int rfd, int wfd) { Fcall rx, tx; for(;;){ getfcall(rfd, &rx); if(chatty9p) fprint(2, "<- %F\n", &rx); memset(&tx, 0, sizeof tx); tx.type = rx.type+1; tx.tag = rx.tag; switch(rx.type){ case Tflush: break; case Tversion: rversion(&rx, &tx); break; case Tauth: rauth(&rx, &tx); break; case Tattach: rattach(&rx, &tx); break; case Twalk: rwalk(&rx, &tx); break; case Tstat: tx.stat = databuf; rstat(&rx, &tx); break; case Twstat: rwstat(&rx, &tx); break; case Topen: ropen(&rx, &tx); break; case Tcreate: rcreate(&rx, &tx); break; case Tread: tx.data = databuf; rread(&rx, &tx); break; case Twrite: rwrite(&rx, &tx); break; case Tclunk: rclunk(&rx, &tx); break; case Tremove: rremove(&rx, &tx); break; default: fprint(2, "unknown message %F\n", &rx); seterror(&tx, "bad message"); break; } if(chatty9p) fprint(2, "-> %F\n", &tx); (old9p ? putfcallold : putfcallnew)(wfd, &tx); } } void rversion(Fcall *rx, Fcall *tx) { if(msize > rx->msize) msize = rx->msize; tx->msize = msize; if(strncmp(rx->version, "9P", 2) != 0) tx->version = "unknown"; else tx->version = "9P2000"; } void rauth(Fcall *rx, Fcall *tx) { char *e; if((e = auth->auth(rx, tx)) != nil) seterror(tx, e); } void rattach(Fcall *rx, Fcall *tx) { char *e; Fid *fid; User *u; if(rx->aname == nil) rx->aname = ""; if(strcmp(rx->aname, "device") == 0){ if(connected && !devallowed){ seterror(tx, Especial0); return; } devallowed = 1; }else{ if(connected && devallowed){ seterror(tx, Especial1); return; } } if(strcmp(rx->uname, "none") == 0){ if(authed == 0){ seterror(tx, Eauth); return; } if (none != nil) rx->uname = none->name; } else { if((e = auth->attach(rx, tx)) != nil){ seterror(tx, e); return; } authed++; } if((fid = newfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } fid->path = estrdup("/"); if(fidstat(fid, &e) < 0){ seterror(tx, e); freefid(fid); return; } if(defaultuser) rx->uname = defaultuser; if((u = uname2user(rx->uname)) == nil || (!defaultuser && u->id == 0)){ /* we don't know anyone named root... */ seterror(tx, Eunknownuser); freefid(fid); return; } fid->u = u; tx->qid = stat2qid(&fid->st); return; } void rwalk(Fcall *rx, Fcall *tx) { int i; char *path, *e; Fid *fid, *nfid; e = nil; if((fid = oldfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } if(fid->omode != -1){ seterror(tx, Ebadusefid); return; } if(fidstat(fid, &e) < 0){ seterror(tx, e); return; } if(!S_ISDIR(fid->st.st_mode) && rx->nwname){ seterror(tx, Enotdir); return; } nfid = nil; if(rx->newfid != rx->fid && (nfid = newfid(rx->newfid, &e)) == nil){ seterror(tx, e); return; } path = estrdup(fid->path); e = nil; for(i=0; inwname; i++) if(userwalk(fid->u, &path, rx->wname[i], &tx->wqid[i], &e) < 0) break; if(i == rx->nwname){ /* successful clone or walk */ tx->nwqid = i; if(nfid){ nfid->path = path; nfid->u = fid->u; }else{ free(fid->path); fid->path = path; } }else{ if(i > 0) /* partial walk? */ tx->nwqid = i; else seterror(tx, e); if(nfid) /* clone implicit new fid */ freefid(nfid); free(path); } return; } void ropen(Fcall *rx, Fcall *tx) { char *e; Fid *fid; if((fid = oldfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } if(fid->omode != -1){ seterror(tx, Ebadusefid); return; } if(fidstat(fid, &e) < 0){ seterror(tx, e); return; } if(!devallowed && S_ISSPECIAL(fid->st.st_mode)){ seterror(tx, Especial); return; } if(useropen(fid, rx->mode, &e) < 0){ seterror(tx, e); return; } tx->iounit = 0; tx->qid = stat2qid(&fid->st); } void rcreate(Fcall *rx, Fcall *tx) { char *e; Fid *fid; if((fid = oldfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } if(fid->omode != -1){ seterror(tx, Ebadusefid); return; } if(fidstat(fid, &e) < 0){ seterror(tx, e); return; } if(!S_ISDIR(fid->st.st_mode)){ seterror(tx, Enotdir); return; } if(usercreate(fid, rx->name, rx->mode, rx->perm, &e) < 0){ seterror(tx, e); return; } if(fidstat(fid, &e) < 0){ seterror(tx, e); return; } tx->iounit = 0; tx->qid = stat2qid(&fid->st); } uchar modebyte(struct stat *st) { uchar b; b = 0; if(S_ISDIR(st->st_mode)) b |= QTDIR; /* no way to test append-only */ /* no real way to test exclusive use, but mark devices as such */ if(S_ISSPECIAL(st->st_mode)) b |= QTEXCL; return b; } ulong plan9mode(struct stat *st) { return ((ulong)modebyte(st)<<24) | (st->st_mode & 0777); } /* * this is for chmod, so don't worry about S_IFDIR */ mode_t unixmode(Dir *d) { return (mode_t)(d->mode&0777); } Qid stat2qid(struct stat *st) { uchar *p, *ep, *q; Qid qid; /* * For now, ignore the device number. */ qid.path = 0; p = (uchar*)&qid.path; ep = p+sizeof(qid.path); q = p+sizeof(ino_t); if(q > ep){ fprint(2, "warning: inode number too big\n"); q = ep; } memmove(p, &st->st_ino, q-p); q = q+sizeof(dev_t); if(q > ep){ /* * fprint(2, "warning: inode number + device number too big %d+%d\n", * sizeof(ino_t), sizeof(dev_t)); */ q = ep - sizeof(dev_t); if(q < p) fprint(2, "warning: device number too big by itself\n"); else *(dev_t*)q ^= st->st_dev; } qid.vers = st->st_mtime ^ (st->st_size << 8); qid.type = modebyte(st); return qid; } char * enfrog(char *src) { char *d, *dst; uchar *s; d = dst = emalloc(strlen(src)*3 + 1); for (s = (uchar *)src; *s; s++) if(isfrog[*s] || *s == '\\') d += sprintf(d, "\\%02x", *s); else *d++ = *s; *d = 0; return dst; } char * defrog(char *s) { char *d, *dst, buf[3]; d = dst = emalloc(strlen(s) + 1); for(; *s; s++) if(*s == '\\' && strlen(s) >= 3){ buf[0] = *++s; /* skip \ */ buf[1] = *++s; buf[2] = 0; *d++ = strtoul(buf, NULL, 16); } else *d++ = *s; *d = 0; return dst; } void stat2dir(char *path, struct stat *st, Dir *d) { User *u; char *q, *p, *npath; memset(d, 0, sizeof(*d)); d->qid = stat2qid(st); d->mode = plan9mode(st); d->atime = st->st_atime; d->mtime = st->st_mtime; d->length = st->st_size; d->uid = (u = uid2user(st->st_uid)) ? u->name : "???"; d->gid = (u = gid2user(st->st_gid)) ? u->name : "???"; d->muid = ""; if((q = strrchr(path, '/')) != nil) d->name = enfrog(q+1); else d->name = enfrog(path); } void rread(Fcall *rx, Fcall *tx) { char *e, *path; uchar *p, *ep; int n; Fid *fid; Dir d; struct stat st; if(rx->count > msize-IOHDRSZ){ seterror(tx, Etoolarge); return; } if((fid = oldfidex(rx->fid, -1, &e)) == nil){ seterror(tx, e); return; } if (fid->auth) { char *e; e = auth->read(rx, tx); if (e) seterror(tx, e); return; } if(fid->omode == -1 || (fid->omode&3) == OWRITE){ seterror(tx, Ebadusefid); return; } if(fid->dir){ if(rx->offset != fid->diroffset){ if(rx->offset != 0){ seterror(tx, Ebadoffset); return; } rewinddir(fid->dir); fid->diroffset = 0; fid->direof = 0; } if(fid->direof){ tx->count = 0; return; } p = (uchar*)tx->data; ep = (uchar*)tx->data+rx->count; for(;;){ if(p+BIT16SZ >= ep) break; if(fid->dirent == nil) /* one entry cache for when convD2M fails */ if((fid->dirent = readdir(fid->dir)) == nil){ fid->direof = 1; break; } if(strcmp(fid->dirent->d_name, ".") == 0 || strcmp(fid->dirent->d_name, "..") == 0){ fid->dirent = nil; continue; } path = estrpath(fid->path, fid->dirent->d_name, 0); memset(&st, 0, sizeof st); if(stat(path, &st) < 0){ fprint(2, "dirread: stat(%s) failed: %s\n", path, strerror(errno)); fid->dirent = nil; free(path); continue; } free(path); stat2dir(fid->dirent->d_name, &st, &d); if((n=(old9p ? convD2Mold : convD2M)(&d, p, ep-p)) <= BIT16SZ) break; p += n; fid->dirent = nil; } tx->count = p - (uchar*)tx->data; fid->diroffset += tx->count; }else{ if((n = pread(fid->fd, tx->data, rx->count, rx->offset)) < 0){ seterror(tx, strerror(errno)); return; } tx->count = n; } } void rwrite(Fcall *rx, Fcall *tx) { char *e; Fid *fid; int n; if(rx->count > msize-IOHDRSZ){ seterror(tx, Etoolarge); return; } if((fid = oldfidex(rx->fid, -1, &e)) == nil){ seterror(tx, e); return; } if (fid->auth) { char *e; e = auth->write(rx, tx); if (e) seterror(tx, e); return; } if(fid->omode == -1 || (fid->omode&3) == OREAD || (fid->omode&3) == OEXEC){ seterror(tx, Ebadusefid); return; } if((n = pwrite(fid->fd, rx->data, rx->count, rx->offset)) < 0){ seterror(tx, strerror(errno)); return; } tx->count = n; } void rclunk(Fcall *rx, Fcall *tx) { char *e; Fid *fid; if((fid = oldfidex(rx->fid, -1, &e)) == nil){ seterror(tx, e); return; } if (fid->auth) { if (auth->clunk) { e = (*auth->clunk)(rx, tx); if (e) { seterror(tx, e); return; } } } else if(fid->omode != -1 && fid->omode&ORCLOSE) remove(fid->path); freefid(fid); } void rremove(Fcall *rx, Fcall *tx) { char *e; Fid *fid; if((fid = oldfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } if(userremove(fid, &e) < 0) seterror(tx, e); freefid(fid); } void rstat(Fcall *rx, Fcall *tx) { char *e; Fid *fid; Dir d; if((fid = oldfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } if(fidstat(fid, &e) < 0){ seterror(tx, e); return; } stat2dir(fid->path, &fid->st, &d); if((tx->nstat=(old9p ? convD2Mold : convD2M)(&d, tx->stat, msize)) <= BIT16SZ) seterror(tx, "convD2M fails"); } void rwstat(Fcall *rx, Fcall *tx) { char *e; char *p, *old, *new, *dir; gid_t gid; Dir d; Fid *fid; if((fid = oldfid(rx->fid, &e)) == nil){ seterror(tx, e); return; } /* * wstat is supposed to be atomic. * we check all the things we can before trying anything. * still, if we are told to truncate a file and rename it and only * one works, we're screwed. in such cases we leave things * half broken and return an error. it's hardly perfect. */ if((old9p ? convM2Dold : convM2D)(rx->stat, rx->nstat, &d, (char*)rx->stat) <= BIT16SZ){ seterror(tx, Ewstatbuffer); return; } if(fidstat(fid, &e) < 0){ seterror(tx, e); return; } /* * The casting is necessary because d.mode is ulong and might, * on some systems, be 64 bits. We only want to compare the * bottom 32 bits, since that's all that gets sent in the protocol. * * Same situation for d.mtime and d.length (although that last check * is admittedly superfluous, given the current lack of 128-bit machines). */ gid = (gid_t)-1; if(d.gid[0] != '\0'){ User *g; g = gname2user(d.gid); if(g == nil){ seterror(tx, Eunknowngroup); return; } gid = (gid_t)g->id; if(groupchange(fid->u, gid2user(gid), &e) < 0){ seterror(tx, e); return; } } if((u32int)d.mode != (u32int)~0 && (((d.mode&DMDIR)!=0) ^ (S_ISDIR(fid->st.st_mode)!=0))){ seterror(tx, Edirchange); return; } if(strcmp(fid->path, "/") == 0){ seterror(tx, "no wstat of root"); return; } /* * try things in increasing order of harm to the file. * mtime should come after truncate so that if you * do both the mtime actually takes effect, but i'd rather * leave truncate until last. * (see above comment about atomicity). */ if((u32int)d.mode != (u32int)~0 && chmod(fid->path, unixmode(&d)) < 0){ if(chatty9p) fprint(2, "chmod(%s, 0%luo) failed\n", fid->path, unixmode(&d)); seterror(tx, strerror(errno)); return; } if((u32int)d.mtime != (u32int)~0){ struct utimbuf t; t.actime = 0; t.modtime = d.mtime; if(utime(fid->path, &t) < 0){ if(chatty9p) fprint(2, "utime(%s) failed\n", fid->path); seterror(tx, strerror(errno)); return; } } if(gid != (gid_t)-1 && gid != fid->st.st_gid){ if(chown(fid->path, (uid_t)-1, gid) < 0){ if(chatty9p) fprint(2, "chgrp(%s, %d) failed\n", fid->path, gid); seterror(tx, strerror(errno)); return; } } if(d.name[0]){ old = fid->path; dir = estrdup(fid->path); if((p = strrchr(dir, '/')) > dir) *p = '\0'; else{ seterror(tx, "whoops: can't happen in u9fs"); return; } new = estrpath(dir, d.name, 1); if(strcmp(old, new) != 0 && rename(old, new) < 0){ if(chatty9p) fprint(2, "rename(%s, %s) failed\n", old, new); seterror(tx, strerror(errno)); free(new); free(dir); return; } fid->path = new; free(old); free(dir); } if((u64int)d.length != (u64int)~0 && truncate(fid->path, d.length) < 0){ fprint(2, "truncate(%s, %lld) failed\n", fid->path, d.length); seterror(tx, strerror(errno)); return; } } /* * we keep a table by numeric id. by name lookups happen infrequently * while by-number lookups happen once for every directory entry read * and every stat request. */ User *utab[64]; User *gtab[64]; User* adduser(struct passwd *p) { User *u; u = emalloc(sizeof(*u)); u->id = p->pw_uid; u->name = estrdup(p->pw_name); u->next = utab[p->pw_uid%nelem(utab)]; u->defaultgid = p->pw_gid; utab[p->pw_uid%nelem(utab)] = u; return u; } int useringroup(User *u, User *g) { int i; for(i=0; inmem; i++) if(strcmp(g->mem[i], u->name) == 0) return 1; /* * Hack around common Unix problem that everyone has * default group "user" but /etc/group lists no members. */ if(u->defaultgid == g->id) return 1; return 0; } User* addgroup(struct group *g) { User *u; char **p; int n; u = emalloc(sizeof(*u)); n = 0; for(p=g->gr_mem; *p; p++) n++; u->mem = emalloc(sizeof(u->mem[0])*n); n = 0; for(p=g->gr_mem; *p; p++) u->mem[n++] = estrdup(*p); u->nmem = n; u->id = g->gr_gid; u->name = estrdup(g->gr_name); u->next = gtab[g->gr_gid%nelem(gtab)]; gtab[g->gr_gid%nelem(gtab)] = u; return u; } User* uname2user(char *name) { int i; User *u; struct passwd *p; for(i=0; inext) if(strcmp(u->name, name) == 0) return u; if((p = getpwnam(name)) == nil) return nil; return adduser(p); } User* uid2user(int id) { User *u; struct passwd *p; for(u=utab[id%nelem(utab)]; u; u=u->next) if(u->id == id) return u; if((p = getpwuid(id)) == nil) return nil; return adduser(p); } User* gname2user(char *name) { int i; User *u; struct group *g; for(i=0; inext) if(strcmp(u->name, name) == 0) return u; if((g = getgrnam(name)) == nil) return nil; return addgroup(g); } User* gid2user(int id) { User *u; struct group *g; for(u=gtab[id%nelem(gtab)]; u; u=u->next) if(u->id == id) return u; if((g = getgrgid(id)) == nil) return nil; return addgroup(g); } void sysfatal(char *fmt, ...) { char buf[1024]; va_list va, temp; va_start(va, fmt); va_copy(temp, va); doprint(buf, buf+sizeof buf, fmt, &temp); va_end(temp); va_end(va); fprint(2, "u9fs: %s\n", buf); fprint(2, "last unix error: %s\n", strerror(errno)); exit(1); } void* emalloc(size_t n) { void *p; if(n == 0) n = 1; p = malloc(n); if(p == 0) sysfatal("malloc(%ld) fails", (long)n); memset(p, 0, n); return p; } void* erealloc(void *p, size_t n) { if(p == 0) p = malloc(n); else p = realloc(p, n); if(p == 0) sysfatal("realloc(..., %ld) fails", (long)n); return p; } char* estrdup(char *p) { p = strdup(p); if(p == 0) sysfatal("strdup(%.20s) fails", p); return p; } char* estrpath(char *p, char *q, int frog) { char *r, *s; if(strcmp(q, "..") == 0){ r = estrdup(p); if((s = strrchr(r, '/')) && s > r) *s = '\0'; else if(s == r) s[1] = '\0'; return r; } if(frog) q = defrog(q); else q = strdup(q); r = emalloc(strlen(p)+1+strlen(q)+1); strcpy(r, p); if(r[0]=='\0' || r[strlen(r)-1] != '/') strcat(r, "/"); strcat(r, q); free(q); return r; } Fid *fidtab[1]; Fid* lookupfid(int fid) { Fid *f; for(f=fidtab[fid%nelem(fidtab)]; f; f=f->next) if(f->fid == fid) return f; return nil; } Fid* newfid(int fid, char **ep) { Fid *f; if(lookupfid(fid) != nil){ *ep = Efidactive; return nil; } f = emalloc(sizeof(*f)); f->next = fidtab[fid%nelem(fidtab)]; if(f->next) f->next->prev = f; fidtab[fid%nelem(fidtab)] = f; f->fid = fid; f->fd = -1; f->omode = -1; return f; } Fid* newauthfid(int fid, void *magic, char **ep) { Fid *af; af = newfid(fid, ep); if (af == nil) return nil; af->auth = 1; af->authmagic = magic; return af; } Fid* oldfidex(int fid, int auth, char **ep) { Fid *f; if((f = lookupfid(fid)) == nil){ *ep = Ebadfid; return nil; } if (auth != -1 && f->auth != auth) { *ep = Ebadfid; return nil; } if (!f->auth) { if(userchange(f->u, ep) < 0) return nil; } return f; } Fid* oldfid(int fid, char **ep) { return oldfidex(fid, 0, ep); } Fid* oldauthfid(int fid, void **magic, char **ep) { Fid *af; af = oldfidex(fid, 1, ep); if (af == nil) return nil; *magic = af->authmagic; return af; } void freefid(Fid *f) { if(f->prev) f->prev->next = f->next; else fidtab[f->fid%nelem(fidtab)] = f->next; if(f->next) f->next->prev = f->prev; if(f->dir) closedir(f->dir); if(f->fd) close(f->fd); free(f->path); free(f); } int fidstat(Fid *fid, char **ep) { if(stat(fid->path, &fid->st) < 0){ fprint(2, "fidstat(%s) failed\n", fid->path); if(ep) *ep = strerror(errno); return -1; } if(S_ISDIR(fid->st.st_mode)) fid->st.st_size = 0; return 0; } int userchange(User *u, char **ep) { if(defaultuser) return 0; if(setreuid(0, 0) < 0){ fprint(2, "setreuid(0, 0) failed\n"); *ep = "cannot setuid back to root"; return -1; } /* * Initgroups does not appear to be SUSV standard. * But it exists on SGI and on Linux, which makes me * think it's standard enough. We have to do something * like this, and the closest other function I can find is * setgroups (which initgroups eventually calls). * Setgroups is the same as far as standardization though, * so we're stuck using a non-SUSV call. Sigh. */ if(initgroups(u->name, u->defaultgid) < 0) fprint(2, "initgroups(%s) failed: %s\n", u->name, strerror(errno)); if(setreuid(-1, u->id) < 0){ fprint(2, "setreuid(-1, %s) failed\n", u->name); *ep = strerror(errno); return -1; } return 0; } /* * We do our own checking here, then switch to root temporarily * to set our gid. In a perfect world, you'd be allowed to set your * egid to any of the supplemental groups of your euid, but this * is not the case on Linux 2.2.14 (and perhaps others). * * This is a race, of course, but it's a race against processes * that can edit the group lists. If you can do that, you can * change your own group without our help. */ int groupchange(User *u, User *g, char **ep) { if(g == nil) return -1; if(!useringroup(u, g)){ if(chatty9p) fprint(2, "%s not in group %s\n", u->name, g->name); *ep = Enotingroup; return -1; } setreuid(0,0); if(setregid(-1, g->id) < 0){ fprint(2, "setegid(%s/%d) failed in groupchange\n", g->name, g->id); *ep = strerror(errno); return -1; } if(userchange(u, ep) < 0) return -1; return 0; } /* * An attempt to enforce permissions by looking at the * file system. Separation of checking permission and * actually performing the action is a terrible idea, of * course, so we use setreuid for most of the permission * enforcement. This is here only so we can give errors * on open(ORCLOSE) in some cases. */ int userperm(User *u, char *path, int type, int need) { char *p, *q; int i, have; struct stat st; User *g; switch(type){ default: fprint(2, "bad type %d in userperm\n", type); return -1; case Tdot: if(stat(path, &st) < 0){ fprint(2, "userperm: stat(%s) failed\n", path); return -1; } break; case Tdotdot: p = estrdup(path); if((q = strrchr(p, '/'))==nil){ fprint(2, "userperm(%s, ..): bad path\n", p); free(p); return -1; } if(q > p) *q = '\0'; else *(q+1) = '\0'; if(stat(p, &st) < 0){ fprint(2, "userperm: stat(%s) (dotdot of %s) failed\n", p, path); free(p); return -1; } free(p); break; } if(u == none){ fprint(2, "userperm: none wants %d in 0%luo\n", need, st.st_mode); have = st.st_mode&7; if((have&need)==need) return 0; return -1; } have = st.st_mode&7; if((uid_t)u->id == st.st_uid) have |= (st.st_mode>>6)&7; if((have&need)==need) return 0; if(((have|((st.st_mode>>3)&7))&need) != need) /* group won't help */ return -1; g = gid2user(st.st_gid); for(i=0; inmem; i++){ if(strcmp(g->mem[i], u->name) == 0){ have |= (st.st_mode>>3)&7; break; } } if((have&need)==need) return 0; return -1; } int userwalk(User *u, char **path, char *elem, Qid *qid, char **ep) { char *npath; struct stat st; npath = estrpath(*path, elem, 1); if(stat(npath, &st) < 0){ free(npath); *ep = strerror(errno); return -1; } *qid = stat2qid(&st); free(*path); *path = npath; return 0; } int useropen(Fid *fid, int omode, char **ep) { int a, o; /* * Check this anyway, to try to head off problems later. */ if((omode&ORCLOSE) && userperm(fid->u, fid->path, Tdotdot, W_OK) < 0){ *ep = Eperm; return -1; } switch(omode&3){ default: *ep = "programmer error"; return -1; case OREAD: a = R_OK; o = O_RDONLY; break; case ORDWR: a = R_OK|W_OK; o = O_RDWR; break; case OWRITE: a = W_OK; o = O_WRONLY; break; case OEXEC: a = X_OK; o = O_RDONLY; break; } if(omode & OTRUNC){ a |= W_OK; o |= O_TRUNC; } if(S_ISDIR(fid->st.st_mode)){ if(a != R_OK){ fprint(2, "attempt by %s to open dir %d\n", fid->u->name, omode); *ep = Eperm; return -1; } if((fid->dir = opendir(fid->path)) == nil){ *ep = strerror(errno); return -1; } }else{ /* * This is wrong because access used the real uid * and not the effective uid. Let the open sort it out. * if(access(fid->path, a) < 0){ *ep = strerror(errno); return -1; } * */ if((fid->fd = open(fid->path, o)) < 0){ *ep = strerror(errno); return -1; } } fid->omode = omode; return 0; } int usercreate(Fid *fid, char *elem, int omode, long perm, char **ep) { int o, m; char *opath, *npath; struct stat st, parent; if(stat(fid->path, &parent) < 0){ *ep = strerror(errno); return -1; } /* * Change group so that created file has expected group * by Plan 9 semantics. If that fails, might as well go * with the user's default group. */ if(groupchange(fid->u, gid2user(parent.st_gid), ep) < 0 && groupchange(fid->u, gid2user(fid->u->defaultgid), ep) < 0) return -1; m = (perm & DMDIR) ? 0777 : 0666; perm = perm & (~m | (fid->st.st_mode & m)); npath = estrpath(fid->path, elem, 1); if(perm & DMDIR){ if((omode&~ORCLOSE) != OREAD){ *ep = Eperm; free(npath); return -1; } if(stat(npath, &st) >= 0 || errno != ENOENT){ *ep = Eexist; free(npath); return -1; } /* race */ if(mkdir(npath, (perm|0400)&0777) < 0){ *ep = strerror(errno); free(npath); return -1; } if((fid->dir = opendir(npath)) == nil){ *ep = strerror(errno); remove(npath); /* race */ free(npath); return -1; } }else{ o = O_CREAT|O_EXCL; switch(omode&3){ default: *ep = "programmer error"; return -1; case OREAD: case OEXEC: o |= O_RDONLY; break; case ORDWR: o |= O_RDWR; break; case OWRITE: o |= O_WRONLY; break; } if(omode & OTRUNC) o |= O_TRUNC; if((fid->fd = open(npath, o, perm&0777)) < 0){ if(chatty9p) fprint(2, "create(%s, 0x%x, 0%o) failed\n", npath, o, perm&0777); *ep = strerror(errno); free(npath); return -1; } } opath = fid->path; fid->path = npath; if(fidstat(fid, ep) < 0){ fprint(2, "stat after create on %s failed\n", npath); remove(npath); /* race */ free(npath); fid->path = opath; if(fid->fd >= 0){ close(fid->fd); fid->fd = -1; }else{ closedir(fid->dir); fid->dir = nil; } return -1; } fid->omode = omode; free(opath); return 0; } int userremove(Fid *fid, char **ep) { if(remove(fid->path) < 0){ *ep = strerror(errno); return -1; } return 0; } void usage(void) { fprint(2, "usage: u9fs [-Dnz] [-a authmethod] [-m msize] [-u user] [root]\n"); exit(1); } int main(int argc, char **argv) { char *authtype; int i; int fd; int logflag; auth = authmethods[0]; logflag = O_WRONLY|O_APPEND|O_CREAT; ARGBEGIN{ case 'D': chatty9p = 1; break; case 'a': authtype = EARGF(usage()); auth = nil; for(i=0; iname, authtype)==0) auth = authmethods[i]; if(auth == nil) sysfatal("unknown auth type '%s'", authtype); break; case 'A': autharg = EARGF(usage()); break; case 'l': logfile = EARGF(usage()); break; case 'm': msize = strtol(EARGF(usage()), 0, 0); break; case 'n': network = 0; break; case 'u': defaultuser = EARGF(usage()); break; case 'z': logflag |= O_TRUNC; }ARGEND if(argc > 1) usage(); fd = open(logfile, logflag, 0666); if(fd < 0) sysfatal("cannot open log '%s'", logfile); if(dup2(fd, 2) < 0) sysfatal("cannot dup fd onto stderr"); fprint(2, "u9fs\nkill %d\n", (int)getpid()); fmtinstall('F', fcallconv); fmtinstall('D', dirconv); fmtinstall('M', dirmodeconv); rxbuf = emalloc(msize); txbuf = emalloc(msize); databuf = emalloc(msize); if(auth->init) auth->init(); if(network) getremotehostname(remotehostname, sizeof remotehostname); if(gethostname(hostname, sizeof hostname) < 0) strcpy(hostname, "gnot"); umask(0); if(argc == 1) if(chroot(argv[0]) < 0) sysfatal("chroot '%s' failed", argv[0]); none = uname2user("none"); if(none == nil) none = uname2user("nobody"); serve(0, 1); return 0; }