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