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