xref: /plan9-contrib/sys/src/cmd/fossil/9p.c (revision addada5276a0d9f3f0933c5a56f279a7ac07163d)
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
permFile(File * file,Fid * fid,int perm)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
permFid(Fid * fid,int p)70 permFid(Fid* fid, int p)
71 {
72 	return permFile(fid->file, fid, p);
73 }
74 
75 static int
permParent(Fid * fid,int p)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
validFileName(char * name)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
rTwstat(Msg * m)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 != ~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 != ~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 != ~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 != ~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 	fid->qid.vers = fileGetMcount(fid->file);
365 	m->r.qid = fid->qid;
366 	m->r.iounit = m->con->msize-IOHDRSZ;
367 
368 	if(tsync){
369 		/*
370 		 * All values were defaulted,
371 		 * make the state of the file exactly what it
372 		 * claims to be before returning...
373 		 */
374 		USED(tsync);
375 	}
376 
377 error:
378 	deCleanup(&de);
379 	vtfree(strs);
380 	if(gid != nil)
381 		vtfree(gid);
382 	if(uid != nil)
383 		vtfree(uid);
384 error0:
385 	fidPut(fid);
386 	return retval;
387 };
388 
389 static int
rTstat(Msg * m)390 rTstat(Msg* m)
391 {
392 	Dir dir;
393 	Fid *fid;
394 	DirEntry de;
395 
396 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
397 		return 0;
398 	if(fid->qid.type & QTAUTH){
399 		memset(&dir, 0, sizeof(Dir));
400 		dir.qid = fid->qid;
401 		dir.mode = DMAUTH;
402 		dir.atime = time(0L);
403 		dir.mtime = dir.atime;
404 		dir.length = 0;
405 		dir.name = "#¿";
406 		dir.uid = fid->uname;
407 		dir.gid = fid->uname;
408 		dir.muid = fid->uname;
409 
410 		if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){
411 			werrstr("stat QTAUTH botch");
412 			fidPut(fid);
413 			return 0;
414 		}
415 		m->r.stat = m->data;
416 
417 		fidPut(fid);
418 		return 1;
419 	}
420 	if(!fileGetDir(fid->file, &de)){
421 		fidPut(fid);
422 		return 0;
423 	}
424 	fidPut(fid);
425 
426 	/*
427 	 * TODO: optimise this copy (in convS2M) away somehow.
428 	 * This pettifoggery with m->data will do for the moment.
429 	 */
430 	m->r.nstat = dirDe2M(&de, m->data, m->con->msize);
431 	m->r.stat = m->data;
432 	deCleanup(&de);
433 
434 	return 1;
435 }
436 
437 static int
_rTclunk(Fid * fid,int remove)438 _rTclunk(Fid* fid, int remove)
439 {
440 	int rok;
441 
442 	if(fid->excl)
443 		exclFree(fid);
444 
445 	rok = 1;
446 	if(remove && !(fid->qid.type & QTAUTH)){
447 		if((rok = permParent(fid, PermW)) > 0)
448 			rok = fileRemove(fid->file, fid->uid);
449 	}
450 	fidClunk(fid);
451 
452 	return rok;
453 }
454 
455 static int
rTremove(Msg * m)456 rTremove(Msg* m)
457 {
458 	Fid *fid;
459 
460 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
461 		return 0;
462 	return _rTclunk(fid, 1);
463 }
464 
465 static int
rTclunk(Msg * m)466 rTclunk(Msg* m)
467 {
468 	Fid *fid;
469 
470 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
471 		return 0;
472 	_rTclunk(fid, (fid->open & FidORclose));
473 
474 	return 1;
475 }
476 
477 static int
rTwrite(Msg * m)478 rTwrite(Msg* m)
479 {
480 	Fid *fid;
481 	int count, n;
482 
483 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
484 		return 0;
485 	if(!(fid->open & FidOWrite)){
486 		werrstr("fid not open for write");
487 		goto error;
488 	}
489 
490 	count = m->t.count;
491 	if(count < 0 || count > m->con->msize-IOHDRSZ){
492 		werrstr("write count too big");
493 		goto error;
494 	}
495 	if(m->t.offset < 0){
496 		werrstr("write offset negative");
497 		goto error;
498 	}
499 	if(fid->excl != nil && !exclUpdate(fid))
500 		goto error;
501 
502 	if(fid->qid.type & QTDIR){
503 		werrstr("is a directory");
504 		goto error;
505 	}
506 	else if(fid->qid.type & QTAUTH)
507 		n = authWrite(fid, m->t.data, count);
508 	else
509 		n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid);
510 	if(n < 0)
511 		goto error;
512 
513 
514 	m->r.count = n;
515 
516 	fidPut(fid);
517 	return 1;
518 
519 error:
520 	fidPut(fid);
521 	return 0;
522 }
523 
524 static int
rTread(Msg * m)525 rTread(Msg* m)
526 {
527 	Fid *fid;
528 	uchar *data;
529 	int count, n;
530 
531 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
532 		return 0;
533 	if(!(fid->open & FidORead)){
534 		werrstr("fid not open for read");
535 		goto error;
536 	}
537 
538 	count = m->t.count;
539 	if(count < 0 || count > m->con->msize-IOHDRSZ){
540 		werrstr("read count too big");
541 		goto error;
542 	}
543 	if(m->t.offset < 0){
544 		werrstr("read offset negative");
545 		goto error;
546 	}
547 	if(fid->excl != nil && !exclUpdate(fid))
548 		goto error;
549 
550 	/*
551 	 * TODO: optimise this copy (in convS2M) away somehow.
552 	 * This pettifoggery with m->data will do for the moment.
553 	 */
554 	data = m->data+IOHDRSZ;
555 	if(fid->qid.type & QTDIR)
556 		n = dirRead(fid, data, count, m->t.offset);
557 	else if(fid->qid.type & QTAUTH)
558 		n = authRead(fid, data, count);
559 	else
560 		n = fileRead(fid->file, data, count, m->t.offset);
561 	if(n < 0)
562 		goto error;
563 
564 	m->r.count = n;
565 	m->r.data = (char*)data;
566 
567 	fidPut(fid);
568 	return 1;
569 
570 error:
571 	fidPut(fid);
572 	return 0;
573 }
574 
575 static int
rTcreate(Msg * m)576 rTcreate(Msg* m)
577 {
578 	Fid *fid;
579 	File *file;
580 	ulong mode;
581 	int omode, open, perm;
582 
583 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
584 		return 0;
585 	if(fid->open){
586 		werrstr("fid open for I/O");
587 		goto error;
588 	}
589 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
590 		werrstr("read-only filesystem");
591 		goto error;
592 	}
593 	if(!fileIsDir(fid->file)){
594 		werrstr("not a directory");
595 		goto error;
596 	}
597 	if(permFid(fid, PermW) <= 0)
598 		goto error;
599 	if(!validFileName(m->t.name))
600 		goto error;
601 	if(strcmp(fid->uid, uidnoworld) == 0){
602 		werrstr(EPermission);
603 		goto error;
604 	}
605 
606 	omode = m->t.mode & OMODE;
607 	open = 0;
608 
609 	if(omode == OREAD || omode == ORDWR || omode == OEXEC)
610 		open |= FidORead;
611 	if(omode == OWRITE || omode == ORDWR)
612 		open |= FidOWrite;
613 	if((open & (FidOWrite|FidORead)) == 0){
614 		werrstr("unknown mode");
615 		goto error;
616 	}
617 	if(m->t.perm & DMDIR){
618 		if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){
619 			werrstr("illegal mode");
620 			goto error;
621 		}
622 		if(m->t.perm & DMAPPEND){
623 			werrstr("illegal perm");
624 			goto error;
625 		}
626 	}
627 
628 	mode = fileGetMode(fid->file);
629 	perm = m->t.perm;
630 	if(m->t.perm & DMDIR)
631 		perm &= ~0777|(mode & 0777);
632 	else
633 		perm &= ~0666|(mode & 0666);
634 	mode = perm & 0777;
635 	if(m->t.perm & DMDIR)
636 		mode |= ModeDir;
637 	if(m->t.perm & DMAPPEND)
638 		mode |= ModeAppend;
639 	if(m->t.perm & DMEXCL)
640 		mode |= ModeExclusive;
641 	if(m->t.perm & DMTMP)
642 		mode |= ModeTemporary;
643 
644 	if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){
645 		fidPut(fid);
646 		return 0;
647 	}
648 	fileDecRef(fid->file);
649 
650 	fid->qid.vers = fileGetMcount(file);
651 	fid->qid.path = fileGetId(file);
652 	fid->file = file;
653 	mode = fileGetMode(fid->file);
654 	if(mode & ModeDir)
655 		fid->qid.type = QTDIR;
656 	else
657 		fid->qid.type = QTFILE;
658 	if(mode & ModeAppend)
659 		fid->qid.type |= QTAPPEND;
660 	if(mode & ModeExclusive){
661 		fid->qid.type |= QTEXCL;
662 		assert(exclAlloc(fid) != 0);
663 	}
664 	if(m->t.mode & ORCLOSE)
665 		open |= FidORclose;
666 	fid->open = open;
667 
668 	m->r.qid = fid->qid;
669 	m->r.iounit = m->con->msize-IOHDRSZ;
670 
671 	fidPut(fid);
672 	return 1;
673 
674 error:
675 	fidPut(fid);
676 	return 0;
677 }
678 
679 static int
rTopen(Msg * m)680 rTopen(Msg* m)
681 {
682 	Fid *fid;
683 	int isdir, mode, omode, open, rofs;
684 
685 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
686 		return 0;
687 	if(fid->open){
688 		werrstr("fid open for I/O");
689 		goto error;
690 	}
691 
692 	isdir = fileIsDir(fid->file);
693 	open = 0;
694 	rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname);
695 
696 	if(m->t.mode & ORCLOSE){
697 		if(isdir){
698 			werrstr("is a directory");
699 			goto error;
700 		}
701 		if(rofs){
702 			werrstr("read-only filesystem");
703 			goto error;
704 		}
705 		if(permParent(fid, PermW) <= 0)
706 			goto error;
707 
708 		open |= FidORclose;
709 	}
710 
711 	omode = m->t.mode & OMODE;
712 	if(omode == OREAD || omode == ORDWR){
713 		if(permFid(fid, PermR) <= 0)
714 			goto error;
715 		open |= FidORead;
716 	}
717 	if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){
718 		if(isdir){
719 			werrstr("is a directory");
720 			goto error;
721 		}
722 		if(rofs){
723 			werrstr("read-only filesystem");
724 			goto error;
725 		}
726 		if(permFid(fid, PermW) <= 0)
727 			goto error;
728 		open |= FidOWrite;
729 	}
730 	if(omode == OEXEC){
731 		if(isdir){
732 			werrstr("is a directory");
733 			goto error;
734 		}
735 		if(permFid(fid, PermX) <= 0)
736 			goto error;
737 		open |= FidORead;
738 	}
739 	if((open & (FidOWrite|FidORead)) == 0){
740 		werrstr("unknown mode");
741 		goto error;
742 	}
743 
744 	mode = fileGetMode(fid->file);
745 	if((mode & ModeExclusive) && exclAlloc(fid) == 0)
746 		goto error;
747 
748 	/*
749 	 * Everything checks out, try to commit any changes.
750 	 */
751 	if((m->t.mode & OTRUNC) && !(mode & ModeAppend))
752 		if(!fileTruncate(fid->file, fid->uid))
753 			goto error;
754 
755 	if(isdir && fid->db != nil){
756 		dirBufFree(fid->db);
757 		fid->db = nil;
758 	}
759 
760 	fid->qid.vers = fileGetMcount(fid->file);
761 	m->r.qid = fid->qid;
762 	m->r.iounit = m->con->msize-IOHDRSZ;
763 
764 	fid->open = open;
765 
766 	fidPut(fid);
767 	return 1;
768 
769 error:
770 	if(fid->excl != nil)
771 		exclFree(fid);
772 	fidPut(fid);
773 	return 0;
774 }
775 
776 static int
rTwalk(Msg * m)777 rTwalk(Msg* m)
778 {
779 	Qid qid;
780 	Fcall *r, *t;
781 	int nwname, wlock;
782 	File *file, *nfile;
783 	Fid *fid, *ofid, *nfid;
784 
785 	t = &m->t;
786 	if(t->fid == t->newfid)
787 		wlock = FidFWlock;
788 	else
789 		wlock = 0;
790 
791 	/*
792 	 * The file identified by t->fid must be valid in the
793 	 * current session and must not have been opened for I/O
794 	 * by an open or create message.
795 	 */
796 	if((ofid = fidGet(m->con, t->fid, wlock)) == nil)
797 		return 0;
798 	if(ofid->open){
799 		werrstr("file open for I/O");
800 		fidPut(ofid);
801 		return 0;
802 	}
803 
804 	/*
805 	 * If newfid is not the same as fid, allocate a new file;
806 	 * a side effect is checking newfid is not already in use (error);
807 	 * if there are no names to walk this will be equivalent to a
808 	 * simple 'clone' operation.
809 	 * It's a no-op if newfid is the same as fid and t->nwname is 0.
810 	 */
811 	nfid = nil;
812 	if(t->fid != t->newfid){
813 		nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate);
814 		if(nfid == nil){
815 			werrstr("%s: walk: newfid 0x%ud in use",
816 				argv0, t->newfid);
817 			fidPut(ofid);
818 			return 0;
819 		}
820 		nfid->open = ofid->open & ~FidORclose;
821 		nfid->file = fileIncRef(ofid->file);
822 		nfid->qid = ofid->qid;
823 		nfid->uid = vtstrdup(ofid->uid);
824 		nfid->uname = vtstrdup(ofid->uname);
825 		nfid->fsys = fsysIncRef(ofid->fsys);
826 		fid = nfid;
827 	}
828 	else
829 		fid = ofid;
830 
831 	r = &m->r;
832 	r->nwqid = 0;
833 
834 	if(t->nwname == 0){
835 		if(nfid != nil)
836 			fidPut(nfid);
837 		fidPut(ofid);
838 
839 		return 1;
840 	}
841 
842 	file = fid->file;
843 	fileIncRef(file);
844 	qid = fid->qid;
845 
846 	for(nwname = 0; nwname < t->nwname; nwname++){
847 		/*
848 		 * Walked elements must represent a directory and
849 		 * the implied user must have permission to search
850 		 * the directory.  Walking .. is always allowed, so that
851 		 * you can't walk into a directory and then not be able
852 		 * to walk out of it.
853 		 */
854 		if(!(qid.type & QTDIR)){
855 			werrstr("not a directory");
856 			break;
857 		}
858 		switch(permFile(file, fid, PermX)){
859 		case 1:
860 			break;
861 		case 0:
862 			if(strcmp(t->wname[nwname], "..") == 0)
863 				break;
864 		case -1:
865 			goto Out;
866 		}
867 		if((nfile = fileWalk(file, t->wname[nwname])) == nil)
868 			break;
869 		fileDecRef(file);
870 		file = nfile;
871 		qid.type = QTFILE;
872 		if(fileIsDir(file))
873 			qid.type = QTDIR;
874 		if(fileIsAppend(file))
875 			qid.type |= QTAPPEND;
876 		if(fileIsTemporary(file))
877 			qid.type |= QTTMP;
878 		if(fileIsExclusive(file))
879 			qid.type |= QTEXCL;
880 		qid.vers = fileGetMcount(file);
881 		qid.path = fileGetId(file);
882 		r->wqid[r->nwqid++] = qid;
883 	}
884 
885 	if(nwname == t->nwname){
886 		/*
887 		 * Walked all elements. Update the target fid
888 		 * from the temporary qid used during the walk,
889 		 * and tidy up.
890 		 */
891 		fid->qid = r->wqid[r->nwqid-1];
892 		fileDecRef(fid->file);
893 		fid->file = file;
894 
895 		if(nfid != nil)
896 			fidPut(nfid);
897 
898 		fidPut(ofid);
899 		return 1;
900 	}
901 
902 Out:
903 	/*
904 	 * Didn't walk all elements, 'clunk' nfid if it exists
905 	 * and leave fid untouched.
906 	 * It's not an error if some of the elements were walked OK.
907 	 */
908 	fileDecRef(file);
909 	if(nfid != nil)
910 		fidClunk(nfid);
911 
912 	fidPut(ofid);
913 	if(nwname == 0)
914 		return 0;
915 	return 1;
916 }
917 
918 static int
rTflush(Msg * m)919 rTflush(Msg* m)
920 {
921 	if(m->t.oldtag != NOTAG)
922 		msgFlush(m);
923 	return 1;
924 }
925 
926 static void
parseAname(char * aname,char ** fsname,char ** path)927 parseAname(char *aname, char **fsname, char **path)
928 {
929 	char *s;
930 
931 	if(aname && aname[0])
932 		s = vtstrdup(aname);
933 	else
934 		s = vtstrdup("main/active");
935 	*fsname = s;
936 	if((*path = strchr(s, '/')) != nil)
937 		*(*path)++ = '\0';
938 	else
939 		*path = "";
940 }
941 
942 /*
943  * Check remote IP address against /mnt/ipok.
944  * Sources.cs.bell-labs.com uses this to disallow
945  * network connections from Sudan, Libya, etc.,
946  * following U.S. cryptography export regulations.
947  */
948 static int
conIPCheck(Con * con)949 conIPCheck(Con* con)
950 {
951 	char ok[256], *p;
952 	int fd;
953 
954 	if(con->flags&ConIPCheck){
955 		if(con->remote[0] == 0){
956 			werrstr("cannot verify unknown remote address");
957 			return 0;
958 		}
959 		if(access("/mnt/ipok/ok", AEXIST) < 0){
960 			/* mount closes the fd on success */
961 			if((fd = open("/srv/ipok", ORDWR)) >= 0
962 			&& mount(fd, -1, "/mnt/ipok", MREPL, "") < 0)
963 				close(fd);
964 			if(access("/mnt/ipok/ok", AEXIST) < 0){
965 				werrstr("cannot verify remote address");
966 				return 0;
967 			}
968 		}
969 		snprint(ok, sizeof ok, "/mnt/ipok/ok/%s", con->remote);
970 		if((p = strchr(ok, '!')) != nil)
971 			*p = 0;
972 		if(access(ok, AEXIST) < 0){
973 			werrstr("restricted remote address");
974 			return 0;
975 		}
976 	}
977 	return 1;
978 }
979 
980 static int
rTattach(Msg * m)981 rTattach(Msg* m)
982 {
983 	Fid *fid;
984 	Fsys *fsys;
985 	char *fsname, *path;
986 
987 	if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil)
988 		return 0;
989 
990 	parseAname(m->t.aname, &fsname, &path);
991 	if((fsys = fsysGet(fsname)) == nil){
992 		fidClunk(fid);
993 		vtfree(fsname);
994 		return 0;
995 	}
996 	fid->fsys = fsys;
997 
998 	if(m->t.uname[0] != '\0')
999 		fid->uname = vtstrdup(m->t.uname);
1000 	else
1001 		fid->uname = vtstrdup(unamenone);
1002 
1003 	if((fid->con->flags&ConIPCheck) && !conIPCheck(fid->con)){
1004 		consPrint("reject %s from %s: %r\n", fid->uname, fid->con->remote);
1005 		fidClunk(fid);
1006 		vtfree(fsname);
1007 		return 0;
1008 	}
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
rTauth(Msg * m)1037 rTauth(Msg* m)
1038 {
1039 	int afd;
1040 	Con *con;
1041 	Fid *afid;
1042 	Fsys *fsys;
1043 	char *fsname, *path;
1044 
1045 	parseAname(m->t.aname, &fsname, &path);
1046 	if((fsys = fsysGet(fsname)) == nil){
1047 		vtfree(fsname);
1048 		return 0;
1049 	}
1050 	vtfree(fsname);
1051 
1052 	if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
1053 		m->con->aok = 1;
1054 		werrstr("authentication disabled");
1055 		fsysPut(fsys);
1056 		return 0;
1057 	}
1058 	if(strcmp(m->t.uname, unamenone) == 0){
1059 		werrstr("user 'none' requires no authentication");
1060 		fsysPut(fsys);
1061 		return 0;
1062 	}
1063 
1064 	con = m->con;
1065 	if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){
1066 		fsysPut(fsys);
1067 		return 0;
1068 	}
1069 	afid->fsys = fsys;
1070 
1071 	if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
1072 		werrstr("can't open \"/mnt/factotum/rpc\"");
1073 		fidClunk(afid);
1074 		return 0;
1075 	}
1076 	if((afid->rpc = auth_allocrpc(afd)) == nil){
1077 		close(afd);
1078 		werrstr("can't auth_allocrpc");
1079 		fidClunk(afid);
1080 		return 0;
1081 	}
1082 	if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){
1083 		werrstr("can't auth_rpc");
1084 		fidClunk(afid);
1085 		return 0;
1086 	}
1087 
1088 	afid->open = FidOWrite|FidORead;
1089 	afid->qid.type = QTAUTH;
1090 	afid->qid.path = m->t.afid;
1091 	afid->uname = vtstrdup(m->t.uname);
1092 
1093 	m->r.qid = afid->qid;
1094 
1095 	fidPut(afid);
1096 	return 1;
1097 }
1098 
1099 static int
rTversion(Msg * m)1100 rTversion(Msg* m)
1101 {
1102 	int v;
1103 	Con *con;
1104 	Fcall *r, *t;
1105 
1106 	t = &m->t;
1107 	r = &m->r;
1108 	con = m->con;
1109 
1110 	qlock(&con->lock);
1111 	if(con->state != ConInit){
1112 		qunlock(&con->lock);
1113 		werrstr("Tversion: down");
1114 		return 0;
1115 	}
1116 	con->state = ConNew;
1117 
1118 	/*
1119 	 * Release the karma of past lives and suffering.
1120 	 * Should this be done before or after checking the
1121 	 * validity of the Tversion?
1122 	 */
1123 	fidClunkAll(con);
1124 
1125 	if(t->tag != NOTAG){
1126 		qunlock(&con->lock);
1127 		werrstr("Tversion: invalid tag");
1128 		return 0;
1129 	}
1130 
1131 	if(t->msize < 256){
1132 		qunlock(&con->lock);
1133 		werrstr("Tversion: message size too small");
1134 		return 0;
1135 	}
1136 	if(t->msize < con->msize)
1137 		r->msize = t->msize;
1138 	else
1139 		r->msize = con->msize;
1140 
1141 	r->version = "unknown";
1142 	if(t->version[0] == '9' && t->version[1] == 'P'){
1143 		/*
1144 		 * Currently, the only defined version
1145 		 * is "9P2000"; ignore any later versions.
1146 		 */
1147 		v = strtol(&t->version[2], 0, 10);
1148 		if(v >= 2000){
1149 			r->version = VERSION9P;
1150 			con->msize = r->msize;
1151 			con->state = ConUp;
1152 		}
1153 		else if(strcmp(t->version, "9PEoF") == 0){
1154 			r->version = "9PEoF";
1155 			con->msize = r->msize;
1156 			con->state = ConMoribund;
1157 
1158 			/*
1159 			 * Don't want to attempt to write this
1160 			 * message as the connection may be already
1161 			 * closed.
1162 			 */
1163 			m->state = MsgF;
1164 		}
1165 	}
1166 	qunlock(&con->lock);
1167 
1168 	return 1;
1169 }
1170 
1171 int (*rFcall[Tmax])(Msg*) = {
1172 	[Tversion]	= rTversion,
1173 	[Tauth]		= rTauth,
1174 	[Tattach]	= rTattach,
1175 	[Tflush]	= rTflush,
1176 	[Twalk]		= rTwalk,
1177 	[Topen]		= rTopen,
1178 	[Tcreate]	= rTcreate,
1179 	[Tread]		= rTread,
1180 	[Twrite]	= rTwrite,
1181 	[Tclunk]	= rTclunk,
1182 	[Tremove]	= rTremove,
1183 	[Tstat]		= rTstat,
1184 	[Twstat]	= rTwstat,
1185 };
1186