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