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