xref: /plan9/sys/src/cmd/ip/ftpd.c (revision a6a9e07217f318acf170f99684a55fba5200524f)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include <ip.h>
6 #include <libsec.h>
7 #include <String.h>
8 
9 #include "glob.h"
10 
11 enum
12 {
13 	/* telnet control character */
14 	Iac=		255,
15 
16 	/* representation types */
17 	Tascii=		0,
18 	Timage=		1,
19 
20 	/* transmission modes */
21 	Mstream=	0,
22 	Mblock=		1,
23 	Mpage=		2,
24 
25 	/* file structure */
26 	Sfile=		0,
27 	Sblock=		1,
28 	Scompressed=	2,
29 
30 	/* read/write buffer size */
31 	Nbuf=		4096,
32 
33 	/* maximum ms we'll wait for a command */
34 	Maxwait=	1000*60*30,		/* inactive for 30 minutes, we hang up */
35 
36 	Maxerr=		128,
37 	Maxpath=	512,
38 };
39 
40 int	abortcmd(char*);
41 int	appendcmd(char*);
42 int	cdupcmd(char*);
43 int	cwdcmd(char*);
44 int	delcmd(char*);
45 int	helpcmd(char*);
46 int	listcmd(char*);
47 int	mdtmcmd(char*);
48 int	mkdircmd(char*);
49 int	modecmd(char*);
50 int	namelistcmd(char*);
51 int	nopcmd(char*);
52 int	passcmd(char*);
53 int	pasvcmd(char*);
54 int	portcmd(char*);
55 int	pwdcmd(char*);
56 int	quitcmd(char*);
57 int	rnfrcmd(char*);
58 int	rntocmd(char*);
59 int	reply(char*, ...);
60 int	restartcmd(char*);
61 int	retrievecmd(char*);
62 int	sizecmd(char*);
63 int	storecmd(char*);
64 int	storeucmd(char*);
65 int	structcmd(char*);
66 int	systemcmd(char*);
67 int	typecmd(char*);
68 int	usercmd(char*);
69 
70 int	dialdata(void);
71 char*	abspath(char*);
72 int	crlfwrite(int, char*, int);
73 int	sodoff(void);
74 int	accessok(char*);
75 
76 typedef struct Cmd	Cmd;
77 struct Cmd
78 {
79 	char	*name;
80 	int	(*f)(char*);
81 	int	needlogin;
82 };
83 
84 Cmd cmdtab[] =
85 {
86 	{ "abor",	abortcmd,	0, },
87 	{ "appe",	appendcmd,	1, },
88 	{ "cdup",	cdupcmd,	1, },
89 	{ "cwd",	cwdcmd,		1, },
90 	{ "dele",	delcmd,		1, },
91 	{ "help",	helpcmd,	0, },
92 	{ "list",	listcmd,	1, },
93 	{ "mdtm",	mdtmcmd,	1, },
94 	{ "mkd",	mkdircmd,	1, },
95 	{ "mode",	modecmd,	0, },
96 	{ "nlst",	namelistcmd,	1, },
97 	{ "noop",	nopcmd,		0, },
98 	{ "pass",	passcmd,	0, },
99 	{ "pasv",	pasvcmd,	1, },
100 	{ "pwd",	pwdcmd,		0, },
101 	{ "port", 	portcmd,	1, },
102 	{ "quit",	quitcmd,	0, },
103 	{ "rest",	restartcmd,	1, },
104 	{ "retr",	retrievecmd,	1, },
105 	{ "rmd",	delcmd,		1, },
106 	{ "rnfr",	rnfrcmd,	1, },
107 	{ "rnto",	rntocmd,	1, },
108 	{ "size", 	sizecmd,	1, },
109 	{ "stor", 	storecmd,	1, },
110 	{ "stou", 	storeucmd,	1, },
111 	{ "stru",	structcmd,	1, },
112 	{ "syst",	systemcmd,	0, },
113 	{ "type", 	typecmd,	0, },
114 	{ "user",	usercmd,	0, },
115 	{ 0, 0, 0 },
116 };
117 
118 #define NONENS "/lib/namespace.ftp"	/* default ns for none */
119 
120 char	user[Maxpath];		/* logged in user */
121 char	curdir[Maxpath];	/* current directory path */
122 Chalstate	*ch;
123 int	loggedin;
124 int	type;			/* transmission type */
125 int	mode;			/* transmission mode */
126 int	structure;		/* file structure */
127 char	data[64];		/* data address */
128 int	pid;			/* transfer process */
129 int	encryption;		/* encryption state */
130 int	isnone, anon_ok, anon_only;
131 char	cputype[Maxpath];	/* the environment variable of the same name */
132 char	bindir[Maxpath];	/* bin directory for this architecture */
133 char	mailaddr[Maxpath];
134 char	*namespace = NONENS;
135 int	debug;
136 NetConnInfo	*nci;
137 int	createperm = 0660;
138 int	isnoworld;
139 vlong	offset;			/* from restart command */
140 
141 ulong id;
142 
143 typedef struct Passive Passive;
144 struct Passive
145 {
146 	int	inuse;
147 	char	adir[40];
148 	int	afd;
149 	int	port;
150 	uchar	ipaddr[IPaddrlen];
151 } passive;
152 
153 #define FTPLOG "ftp"
154 
155 void
156 logit(char *fmt, ...)
157 {
158 	char buf[8192];
159 	va_list arg;
160 	char errstr[128];
161 
162 	rerrstr(errstr, sizeof errstr);
163 	va_start(arg, fmt);
164 	vseprint(buf, buf+sizeof(buf), fmt, arg);
165 	va_end(arg);
166 	syslog(0, FTPLOG, "%s.%s %s", nci->rsys, nci->rserv, buf);
167 	werrstr(errstr, sizeof errstr);
168 }
169 
170 /*
171  *  read commands from the control stream and dispatch
172  */
173 void
174 main(int argc, char **argv)
175 {
176 	char *cmd;
177 	char *arg;
178 	char *p;
179 	Cmd *t;
180 	Biobuf in;
181 	int i;
182 
183 	ARGBEGIN{
184 	case 'd':
185 		debug++;
186 		break;
187 	case 'a':		/* anonymous OK */
188 		anon_ok = 1;
189 		break;
190 	case 'A':
191 		anon_ok = 1;
192 		anon_only = 1;
193 		break;
194 	case 'n':
195 		namespace = ARGF();
196 		break;
197 	}ARGEND
198 
199 	/* open log file before doing a newns */
200 	syslog(0, FTPLOG, nil);
201 
202 	/* find out who is calling */
203 	if(argc < 1)
204 		nci = getnetconninfo(nil, 0);
205 	else
206 		nci = getnetconninfo(argv[argc-1], 0);
207 	if(nci == nil)
208 		sysfatal("ftpd needs a network address");
209 
210 	strcpy(mailaddr, "?");
211 	id = getpid();
212 
213 	/* figure out which binaries to bind in later */
214 	arg = getenv("cputype");
215 	if(arg)
216 		strcpy(cputype, arg);
217 	else
218 		strcpy(cputype, "mips");
219 	snprint(bindir, sizeof(bindir), "/bin/%s/bin", cputype);
220 
221 	Binit(&in, 0, OREAD);
222 	reply("220 Plan 9 FTP server ready");
223 	alarm(Maxwait);
224 	while(cmd = Brdline(&in, '\n')){
225 		alarm(0);
226 
227 		/*
228 		 *  strip out trailing cr's & lf and delimit with null
229 		 */
230 		i = Blinelen(&in)-1;
231 		cmd[i] = 0;
232 		if(debug)
233 			logit("%s", cmd);
234 		while(i > 0 && cmd[i-1] == '\r')
235 			cmd[--i] = 0;
236 
237 		/*
238 		 *  hack for GatorFTP+, look for a 0x10 used as a delimiter
239 		 */
240 		p = strchr(cmd, 0x10);
241 		if(p)
242 			*p = 0;
243 
244 		/*
245 		 *  get rid of telnet control sequences (we don't need them)
246 		 */
247 		while(*cmd && *cmd == Iac){
248 			cmd++;
249 			if(*cmd)
250 				cmd++;
251 		}
252 
253 		/*
254 		 *  parse the message (command arg)
255 		 */
256 		arg = strchr(cmd, ' ');
257 		if(arg){
258 			*arg++ = 0;
259 			while(*arg == ' ')
260 				arg++;
261 		}
262 
263 		/*
264 		 *  ignore blank commands
265 		 */
266 		if(*cmd == 0)
267 			continue;
268 
269 		/*
270 		 *  lookup the command and do it
271 		 */
272 		for(p = cmd; *p; p++)
273 			*p = tolower(*p);
274 		for(t = cmdtab; t->name; t++)
275 			if(strcmp(cmd, t->name) == 0){
276 				if(t->needlogin && !loggedin)
277 					sodoff();
278 				else if((*t->f)(arg) < 0)
279 					exits(0);
280 				break;
281 			}
282 		if(t->f != restartcmd){
283 			/*
284 			 *  the file offset is set to zero following
285 			 *  all commands except the restart command
286 			 */
287 			offset = 0;
288 		}
289 		if(t->name == 0){
290 			/*
291 			 *  the OOB bytes preceding an abort from UCB machines
292 			 *  comes out as something unrecognizable instead of
293 			 *  IAC's.  Certainly a Plan 9 bug but I can't find it.
294 			 *  This is a major hack to avoid the problem. -- presotto
295 			 */
296 			i = strlen(cmd);
297 			if(i > 4 && strcmp(cmd+i-4, "abor") == 0){
298 				abortcmd(0);
299 			} else{
300 				logit("%s (%s) command not implemented", cmd, arg?arg:"");
301 				reply("502 %s command not implemented", cmd);
302 			}
303 		}
304 		alarm(Maxwait);
305 	}
306 	if(pid)
307 		postnote(PNGROUP, pid, "kill");
308 }
309 
310 /*
311  *  reply to a command
312  */
313 int
314 reply(char *fmt, ...)
315 {
316 	va_list arg;
317 	char buf[8192], *s;
318 
319 	va_start(arg, fmt);
320 	s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg);
321 	va_end(arg);
322 	if(debug){
323 		*s = 0;
324 		logit("%s", buf);
325 	}
326 	*s++ = '\r';
327 	*s++ = '\n';
328 	write(1, buf, s - buf);
329 	return 0;
330 }
331 
332 int
333 sodoff(void)
334 {
335 	return reply("530 Sod off, service requires login");
336 }
337 
338 /*
339  *  run a command in a separate process
340  */
341 int
342 asproc(void (*f)(char*, int), char *arg, int arg2)
343 {
344 	int i;
345 
346 	if(pid){
347 		/* wait for previous command to finish */
348 		for(;;){
349 			i = waitpid();
350 			if(i == pid || i < 0)
351 				break;
352 		}
353 	}
354 
355 	switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
356 	case -1:
357 		return reply("450 Out of processes: %r");
358 	case 0:
359 		(*f)(arg, arg2);
360 		exits(0);
361 	default:
362 		break;
363 	}
364 	return 0;
365 }
366 
367 /*
368  * run a command to filter a tail
369  */
370 int
371 transfer(char *cmd, char *a1, char *a2, char *a3, int image)
372 {
373 	int n, dfd, fd, bytes, eofs, pid;
374 	int pfd[2];
375 	char buf[Nbuf], *p;
376 	Waitmsg *w;
377 
378 	reply("150 Opening data connection for %s (%s)", cmd, data);
379 	dfd = dialdata();
380 	if(dfd < 0)
381 		return reply("425 Error opening data connection: %r");
382 
383 	if(pipe(pfd) < 0)
384 		return reply("520 Internal Error: %r");
385 
386 	bytes = 0;
387 	switch(pid = rfork(RFFDG|RFPROC|RFNAMEG)){
388 	case -1:
389 		return reply("450 Out of processes: %r");
390 	case 0:
391 logit("running tar %d\n", getpid());
392 		close(pfd[1]);
393 		close(dfd);
394 		dup(pfd[0], 1);
395 		dup(pfd[0], 2);
396 		if(isnone){
397 			fd = open("#s/boot", ORDWR);
398 			if(fd < 0
399 			|| bind("#/", "/", MAFTER) < 0
400 			|| amount(fd, "/bin", MREPL, "") < 0
401 			|| bind("#c", "/dev", MAFTER) < 0
402 			|| bind(bindir, "/bin", MREPL) < 0)
403 				exits("building name space");
404 			close(fd);
405 		}
406 		execl(cmd, cmd, a1, a2, a3, 0);
407 		exits(cmd);
408 	default:
409 		close(pfd[0]);
410 		eofs = 0;
411 		while((n = read(pfd[1], buf, sizeof buf)) >= 0){
412 			if(n == 0){
413 				if(eofs++ > 5)
414 					break;
415 				else
416 					continue;
417 			}
418 			eofs = 0;
419 			p = buf;
420 			if(offset > 0){
421 				if(n > offset){
422 					p = buf+offset;
423 					n -= offset;
424 					offset = 0;
425 				} else {
426 					offset -= n;
427 					continue;
428 				}
429 			}
430 			if(!image)
431 				n = crlfwrite(dfd, p, n);
432 			else
433 				n = write(dfd, p, n);
434 			if(n < 0){
435 				postnote(PNPROC, pid, "kill");
436 				bytes = -1;
437 				break;
438 			}
439 			bytes += n;
440 		}
441 		close(pfd[1]);
442 		close(dfd);
443 		break;
444 	}
445 
446 	/* wait for this command to finish */
447 	for(;;){
448 		w = wait();
449 		if(w == nil || w->pid == pid)
450 			break;
451 		free(w);
452 	}
453 	if(w != nil && w->msg != nil){
454 		bytes = -1;
455 		logit("%s", w->msg);
456 		logit("%s %s %s %s failed %s", cmd, a1?a1:"", a2?a2:"" , a3?a3:"", w->msg);
457 	}
458 	free(w);
459 	reply("226 Transfer complete");
460 	return bytes;
461 }
462 
463 
464 /*
465  *  just reply OK
466  */
467 int
468 nopcmd(char *arg)
469 {
470 	USED(arg);
471 	reply("510 Plan 9 FTP daemon still alive");
472 	return 0;
473 }
474 
475 /*
476  *  login as user
477  */
478 int
479 loginuser(char *user, char *nsfile, int gotoslash)
480 {
481 	logit("login %s %s %s %s", user, mailaddr, nci->rsys, nsfile);
482 	if(nsfile != nil && newns(user, nsfile) < 0){
483 		logit("namespace file %s does not exist", nsfile);
484 		return reply("530 Not logged in: login out of service");
485 		return -1;
486 	}
487 	getwd(curdir, sizeof(curdir));
488 	if(gotoslash){
489 		chdir("/");
490 		strcpy(curdir, "/");
491 	}
492 	putenv("service", "ftp");
493 	loggedin = 1;
494 	if(debug == 0)
495 		reply("230- If you have problems, send mail to 'postmaster'.");
496 	return reply("230 Logged in");
497 }
498 
499 /*
500  *  get a user id, reply with a challenge.  The users 'anonymous'
501  *  and 'ftp' are equivalent to 'none'.  The user 'none' requires
502  *  no challenge.
503  */
504 int
505 usercmd(char *name)
506 {
507 	logit("user %s %s", name, nci->rsys);
508 	if(loggedin)
509 		return reply("530 Already logged in as %s", user);
510 	if(name == 0 || *name == 0)
511 		return reply("530 user command needs user name");
512 	isnoworld = 0;
513 	if(*name == ':'){
514 		debug = 1;
515 		name++;
516 	}
517 	strncpy(user, name, sizeof(user));
518 	if(debug)
519 		logit("debugging");
520 	user[sizeof(user)-1] = 0;
521 	if(strcmp(user, "anonymous") == 0 || strcmp(user, "ftp") == 0)
522 		strcpy(user, "none");
523 	if(strcmp(user, "*none") == 0){
524 		if(!anon_ok)
525 			return reply("530 Not logged in: anonymous disallowed");
526 		return loginuser("none", namespace, 1);
527 	}
528 	if(strcmp(user, "none") == 0){
529 		if(!anon_ok)
530 			return reply("530 Not logged in: anonymous disallowed");
531 		return reply("331 Send email address as password");
532 	}
533 	if(anon_only)
534 		return reply("530 Not logged in: anonymous access only");
535 	isnoworld = noworld(name);
536 	if(isnoworld)
537 		return reply("331 OK");
538 	if(ch)
539 		auth_freechal(ch);
540 	if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
541 		return reply("421 %r");
542 	return reply("331 encrypt challenge, %s, as a password", ch->chal);
543 }
544 
545 /*
546  *  get a password, set up user if it works.
547  */
548 int
549 passcmd(char *response)
550 {
551 	char namefile[128];
552 	AuthInfo *ai;
553 
554 	if(response == nil)
555 		response = "";
556 
557 	if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
558 		/* for none, accept anything as a password */
559 		isnone = 1;
560 		strncpy(mailaddr, response, sizeof(mailaddr)-1);
561 		return loginuser("none", namespace, 1);
562 	}
563 
564 	if(isnoworld){
565 		/* noworld gets a password in the clear */
566 		if(login(user, response, "/lib/namespace.noworld") < 0)
567 			return reply("530 Not logged in");
568 		createperm = 0664;
569 		/* login has already setup the namespace */
570 		return loginuser(user, nil, 0);
571 	} else {
572 		/* for everyone else, do challenge response */
573 		if(ch == nil)
574 			return reply("531 Send user id before encrypted challenge");
575 		ch->resp = response;
576 		ch->nresp = strlen(response);
577 		ai = auth_response(ch);
578 		if(ai == nil)
579 			return reply("530 Not logged in: %r");
580 		if(auth_chuid(ai, nil) < 0)
581 			return reply("530 Not logged in: %r");
582 		auth_freechal(ch);
583 		ch = nil;
584 
585 		/* if the user has specified a namespace for ftp, use it */
586 		snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
587 		strcpy(mailaddr, user);
588 		createperm = 0660;
589 		if(access(namefile, 0) == 0)
590 			return loginuser(user, namefile, 0);
591 		else
592 			return loginuser(user, "/lib/namespace", 0);
593 	}
594 }
595 
596 /*
597  *  print working directory
598  */
599 int
600 pwdcmd(char *arg)
601 {
602 	if(arg)
603 		return reply("550 Pwd takes no argument");
604 	return reply("257 \"%s\" is the current directory", curdir);
605 }
606 
607 /*
608  *  chdir
609  */
610 int
611 cwdcmd(char *dir)
612 {
613 	char *rp;
614 	char buf[Maxpath];
615 
616 	/* shell cd semantics */
617 	if(dir == 0 || *dir == 0){
618 		if(isnone)
619 			rp = "/";
620 		else {
621 			snprint(buf, sizeof buf, "/usr/%s", user);
622 			rp = buf;
623 		}
624 		if(accessok(rp) == 0)
625 			rp = nil;
626 	} else
627 		rp = abspath(dir);
628 
629 	if(rp == nil)
630 		return reply("550 Permission denied");
631 
632 	if(chdir(rp) < 0)
633 		return reply("550 Cwd failed: %r");
634 	strcpy(curdir, rp);
635 	return reply("250 directory changed to %s", curdir);
636 }
637 
638 /*
639  *  chdir ..
640  */
641 int
642 cdupcmd(char *dp)
643 {
644 	USED(dp);
645 	return cwdcmd("..");
646 }
647 
648 int
649 quitcmd(char *arg)
650 {
651 	USED(arg);
652 	reply("200 Bye");
653 	if(pid)
654 		postnote(PNGROUP, pid, "kill");
655 	return -1;
656 }
657 
658 int
659 typecmd(char *arg)
660 {
661 	int c;
662 	char *x;
663 
664 	x = arg;
665 	if(arg == 0)
666 		return reply("501 Type command needs arguments");
667 
668 	while(c = *arg++){
669 		switch(tolower(c)){
670 		case 'a':
671 			type = Tascii;
672 			break;
673 		case 'i':
674 		case 'l':
675 			type = Timage;
676 			break;
677 		case '8':
678 		case ' ':
679 		case 'n':
680 		case 't':
681 		case 'c':
682 			break;
683 		default:
684 			return reply("501 Unimplemented type %s", x);
685 			break;
686 		}
687 	}
688 	return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
689 }
690 
691 int
692 modecmd(char *arg)
693 {
694 	if(arg == 0)
695 		return reply("501 Mode command needs arguments");
696 	while(*arg){
697 		switch(tolower(*arg)){
698 		case 's':
699 			mode = Mstream;
700 			break;
701 		default:
702 			return reply("501 Unimplemented mode %c", *arg);
703 			break;
704 		}
705 		arg++;
706 	}
707 	return reply("200 Stream mode");
708 }
709 
710 int
711 structcmd(char *arg)
712 {
713 	if(arg == 0)
714 		return reply("501 Struct command needs arguments");
715 	for(; *arg; arg++){
716 		switch(tolower(*arg)){
717 		case 'f':
718 			structure = Sfile;
719 			break;
720 		default:
721 			return reply("501 Unimplemented structure %c", *arg);
722 			break;
723 		}
724 	}
725 	return reply("200 File structure");
726 }
727 
728 int
729 portcmd(char *arg)
730 {
731 	char *field[7];
732 	int n;
733 
734 	if(arg == 0)
735 		return reply("501 Port command needs arguments");
736 	n = getfields(arg, field, 7, 0, ", ");
737 	if(n != 6)
738 		return reply("501 Incorrect port specification");
739 	snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
740 		field[3], atoi(field[4])*256 + atoi(field[5]));
741 	return reply("200 Data port is %s", data);
742 }
743 
744 int
745 mountnet(void)
746 {
747 	int rv;
748 
749 	rv = 0;
750 
751 	if(bind("#/", "/", MAFTER) < 0){
752 		logit("can't bind #/ to /: %r");
753 		return reply("500 can't bind #/ to /: %r");
754 	}
755 
756 	if(bind(nci->spec, "/net", MBEFORE) < 0){
757 		logit("can't bind %s to /net: %r", nci->spec);
758 		rv = reply("500 can't bind %s to /net: %r", nci->spec);
759 		unmount("#/", "/");
760 	}
761 
762 	return rv;
763 }
764 
765 void
766 unmountnet(void)
767 {
768 	unmount(0, "/net");
769 	unmount("#/", "/");
770 }
771 
772 int
773 pasvcmd(char *arg)
774 {
775 	NetConnInfo *nnci;
776 	Passive *p;
777 
778 	USED(arg);
779 	p = &passive;
780 
781 	if(p->inuse){
782 		close(p->afd);
783 		p->inuse = 0;
784 	}
785 
786 	if(mountnet() < 0)
787 		return 0;
788 
789 	p->afd = announce("tcp!*!0", passive.adir);
790 	if(p->afd < 0){
791 		unmountnet();
792 		return reply("500 No free ports");
793 	}
794 	nnci = getnetconninfo(p->adir, -1);
795 	unmountnet();
796 
797 	/* parse the local address */
798 	if(debug)
799 		logit("local sys is %s", nci->lsys);
800 	parseip(p->ipaddr, nci->lsys);
801 	if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
802 		parseip(p->ipaddr, nci->lsys);
803 	p->port = atoi(nnci->lserv);
804 
805 	freenetconninfo(nnci);
806 	p->inuse = 1;
807 
808 	return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
809 		p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
810 		p->port>>8, p->port&0xff);
811 }
812 
813 enum
814 {
815 	Narg=32,
816 };
817 int Cflag, rflag, tflag, Rflag;
818 int maxnamelen;
819 int col;
820 
821 char*
822 mode2asc(int m)
823 {
824 	static char asc[12];
825 	char *p;
826 
827 	strcpy(asc, "----------");
828 	if(DMDIR & m)
829 		asc[0] = 'd';
830 	if(DMAPPEND & m)
831 		asc[0] = 'a';
832 	else if(DMEXCL & m)
833 		asc[3] = 'l';
834 
835 	for(p = asc+1; p < asc + 10; p += 3, m<<=3){
836 		if(m & 0400)
837 			p[0] = 'r';
838 		if(m & 0200)
839 			p[1] = 'w';
840 		if(m & 0100)
841 			p[2] = 'x';
842 	}
843 	return asc;
844 }
845 void
846 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
847 {
848 	char ts[32];
849 	char buf[256];
850 	int n, links;
851 	long now;
852 	char *x;
853 	Dir *d;
854 
855 	x = abspath(name);
856 	if(x == nil)
857 		return;
858 	d = dirstat(x);
859 	if(d == nil)
860 		return;
861 	if(isnone){
862 		if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
863 			d->mode &= ~0222;
864 		strcpy(d->uid, "none");
865 		strcpy(d->gid, "none");
866 	}
867 
868 	strcpy(ts, ctime(d->mtime));
869 	ts[16] = 0;
870 	now = time(0);
871 	if(now - d->mtime > 6*30*24*60*60)
872 		memmove(ts+11, ts+23, 5);
873 	if(lflag){
874 		/* Unix style long listing */
875 		if(DMDIR&d->mode){
876 			links = 2;
877 			d->length = 512;
878 		} else
879 			links = 1;
880 
881 		n = snprint(buf, sizeof(buf), "%s %3d %-8s %-8s %7lld %s ", mode2asc(d->mode), links,
882 			d->uid, d->gid, d->length, ts+4);
883 	} else
884 		n = 0;
885 	if(dname)
886 		n += snprint(buf+n, sizeof(buf)-n, "%s/", dname);
887 	n += snprint(buf+n, sizeof(buf)-n, "%s", name);
888 	if(Cflag && col + maxnamelen + 1 < 40){
889 		if(n < maxnamelen+1){
890 			memset(buf+n, ' ', maxnamelen+1-n);
891 			buf[maxnamelen+1] = 0;
892 		}
893 		col += maxnamelen + 1;
894 	} else {
895 		n += snprint(buf+n, sizeof(buf)-n, "\r\n");
896 		col = 0;
897 	}
898 	Bwrite(b, buf, n);
899 	free(d);
900 }
901 int
902 dircomp(void *va, void *vb)
903 {
904 	int rv;
905 	Dir *a, *b;
906 
907 	a = va;
908 	b = vb;
909 
910 	if(tflag)
911 		rv = b->mtime - a->mtime;
912 	else
913 		rv = strcmp(a->name, b->name);
914 	return (rflag?-1:1)*rv;
915 }
916 void
917 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
918 {
919 	Dir *p;
920 	int fd, n, i;
921 	char *dname;
922 	uvlong total;
923 
924 	col = 0;
925 
926 	fd = open(name, OREAD);
927 	if(fd < 0){
928 		Bprint(b, "can't read %s: %r\r\n", name);
929 		return;
930 	}
931 	dname = 0;
932 	if(*printname){
933 		if(Rflag || lflag)
934 			Bprint(b, "\r\n%s:\r\n", name);
935 		else
936 			dname = name;
937 	}
938 	n = dirreadall(fd, &p);
939 	close(fd);
940 	if(Cflag){
941 		for(i = 0; i < n; i++)
942 			if(strlen(p[i].name) > maxnamelen)
943 				maxnamelen = strlen(p[i].name);
944 	}
945 
946 	/* Unix style total line */
947 	if(lflag){
948 		total = 0;
949 		for(i = 0; i < n; i++){
950 			if(p[i].qid.type & QTDIR)
951 				total += 512;
952 			else
953 				total += p[i].length;
954 		}
955 		Bprint(b, "total %ulld\r\n", total/512);
956 	}
957 
958 	qsort(p, n, sizeof(Dir), dircomp);
959 	for(i = 0; i < n; i++){
960 		if(Rflag && (p[i].qid.type & QTDIR)){
961 			*printname = 1;
962 			globadd(gl, name, p[i].name);
963 		}
964 		listfile(b, p[i].name, lflag, dname);
965 	}
966 	free(p);
967 }
968 void
969 list(char *arg, int lflag)
970 {
971 	Dir *d;
972 	Globlist *gl;
973 	Glob *g;
974 	int dfd, printname;
975 	int i, n, argc;
976 	char *alist[Narg];
977 	char path[Maxpath];
978 	char **argv;
979 	Biobufhdr bh;
980 	uchar buf[512];
981 	char *p;
982 
983 	if(arg == 0)
984 		arg = "";
985 
986 	if(debug)
987 		logit("ls %s (. = %s)", arg, curdir);
988 
989 	/* process arguments, understand /bin/ls -l option */
990 	argv = alist;
991 	argv[0] = "/bin/ls";
992 	argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
993 	argv[argc] = 0;
994 	rflag = 0;
995 	tflag = 0;
996 	Rflag = 0;
997 	Cflag = 0;
998 	col = 0;
999 	ARGBEGIN{
1000 	case 'l':
1001 		lflag++;
1002 		break;
1003 	case 'R':
1004 		Rflag++;
1005 		break;
1006 	case 'C':
1007 		Cflag++;
1008 		break;
1009 	case 'r':
1010 		rflag++;
1011 		break;
1012 	case 't':
1013 		tflag++;
1014 		break;
1015 	}ARGEND;
1016 	if(Cflag)
1017 		lflag = 0;
1018 
1019 	dfd = dialdata();
1020 	if(dfd < 0){
1021 		reply("425 Error opening data connection:%r");
1022 		return;
1023 	}
1024 	reply("150 Opened data connection (%s)", data);
1025 
1026 	Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1027 	if(argc == 0){
1028 		argc = 1;
1029 		argv = alist;
1030 		argv[0] = ".";
1031 	}
1032 	for(i = 0; i < argc; i++){
1033 		chdir(curdir);
1034 		strncpy(path, argv[i], sizeof(path));
1035 		path[sizeof(path)-1] = 0;
1036 		gl = glob(path);
1037 		if(gl == nil)
1038 			continue;
1039 
1040 		printname = gl->first != nil && gl->first->next != nil;
1041 		maxnamelen = 8;
1042 
1043 		if(Cflag)
1044 			for(g = gl->first; g; g = g->next)
1045 				if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1046 					maxnamelen = n;
1047 
1048 		for(g = gl->first; g; g = g->next){
1049 			if(debug)
1050 				logit("glob %s", s_to_c(g->glob));
1051 			p = abspath(s_to_c(g->glob));
1052 			if(p == nil)
1053 				continue;
1054 			d = dirstat(p);
1055 			if(d == nil)
1056 				continue;
1057 			if(d->qid.type & QTDIR)
1058 				listdir(s_to_c(g->glob), &bh, lflag, &printname, gl);
1059 			else
1060 				listfile(&bh, s_to_c(g->glob), lflag, 0);
1061 			free(d);
1062 		}
1063 		globlistfree(gl);
1064 	}
1065 	if(Cflag)
1066 		Bprint(&bh, "\r\n");
1067 	Bflush(&bh);
1068 	close(dfd);
1069 
1070 	reply("226 Transfer complete (list %s)", arg);
1071 }
1072 int
1073 namelistcmd(char *arg)
1074 {
1075 	return asproc(list, arg, 0);
1076 }
1077 int
1078 listcmd(char *arg)
1079 {
1080 	return asproc(list, arg, 1);
1081 }
1082 
1083 /*
1084  *  return the size of the file
1085  */
1086 int
1087 sizecmd(char *arg)
1088 {
1089 	Dir *d;
1090 	int rv;
1091 
1092 	if(arg == 0)
1093 		return reply("501 Size command requires pathname");
1094 	arg = abspath(arg);
1095 	d = dirstat(arg);
1096 	if(d == nil)
1097 		return reply("501 %r accessing %s", arg);
1098 	rv = reply("213 %lld", d->length);
1099 	free(d);
1100 	return rv;
1101 }
1102 
1103 /*
1104  *  return the modify time of the file
1105  */
1106 int
1107 mdtmcmd(char *arg)
1108 {
1109 	Dir *d;
1110 	Tm *t;
1111 	int rv;
1112 
1113 	if(arg == 0)
1114 		return reply("501 Mdtm command requires pathname");
1115 	if(arg == 0)
1116 		return reply("550 Permission denied");
1117 	d = dirstat(arg);
1118 	if(d == nil)
1119 		return reply("501 %r accessing %s", arg);
1120 	t = gmtime(d->mtime);
1121 	rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1122 			t->year+1900, t->mon+1, t->mday,
1123 			t->hour, t->min, t->sec);
1124 	free(d);
1125 	return rv;
1126 }
1127 
1128 /*
1129  *  set an offset to start reading a file from
1130  *  only lasts for one command
1131  */
1132 int
1133 restartcmd(char *arg)
1134 {
1135 	if(arg == 0)
1136 		return reply("501 Restart command requires offset");
1137 	offset = atoll(arg);
1138 	if(offset < 0){
1139 		offset = 0;
1140 		return reply("501 Bad offset");
1141 	}
1142 
1143 	return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1144 }
1145 
1146 /*
1147  *  send a file to the user
1148  */
1149 int
1150 crlfwrite(int fd, char *p, int n)
1151 {
1152 	char *ep, *np;
1153 	char buf[2*Nbuf];
1154 
1155 	for(np = buf, ep = p + n; p < ep; p++){
1156 		if(*p == '\n')
1157 			*np++ = '\r';
1158 		*np++ = *p;
1159 	}
1160 	if(write(fd, buf, np - buf) == np - buf)
1161 		return n;
1162 	else
1163 		return -1;
1164 }
1165 void
1166 retrievedir(char *arg)
1167 {
1168 	int n;
1169 	char *p;
1170 	char file[Maxpath];
1171 
1172 	if(type != Timage){
1173 		reply("550 This file requires type binary/image");
1174 		return;
1175 	}
1176 
1177 	strcpy(file, arg);
1178 	p = strrchr(arg, '/');
1179 	if(p != arg){
1180 		*p++ = 0;
1181 		chdir(arg);
1182 	} else {
1183 		chdir("/");
1184 		p = arg+1;
1185 	}
1186 
1187 	n = transfer("/bin/tar", "c", p, 0, 1);
1188 	if(n < 0)
1189 		logit("get %s failed", file);
1190 	else
1191 		logit("get %s OK %d", file, n);
1192 }
1193 void
1194 retrieve(char *arg, int arg2)
1195 {
1196 	int dfd, fd, n, i, bytes;
1197 	Dir *d;
1198 	char buf[Nbuf];
1199 	char *p, *ep;
1200 
1201 	USED(arg2);
1202 
1203 	p = strchr(arg, '\r');
1204 	if(p){
1205 		logit("cr in file name", arg);
1206 		*p = 0;
1207 	}
1208 
1209 	fd = open(arg, OREAD);
1210 	if(fd == -1){
1211 		n = strlen(arg);
1212 		if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1213 			*(arg+n-4) = 0;
1214 			d = dirstat(arg);
1215 			if(d != nil){
1216 				if(d->qid.type & QTDIR){
1217 					retrievedir(arg);
1218 					free(d);
1219 					return;
1220 				}
1221 				free(d);
1222 			}
1223 		}
1224 		logit("get %s failed", arg);
1225 		reply("550 Error opening %s: %r", arg);
1226 		return;
1227 	}
1228 	if(offset != 0)
1229 		if(seek(fd, offset, 0) < 0){
1230 			reply("550 %s: seek to %lld failed", arg, offset);
1231 			close(fd);
1232 			return;
1233 		}
1234 	d = dirfstat(fd);
1235 	if(d != nil){
1236 		if(d->qid.type & QTDIR){
1237 			reply("550 %s: not a plain file.", arg);
1238 			close(fd);
1239 			free(d);
1240 			return;
1241 		}
1242 		free(d);
1243 	}
1244 
1245 	n = read(fd, buf, sizeof(buf));
1246 	if(n < 0){
1247 		logit("get %s failed", arg, mailaddr, nci->rsys);
1248 		reply("550 Error reading %s: %r", arg);
1249 		close(fd);
1250 		return;
1251 	}
1252 
1253 	if(type != Timage)
1254 		for(p = buf, ep = &buf[n]; p < ep; p++)
1255 			if(*p & 0x80){
1256 				close(fd);
1257 				reply("550 This file requires type binary/image");
1258 				return;
1259 			}
1260 
1261 	reply("150 Opening data connection for %s (%s)", arg, data);
1262 	dfd = dialdata();
1263 	if(dfd < 0){
1264 		reply("425 Error opening data connection:%r");
1265 		close(fd);
1266 		return;
1267 	}
1268 
1269 	bytes = 0;
1270 	do {
1271 		switch(type){
1272 		case Timage:
1273 			i = write(dfd, buf, n);
1274 			break;
1275 		default:
1276 			i = crlfwrite(dfd, buf, n);
1277 			break;
1278 		}
1279 		if(i != n){
1280 			close(fd);
1281 			close(dfd);
1282 			logit("get %s %r to data connection after %d", arg, bytes);
1283 			reply("550 Error writing to data connection: %r");
1284 			return;
1285 		}
1286 		bytes += n;
1287 	} while((n = read(fd, buf, sizeof(buf))) > 0);
1288 
1289 	if(n < 0)
1290 		logit("get %s %r after %d", arg, bytes);
1291 
1292 	close(fd);
1293 	close(dfd);
1294 	reply("226 Transfer complete");
1295 	logit("get %s OK %d", arg, bytes);
1296 }
1297 int
1298 retrievecmd(char *arg)
1299 {
1300 	if(arg == 0)
1301 		return reply("501 Retrieve command requires an argument");
1302 	arg = abspath(arg);
1303 	if(arg == 0)
1304 		return reply("550 Permission denied");
1305 
1306 	return asproc(retrieve, arg, 0);
1307 }
1308 
1309 /*
1310  *  get a file from the user
1311  */
1312 int
1313 lfwrite(int fd, char *p, int n)
1314 {
1315 	char *ep, *np;
1316 	char buf[Nbuf];
1317 
1318 	for(np = buf, ep = p + n; p < ep; p++){
1319 		if(*p != '\r')
1320 			*np++ = *p;
1321 	}
1322 	if(write(fd, buf, np - buf) == np - buf)
1323 		return n;
1324 	else
1325 		return -1;
1326 }
1327 void
1328 store(char *arg, int fd)
1329 {
1330 	int dfd, n, i;
1331 	char buf[Nbuf];
1332 
1333 	reply("150 Opening data connection for %s (%s)", arg, data);
1334 	dfd = dialdata();
1335 	if(dfd < 0){
1336 		reply("425 Error opening data connection:%r");
1337 		close(fd);
1338 		return;
1339 	}
1340 
1341 	while((n = read(dfd, buf, sizeof(buf))) > 0){
1342 		switch(type){
1343 		case Timage:
1344 			i = write(fd, buf, n);
1345 			break;
1346 		default:
1347 			i = lfwrite(fd, buf, n);
1348 			break;
1349 		}
1350 		if(i != n){
1351 			close(fd);
1352 			close(dfd);
1353 			reply("550 Error writing file");
1354 			return;
1355 		}
1356 	}
1357 	close(fd);
1358 	close(dfd);
1359 	logit("put %s OK", arg);
1360 	reply("226 Transfer complete");
1361 }
1362 int
1363 storecmd(char *arg)
1364 {
1365 	int fd, rv;
1366 
1367 	if(arg == 0)
1368 		return reply("501 Store command requires an argument");
1369 	arg = abspath(arg);
1370 	if(arg == 0)
1371 		return reply("550 Permission denied");
1372 	if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1373 		return reply("550 Permission denied");
1374 	if(offset){
1375 		fd = open(arg, OWRITE);
1376 		if(fd == -1)
1377 			return reply("550 Error opening %s: %r", arg);
1378 		if(seek(fd, offset, 0) == -1)
1379 			return reply("550 Error seeking %s to %d: %r",
1380 				arg, offset);
1381 	} else {
1382 		fd = create(arg, OWRITE, createperm);
1383 		if(fd == -1)
1384 			return reply("550 Error creating %s: %r", arg);
1385 	}
1386 
1387 	rv = asproc(store, arg, fd);
1388 	close(fd);
1389 	return rv;
1390 }
1391 int
1392 appendcmd(char *arg)
1393 {
1394 	int fd, rv;
1395 
1396 	if(arg == 0)
1397 		return reply("501 Append command requires an argument");
1398 	if(isnone)
1399 		return reply("550 Permission denied");
1400 	arg = abspath(arg);
1401 	if(arg == 0)
1402 		return reply("550 Error creating %s: Permission denied", arg);
1403 	fd = open(arg, OWRITE);
1404 	if(fd == -1){
1405 		fd = create(arg, OWRITE, createperm);
1406 		if(fd == -1)
1407 			return reply("550 Error creating %s: %r", arg);
1408 	}
1409 	seek(fd, 0, 2);
1410 
1411 	rv = asproc(store, arg, fd);
1412 	close(fd);
1413 	return rv;
1414 }
1415 int
1416 storeucmd(char *arg)
1417 {
1418 	int fd, rv;
1419 	char name[Maxpath];
1420 
1421 	USED(arg);
1422 	if(isnone)
1423 		return reply("550 Permission denied");
1424 	strcpy(name, "ftpXXXXXXXXXXX");
1425 	mktemp(name);
1426 	fd = create(name, OWRITE, createperm);
1427 	if(fd == -1)
1428 		return reply("550 Error creating %s: %r", name);
1429 
1430 	rv = asproc(store, name, fd);
1431 	close(fd);
1432 	return rv;
1433 }
1434 
1435 int
1436 mkdircmd(char *name)
1437 {
1438 	int fd;
1439 
1440 	if(name == 0)
1441 		return reply("501 Mkdir command requires an argument");
1442 	if(isnone)
1443 		return reply("550 Permission denied");
1444 	name = abspath(name);
1445 	if(name == 0)
1446 		return reply("550 Permission denied");
1447 	fd = create(name, OREAD, DMDIR|0775);
1448 	if(fd < 0)
1449 		return reply("550 Can't create %s: %r", name);
1450 	close(fd);
1451 	return reply("226 %s created", name);
1452 }
1453 
1454 int
1455 delcmd(char *name)
1456 {
1457 	if(name == 0)
1458 		return reply("501 Rmdir/delete command requires an argument");
1459 	if(isnone)
1460 		return reply("550 Permission denied");
1461 	name = abspath(name);
1462 	if(name == 0)
1463 		return reply("550 Permission denied");
1464 	if(remove(name) < 0)
1465 		return reply("550 Can't remove %s: %r", name);
1466 	else
1467 		return reply("226 %s removed", name);
1468 }
1469 
1470 /*
1471  *  kill off the last transfer (if the process still exists)
1472  */
1473 int
1474 abortcmd(char *arg)
1475 {
1476 	USED(arg);
1477 	if(pid && postnote(PNGROUP, pid, "kill") == 0)
1478 		reply("426 Command aborted");
1479 	return reply("226 Abort processed");
1480 }
1481 
1482 int
1483 systemcmd(char *arg)
1484 {
1485 	USED(arg);
1486 	return reply("215 UNIX Type: L8 Version: Plan 9");
1487 }
1488 
1489 int
1490 helpcmd(char *arg)
1491 {
1492 	int i;
1493 	char buf[80];
1494 	char *p, *e;
1495 
1496 	USED(arg);
1497 	reply("214- the following commands are implemented:");
1498 	p = buf;
1499 	e = buf+sizeof buf;
1500 	for(i = 0; cmdtab[i].name; i++){
1501 		if((i%8) == 0){
1502 			reply("214-%s", buf);
1503 			p = buf;
1504 		}
1505 		p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1506 	}
1507 	if(p != buf)
1508 		reply("214-%s", buf);
1509 	reply("214 ");
1510 	return 0;
1511 }
1512 
1513 /*
1514  *  renaming a file takes two commands
1515  */
1516 static char filepath[256];
1517 
1518 int
1519 rnfrcmd(char *from)
1520 {
1521 	if(isnone)
1522 		return reply("550 Permission denied");
1523 	if(from == 0)
1524 		return reply("501 Rename command requires an argument");
1525 	from = abspath(from);
1526 	if(from == 0)
1527 		return reply("550 Permission denied");
1528 	strncpy(filepath, from, sizeof(filepath));
1529 	filepath[sizeof(filepath)-1] = 0;
1530 	return reply("350 Rename %s to ...", filepath);
1531 }
1532 int
1533 rntocmd(char *to)
1534 {
1535 	Dir nd;
1536 	char *fp;
1537 	char *tp;
1538 
1539 	if(isnone)
1540 		return reply("550 Permission denied");
1541 	if(to == 0)
1542 		return reply("501 Rename command requires an argument");
1543 	to = abspath(to);
1544 	if(to == 0)
1545 		return reply("550 Permission denied");
1546 	if(*filepath == 0)
1547 		return reply("503 Rnto must be preceeded by an rnfr");
1548 
1549 	tp = strrchr(to, '/');
1550 	fp = strrchr(filepath, '/');
1551 	if((tp && fp == 0) || (fp && tp == 0)
1552 	|| (fp && tp && (fp-filepath != tp-to || memcmp(filepath, to, tp-to))))
1553 		return reply("550 Rename can't change directory");
1554 	if(tp)
1555 		to = tp+1;
1556 
1557 	nulldir(&nd);
1558 	nd.name = to;
1559 	if(dirwstat(filepath, &nd) < 0)
1560 		return reply("550 Can't rename %s to %s: %r\n", filepath, to);
1561 
1562 	filepath[0] = 0;
1563 	return reply("250 %s now %s", filepath, to);
1564 }
1565 
1566 /*
1567  *  to dial out we need the network file system in our
1568  *  name space.
1569  */
1570 int
1571 dialdata(void)
1572 {
1573 	int fd, cfd;
1574 	char ldir[40];
1575 	char err[Maxerr];
1576 
1577 	if(mountnet() < 0)
1578 		return -1;
1579 
1580 	if(!passive.inuse){
1581 		fd = dial(data, "20", 0, 0);
1582 		errstr(err, sizeof err);
1583 	} else {
1584 		alarm(5*60*1000);
1585 		cfd = listen(passive.adir, ldir);
1586 		alarm(0);
1587 		errstr(err, sizeof err);
1588 		if(cfd < 0)
1589 			return -1;
1590 		fd = accept(cfd, ldir);
1591 		errstr(err, sizeof err);
1592 		close(cfd);
1593 	}
1594 	if(fd < 0)
1595 		logit("can't dial %s: %s", data, err);
1596 
1597 	unmountnet();
1598 	werrstr(err, sizeof err);
1599 	return fd;
1600 }
1601 
1602 /*
1603  *  to circumscribe the accessible files we have to eliminate ..'s
1604  *  and resolve all names from the root.  We also remove any /bin/rc
1605  *  special characters to avoid later problems with executed commands.
1606  */
1607 char *special = "`;| ";
1608 
1609 char*
1610 abspath(char *origpath)
1611 {
1612 	int n, c;
1613 	char *p, *sp, *path;
1614 	char work[Maxpath];
1615 	static char rpath[Maxpath];
1616 
1617 	if(origpath == 0)
1618 		*work = 0;
1619 	else
1620 		strncpy(work, origpath, sizeof(work));
1621 	path = work;
1622 
1623 	for(sp = special; *sp; sp++){
1624 		p = strchr(path, *sp);
1625 		if(p)
1626 			*p = 0;
1627 	}
1628 
1629 	if(*path == '/')
1630 		rpath[0] = 0;
1631 	else
1632 		strcpy(rpath, curdir);
1633 	n = strlen(rpath);
1634 
1635 	while(*path && n < Maxpath-2){
1636 		p = strchr(path, '/');
1637 		if(p)
1638 			*p++ = 0;
1639 		if(strcmp(path, "..") == 0){
1640 			while(n > 1){
1641 				n--;
1642 				c = rpath[n];
1643 				rpath[n] = 0;
1644 				if(c == '/')
1645 					break;
1646 			}
1647 		} else if(strcmp(path, ".") == 0)
1648 			n = n;
1649 		else if(n == 1)
1650 			n += snprint(rpath+n, Maxpath - n, "%s", path);
1651 		else
1652 			n += snprint(rpath+n, Maxpath - n - 1, "/%s", path);
1653 		if(p)
1654 			path = p;
1655 		else
1656 			break;
1657 	}
1658 
1659 	if(!accessok(rpath))
1660 		return nil;
1661 
1662 	return rpath;
1663 }
1664 
1665 int
1666 postnote(int group, int pid, char *note)
1667 {
1668 	char file[128];
1669 	int f, r;
1670 
1671 	switch(group) {
1672 	case PNPROC:
1673 		sprint(file, "#p/%d/note", pid);
1674 		break;
1675 	case PNGROUP:
1676 		sprint(file, "#p/%d/notepg", pid);
1677 		break;
1678 	default:
1679 		return -1;
1680 	}
1681 
1682 	f = open(file, OWRITE);
1683 	if(f < 0)
1684 		return -1;
1685 
1686 	r = strlen(note);
1687 	if(write(f, note, r) != r) {
1688 		close(f);
1689 		return -1;
1690 	}
1691 	close(f);
1692 	return 0;
1693 }
1694 
1695 static char *pfile = "/.httplogin";
1696 
1697 typedef struct Path Path;
1698 struct Path {
1699 	Path	*next;
1700 	char	*path;
1701 	int	inuse;
1702 	int	ok;
1703 };
1704 
1705 enum
1706 {
1707 	Maxlevel = 16,
1708 	Maxperlevel= 8,
1709 };
1710 
1711 Path *pathlevel[Maxlevel];
1712 
1713 Path*
1714 unlinkpath(char *path, int level)
1715 {
1716 	Path **l, *p;
1717 	int n;
1718 
1719 	n = 0;
1720 	for(l = &pathlevel[level]; *l; l = &(*l)->next){
1721 		p = *l;
1722 		if(strcmp(p->path, path) == 0){
1723 			*l = p->next;
1724 			p->next = nil;
1725 			return p;
1726 		}
1727 		if(++n >= Maxperlevel - 1 || p->next == nil)
1728 			break;
1729 	}
1730 	if(*l){
1731 		/* reuse */
1732 		p = *l;
1733 		*l = p->next;
1734 		free(p->path);
1735 		memset(p, 0, sizeof *p);
1736 
1737 	} else {
1738 		/* allocate */
1739 		p = mallocz(sizeof *p, 1);
1740 	}
1741 	p->path = strdup(path);
1742 	return p;
1743 }
1744 
1745 void
1746 linkpath(Path *p, int level)
1747 {
1748 	p->next = pathlevel[level];
1749 	pathlevel[level] = p;
1750 	p->inuse = 1;
1751 }
1752 
1753 void
1754 addpath(Path *p, int level, int ok)
1755 {
1756 	p->ok = ok;
1757 	p->next = pathlevel[level];
1758 	pathlevel[level] = p;
1759 }
1760 
1761 int
1762 _accessok(char *path, int level)
1763 {
1764 	Path *p;
1765 	char *cp;
1766 	int lvl;
1767 
1768 	if(level < 0)
1769 		return 1;
1770 	lvl = level;
1771 	if(lvl >= Maxlevel)
1772 		lvl = Maxlevel - 1;
1773 
1774 	p = unlinkpath(path, lvl);
1775 	if(p->inuse){
1776 		/* move to front */
1777 		linkpath(p, lvl);
1778 		return p->ok;
1779 	}
1780 	cp = strrchr(path, '/');
1781 	if(cp == nil)
1782 		cp = path;
1783 	p->path = strdup(path);
1784 	strcat(path, pfile);
1785 	if(access(path, AEXIST) == 0){
1786 		addpath(p, lvl, 0);
1787 		return 0;
1788 	}
1789 	*cp = 0;
1790 	addpath(p, lvl, _accessok(path, level-1));
1791 	return p->ok;
1792 }
1793 
1794 /*
1795  *  work down from the root
1796  */
1797 int
1798 accessok(char *path)
1799 {
1800 	int level, n;
1801 	char *p;
1802 	char npath[Maxpath+1];
1803 
1804 	strcpy(npath, path);
1805 	n = strlen(path)-1;
1806 	if(npath[n] == '/')
1807 		npath[n] = 0;
1808 	p = npath+1;
1809 	for(level = 1; p >= path && level < Maxlevel; level++){
1810 		p = strchr(p, '/');
1811 		if(p == nil)
1812 			break;
1813 		p++;
1814 	}
1815 	return _accessok(npath, level-1);
1816 }
1817