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