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