]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ftpfs/proto.c
merge
[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 /*
59  *  connect to remote server, default network is "tcp/ip"
60  */
61 void
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
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
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
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
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
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
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
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*
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*
505 crackdir(char *p, String **remname, int nlst)
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         if(nlst != 0){
517                 field[0] = p;
518                 n = 1;
519         } else {
520                 n = getfields(p, field, 15, 1, " \t");
521                 if(n > 2 && strcmp(field[n-2], "->") == 0)
522                         n -= 2;
523         }
524
525         switch(os){
526         case TSO:
527                 cp = strchr(field[0], '.');
528                 if(cp){
529                         *cp++ = 0;
530                         s = s_copy(cp);
531                         d.uid = field[0];
532                 } else {
533                         s = s_copy(field[0]);
534                         d.uid = "TSO";
535                 }
536                 d.gid = "TSO";
537                 d.mode = 0666;
538                 d.length = 0;
539                 d.atime = 0;
540                 break;
541         case OS½:
542                 s = s_copy(field[n-1]);
543                 d.uid = "OS½";
544                 d.gid = d.uid;
545                 d.mode = 0666;
546                 switch(n){
547                 case 5:
548                         if(strcmp(field[1], "DIR") == 0)
549                                 d.mode |= DMDIR;
550                         d.length = atoll(field[0]);
551                         dn = getfields(field[2], dfield, 4, 1, "-");
552                         if(dn == 3)
553                                 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[3]);
554                         break;
555                 }
556                 break;
557         case Tops:
558                 if(n != 4){ /* tops directory name */
559                         safecpy(topsdir, field[0], sizeof(topsdir));
560                         return 0;
561                 }
562                 s = s_copy(field[3]);
563                 d.length = atoll(field[0]);
564                 d.mode = 0666;
565                 d.uid = "Tops";
566                 d.gid = d.uid;
567                 dn = getfields(field[1], dfield, 4, 1, "-");
568                 if(dn == 3)
569                         d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[2]);
570                 else
571                         d.atime = time(0);
572                 break;
573         case VM:
574                 switch(n){
575                 case 9:
576                         s = s_copy(field[0]);
577                         s_append(s, ".");
578                         s_append(s, field[1]);
579                         d.length = atoll(field[3]) * atoll(field[4]);
580                         if(*field[2] == 'F')
581                                 d.mode = 0666;
582                         else
583                                 d.mode = 0777;
584                         d.uid = "VM";
585                         d.gid = d.uid;
586                         dn = getfields(field[6], dfield, 4, 1, "/-");
587                         if(dn == 3)
588                                 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[7]);
589                         else
590                                 d.atime = time(0);
591                         break;
592                 case 1:
593                         s = s_copy(field[0]);
594                         d.uid = "VM";
595                         d.gid = d.uid;
596                         d.mode = 0777;
597                         d.atime = 0;
598                         break;
599                 default:
600                         return nil;
601                 }
602                 break;
603         case VMS:
604                 switch(n){
605                 case 6:
606                         for(cp = field[0]; *cp; cp++)
607                                 *cp = tolower(*cp);
608                         cp = strchr(field[0], ';');
609                         if(cp)
610                                 *cp = 0;
611                         d.mode = 0666;
612                         cp = field[0] + strlen(field[0]) - 4;
613                         if(strcmp(cp, ".dir") == 0){
614                                 d.mode |= DMDIR;
615                                 *cp = 0;
616                         }
617                         s = s_copy(field[0]);
618                         d.length = atoll(field[1]) * 512;
619                         field[4][strlen(field[4])-1] = 0;
620                         d.uid = field[4]+1;
621                         d.gid = d.uid;
622                         dn = getfields(field[2], dfield, 4, 1, "/-");
623                         if(dn == 3)
624                                 d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[3]);
625                         else
626                                 d.atime = time(0);
627                         break;
628                 default:
629                         return nil;
630                 }
631                 break;
632         case NetWare:
633                 switch(n){
634                 case 8:         /* New style */
635                         s = s_copy(field[7]);
636                         d.uid = field[2];
637                         d.gid = d.uid;
638                         d.mode = nw_mode(field[0][0], field[1]);
639                         d.length = atoll(field[3]);
640                         if(strchr(field[6], ':'))
641                                 d.atime = cracktime(field[4], field[5], nil, field[6]);
642                         else
643                                 d.atime = cracktime(field[4], field[5], field[6], nil);
644                         break;
645                 case 9:
646                         s = s_copy(field[8]);
647                         d.uid = field[2];
648                         d.gid = d.uid;
649                         d.mode = 0666;
650                         if(*field[0] == 'd')
651                                 d.mode |= DMDIR;
652                         d.length = atoll(field[3]);
653                         d.atime = cracktime(field[4], field[5], field[6], field[7]);
654                         break;
655                 case 1:
656                         s = s_copy(field[0]);
657                         d.uid = "none";
658                         d.gid = d.uid;
659                         d.mode = 0777;
660                         d.atime = 0;
661                         break;
662                 default:
663                         return nil;
664                 }
665                 break;
666         case Unix:
667         case Plan9:
668         default:
669                 switch(n){
670                 case 8:         /* ls -lg */
671                         s = s_copy(field[7]);
672                         d.uid = field[2];
673                         d.gid = d.uid;
674                         d.mode = crackmode(field[0]);
675                         d.length = atoll(field[3]);
676                         if(strchr(field[6], ':'))
677                                 d.atime = cracktime(field[4], field[5], 0, field[6]);
678                         else
679                                 d.atime = cracktime(field[4], field[5], field[6], 0);
680                         break;
681                 case 9:         /* ls -l */
682                         s = s_copy(field[8]);
683                         d.uid = field[2];
684                         d.gid = field[3];
685                         d.mode = crackmode(field[0]);
686                         d.length = atoll(field[4]);
687                         if(strchr(field[7], ':'))
688                                 d.atime = cracktime(field[5], field[6], 0, field[7]);
689                         else
690                                 d.atime = cracktime(field[5], field[6], field[7], 0);
691                         break;
692                 case 10:        /* plan 9 */
693                         s = s_copy(field[9]);
694                         d.uid = field[3];
695                         d.gid = field[4];
696                         d.mode = crackmode(field[0]);
697                         d.length = atoll(field[5]);
698                         if(strchr(field[8], ':'))
699                                 d.atime = cracktime(field[6], field[7], 0, field[8]);
700                         else
701                                 d.atime = cracktime(field[6], field[7], field[8], 0);
702                         break;
703                 case 4:         /* a Windows_NT version */
704                         s = s_copy(field[3]);
705                         d.uid = "NT";
706                         d.gid = d.uid;
707                         if(strcmp("<DIR>", field[2]) == 0){
708                                 d.length = 0;
709                                 d.mode = DMDIR|0777;
710                         } else {
711                                 d.mode = 0666;
712                                 d.length = atoll(field[2]);
713                         }
714                         dn = getfields(field[0], dfield, 4, 1, "/-");
715                         if(dn == 3)
716                                 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[1]);
717                         break;
718                 case 1:
719                         s = s_copy(field[0]);
720                         d.uid = "none";
721                         d.gid = d.uid;
722                         d.mode = 0777;
723                         d.atime = 0;
724                         break;
725                 default:
726                         return nil;
727                 }
728         }
729         d.muid = d.uid;
730         d.qid.type = (d.mode & DMDIR) ? QTDIR : QTFILE;
731         d.mtime = d.atime;
732         if(ext && (d.qid.type & QTDIR) == 0)
733                 s_append(s, ext);
734         d.name = s_to_c(s);
735
736         /* allocate a freeable dir structure */
737         dp = reallocdir(&d, 0);
738         *remname = s;
739
740         return dp;
741 }
742
743 /*
744  *  probe files in a directory to see if they are directories
745  */
746 /*
747  *  read a remote directory
748  */
749 int
750 readdir(Node *node)
751 {
752         Biobuf *bp;
753         char *line;
754         Node *np;
755         Dir *d;
756         long n;
757         int tries, x, files;
758         static int uselist;
759         int usenlist;
760         String *remname;
761
762         if(changedir(node) < 0)
763                 return -1;
764
765         usenlist = 0;
766         for(tries = 0; tries < 3; tries++){
767                 if(usenlist || usenlst)
768                         x = data(OREAD, &bp, "NLST", nil);
769                 else if(os == Unix && !uselist)
770                         x = data(OREAD, &bp, "LIST -l", nil);
771                 else
772                         x = data(OREAD, &bp, "LIST", nil);
773                 switch(x){
774                 case Extra:
775                         break;
776 /*              case TempFail:
777                         continue;
778 */
779                 default:
780                         if(os == Unix && uselist == 0){
781                                 uselist = 1;
782                                 continue;
783                         }
784                         return seterr(nosuchfile);
785                 }
786                 files = 0;
787                 while(line = Brdline(bp, '\n')){
788                         n = Blinelen(bp);
789                         if(debug)
790                                 write(2, line, n);
791                         if(n > 1 && line[n-2] == '\r')
792                                 n--;
793                         line[n - 1] = 0;
794
795                         d = crackdir(line, &remname, (usenlist || usenlst));
796                         if(d == nil)
797                                 continue;
798                         files++;
799                         np = extendpath(node, remname);
800                         d->qid.path = np->d->qid.path;
801                         d->qid.vers = np->d->qid.vers;
802                         d->type = np->d->type;
803                         d->dev = 1;                     /* mark node as valid */
804                         if(os == MVS && node == remroot){
805                                 d->qid.type = QTDIR;
806                                 d->mode |= DMDIR;
807                         }
808                         free(np->d);
809                         np->d = d;
810                 }
811                 close(Bfildes(bp));
812
813                 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
814                 case Success:
815                         if(files == 0 && !usenlst && !usenlist){
816                                 usenlist = 1;
817                                 continue;
818                         }
819                         if(files && usenlist)
820                                 usenlst = 1;
821                         if(usenlst)
822                                 node->chdirunknown = 1;
823                         return 0;
824                 case TempFail:
825                         break;
826                 default:
827                         return seterr(nosuchfile);
828                 }
829         }
830         return seterr(nosuchfile);
831 }
832
833 /*
834  *  create a remote directory
835  */
836 int
837 createdir(Node *node)
838 {
839         if(changedir(node->parent) < 0)
840                 return -1;
841         
842         sendrequest("MKD", node->d->name);
843         if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
844                 return -1;
845         return 0;
846 }
847
848 /*
849  *  change to a remote directory.
850  */
851 int
852 changedir(Node *node)
853 {
854         Node *to;
855         String *cdpath;
856
857         to = node;
858         if(to == remdir)
859                 return 0;
860
861         /* build an absolute path */
862         switch(os){
863         case Tops:
864         case VM:
865                 switch(node->depth){
866                 case 0:
867                         remdir = node;
868                         return 0;
869                 case 1:
870                         cdpath = s_clone(node->remname);
871                         break;
872                 default:
873                         return seterr(nosuchfile);
874                 }
875                 break;
876         case VMS:
877                 switch(node->depth){
878                 case 0:
879                         remdir = node;
880                         return 0;
881                 default:
882                         cdpath = s_new();
883                         vmspath(node, cdpath);
884                 }
885                 break;
886         case MVS:
887                 if(node->depth == 0)
888                         cdpath = s_clone(remrootpath);
889                 else{
890                         cdpath = s_new();
891                         mvspath(node, cdpath);
892                 }
893                 break;
894         default:
895                 if(node->depth == 0)
896                         cdpath = s_clone(remrootpath);
897                 else{
898                         cdpath = s_new();
899                         unixpath(node, cdpath);
900                 }
901                 break;
902         }
903
904         uncachedir(remdir, 0);
905
906         /*
907          *  connect, if we need a password (Incomplete)
908          *  act like it worked (best we can do).
909          */
910         sendrequest("CWD", s_to_c(cdpath));
911         s_free(cdpath);
912         switch(getreply(&ctlin, msg, sizeof(msg), 0)){
913         case Success:
914         case Incomplete:
915                 remdir = node;
916                 return 0;
917         default:
918                 return seterr(nosuchfile);
919         }
920 }
921
922 /*
923  *  read a remote file
924  */
925 int
926 readfile1(Node *node)
927 {
928         Biobuf *bp;
929         char buf[4*1024];
930         long off;
931         int n;
932         int tries;
933
934         if(changedir(node->parent) < 0)
935                 return -1;
936
937         for(tries = 0; tries < 4; tries++){
938                 switch(data(OREAD, &bp, "RETR", s_to_c(node->remname))){
939                 case Extra:
940                         break;
941                 case TempFail:
942                         continue;
943                 default:
944                         return seterr(nosuchfile);
945                 }
946                 off = 0;
947                 while((n = read(Bfildes(bp), buf, sizeof buf)) > 0){
948                         if(filewrite(node, buf, off, n) != n){
949                                 off = -1;
950                                 break;
951                         }
952                         off += n;
953                 }
954                 if(off < 0)
955                         return -1;
956
957                 /* make sure a file gets created even for a zero length file */
958                 if(off == 0)
959                         filewrite(node, buf, 0, 0);
960
961                 close(Bfildes(bp));
962                 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
963                 case Success:
964                         return off;
965                 case TempFail:
966                         continue;
967                 default:
968                         return seterr(nosuchfile);
969                 }
970         }
971         return seterr(nosuchfile);
972 }
973
974 int
975 readfile(Node *node)
976 {
977         int rv, inimage;
978
979         switch(os){
980         case MVS:
981         case Plan9:
982         case Tops:
983         case TSO:
984                 inimage = 0;
985                 break;
986         default:
987                 inimage = 1;
988                 image();
989                 break;
990         }
991
992         rv = readfile1(node);
993
994         if(inimage)
995                 ascii();
996         return rv;
997 }
998
999 /*
1000  *  write back a file
1001  */
1002 int
1003 createfile1(Node *node)
1004 {
1005         Biobuf *bp;
1006         char buf[4*1024];
1007         long off;
1008         int n;
1009
1010         if(changedir(node->parent) < 0)
1011                 return -1;
1012
1013         if(data(OWRITE, &bp, "STOR", s_to_c(node->remname)) != Extra)
1014                 return -1;
1015         for(off = 0; ; off += n){
1016                 n = fileread(node, buf, off, sizeof(buf));
1017                 if(n <= 0)
1018                         break;
1019                 write(Bfildes(bp), buf, n);
1020         }
1021         close(Bfildes(bp));
1022         if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1023                 return -1;
1024         return off;
1025 }
1026
1027 int
1028 createfile(Node *node)
1029 {
1030         int rv;
1031
1032         switch(os){
1033         case Plan9:
1034         case Tops:
1035                 break;
1036         default:
1037                 image();
1038                 break;
1039         }
1040         rv = createfile1(node);
1041         switch(os){
1042         case Plan9:
1043         case Tops:
1044                 break;
1045         default:
1046                 ascii();
1047                 break;
1048         }
1049         return rv;
1050 }
1051
1052 /*
1053  *  remove a remote file
1054  */
1055 int
1056 removefile(Node *node)
1057 {
1058         if(changedir(node->parent) < 0)
1059                 return -1;
1060         
1061         sendrequest("DELE", s_to_c(node->remname));
1062         if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1063                 return -1;
1064         return 0;
1065 }
1066
1067 /*
1068  *  remove a remote directory
1069  */
1070 int
1071 removedir(Node *node)
1072 {
1073         if(changedir(node->parent) < 0)
1074                 return -1;
1075         
1076         sendrequest("RMD", s_to_c(node->remname));
1077         if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1078                 return -1;
1079         return 0;
1080 }
1081
1082 /*
1083  *  tell remote that we're exiting and then do it
1084  */
1085 void
1086 quit(void)
1087 {
1088         sendrequest("QUIT", nil);
1089         getreply(&ctlin, msg, sizeof(msg), 0);
1090         exits(0);
1091 }
1092
1093 /*
1094  *  send a request
1095  */
1096 static void
1097 sendrequest(char *a, char *b)
1098 {
1099         char buf[2*1024];
1100         int n;
1101
1102         n = strlen(a)+2+1;
1103         if(b != nil)
1104                 n += strlen(b)+1;
1105         if(n >= sizeof(buf))
1106                 fatal("proto request too long");
1107         strcpy(buf, a);
1108         if(b != nil){
1109                 strcat(buf, " ");
1110                 strcat(buf, b);
1111         }
1112         strcat(buf, "\r\n");
1113         n = strlen(buf);
1114         if(write(ctlfd, buf, n) != n)
1115                 fatal("remote side hung up");
1116         if(debug)
1117                 write(2, buf, n);
1118         lastsend = time(0);
1119 }
1120
1121 /*
1122  *  replies codes are in the range [100, 999] and may contain multiple lines of
1123  *  continuation.
1124  */
1125 static int
1126 getreply(Biobuf *bp, char *msg, int len, int printreply)
1127 {
1128         char *line;
1129         char *p;
1130         int rv;
1131         int i, n;
1132
1133         while(line = Brdline(bp, '\n')){
1134                 /* add line to message buffer, strip off \r */
1135                 n = Blinelen(bp);
1136                 if(n > 1 && line[n-2] == '\r'){
1137                         n--;
1138                         line[n-1] = '\n';
1139                 }
1140                 if(printreply && !quiet)
1141                         write(1, line, n);
1142                 else if(debug)
1143                         write(2, line, n);
1144                 if(n > len - 1)
1145                         i = len - 1;
1146                 else
1147                         i = n;
1148                 if(i > 0){
1149                         memmove(msg, line, i);
1150                         msg += i;
1151                         len -= i;
1152                         *msg = 0;
1153                 }
1154
1155                 /* stop if not a continuation */
1156                 rv = strtol(line, &p, 10);
1157                 if(rv >= 100 && rv < 600 && p==line+3 && *p != '-')
1158                         return rv/100;
1159
1160                 /* tell user about continuations */
1161                 if(!debug && !quiet && !printreply)
1162                         write(2, line, n);
1163         }
1164
1165         fatal("remote side closed connection");
1166         return 0;
1167 }
1168
1169 /*
1170  *  Announce on a local port and tell its address to the the remote side
1171  */
1172 static int
1173 port(void)
1174 {
1175         char buf[256];
1176         int n, fd;
1177         char *ptr;
1178         uchar ipaddr[IPaddrlen];
1179         int port;
1180
1181         /* get a channel to listen on, let kernel pick the port number */
1182         sprint(buf, "%s!*!0", net);
1183         listenfd = announce(buf, netdir);
1184         if(listenfd < 0)
1185                 return seterr("can't announce");
1186
1187         /* get the local address and port number */
1188         sprint(buf, "%s/local", netdir);
1189         fd = open(buf, OREAD);
1190         if(fd < 0)
1191                 return seterr("opening %s: %r", buf);
1192         n = read(fd, buf, sizeof(buf)-1);
1193         close(fd);
1194         if(n <= 0)
1195                 return seterr("opening %s/local: %r", netdir);
1196         buf[n] = 0;
1197         ptr = strchr(buf, ' ');
1198         if(ptr)
1199                 *ptr = 0;
1200         ptr = strchr(buf, '!')+1;
1201         port = atoi(ptr);
1202
1203         memset(ipaddr, 0, IPaddrlen);
1204         if (*net){
1205                 strcpy(buf, net);
1206                 ptr = strchr(buf +1, '/');
1207                 if (ptr)
1208                         *ptr = 0;
1209                 myipaddr(ipaddr, buf);
1210         }
1211
1212         /* tell remote side */
1213         sprint(buf, "PORT %d,%d,%d,%d,%d,%d", ipaddr[IPv4off+0], ipaddr[IPv4off+1],
1214                 ipaddr[IPv4off+2], ipaddr[IPv4off+3], port>>8, port&0xff);
1215         sendrequest(buf, nil);
1216         if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1217                 return seterr(msg);
1218         return 0;
1219 }
1220
1221 /*
1222  *  have server call back for a data connection
1223  */
1224 static int
1225 active(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1226 {
1227         int cfd, dfd, rv;
1228         char newdir[Maxpath];
1229         char datafile[Maxpath + 6];
1230         TLSconn conn;
1231
1232         if(port() < 0)
1233                 return TempFail;
1234
1235         sendrequest(cmda, cmdb);
1236
1237         rv = getreply(&ctlin, msg, sizeof(msg), 0);
1238         if(rv != Extra){
1239                 close(listenfd);
1240                 return rv;
1241         }
1242
1243         /* wait for a new call */
1244         cfd = listen(netdir, newdir);
1245         if(cfd < 0)
1246                 fatal("waiting for data connection");
1247         close(listenfd);
1248
1249         /* open it's data connection and close the control connection */
1250         sprint(datafile, "%s/data", newdir);
1251         dfd = open(datafile, ORDWR);
1252         close(cfd);
1253         if(dfd < 0)
1254                 fatal("opening data connection");
1255
1256         if(usetls){
1257                 memset(&conn, 0, sizeof(conn));
1258                 dfd = tlsClient(dfd, &conn);
1259                 if(dfd < 0)
1260                         fatal("starting tls: %r");
1261                 free(conn.cert);
1262         }
1263
1264         Binit(&dbuf, dfd, mode);
1265         *bpp = &dbuf;
1266         return Extra;
1267 }
1268
1269 /*
1270  *  call out for a data connection
1271  */
1272 static int
1273 passive(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1274 {
1275         char msg[1024];
1276         char ds[1024];
1277         char *f[6];
1278         char *p;
1279         int x, fd;
1280         TLSconn conn;
1281
1282         if(nopassive)
1283                 return Impossible;
1284
1285         sendrequest("PASV", nil);
1286         if(getreply(&ctlin, msg, sizeof(msg), 0) != Success){
1287                 nopassive = 1;
1288                 return Impossible;
1289         }
1290
1291         /* get address and port number from reply, this is AI */
1292         p = strchr(msg, '(');
1293         if(p == 0){
1294                 for(p = msg+3; *p; p++)
1295                         if(isdigit(*p))
1296                                 break;
1297         } else
1298                 p++;
1299         if(getfields(p, f, 6, 0, ",") < 6){
1300                 if(debug)
1301                         fprint(2, "passive mode protocol botch: %s\n", msg);
1302                 werrstr("ftp protocol botch");
1303                 nopassive = 1;
1304                 return Impossible;
1305         }
1306         snprint(ds, sizeof(ds), "%s!%s.%s.%s.%s!%d", net,
1307                 f[0], f[1], f[2], f[3],
1308                 ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff));
1309
1310         /* open data connection */
1311         fd = dial(ds, 0, 0, 0);
1312         if(fd < 0){
1313                 if(debug)
1314                         fprint(2, "passive mode connect to %s failed: %r\n", ds);
1315                 nopassive = 1;
1316                 return TempFail;
1317         }
1318
1319         /* tell remote to send a file */
1320         sendrequest(cmda, cmdb);
1321         x = getreply(&ctlin, msg, sizeof(msg), 0);
1322         if(x != Extra){
1323                 close(fd);
1324                 if(debug)
1325                         fprint(2, "passive mode retrieve failed: %s\n", msg);
1326                 werrstr(msg);
1327                 return x;
1328         }
1329
1330         if(usetls){
1331                 memset(&conn, 0, sizeof(conn));
1332                 fd = tlsClient(fd, &conn);
1333                 if(fd < 0)
1334                         fatal("starting tls: %r");
1335                 free(conn.cert);
1336         }
1337         Binit(&dbuf, fd, mode);
1338
1339         *bpp = &dbuf;
1340         return Extra;
1341 }
1342
1343 static int
1344 data(int mode, Biobuf **bpp, char* cmda, char *cmdb)
1345 {
1346         int x;
1347
1348         x = passive(mode, bpp, cmda, cmdb);
1349         if(x != Impossible)
1350                 return x;
1351         return active(mode, bpp, cmda, cmdb);
1352 }
1353
1354 /*
1355  *  used for keep alives
1356  */
1357 void
1358 nop(void)
1359 {
1360         if(lastsend - time(0) < 15)
1361                 return;
1362         sendrequest("PWD", nil);
1363         getreply(&ctlin, msg, sizeof(msg), 0);
1364 }
1365
1366 /*
1367  *  turn a vms spec into a path
1368  */
1369 static Node*
1370 vmsextendpath(Node *np, char *name)
1371 {
1372         np = extendpath(np, s_copy(name));
1373         if(!ISVALID(np)){
1374                 np->d->qid.type = QTDIR;
1375                 np->d->atime = time(0);
1376                 np->d->mtime = np->d->atime;
1377                 strcpy(np->d->uid, "who");
1378                 strcpy(np->d->gid, "cares");
1379                 np->d->mode = DMDIR|0777;
1380                 np->d->length = 0;
1381                 if(changedir(np) >= 0)
1382                         VALID(np);
1383         }
1384         return np;
1385 }
1386 static Node*
1387 vmsdir(char *name)
1388 {
1389         char *cp;
1390         Node *np;
1391         char *oname;
1392
1393         np = remroot;
1394         cp = strchr(name, '[');
1395         if(cp)
1396                 strcpy(cp, cp+1);
1397         cp = strchr(name, ']');
1398         if(cp)
1399                 *cp = 0;
1400         oname = name = strdup(name);
1401         if(name == 0)
1402                 return 0;
1403
1404         while(cp = strchr(name, '.')){
1405                 *cp = 0;
1406                 np = vmsextendpath(np, name);
1407                 name = cp+1;
1408         }
1409         np = vmsextendpath(np, name);
1410
1411         /*
1412          *  walk back to first accessible directory
1413          */
1414         for(; np->parent != np; np = np->parent)
1415                 if(ISVALID(np)){
1416                         CACHED(np->parent);
1417                         break;
1418                 }
1419
1420         free(oname);
1421         return np;
1422 }
1423
1424 /*
1425  *  walk up the tree building a VMS style path
1426  */
1427 static void
1428 vmspath(Node *node, String *path)
1429 {
1430         char *p;
1431         int n;
1432
1433         if(node->depth == 1){
1434                 p = strchr(s_to_c(node->remname), ':');
1435                 if(p){
1436                         n = p - s_to_c(node->remname) + 1;
1437                         s_nappend(path, s_to_c(node->remname), n);
1438                         s_append(path, "[");
1439                         s_append(path, p+1);
1440                 } else {
1441                         s_append(path, "[");
1442                         s_append(path, s_to_c(node->remname));
1443                 }
1444                 s_append(path, "]");
1445                 return;
1446         }
1447         vmspath(node->parent, path);
1448         s_append(path, ".");
1449         s_append(path, s_to_c(node->remname));
1450 }
1451
1452 /*
1453  *  walk up the tree building a Unix style path
1454  */
1455 static void
1456 unixpath(Node *node, String *path)
1457 {
1458         if(node == node->parent){
1459                 s_append(path, s_to_c(remrootpath));
1460                 return;
1461         }
1462         unixpath(node->parent, path);
1463         if(s_len(path) > 0 && strcmp(s_to_c(path), "/") != 0)
1464                 s_append(path, "/");
1465         s_append(path, s_to_c(node->remname));
1466 }
1467
1468 /*
1469  *  walk up the tree building a MVS style path
1470  */
1471 static void
1472 mvspath(Node *node, String *path)
1473 {
1474         if(node == node->parent){
1475                 s_append(path, s_to_c(remrootpath));
1476                 return;
1477         }
1478         mvspath(node->parent, path);
1479         if(s_len(path) > 0)
1480                 s_append(path, ".");
1481         s_append(path, s_to_c(node->remname));
1482 }
1483
1484 static int
1485 getpassword(char *buf, char *e)
1486 {
1487         char *p;
1488         int c;
1489         int consctl, rv = 0;
1490
1491         consctl = open("/dev/consctl", OWRITE);
1492         if(consctl >= 0)
1493                 write(consctl, "rawon", 5);
1494         print("Password: ");
1495         e--;
1496         for(p = buf; p <= e; p++){
1497                 c = Bgetc(&stdin);
1498                 if(c < 0){
1499                         rv = -1;
1500                         goto out;
1501                 }
1502                 if(c == '\n' || c == '\r')
1503                         break;
1504                 *p = c;
1505         }
1506         *p = 0;
1507         print("\n");
1508
1509 out:
1510         if(consctl >= 0)
1511                 close(consctl);
1512         return rv;
1513 }
1514
1515 /*
1516  *  convert from latin1 to utf
1517  */
1518 static char*
1519 fromlatin1(char *from)
1520 {
1521         char *p, *to;
1522         Rune r;
1523
1524         if(os == Plan9)
1525                 return nil;
1526
1527         /* don't convert if we don't have to */
1528         for(p = from; *p; p++)
1529                 if(*p & 0x80)
1530                         break;
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 }