24 Node *remdir; /* current directory on remote machine */
25 Node *remroot; /* root directory on remote machine */
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 */
36 char topsdir[64]; /* name of listed directory for TOPS */
37 String *remrootpath; /* path on remote side to remote root */
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);
63 memset(&conn, 0, sizeof(conn));
64 if((*fd = tlsClient(*fd, &conn)) < 0)
65 fatal("starting tls: %r");
71 * connect to remote server, default network is "tcp/ip"
79 Binit(&stdin, 0, OREAD); /* init for later use */
81 ctlfd = dial(netmkaddr(dest, "tcp", "ftp"), 0, dir, 0);
83 fprint(2, "can't dial %s: %r\n", dest);
87 Binit(&ctlin, ctlfd, OREAD);
89 /* remember network for the data connections */
90 p = strrchr(dir+1, '/');
92 fatal("wrong dial(2) linked with ftp");
94 safecpy(net, dir, sizeof(net));
96 /* wait for hello from other side */
97 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
99 if(strstr(msg, "Plan 9"))
103 sendrequest("AUTH", "TLS");
104 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
105 fatal("bad auth tls");
109 Binit(&ctlin, ctlfd, OREAD);
111 sendrequest("PBSZ", "0");
112 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
114 sendrequest("PROT", "P");
115 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
121 * login to remote system
124 rlogin(char *rsys, char *keyspec)
132 if(up == nil && os != Plan9)
133 up = auth_getuserpasswd(auth_getkey, "proto=pass server=%s service=ftp %s", rsys, keyspec);
135 sendrequest("USER", up->user);
137 print("User[default = %s]: ", user);
138 line = Brdline(&stdin, '\n');
141 line[Blinelen(&stdin)-1] = 0;
146 sendrequest("USER", user);
148 switch(getreply(&ctlin, msg, sizeof(msg), 1)){
159 sendrequest("PASS", up->passwd);
161 if(getpassword(pass, pass+sizeof(pass)) < 0)
163 sendrequest("PASS", pass);
165 if(getreply(&ctlin, msg, sizeof(msg), 1) == Success){
166 if(strstr(msg, "Sess#"))
173 memset(up, 0, sizeof(*up));
179 * login to remote system with given user name and password.
182 clogin(char *cuser, char *cpassword)
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)){
197 fatal("login failed");
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#"))
210 * find out about the other side. go to it's root if requested. set
211 * image mode if a Plan9 system.
214 preamble(char *mountroot)
221 * create a root directory mirror
223 remroot = newnode(0, s_copy("/"));
224 remroot->d->qid.type = QTDIR;
225 remroot->d->mode = DMDIR|0777;
231 sendrequest("SYST", nil);
232 switch(getreply(&ctlin, msg, sizeof(msg), 1)){
234 for(o = oslist; o->os != Unknown; o++)
235 if(strncmp(msg+4, o->name, strlen(o->name)) == 0)
248 remrootpath = s_reset(remrootpath);
252 * Request long, rather than 8.3 filenames,
253 * where the Servers & Volume support them.
255 sendrequest("SITE LONG", nil);
256 getreply(&ctlin, msg, sizeof(msg), 0);
261 * go to the remote root, if asked
264 sendrequest("CWD", mountroot);
265 getreply(&ctlin, msg, sizeof(msg), 0);
267 s_append(remrootpath, "/usr/");
268 s_append(remrootpath, user);
272 * get the root directory
274 sendrequest("PWD", nil);
275 rv = getreply(&ctlin, msg, sizeof(msg), 1);
277 sendrequest("XPWD", nil);
278 rv = getreply(&ctlin, msg, sizeof(msg), 1);
281 p = strchr(msg, '"');
287 s_append(s_reset(remrootpath), p);
296 * top directory is a figment of our imagination.
297 * make it permanently cached & valid.
301 remroot->d->atime = time(0) + 100000;
304 * no initial directory. We are in the
307 remdir = newtopsdir("???");
309 if(os == Tops && readdir(remdir) >= 0){
312 remdir->remname = s_copy(topsdir);
318 * top directory is a figment of our imagination.
319 * make it permanently cached & valid.
323 remroot->d->atime = time(0) + 100000;
326 * get current directory
328 sendrequest("PWD", nil);
329 rv = getreply(&ctlin, msg, sizeof(msg), 1);
331 sendrequest("XPWD", nil);
332 rv = getreply(&ctlin, msg, sizeof(msg), 1);
335 p = strchr(msg, '"');
341 remroot = remdir = vmsdir(p);
358 sendrequest("TYPE A", nil);
359 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
363 fatal("can't set type to ascii");
370 sendrequest("TYPE I", nil);
371 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
375 fatal("can't set type to image/binary");
380 * decode the time fields, return seconds since epoch began
382 char *monthchars = "janfebmaraprmayjunjulaugsepoctnovdec";
386 cracktime(char *month, char *day, char *yr, char *hms)
395 now = *localtime(time(0));
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)
405 for(p = month; *p; p++)
407 for(i = 0; i < 12; i++)
408 if(strncmp(&monthchars[i*3], month, 3) == 0){
417 tm.hour = strtol(hms, &p, 0);
419 tm.min = strtol(p+1, &p, 0);
421 tm.sec = strtol(p+1, &p, 0);
423 if(tolower(*p) == 'p')
432 if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
436 /* convert to epoch seconds */
441 * decode a Unix or Plan 9 file mode
452 case 10: /* unix and new style plan 9 */
465 case 11: /* old style plan 9 */
481 for(i = 0; i < 3; i++){
487 if(*p == 'x' || *p == 's' || *p == 'S')
495 * find first punctuation
510 * decode a Unix or Plan 9 directory listing
513 crackdir(char *p, String **remname, int nlst)
522 memset(&d, 0, sizeof(d));
528 n = getfields(p, field, 15, 1, " \t");
529 if(n > 2 && strcmp(field[n-2], "->") == 0)
535 cp = strchr(field[0], '.');
541 s = s_copy(field[0]);
550 s = s_copy(field[n-1]);
556 if(strcmp(field[1], "DIR") == 0)
558 d.length = atoll(field[0]);
559 dn = getfields(field[2], dfield, 4, 1, "-");
561 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[3]);
566 if(n != 4){ /* tops directory name */
567 safecpy(topsdir, field[0], sizeof(topsdir));
570 s = s_copy(field[3]);
571 d.length = atoll(field[0]);
575 dn = getfields(field[1], dfield, 4, 1, "-");
577 d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[2]);
584 s = s_copy(field[0]);
586 s_append(s, field[1]);
587 d.length = atoll(field[3]) * atoll(field[4]);
594 dn = getfields(field[6], dfield, 4, 1, "/-");
596 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[7]);
601 s = s_copy(field[0]);
614 for(cp = field[0]; *cp; cp++)
616 cp = strchr(field[0], ';');
620 cp = field[0] + strlen(field[0]) - 4;
621 if(strcmp(cp, ".dir") == 0){
625 s = s_copy(field[0]);
626 d.length = atoll(field[1]) * 512;
627 field[4][strlen(field[4])-1] = 0;
630 dn = getfields(field[2], dfield, 4, 1, "/-");
632 d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[3]);
642 case 8: /* New style */
643 s = s_copy(field[7]);
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]);
651 d.atime = cracktime(field[4], field[5], field[6], nil);
654 s = s_copy(field[8]);
660 d.length = atoll(field[3]);
661 d.atime = cracktime(field[4], field[5], field[6], field[7]);
664 s = s_copy(field[0]);
679 s = s_copy(field[7]);
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]);
687 d.atime = cracktime(field[4], field[5], field[6], 0);
690 s = s_copy(field[8]);
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]);
698 d.atime = cracktime(field[5], field[6], field[7], 0);
700 case 10: /* plan 9 */
701 s = s_copy(field[9]);
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]);
709 d.atime = cracktime(field[6], field[7], field[8], 0);
711 case 4: /* a Windows_NT version */
712 s = s_copy(field[3]);
715 if(strcmp("<DIR>", field[2]) == 0){
720 d.length = atoll(field[2]);
722 dn = getfields(field[0], dfield, 4, 1, "/-");
724 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[1]);
727 s = s_copy(field[0]);
738 d.qid.type = (d.mode & DMDIR) ? QTDIR : QTFILE;
740 if(ext && (d.qid.type & QTDIR) == 0)
744 /* allocate a freeable dir structure */
745 dp = reallocdir(&d, 0);
752 * probe files in a directory to see if they are directories
755 * read a remote directory
770 if(changedir(node) < 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);
780 x = data(OREAD, &bp, "LIST", nil);
788 if(os == Unix && uselist == 0){
792 return seterr(nosuchfile);
795 while(line = Brdline(bp, '\n')){
799 if(n > 1 && line[n-2] == '\r')
803 d = crackdir(line, &remname, (usenlist || usenlst));
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){
821 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
823 if(files == 0 && !usenlst && !usenlist){
827 if(files && usenlist)
830 node->chdirunknown = 1;
835 return seterr(nosuchfile);
838 return seterr(nosuchfile);
842 * create a remote directory
845 createdir(Node *node)
847 if(changedir(node->parent) < 0)
850 sendrequest("MKD", node->d->name);
851 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
857 * change to a remote directory.
860 changedir(Node *node)
869 /* build an absolute path */
878 cdpath = s_clone(node->remname);
881 return seterr(nosuchfile);
891 vmspath(node, cdpath);
896 cdpath = s_clone(remrootpath);
899 mvspath(node, cdpath);
904 cdpath = s_clone(remrootpath);
907 unixpath(node, cdpath);
912 uncachedir(remdir, 0);
915 * connect, if we need a password (Incomplete)
916 * act like it worked (best we can do).
918 sendrequest("CWD", s_to_c(cdpath));
920 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
926 return seterr(nosuchfile);
934 readfile1(Node *node)
942 if(changedir(node->parent) < 0)
945 for(tries = 0; tries < 4; tries++){
946 switch(data(OREAD, &bp, "RETR", s_to_c(node->remname))){
952 return seterr(nosuchfile);
955 while((n = read(Bfildes(bp), buf, sizeof buf)) > 0){
956 if(filewrite(node, buf, off, n) != n){
965 /* make sure a file gets created even for a zero length file */
967 filewrite(node, buf, 0, 0);
970 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
976 return seterr(nosuchfile);
979 return seterr(nosuchfile);
1000 rv = readfile1(node);
1011 createfile1(Node *node)
1018 if(changedir(node->parent) < 0)
1021 if(data(OWRITE, &bp, "STOR", s_to_c(node->remname)) != Extra)
1023 for(off = 0; ; off += n){
1024 n = fileread(node, buf, off, sizeof(buf));
1027 write(Bfildes(bp), buf, n);
1030 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1036 createfile(Node *node)
1048 rv = createfile1(node);
1061 * remove a remote file
1064 removefile(Node *node)
1066 if(changedir(node->parent) < 0)
1069 sendrequest("DELE", s_to_c(node->remname));
1070 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1076 * remove a remote directory
1079 removedir(Node *node)
1081 if(changedir(node->parent) < 0)
1084 sendrequest("RMD", s_to_c(node->remname));
1085 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1091 * tell remote that we're exiting and then do it
1096 sendrequest("QUIT", nil);
1097 getreply(&ctlin, msg, sizeof(msg), 0);
1105 sendrequest(char *a, char *b)
1113 if(n >= sizeof(buf))
1114 fatal("proto request too long");
1120 strcat(buf, "\r\n");
1122 if(write(ctlfd, buf, n) != n)
1123 fatal("remote side hung up");
1130 * replies codes are in the range [100, 999] and may contain multiple lines of
1134 getreply(Biobuf *bp, char *msg, int len, int printreply)
1141 while(line = Brdline(bp, '\n')){
1142 /* add line to message buffer, strip off \r */
1144 if(n > 1 && line[n-2] == '\r'){
1148 if(printreply && !quiet)
1157 memmove(msg, line, i);
1163 /* stop if not a continuation */
1164 rv = strtol(line, &p, 10);
1165 if(rv >= 100 && rv < 600 && p==line+3 && *p != '-')
1168 /* tell user about continuations */
1169 if(!debug && !quiet && !printreply)
1173 fatal("remote side closed connection");
1178 * Announce on a local port and tell its address to the the remote side
1186 uchar ipaddr[IPaddrlen];
1189 /* get a channel to listen on, let kernel pick the port number */
1190 sprint(buf, "%s!*!0", net);
1191 listenfd = announce(buf, netdir);
1193 return seterr("can't announce");
1195 /* get the local address and port number */
1196 sprint(buf, "%s/local", netdir);
1197 fd = open(buf, OREAD);
1199 return seterr("opening %s: %r", buf);
1200 n = read(fd, buf, sizeof(buf)-1);
1203 return seterr("opening %s/local: %r", netdir);
1205 ptr = strchr(buf, ' ');
1208 ptr = strchr(buf, '!')+1;
1211 memset(ipaddr, 0, IPaddrlen);
1214 ptr = strchr(buf +1, '/');
1217 myipaddr(ipaddr, buf);
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)
1230 * have server call back for a data connection
1233 active(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1236 char newdir[Maxpath];
1237 char datafile[Maxpath + 6];
1242 sendrequest(cmda, cmdb);
1244 rv = getreply(&ctlin, msg, sizeof(msg), 0);
1250 /* wait for a new call */
1251 cfd = listen(netdir, newdir);
1253 fatal("waiting for data connection");
1256 /* open it's data connection and close the control connection */
1257 sprint(datafile, "%s/data", newdir);
1258 dfd = open(datafile, ORDWR);
1261 fatal("opening data connection");
1266 Binit(&dbuf, dfd, mode);
1272 * call out for a data connection
1275 passive(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1286 sendrequest("PASV", nil);
1287 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success){
1292 /* get address and port number from reply, this is AI */
1293 p = strchr(msg, '(');
1295 for(p = msg+3; *p; p++)
1300 if(getfields(p, f, 6, 0, ",") < 6){
1302 fprint(2, "passive mode protocol botch: %s\n", msg);
1303 werrstr("ftp protocol botch");
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));
1311 /* open data connection */
1312 fd = dial(ds, 0, 0, 0);
1315 fprint(2, "passive mode connect to %s failed: %r\n", ds);
1320 /* tell remote to send a file */
1321 sendrequest(cmda, cmdb);
1322 x = getreply(&ctlin, msg, sizeof(msg), 0);
1326 fprint(2, "passive mode retrieve failed: %s\n", msg);
1334 Binit(&dbuf, fd, mode);
1341 data(int mode, Biobuf **bpp, char* cmda, char *cmdb)
1345 x = passive(mode, bpp, cmda, cmdb);
1348 return active(mode, bpp, cmda, cmdb);
1352 * used for keep alives
1357 if(lastsend - time(0) < 15)
1359 sendrequest("PWD", nil);
1360 getreply(&ctlin, msg, sizeof(msg), 0);
1364 * turn a vms spec into a path
1367 vmsextendpath(Node *np, char *name)
1369 np = extendpath(np, s_copy(name));
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;
1378 if(changedir(np) >= 0)
1391 cp = strchr(name, '[');
1394 cp = strchr(name, ']');
1397 oname = name = strdup(name);
1401 while(cp = strchr(name, '.')){
1403 np = vmsextendpath(np, name);
1406 np = vmsextendpath(np, name);
1409 * walk back to first accessible directory
1411 for(; np->parent != np; np = np->parent)
1422 * walk up the tree building a VMS style path
1425 vmspath(Node *node, String *path)
1430 if(node->depth == 1){
1431 p = strchr(s_to_c(node->remname), ':');
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);
1438 s_append(path, "[");
1439 s_append(path, s_to_c(node->remname));
1441 s_append(path, "]");
1444 vmspath(node->parent, path);
1445 s_append(path, ".");
1446 s_append(path, s_to_c(node->remname));
1450 * walk up the tree building a Unix style path
1453 unixpath(Node *node, String *path)
1455 if(node == node->parent){
1456 s_append(path, s_to_c(remrootpath));
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));
1466 * walk up the tree building a MVS style path
1469 mvspath(Node *node, String *path)
1471 if(node == node->parent){
1472 s_append(path, s_to_c(remrootpath));
1475 mvspath(node->parent, path);
1477 s_append(path, ".");
1478 s_append(path, s_to_c(node->remname));
1482 getpassword(char *buf, char *e)
1486 int consctl, rv = 0;
1488 consctl = open("/dev/consctl", OWRITE);
1490 write(consctl, "rawon", 5);
1491 print("Password: ");
1493 for(p = buf; p <= e; p++){
1499 if(c == '\n' || c == '\r')
1513 * convert from latin1 to utf
1516 fromlatin1(char *from)
1525 /* don't convert if we don't have to */
1526 for(p = from; *p; p += n){
1527 n = chartorune(&r, p);
1534 to = malloc(UTFmax*strlen(from)+2);
1537 for(p = to; *from; from++){
1539 p += runetochar(p, &r);
1546 reallocdir(Dir *d, int dofree)
1562 utf = fromlatin1(d->name);
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);
1599 dir_change_name(Dir *d, char *name)
1601 if(d->name && strlen(d->name) >= strlen(name)){
1602 strcpy(d->name, name);
1606 return reallocdir(d, 1);
1610 dir_change_uid(Dir *d, char *name)
1612 if(d->uid && strlen(d->uid) >= strlen(name)){
1613 strcpy(d->name, name);
1617 return reallocdir(d, 1);
1621 dir_change_gid(Dir *d, char *name)
1623 if(d->gid && strlen(d->gid) >= strlen(name)){
1624 strcpy(d->name, name);
1628 return reallocdir(d, 1);
1632 dir_change_muid(Dir *d, char *name)
1634 if(d->muid && strlen(d->muid) >= strlen(name)){
1635 strcpy(d->name, name);
1639 return reallocdir(d, 1);
1643 nw_mode(char dirlet, char *s) /* NetWare file mode mapping */
1650 if (strlen(s) >= 10 && s[0] != '[' || s[9] != ']')
1653 if (s[1] == '-') /* can't read file */
1655 if (dirlet == 'd' && s[6] == '-') /* cannot scan dir */
1657 if (s[2] == '-') /* can't write file */
1659 if (dirlet == 'd' && s[7] == '-' && s[3] == '-') /* cannot create in, or modify dir */