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);
59 * connect to remote server, default network is "tcp/ip"
68 Binit(&stdin, 0, OREAD); /* init for later use */
70 ctlfd = dial(netmkaddr(dest, "tcp", "ftp"), 0, dir, 0);
72 fprint(2, "can't dial %s: %r\n", dest);
76 Binit(&ctlin, ctlfd, OREAD);
78 /* remember network for the data connections */
79 p = strrchr(dir+1, '/');
81 fatal("wrong dial(2) linked with ftp");
83 safecpy(net, dir, sizeof(net));
85 /* wait for hello from other side */
86 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
88 if(strstr(msg, "Plan 9"))
92 sendrequest("AUTH", "TLS");
93 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
94 fatal("bad auth tls");
96 ctlfd = tlsClient(ctlfd, &conn);
98 fatal("starting tls: %r");
101 Binit(&ctlin, ctlfd, OREAD);
103 sendrequest("PBSZ", "0");
104 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
106 sendrequest("PROT", "P");
107 if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
113 * login to remote system
116 rlogin(char *rsys, char *keyspec)
124 if(up == nil && os != Plan9)
125 up = auth_getuserpasswd(auth_getkey, "proto=pass server=%s service=ftp %s", rsys, keyspec);
127 sendrequest("USER", up->user);
129 print("User[default = %s]: ", user);
130 line = Brdline(&stdin, '\n');
133 line[Blinelen(&stdin)-1] = 0;
138 sendrequest("USER", user);
140 switch(getreply(&ctlin, msg, sizeof(msg), 1)){
151 sendrequest("PASS", up->passwd);
153 if(getpassword(pass, pass+sizeof(pass)) < 0)
155 sendrequest("PASS", pass);
157 if(getreply(&ctlin, msg, sizeof(msg), 1) == Success){
158 if(strstr(msg, "Sess#"))
165 memset(up, 0, sizeof(*up));
171 * login to remote system with given user name and password.
174 clogin(char *cuser, char *cpassword)
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)){
189 fatal("login failed");
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#"))
202 * find out about the other side. go to it's root if requested. set
203 * image mode if a Plan9 system.
206 preamble(char *mountroot)
213 * create a root directory mirror
215 remroot = newnode(0, s_copy("/"));
216 remroot->d->qid.type = QTDIR;
217 remroot->d->mode = DMDIR|0777;
223 sendrequest("SYST", nil);
224 switch(getreply(&ctlin, msg, sizeof(msg), 1)){
226 for(o = oslist; o->os != Unknown; o++)
227 if(strncmp(msg+4, o->name, strlen(o->name)) == 0)
240 remrootpath = s_reset(remrootpath);
244 * Request long, rather than 8.3 filenames,
245 * where the Servers & Volume support them.
247 sendrequest("SITE LONG", nil);
248 getreply(&ctlin, msg, sizeof(msg), 0);
253 * go to the remote root, if asked
256 sendrequest("CWD", mountroot);
257 getreply(&ctlin, msg, sizeof(msg), 0);
259 s_append(remrootpath, "/usr/");
260 s_append(remrootpath, user);
264 * get the root directory
266 sendrequest("PWD", nil);
267 rv = getreply(&ctlin, msg, sizeof(msg), 1);
269 sendrequest("XPWD", nil);
270 rv = getreply(&ctlin, msg, sizeof(msg), 1);
273 p = strchr(msg, '"');
279 s_append(s_reset(remrootpath), p);
288 * top directory is a figment of our imagination.
289 * make it permanently cached & valid.
293 remroot->d->atime = time(0) + 100000;
296 * no initial directory. We are in the
299 remdir = newtopsdir("???");
301 if(os == Tops && readdir(remdir) >= 0){
304 remdir->remname = s_copy(topsdir);
310 * top directory is a figment of our imagination.
311 * make it permanently cached & valid.
315 remroot->d->atime = time(0) + 100000;
318 * get current directory
320 sendrequest("PWD", nil);
321 rv = getreply(&ctlin, msg, sizeof(msg), 1);
323 sendrequest("XPWD", nil);
324 rv = getreply(&ctlin, msg, sizeof(msg), 1);
327 p = strchr(msg, '"');
333 remroot = remdir = vmsdir(p);
350 sendrequest("TYPE A", nil);
351 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
355 fatal("can't set type to ascii");
362 sendrequest("TYPE I", nil);
363 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
367 fatal("can't set type to image/binary");
372 * decode the time fields, return seconds since epoch began
374 char *monthchars = "janfebmaraprmayjunjulaugsepoctnovdec";
378 cracktime(char *month, char *day, char *yr, char *hms)
387 now = *localtime(time(0));
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)
397 for(p = month; *p; p++)
399 for(i = 0; i < 12; i++)
400 if(strncmp(&monthchars[i*3], month, 3) == 0){
409 tm.hour = strtol(hms, &p, 0);
411 tm.min = strtol(p+1, &p, 0);
413 tm.sec = strtol(p+1, &p, 0);
415 if(tolower(*p) == 'p')
424 if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
428 /* convert to epoch seconds */
433 * decode a Unix or Plan 9 file mode
444 case 10: /* unix and new style plan 9 */
457 case 11: /* old style plan 9 */
473 for(i = 0; i < 3; i++){
479 if(*p == 'x' || *p == 's' || *p == 'S')
487 * find first punctuation
502 * decode a Unix or Plan 9 directory listing
505 crackdir(char *p, String **remname, int nlst)
514 memset(&d, 0, sizeof(d));
520 n = getfields(p, field, 15, 1, " \t");
521 if(n > 2 && strcmp(field[n-2], "->") == 0)
527 cp = strchr(field[0], '.');
533 s = s_copy(field[0]);
542 s = s_copy(field[n-1]);
548 if(strcmp(field[1], "DIR") == 0)
550 d.length = atoll(field[0]);
551 dn = getfields(field[2], dfield, 4, 1, "-");
553 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[3]);
558 if(n != 4){ /* tops directory name */
559 safecpy(topsdir, field[0], sizeof(topsdir));
562 s = s_copy(field[3]);
563 d.length = atoll(field[0]);
567 dn = getfields(field[1], dfield, 4, 1, "-");
569 d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[2]);
576 s = s_copy(field[0]);
578 s_append(s, field[1]);
579 d.length = atoll(field[3]) * atoll(field[4]);
586 dn = getfields(field[6], dfield, 4, 1, "/-");
588 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[7]);
593 s = s_copy(field[0]);
606 for(cp = field[0]; *cp; cp++)
608 cp = strchr(field[0], ';');
612 cp = field[0] + strlen(field[0]) - 4;
613 if(strcmp(cp, ".dir") == 0){
617 s = s_copy(field[0]);
618 d.length = atoll(field[1]) * 512;
619 field[4][strlen(field[4])-1] = 0;
622 dn = getfields(field[2], dfield, 4, 1, "/-");
624 d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[3]);
634 case 8: /* New style */
635 s = s_copy(field[7]);
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]);
643 d.atime = cracktime(field[4], field[5], field[6], nil);
646 s = s_copy(field[8]);
652 d.length = atoll(field[3]);
653 d.atime = cracktime(field[4], field[5], field[6], field[7]);
656 s = s_copy(field[0]);
671 s = s_copy(field[7]);
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]);
679 d.atime = cracktime(field[4], field[5], field[6], 0);
682 s = s_copy(field[8]);
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]);
690 d.atime = cracktime(field[5], field[6], field[7], 0);
692 case 10: /* plan 9 */
693 s = s_copy(field[9]);
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]);
701 d.atime = cracktime(field[6], field[7], field[8], 0);
703 case 4: /* a Windows_NT version */
704 s = s_copy(field[3]);
707 if(strcmp("<DIR>", field[2]) == 0){
712 d.length = atoll(field[2]);
714 dn = getfields(field[0], dfield, 4, 1, "/-");
716 d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[1]);
719 s = s_copy(field[0]);
730 d.qid.type = (d.mode & DMDIR) ? QTDIR : QTFILE;
732 if(ext && (d.qid.type & QTDIR) == 0)
736 /* allocate a freeable dir structure */
737 dp = reallocdir(&d, 0);
744 * probe files in a directory to see if they are directories
747 * read a remote directory
762 if(changedir(node) < 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);
772 x = data(OREAD, &bp, "LIST", nil);
780 if(os == Unix && uselist == 0){
784 return seterr(nosuchfile);
787 while(line = Brdline(bp, '\n')){
791 if(n > 1 && line[n-2] == '\r')
795 d = crackdir(line, &remname, (usenlist || usenlst));
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){
813 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
815 if(files == 0 && !usenlst && !usenlist){
819 if(files && usenlist)
822 node->chdirunknown = 1;
827 return seterr(nosuchfile);
830 return seterr(nosuchfile);
834 * create a remote directory
837 createdir(Node *node)
839 if(changedir(node->parent) < 0)
842 sendrequest("MKD", node->d->name);
843 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
849 * change to a remote directory.
852 changedir(Node *node)
861 /* build an absolute path */
870 cdpath = s_clone(node->remname);
873 return seterr(nosuchfile);
883 vmspath(node, cdpath);
888 cdpath = s_clone(remrootpath);
891 mvspath(node, cdpath);
896 cdpath = s_clone(remrootpath);
899 unixpath(node, cdpath);
904 uncachedir(remdir, 0);
907 * connect, if we need a password (Incomplete)
908 * act like it worked (best we can do).
910 sendrequest("CWD", s_to_c(cdpath));
912 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
918 return seterr(nosuchfile);
926 readfile1(Node *node)
934 if(changedir(node->parent) < 0)
937 for(tries = 0; tries < 4; tries++){
938 switch(data(OREAD, &bp, "RETR", s_to_c(node->remname))){
944 return seterr(nosuchfile);
947 while((n = read(Bfildes(bp), buf, sizeof buf)) > 0){
948 if(filewrite(node, buf, off, n) != n){
957 /* make sure a file gets created even for a zero length file */
959 filewrite(node, buf, 0, 0);
962 switch(getreply(&ctlin, msg, sizeof(msg), 0)){
968 return seterr(nosuchfile);
971 return seterr(nosuchfile);
992 rv = readfile1(node);
1003 createfile1(Node *node)
1010 if(changedir(node->parent) < 0)
1013 if(data(OWRITE, &bp, "STOR", s_to_c(node->remname)) != Extra)
1015 for(off = 0; ; off += n){
1016 n = fileread(node, buf, off, sizeof(buf));
1019 write(Bfildes(bp), buf, n);
1022 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1028 createfile(Node *node)
1040 rv = createfile1(node);
1053 * remove a remote file
1056 removefile(Node *node)
1058 if(changedir(node->parent) < 0)
1061 sendrequest("DELE", s_to_c(node->remname));
1062 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1068 * remove a remote directory
1071 removedir(Node *node)
1073 if(changedir(node->parent) < 0)
1076 sendrequest("RMD", s_to_c(node->remname));
1077 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
1083 * tell remote that we're exiting and then do it
1088 sendrequest("QUIT", nil);
1089 getreply(&ctlin, msg, sizeof(msg), 0);
1097 sendrequest(char *a, char *b)
1105 if(n >= sizeof(buf))
1106 fatal("proto request too long");
1112 strcat(buf, "\r\n");
1114 if(write(ctlfd, buf, n) != n)
1115 fatal("remote side hung up");
1122 * replies codes are in the range [100, 999] and may contain multiple lines of
1126 getreply(Biobuf *bp, char *msg, int len, int printreply)
1133 while(line = Brdline(bp, '\n')){
1134 /* add line to message buffer, strip off \r */
1136 if(n > 1 && line[n-2] == '\r'){
1140 if(printreply && !quiet)
1149 memmove(msg, line, i);
1155 /* stop if not a continuation */
1156 rv = strtol(line, &p, 10);
1157 if(rv >= 100 && rv < 600 && p==line+3 && *p != '-')
1160 /* tell user about continuations */
1161 if(!debug && !quiet && !printreply)
1165 fatal("remote side closed connection");
1170 * Announce on a local port and tell its address to the the remote side
1178 uchar ipaddr[IPaddrlen];
1181 /* get a channel to listen on, let kernel pick the port number */
1182 sprint(buf, "%s!*!0", net);
1183 listenfd = announce(buf, netdir);
1185 return seterr("can't announce");
1187 /* get the local address and port number */
1188 sprint(buf, "%s/local", netdir);
1189 fd = open(buf, OREAD);
1191 return seterr("opening %s: %r", buf);
1192 n = read(fd, buf, sizeof(buf)-1);
1195 return seterr("opening %s/local: %r", netdir);
1197 ptr = strchr(buf, ' ');
1200 ptr = strchr(buf, '!')+1;
1203 memset(ipaddr, 0, IPaddrlen);
1206 ptr = strchr(buf +1, '/');
1209 myipaddr(ipaddr, buf);
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)
1222 * have server call back for a data connection
1225 active(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1228 char newdir[Maxpath];
1229 char datafile[Maxpath + 6];
1235 sendrequest(cmda, cmdb);
1237 rv = getreply(&ctlin, msg, sizeof(msg), 0);
1243 /* wait for a new call */
1244 cfd = listen(netdir, newdir);
1246 fatal("waiting for data connection");
1249 /* open it's data connection and close the control connection */
1250 sprint(datafile, "%s/data", newdir);
1251 dfd = open(datafile, ORDWR);
1254 fatal("opening data connection");
1257 memset(&conn, 0, sizeof(conn));
1258 dfd = tlsClient(dfd, &conn);
1260 fatal("starting tls: %r");
1264 Binit(&dbuf, dfd, mode);
1270 * call out for a data connection
1273 passive(int mode, Biobuf **bpp, char *cmda, char *cmdb)
1285 sendrequest("PASV", nil);
1286 if(getreply(&ctlin, msg, sizeof(msg), 0) != Success){
1291 /* get address and port number from reply, this is AI */
1292 p = strchr(msg, '(');
1294 for(p = msg+3; *p; p++)
1299 if(getfields(p, f, 6, 0, ",") < 6){
1301 fprint(2, "passive mode protocol botch: %s\n", msg);
1302 werrstr("ftp protocol botch");
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));
1310 /* open data connection */
1311 fd = dial(ds, 0, 0, 0);
1314 fprint(2, "passive mode connect to %s failed: %r\n", ds);
1319 /* tell remote to send a file */
1320 sendrequest(cmda, cmdb);
1321 x = getreply(&ctlin, msg, sizeof(msg), 0);
1325 fprint(2, "passive mode retrieve failed: %s\n", msg);
1331 memset(&conn, 0, sizeof(conn));
1332 fd = tlsClient(fd, &conn);
1334 fatal("starting tls: %r");
1337 Binit(&dbuf, fd, mode);
1344 data(int mode, Biobuf **bpp, char* cmda, char *cmdb)
1348 x = passive(mode, bpp, cmda, cmdb);
1351 return active(mode, bpp, cmda, cmdb);
1355 * used for keep alives
1360 if(lastsend - time(0) < 15)
1362 sendrequest("PWD", nil);
1363 getreply(&ctlin, msg, sizeof(msg), 0);
1367 * turn a vms spec into a path
1370 vmsextendpath(Node *np, char *name)
1372 np = extendpath(np, s_copy(name));
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;
1381 if(changedir(np) >= 0)
1394 cp = strchr(name, '[');
1397 cp = strchr(name, ']');
1400 oname = name = strdup(name);
1404 while(cp = strchr(name, '.')){
1406 np = vmsextendpath(np, name);
1409 np = vmsextendpath(np, name);
1412 * walk back to first accessible directory
1414 for(; np->parent != np; np = np->parent)
1425 * walk up the tree building a VMS style path
1428 vmspath(Node *node, String *path)
1433 if(node->depth == 1){
1434 p = strchr(s_to_c(node->remname), ':');
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);
1441 s_append(path, "[");
1442 s_append(path, s_to_c(node->remname));
1444 s_append(path, "]");
1447 vmspath(node->parent, path);
1448 s_append(path, ".");
1449 s_append(path, s_to_c(node->remname));
1453 * walk up the tree building a Unix style path
1456 unixpath(Node *node, String *path)
1458 if(node == node->parent){
1459 s_append(path, s_to_c(remrootpath));
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));
1469 * walk up the tree building a MVS style path
1472 mvspath(Node *node, String *path)
1474 if(node == node->parent){
1475 s_append(path, s_to_c(remrootpath));
1478 mvspath(node->parent, path);
1480 s_append(path, ".");
1481 s_append(path, s_to_c(node->remname));
1485 getpassword(char *buf, char *e)
1489 int consctl, rv = 0;
1491 consctl = open("/dev/consctl", OWRITE);
1493 write(consctl, "rawon", 5);
1494 print("Password: ");
1496 for(p = buf; p <= e; p++){
1502 if(c == '\n' || c == '\r')
1516 * convert from latin1 to utf
1519 fromlatin1(char *from)
1527 /* don't convert if we don't have to */
1528 for(p = from; *p; 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 */