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