Remote whitespace at the ends of lines. Remove blank lines from the ends of files. Change modes on source files so that they are not executable. Signed-off-by: Dan Cross <cross@gajendra.net>
1194 lines
23 KiB
C
1194 lines
23 KiB
C
#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,
|
|
};
|