xref: /plan9-contrib/sys/src/cmd/ip/ftpfs/proto.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ip.h>
5 #include <mp.h>
6 #include <libsec.h>
7 #include <auth.h>
8 #include <fcall.h>
9 #include <ctype.h>
10 #include <String.h>
11 #include "ftpfs.h"
12 
13 enum
14 {
15 	/* return codes */
16 	Extra=		1,
17 	Success=	2,
18 	Incomplete=	3,
19 	TempFail=	4,
20 	PermFail=	5,
21 	Impossible=	6,
22 };
23 
24 Node	*remdir;		/* current directory on remote machine */
25 Node	*remroot;		/* root directory on remote machine */
26 
27 int	ctlfd;			/* fd for control connection */
28 Biobuf	ctlin;			/* input buffer for control connection */
29 Biobuf	stdin;			/* input buffer for standard input */
30 Biobuf	dbuf;			/* buffer for data connection */
31 char	msg[512];		/* buffer for replies */
32 char	net[Maxpath];		/* network for connections */
33 int	listenfd;		/* fd to listen on for connections */
34 char	netdir[Maxpath];
35 int	os, defos;
36 char	topsdir[64];		/* name of listed directory for TOPS */
37 String	*remrootpath;	/* path on remote side to remote root */
38 char	*user;
39 int	nopassive;
40 long	lastsend;
41 extern int usetls;
42 
43 static void	sendrequest(char*, char*);
44 static int	getreply(Biobuf*, char*, int, int);
45 static int	active(int, Biobuf**, char*, char*);
46 static int	passive(int, Biobuf**, char*, char*);
47 static int	data(int, Biobuf**, char*, char*);
48 static int	port(void);
49 static void	ascii(void);
50 static void	image(void);
51 static void	unixpath(Node*, String*);
52 static void	vmspath(Node*, String*);
53 static void	mvspath(Node*, String*);
54 static Node*	vmsdir(char*);
55 static int	getpassword(char*, char*);
56 static int	nw_mode(char dirlet, char *s);
57 
58 /*
59  *  connect to remote server, default network is "tcp/ip"
60  */
61 void
62 hello(char *dest)
63 {
64 	char *p;
65 	char dir[Maxpath];
66 	int ts;
67 	TLSconn conn;
68 
69 	Binit(&stdin, 0, OREAD);	/* init for later use */
70 
71 	ctlfd = dial(netmkaddr(dest, "tcp", "ftp"), 0, dir, 0);
72 	if(ctlfd < 0){
73 		fprint(2, "can't dial %s: %r\n", dest);
74 		exits("dialing");
75 	}
76 
77 	Binit(&ctlin, ctlfd, OREAD);
78 
79 	/* remember network for the data connections */
80 	p = strrchr(dir+1, '/');
81 	if(p == 0)
82 		fatal("wrong dial(2) linked with ftp");
83 	*p = 0;
84 	safecpy(net, dir, sizeof(net));
85 
86 	/* wait for hello from other side */
87 	if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
88 		fatal("bad hello");
89 	if(strstr(msg, "Plan 9"))
90 		os = Plan9;
91 
92 	if(usetls){
93 		sendrequest("AUTH", "TLS");
94 		if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
95 			fatal("bad auth tls");
96 
97 		ctlfd = tlsClient(ctlfd, &conn);
98 		if(ctlfd < 0)
99 			fatal("starting tls: %r");
100 		free(conn.cert);
101 
102 		Binit(&ctlin, ctlfd, OREAD);
103 
104 		sendrequest("PBSZ", "0");
105 		if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
106 			fatal("bad pbsz 0");
107 		sendrequest("PROT", "P");
108 		if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
109 			fatal("bad prot p");
110 	}
111 }
112 
113 /*
114  *  login to remote system
115  */
116 void
117 rlogin(char *rsys, char *keyspec)
118 {
119 	char *line;
120 	char pass[128];
121 	UserPasswd *up;
122 
123 	up = nil;
124 	for(;;){
125 		if(up == nil && os != Plan9)
126 			up = auth_getuserpasswd(auth_getkey, "proto=pass server=%s service=ftp %s", rsys, keyspec);
127 		if(up != nil){
128 			sendrequest("USER", up->user);
129 		} else {
130 			print("User[default = %s]: ", user);
131 			line = Brdline(&stdin, '\n');
132 			if(line == 0)
133 				exits(0);
134 			line[Blinelen(&stdin)-1] = 0;
135 			if(*line){
136 				free(user);
137 				user = strdup(line);
138 			}
139 			sendrequest("USER", user);
140 		}
141 		switch(getreply(&ctlin, msg, sizeof(msg), 1)){
142 		case Success:
143 			goto out;
144 		case Incomplete:
145 			break;
146 		case TempFail:
147 		case PermFail:
148 			continue;
149 		}
150 
151 		if(up != nil){
152 			sendrequest("PASS", up->passwd);
153 		} else {
154 			if(getpassword(pass, pass+sizeof(pass)) < 0)
155 				exits(0);
156 			sendrequest("PASS", pass);
157 		}
158 		if(getreply(&ctlin, msg, sizeof(msg), 1) == Success){
159 			if(strstr(msg, "Sess#"))
160 				defos = MVS;
161 			break;
162 		}
163 	}
164 out:
165 	if(up != nil){
166 		memset(up, 0, sizeof(*up));
167 		free(up);
168 	}
169 }
170 
171 /*
172  *  login to remote system with given user name and password.
173  */
174 void
175 clogin(char *cuser, char *cpassword)
176 {
177 	free(user);
178 	user = strdup(cuser);
179 	if (strcmp(user, "anonymous") != 0 &&
180 	    strcmp(user, "ftp") != 0)
181 		fatal("User must be 'anonymous' or 'ftp'");
182 	sendrequest("USER", user);
183 	switch(getreply(&ctlin, msg, sizeof(msg), 1)){
184 	case Success:
185 		return;
186 	case Incomplete:
187 		break;
188 	case TempFail:
189 	case PermFail:
190 		fatal("login failed");
191 	}
192 	if (cpassword == 0)
193 		fatal("password needed");
194 	sendrequest("PASS", cpassword);
195 	if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
196 		fatal("password failed");
197 	if(strstr(msg, "Sess#"))
198 		defos = MVS;
199 	return;
200 }
201 
202 /*
203  *  find out about the other side.  go to it's root if requested.  set
204  *  image mode if a Plan9 system.
205  */
206 void
207 preamble(char *mountroot)
208 {
209 	char *p, *ep;
210 	int rv;
211 	OS *o;
212 
213 	/*
214 	 *  create a root directory mirror
215 	 */
216 	remroot = newnode(0, s_copy("/"));
217 	remroot->d->qid.type = QTDIR;
218 	remroot->d->mode = DMDIR|0777;
219 	remdir = remroot;
220 
221 	/*
222 	 *  get system type
223 	 */
224 	sendrequest("SYST", nil);
225 	switch(getreply(&ctlin, msg, sizeof(msg), 1)){
226 	case Success:
227 		for(o = oslist; o->os != Unknown; o++)
228 			if(strncmp(msg+4, o->name, strlen(o->name)) == 0)
229 				break;
230 		os = o->os;
231 		if(os == NT)
232 			os = Unix;
233 		break;
234 	default:
235 		os = defos;
236 		break;
237 	}
238 	if(os == Unknown)
239 		os = defos;
240 
241 	remrootpath = s_reset(remrootpath);
242 	switch(os){
243 	case NetWare:
244               /*
245                * Request long, rather than 8.3 filenames,
246                * where the Servers & Volume support them.
247                */
248               sendrequest("SITE LONG", nil);
249               getreply(&ctlin, msg, sizeof(msg), 0);
250               /* FALL THRU */
251 	case Unix:
252 	case Plan9:
253 		/*
254 		 *  go to the remote root, if asked
255 		 */
256 		if(mountroot){
257 			sendrequest("CWD", mountroot);
258 			getreply(&ctlin, msg, sizeof(msg), 0);
259 		} else {
260 			s_append(remrootpath, "/usr/");
261 			s_append(remrootpath, user);
262 		}
263 
264 		/*
265 		 *  get the root directory
266 		 */
267 		sendrequest("PWD", nil);
268 		rv = getreply(&ctlin, msg, sizeof(msg), 1);
269 		if(rv == PermFail){
270 			sendrequest("XPWD", nil);
271 			rv = getreply(&ctlin, msg, sizeof(msg), 1);
272 		}
273 		if(rv == Success){
274 			p = strchr(msg, '"');
275 			if(p){
276 				p++;
277 				ep = strchr(p, '"');
278 				if(ep){
279 					*ep = 0;
280 					s_append(s_reset(remrootpath), p);
281 				}
282 			}
283 		}
284 
285 		break;
286 	case Tops:
287 	case VM:
288 		/*
289 		 *  top directory is a figment of our imagination.
290 		 *  make it permanently cached & valid.
291 		 */
292 		CACHED(remroot);
293 		VALID(remroot);
294 		remroot->d->atime = time(0) + 100000;
295 
296 		/*
297 		 *  no initial directory.  We are in the
298 		 *  imaginary root.
299 		 */
300 		remdir = newtopsdir("???");
301 		topsdir[0] = 0;
302 		if(os == Tops && readdir(remdir) >= 0){
303 			CACHED(remdir);
304 			if(*topsdir)
305 				remdir->remname = s_copy(topsdir);
306 			VALID(remdir);
307 		}
308 		break;
309 	case VMS:
310 		/*
311 		 *  top directory is a figment of our imagination.
312 		 *  make it permanently cached & valid.
313 		 */
314 		CACHED(remroot);
315 		VALID(remroot);
316 		remroot->d->atime = time(0) + 100000;
317 
318 		/*
319 		 *  get current directory
320 		 */
321 		sendrequest("PWD", nil);
322 		rv = getreply(&ctlin, msg, sizeof(msg), 1);
323 		if(rv == PermFail){
324 			sendrequest("XPWD", nil);
325 			rv = getreply(&ctlin, msg, sizeof(msg), 1);
326 		}
327 		if(rv == Success){
328 			p = strchr(msg, '"');
329 			if(p){
330 				p++;
331 				ep = strchr(p, '"');
332 				if(ep){
333 					*ep = 0;
334 					remroot = remdir = vmsdir(p);
335 				}
336 			}
337 		}
338 		break;
339 	case MVS:
340 		usenlst = 1;
341 		break;
342 	}
343 
344 	if(os == Plan9)
345 		image();
346 }
347 
348 static void
349 ascii(void)
350 {
351 	sendrequest("TYPE A", nil);
352 	switch(getreply(&ctlin, msg, sizeof(msg), 0)){
353 	case Success:
354 		break;
355 	default:
356 		fatal("can't set type to ascii");
357 	}
358 }
359 
360 static void
361 image(void)
362 {
363 	sendrequest("TYPE I", nil);
364 	switch(getreply(&ctlin, msg, sizeof(msg), 0)){
365 	case Success:
366 		break;
367 	default:
368 		fatal("can't set type to image/binary");
369 	}
370 }
371 
372 /*
373  *  decode the time fields, return seconds since epoch began
374  */
375 char *monthchars = "janfebmaraprmayjunjulaugsepoctnovdec";
376 static Tm now;
377 
378 static ulong
379 cracktime(char *month, char *day, char *yr, char *hms)
380 {
381 	Tm tm;
382 	int i;
383 	char *p;
384 
385 
386 	/* default time */
387 	if(now.year == 0)
388 		now = *localtime(time(0));
389 	tm = now;
390 	tm.yday = 0;
391 
392 	/* convert ascii month to a number twixt 1 and 12 */
393 	if(*month >= '0' && *month <= '9'){
394 		tm.mon = atoi(month) - 1;
395 		if(tm.mon < 0 || tm.mon > 11)
396 			tm.mon = 5;
397 	} else {
398 		for(p = month; *p; p++)
399 			*p = tolower(*p);
400 		for(i = 0; i < 12; i++)
401 			if(strncmp(&monthchars[i*3], month, 3) == 0){
402 				tm.mon = i;
403 				break;
404 			}
405 	}
406 
407 	tm.mday = atoi(day);
408 
409 	if(hms){
410 		tm.hour = strtol(hms, &p, 0);
411 		if(*p == ':'){
412 			tm.min = strtol(p+1, &p, 0);
413 			if(*p == ':')
414 				tm.sec = strtol(p+1, &p, 0);
415 		}
416 		if(tolower(*p) == 'p')
417 			tm.hour += 12;
418 	}
419 
420 	if(yr){
421 		tm.year = atoi(yr);
422 		if(tm.year >= 1900)
423 			tm.year -= 1900;
424 	} else {
425 		if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
426 			tm.year--;
427 	}
428 
429 	/* convert to epoch seconds */
430 	return tm2sec(&tm);
431 }
432 
433 /*
434  *  decode a Unix or Plan 9 file mode
435  */
436 static ulong
437 crackmode(char *p)
438 {
439 	ulong flags;
440 	ulong mode;
441 	int i;
442 
443 	flags = 0;
444 	switch(strlen(p)){
445 	case 10:	/* unix and new style plan 9 */
446 		switch(*p){
447 		case 'l':
448 			return DMSYML|0777;
449 		case 'd':
450 			flags |= DMDIR;
451 		case 'a':
452 			flags |= DMAPPEND;
453 		}
454 		p++;
455 		if(p[2] == 'l')
456 			flags |= DMEXCL;
457 		break;
458 	case 11:	/* old style plan 9 */
459 		switch(*p++){
460 		case 'd':
461 			flags |= DMDIR;
462 			break;
463 		case 'a':
464 			flags |= DMAPPEND;
465 			break;
466 		}
467 		if(*p++ == 'l')
468 			flags |= DMEXCL;
469 		break;
470 	default:
471 		return DMDIR|0777;
472 	}
473 	mode = 0;
474 	for(i = 0; i < 3; i++){
475 		mode <<= 3;
476 		if(*p++ == 'r')
477 			mode |= DMREAD;
478 		if(*p++ == 'w')
479 			mode |= DMWRITE;
480 		if(*p == 'x' || *p == 's' || *p == 'S')
481 			mode |= DMEXEC;
482 		p++;
483 	}
484 	return mode | flags;
485 }
486 
487 /*
488  *  find first punctuation
489  */
490 char*
491 strpunct(char *p)
492 {
493 	int c;
494 
495 	for(;c = *p; p++){
496 		if(ispunct(c))
497 			return p;
498 	}
499 	return 0;
500 }
501 
502 /*
503  *  decode a Unix or Plan 9 directory listing
504  */
505 static Dir*
506 crackdir(char *p, String **remname)
507 {
508 	char *field[15];
509 	char *dfield[4];
510 	char *cp;
511 	String *s;
512 	int dn, n;
513 	Dir d, *dp;
514 
515 	memset(&d, 0, sizeof(d));
516 
517 	n = getfields(p, field, 15, 1, " \t");
518 	if(n > 2 && strcmp(field[n-2], "->") == 0)
519 		n -= 2;
520 	switch(os){
521 	case TSO:
522 		cp = strchr(field[0], '.');
523 		if(cp){
524 			*cp++ = 0;
525 			s = s_copy(cp);
526 			d.uid = field[0];
527 		} else {
528 			s = s_copy(field[0]);
529 			d.uid = "TSO";
530 		}
531 		d.gid = "TSO";
532 		d.mode = 0666;
533 		d.length = 0;
534 		d.atime = 0;
535 		break;
536 	case OS½:
537 		s = s_copy(field[n-1]);
538 		d.uid = "OS½";
539 		d.gid = d.uid;
540 		d.mode = 0666;
541 		switch(n){
542 		case 5:
543 			if(strcmp(field[1], "DIR") == 0)
544 				d.mode |= DMDIR;
545 			d.length = atoi(field[0]);
546 			dn = getfields(field[2], dfield, 4, 1, "-");
547 			if(dn == 3)
548 				d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[3]);
549 			break;
550 		}
551 		break;
552 	case Tops:
553 		if(n != 4){ /* tops directory name */
554 			safecpy(topsdir, field[0], sizeof(topsdir));
555 			return 0;
556 		}
557 		s = s_copy(field[3]);
558 		d.length = atoi(field[0]);
559 		d.mode = 0666;
560 		d.uid = "Tops";
561 		d.gid = d.uid;
562 		dn = getfields(field[1], dfield, 4, 1, "-");
563 		if(dn == 3)
564 			d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[2]);
565 		else
566 			d.atime = time(0);
567 		break;
568 	case VM:
569 		switch(n){
570 		case 9:
571 			s = s_copy(field[0]);
572 			s_append(s, ".");
573 			s_append(s, field[1]);
574 			d.length = atoi(field[3])*atoi(field[4]);
575 			if(*field[2] == 'F')
576 				d.mode = 0666;
577 			else
578 				d.mode = 0777;
579 			d.uid = "VM";
580 			d.gid = d.uid;
581 			dn = getfields(field[6], dfield, 4, 1, "/-");
582 			if(dn == 3)
583 				d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[7]);
584 			else
585 				d.atime = time(0);
586 			break;
587 		case 1:
588 			s = s_copy(field[0]);
589 			d.uid = "VM";
590 			d.gid = d.uid;
591 			d.mode = 0777;
592 			d.atime = 0;
593 			break;
594 		default:
595 			return nil;
596 		}
597 		break;
598 	case VMS:
599 		switch(n){
600 		case 6:
601 			for(cp = field[0]; *cp; cp++)
602 				*cp = tolower(*cp);
603 			cp = strchr(field[0], ';');
604 			if(cp)
605 				*cp = 0;
606 			d.mode = 0666;
607 			cp = field[0] + strlen(field[0]) - 4;
608 			if(strcmp(cp, ".dir") == 0){
609 				d.mode |= DMDIR;
610 				*cp = 0;
611 			}
612 			s = s_copy(field[0]);
613 			d.length = atoi(field[1])*512;
614 			field[4][strlen(field[4])-1] = 0;
615 			d.uid = field[4]+1;
616 			d.gid = d.uid;
617 			dn = getfields(field[2], dfield, 4, 1, "/-");
618 			if(dn == 3)
619 				d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[3]);
620 			else
621 				d.atime = time(0);
622 			break;
623 		default:
624 			return nil;
625 		}
626 		break;
627 	case NetWare:
628 		switch(n){
629 		case 8:		/* New style */
630 			s = s_copy(field[7]);
631 			d.uid = field[2];
632 			d.gid = d.uid;
633 			d.mode = nw_mode(field[0][0], field[1]);
634 			d.length = atoi(field[3]);
635 			if(strchr(field[6], ':'))
636 				d.atime = cracktime(field[4], field[5], nil, field[6]);
637 			else
638 				d.atime = cracktime(field[4], field[5], field[6], nil);
639 			break;
640 		case 9:
641 			s = s_copy(field[8]);
642 			d.uid = field[2];
643 			d.gid = d.uid;
644 			d.mode = 0666;
645 			if(*field[0] == 'd')
646 				d.mode |= DMDIR;
647 			d.length = atoi(field[3]);
648 			d.atime = cracktime(field[4], field[5], field[6], field[7]);
649 			break;
650 		case 1:
651 			s = s_copy(field[0]);
652 			d.uid = "none";
653 			d.gid = d.uid;
654 			d.mode = 0777;
655 			d.atime = 0;
656 			break;
657 		default:
658 			return nil;
659 		}
660 		break;
661 	case Unix:
662 	case Plan9:
663 	default:
664 		switch(n){
665 		case 8:		/* ls -l */
666 			s = s_copy(field[7]);
667 			d.uid = field[2];
668 			d.gid = d.uid;
669 			d.mode = crackmode(field[0]);
670 			d.length = atoi(field[3]);
671 			if(strchr(field[6], ':'))
672 				d.atime = cracktime(field[4], field[5], 0, field[6]);
673 			else
674 				d.atime = cracktime(field[4], field[5], field[6], 0);
675 			break;
676 		case 9:		/* ls -lg */
677 			s = s_copy(field[8]);
678 			d.uid = field[2];
679 			d.gid = field[3];
680 			d.mode = crackmode(field[0]);
681 			d.length = atoi(field[4]);
682 			if(strchr(field[7], ':'))
683 				d.atime = cracktime(field[5], field[6], 0, field[7]);
684 			else
685 				d.atime = cracktime(field[5], field[6], field[7], 0);
686 			break;
687 		case 10:	/* plan 9 */
688 			s = s_copy(field[9]);
689 			d.uid = field[3];
690 			d.gid = field[4];
691 			d.mode = crackmode(field[0]);
692 			d.length = atoi(field[5]);
693 			if(strchr(field[8], ':'))
694 				d.atime = cracktime(field[6], field[7], 0, field[8]);
695 			else
696 				d.atime = cracktime(field[6], field[7], field[8], 0);
697 			break;
698 		case 4:		/* a Windows_NT version */
699 			s = s_copy(field[3]);
700 			d.uid = "NT";
701 			d.gid = d.uid;
702 			if(strcmp("<DIR>", field[2]) == 0){
703 				d.length = 0;
704 				d.mode = DMDIR|0777;
705 			} else {
706 				d.mode = 0666;
707 				d.length = atoi(field[2]);
708 			}
709 			dn = getfields(field[0], dfield, 4, 1, "/-");
710 			if(dn == 3)
711 				d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[1]);
712 			break;
713 		case 1:
714 			s = s_copy(field[0]);
715 			d.uid = "none";
716 			d.gid = d.uid;
717 			d.mode = 0777;
718 			d.atime = 0;
719 			break;
720 		default:
721 			return nil;
722 		}
723 	}
724 	d.muid = d.uid;
725 	d.qid.type = (d.mode & DMDIR) ? QTDIR : QTFILE;
726 	d.mtime = d.atime;
727 	if(ext && (d.qid.type & QTDIR) == 0)
728 		s_append(s, ext);
729 	d.name = s_to_c(s);
730 
731 	/* allocate a freeable dir structure */
732 	dp = reallocdir(&d, 0);
733 	*remname = s;
734 
735 	return dp;
736 }
737 
738 /*
739  *  probe files in a directory to see if they are directories
740  */
741 /*
742  *  read a remote directory
743  */
744 int
745 readdir(Node *node)
746 {
747 	Biobuf *bp;
748 	char *line;
749 	Node *np;
750 	Dir *d;
751 	long n;
752 	int tries, x, files;
753 	static int uselist;
754 	int usenlist;
755 	String *remname;
756 
757 	if(changedir(node) < 0)
758 		return -1;
759 
760 	usenlist = 0;
761 	for(tries = 0; tries < 3; tries++){
762 		if(usenlist || usenlst)
763 			x = data(OREAD, &bp, "NLST", nil);
764 		else if(os == Unix && !uselist)
765 			x = data(OREAD, &bp, "LIST -l", nil);
766 		else
767 			x = data(OREAD, &bp, "LIST", nil);
768 		switch(x){
769 		case Extra:
770 			break;
771 /*		case TempFail:
772 			continue;
773 */
774 		default:
775 			if(os == Unix && uselist == 0){
776 				uselist = 1;
777 				continue;
778 			}
779 			return seterr(nosuchfile);
780 		}
781 		files = 0;
782 		while(line = Brdline(bp, '\n')){
783 			n = Blinelen(bp);
784 			if(debug)
785 				write(2, line, n);
786 			if(n > 1 && line[n-2] == '\r')
787 				n--;
788 			line[n - 1] = 0;
789 
790 			d = crackdir(line, &remname);
791 			if(d == nil)
792 				continue;
793 			files++;
794 			np = extendpath(node, remname);
795 			d->qid.path = np->d->qid.path;
796 			d->qid.vers = np->d->qid.vers;
797 			d->type = np->d->type;
798 			d->dev = 1;			/* mark node as valid */
799 			if(os == MVS && node == remroot){
800 				d->qid.type = QTDIR;
801 				d->mode |= DMDIR;
802 			}
803 			free(np->d);
804 			np->d = d;
805 		}
806 		close(Bfildes(bp));
807 
808 		switch(getreply(&ctlin, msg, sizeof(msg), 0)){
809 		case Success:
810 			if(files == 0 && !usenlst && !usenlist){
811 				usenlist = 1;
812 				continue;
813 			}
814 			if(files && usenlist)
815 				usenlst = 1;
816 			if(usenlst)
817 				node->chdirunknown = 1;
818 			return 0;
819 		case TempFail:
820 			break;
821 		default:
822 			return seterr(nosuchfile);
823 		}
824 	}
825 	return seterr(nosuchfile);
826 }
827 
828 /*
829  *  create a remote directory
830  */
831 int
832 createdir(Node *node)
833 {
834 	if(changedir(node->parent) < 0)
835 		return -1;
836 
837 	sendrequest("MKD", node->d->name);
838 	if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
839 		return -1;
840 	return 0;
841 }
842 
843 /*
844  *  change to a remote directory.
845  */
846 int
847 changedir(Node *node)
848 {
849 	Node *to;
850 	String *cdpath;
851 
852 	to = node;
853 	if(to == remdir)
854 		return 0;
855 
856 	/* build an absolute path */
857 	switch(os){
858 	case Tops:
859 	case VM:
860 		switch(node->depth){
861 		case 0:
862 			remdir = node;
863 			return 0;
864 		case 1:
865 			cdpath = s_clone(node->remname);
866 			break;
867 		default:
868 			return seterr(nosuchfile);
869 		}
870 		break;
871 	case VMS:
872 		switch(node->depth){
873 		case 0:
874 			remdir = node;
875 			return 0;
876 		default:
877 			cdpath = s_new();
878 			vmspath(node, cdpath);
879 		}
880 		break;
881 	case MVS:
882 		if(node->depth == 0)
883 			cdpath = s_clone(remrootpath);
884 		else{
885 			cdpath = s_new();
886 			mvspath(node, cdpath);
887 		}
888 		break;
889 	default:
890 		if(node->depth == 0)
891 			cdpath = s_clone(remrootpath);
892 		else{
893 			cdpath = s_new();
894 			unixpath(node, cdpath);
895 		}
896 		break;
897 	}
898 
899 	uncachedir(remdir, 0);
900 
901 	/*
902 	 *  connect, if we need a password (Incomplete)
903 	 *  act like it worked (best we can do).
904 	 */
905 	sendrequest("CWD", s_to_c(cdpath));
906 	s_free(cdpath);
907 	switch(getreply(&ctlin, msg, sizeof(msg), 0)){
908 	case Success:
909 	case Incomplete:
910 		remdir = node;
911 		return 0;
912 	default:
913 		return seterr(nosuchfile);
914 	}
915 }
916 
917 /*
918  *  read a remote file
919  */
920 int
921 readfile1(Node *node)
922 {
923 	Biobuf *bp;
924 	char buf[4*1024];
925 	long off;
926 	int n;
927 	int tries;
928 
929 	if(changedir(node->parent) < 0)
930 		return -1;
931 
932 	for(tries = 0; tries < 4; tries++){
933 		switch(data(OREAD, &bp, "RETR", s_to_c(node->remname))){
934 		case Extra:
935 			break;
936 		case TempFail:
937 			continue;
938 		default:
939 			return seterr(nosuchfile);
940 		}
941 		off = 0;
942 		while((n = read(Bfildes(bp), buf, sizeof buf)) > 0){
943 			if(filewrite(node, buf, off, n) != n){
944 				off = -1;
945 				break;
946 			}
947 			off += n;
948 		}
949 		if(off < 0)
950 			return -1;
951 
952 		/* make sure a file gets created even for a zero length file */
953 		if(off == 0)
954 			filewrite(node, buf, 0, 0);
955 
956 		close(Bfildes(bp));
957 		switch(getreply(&ctlin, msg, sizeof(msg), 0)){
958 		case Success:
959 			return off;
960 		case TempFail:
961 			continue;
962 		default:
963 			return seterr(nosuchfile);
964 		}
965 	}
966 	return seterr(nosuchfile);
967 }
968 
969 int
970 readfile(Node *node)
971 {
972 	int rv, inimage;
973 
974 	switch(os){
975 	case MVS:
976 	case Plan9:
977 	case Tops:
978 	case TSO:
979 		inimage = 0;
980 		break;
981 	default:
982 		inimage = 1;
983 		image();
984 		break;
985 	}
986 
987 	rv = readfile1(node);
988 
989 	if(inimage)
990 		ascii();
991 	return rv;
992 }
993 
994 /*
995  *  write back a file
996  */
997 int
998 createfile1(Node *node)
999 {
1000 	Biobuf *bp;
1001 	char buf[4*1024];
1002 	long off;
1003 	int n;
1004 
1005 	if(changedir(node->parent) < 0)
1006 		return -1;
1007 
1008 	if(data(OWRITE, &bp, "STOR", s_to_c(node->remname)) != Extra)
1009 		return -1;
1010 	for(off = 0; ; off += n){
1011 		n = fileread(node, buf, off, sizeof(buf));
1012 		if(n <= 0)
1013 			break;
1014 		write(Bfildes(bp), buf, n);
1015 	}
1016 	close(Bfildes(bp));
1017 	if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1018 		return -1;
1019 	return off;
1020 }
1021 
1022 int
1023 createfile(Node *node)
1024 {
1025 	int rv;
1026 
1027 	switch(os){
1028 	case Plan9:
1029 	case Tops:
1030 		break;
1031 	default:
1032 		image();
1033 		break;
1034 	}
1035 	rv = createfile1(node);
1036 	switch(os){
1037 	case Plan9:
1038 	case Tops:
1039 		break;
1040 	default:
1041 		ascii();
1042 		break;
1043 	}
1044 	return rv;
1045 }
1046 
1047 /*
1048  *  remove a remote file
1049  */
1050 int
1051 removefile(Node *node)
1052 {
1053 	if(changedir(node->parent) < 0)
1054 		return -1;
1055 
1056 	sendrequest("DELE", s_to_c(node->remname));
1057 	if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1058 		return -1;
1059 	return 0;
1060 }
1061 
1062 /*
1063  *  remove a remote directory
1064  */
1065 int
1066 removedir(Node *node)
1067 {
1068 	if(changedir(node->parent) < 0)
1069 		return -1;
1070 
1071 	sendrequest("RMD", s_to_c(node->remname));
1072 	if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1073 		return -1;
1074 	return 0;
1075 }
1076 
1077 /*
1078  *  tell remote that we're exiting and then do it
1079  */
1080 void
1081 quit(void)
1082 {
1083 	sendrequest("QUIT", nil);
1084 	getreply(&ctlin, msg, sizeof(msg), 0);
1085 	exits(0);
1086 }
1087 
1088 /*
1089  *  send a request
1090  */
1091 static void
1092 sendrequest(char *a, char *b)
1093 {
1094 	char buf[2*1024];
1095 	int n;
1096 
1097 	n = strlen(a)+2+1;
1098 	if(b != nil)
1099 		n += strlen(b)+1;
1100 	if(n >= sizeof(buf))
1101 		fatal("proto request too long");
1102 	strcpy(buf, a);
1103 	if(b != nil){
1104 		strcat(buf, " ");
1105 		strcat(buf, b);
1106 	}
1107 	strcat(buf, "\r\n");
1108 	n = strlen(buf);
1109 	if(write(ctlfd, buf, n) != n)
1110 		fatal("remote side hung up");
1111 	if(debug)
1112 		write(2, buf, n);
1113 	lastsend = time(0);
1114 }
1115 
1116 /*
1117  *  replies codes are in the range [100, 999] and may contain multiple lines of
1118  *  continuation.
1119  */
1120 static int
1121 getreply(Biobuf *bp, char *msg, int len, int printreply)
1122 {
1123 	char *line;
1124 	char *p;
1125 	int rv;
1126 	int i, n;
1127 
1128 	while(line = Brdline(bp, '\n')){
1129 		/* add line to message buffer, strip off \r */
1130 		n = Blinelen(bp);
1131 		if(n > 1 && line[n-2] == '\r'){
1132 			n--;
1133 			line[n-1] = '\n';
1134 		}
1135 		if(printreply && !quiet)
1136 			write(1, line, n);
1137 		else if(debug)
1138 			write(2, line, n);
1139 		if(n > len - 1)
1140 			i = len - 1;
1141 		else
1142 			i = n;
1143 		if(i > 0){
1144 			memmove(msg, line, i);
1145 			msg += i;
1146 			len -= i;
1147 			*msg = 0;
1148 		}
1149 
1150 		/* stop if not a continuation */
1151 		rv = strtol(line, &p, 10);
1152 		if(rv >= 100 && rv < 600 && p==line+3 && *p != '-')
1153 			return rv/100;
1154 
1155 		/* tell user about continuations */
1156 		if(!debug && !quiet && !printreply)
1157 			write(2, line, n);
1158 	}
1159 
1160 	fatal("remote side closed connection");
1161 	return 0;
1162 }
1163 
1164 /*
1165  *  Announce on a local port and tell its address to the the remote side
1166  */
1167 static int
1168 port(void)
1169 {
1170 	char buf[256];
1171 	int n, fd;
1172 	char *ptr;
1173 	uchar ipaddr[IPaddrlen];
1174 	int port;
1175 
1176 	/* get a channel to listen on, let kernel pick the port number */
1177 	sprint(buf, "%s!*!0", net);
1178 	listenfd = announce(buf, netdir);
1179 	if(listenfd < 0)
1180 		return seterr("can't announce");
1181 
1182 	/* get the local address and port number */
1183 	sprint(buf, "%s/local", netdir);
1184 	fd = open(buf, OREAD);
1185 	if(fd < 0)
1186 		return seterr("opening %s: %r", buf);
1187 	n = read(fd, buf, sizeof(buf)-1);
1188 	close(fd);
1189 	if(n <= 0)
1190 		return seterr("opening %s/local: %r", netdir);
1191 	buf[n] = 0;
1192 	ptr = strchr(buf, ' ');
1193 	if(ptr)
1194 		*ptr = 0;
1195 	ptr = strchr(buf, '!')+1;
1196 	port = atoi(ptr);
1197 
1198 	memset(ipaddr, 0, IPaddrlen);
1199 	if (*net){
1200 		strcpy(buf, net);
1201 		ptr = strchr(buf +1, '/');
1202 		if (ptr)
1203 			*ptr = 0;
1204 		myipaddr(ipaddr, buf);
1205 	}
1206 
1207 	/* tell remote side */
1208 	sprint(buf, "PORT %d,%d,%d,%d,%d,%d", ipaddr[IPv4off+0], ipaddr[IPv4off+1],
1209 		ipaddr[IPv4off+2], ipaddr[IPv4off+3], port>>8, port&0xff);
1210 	sendrequest(buf, nil);
1211 	if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1212 		return seterr(msg);
1213 	return 0;
1214 }
1215 
1216 /*
1217  *  have server call back for a data connection
1218  */
1219 static int
1220 active(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1221 {
1222 	int cfd, dfd, rv;
1223 	char newdir[Maxpath];
1224 	char datafile[Maxpath + 6];
1225 	TLSconn conn;
1226 
1227 	if(port() < 0)
1228 		return TempFail;
1229 
1230 	sendrequest(cmda, cmdb);
1231 
1232 	rv = getreply(&ctlin, msg, sizeof(msg), 0);
1233 	if(rv != Extra){
1234 		close(listenfd);
1235 		return rv;
1236 	}
1237 
1238 	/* wait for a new call */
1239 	cfd = listen(netdir, newdir);
1240 	if(cfd < 0)
1241 		fatal("waiting for data connection");
1242 	close(listenfd);
1243 
1244 	/* open it's data connection and close the control connection */
1245 	sprint(datafile, "%s/data", newdir);
1246 	dfd = open(datafile, ORDWR);
1247 	close(cfd);
1248 	if(dfd < 0)
1249 		fatal("opening data connection");
1250 
1251 	if(usetls){
1252 		memset(&conn, 0, sizeof(conn));
1253 		dfd = tlsClient(dfd, &conn);
1254 		if(dfd < 0)
1255 			fatal("starting tls: %r");
1256 		free(conn.cert);
1257 	}
1258 
1259 	Binit(&dbuf, dfd, mode);
1260 	*bpp = &dbuf;
1261 	return Extra;
1262 }
1263 
1264 /*
1265  *  call out for a data connection
1266  */
1267 static int
1268 passive(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1269 {
1270 	char msg[1024];
1271 	char ds[1024];
1272 	char *f[6];
1273 	char *p;
1274 	int x, fd;
1275 	TLSconn conn;
1276 
1277 	if(nopassive)
1278 		return Impossible;
1279 
1280 	sendrequest("PASV", nil);
1281 	if(getreply(&ctlin, msg, sizeof(msg), 0) != Success){
1282 		nopassive = 1;
1283 		return Impossible;
1284 	}
1285 
1286 	/* get address and port number from reply, this is AI */
1287 	p = strchr(msg, '(');
1288 	if(p == 0){
1289 		for(p = msg+3; *p; p++)
1290 			if(isdigit(*p))
1291 				break;
1292 	} else
1293 		p++;
1294 	if(getfields(p, f, 6, 0, ",") < 6){
1295 		if(debug)
1296 			fprint(2, "passive mode protocol botch: %s\n", msg);
1297 		werrstr("ftp protocol botch");
1298 		nopassive = 1;
1299 		return Impossible;
1300 	}
1301 	snprint(ds, sizeof(ds), "%s!%s.%s.%s.%s!%d", net,
1302 		f[0], f[1], f[2], f[3],
1303 		((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff));
1304 
1305 	/* open data connection */
1306 	fd = dial(ds, 0, 0, 0);
1307 	if(fd < 0){
1308 		if(debug)
1309 			fprint(2, "passive mode connect to %s failed: %r\n", ds);
1310 		nopassive = 1;
1311 		return TempFail;
1312 	}
1313 
1314 	/* tell remote to send a file */
1315 	sendrequest(cmda, cmdb);
1316 	x = getreply(&ctlin, msg, sizeof(msg), 0);
1317 	if(x != Extra){
1318 		close(fd);
1319 		if(debug)
1320 			fprint(2, "passive mode retrieve failed: %s\n", msg);
1321 		werrstr(msg);
1322 		return x;
1323 	}
1324 
1325 	if(usetls){
1326 		memset(&conn, 0, sizeof(conn));
1327 		fd = tlsClient(fd, &conn);
1328 		if(fd < 0)
1329 			fatal("starting tls: %r");
1330 		free(conn.cert);
1331 	}
1332 	Binit(&dbuf, fd, mode);
1333 
1334 	*bpp = &dbuf;
1335 	return Extra;
1336 }
1337 
1338 static int
1339 data(int mode, Biobuf **bpp, char* cmda, char *cmdb)
1340 {
1341 	int x;
1342 
1343 	x = passive(mode, bpp, cmda, cmdb);
1344 	if(x != Impossible)
1345 		return x;
1346 	return active(mode, bpp, cmda, cmdb);
1347 }
1348 
1349 /*
1350  *  used for keep alives
1351  */
1352 void
1353 nop(void)
1354 {
1355 	if(lastsend - time(0) < 15)
1356 		return;
1357 	sendrequest("PWD", nil);
1358 	getreply(&ctlin, msg, sizeof(msg), 0);
1359 }
1360 
1361 /*
1362  *  turn a vms spec into a path
1363  */
1364 static Node*
1365 vmsextendpath(Node *np, char *name)
1366 {
1367 	np = extendpath(np, s_copy(name));
1368 	if(!ISVALID(np)){
1369 		np->d->qid.type = QTDIR;
1370 		np->d->atime = time(0);
1371 		np->d->mtime = np->d->atime;
1372 		strcpy(np->d->uid, "who");
1373 		strcpy(np->d->gid, "cares");
1374 		np->d->mode = DMDIR|0777;
1375 		np->d->length = 0;
1376 		if(changedir(np) >= 0)
1377 			VALID(np);
1378 	}
1379 	return np;
1380 }
1381 static Node*
1382 vmsdir(char *name)
1383 {
1384 	char *cp;
1385 	Node *np;
1386 	char *oname;
1387 
1388 	np = remroot;
1389 	cp = strchr(name, '[');
1390 	if(cp)
1391 		strcpy(cp, cp+1);
1392 	cp = strchr(name, ']');
1393 	if(cp)
1394 		*cp = 0;
1395 	oname = name = strdup(name);
1396 	if(name == 0)
1397 		return 0;
1398 
1399 	while(cp = strchr(name, '.')){
1400 		*cp = 0;
1401 		np = vmsextendpath(np, name);
1402 		name = cp+1;
1403 	}
1404 	np = vmsextendpath(np, name);
1405 
1406 	/*
1407 	 *  walk back to first accessible directory
1408 	 */
1409 	for(; np->parent != np; np = np->parent)
1410 		if(ISVALID(np)){
1411 			CACHED(np->parent);
1412 			break;
1413 		}
1414 
1415 	free(oname);
1416 	return np;
1417 }
1418 
1419 /*
1420  *  walk up the tree building a VMS style path
1421  */
1422 static void
1423 vmspath(Node *node, String *path)
1424 {
1425 	char *p;
1426 	int n;
1427 
1428 	if(node->depth == 1){
1429 		p = strchr(s_to_c(node->remname), ':');
1430 		if(p){
1431 			n = p - s_to_c(node->remname) + 1;
1432 			s_nappend(path, s_to_c(node->remname), n);
1433 			s_append(path, "[");
1434 			s_append(path, p+1);
1435 		} else {
1436 			s_append(path, "[");
1437 			s_append(path, s_to_c(node->remname));
1438 		}
1439 		s_append(path, "]");
1440 		return;
1441 	}
1442 	vmspath(node->parent, path);
1443 	s_append(path, ".");
1444 	s_append(path, s_to_c(node->remname));
1445 }
1446 
1447 /*
1448  *  walk up the tree building a Unix style path
1449  */
1450 static void
1451 unixpath(Node *node, String *path)
1452 {
1453 	if(node == node->parent){
1454 		s_append(path, s_to_c(remrootpath));
1455 		return;
1456 	}
1457 	unixpath(node->parent, path);
1458 	if(s_len(path) > 0 && strcmp(s_to_c(path), "/") != 0)
1459 		s_append(path, "/");
1460 	s_append(path, s_to_c(node->remname));
1461 }
1462 
1463 /*
1464  *  walk up the tree building a MVS style path
1465  */
1466 static void
1467 mvspath(Node *node, String *path)
1468 {
1469 	if(node == node->parent){
1470 		s_append(path, s_to_c(remrootpath));
1471 		return;
1472 	}
1473 	mvspath(node->parent, path);
1474 	if(s_len(path) > 0)
1475 		s_append(path, ".");
1476 	s_append(path, s_to_c(node->remname));
1477 }
1478 
1479 static int
1480 getpassword(char *buf, char *e)
1481 {
1482 	char *p;
1483 	int c;
1484 	int consctl, rv = 0;
1485 
1486 	consctl = open("/dev/consctl", OWRITE);
1487 	if(consctl >= 0)
1488 		write(consctl, "rawon", 5);
1489 	print("Password: ");
1490 	e--;
1491 	for(p = buf; p <= e; p++){
1492 		c = Bgetc(&stdin);
1493 		if(c < 0){
1494 			rv = -1;
1495 			goto out;
1496 		}
1497 		if(c == '\n' || c == '\r')
1498 			break;
1499 		*p = c;
1500 	}
1501 	*p = 0;
1502 	print("\n");
1503 
1504 out:
1505 	if(consctl >= 0)
1506 		close(consctl);
1507 	return rv;
1508 }
1509 
1510 /*
1511  *  convert from latin1 to utf
1512  */
1513 static char*
1514 fromlatin1(char *from)
1515 {
1516 	char *p, *to;
1517 	Rune r;
1518 
1519 	if(os == Plan9)
1520 		return nil;
1521 
1522 	/* don't convert if we don't have to */
1523 	for(p = from; *p; p++)
1524 		if(*p & 0x80)
1525 			break;
1526 	if(*p == 0)
1527 		return nil;
1528 
1529 	to = malloc(3*strlen(from)+2);
1530 	if(to == nil)
1531 		return nil;
1532 	for(p = to; *from; from++){
1533 		r = (*from) & 0xff;
1534 		p += runetochar(p, &r);
1535 	}
1536 	*p = 0;
1537 	return to;
1538 }
1539 
1540 Dir*
1541 reallocdir(Dir *d, int dofree)
1542 {
1543 	Dir *dp;
1544 	char *p;
1545 	int nn, ng, nu, nm;
1546 	char *utf;
1547 
1548 	if(d->name == nil)
1549 		d->name = "?name?";
1550 	if(d->uid == nil)
1551 		d->uid = "?uid?";
1552 	if(d->gid == nil)
1553 		d->gid = d->uid;
1554 	if(d->muid == nil)
1555 		d->muid = d->uid;
1556 
1557 	utf = fromlatin1(d->name);
1558 	if(utf != nil)
1559 		d->name = utf;
1560 
1561 	nn = strlen(d->name)+1;
1562 	nu = strlen(d->uid)+1;
1563 	ng = strlen(d->gid)+1;
1564 	nm = strlen(d->muid)+1;
1565 	dp = malloc(sizeof(Dir)+nn+nu+ng+nm);
1566 	if(dp == nil){
1567 		if(dofree)
1568 			free(d);
1569 		if(utf != nil)
1570 			free(utf);
1571 		return nil;
1572 	}
1573 	*dp = *d;
1574 	p = (char*)&dp[1];
1575 	strcpy(p, d->name);
1576 	dp->name = p;
1577 	p += nn;
1578 	strcpy(p, d->uid);
1579 	dp->uid = p;
1580 	p += nu;
1581 	strcpy(p, d->gid);
1582 	dp->gid = p;
1583 	p += ng;
1584 	strcpy(p, d->muid);
1585 	dp->muid = p;
1586 	if(dofree)
1587 		free(d);
1588 	if(utf != nil)
1589 		free(utf);
1590 	return dp;
1591 }
1592 
1593 Dir*
1594 dir_change_name(Dir *d, char *name)
1595 {
1596 	if(d->name && strlen(d->name) >= strlen(name)){
1597 		strcpy(d->name, name);
1598 		return d;
1599 	}
1600 	d->name = name;
1601 	return reallocdir(d, 1);
1602 }
1603 
1604 Dir*
1605 dir_change_uid(Dir *d, char *name)
1606 {
1607 	if(d->uid && strlen(d->uid) >= strlen(name)){
1608 		strcpy(d->name, name);
1609 		return d;
1610 	}
1611 	d->uid = name;
1612 	return reallocdir(d, 1);
1613 }
1614 
1615 Dir*
1616 dir_change_gid(Dir *d, char *name)
1617 {
1618 	if(d->gid && strlen(d->gid) >= strlen(name)){
1619 		strcpy(d->name, name);
1620 		return d;
1621 	}
1622 	d->gid = name;
1623 	return reallocdir(d, 1);
1624 }
1625 
1626 Dir*
1627 dir_change_muid(Dir *d, char *name)
1628 {
1629 	if(d->muid && strlen(d->muid) >= strlen(name)){
1630 		strcpy(d->name, name);
1631 		return d;
1632 	}
1633 	d->muid = name;
1634 	return reallocdir(d, 1);
1635 }
1636 
1637 static int
1638 nw_mode(char dirlet, char *s)		/* NetWare file mode mapping */
1639 {
1640 	int mode = 0777;
1641 
1642 	if(dirlet == 'd')
1643 		mode |= DMDIR;
1644 
1645 	if (strlen(s) >= 10 && s[0] != '[' || s[9] != ']')
1646 		return(mode);
1647 
1648 	if (s[1] == '-')					/* can't read file */
1649 		mode &= ~0444;
1650 	if (dirlet == 'd' && s[6] == '-')			/* cannot scan dir */
1651 		mode &= ~0444;
1652 	if (s[2] == '-')					/* can't write file */
1653 		mode &= ~0222;
1654 	if (dirlet == 'd' && s[7] == '-' && s[3] == '-')	/* cannot create in, or modify dir */
1655 		mode &= ~0222;
1656 
1657 	return(mode);
1658 }
1659