diff options
Diffstat (limited to 'src/cmd/fossil/9p.c')
-rw-r--r-- | src/cmd/fossil/9p.c | 1181 |
1 files changed, 1181 insertions, 0 deletions
diff --git a/src/cmd/fossil/9p.c b/src/cmd/fossil/9p.c new file mode 100644 index 00000000..c3fae5ad --- /dev/null +++ b/src/cmd/fossil/9p.c @@ -0,0 +1,1181 @@ +#include "stdinc.h" + +#include "9.h" + +enum { + OMODE = 0x7, /* Topen/Tcreate mode */ +}; + +enum { + PermX = 1, + PermW = 2, + PermR = 4, +}; + +static char EPermission[] = "permission denied"; + +static int +permFile(File* file, Fid* fid, int perm) +{ + char *u; + DirEntry de; + + if(!fileGetDir(file, &de)) + return -1; + + /* + * User none only gets other permissions. + */ + if(strcmp(fid->uname, unamenone) != 0){ + /* + * There is only one uid<->uname mapping + * and it's already cached in the Fid, but + * it might have changed during the lifetime + * if this Fid. + */ + if((u = unameByUid(de.uid)) != nil){ + if(strcmp(fid->uname, u) == 0 && ((perm<<6) & de.mode)){ + vtMemFree(u); + deCleanup(&de); + return 1; + } + vtMemFree(u); + } + if(groupMember(de.gid, fid->uname) && ((perm<<3) & de.mode)){ + deCleanup(&de); + return 1; + } + } + if(perm & de.mode){ + if(perm == PermX && (de.mode & ModeDir)){ + deCleanup(&de); + return 1; + } + if(!groupMember(uidnoworld, fid->uname)){ + deCleanup(&de); + return 1; + } + } + if(fsysNoPermCheck(fid->fsys) || (fid->con->flags&ConNoPermCheck)){ + deCleanup(&de); + return 1; + } + vtSetError(EPermission); + + deCleanup(&de); + return 0; +} + +static int +permFid(Fid* fid, int p) +{ + return permFile(fid->file, fid, p); +} + +static int +permParent(Fid* fid, int p) +{ + int r; + File *parent; + + parent = fileGetParent(fid->file); + r = permFile(parent, fid, p); + fileDecRef(parent); + + return r; +} + +int +validFileName(char* name) +{ + char *p; + + if(name == nil || name[0] == '\0'){ + vtSetError("no file name"); + return 0; + } + if(name[0] == '.'){ + if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){ + vtSetError(". and .. illegal as file name"); + return 0; + } + } + + for(p = name; *p != '\0'; p++){ + if((*p & 0xFF) < 040){ + vtSetError("bad character in file name"); + return 0; + } + } + + return 1; +} + +static int +rTwstat(Msg* m) +{ + Dir dir; + Fid *fid; + ulong mode, oldmode; + DirEntry de; + char *gid, *strs, *uid; + int gl, op, retval, tsync, wstatallow; + + if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil) + return 0; + + gid = uid = nil; + retval = 0; + + if(strcmp(fid->uname, unamenone) == 0 || (fid->qid.type & QTAUTH)){ + vtSetError(EPermission); + goto error0; + } + if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){ + vtSetError("read-only filesystem"); + goto error0; + } + + if(!fileGetDir(fid->file, &de)) + goto error0; + + strs = vtMemAlloc(m->t.nstat); + if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){ + vtSetError("wstat -- protocol botch"); + goto error; + } + + /* + * Run through each of the (sub-)fields in the provided Dir + * checking for validity and whether it's a default: + * .type, .dev and .atime are completely ignored and not checked; + * .qid.path, .qid.vers and .muid are checked for validity but + * any attempt to change them is an error. + * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can + * possibly be changed. + * + * 'Op' flags there are changed fields, i.e. it's not a no-op. + * 'Tsync' flags all fields are defaulted. + */ + tsync = 1; + if(dir.qid.path != ~0){ + if(dir.qid.path != de.qid){ + vtSetError("wstat -- attempt to change qid.path"); + goto error; + } + tsync = 0; + } + if(dir.qid.vers != ~0){ + if(dir.qid.vers != de.mcount){ + vtSetError("wstat -- attempt to change qid.vers"); + goto error; + } + tsync = 0; + } + if(dir.muid != nil && *dir.muid != '\0'){ + if((uid = uidByUname(dir.muid)) == nil){ + vtSetError("wstat -- unknown muid"); + goto error; + } + if(strcmp(uid, de.mid) != 0){ + vtSetError("wstat -- attempt to change muid"); + goto error; + } + vtMemFree(uid); + uid = nil; + tsync = 0; + } + + /* + * Check .qid.type and .mode agree if neither is defaulted. + */ + if(dir.qid.type != (uchar)~0 && dir.mode != ~0){ + if(dir.qid.type != ((dir.mode>>24) & 0xFF)){ + vtSetError("wstat -- qid.type/mode mismatch"); + goto error; + } + } + + op = 0; + + oldmode = de.mode; + if(dir.qid.type != (uchar)~0 || dir.mode != ~0){ + /* + * .qid.type or .mode isn't defaulted, check for unknown bits. + */ + if(dir.mode == ~0) + dir.mode = (dir.qid.type<<24)|(de.mode & 0777); + if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|DMTMP|0777)){ + vtSetError("wstat -- unknown bits in qid.type/mode"); + goto error; + } + + /* + * Synthesise a mode to check against the current settings. + */ + mode = dir.mode & 0777; + if(dir.mode & DMEXCL) + mode |= ModeExclusive; + if(dir.mode & DMAPPEND) + mode |= ModeAppend; + if(dir.mode & DMDIR) + mode |= ModeDir; + if(dir.mode & DMTMP) + mode |= ModeTemporary; + + if((de.mode^mode) & ModeDir){ + vtSetError("wstat -- attempt to change directory bit"); + goto error; + } + + if((de.mode & (ModeAppend|ModeExclusive|ModeTemporary|0777)) != mode){ + de.mode &= ~(ModeAppend|ModeExclusive|ModeTemporary|0777); + de.mode |= mode; + op = 1; + } + tsync = 0; + } + + if(dir.mtime != ~0){ + if(dir.mtime != de.mtime){ + de.mtime = dir.mtime; + op = 1; + } + tsync = 0; + } + + if(dir.length != ~0){ + if(dir.length != de.size){ + /* + * Cannot change length on append-only files. + * If we're changing the append bit, it's okay. + */ + if(de.mode & oldmode & ModeAppend){ + vtSetError("wstat -- attempt to change length of append-only file"); + goto error; + } + if(de.mode & ModeDir){ + vtSetError("wstat -- attempt to change length of directory"); + goto error; + } + de.size = dir.length; + op = 1; + } + tsync = 0; + } + + /* + * Check for permission to change .mode, .mtime or .length, + * must be owner or leader of either group, for which test gid + * is needed; permission checks on gid will be done later. + */ + if(dir.gid != nil && *dir.gid != '\0'){ + if((gid = uidByUname(dir.gid)) == nil){ + vtSetError("wstat -- unknown gid"); + goto error; + } + tsync = 0; + } + else + gid = vtStrDup(de.gid); + + wstatallow = (fsysWstatAllow(fid->fsys) || (m->con->flags&ConWstatAllow)); + + /* + * 'Gl' counts whether neither, one or both groups are led. + */ + gl = groupLeader(gid, fid->uname) != 0; + gl += groupLeader(de.gid, fid->uname) != 0; + + if(op && !wstatallow){ + if(strcmp(fid->uid, de.uid) != 0 && !gl){ + vtSetError("wstat -- not owner or group leader"); + goto error; + } + } + + /* + * Check for permission to change group, must be + * either owner and in new group or leader of both groups. + * If gid is nil here then + */ + if(strcmp(gid, de.gid) != 0){ + if(!wstatallow + && !(strcmp(fid->uid, de.uid) == 0 && groupMember(gid, fid->uname)) + && !(gl == 2)){ + vtSetError("wstat -- not owner and not group leaders"); + goto error; + } + vtMemFree(de.gid); + de.gid = gid; + gid = nil; + op = 1; + tsync = 0; + } + + /* + * Rename. + * Check .name is valid and different to the current. + * If so, check write permission in parent. + */ + if(dir.name != nil && *dir.name != '\0'){ + if(!validFileName(dir.name)) + goto error; + if(strcmp(dir.name, de.elem) != 0){ + if(permParent(fid, PermW) <= 0) + goto error; + vtMemFree(de.elem); + de.elem = vtStrDup(dir.name); + op = 1; + } + tsync = 0; + } + + /* + * Check for permission to change owner - must be god. + */ + if(dir.uid != nil && *dir.uid != '\0'){ + if((uid = uidByUname(dir.uid)) == nil){ + vtSetError("wstat -- unknown uid"); + goto error; + } + if(strcmp(uid, de.uid) != 0){ + if(!wstatallow){ + vtSetError("wstat -- not owner"); + goto error; + } + if(strcmp(uid, uidnoworld) == 0){ + vtSetError(EPermission); + goto error; + } + vtMemFree(de.uid); + de.uid = uid; + uid = nil; + op = 1; + } + tsync = 0; + } + + if(op) + retval = fileSetDir(fid->file, &de, fid->uid); + else + retval = 1; + + if(tsync){ + /* + * All values were defaulted, + * make the state of the file exactly what it + * claims to be before returning... + */ + USED(tsync); + } + +error: + deCleanup(&de); + vtMemFree(strs); + if(gid != nil) + vtMemFree(gid); + if(uid != nil) + vtMemFree(uid); +error0: + fidPut(fid); + return retval; +}; + +static int +rTstat(Msg* m) +{ + Dir dir; + Fid *fid; + DirEntry de; + + if((fid = fidGet(m->con, m->t.fid, 0)) == nil) + return 0; + if(fid->qid.type & QTAUTH){ + memset(&dir, 0, sizeof(Dir)); + dir.qid = fid->qid; + dir.mode = DMAUTH; + dir.atime = time(0L); + dir.mtime = dir.atime; + dir.length = 0; + dir.name = "#¿"; + dir.uid = fid->uname; + dir.gid = fid->uname; + dir.muid = fid->uname; + + if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){ + vtSetError("stat QTAUTH botch"); + fidPut(fid); + return 0; + } + m->r.stat = m->data; + + fidPut(fid); + return 1; + } + if(!fileGetDir(fid->file, &de)){ + fidPut(fid); + return 0; + } + fidPut(fid); + + /* + * TODO: optimise this copy (in convS2M) away somehow. + * This pettifoggery with m->data will do for the moment. + */ + m->r.nstat = dirDe2M(&de, m->data, m->con->msize); + m->r.stat = m->data; + deCleanup(&de); + + return 1; +} + +static int +_rTclunk(Fid* fid, int remove) +{ + int rok; + + if(fid->excl) + exclFree(fid); + + rok = 1; + if(remove && !(fid->qid.type & QTAUTH)){ + if((rok = permParent(fid, PermW)) > 0) + rok = fileRemove(fid->file, fid->uid); + } + fidClunk(fid); + + return rok; +} + +static int +rTremove(Msg* m) +{ + Fid *fid; + + if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil) + return 0; + return _rTclunk(fid, 1); +} + +static int +rTclunk(Msg* m) +{ + Fid *fid; + + if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil) + return 0; + _rTclunk(fid, (fid->open & FidORclose)); + + return 1; +} + +static int +rTwrite(Msg* m) +{ + Fid *fid; + int count, n; + + if((fid = fidGet(m->con, m->t.fid, 0)) == nil) + return 0; + if(!(fid->open & FidOWrite)){ + vtSetError("fid not open for write"); + goto error; + } + + count = m->t.count; + if(count < 0 || count > m->con->msize-IOHDRSZ){ + vtSetError("write count too big"); + goto error; + } + if(m->t.offset < 0){ + vtSetError("write offset negative"); + goto error; + } + if(fid->excl != nil && !exclUpdate(fid)) + goto error; + + if(fid->qid.type & QTDIR){ + vtSetError("is a directory"); + goto error; + } + else if(fid->qid.type & QTAUTH) + n = authWrite(fid, m->t.data, count); + else + n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid); + if(n < 0) + goto error; + + + m->r.count = n; + + fidPut(fid); + return 1; + +error: + fidPut(fid); + return 0; +} + +static int +rTread(Msg* m) +{ + Fid *fid; + uchar *data; + int count, n; + + if((fid = fidGet(m->con, m->t.fid, 0)) == nil) + return 0; + if(!(fid->open & FidORead)){ + vtSetError("fid not open for read"); + goto error; + } + + count = m->t.count; + if(count < 0 || count > m->con->msize-IOHDRSZ){ + vtSetError("read count too big"); + goto error; + } + if(m->t.offset < 0){ + vtSetError("read offset negative"); + goto error; + } + if(fid->excl != nil && !exclUpdate(fid)) + goto error; + + /* + * TODO: optimise this copy (in convS2M) away somehow. + * This pettifoggery with m->data will do for the moment. + */ + data = m->data+IOHDRSZ; + if(fid->qid.type & QTDIR) + n = dirRead(fid, data, count, m->t.offset); + else if(fid->qid.type & QTAUTH) + n = authRead(fid, data, count); + else + n = fileRead(fid->file, data, count, m->t.offset); + if(n < 0) + goto error; + + m->r.count = n; + m->r.data = (char*)data; + + fidPut(fid); + return 1; + +error: + fidPut(fid); + return 0; +} + +static int +rTcreate(Msg* m) +{ + Fid *fid; + File *file; + ulong mode; + int omode, open, perm; + + if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil) + return 0; + if(fid->open){ + vtSetError("fid open for I/O"); + goto error; + } + if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){ + vtSetError("read-only filesystem"); + goto error; + } + if(!fileIsDir(fid->file)){ + vtSetError("not a directory"); + goto error; + } + if(permFid(fid, PermW) <= 0) + goto error; + if(!validFileName(m->t.name)) + goto error; + if(strcmp(fid->uid, uidnoworld) == 0){ + vtSetError(EPermission); + goto error; + } + + omode = m->t.mode & OMODE; + open = 0; + + if(omode == OREAD || omode == ORDWR || omode == OEXEC) + open |= FidORead; + if(omode == OWRITE || omode == ORDWR) + open |= FidOWrite; + if((open & (FidOWrite|FidORead)) == 0){ + vtSetError("unknown mode"); + goto error; + } + if(m->t.perm & DMDIR){ + if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){ + vtSetError("illegal mode"); + goto error; + } + if(m->t.perm & DMAPPEND){ + vtSetError("illegal perm"); + goto error; + } + } + + mode = fileGetMode(fid->file); + perm = m->t.perm; + if(m->t.perm & DMDIR) + perm &= ~0777|(mode & 0777); + else + perm &= ~0666|(mode & 0666); + mode = perm & 0777; + if(m->t.perm & DMDIR) + mode |= ModeDir; + if(m->t.perm & DMAPPEND) + mode |= ModeAppend; + if(m->t.perm & DMEXCL) + mode |= ModeExclusive; + if(m->t.perm & DMTMP) + mode |= ModeTemporary; + + if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){ + fidPut(fid); + return 0; + } + fileDecRef(fid->file); + + fid->qid.vers = fileGetMcount(file); + fid->qid.path = fileGetId(file); + fid->file = file; + mode = fileGetMode(fid->file); + if(mode & ModeDir) + fid->qid.type = QTDIR; + else + fid->qid.type = QTFILE; + if(mode & ModeAppend) + fid->qid.type |= QTAPPEND; + if(mode & ModeExclusive){ + fid->qid.type |= QTEXCL; + assert(exclAlloc(fid) != 0); + } + if(m->t.mode & ORCLOSE) + open |= FidORclose; + fid->open = open; + + m->r.qid = fid->qid; + m->r.iounit = m->con->msize-IOHDRSZ; + + fidPut(fid); + return 1; + +error: + fidPut(fid); + return 0; +} + +static int +rTopen(Msg* m) +{ + Fid *fid; + int isdir, mode, omode, open, rofs; + + if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil) + return 0; + if(fid->open){ + vtSetError("fid open for I/O"); + goto error; + } + + isdir = fileIsDir(fid->file); + open = 0; + rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname); + + if(m->t.mode & ORCLOSE){ + if(isdir){ + vtSetError("is a directory"); + goto error; + } + if(rofs){ + vtSetError("read-only filesystem"); + goto error; + } + if(permParent(fid, PermW) <= 0) + goto error; + + open |= FidORclose; + } + + omode = m->t.mode & OMODE; + if(omode == OREAD || omode == ORDWR){ + if(permFid(fid, PermR) <= 0) + goto error; + open |= FidORead; + } + if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){ + if(isdir){ + vtSetError("is a directory"); + goto error; + } + if(rofs){ + vtSetError("read-only filesystem"); + goto error; + } + if(permFid(fid, PermW) <= 0) + goto error; + open |= FidOWrite; + } + if(omode == OEXEC){ + if(isdir){ + vtSetError("is a directory"); + goto error; + } + if(permFid(fid, PermX) <= 0) + goto error; + open |= FidORead; + } + if((open & (FidOWrite|FidORead)) == 0){ + vtSetError("unknown mode"); + goto error; + } + + mode = fileGetMode(fid->file); + if((mode & ModeExclusive) && exclAlloc(fid) == 0) + goto error; + + /* + * Everything checks out, try to commit any changes. + */ + if((m->t.mode & OTRUNC) && !(mode & ModeAppend)) + if(!fileTruncate(fid->file, fid->uid)) + goto error; + + if(isdir && fid->db != nil){ + dirBufFree(fid->db); + fid->db = nil; + } + + fid->qid.vers = fileGetMcount(fid->file); + m->r.qid = fid->qid; + m->r.iounit = m->con->msize-IOHDRSZ; + + fid->open = open; + + fidPut(fid); + return 1; + +error: + if(fid->excl != nil) + exclFree(fid); + fidPut(fid); + return 0; +} + +static int +rTwalk(Msg* m) +{ + Qid qid; + Fcall *r, *t; + int nwname, wlock; + File *file, *nfile; + Fid *fid, *ofid, *nfid; + + t = &m->t; + if(t->fid == t->newfid) + wlock = FidFWlock; + else + wlock = 0; + + /* + * The file identified by t->fid must be valid in the + * current session and must not have been opened for I/O + * by an open or create message. + */ + if((ofid = fidGet(m->con, t->fid, wlock)) == nil) + return 0; + if(ofid->open){ + vtSetError("file open for I/O"); + fidPut(ofid); + return 0; + } + + /* + * If newfid is not the same as fid, allocate a new file; + * a side effect is checking newfid is not already in use (error); + * if there are no names to walk this will be equivalent to a + * simple 'clone' operation. + * It's a no-op if newfid is the same as fid and t->nwname is 0. + */ + nfid = nil; + if(t->fid != t->newfid){ + nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate); + if(nfid == nil){ + vtSetError("%s: walk: newfid 0x%ud in use", + argv0, t->newfid); + fidPut(ofid); + return 0; + } + nfid->open = ofid->open & ~FidORclose; + nfid->file = fileIncRef(ofid->file); + nfid->qid = ofid->qid; + nfid->uid = vtStrDup(ofid->uid); + nfid->uname = vtStrDup(ofid->uname); + nfid->fsys = fsysIncRef(ofid->fsys); + fid = nfid; + } + else + fid = ofid; + + r = &m->r; + r->nwqid = 0; + + if(t->nwname == 0){ + if(nfid != nil) + fidPut(nfid); + fidPut(ofid); + + return 1; + } + + file = fid->file; + fileIncRef(file); + qid = fid->qid; + + for(nwname = 0; nwname < t->nwname; nwname++){ + /* + * Walked elements must represent a directory and + * the implied user must have permission to search + * the directory. Walking .. is always allowed, so that + * you can't walk into a directory and then not be able + * to walk out of it. + */ + if(!(qid.type & QTDIR)){ + vtSetError("not a directory"); + break; + } + switch(permFile(file, fid, PermX)){ + case 1: + break; + case 0: + if(strcmp(t->wname[nwname], "..") == 0) + break; + case -1: + goto Out; + } + if((nfile = fileWalk(file, t->wname[nwname])) == nil) + break; + fileDecRef(file); + file = nfile; + qid.type = QTFILE; + if(fileIsDir(file)) + qid.type = QTDIR; + if(fileIsAppend(file)) + qid.type |= QTAPPEND; + if(fileIsTemporary(file)) + qid.type |= QTTMP; + if(fileIsExclusive(file)) + qid.type |= QTEXCL; + qid.vers = fileGetMcount(file); + qid.path = fileGetId(file); + r->wqid[r->nwqid++] = qid; + } + + if(nwname == t->nwname){ + /* + * Walked all elements. Update the target fid + * from the temporary qid used during the walk, + * and tidy up. + */ + fid->qid = r->wqid[r->nwqid-1]; + fileDecRef(fid->file); + fid->file = file; + + if(nfid != nil) + fidPut(nfid); + + fidPut(ofid); + return 1; + } + +Out: + /* + * Didn't walk all elements, 'clunk' nfid if it exists + * and leave fid untouched. + * It's not an error if some of the elements were walked OK. + */ + fileDecRef(file); + if(nfid != nil) + fidClunk(nfid); + + fidPut(ofid); + if(nwname == 0) + return 0; + return 1; +} + +static int +rTflush(Msg* m) +{ + if(m->t.oldtag != NOTAG) + msgFlush(m); + return 1; +} + +static void +parseAname(char *aname, char **fsname, char **path) +{ + char *s; + + if(aname && aname[0]) + s = vtStrDup(aname); + else + s = vtStrDup("main/active"); + *fsname = s; + if((*path = strchr(s, '/')) != nil) + *(*path)++ = '\0'; + else + *path = ""; +} + +/* + * Check remote IP address against /mnt/ipok. + * Sources.cs.bell-labs.com uses this to disallow + * network connections from Sudan, Libya, etc., + * following U.S. cryptography export regulations. + */ +static int +conIPCheck(Con* con) +{ + char ok[256], *p; + int fd; + + if(con->flags&ConIPCheck){ + if(con->remote[0] == 0){ + vtSetError("cannot verify unknown remote address"); + return 0; + } + if(access("/mnt/ipok/ok", AEXIST) < 0){ + /* mount closes the fd on success */ + if((fd = open("/srv/ipok", ORDWR)) >= 0 + && mount(fd, -1, "/mnt/ipok", MREPL, "") < 0) + close(fd); + if(access("/mnt/ipok/ok", AEXIST) < 0){ + vtSetError("cannot verify remote address"); + return 0; + } + } + snprint(ok, sizeof ok, "/mnt/ipok/ok/%s", con->remote); + if((p = strchr(ok, '!')) != nil) + *p = 0; + if(access(ok, AEXIST) < 0){ + vtSetError("restricted remote address"); + return 0; + } + } + return 1; +} + +static int +rTattach(Msg* m) +{ + Fid *fid; + Fsys *fsys; + char *fsname, *path; + + if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil) + return 0; + + parseAname(m->t.aname, &fsname, &path); + if((fsys = fsysGet(fsname)) == nil){ + fidClunk(fid); + vtMemFree(fsname); + return 0; + } + fid->fsys = fsys; + + if(m->t.uname[0] != '\0') + fid->uname = vtStrDup(m->t.uname); + else + fid->uname = vtStrDup(unamenone); + + if((fid->con->flags&ConIPCheck) && !conIPCheck(fid->con)){ + consPrint("reject %s from %s: %R\n", fid->uname, fid->con->remote); + fidClunk(fid); + vtMemFree(fsname); + return 0; + } + if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){ + if((fid->uid = uidByUname(fid->uname)) == nil) + fid->uid = vtStrDup(unamenone); + } + else if(!authCheck(&m->t, fid, fsys)){ + fidClunk(fid); + vtMemFree(fsname); + return 0; + } + + fsysFsRlock(fsys); + if((fid->file = fsysGetRoot(fsys, path)) == nil){ + fsysFsRUnlock(fsys); + fidClunk(fid); + vtMemFree(fsname); + return 0; + } + fsysFsRUnlock(fsys); + vtMemFree(fsname); + + fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR}; + m->r.qid = fid->qid; + + fidPut(fid); + return 1; +} + +static int +rTauth(Msg* m) +{ + int afd; + Con *con; + Fid *afid; + Fsys *fsys; + char *fsname, *path; + + parseAname(m->t.aname, &fsname, &path); + if((fsys = fsysGet(fsname)) == nil){ + vtMemFree(fsname); + return 0; + } + vtMemFree(fsname); + + if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){ + m->con->aok = 1; + vtSetError("authentication disabled"); + fsysPut(fsys); + return 0; + } + if(strcmp(m->t.uname, unamenone) == 0){ + vtSetError("user 'none' requires no authentication"); + fsysPut(fsys); + return 0; + } + + con = m->con; + if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){ + fsysPut(fsys); + return 0; + } + afid->fsys = fsys; + + if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){ + vtSetError("can't open \"/mnt/factotum/rpc\""); + fidClunk(afid); + return 0; + } + if((afid->rpc = auth_allocrpc(afd)) == nil){ + close(afd); + vtSetError("can't auth_allocrpc"); + fidClunk(afid); + return 0; + } + if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){ + vtSetError("can't auth_rpc"); + fidClunk(afid); + return 0; + } + + afid->open = FidOWrite|FidORead; + afid->qid.type = QTAUTH; + afid->qid.path = m->t.afid; + afid->uname = vtStrDup(m->t.uname); + + m->r.qid = afid->qid; + + fidPut(afid); + return 1; +} + +static int +rTversion(Msg* m) +{ + int v; + Con *con; + Fcall *r, *t; + + t = &m->t; + r = &m->r; + con = m->con; + + vtLock(con->lock); + if(con->state != ConInit){ + vtUnlock(con->lock); + vtSetError("Tversion: down"); + return 0; + } + con->state = ConNew; + + /* + * Release the karma of past lives and suffering. + * Should this be done before or after checking the + * validity of the Tversion? + */ + fidClunkAll(con); + + if(t->tag != NOTAG){ + vtUnlock(con->lock); + vtSetError("Tversion: invalid tag"); + return 0; + } + + if(t->msize < 256){ + vtUnlock(con->lock); + vtSetError("Tversion: message size too small"); + return 0; + } + if(t->msize < con->msize) + r->msize = t->msize; + else + r->msize = con->msize; + + r->version = "unknown"; + if(t->version[0] == '9' && t->version[1] == 'P'){ + /* + * Currently, the only defined version + * is "9P2000"; ignore any later versions. + */ + v = strtol(&t->version[2], 0, 10); + if(v >= 2000){ + r->version = VERSION9P; + con->msize = r->msize; + con->state = ConUp; + } + else if(strcmp(t->version, "9PEoF") == 0){ + r->version = "9PEoF"; + con->msize = r->msize; + con->state = ConMoribund; + + /* + * Don't want to attempt to write this + * message as the connection may be already + * closed. + */ + m->state = MsgF; + } + } + vtUnlock(con->lock); + + return 1; +} + +int (*rFcall[Tmax])(Msg*) = { + [Tversion] = rTversion, + [Tauth] = rTauth, + [Tattach] = rTattach, + [Tflush] = rTflush, + [Twalk] = rTwalk, + [Topen] = rTopen, + [Tcreate] = rTcreate, + [Tread] = rTread, + [Twrite] = rTwrite, + [Tclunk] = rTclunk, + [Tremove] = rTremove, + [Tstat] = rTstat, + [Twstat] = rTwstat, +}; |