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