xref: /plan9/sys/src/cmd/fossil/9p.c (revision 0b459c2cb92b7c9d88818e9a2f72e678e5bc4553)
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 0;
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 				vtMemFree(u);
39 				deCleanup(&de);
40 				return 1;
41 			}
42 			vtMemFree(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)){
60 		deCleanup(&de);
61 		return 1;
62 	}
63 	vtSetError(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 		vtSetError("no file name");
95 		return 0;
96 	}
97 	if(name[0] == '.'){
98 		if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){
99 			vtSetError(". and .. illegal as file name");
100 			return 0;
101 		}
102 	}
103 
104 	for(p = name; *p != '\0'; p++){
105 		if((*p & 0xFF) < 040){
106 			vtSetError("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;
120 	DirEntry de;
121 	char *gid, *strs, *uid;
122 	int gl, op, retval, tsync;
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 		vtSetError(EPermission);
132 		goto error0;
133 	}
134 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
135 		vtSetError("read-only filesystem");
136 		goto error0;
137 	}
138 
139 	if(!fileGetDir(fid->file, &de))
140 		goto error0;
141 
142 	strs = vtMemAlloc(m->t.nstat);
143 	if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){
144 		vtSetError("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 			vtSetError("wstat -- attempt to change qid.path");
164 			goto error;
165 		}
166 		tsync = 0;
167 	}
168 	if(dir.qid.vers != ~0){
169 		if(dir.qid.vers != de.mcount){
170 			vtSetError("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 			vtSetError("wstat -- unknown muid");
178 			goto error;
179 		}
180 		if(strcmp(uid, de.mid) != 0){
181 			vtSetError("wstat -- attempt to change muid");
182 			goto error;
183 		}
184 		vtMemFree(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 != ~0){
193 		if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
194 			vtSetError("wstat -- qid.type/mode mismatch");
195 			goto error;
196 		}
197 	}
198 
199 	op = 0;
200 
201 	if(dir.qid.type != (uchar)~0 || dir.mode != ~0){
202 		/*
203 		 * .qid.type or .mode isn't defaulted, check for unknown bits.
204 		 */
205 		if(dir.mode == ~0)
206 			dir.mode = (dir.qid.type<<24)|(de.mode & 0777);
207 		if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
208 			vtSetError("wstat -- unknown bits in qid.type/mode");
209 			goto error;
210 		}
211 
212 		/*
213 		 * Synthesise a mode to check against the current settings.
214 		 */
215 		mode = dir.mode & 0777;
216 		if(dir.mode & DMEXCL)
217 			mode |= ModeExclusive;
218 		if(dir.mode & DMAPPEND)
219 			mode |= ModeAppend;
220 		if(dir.mode & DMDIR)
221 			mode |= ModeDir;
222 
223 		if((de.mode^mode) & ModeDir){
224 			vtSetError("wstat -- attempt to change directory bit");
225 			goto error;
226 		}
227 
228 		if((de.mode & (ModeAppend|ModeExclusive|0777)) != mode){
229 			de.mode &= ~(ModeAppend|ModeExclusive|0777);
230 			de.mode |= mode;
231 			op = 1;
232 		}
233 		tsync = 0;
234 	}
235 
236 	if(dir.mtime != ~0){
237 		if(dir.mtime != de.mtime){
238 			de.mtime = dir.mtime;
239 			op = 1;
240 		}
241 		tsync = 0;
242 	}
243 
244 	if(dir.length != ~0){
245 		if(de.mode & ModeDir){
246 			vtSetError("wstat -- attempt to change length of directory");
247 			goto error;
248 		}
249 		if(dir.length != de.size){
250 			de.size = dir.length;
251 			op = 1;
252 		}
253 		tsync = 0;
254 	}
255 
256 	/*
257 	 * Check for permission to change .mode, .mtime or .length,
258 	 * must be owner or leader of either group, for which test gid
259 	 * is needed; permission checks on gid will be done later.
260 	 */
261 	if(dir.gid != nil && *dir.gid != '\0'){
262 		if((gid = uidByUname(dir.gid)) == nil){
263 			vtSetError("wstat -- unknown gid");
264 			goto error;
265 		}
266 		tsync = 0;
267 	}
268 	else
269 		gid = vtStrDup(de.gid);
270 
271 	/*
272 	 * 'Gl' counts whether neither, one or both groups are led.
273 	 */
274 	gl = groupLeader(gid, fid->uname) != 0;
275 	gl += groupLeader(de.gid, fid->uname) != 0;
276 
277 	if(op && !fsysWstatAllow(fid->fsys)){
278 		if(strcmp(fid->uid, de.uid) != 0 && !gl){
279 			vtSetError("wstat -- not owner or group leader");
280 			goto error;
281 		}
282 	}
283 
284 	/*
285 	 * Check for permission to change group, must be
286 	 * either owner and in new group or leader of both groups.
287 	 * If gid is nil here then
288 	 */
289 	if(strcmp(gid, de.gid) != 0){
290 		if(!fsysWstatAllow(fid->fsys)
291 		&& !(strcmp(fid->uid, de.uid) == 0 && groupMember(gid, fid->uname))
292 		&& !(gl == 2)){
293 			vtSetError("wstat -- not owner and not group leaders");
294 			goto error;
295 		}
296 		vtMemFree(de.gid);
297 		de.gid = gid;
298 		gid = nil;
299 		op = 1;
300 	}
301 
302 	/*
303 	 * Rename.
304 	 * Check .name is valid and different to the current.
305 	 * If so, check write permission in parent.
306 	 */
307 	if(dir.name != nil && *dir.name != '\0'){
308 		if(!validFileName(dir.name))
309 			goto error;
310 		if(strcmp(dir.name, de.elem) != 0){
311 			if(!permParent(fid, PermW))
312 				goto error;
313 			vtMemFree(de.elem);
314 			de.elem = vtStrDup(dir.name);
315 			op = 1;
316 		}
317 		tsync = 0;
318 	}
319 
320 	/*
321 	 * Check for permission to change owner - must be god.
322 	 */
323 	if(dir.uid != nil && *dir.uid != '\0'){
324 		if((uid = uidByUname(dir.uid)) == nil){
325 			vtSetError("wstat -- unknown uid");
326 			goto error;
327 		}
328 		if(strcmp(uid, de.uid) != 0){
329 			if(!fsysWstatAllow(fid->fsys)){
330 				vtSetError("wstat -- not owner");
331 				goto error;
332 			}
333 			if(strcmp(uid, uidnoworld) == 0){
334 				vtSetError(EPermission);
335 				goto error;
336 			}
337 			vtMemFree(de.uid);
338 			de.uid = uid;
339 			uid = nil;
340 			op = 1;
341 		}
342 		tsync = 0;
343 	}
344 
345 	if(op)
346 		retval = fileSetDir(fid->file, &de, fid->uid);
347 	else
348 		retval = 1;
349 
350 	if(tsync){
351 		/*
352 		 * All values were defaulted,
353 		 * make the state of the file exactly what it
354 		 * claims to be before returning...
355 		 */
356 		USED(tsync);
357 	}
358 
359 error:
360 	deCleanup(&de);
361 	vtMemFree(strs);
362 	if(gid != nil)
363 		vtMemFree(gid);
364 	if(uid != nil)
365 		vtMemFree(uid);
366 error0:
367 	fidPut(fid);
368 	return retval;
369 };
370 
371 static int
372 rTstat(Msg* m)
373 {
374 	Dir dir;
375 	Fid *fid;
376 	DirEntry de;
377 
378 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
379 		return 0;
380 	if(fid->qid.type & QTAUTH){
381 		memset(&dir, 0, sizeof(Dir));
382 		dir.qid = fid->qid;
383 		dir.mode = DMAUTH;
384 		dir.atime = time(0L);
385 		dir.mtime = dir.atime;
386 		dir.length = 0;
387 		dir.name = "#¿";
388 		dir.uid = fid->uname;
389 		dir.gid = fid->uname;
390 		dir.muid = fid->uname;
391 
392 		if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){
393 			vtSetError("stat QTAUTH botch");
394 			fidPut(fid);
395 			return 0;
396 		}
397 		m->r.stat = m->data;
398 
399 		fidPut(fid);
400 		return 1;
401 	}
402 	if(!fileGetDir(fid->file, &de)){
403 		fidPut(fid);
404 		return 0;
405 	}
406 	fidPut(fid);
407 
408 	/*
409 	 * TODO: optimise this copy (in convS2M) away somehow.
410 	 * This pettifoggery with m->data will do for the moment.
411 	 */
412 	m->r.nstat = dirDe2M(&de, m->data, m->con->msize);
413 	m->r.stat = m->data;
414 	deCleanup(&de);
415 
416 	return 1;
417 }
418 
419 static int
420 _rTclunk(Fid* fid, int remove)
421 {
422 	int rok;
423 
424 	if(fid->excl)
425 		exclFree(fid);
426 
427 	rok = 1;
428 	if(remove && !(fid->qid.type & QTAUTH)){
429 		if((rok = permParent(fid, PermW)) != 0)
430 			rok = fileRemove(fid->file, fid->uid);
431 	}
432 	fidClunk(fid);
433 
434 	return rok;
435 }
436 
437 static int
438 rTremove(Msg* m)
439 {
440 	Fid *fid;
441 
442 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
443 		return 0;
444 	return _rTclunk(fid, 1);
445 }
446 
447 static int
448 rTclunk(Msg* m)
449 {
450 	Fid *fid;
451 
452 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
453 		return 0;
454 	_rTclunk(fid, (fid->open & FidORclose));
455 
456 	return 1;
457 }
458 
459 static int
460 rTwrite(Msg* m)
461 {
462 	Fid *fid;
463 	int count, n;
464 
465 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
466 		return 0;
467 	if(!(fid->open & FidOWrite)){
468 		vtSetError("fid not open for write");
469 		goto error;
470 	}
471 
472 	count = m->t.count;
473 	if(count < 0 || count > m->con->msize-IOHDRSZ){
474 		vtSetError("write count too big");
475 		goto error;
476 	}
477 	if(m->t.offset < 0){
478 		vtSetError("write offset negative");
479 		goto error;
480 	}
481 	if(fid->excl != nil && !exclUpdate(fid))
482 		goto error;
483 
484 	if(fid->qid.type & QTDIR){
485 		vtSetError("is a directory");
486 		goto error;
487 	}
488 	else if(fid->qid.type & QTAUTH)
489 		n = authWrite(fid, m->t.data, count);
490 	else
491 		n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid);
492 	if(n < 0)
493 		goto error;
494 
495 
496 	m->r.count = n;
497 
498 	fidPut(fid);
499 	return 1;
500 
501 error:
502 	fidPut(fid);
503 	return 0;
504 }
505 
506 static int
507 rTread(Msg* m)
508 {
509 	Fid *fid;
510 	uchar *data;
511 	int count, n;
512 
513 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
514 		return 0;
515 	if(!(fid->open & FidORead)){
516 		vtSetError("fid not open for read");
517 		goto error;
518 	}
519 
520 	count = m->t.count;
521 	if(count < 0 || count > m->con->msize-IOHDRSZ){
522 		vtSetError("read count too big");
523 		goto error;
524 	}
525 	if(m->t.offset < 0){
526 		vtSetError("read offset negative");
527 		goto error;
528 	}
529 	if(fid->excl != nil && !exclUpdate(fid))
530 		goto error;
531 
532 	/*
533 	 * TODO: optimise this copy (in convS2M) away somehow.
534 	 * This pettifoggery with m->data will do for the moment.
535 	 */
536 	data = m->data+IOHDRSZ;
537 	if(fid->qid.type & QTDIR)
538 		n = dirRead(fid, data, count, m->t.offset);
539 	else if(fid->qid.type & QTAUTH)
540 		n = authRead(fid, data, count);
541 	else
542 		n = fileRead(fid->file, data, count, m->t.offset);
543 	if(n < 0)
544 		goto error;
545 
546 	m->r.count = n;
547 	m->r.data = (char*)data;
548 
549 	fidPut(fid);
550 	return 1;
551 
552 error:
553 	fidPut(fid);
554 	return 0;
555 }
556 
557 static int
558 rTcreate(Msg* m)
559 {
560 	Fid *fid;
561 	File *file;
562 	ulong mode;
563 	int omode, open, perm;
564 
565 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
566 		return 0;
567 	if(fid->open){
568 		vtSetError("fid open for I/O");
569 		goto error;
570 	}
571 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
572 		vtSetError("read-only filesystem");
573 		goto error;
574 	}
575 	if(!fileIsDir(fid->file)){
576 		vtSetError("not a directory");
577 		goto error;
578 	}
579 	if(!permFid(fid, PermW))
580 		goto error;
581 	if(!validFileName(m->t.name))
582 		goto error;
583 	if(strcmp(fid->uid, uidnoworld) == 0){
584 		vtSetError(EPermission);
585 		goto error;
586 	}
587 
588 	omode = m->t.mode & OMODE;
589 	open = 0;
590 
591 	if(omode == OREAD || omode == ORDWR || omode == OEXEC)
592 		open |= FidORead;
593 	if(omode == OWRITE || omode == ORDWR)
594 		open |= FidOWrite;
595 	if((open & (FidOWrite|FidORead)) == 0){
596 		vtSetError("unknown mode");
597 		goto error;
598 	}
599 	if(m->t.perm & DMDIR){
600 		if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){
601 			vtSetError("illegal mode");
602 			goto error;
603 		}
604 		if(m->t.perm & DMAPPEND){
605 			vtSetError("illegal perm");
606 			goto error;
607 		}
608 	}
609 
610 	mode = fileGetMode(fid->file);
611 	perm = m->t.perm;
612 	if(m->t.perm & DMDIR)
613 		perm &= ~0777|(mode & 0777);
614 	else
615 		perm &= ~0666|(mode & 0666);
616 	mode = perm & 0777;
617 	if(m->t.perm & DMDIR)
618 		mode |= ModeDir;
619 	if(m->t.perm & DMAPPEND)
620 		mode |= ModeAppend;
621 	if(m->t.perm & DMEXCL)
622 		mode |= ModeExclusive;
623 
624 	if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){
625 		fidPut(fid);
626 		return 0;
627 	}
628 	fileDecRef(fid->file);
629 
630 	fid->qid.vers = fileGetMcount(file);
631 	fid->qid.path = fileGetId(file);
632 	fid->file = file;
633 	mode = fileGetMode(fid->file);
634 	if(mode & ModeDir)
635 		fid->qid.type = QTDIR;
636 	else
637 		fid->qid.type = QTFILE;
638 	if(mode & ModeAppend)
639 		fid->qid.type |= QTAPPEND;
640 	if(mode & ModeExclusive){
641 		fid->qid.type |= QTEXCL;
642 		assert(exclAlloc(fid) != 0);
643 	}
644 	if(m->t.mode & ORCLOSE)
645 		open |= FidORclose;
646 	fid->open = open;
647 
648 	m->r.qid = fid->qid;
649 	m->r.iounit = m->con->msize-IOHDRSZ;
650 
651 	fidPut(fid);
652 	return 1;
653 
654 error:
655 	fidPut(fid);
656 	return 0;
657 }
658 
659 static int
660 rTopen(Msg* m)
661 {
662 	Fid *fid;
663 	int isdir, mode, omode, open, rofs;
664 
665 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
666 		return 0;
667 	if(fid->open){
668 		vtSetError("fid open for I/O");
669 		goto error;
670 	}
671 
672 	isdir = fileIsDir(fid->file);
673 	open = 0;
674 	rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname);
675 
676 	if(m->t.mode & ORCLOSE){
677 		if(isdir){
678 			vtSetError("is a directory");
679 			goto error;
680 		}
681 		if(rofs){
682 			vtSetError("read-only filesystem");
683 			goto error;
684 		}
685 		if(!permParent(fid, PermW))
686 			goto error;
687 
688 		open |= FidORclose;
689 	}
690 
691 	omode = m->t.mode & OMODE;
692 	if(omode == OREAD || omode == ORDWR){
693 		if(!permFid(fid, PermR))
694 			goto error;
695 		open |= FidORead;
696 	}
697 	if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){
698 		if(isdir){
699 			vtSetError("is a directory");
700 			goto error;
701 		}
702 		if(rofs){
703 			vtSetError("read-only filesystem");
704 			goto error;
705 		}
706 		if(!permFid(fid, PermW))
707 			goto error;
708 		open |= FidOWrite;
709 	}
710 	if(omode == OEXEC){
711 		if(isdir){
712 			vtSetError("is a directory");
713 			goto error;
714 		}
715 		if(!permFid(fid, PermX))
716 			goto error;
717 		open |= FidORead;
718 	}
719 	if((open & (FidOWrite|FidORead)) == 0){
720 		vtSetError("unknown mode");
721 		goto error;
722 	}
723 
724 	mode = fileGetMode(fid->file);
725 	if((mode & ModeExclusive) && exclAlloc(fid) == 0)
726 		goto error;
727 
728 	/*
729 	 * Everything checks out, try to commit any changes.
730 	 */
731 	if((m->t.mode & OTRUNC) && !(mode & ModeAppend)){
732 		if(!fileTruncate(fid->file, fid->uid))
733 			goto error;
734 		fid->qid.vers = fileGetMcount(fid->file);
735 	}
736 	if(isdir && fid->db != nil){
737 		dirBufFree(fid->db);
738 		fid->db = nil;
739 	}
740 
741 	m->r.qid = fid->qid;
742 	m->r.iounit = m->con->msize-IOHDRSZ;
743 
744 	fid->open = open;
745 
746 	fidPut(fid);
747 	return 1;
748 
749 error:
750 	if(fid->excl != nil)
751 		exclFree(fid);
752 	fidPut(fid);
753 	return 0;
754 }
755 
756 static int
757 rTwalk(Msg* m)
758 {
759 	Qid qid;
760 	Fcall *r, *t;
761 	int nwname, wlock;
762 	File *file, *nfile;
763 	Fid *fid, *ofid, *nfid;
764 
765 	t = &m->t;
766 	if(t->fid == t->newfid)
767 		wlock = FidFWlock;
768 	else
769 		wlock = 0;
770 
771 	/*
772 	 * The file identified by t->fid must be valid in the
773 	 * current session and must not have been opened for I/O
774 	 * by an open or create message.
775 	 */
776 	if((ofid = fidGet(m->con, t->fid, wlock)) == nil)
777 		return 0;
778 	if(ofid->open){
779 		vtSetError("file open for I/O");
780 		fidPut(ofid);
781 		return 0;
782 	}
783 
784 	/*
785 	 * If newfid is not the same as fid, allocate a new file;
786 	 * a side effect is checking newfid is not already in use (error);
787 	 * if there are no names to walk this will be equivalent to a
788 	 * simple 'clone' operation.
789 	 * It's a no-op if newfid is the same as fid and t->nwname is 0.
790 	 */
791 	nfid = nil;
792 	if(t->fid != t->newfid){
793 		nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate);
794 		if(nfid == nil){
795 			vtSetError("fid in use");
796 			fidPut(ofid);
797 			return 0;
798 		}
799 		nfid->open = ofid->open & ~FidORclose;
800 		nfid->file = fileIncRef(ofid->file);
801 		nfid->qid = ofid->qid;
802 		nfid->uid = vtStrDup(ofid->uid);
803 		nfid->uname = vtStrDup(ofid->uname);
804 		nfid->fsys = fsysIncRef(ofid->fsys);
805 		fid = nfid;
806 	}
807 	else
808 		fid = ofid;
809 
810 	r = &m->r;
811 	r->nwqid = 0;
812 
813 	if(t->nwname == 0){
814 		if(nfid != nil)
815 			fidPut(nfid);
816 		fidPut(ofid);
817 
818 		return 1;
819 	}
820 
821 	file = fid->file;
822 	fileIncRef(file);
823 	qid = fid->qid;
824 
825 	for(nwname = 0; nwname < t->nwname; nwname++){
826 		/*
827 		 * Walked elements must represent a directory and
828 		 * the implied user must have permission to search
829 		 * the directory.  Walking .. is always allowed, so that
830 		 * you can't walk into a directory and then not be able
831 		 * to walk out of it.
832 		 */
833 		if(!(qid.type & QTDIR)){
834 			vtSetError("not a directory");
835 			break;
836 		}
837 		if(!permFile(file, fid, PermX) && strcmp(t->wname[nwname], "..") != 0)
838 			break;
839 		if((nfile = fileWalk(file, t->wname[nwname])) == nil)
840 			break;
841 		fileDecRef(file);
842 		file = nfile;
843 		qid.type = QTFILE;
844 		if(fileIsDir(file))
845 			qid.type = QTDIR;
846 		qid.vers = fileGetMcount(file);
847 		qid.path = fileGetId(file);
848 		r->wqid[r->nwqid++] = qid;
849 	}
850 
851 	if(nwname == t->nwname){
852 		/*
853 		 * Walked all elements. Update the target fid
854 		 * from the temporary qid used during the walk,
855 		 * and tidy up.
856 		 */
857 		fid->qid = r->wqid[r->nwqid-1];
858 		fileDecRef(fid->file);
859 		fid->file = file;
860 
861 		if(nfid != nil)
862 			fidPut(nfid);
863 
864 		fidPut(ofid);
865 		return 1;
866 	}
867 
868 	/*
869 	 * Didn't walk all elements, 'clunk' nfid if it exists
870 	 * and leave fid untouched.
871 	 * It's not an error if some of the elements were walked OK.
872 	 */
873 	fileDecRef(file);
874 	if(nfid != nil)
875 		fidClunk(nfid);
876 
877 	fidPut(ofid);
878 	if(nwname == 0)
879 		return 0;
880 	return 1;
881 }
882 
883 static int
884 rTflush(Msg* m)
885 {
886 	if(m->t.oldtag != NOTAG)
887 		msgFlush(m);
888 	return 1;
889 }
890 
891 static void
892 parseAname(char *aname, char **fsname, char **path)
893 {
894 	char *s;
895 
896 	if(aname && aname[0])
897 		s = vtStrDup(aname);
898 	else
899 		s = vtStrDup("main/active");
900 	*fsname = s;
901 	if((*path = strchr(s, '/')) != nil)
902 		*(*path)++ = '\0';
903 	else
904 		*path = "";
905 }
906 
907 static int
908 rTattach(Msg* m)
909 {
910 	Fid *fid;
911 	Fsys *fsys;
912 	char *fsname, *path;
913 
914 	if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil)
915 		return 0;
916 
917 	parseAname(m->t.aname, &fsname, &path);
918 	if((fsys = fsysGet(fsname)) == nil){
919 		fidClunk(fid);
920 		vtMemFree(fsname);
921 		return 0;
922 	}
923 	fid->fsys = fsys;
924 
925 	if(m->t.uname[0] != '\0')
926 		fid->uname = vtStrDup(m->t.uname);
927 	else
928 		fid->uname = vtStrDup(unamenone);
929 
930 	if(fsysNoAuthCheck(fsys)){
931 		if((fid->uid = uidByUname(fid->uname)) == nil)
932 			fid->uid = vtStrDup(unamenone);
933 	}
934 	else if(!authCheck(&m->t, fid, fsys)){
935 		fidClunk(fid);
936 		vtMemFree(fsname);
937 		vtSetError("authentication failed");
938 		return 0;
939 	}
940 
941 	fsysFsRlock(fsys);
942 	if((fid->file = fsysGetRoot(fsys, path)) == nil){
943 		fsysFsRUnlock(fsys);
944 		fidClunk(fid);
945 		vtMemFree(fsname);
946 		return 0;
947 	}
948 	fsysFsRUnlock(fsys);
949 	vtMemFree(fsname);
950 
951 	fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR};
952 	m->r.qid = fid->qid;
953 
954 	fidPut(fid);
955 	return 1;
956 }
957 
958 static int
959 rTauth(Msg* m)
960 {
961 	int afd;
962 	Con *con;
963 	Fid *afid;
964 	Fsys *fsys;
965 	char *fsname, *path;
966 
967 	parseAname(m->t.aname, &fsname, &path);
968 	if((fsys = fsysGet(fsname)) == nil){
969 		vtMemFree(fsname);
970 		return 0;
971 	}
972 	vtMemFree(fsname);
973 
974 	if(fsysNoAuthCheck(fsys)){
975 		m->con->aok = 1;
976 		vtSetError("authentication disabled");
977 		fsysPut(fsys);
978 		return 0;
979 	}
980 	if(strcmp(m->t.uname, unamenone) == 0){
981 		vtSetError("user 'none' requires no authentication");
982 		fsysPut(fsys);
983 		return 0;
984 	}
985 
986 	con = m->con;
987 	if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){
988 		fsysPut(fsys);
989 		return 0;
990 	}
991 	afid->fsys = fsys;
992 
993 	if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
994 		vtSetError("can't open \"/mnt/factotum/rpc\"");
995 		fidClunk(afid);
996 		return 0;
997 	}
998 	if((afid->rpc = auth_allocrpc(afd)) == nil){
999 		close(afd);
1000 		vtSetError("can't auth_allocrpc");
1001 		fidClunk(afid);
1002 		return 0;
1003 	}
1004 	if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){
1005 		vtSetError("can't auth_rpc");
1006 		fidClunk(afid);
1007 		return 0;
1008 	}
1009 
1010 	afid->open = FidOWrite|FidORead;
1011 	afid->qid.type = QTAUTH;
1012 	afid->qid.path = m->t.afid;
1013 	afid->uname = vtStrDup(m->t.uname);
1014 
1015 	m->r.qid = afid->qid;
1016 
1017 	fidPut(afid);
1018 	return 1;
1019 }
1020 
1021 static int
1022 rTversion(Msg* m)
1023 {
1024 	int v;
1025 	Con *con;
1026 	Fcall *r, *t;
1027 
1028 	t = &m->t;
1029 	r = &m->r;
1030 	con = m->con;
1031 
1032 	vtLock(con->lock);
1033 	if(con->state != ConInit){
1034 		vtUnlock(con->lock);
1035 		vtSetError("Tversion: down");
1036 		return 0;
1037 	}
1038 	con->state = ConNew;
1039 
1040 	/*
1041 	 * Release the karma of past lives and suffering.
1042 	 * Should this be done before or after checking the
1043 	 * validity of the Tversion?
1044 	 */
1045 	fidClunkAll(con);
1046 
1047 	if(t->tag != NOTAG){
1048 		vtUnlock(con->lock);
1049 		vtSetError("Tversion: invalid tag");
1050 		return 0;
1051 	}
1052 
1053 	if(t->msize < 256){
1054 		vtUnlock(con->lock);
1055 		vtSetError("Tversion: message size too small");
1056 		return 0;
1057 	}
1058 	if(t->msize < con->msize)
1059 		r->msize = t->msize;
1060 	else
1061 		r->msize = con->msize;
1062 
1063 	r->version = "unknown";
1064 	if(t->version[0] == '9' && t->version[1] == 'P'){
1065 		/*
1066 		 * Currently, the only defined version
1067 		 * is "9P2000"; ignore any later versions.
1068           	 */
1069 		v = strtol(&t->version[2], 0, 10);
1070 		if(v >= 2000){
1071 			r->version = VERSION9P;
1072 			con->msize = r->msize;
1073 			con->state = ConUp;
1074 		}
1075 		else if(strcmp(t->version, "9PEoF") == 0){
1076 			r->version = "9PEoF";
1077 			con->msize = r->msize;
1078 			con->state = ConMoribund;
1079 		}
1080 	}
1081 	vtUnlock(con->lock);
1082 
1083 	return 1;
1084 }
1085 
1086 int (*rFcall[Tmax])(Msg*) = {
1087 	[Tversion]	= rTversion,
1088 	[Tauth]		= rTauth,
1089 	[Tattach]	= rTattach,
1090 	[Tflush]	= rTflush,
1091 	[Twalk]		= rTwalk,
1092 	[Topen]		= rTopen,
1093 	[Tcreate]	= rTcreate,
1094 	[Tread]		= rTread,
1095 	[Twrite]	= rTwrite,
1096 	[Tclunk]	= rTclunk,
1097 	[Tremove]	= rTremove,
1098 	[Tstat]		= rTstat,
1099 	[Twstat]	= rTwstat,
1100 };
1101