Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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,
};