| #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)){ |
| vtfree(u); |
| deCleanup(&de); |
| return 1; |
| } |
| vtfree(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; |
| } |
| werrstr(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'){ |
| werrstr("no file name"); |
| return 0; |
| } |
| if(name[0] == '.'){ |
| if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){ |
| werrstr(". and .. illegal as file name"); |
| return 0; |
| } |
| } |
| |
| for(p = name; *p != '\0'; p++){ |
| if((*p & 0xFF) < 040){ |
| werrstr("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)){ |
| werrstr(EPermission); |
| goto error0; |
| } |
| if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){ |
| werrstr("read-only filesystem"); |
| goto error0; |
| } |
| |
| if(!fileGetDir(fid->file, &de)) |
| goto error0; |
| |
| strs = vtmalloc(m->t.nstat); |
| if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){ |
| werrstr("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){ |
| werrstr("wstat -- attempt to change qid.path"); |
| goto error; |
| } |
| tsync = 0; |
| } |
| if(dir.qid.vers != (u32int)~0){ |
| if(dir.qid.vers != de.mcount){ |
| werrstr("wstat -- attempt to change qid.vers"); |
| goto error; |
| } |
| tsync = 0; |
| } |
| if(dir.muid != nil && *dir.muid != '\0'){ |
| if((uid = uidByUname(dir.muid)) == nil){ |
| werrstr("wstat -- unknown muid"); |
| goto error; |
| } |
| if(strcmp(uid, de.mid) != 0){ |
| werrstr("wstat -- attempt to change muid"); |
| goto error; |
| } |
| vtfree(uid); |
| uid = nil; |
| tsync = 0; |
| } |
| |
| /* |
| * Check .qid.type and .mode agree if neither is defaulted. |
| */ |
| if(dir.qid.type != (uchar)~0 && dir.mode != (u32int)~0){ |
| if(dir.qid.type != ((dir.mode>>24) & 0xFF)){ |
| werrstr("wstat -- qid.type/mode mismatch"); |
| goto error; |
| } |
| } |
| |
| op = 0; |
| |
| oldmode = de.mode; |
| if(dir.qid.type != (uchar)~0 || dir.mode != (u32int)~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)){ |
| werrstr("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){ |
| werrstr("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 != (u32int)~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){ |
| werrstr("wstat -- attempt to change length of append-only file"); |
| goto error; |
| } |
| if(de.mode & ModeDir){ |
| werrstr("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){ |
| werrstr("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){ |
| werrstr("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)){ |
| werrstr("wstat -- not owner and not group leaders"); |
| goto error; |
| } |
| vtfree(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; |
| vtfree(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){ |
| werrstr("wstat -- unknown uid"); |
| goto error; |
| } |
| if(strcmp(uid, de.uid) != 0){ |
| if(!wstatallow){ |
| werrstr("wstat -- not owner"); |
| goto error; |
| } |
| if(strcmp(uid, uidnoworld) == 0){ |
| werrstr(EPermission); |
| goto error; |
| } |
| vtfree(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); |
| vtfree(strs); |
| if(gid != nil) |
| vtfree(gid); |
| if(uid != nil) |
| vtfree(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){ |
| werrstr("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)){ |
| werrstr("fid not open for write"); |
| goto error; |
| } |
| |
| count = m->t.count; |
| if(count < 0 || count > m->con->msize-IOHDRSZ){ |
| werrstr("write count too big"); |
| goto error; |
| } |
| if(m->t.offset < 0){ |
| werrstr("write offset negative"); |
| goto error; |
| } |
| if(fid->excl != nil && !exclUpdate(fid)) |
| goto error; |
| |
| if(fid->qid.type & QTDIR){ |
| werrstr("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)){ |
| werrstr("fid not open for read"); |
| goto error; |
| } |
| |
| count = m->t.count; |
| if(count < 0 || count > m->con->msize-IOHDRSZ){ |
| werrstr("read count too big"); |
| goto error; |
| } |
| if(m->t.offset < 0){ |
| werrstr("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){ |
| werrstr("fid open for I/O"); |
| goto error; |
| } |
| if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){ |
| werrstr("read-only filesystem"); |
| goto error; |
| } |
| if(!fileIsDir(fid->file)){ |
| werrstr("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){ |
| werrstr(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){ |
| werrstr("unknown mode"); |
| goto error; |
| } |
| if(m->t.perm & DMDIR){ |
| if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){ |
| werrstr("illegal mode"); |
| goto error; |
| } |
| if(m->t.perm & DMAPPEND){ |
| werrstr("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){ |
| werrstr("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){ |
| werrstr("is a directory"); |
| goto error; |
| } |
| if(rofs){ |
| werrstr("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){ |
| werrstr("is a directory"); |
| goto error; |
| } |
| if(rofs){ |
| werrstr("read-only filesystem"); |
| goto error; |
| } |
| if(permFid(fid, PermW) <= 0) |
| goto error; |
| open |= FidOWrite; |
| } |
| if(omode == OEXEC){ |
| if(isdir){ |
| werrstr("is a directory"); |
| goto error; |
| } |
| if(permFid(fid, PermX) <= 0) |
| goto error; |
| open |= FidORead; |
| } |
| if((open & (FidOWrite|FidORead)) == 0){ |
| werrstr("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){ |
| werrstr("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){ |
| werrstr("%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)){ |
| werrstr("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 = ""; |
| } |
| |
| #ifndef PLAN9PORT |
| /* |
| * 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){ |
| werrstr("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){ |
| werrstr("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){ |
| werrstr("restricted remote address"); |
| return 0; |
| } |
| } |
| return 1; |
| } |
| #endif |
| |
| 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); |
| vtfree(fsname); |
| return 0; |
| } |
| fid->fsys = fsys; |
| |
| if(m->t.uname[0] != '\0') |
| fid->uname = vtstrdup(m->t.uname); |
| else |
| fid->uname = vtstrdup(unamenone); |
| |
| #ifndef PLAN9PORT |
| if((fid->con->flags&ConIPCheck) && !conIPCheck(fid->con)){ |
| consPrint("reject %s from %s: %r\n", fid->uname, fid->con->remote); |
| fidClunk(fid); |
| vtfree(fsname); |
| return 0; |
| } |
| #endif |
| 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); |
| vtfree(fsname); |
| return 0; |
| } |
| |
| fsysFsRlock(fsys); |
| if((fid->file = fsysGetRoot(fsys, path)) == nil){ |
| fsysFsRUnlock(fsys); |
| fidClunk(fid); |
| vtfree(fsname); |
| return 0; |
| } |
| fsysFsRUnlock(fsys); |
| vtfree(fsname); |
| |
| fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR}; |
| m->r.qid = fid->qid; |
| |
| fidPut(fid); |
| return 1; |
| } |
| |
| static int |
| rTauth(Msg* m) |
| { |
| #ifndef PLAN9PORT |
| int afd; |
| #endif |
| Con *con; |
| Fid *afid; |
| Fsys *fsys; |
| char *fsname, *path; |
| |
| parseAname(m->t.aname, &fsname, &path); |
| if((fsys = fsysGet(fsname)) == nil){ |
| vtfree(fsname); |
| return 0; |
| } |
| vtfree(fsname); |
| |
| if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){ |
| m->con->aok = 1; |
| werrstr("authentication disabled"); |
| fsysPut(fsys); |
| return 0; |
| } |
| if(strcmp(m->t.uname, unamenone) == 0){ |
| werrstr("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; |
| |
| #ifndef PLAN9PORT |
| if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){ |
| werrstr("can't open \"/mnt/factotum/rpc\""); |
| fidClunk(afid); |
| return 0; |
| } |
| #endif |
| |
| #ifdef PLAN9PORT |
| if((afid->rpc = auth_allocrpc()) == nil){ |
| #else |
| if((afid->rpc = auth_allocrpc(afd)) == nil){ |
| close(afd); |
| #endif |
| werrstr("can't auth_allocrpc"); |
| fidClunk(afid); |
| return 0; |
| } |
| if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){ |
| werrstr("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; |
| |
| qlock(&con->lock); |
| if(con->state != ConInit){ |
| qunlock(&con->lock); |
| werrstr("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){ |
| qunlock(&con->lock); |
| werrstr("Tversion: invalid tag"); |
| return 0; |
| } |
| |
| if(t->msize < 256){ |
| qunlock(&con->lock); |
| werrstr("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; |
| } |
| } |
| qunlock(&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, |
| }; |