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