#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <mp.h>
#include <libsec.h>
#include <auth.h>
#include <fcall.h>
#include <ctype.h>
#include <String.h>
#include "ftpfs.h"

enum
{
       /* return codes */
       Extra=          1,
       Success=        2,
       Incomplete=     3,
       TempFail=       4,
       PermFail=       5,
       Impossible=     6,
};

Node    *remdir;                /* current directory on remote machine */
Node    *remroot;               /* root directory on remote machine */

int     ctlfd;                  /* fd for control connection */
Biobuf  ctlin;                  /* input buffer for control connection */
Biobuf  stdin;                  /* input buffer for standard input */
Biobuf  dbuf;                   /* buffer for data connection */
char    msg[512];               /* buffer for replies */
char    net[Maxpath];           /* network for connections */
int     listenfd;               /* fd to listen on for connections */
char    netdir[Maxpath];
int     os, defos;
char    topsdir[64];            /* name of listed directory for TOPS */
String  *remrootpath;   /* path on remote side to remote root */
char    *user;
int     nopassive;
long    lastsend;
extern int usetls;

static void     sendrequest(char*, char*);
static int      getreply(Biobuf*, char*, int, int);
static int      active(int, Biobuf**, char*, char*);
static int      passive(int, Biobuf**, char*, char*);
static int      data(int, Biobuf**, char*, char*);
static int      port(void);
static void     ascii(void);
static void     image(void);
static void     unixpath(Node*, String*);
static void     vmspath(Node*, String*);
static void     mvspath(Node*, String*);
static Node*    vmsdir(char*);
static int      getpassword(char*, char*);
static int      nw_mode(char dirlet, char *s);

static void
starttls(int *fd)
{
       TLSconn conn;

       memset(&conn, 0, sizeof(conn));
       if((*fd = tlsClient(*fd, &conn)) < 0)
               fatal("starting tls: %r");
       free(conn.cert);
       free(conn.sessionID);
}

/*
*  connect to remote server, default network is "tcp/ip"
*/
void
hello(char *dest)
{
       char *p;
       char dir[Maxpath];

       Binit(&stdin, 0, OREAD);        /* init for later use */

       ctlfd = dial(netmkaddr(dest, "tcp", "ftp"), 0, dir, 0);
       if(ctlfd < 0){
               fprint(2, "can't dial %s: %r\n", dest);
               exits("dialing");
       }

       Binit(&ctlin, ctlfd, OREAD);

       /* remember network for the data connections */
       p = strrchr(dir+1, '/');
       if(p == 0)
               fatal("wrong dial(2) linked with ftp");
       *p = 0;
       safecpy(net, dir, sizeof(net));

       /* wait for hello from other side */
       if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
               fatal("bad hello");
       if(strstr(msg, "Plan 9"))
               os = Plan9;

       if(usetls){
               sendrequest("AUTH", "TLS");
               if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
                       fatal("bad auth tls");

               starttls(&ctlfd);

               Binit(&ctlin, ctlfd, OREAD);

               sendrequest("PBSZ", "0");
               if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
                       fatal("bad pbsz 0");
               sendrequest("PROT", "P");
               if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
                       fatal("bad prot p");
       }
}

/*
*  login to remote system
*/
void
rlogin(char *rsys, char *keyspec)
{
       char *line;
       char pass[128];
       UserPasswd *up;

       up = nil;
       for(;;){
               if(up == nil && os != Plan9)
                       up = auth_getuserpasswd(auth_getkey, "proto=pass server=%s service=ftp %s", rsys, keyspec);
               if(up != nil){
                       sendrequest("USER", up->user);
               } else {
                       print("User[default = %s]: ", user);
                       line = Brdline(&stdin, '\n');
                       if(line == 0)
                               exits(0);
                       line[Blinelen(&stdin)-1] = 0;
                       if(*line){
                               free(user);
                               user = strdup(line);
                       }
                       sendrequest("USER", user);
               }
               switch(getreply(&ctlin, msg, sizeof(msg), 1)){
               case Success:
                       goto out;
               case Incomplete:
                       break;
               case TempFail:
               case PermFail:
                       continue;
               }

               if(up != nil){
                       sendrequest("PASS", up->passwd);
               } else {
                       if(getpassword(pass, pass+sizeof(pass)) < 0)
                               exits(0);
                       sendrequest("PASS", pass);
               }
               if(getreply(&ctlin, msg, sizeof(msg), 1) == Success){
                       if(strstr(msg, "Sess#"))
                               defos = MVS;
                       break;
               }
       }
out:
       if(up != nil){
               memset(up, 0, sizeof(*up));
               free(up);
       }
}

/*
*  login to remote system with given user name and password.
*/
void
clogin(char *cuser, char *cpassword)
{
       free(user);
       user = strdup(cuser);
       if (strcmp(user, "anonymous") != 0 &&
           strcmp(user, "ftp") != 0)
               fatal("User must be 'anonymous' or 'ftp'");
       sendrequest("USER", user);
       switch(getreply(&ctlin, msg, sizeof(msg), 1)){
       case Success:
               return;
       case Incomplete:
               break;
       case TempFail:
       case PermFail:
               fatal("login failed");
       }
       if (cpassword == 0)
               fatal("password needed");
       sendrequest("PASS", cpassword);
       if(getreply(&ctlin, msg, sizeof(msg), 1) != Success)
               fatal("password failed");
       if(strstr(msg, "Sess#"))
               defos = MVS;
       return;
}

/*
*  find out about the other side.  go to it's root if requested.  set
*  image mode if a Plan9 system.
*/
void
preamble(char *mountroot)
{
       char *p, *ep;
       int rv;
       OS *o;

       /*
        *  create a root directory mirror
        */
       remroot = newnode(0, s_copy("/"));
       remroot->d->qid.type = QTDIR;
       remroot->d->mode = DMDIR|0777;
       remdir = remroot;

       /*
        *  get system type
        */
       sendrequest("SYST", nil);
       switch(getreply(&ctlin, msg, sizeof(msg), 1)){
       case Success:
               for(o = oslist; o->os != Unknown; o++)
                       if(strncmp(msg+4, o->name, strlen(o->name)) == 0)
                               break;
               os = o->os;
               if(os == NT)
                       os = Unix;
               break;
       default:
               os = defos;
               break;
       }
       if(os == Unknown)
               os = defos;

       remrootpath = s_reset(remrootpath);
       switch(os){
       case NetWare:
             /*
              * Request long, rather than 8.3 filenames,
              * where the Servers & Volume support them.
              */
             sendrequest("SITE LONG", nil);
             getreply(&ctlin, msg, sizeof(msg), 0);
             /* FALL THRU */
       case Unix:
       case Plan9:
               /*
                *  go to the remote root, if asked
                */
               if(mountroot){
                       sendrequest("CWD", mountroot);
                       getreply(&ctlin, msg, sizeof(msg), 0);
               } else {
                       s_append(remrootpath, "/usr/");
                       s_append(remrootpath, user);
               }

               /*
                *  get the root directory
                */
               sendrequest("PWD", nil);
               rv = getreply(&ctlin, msg, sizeof(msg), 1);
               if(rv == PermFail){
                       sendrequest("XPWD", nil);
                       rv = getreply(&ctlin, msg, sizeof(msg), 1);
               }
               if(rv == Success){
                       p = strchr(msg, '"');
                       if(p){
                               p++;
                               ep = strchr(p, '"');
                               if(ep){
                                       *ep = 0;
                                       s_append(s_reset(remrootpath), p);
                               }
                       }
               }

               break;
       case Tops:
       case VM:
               /*
                *  top directory is a figment of our imagination.
                *  make it permanently cached & valid.
                */
               CACHED(remroot);
               VALID(remroot);
               remroot->d->atime = time(0) + 100000;

               /*
                *  no initial directory.  We are in the
                *  imaginary root.
                */
               remdir = newtopsdir("???");
               topsdir[0] = 0;
               if(os == Tops && readdir(remdir) >= 0){
                       CACHED(remdir);
                       if(*topsdir)
                               remdir->remname = s_copy(topsdir);
                       VALID(remdir);
               }
               break;
       case VMS:
               /*
                *  top directory is a figment of our imagination.
                *  make it permanently cached & valid.
                */
               CACHED(remroot);
               VALID(remroot);
               remroot->d->atime = time(0) + 100000;

               /*
                *  get current directory
                */
               sendrequest("PWD", nil);
               rv = getreply(&ctlin, msg, sizeof(msg), 1);
               if(rv == PermFail){
                       sendrequest("XPWD", nil);
                       rv = getreply(&ctlin, msg, sizeof(msg), 1);
               }
               if(rv == Success){
                       p = strchr(msg, '"');
                       if(p){
                               p++;
                               ep = strchr(p, '"');
                               if(ep){
                                       *ep = 0;
                                       remroot = remdir = vmsdir(p);
                               }
                       }
               }
               break;
       case MVS:
               usenlst = 1;
               break;
       }

       if(os == Plan9)
               image();
}

static void
ascii(void)
{
       sendrequest("TYPE A", nil);
       switch(getreply(&ctlin, msg, sizeof(msg), 0)){
       case Success:
               break;
       default:
               fatal("can't set type to ascii");
       }
}

static void
image(void)
{
       sendrequest("TYPE I", nil);
       switch(getreply(&ctlin, msg, sizeof(msg), 0)){
       case Success:
               break;
       default:
               fatal("can't set type to image/binary");
       }
}

/*
*  decode the time fields, return seconds since epoch began
*/
char *monthchars = "janfebmaraprmayjunjulaugsepoctnovdec";
static Tm now;

static ulong
cracktime(char *month, char *day, char *yr, char *hms)
{
       Tm tm;
       int i;
       char *p;


       /* default time */
       if(now.year == 0)
               now = *localtime(time(0));
       tm = now;
       tm.yday = 0;

       /* convert ascii month to a number twixt 1 and 12 */
       if(*month >= '0' && *month <= '9'){
               tm.mon = atoi(month) - 1;
               if(tm.mon < 0 || tm.mon > 11)
                       tm.mon = 5;
       } else {
               for(p = month; *p; p++)
                       *p = tolower(*p);
               for(i = 0; i < 12; i++)
                       if(strncmp(&monthchars[i*3], month, 3) == 0){
                               tm.mon = i;
                               break;
                       }
       }

       tm.mday = atoi(day);

       if(hms){
               tm.hour = strtol(hms, &p, 0);
               if(*p == ':'){
                       tm.min = strtol(p+1, &p, 0);
                       if(*p == ':')
                               tm.sec = strtol(p+1, &p, 0);
               }
               if(tolower(*p) == 'p')
                       tm.hour += 12;
       }

       if(yr){
               tm.year = atoi(yr);
               if(tm.year >= 1900)
                       tm.year -= 1900;
       } else {
               if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
                       tm.year--;
       }

       /* convert to epoch seconds */
       return tm2sec(&tm);
}

/*
*  decode a Unix or Plan 9 file mode
*/
static ulong
crackmode(char *p)
{
       ulong flags;
       ulong mode;
       int i;

       flags = 0;
       switch(strlen(p)){
       case 10:        /* unix and new style plan 9 */
               switch(*p){
               case 'l':
                       return DMSYML|0777;
               case 'd':
                       flags |= DMDIR;
               case 'a':
                       flags |= DMAPPEND;
               }
               p++;
               if(p[2] == 'l')
                       flags |= DMEXCL;
               break;
       case 11:        /* old style plan 9 */
               switch(*p++){
               case 'd':
                       flags |= DMDIR;
                       break;
               case 'a':
                       flags |= DMAPPEND;
                       break;
               }
               if(*p++ == 'l')
                       flags |= DMEXCL;
               break;
       default:
               return DMDIR|0777;
       }
       mode = 0;
       for(i = 0; i < 3; i++){
               mode <<= 3;
               if(*p++ == 'r')
                       mode |= DMREAD;
               if(*p++ == 'w')
                       mode |= DMWRITE;
               if(*p == 'x' || *p == 's' || *p == 'S')
                       mode |= DMEXEC;
               p++;
       }
       return mode | flags;
}

/*
*  find first punctuation
*/
char*
strpunct(char *p)
{
       int c;

       for(;c = *p; p++){
               if(ispunct(c))
                       return p;
       }
       return 0;
}

/*
*  decode a Unix or Plan 9 directory listing
*/
static Dir*
crackdir(char *p, String **remname, int nlst)
{
       char *field[15];
       char *dfield[4];
       char *cp;
       String *s;
       int dn, n;
       Dir d, *dp;

       memset(&d, 0, sizeof(d));

       if(nlst != 0){
               field[0] = p;
               n = 1;
       } else {
               n = getfields(p, field, 15, 1, " \t");
               if(n > 2 && strcmp(field[n-2], "->") == 0)
                       n -= 2;
       }

       switch(os){
       case TSO:
               cp = strchr(field[0], '.');
               if(cp){
                       *cp++ = 0;
                       s = s_copy(cp);
                       d.uid = field[0];
               } else {
                       s = s_copy(field[0]);
                       d.uid = "TSO";
               }
               d.gid = "TSO";
               d.mode = 0666;
               d.length = 0;
               d.atime = 0;
               break;
       case OS½:
               s = s_copy(field[n-1]);
               d.uid = "OS½";
               d.gid = d.uid;
               d.mode = 0666;
               switch(n){
               case 5:
                       if(strcmp(field[1], "DIR") == 0)
                               d.mode |= DMDIR;
                       d.length = atoll(field[0]);
                       dn = getfields(field[2], dfield, 4, 1, "-");
                       if(dn == 3)
                               d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[3]);
                       break;
               }
               break;
       case Tops:
               if(n != 4){ /* tops directory name */
                       safecpy(topsdir, field[0], sizeof(topsdir));
                       return 0;
               }
               s = s_copy(field[3]);
               d.length = atoll(field[0]);
               d.mode = 0666;
               d.uid = "Tops";
               d.gid = d.uid;
               dn = getfields(field[1], dfield, 4, 1, "-");
               if(dn == 3)
                       d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[2]);
               else
                       d.atime = time(0);
               break;
       case VM:
               switch(n){
               case 9:
                       s = s_copy(field[0]);
                       s_append(s, ".");
                       s_append(s, field[1]);
                       d.length = atoll(field[3]) * atoll(field[4]);
                       if(*field[2] == 'F')
                               d.mode = 0666;
                       else
                               d.mode = 0777;
                       d.uid = "VM";
                       d.gid = d.uid;
                       dn = getfields(field[6], dfield, 4, 1, "/-");
                       if(dn == 3)
                               d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[7]);
                       else
                               d.atime = time(0);
                       break;
               case 1:
                       s = s_copy(field[0]);
                       d.uid = "VM";
                       d.gid = d.uid;
                       d.mode = 0777;
                       d.atime = 0;
                       break;
               default:
                       return nil;
               }
               break;
       case VMS:
               switch(n){
               case 6:
                       for(cp = field[0]; *cp; cp++)
                               *cp = tolower(*cp);
                       cp = strchr(field[0], ';');
                       if(cp)
                               *cp = 0;
                       d.mode = 0666;
                       cp = field[0] + strlen(field[0]) - 4;
                       if(strcmp(cp, ".dir") == 0){
                               d.mode |= DMDIR;
                               *cp = 0;
                       }
                       s = s_copy(field[0]);
                       d.length = atoll(field[1]) * 512;
                       field[4][strlen(field[4])-1] = 0;
                       d.uid = field[4]+1;
                       d.gid = d.uid;
                       dn = getfields(field[2], dfield, 4, 1, "/-");
                       if(dn == 3)
                               d.atime = cracktime(dfield[1], dfield[0], dfield[2], field[3]);
                       else
                               d.atime = time(0);
                       break;
               default:
                       return nil;
               }
               break;
       case NetWare:
               switch(n){
               case 8:         /* New style */
                       s = s_copy(field[7]);
                       d.uid = field[2];
                       d.gid = d.uid;
                       d.mode = nw_mode(field[0][0], field[1]);
                       d.length = atoll(field[3]);
                       if(strchr(field[6], ':'))
                               d.atime = cracktime(field[4], field[5], nil, field[6]);
                       else
                               d.atime = cracktime(field[4], field[5], field[6], nil);
                       break;
               case 9:
                       s = s_copy(field[8]);
                       d.uid = field[2];
                       d.gid = d.uid;
                       d.mode = 0666;
                       if(*field[0] == 'd')
                               d.mode |= DMDIR;
                       d.length = atoll(field[3]);
                       d.atime = cracktime(field[4], field[5], field[6], field[7]);
                       break;
               case 1:
                       s = s_copy(field[0]);
                       d.uid = "none";
                       d.gid = d.uid;
                       d.mode = 0777;
                       d.atime = 0;
                       break;
               default:
                       return nil;
               }
               break;
       case Unix:
       case Plan9:
       default:
               switch(n){
               case 8:         /* ls -lg */
                       s = s_copy(field[7]);
                       d.uid = field[2];
                       d.gid = d.uid;
                       d.mode = crackmode(field[0]);
                       d.length = atoll(field[3]);
                       if(strchr(field[6], ':'))
                               d.atime = cracktime(field[4], field[5], 0, field[6]);
                       else
                               d.atime = cracktime(field[4], field[5], field[6], 0);
                       break;
               case 9:         /* ls -l */
                       s = s_copy(field[8]);
                       d.uid = field[2];
                       d.gid = field[3];
                       d.mode = crackmode(field[0]);
                       d.length = atoll(field[4]);
                       if(strchr(field[7], ':'))
                               d.atime = cracktime(field[5], field[6], 0, field[7]);
                       else
                               d.atime = cracktime(field[5], field[6], field[7], 0);
                       break;
               case 10:        /* plan 9 */
                       s = s_copy(field[9]);
                       d.uid = field[3];
                       d.gid = field[4];
                       d.mode = crackmode(field[0]);
                       d.length = atoll(field[5]);
                       if(strchr(field[8], ':'))
                               d.atime = cracktime(field[6], field[7], 0, field[8]);
                       else
                               d.atime = cracktime(field[6], field[7], field[8], 0);
                       break;
               case 4:         /* a Windows_NT version */
                       s = s_copy(field[3]);
                       d.uid = "NT";
                       d.gid = d.uid;
                       if(strcmp("<DIR>", field[2]) == 0){
                               d.length = 0;
                               d.mode = DMDIR|0777;
                       } else {
                               d.mode = 0666;
                               d.length = atoll(field[2]);
                       }
                       dn = getfields(field[0], dfield, 4, 1, "/-");
                       if(dn == 3)
                               d.atime = cracktime(dfield[0], dfield[1], dfield[2], field[1]);
                       break;
               case 1:
                       s = s_copy(field[0]);
                       d.uid = "none";
                       d.gid = d.uid;
                       d.mode = 0777;
                       d.atime = 0;
                       break;
               default:
                       return nil;
               }
       }
       d.muid = d.uid;
       d.qid.type = (d.mode & DMDIR) ? QTDIR : QTFILE;
       d.mtime = d.atime;
       if(ext && (d.qid.type & QTDIR) == 0)
               s_append(s, ext);
       d.name = s_to_c(s);

       /* allocate a freeable dir structure */
       dp = reallocdir(&d, 0);
       *remname = s;

       return dp;
}

/*
*  probe files in a directory to see if they are directories
*/
/*
*  read a remote directory
*/
int
readdir(Node *node)
{
       Biobuf *bp;
       char *line;
       Node *np;
       Dir *d;
       long n;
       int tries, x, files;
       static int uselist;
       int usenlist;
       String *remname;

       if(changedir(node) < 0)
               return -1;

       usenlist = 0;
       for(tries = 0; tries < 3; tries++){
               if(usenlist || usenlst)
                       x = data(OREAD, &bp, "NLST", nil);
               else if(os == Unix && !uselist)
                       x = data(OREAD, &bp, "LIST -l", nil);
               else
                       x = data(OREAD, &bp, "LIST", nil);
               switch(x){
               case Extra:
                       break;
/*              case TempFail:
                       continue;
*/
               default:
                       if(os == Unix && uselist == 0){
                               uselist = 1;
                               continue;
                       }
                       return seterr(nosuchfile);
               }
               files = 0;
               while(line = Brdline(bp, '\n')){
                       n = Blinelen(bp);
                       if(debug)
                               write(2, line, n);
                       if(n > 1 && line[n-2] == '\r')
                               n--;
                       line[n - 1] = 0;

                       d = crackdir(line, &remname, (usenlist || usenlst));
                       if(d == nil)
                               continue;
                       files++;
                       np = extendpath(node, remname);
                       d->qid.path = np->d->qid.path;
                       d->qid.vers = np->d->qid.vers;
                       d->type = np->d->type;
                       d->dev = 1;                     /* mark node as valid */
                       if(os == MVS && node == remroot){
                               d->qid.type = QTDIR;
                               d->mode |= DMDIR;
                       }
                       free(np->d);
                       np->d = d;
               }
               close(Bfildes(bp));

               switch(getreply(&ctlin, msg, sizeof(msg), 0)){
               case Success:
                       if(files == 0 && !usenlst && !usenlist){
                               usenlist = 1;
                               continue;
                       }
                       if(files && usenlist)
                               usenlst = 1;
                       if(usenlst)
                               node->chdirunknown = 1;
                       return 0;
               case TempFail:
                       break;
               default:
                       return seterr(nosuchfile);
               }
       }
       return seterr(nosuchfile);
}

/*
*  create a remote directory
*/
int
createdir(Node *node)
{
       if(changedir(node->parent) < 0)
               return -1;

       sendrequest("MKD", node->d->name);
       if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
               return -1;
       return 0;
}

/*
*  change to a remote directory.
*/
int
changedir(Node *node)
{
       Node *to;
       String *cdpath;

       to = node;
       if(to == remdir)
               return 0;

       /* build an absolute path */
       switch(os){
       case Tops:
       case VM:
               switch(node->depth){
               case 0:
                       remdir = node;
                       return 0;
               case 1:
                       cdpath = s_clone(node->remname);
                       break;
               default:
                       return seterr(nosuchfile);
               }
               break;
       case VMS:
               switch(node->depth){
               case 0:
                       remdir = node;
                       return 0;
               default:
                       cdpath = s_new();
                       vmspath(node, cdpath);
               }
               break;
       case MVS:
               if(node->depth == 0)
                       cdpath = s_clone(remrootpath);
               else{
                       cdpath = s_new();
                       mvspath(node, cdpath);
               }
               break;
       default:
               if(node->depth == 0)
                       cdpath = s_clone(remrootpath);
               else{
                       cdpath = s_new();
                       unixpath(node, cdpath);
               }
               break;
       }

       uncachedir(remdir, 0);

       /*
        *  connect, if we need a password (Incomplete)
        *  act like it worked (best we can do).
        */
       sendrequest("CWD", s_to_c(cdpath));
       s_free(cdpath);
       switch(getreply(&ctlin, msg, sizeof(msg), 0)){
       case Success:
       case Incomplete:
               remdir = node;
               return 0;
       default:
               return seterr(nosuchfile);
       }
}

/*
*  read a remote file
*/
int
readfile1(Node *node)
{
       Biobuf *bp;
       char buf[4*1024];
       long off;
       int n;
       int tries;

       if(changedir(node->parent) < 0)
               return -1;

       for(tries = 0; tries < 4; tries++){
               switch(data(OREAD, &bp, "RETR", s_to_c(node->remname))){
               case Extra:
                       break;
               case TempFail:
                       continue;
               default:
                       return seterr(nosuchfile);
               }
               off = 0;
               while((n = read(Bfildes(bp), buf, sizeof buf)) > 0){
                       if(filewrite(node, buf, off, n) != n){
                               off = -1;
                               break;
                       }
                       off += n;
               }
               if(off < 0)
                       return -1;

               /* make sure a file gets created even for a zero length file */
               if(off == 0)
                       filewrite(node, buf, 0, 0);

               close(Bfildes(bp));
               switch(getreply(&ctlin, msg, sizeof(msg), 0)){
               case Success:
                       return off;
               case TempFail:
                       continue;
               default:
                       return seterr(nosuchfile);
               }
       }
       return seterr(nosuchfile);
}

int
readfile(Node *node)
{
       int rv, inimage;

       switch(os){
       case MVS:
       case Plan9:
       case Tops:
       case TSO:
               inimage = 0;
               break;
       default:
               inimage = 1;
               image();
               break;
       }

       rv = readfile1(node);

       if(inimage)
               ascii();
       return rv;
}

/*
*  write back a file
*/
int
createfile1(Node *node)
{
       Biobuf *bp;
       char buf[4*1024];
       long off;
       int n;

       if(changedir(node->parent) < 0)
               return -1;

       if(data(OWRITE, &bp, "STOR", s_to_c(node->remname)) != Extra)
               return -1;
       for(off = 0; ; off += n){
               n = fileread(node, buf, off, sizeof(buf));
               if(n <= 0)
                       break;
               write(Bfildes(bp), buf, n);
       }
       close(Bfildes(bp));
       if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
               return -1;
       return off;
}

int
createfile(Node *node)
{
       int rv;

       switch(os){
       case Plan9:
       case Tops:
               break;
       default:
               image();
               break;
       }
       rv = createfile1(node);
       switch(os){
       case Plan9:
       case Tops:
               break;
       default:
               ascii();
               break;
       }
       return rv;
}

/*
*  remove a remote file
*/
int
removefile(Node *node)
{
       if(changedir(node->parent) < 0)
               return -1;

       sendrequest("DELE", s_to_c(node->remname));
       if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
               return -1;
       return 0;
}

/*
*  remove a remote directory
*/
int
removedir(Node *node)
{
       if(changedir(node->parent) < 0)
               return -1;

       sendrequest("RMD", s_to_c(node->remname));
       if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
               return -1;
       return 0;
}

/*
*  tell remote that we're exiting and then do it
*/
void
quit(void)
{
       sendrequest("QUIT", nil);
       getreply(&ctlin, msg, sizeof(msg), 0);
       exits(0);
}

/*
*  send a request
*/
static void
sendrequest(char *a, char *b)
{
       char buf[2*1024];
       int n;

       n = strlen(a)+2+1;
       if(b != nil)
               n += strlen(b)+1;
       if(n >= sizeof(buf))
               fatal("proto request too long");
       strcpy(buf, a);
       if(b != nil){
               strcat(buf, " ");
               strcat(buf, b);
       }
       strcat(buf, "\r\n");
       n = strlen(buf);
       if(write(ctlfd, buf, n) != n)
               fatal("remote side hung up");
       if(debug)
               write(2, buf, n);
       lastsend = time(0);
}

/*
*  replies codes are in the range [100, 999] and may contain multiple lines of
*  continuation.
*/
static int
getreply(Biobuf *bp, char *msg, int len, int printreply)
{
       char *line;
       char *p;
       int rv;
       int i, n;

       while(line = Brdline(bp, '\n')){
               /* add line to message buffer, strip off \r */
               n = Blinelen(bp);
               if(n > 1 && line[n-2] == '\r'){
                       n--;
                       line[n-1] = '\n';
               }
               if(printreply && !quiet)
                       write(1, line, n);
               else if(debug)
                       write(2, line, n);
               if(n > len - 1)
                       i = len - 1;
               else
                       i = n;
               if(i > 0){
                       memmove(msg, line, i);
                       msg += i;
                       len -= i;
                       *msg = 0;
               }

               /* stop if not a continuation */
               rv = strtol(line, &p, 10);
               if(rv >= 100 && rv < 600 && p==line+3 && *p != '-')
                       return rv/100;

               /* tell user about continuations */
               if(!debug && !quiet && !printreply)
                       write(2, line, n);
       }

       fatal("remote side closed connection");
       return 0;
}

/*
*  Announce on a local port and tell its address to the the remote side
*/
static int
port(void)
{
       char buf[256];
       int n, fd;
       char *ptr;
       uchar ipaddr[IPaddrlen];
       int port;

       /* get a channel to listen on, let kernel pick the port number */
       sprint(buf, "%s!*!0", net);
       listenfd = announce(buf, netdir);
       if(listenfd < 0)
               return seterr("can't announce");

       /* get the local address and port number */
       sprint(buf, "%s/local", netdir);
       fd = open(buf, OREAD);
       if(fd < 0)
               return seterr("opening %s: %r", buf);
       n = read(fd, buf, sizeof(buf)-1);
       close(fd);
       if(n <= 0)
               return seterr("opening %s/local: %r", netdir);
       buf[n] = 0;
       ptr = strchr(buf, ' ');
       if(ptr)
               *ptr = 0;
       ptr = strchr(buf, '!')+1;
       port = atoi(ptr);

       memset(ipaddr, 0, IPaddrlen);
       if (*net){
               strcpy(buf, net);
               ptr = strchr(buf +1, '/');
               if (ptr)
                       *ptr = 0;
               myipaddr(ipaddr, buf);
       }

       /* tell remote side */
       sprint(buf, "PORT %d,%d,%d,%d,%d,%d", ipaddr[IPv4off+0], ipaddr[IPv4off+1],
               ipaddr[IPv4off+2], ipaddr[IPv4off+3], port>>8, port&0xff);
       sendrequest(buf, nil);
       if(getreply(&ctlin, msg, sizeof(msg), 0) != Success)
               return seterr(msg);
       return 0;
}

/*
*  have server call back for a data connection
*/
static int
active(int mode, Biobuf **bpp, char *cmda, char *cmdb)
{
       int cfd, dfd, rv;
       char newdir[Maxpath];
       char datafile[Maxpath + 6];

       if(port() < 0)
               return TempFail;

       sendrequest(cmda, cmdb);

       rv = getreply(&ctlin, msg, sizeof(msg), 0);
       if(rv != Extra){
               close(listenfd);
               return rv;
       }

       /* wait for a new call */
       cfd = listen(netdir, newdir);
       if(cfd < 0)
               fatal("waiting for data connection");
       close(listenfd);

       /* open it's data connection and close the control connection */
       sprint(datafile, "%s/data", newdir);
       dfd = open(datafile, ORDWR);
       close(cfd);
       if(dfd < 0)
               fatal("opening data connection");

       if(usetls)
               starttls(&dfd);

       Binit(&dbuf, dfd, mode);
       *bpp = &dbuf;
       return Extra;
}

/*
*  call out for a data connection
*/
static int
passive(int mode, Biobuf **bpp, char *cmda, char *cmdb)
{
       char msg[1024];
       char ds[1024];
       char *f[6];
       char *p;
       int x, fd;

       if(nopassive)
               return Impossible;

       sendrequest("PASV", nil);
       if(getreply(&ctlin, msg, sizeof(msg), 0) != Success){
               nopassive = 1;
               return Impossible;
       }

       /* get address and port number from reply, this is AI */
       p = strchr(msg, '(');
       if(p == 0){
               for(p = msg+3; *p; p++)
                       if(isdigit(*p))
                               break;
       } else
               p++;
       if(getfields(p, f, 6, 0, ",") < 6){
               if(debug)
                       fprint(2, "passive mode protocol botch: %s\n", msg);
               werrstr("ftp protocol botch");
               nopassive = 1;
               return Impossible;
       }
       snprint(ds, sizeof(ds), "%s!%s.%s.%s.%s!%d", net,
               f[0], f[1], f[2], f[3],
               ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff));

       /* open data connection */
       fd = dial(ds, 0, 0, 0);
       if(fd < 0){
               if(debug)
                       fprint(2, "passive mode connect to %s failed: %r\n", ds);
               nopassive = 1;
               return TempFail;
       }

       /* tell remote to send a file */
       sendrequest(cmda, cmdb);
       x = getreply(&ctlin, msg, sizeof(msg), 0);
       if(x != Extra){
               close(fd);
               if(debug)
                       fprint(2, "passive mode retrieve failed: %s\n", msg);
               werrstr("%s", msg);
               return x;
       }

       if(usetls)
               starttls(&fd);

       Binit(&dbuf, fd, mode);

       *bpp = &dbuf;
       return Extra;
}

static int
data(int mode, Biobuf **bpp, char* cmda, char *cmdb)
{
       int x;

       x = passive(mode, bpp, cmda, cmdb);
       if(x != Impossible)
               return x;
       return active(mode, bpp, cmda, cmdb);
}

/*
*  used for keep alives
*/
void
nop(void)
{
       if(lastsend - time(0) < 15)
               return;
       sendrequest("PWD", nil);
       getreply(&ctlin, msg, sizeof(msg), 0);
}

/*
*  turn a vms spec into a path
*/
static Node*
vmsextendpath(Node *np, char *name)
{
       np = extendpath(np, s_copy(name));
       if(!ISVALID(np)){
               np->d->qid.type = QTDIR;
               np->d->atime = time(0);
               np->d->mtime = np->d->atime;
               strcpy(np->d->uid, "who");
               strcpy(np->d->gid, "cares");
               np->d->mode = DMDIR|0777;
               np->d->length = 0;
               if(changedir(np) >= 0)
                       VALID(np);
       }
       return np;
}
static Node*
vmsdir(char *name)
{
       char *cp;
       Node *np;
       char *oname;

       np = remroot;
       cp = strchr(name, '[');
       if(cp)
               strcpy(cp, cp+1);
       cp = strchr(name, ']');
       if(cp)
               *cp = 0;
       oname = name = strdup(name);
       if(name == 0)
               return 0;

       while(cp = strchr(name, '.')){
               *cp = 0;
               np = vmsextendpath(np, name);
               name = cp+1;
       }
       np = vmsextendpath(np, name);

       /*
        *  walk back to first accessible directory
        */
       for(; np->parent != np; np = np->parent)
               if(ISVALID(np)){
                       CACHED(np->parent);
                       break;
               }

       free(oname);
       return np;
}

/*
*  walk up the tree building a VMS style path
*/
static void
vmspath(Node *node, String *path)
{
       char *p;
       int n;

       if(node->depth == 1){
               p = strchr(s_to_c(node->remname), ':');
               if(p){
                       n = p - s_to_c(node->remname) + 1;
                       s_nappend(path, s_to_c(node->remname), n);
                       s_append(path, "[");
                       s_append(path, p+1);
               } else {
                       s_append(path, "[");
                       s_append(path, s_to_c(node->remname));
               }
               s_append(path, "]");
               return;
       }
       vmspath(node->parent, path);
       s_append(path, ".");
       s_append(path, s_to_c(node->remname));
}

/*
*  walk up the tree building a Unix style path
*/
static void
unixpath(Node *node, String *path)
{
       if(node == node->parent){
               s_append(path, s_to_c(remrootpath));
               return;
       }
       unixpath(node->parent, path);
       if(s_len(path) > 0 && strcmp(s_to_c(path), "/") != 0)
               s_append(path, "/");
       s_append(path, s_to_c(node->remname));
}

/*
*  walk up the tree building a MVS style path
*/
static void
mvspath(Node *node, String *path)
{
       if(node == node->parent){
               s_append(path, s_to_c(remrootpath));
               return;
       }
       mvspath(node->parent, path);
       if(s_len(path) > 0)
               s_append(path, ".");
       s_append(path, s_to_c(node->remname));
}

static int
getpassword(char *buf, char *e)
{
       char *p;
       int c;
       int consctl, rv = 0;

       consctl = open("/dev/consctl", OWRITE);
       if(consctl >= 0)
               write(consctl, "rawon", 5);
       print("Password: ");
       e--;
       for(p = buf; p <= e; p++){
               c = Bgetc(&stdin);
               if(c < 0){
                       rv = -1;
                       goto out;
               }
               if(c == '\n' || c == '\r')
                       break;
               *p = c;
       }
       *p = 0;
       print("\n");

out:
       if(consctl >= 0)
               close(consctl);
       return rv;
}

/*
*  convert from latin1 to utf
*/
static char*
fromlatin1(char *from)
{
       char *p, *to;
       Rune r;
       int n;

       if(os == Plan9)
               return nil;

       /* don't convert if we don't have to */
       for(p = from; *p; p += n){
               n = chartorune(&r, p);
               if(r == Runeerror)
                       break;
       }
       if(*p == 0)
               return nil;

       to = malloc(UTFmax*strlen(from)+2);
       if(to == nil)
               return nil;
       for(p = to; *from; from++){
               r = (*from) & 0xff;
               p += runetochar(p, &r);
       }
       *p = 0;
       return to;
}

Dir*
reallocdir(Dir *d, int dofree)
{
       Dir *dp;
       char *p;
       int nn, ng, nu, nm;
       char *utf;

       if(d->name == nil)
               d->name = "?name?";
       if(d->uid == nil)
               d->uid = "?uid?";
       if(d->gid == nil)
               d->gid = d->uid;
       if(d->muid == nil)
               d->muid = d->uid;

       utf = fromlatin1(d->name);
       if(utf != nil)
               d->name = utf;

       nn = strlen(d->name)+1;
       nu = strlen(d->uid)+1;
       ng = strlen(d->gid)+1;
       nm = strlen(d->muid)+1;
       dp = malloc(sizeof(Dir)+nn+nu+ng+nm);
       if(dp == nil){
               if(dofree)
                       free(d);
               if(utf != nil)
                       free(utf);
               return nil;
       }
       *dp = *d;
       p = (char*)&dp[1];
       strcpy(p, d->name);
       dp->name = p;
       p += nn;
       strcpy(p, d->uid);
       dp->uid = p;
       p += nu;
       strcpy(p, d->gid);
       dp->gid = p;
       p += ng;
       strcpy(p, d->muid);
       dp->muid = p;
       if(dofree)
               free(d);
       if(utf != nil)
               free(utf);
       return dp;
}

Dir*
dir_change_name(Dir *d, char *name)
{
       if(d->name && strlen(d->name) >= strlen(name)){
               strcpy(d->name, name);
               return d;
       }
       d->name = name;
       return reallocdir(d, 1);
}

Dir*
dir_change_uid(Dir *d, char *name)
{
       if(d->uid && strlen(d->uid) >= strlen(name)){
               strcpy(d->name, name);
               return d;
       }
       d->uid = name;
       return reallocdir(d, 1);
}

Dir*
dir_change_gid(Dir *d, char *name)
{
       if(d->gid && strlen(d->gid) >= strlen(name)){
               strcpy(d->name, name);
               return d;
       }
       d->gid = name;
       return reallocdir(d, 1);
}

Dir*
dir_change_muid(Dir *d, char *name)
{
       if(d->muid && strlen(d->muid) >= strlen(name)){
               strcpy(d->name, name);
               return d;
       }
       d->muid = name;
       return reallocdir(d, 1);
}

static int
nw_mode(char dirlet, char *s)           /* NetWare file mode mapping */
{
       int mode = 0777;

       if(dirlet == 'd')
               mode |= DMDIR;

       if (strlen(s) >= 10 && s[0] != '[' || s[9] != ']')
               return(mode);

       if (s[1] == '-')                                        /* can't read file */
               mode &= ~0444;
       if (dirlet == 'd' && s[6] == '-')                       /* cannot scan dir */
               mode &= ~0444;
       if (s[2] == '-')                                        /* can't write file */
               mode &= ~0222;
       if (dirlet == 'd' && s[7] == '-' && s[3] == '-')        /* cannot create in, or modify dir */
               mode &= ~0222;

       return(mode);
}