plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

9p.c (23094B)


      1 #include "stdinc.h"
      2 
      3 #include "9.h"
      4 
      5 enum {
      6 	OMODE		= 0x7,		/* Topen/Tcreate mode */
      7 };
      8 
      9 enum {
     10 	PermX		= 1,
     11 	PermW		= 2,
     12 	PermR		= 4,
     13 };
     14 
     15 static char EPermission[] = "permission denied";
     16 
     17 static int
     18 permFile(File* file, Fid* fid, int perm)
     19 {
     20 	char *u;
     21 	DirEntry de;
     22 
     23 	if(!fileGetDir(file, &de))
     24 		return -1;
     25 
     26 	/*
     27 	 * User none only gets other permissions.
     28 	 */
     29 	if(strcmp(fid->uname, unamenone) != 0){
     30 		/*
     31 		 * There is only one uid<->uname mapping
     32 		 * and it's already cached in the Fid, but
     33 		 * it might have changed during the lifetime
     34 		 * if this Fid.
     35 		 */
     36 		if((u = unameByUid(de.uid)) != nil){
     37 			if(strcmp(fid->uname, u) == 0 && ((perm<<6) & de.mode)){
     38 				vtfree(u);
     39 				deCleanup(&de);
     40 				return 1;
     41 			}
     42 			vtfree(u);
     43 		}
     44 		if(groupMember(de.gid, fid->uname) && ((perm<<3) & de.mode)){
     45 			deCleanup(&de);
     46 			return 1;
     47 		}
     48 	}
     49 	if(perm & de.mode){
     50 		if(perm == PermX && (de.mode & ModeDir)){
     51 			deCleanup(&de);
     52 			return 1;
     53 		}
     54 		if(!groupMember(uidnoworld, fid->uname)){
     55 			deCleanup(&de);
     56 			return 1;
     57 		}
     58 	}
     59 	if(fsysNoPermCheck(fid->fsys) || (fid->con->flags&ConNoPermCheck)){
     60 		deCleanup(&de);
     61 		return 1;
     62 	}
     63 	werrstr(EPermission);
     64 
     65 	deCleanup(&de);
     66 	return 0;
     67 }
     68 
     69 static int
     70 permFid(Fid* fid, int p)
     71 {
     72 	return permFile(fid->file, fid, p);
     73 }
     74 
     75 static int
     76 permParent(Fid* fid, int p)
     77 {
     78 	int r;
     79 	File *parent;
     80 
     81 	parent = fileGetParent(fid->file);
     82 	r = permFile(parent, fid, p);
     83 	fileDecRef(parent);
     84 
     85 	return r;
     86 }
     87 
     88 int
     89 validFileName(char* name)
     90 {
     91 	char *p;
     92 
     93 	if(name == nil || name[0] == '\0'){
     94 		werrstr("no file name");
     95 		return 0;
     96 	}
     97 	if(name[0] == '.'){
     98 		if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){
     99 			werrstr(". and .. illegal as file name");
    100 			return 0;
    101 		}
    102 	}
    103 
    104 	for(p = name; *p != '\0'; p++){
    105 		if((*p & 0xFF) < 040){
    106 			werrstr("bad character in file name");
    107 			return 0;
    108 		}
    109 	}
    110 
    111 	return 1;
    112 }
    113 
    114 static int
    115 rTwstat(Msg* m)
    116 {
    117 	Dir dir;
    118 	Fid *fid;
    119 	ulong mode, oldmode;
    120 	DirEntry de;
    121 	char *gid, *strs, *uid;
    122 	int gl, op, retval, tsync, wstatallow;
    123 
    124 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
    125 		return 0;
    126 
    127 	gid = uid = nil;
    128 	retval = 0;
    129 
    130 	if(strcmp(fid->uname, unamenone) == 0 || (fid->qid.type & QTAUTH)){
    131 		werrstr(EPermission);
    132 		goto error0;
    133 	}
    134 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
    135 		werrstr("read-only filesystem");
    136 		goto error0;
    137 	}
    138 
    139 	if(!fileGetDir(fid->file, &de))
    140 		goto error0;
    141 
    142 	strs = vtmalloc(m->t.nstat);
    143 	if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){
    144 		werrstr("wstat -- protocol botch");
    145 		goto error;
    146 	}
    147 
    148 	/*
    149 	 * Run through each of the (sub-)fields in the provided Dir
    150 	 * checking for validity and whether it's a default:
    151 	 * .type, .dev and .atime are completely ignored and not checked;
    152 	 * .qid.path, .qid.vers and .muid are checked for validity but
    153 	 * any attempt to change them is an error.
    154 	 * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
    155 	 * possibly be changed.
    156 	 *
    157 	 * 'Op' flags there are changed fields, i.e. it's not a no-op.
    158 	 * 'Tsync' flags all fields are defaulted.
    159 	 */
    160 	tsync = 1;
    161 	if(dir.qid.path != ~0){
    162 		if(dir.qid.path != de.qid){
    163 			werrstr("wstat -- attempt to change qid.path");
    164 			goto error;
    165 		}
    166 		tsync = 0;
    167 	}
    168 	if(dir.qid.vers != (u32int)~0){
    169 		if(dir.qid.vers != de.mcount){
    170 			werrstr("wstat -- attempt to change qid.vers");
    171 			goto error;
    172 		}
    173 		tsync = 0;
    174 	}
    175 	if(dir.muid != nil && *dir.muid != '\0'){
    176 		if((uid = uidByUname(dir.muid)) == nil){
    177 			werrstr("wstat -- unknown muid");
    178 			goto error;
    179 		}
    180 		if(strcmp(uid, de.mid) != 0){
    181 			werrstr("wstat -- attempt to change muid");
    182 			goto error;
    183 		}
    184 		vtfree(uid);
    185 		uid = nil;
    186 		tsync = 0;
    187 	}
    188 
    189 	/*
    190 	 * Check .qid.type and .mode agree if neither is defaulted.
    191 	 */
    192 	if(dir.qid.type != (uchar)~0 && dir.mode != (u32int)~0){
    193 		if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
    194 			werrstr("wstat -- qid.type/mode mismatch");
    195 			goto error;
    196 		}
    197 	}
    198 
    199 	op = 0;
    200 
    201 	oldmode = de.mode;
    202 	if(dir.qid.type != (uchar)~0 || dir.mode != (u32int)~0){
    203 		/*
    204 		 * .qid.type or .mode isn't defaulted, check for unknown bits.
    205 		 */
    206 		if(dir.mode == ~0)
    207 			dir.mode = (dir.qid.type<<24)|(de.mode & 0777);
    208 		if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|DMTMP|0777)){
    209 			werrstr("wstat -- unknown bits in qid.type/mode");
    210 			goto error;
    211 		}
    212 
    213 		/*
    214 		 * Synthesise a mode to check against the current settings.
    215 		 */
    216 		mode = dir.mode & 0777;
    217 		if(dir.mode & DMEXCL)
    218 			mode |= ModeExclusive;
    219 		if(dir.mode & DMAPPEND)
    220 			mode |= ModeAppend;
    221 		if(dir.mode & DMDIR)
    222 			mode |= ModeDir;
    223 		if(dir.mode & DMTMP)
    224 			mode |= ModeTemporary;
    225 
    226 		if((de.mode^mode) & ModeDir){
    227 			werrstr("wstat -- attempt to change directory bit");
    228 			goto error;
    229 		}
    230 
    231 		if((de.mode & (ModeAppend|ModeExclusive|ModeTemporary|0777)) != mode){
    232 			de.mode &= ~(ModeAppend|ModeExclusive|ModeTemporary|0777);
    233 			de.mode |= mode;
    234 			op = 1;
    235 		}
    236 		tsync = 0;
    237 	}
    238 
    239 	if(dir.mtime != (u32int)~0){
    240 		if(dir.mtime != de.mtime){
    241 			de.mtime = dir.mtime;
    242 			op = 1;
    243 		}
    244 		tsync = 0;
    245 	}
    246 
    247 	if(dir.length != ~0){
    248 		if(dir.length != de.size){
    249 			/*
    250 			 * Cannot change length on append-only files.
    251 			 * If we're changing the append bit, it's okay.
    252 			 */
    253 			if(de.mode & oldmode & ModeAppend){
    254 				werrstr("wstat -- attempt to change length of append-only file");
    255 				goto error;
    256 			}
    257 			if(de.mode & ModeDir){
    258 				werrstr("wstat -- attempt to change length of directory");
    259 				goto error;
    260 			}
    261 			de.size = dir.length;
    262 			op = 1;
    263 		}
    264 		tsync = 0;
    265 	}
    266 
    267 	/*
    268 	 * Check for permission to change .mode, .mtime or .length,
    269 	 * must be owner or leader of either group, for which test gid
    270 	 * is needed; permission checks on gid will be done later.
    271 	 */
    272 	if(dir.gid != nil && *dir.gid != '\0'){
    273 		if((gid = uidByUname(dir.gid)) == nil){
    274 			werrstr("wstat -- unknown gid");
    275 			goto error;
    276 		}
    277 		tsync = 0;
    278 	}
    279 	else
    280 		gid = vtstrdup(de.gid);
    281 
    282 	wstatallow = (fsysWstatAllow(fid->fsys) || (m->con->flags&ConWstatAllow));
    283 
    284 	/*
    285 	 * 'Gl' counts whether neither, one or both groups are led.
    286 	 */
    287 	gl = groupLeader(gid, fid->uname) != 0;
    288 	gl += groupLeader(de.gid, fid->uname) != 0;
    289 
    290 	if(op && !wstatallow){
    291 		if(strcmp(fid->uid, de.uid) != 0 && !gl){
    292 			werrstr("wstat -- not owner or group leader");
    293 			goto error;
    294 		}
    295 	}
    296 
    297 	/*
    298 	 * Check for permission to change group, must be
    299 	 * either owner and in new group or leader of both groups.
    300 	 * If gid is nil here then
    301 	 */
    302 	if(strcmp(gid, de.gid) != 0){
    303 		if(!wstatallow
    304 		&& !(strcmp(fid->uid, de.uid) == 0 && groupMember(gid, fid->uname))
    305 		&& !(gl == 2)){
    306 			werrstr("wstat -- not owner and not group leaders");
    307 			goto error;
    308 		}
    309 		vtfree(de.gid);
    310 		de.gid = gid;
    311 		gid = nil;
    312 		op = 1;
    313 		tsync = 0;
    314 	}
    315 
    316 	/*
    317 	 * Rename.
    318 	 * Check .name is valid and different to the current.
    319 	 * If so, check write permission in parent.
    320 	 */
    321 	if(dir.name != nil && *dir.name != '\0'){
    322 		if(!validFileName(dir.name))
    323 			goto error;
    324 		if(strcmp(dir.name, de.elem) != 0){
    325 			if(permParent(fid, PermW) <= 0)
    326 				goto error;
    327 			vtfree(de.elem);
    328 			de.elem = vtstrdup(dir.name);
    329 			op = 1;
    330 		}
    331 		tsync = 0;
    332 	}
    333 
    334 	/*
    335 	 * Check for permission to change owner - must be god.
    336 	 */
    337 	if(dir.uid != nil && *dir.uid != '\0'){
    338 		if((uid = uidByUname(dir.uid)) == nil){
    339 			werrstr("wstat -- unknown uid");
    340 			goto error;
    341 		}
    342 		if(strcmp(uid, de.uid) != 0){
    343 			if(!wstatallow){
    344 				werrstr("wstat -- not owner");
    345 				goto error;
    346 			}
    347 			if(strcmp(uid, uidnoworld) == 0){
    348 				werrstr(EPermission);
    349 				goto error;
    350 			}
    351 			vtfree(de.uid);
    352 			de.uid = uid;
    353 			uid = nil;
    354 			op = 1;
    355 		}
    356 		tsync = 0;
    357 	}
    358 
    359 	if(op)
    360 		retval = fileSetDir(fid->file, &de, fid->uid);
    361 	else
    362 		retval = 1;
    363 
    364 	if(tsync){
    365 		/*
    366 		 * All values were defaulted,
    367 		 * make the state of the file exactly what it
    368 		 * claims to be before returning...
    369 		 */
    370 		USED(tsync);
    371 	}
    372 
    373 error:
    374 	deCleanup(&de);
    375 	vtfree(strs);
    376 	if(gid != nil)
    377 		vtfree(gid);
    378 	if(uid != nil)
    379 		vtfree(uid);
    380 error0:
    381 	fidPut(fid);
    382 	return retval;
    383 };
    384 
    385 static int
    386 rTstat(Msg* m)
    387 {
    388 	Dir dir;
    389 	Fid *fid;
    390 	DirEntry de;
    391 
    392 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
    393 		return 0;
    394 	if(fid->qid.type & QTAUTH){
    395 		memset(&dir, 0, sizeof(Dir));
    396 		dir.qid = fid->qid;
    397 		dir.mode = DMAUTH;
    398 		dir.atime = time(0L);
    399 		dir.mtime = dir.atime;
    400 		dir.length = 0;
    401 		dir.name = "#¿";
    402 		dir.uid = fid->uname;
    403 		dir.gid = fid->uname;
    404 		dir.muid = fid->uname;
    405 
    406 		if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){
    407 			werrstr("stat QTAUTH botch");
    408 			fidPut(fid);
    409 			return 0;
    410 		}
    411 		m->r.stat = m->data;
    412 
    413 		fidPut(fid);
    414 		return 1;
    415 	}
    416 	if(!fileGetDir(fid->file, &de)){
    417 		fidPut(fid);
    418 		return 0;
    419 	}
    420 	fidPut(fid);
    421 
    422 	/*
    423 	 * TODO: optimise this copy (in convS2M) away somehow.
    424 	 * This pettifoggery with m->data will do for the moment.
    425 	 */
    426 	m->r.nstat = dirDe2M(&de, m->data, m->con->msize);
    427 	m->r.stat = m->data;
    428 	deCleanup(&de);
    429 
    430 	return 1;
    431 }
    432 
    433 static int
    434 _rTclunk(Fid* fid, int remove)
    435 {
    436 	int rok;
    437 
    438 	if(fid->excl)
    439 		exclFree(fid);
    440 
    441 	rok = 1;
    442 	if(remove && !(fid->qid.type & QTAUTH)){
    443 		if((rok = permParent(fid, PermW)) > 0)
    444 			rok = fileRemove(fid->file, fid->uid);
    445 	}
    446 	fidClunk(fid);
    447 
    448 	return rok;
    449 }
    450 
    451 static int
    452 rTremove(Msg* m)
    453 {
    454 	Fid *fid;
    455 
    456 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
    457 		return 0;
    458 	return _rTclunk(fid, 1);
    459 }
    460 
    461 static int
    462 rTclunk(Msg* m)
    463 {
    464 	Fid *fid;
    465 
    466 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
    467 		return 0;
    468 	_rTclunk(fid, (fid->open & FidORclose));
    469 
    470 	return 1;
    471 }
    472 
    473 static int
    474 rTwrite(Msg* m)
    475 {
    476 	Fid *fid;
    477 	int count, n;
    478 
    479 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
    480 		return 0;
    481 	if(!(fid->open & FidOWrite)){
    482 		werrstr("fid not open for write");
    483 		goto error;
    484 	}
    485 
    486 	count = m->t.count;
    487 	if(count < 0 || count > m->con->msize-IOHDRSZ){
    488 		werrstr("write count too big");
    489 		goto error;
    490 	}
    491 	if(m->t.offset < 0){
    492 		werrstr("write offset negative");
    493 		goto error;
    494 	}
    495 	if(fid->excl != nil && !exclUpdate(fid))
    496 		goto error;
    497 
    498 	if(fid->qid.type & QTDIR){
    499 		werrstr("is a directory");
    500 		goto error;
    501 	}
    502 	else if(fid->qid.type & QTAUTH)
    503 		n = authWrite(fid, m->t.data, count);
    504 	else
    505 		n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid);
    506 	if(n < 0)
    507 		goto error;
    508 
    509 
    510 	m->r.count = n;
    511 
    512 	fidPut(fid);
    513 	return 1;
    514 
    515 error:
    516 	fidPut(fid);
    517 	return 0;
    518 }
    519 
    520 static int
    521 rTread(Msg* m)
    522 {
    523 	Fid *fid;
    524 	uchar *data;
    525 	int count, n;
    526 
    527 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
    528 		return 0;
    529 	if(!(fid->open & FidORead)){
    530 		werrstr("fid not open for read");
    531 		goto error;
    532 	}
    533 
    534 	count = m->t.count;
    535 	if(count < 0 || count > m->con->msize-IOHDRSZ){
    536 		werrstr("read count too big");
    537 		goto error;
    538 	}
    539 	if(m->t.offset < 0){
    540 		werrstr("read offset negative");
    541 		goto error;
    542 	}
    543 	if(fid->excl != nil && !exclUpdate(fid))
    544 		goto error;
    545 
    546 	/*
    547 	 * TODO: optimise this copy (in convS2M) away somehow.
    548 	 * This pettifoggery with m->data will do for the moment.
    549 	 */
    550 	data = m->data+IOHDRSZ;
    551 	if(fid->qid.type & QTDIR)
    552 		n = dirRead(fid, data, count, m->t.offset);
    553 	else if(fid->qid.type & QTAUTH)
    554 		n = authRead(fid, data, count);
    555 	else
    556 		n = fileRead(fid->file, data, count, m->t.offset);
    557 	if(n < 0)
    558 		goto error;
    559 
    560 	m->r.count = n;
    561 	m->r.data = (char*)data;
    562 
    563 	fidPut(fid);
    564 	return 1;
    565 
    566 error:
    567 	fidPut(fid);
    568 	return 0;
    569 }
    570 
    571 static int
    572 rTcreate(Msg* m)
    573 {
    574 	Fid *fid;
    575 	File *file;
    576 	ulong mode;
    577 	int omode, open, perm;
    578 
    579 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
    580 		return 0;
    581 	if(fid->open){
    582 		werrstr("fid open for I/O");
    583 		goto error;
    584 	}
    585 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
    586 		werrstr("read-only filesystem");
    587 		goto error;
    588 	}
    589 	if(!fileIsDir(fid->file)){
    590 		werrstr("not a directory");
    591 		goto error;
    592 	}
    593 	if(permFid(fid, PermW) <= 0)
    594 		goto error;
    595 	if(!validFileName(m->t.name))
    596 		goto error;
    597 	if(strcmp(fid->uid, uidnoworld) == 0){
    598 		werrstr(EPermission);
    599 		goto error;
    600 	}
    601 
    602 	omode = m->t.mode & OMODE;
    603 	open = 0;
    604 
    605 	if(omode == OREAD || omode == ORDWR || omode == OEXEC)
    606 		open |= FidORead;
    607 	if(omode == OWRITE || omode == ORDWR)
    608 		open |= FidOWrite;
    609 	if((open & (FidOWrite|FidORead)) == 0){
    610 		werrstr("unknown mode");
    611 		goto error;
    612 	}
    613 	if(m->t.perm & DMDIR){
    614 		if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){
    615 			werrstr("illegal mode");
    616 			goto error;
    617 		}
    618 		if(m->t.perm & DMAPPEND){
    619 			werrstr("illegal perm");
    620 			goto error;
    621 		}
    622 	}
    623 
    624 	mode = fileGetMode(fid->file);
    625 	perm = m->t.perm;
    626 	if(m->t.perm & DMDIR)
    627 		perm &= ~0777|(mode & 0777);
    628 	else
    629 		perm &= ~0666|(mode & 0666);
    630 	mode = perm & 0777;
    631 	if(m->t.perm & DMDIR)
    632 		mode |= ModeDir;
    633 	if(m->t.perm & DMAPPEND)
    634 		mode |= ModeAppend;
    635 	if(m->t.perm & DMEXCL)
    636 		mode |= ModeExclusive;
    637 	if(m->t.perm & DMTMP)
    638 		mode |= ModeTemporary;
    639 
    640 	if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){
    641 		fidPut(fid);
    642 		return 0;
    643 	}
    644 	fileDecRef(fid->file);
    645 
    646 	fid->qid.vers = fileGetMcount(file);
    647 	fid->qid.path = fileGetId(file);
    648 	fid->file = file;
    649 	mode = fileGetMode(fid->file);
    650 	if(mode & ModeDir)
    651 		fid->qid.type = QTDIR;
    652 	else
    653 		fid->qid.type = QTFILE;
    654 	if(mode & ModeAppend)
    655 		fid->qid.type |= QTAPPEND;
    656 	if(mode & ModeExclusive){
    657 		fid->qid.type |= QTEXCL;
    658 		assert(exclAlloc(fid) != 0);
    659 	}
    660 	if(m->t.mode & ORCLOSE)
    661 		open |= FidORclose;
    662 	fid->open = open;
    663 
    664 	m->r.qid = fid->qid;
    665 	m->r.iounit = m->con->msize-IOHDRSZ;
    666 
    667 	fidPut(fid);
    668 	return 1;
    669 
    670 error:
    671 	fidPut(fid);
    672 	return 0;
    673 }
    674 
    675 static int
    676 rTopen(Msg* m)
    677 {
    678 	Fid *fid;
    679 	int isdir, mode, omode, open, rofs;
    680 
    681 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
    682 		return 0;
    683 	if(fid->open){
    684 		werrstr("fid open for I/O");
    685 		goto error;
    686 	}
    687 
    688 	isdir = fileIsDir(fid->file);
    689 	open = 0;
    690 	rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname);
    691 
    692 	if(m->t.mode & ORCLOSE){
    693 		if(isdir){
    694 			werrstr("is a directory");
    695 			goto error;
    696 		}
    697 		if(rofs){
    698 			werrstr("read-only filesystem");
    699 			goto error;
    700 		}
    701 		if(permParent(fid, PermW) <= 0)
    702 			goto error;
    703 
    704 		open |= FidORclose;
    705 	}
    706 
    707 	omode = m->t.mode & OMODE;
    708 	if(omode == OREAD || omode == ORDWR){
    709 		if(permFid(fid, PermR) <= 0)
    710 			goto error;
    711 		open |= FidORead;
    712 	}
    713 	if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){
    714 		if(isdir){
    715 			werrstr("is a directory");
    716 			goto error;
    717 		}
    718 		if(rofs){
    719 			werrstr("read-only filesystem");
    720 			goto error;
    721 		}
    722 		if(permFid(fid, PermW) <= 0)
    723 			goto error;
    724 		open |= FidOWrite;
    725 	}
    726 	if(omode == OEXEC){
    727 		if(isdir){
    728 			werrstr("is a directory");
    729 			goto error;
    730 		}
    731 		if(permFid(fid, PermX) <= 0)
    732 			goto error;
    733 		open |= FidORead;
    734 	}
    735 	if((open & (FidOWrite|FidORead)) == 0){
    736 		werrstr("unknown mode");
    737 		goto error;
    738 	}
    739 
    740 	mode = fileGetMode(fid->file);
    741 	if((mode & ModeExclusive) && exclAlloc(fid) == 0)
    742 		goto error;
    743 
    744 	/*
    745 	 * Everything checks out, try to commit any changes.
    746 	 */
    747 	if((m->t.mode & OTRUNC) && !(mode & ModeAppend))
    748 		if(!fileTruncate(fid->file, fid->uid))
    749 			goto error;
    750 
    751 	if(isdir && fid->db != nil){
    752 		dirBufFree(fid->db);
    753 		fid->db = nil;
    754 	}
    755 
    756 	fid->qid.vers = fileGetMcount(fid->file);
    757 	m->r.qid = fid->qid;
    758 	m->r.iounit = m->con->msize-IOHDRSZ;
    759 
    760 	fid->open = open;
    761 
    762 	fidPut(fid);
    763 	return 1;
    764 
    765 error:
    766 	if(fid->excl != nil)
    767 		exclFree(fid);
    768 	fidPut(fid);
    769 	return 0;
    770 }
    771 
    772 static int
    773 rTwalk(Msg* m)
    774 {
    775 	Qid qid;
    776 	Fcall *r, *t;
    777 	int nwname, wlock;
    778 	File *file, *nfile;
    779 	Fid *fid, *ofid, *nfid;
    780 
    781 	t = &m->t;
    782 	if(t->fid == t->newfid)
    783 		wlock = FidFWlock;
    784 	else
    785 		wlock = 0;
    786 
    787 	/*
    788 	 * The file identified by t->fid must be valid in the
    789 	 * current session and must not have been opened for I/O
    790 	 * by an open or create message.
    791 	 */
    792 	if((ofid = fidGet(m->con, t->fid, wlock)) == nil)
    793 		return 0;
    794 	if(ofid->open){
    795 		werrstr("file open for I/O");
    796 		fidPut(ofid);
    797 		return 0;
    798 	}
    799 
    800 	/*
    801 	 * If newfid is not the same as fid, allocate a new file;
    802 	 * a side effect is checking newfid is not already in use (error);
    803 	 * if there are no names to walk this will be equivalent to a
    804 	 * simple 'clone' operation.
    805 	 * It's a no-op if newfid is the same as fid and t->nwname is 0.
    806 	 */
    807 	nfid = nil;
    808 	if(t->fid != t->newfid){
    809 		nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate);
    810 		if(nfid == nil){
    811 			werrstr("%s: walk: newfid 0x%ud in use",
    812 				argv0, t->newfid);
    813 			fidPut(ofid);
    814 			return 0;
    815 		}
    816 		nfid->open = ofid->open & ~FidORclose;
    817 		nfid->file = fileIncRef(ofid->file);
    818 		nfid->qid = ofid->qid;
    819 		nfid->uid = vtstrdup(ofid->uid);
    820 		nfid->uname = vtstrdup(ofid->uname);
    821 		nfid->fsys = fsysIncRef(ofid->fsys);
    822 		fid = nfid;
    823 	}
    824 	else
    825 		fid = ofid;
    826 
    827 	r = &m->r;
    828 	r->nwqid = 0;
    829 
    830 	if(t->nwname == 0){
    831 		if(nfid != nil)
    832 			fidPut(nfid);
    833 		fidPut(ofid);
    834 
    835 		return 1;
    836 	}
    837 
    838 	file = fid->file;
    839 	fileIncRef(file);
    840 	qid = fid->qid;
    841 
    842 	for(nwname = 0; nwname < t->nwname; nwname++){
    843 		/*
    844 		 * Walked elements must represent a directory and
    845 		 * the implied user must have permission to search
    846 		 * the directory.  Walking .. is always allowed, so that
    847 		 * you can't walk into a directory and then not be able
    848 		 * to walk out of it.
    849 		 */
    850 		if(!(qid.type & QTDIR)){
    851 			werrstr("not a directory");
    852 			break;
    853 		}
    854 		switch(permFile(file, fid, PermX)){
    855 		case 1:
    856 			break;
    857 		case 0:
    858 			if(strcmp(t->wname[nwname], "..") == 0)
    859 				break;
    860 		case -1:
    861 			goto Out;
    862 		}
    863 		if((nfile = fileWalk(file, t->wname[nwname])) == nil)
    864 			break;
    865 		fileDecRef(file);
    866 		file = nfile;
    867 		qid.type = QTFILE;
    868 		if(fileIsDir(file))
    869 			qid.type = QTDIR;
    870 		if(fileIsAppend(file))
    871 			qid.type |= QTAPPEND;
    872 		if(fileIsTemporary(file))
    873 			qid.type |= QTTMP;
    874 		if(fileIsExclusive(file))
    875 			qid.type |= QTEXCL;
    876 		qid.vers = fileGetMcount(file);
    877 		qid.path = fileGetId(file);
    878 		r->wqid[r->nwqid++] = qid;
    879 	}
    880 
    881 	if(nwname == t->nwname){
    882 		/*
    883 		 * Walked all elements. Update the target fid
    884 		 * from the temporary qid used during the walk,
    885 		 * and tidy up.
    886 		 */
    887 		fid->qid = r->wqid[r->nwqid-1];
    888 		fileDecRef(fid->file);
    889 		fid->file = file;
    890 
    891 		if(nfid != nil)
    892 			fidPut(nfid);
    893 
    894 		fidPut(ofid);
    895 		return 1;
    896 	}
    897 
    898 Out:
    899 	/*
    900 	 * Didn't walk all elements, 'clunk' nfid if it exists
    901 	 * and leave fid untouched.
    902 	 * It's not an error if some of the elements were walked OK.
    903 	 */
    904 	fileDecRef(file);
    905 	if(nfid != nil)
    906 		fidClunk(nfid);
    907 
    908 	fidPut(ofid);
    909 	if(nwname == 0)
    910 		return 0;
    911 	return 1;
    912 }
    913 
    914 static int
    915 rTflush(Msg* m)
    916 {
    917 	if(m->t.oldtag != NOTAG)
    918 		msgFlush(m);
    919 	return 1;
    920 }
    921 
    922 static void
    923 parseAname(char *aname, char **fsname, char **path)
    924 {
    925 	char *s;
    926 
    927 	if(aname && aname[0])
    928 		s = vtstrdup(aname);
    929 	else
    930 		s = vtstrdup("main/active");
    931 	*fsname = s;
    932 	if((*path = strchr(s, '/')) != nil)
    933 		*(*path)++ = '\0';
    934 	else
    935 		*path = "";
    936 }
    937 
    938 #ifndef PLAN9PORT
    939 /*
    940  * Check remote IP address against /mnt/ipok.
    941  * Sources.cs.bell-labs.com uses this to disallow
    942  * network connections from Sudan, Libya, etc.,
    943  * following U.S. cryptography export regulations.
    944  */
    945 static int
    946 conIPCheck(Con* con)
    947 {
    948 	char ok[256], *p;
    949 	int fd;
    950 
    951 	if(con->flags&ConIPCheck){
    952 		if(con->remote[0] == 0){
    953 			werrstr("cannot verify unknown remote address");
    954 			return 0;
    955 		}
    956 		if(access("/mnt/ipok/ok", AEXIST) < 0){
    957 			/* mount closes the fd on success */
    958 			if((fd = open("/srv/ipok", ORDWR)) >= 0
    959 			&& mount(fd, -1, "/mnt/ipok", MREPL, "") < 0)
    960 				close(fd);
    961 			if(access("/mnt/ipok/ok", AEXIST) < 0){
    962 				werrstr("cannot verify remote address");
    963 				return 0;
    964 			}
    965 		}
    966 		snprint(ok, sizeof ok, "/mnt/ipok/ok/%s", con->remote);
    967 		if((p = strchr(ok, '!')) != nil)
    968 			*p = 0;
    969 		if(access(ok, AEXIST) < 0){
    970 			werrstr("restricted remote address");
    971 			return 0;
    972 		}
    973 	}
    974 	return 1;
    975 }
    976 #endif
    977 
    978 static int
    979 rTattach(Msg* m)
    980 {
    981 	Fid *fid;
    982 	Fsys *fsys;
    983 	char *fsname, *path;
    984 
    985 	if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil)
    986 		return 0;
    987 
    988 	parseAname(m->t.aname, &fsname, &path);
    989 	if((fsys = fsysGet(fsname)) == nil){
    990 		fidClunk(fid);
    991 		vtfree(fsname);
    992 		return 0;
    993 	}
    994 	fid->fsys = fsys;
    995 
    996 	if(m->t.uname[0] != '\0')
    997 		fid->uname = vtstrdup(m->t.uname);
    998 	else
    999 		fid->uname = vtstrdup(unamenone);
   1000 
   1001 #ifndef PLAN9PORT
   1002 	if((fid->con->flags&ConIPCheck) && !conIPCheck(fid->con)){
   1003 		consPrint("reject %s from %s: %r\n", fid->uname, fid->con->remote);
   1004 		fidClunk(fid);
   1005 		vtfree(fsname);
   1006 		return 0;
   1007 	}
   1008 #endif
   1009 	if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
   1010 		if((fid->uid = uidByUname(fid->uname)) == nil)
   1011 			fid->uid = vtstrdup(unamenone);
   1012 	}
   1013 	else if(!authCheck(&m->t, fid, fsys)){
   1014 		fidClunk(fid);
   1015 		vtfree(fsname);
   1016 		return 0;
   1017 	}
   1018 
   1019 	fsysFsRlock(fsys);
   1020 	if((fid->file = fsysGetRoot(fsys, path)) == nil){
   1021 		fsysFsRUnlock(fsys);
   1022 		fidClunk(fid);
   1023 		vtfree(fsname);
   1024 		return 0;
   1025 	}
   1026 	fsysFsRUnlock(fsys);
   1027 	vtfree(fsname);
   1028 
   1029 	fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR};
   1030 	m->r.qid = fid->qid;
   1031 
   1032 	fidPut(fid);
   1033 	return 1;
   1034 }
   1035 
   1036 static int
   1037 rTauth(Msg* m)
   1038 {
   1039 #ifndef PLAN9PORT
   1040 	int afd;
   1041 #endif
   1042 	Con *con;
   1043 	Fid *afid;
   1044 	Fsys *fsys;
   1045 	char *fsname, *path;
   1046 
   1047 	parseAname(m->t.aname, &fsname, &path);
   1048 	if((fsys = fsysGet(fsname)) == nil){
   1049 		vtfree(fsname);
   1050 		return 0;
   1051 	}
   1052 	vtfree(fsname);
   1053 
   1054 	if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
   1055 		m->con->aok = 1;
   1056 		werrstr("authentication disabled");
   1057 		fsysPut(fsys);
   1058 		return 0;
   1059 	}
   1060 	if(strcmp(m->t.uname, unamenone) == 0){
   1061 		werrstr("user 'none' requires no authentication");
   1062 		fsysPut(fsys);
   1063 		return 0;
   1064 	}
   1065 
   1066 	con = m->con;
   1067 	if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){
   1068 		fsysPut(fsys);
   1069 		return 0;
   1070 	}
   1071 	afid->fsys = fsys;
   1072 
   1073 #ifndef PLAN9PORT
   1074 	if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
   1075 		werrstr("can't open \"/mnt/factotum/rpc\"");
   1076 		fidClunk(afid);
   1077 		return 0;
   1078 	}
   1079 #endif
   1080 
   1081 #ifdef PLAN9PORT
   1082 	if((afid->rpc = auth_allocrpc()) == nil){
   1083 #else
   1084 	if((afid->rpc = auth_allocrpc(afd)) == nil){
   1085 		close(afd);
   1086 #endif
   1087 		werrstr("can't auth_allocrpc");
   1088 		fidClunk(afid);
   1089 		return 0;
   1090 	}
   1091 	if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){
   1092 		werrstr("can't auth_rpc");
   1093 		fidClunk(afid);
   1094 		return 0;
   1095 	}
   1096 
   1097 	afid->open = FidOWrite|FidORead;
   1098 	afid->qid.type = QTAUTH;
   1099 	afid->qid.path = m->t.afid;
   1100 	afid->uname = vtstrdup(m->t.uname);
   1101 
   1102 	m->r.qid = afid->qid;
   1103 
   1104 	fidPut(afid);
   1105 	return 1;
   1106 }
   1107 
   1108 static int
   1109 rTversion(Msg* m)
   1110 {
   1111 	int v;
   1112 	Con *con;
   1113 	Fcall *r, *t;
   1114 
   1115 	t = &m->t;
   1116 	r = &m->r;
   1117 	con = m->con;
   1118 
   1119 	qlock(&con->lock);
   1120 	if(con->state != ConInit){
   1121 		qunlock(&con->lock);
   1122 		werrstr("Tversion: down");
   1123 		return 0;
   1124 	}
   1125 	con->state = ConNew;
   1126 
   1127 	/*
   1128 	 * Release the karma of past lives and suffering.
   1129 	 * Should this be done before or after checking the
   1130 	 * validity of the Tversion?
   1131 	 */
   1132 	fidClunkAll(con);
   1133 
   1134 	if(t->tag != NOTAG){
   1135 		qunlock(&con->lock);
   1136 		werrstr("Tversion: invalid tag");
   1137 		return 0;
   1138 	}
   1139 
   1140 	if(t->msize < 256){
   1141 		qunlock(&con->lock);
   1142 		werrstr("Tversion: message size too small");
   1143 		return 0;
   1144 	}
   1145 	if(t->msize < con->msize)
   1146 		r->msize = t->msize;
   1147 	else
   1148 		r->msize = con->msize;
   1149 
   1150 	r->version = "unknown";
   1151 	if(t->version[0] == '9' && t->version[1] == 'P'){
   1152 		/*
   1153 		 * Currently, the only defined version
   1154 		 * is "9P2000"; ignore any later versions.
   1155 		 */
   1156 		v = strtol(&t->version[2], 0, 10);
   1157 		if(v >= 2000){
   1158 			r->version = VERSION9P;
   1159 			con->msize = r->msize;
   1160 			con->state = ConUp;
   1161 		}
   1162 		else if(strcmp(t->version, "9PEoF") == 0){
   1163 			r->version = "9PEoF";
   1164 			con->msize = r->msize;
   1165 			con->state = ConMoribund;
   1166 
   1167 			/*
   1168 			 * Don't want to attempt to write this
   1169 			 * message as the connection may be already
   1170 			 * closed.
   1171 			 */
   1172 			m->state = MsgF;
   1173 		}
   1174 	}
   1175 	qunlock(&con->lock);
   1176 
   1177 	return 1;
   1178 }
   1179 
   1180 int (*rFcall[Tmax])(Msg*) = {
   1181 	[Tversion]	= rTversion,
   1182 	[Tauth]		= rTauth,
   1183 	[Tattach]	= rTattach,
   1184 	[Tflush]	= rTflush,
   1185 	[Twalk]		= rTwalk,
   1186 	[Topen]		= rTopen,
   1187 	[Tcreate]	= rTcreate,
   1188 	[Tread]		= rTread,
   1189 	[Twrite]	= rTwrite,
   1190 	[Tclunk]	= rTclunk,
   1191 	[Tremove]	= rTremove,
   1192 	[Tstat]		= rTstat,
   1193 	[Twstat]	= rTwstat,
   1194 };