/* Internet FTP client (interactive user)
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by G1EMM and PA0GRI */
/* modifications for encrypted password by ik1che 900419 */
/* added "resume" and "rput" commands for interrupted file transfers
 * by iw0cnb 15 Feb 92 */
  
 /* VIEW command added by Simon G1FHY. Mod by Paul@wolf.demon.co.uk */
#ifdef MSDOS
#include <dir.h>
#endif
#ifdef LINUX
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include "global.h"
#include "mbuf.h"
#include "session.h"
#include "cmdparse.h"
#include "proc.h"
#include "tty.h"
#include "socket.h"
#include "mailbox.h"
#include "ftp.h"
#include "ftpcli.h"
#include "commands.h"
#include "netuser.h"
#include "dirutil.h"
#include "files.h"
#include "config.h"
  
#define DIRBUF  256
  
#ifdef FTPSESSION
  
#ifdef  LZW
#include "lzw.h"
  
int Ftpslzw;
int Ftpclzw;
int doftpslzw __ARGS((int argc,char *argv[],void *p));
int doftpclzw __ARGS((int argc,char *argv[],void *p));
static int dolocftpclzw __ARGS((int argc,char *argv[],void *p));
#endif
  
extern char System[];
static int doascii __ARGS((int argc,char *argv[],void *p));
static int dobatch __ARGS((int argc,char *argv[],void *p));
static int dobinary __ARGS((int argc,char *argv[],void *p));
static int doftpcd __ARGS((int argc,char *argv[],void *p));
static int doftphelp __ARGS((int argc,char *argv[],void *p));
static int doget __ARGS((int argc,char *argv[],void *p));
static int dohash __ARGS((int argc,char *argv[],void *p));
static int doverbose __ARGS((int argc,char *argv[],void *p));
static int dolist __ARGS((int argc,char *argv[],void *p));
static int dols __ARGS((int argc,char *argv[],void *p));
static int doldir  __ARGS((int argc,char *argv[],void *p));
static int dolcd  __ARGS((int argc,char *argv[],void *p));
static int dolmkdir __ARGS((int argc,char *argv[],void *p));
static int domkdir __ARGS((int argc,char *argv[],void *p));
static int domget __ARGS((int argc,char *argv[],void *p));
static int domput __ARGS((int argc,char *argv[],void *p));
static int doput __ARGS((int argc,char *argv[],void *p));
static int doquit __ARGS((int argc,char *argv[],void *p));
static int dormdir __ARGS((int argc,char *argv[],void *p));
static int doresume __ARGS((int argc,char *argv[],void *p));
static int dorput __ARGS((int argc,char *argv[],void *p));
static int dotype __ARGS((int argc,char *argv[],void *p));
static int doview __ARGS((int argc,char *argv[],void *p));
static int getline __ARGS((struct session *sp,char *prompt,char *buf,int n));
static int getresp __ARGS((struct ftpcli *ftp,int mincode));
static long getsub __ARGS((struct ftpcli *ftp,char *command,char *remotename,
char *localname));
static long putsub __ARGS((struct ftpcli *ftp,char *remotename,char *localname,int putr));
static void sendport __ARGS((int s,struct sockaddr_in *socket));
static char *ftpcli_login __ARGS((struct ftpcli *ftp,char *host));
  
static char Notsess[] = "Not an FTP session!\n";
  
static int Ftp_type = ASCII_TYPE;
static int Ftp_logbsize = 8;
  
static struct cmds DFAR Ftpcmds[] = {
    "",             donothing,      0, 0, NULLCHAR,
    "?",            doftphelp,      0, 0, NULLCHAR,
    "ascii",        doascii,        0, 0, NULLCHAR,
    "batch",        dobatch,        0, 0, NULLCHAR,
    "binary",       dobinary,       0, 0, NULLCHAR,
    "cd",           doftpcd,        0, 2, "cd <directory>",
    "dir",          dolist,         0, 0, NULLCHAR,
    "list",         dolist,         0, 0, NULLCHAR,
    "get",          doget,          0, 2, "get <remotefile> <localfile>",
    "hash",         dohash,         0, 0, NULLCHAR,
    "help",         doftphelp,      0, 0, NULLCHAR,
    "ls",           dols,           0, 0, NULLCHAR,
    "lcd",          dolcd,          0, 1, NULLCHAR,
    "ldir",         doldir,         0, 1, NULLCHAR,
    "lmkdir",       dolmkdir,       0, 2, "lmkdir <local Directory>",
#ifdef LZW
    "reclzw",       dolocftpclzw,   0, 0, NULLCHAR,
    "sendlzw",      doftpslzw,      0, 0, NULLCHAR,
#endif
    "mget",         domget,         0, 2, "mget <file> [<file> ...]",
    "mkdir",        domkdir,        0, 2, "mkdir <directory>",
    "mput",         domput,         0, 2, "mput <file> [<file> ...]",
    "nlst",         dols,           0, 0, NULLCHAR,
    "put",          doput,          0, 2, "put <localfile> <remotefile>",
    "quit",         doquit,         0, 0, NULLCHAR,
    "resume",       doresume,       0, 2, "resume <remotefile> <localfile>",
    "rmdir",        dormdir,        0, 2, "rmdir <directory>",
    "rput",         dorput,         0, 2, "rput <localfile> <remotefile>",
    "type",         dotype,         0, 0, NULLCHAR,
    "verbose",      doverbose,      0, 0, NULLCHAR,
    "view",         doview,         0, 2,"view <remotefile>",
    NULLCHAR,       NULLFP,         0, 0, NULLCHAR,
};
  
int
doftphelp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct cmds DFAR *cmdp;
    int i;
    char buf[77];
  
    tputs("\nFTP commands:\n");
    memset(buf,' ',sizeof(buf));
    buf[75] = '\n';
    buf[76] = '\0';
    for(i=0,cmdp = Ftpcmds;cmdp->name != NULL;cmdp++,i = (i+1)%7){
        strncpy(&buf[i*10],cmdp->name,strlen(cmdp->name));
        if(i == 6){
            tputs(buf);
            memset(buf,' ',sizeof(buf));
            buf[75] = '\n';
            buf[76] = '\0';
        }
    }
    if(i != 0)
        tputs(buf);
    tputc('\n');
    return 0;
}
  
/* Handle top-level FTP command */
int
doftp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct session *sp;
    struct ftpcli ftp;
    struct sockaddr_in fsocket;
    int resp,vsave;
    char *buf,*bufsav,*cp,*un;
    char prmt[40];
    char l[17];
    int control;
#ifdef __GNUC__
    static
#endif
    char *ftpcli_login __ARGS((struct ftpcli *, char *));
    char *ftpcli_login();
    FILE *fp1 = NULLFILE;
    struct  cur_dirs dirs;
  
    /* Only from console - WG7J */
    if(Curproc->input != Command->input)
        return 0;
  
    /* Allocate a session control block */
    if((sp = newsession(argv[1],FTP,0)) == NULLSESSION){
        tputs(TooManySessions);
        return 1;
    }
    memset((char *)&ftp,0,sizeof(ftp));
    ftp.control = ftp.data = -1;
    ftp.verbose = V_BYTE;           /* Default changed by IW0CNB */
    ftp.type = Ftp_type;
    ftp.logbsize = Ftp_logbsize;
#ifdef LZW
    ftp.uselzw = Ftpclzw;
#endif
    buf = NULLCHAR;
  
    sp->cb.ftp = &ftp;      /* Downward link */
    ftp.session = sp;       /* Upward link */
  
    sp->curdirs = ftp.curdirs = &dirs;
  
    fsocket.sin_family = AF_INET;
    fsocket.sin_port = IPPORT_FTP;
  
    tprintf("Resolving %s... ",sp->name);
    if((fsocket.sin_addr.s_addr = resolve(sp->name)) == 0){
        tprintf(Badhost,sp->name);
        keywait(NULLCHAR,1);
        freesession(sp);
        return 1;
    }
  
    /* Open the control connection */
    if((control = sp->s = ftp.control = socket(AF_INET,SOCK_STREAM,0)) == -1){
        tputs("Can't create socket\n");
        keywait(NULLCHAR,1);
        freesession(sp);
        return 1;
    }
  
    sockmode(sp->s,SOCK_ASCII);
    setflush(sp->s,-1);     /* Flush output only when we call getresp() */
    tprintf("Trying %s...\n",psocket((struct sockaddr *)&fsocket));
    tprintf("Local Directory - %s\n",init_dirs(&dirs));
    if(connect(control,(char *)&fsocket,sizeof(fsocket)) == -1)
        goto quit;
    tprintf("FTP session %u connected to %s\n",(unsigned)(sp-Sessions),
    sp->name);
  
    /* Wait for greeting from server */
    resp = getresp(&ftp,200);
  
    if(resp >= 400)
        goto quit;
    un = getenv("USER");
    if(un != NULLCHAR)
        sprintf(prmt,"Enter user name (%s): ",un);
    else
        sprintf(prmt,"Enter user name: ");
  
    /* Now process responses and commands */
    buf = mallocw(LINELEN);
  
    if(argc > 2){
        if((fp1 = fopen(argv[2],READ_TEXT)) == NULLFILE) goto quit;
    }
    while(resp != -1){
        if(resp == 220){
            /* Sign-on banner; prompt for and send USER command */
            if((cp = ftpcli_login(&ftp, sp->name)) == NULLCHAR){
                if (getline(sp,prmt,buf,LINELEN) == -1) break;
                /* Send the command only if the user response
                 * was non-null
                 */
                if(buf[0] != '\n'){
                    usprintf(control,"USER %s",buf);
                    resp = getresp(&ftp,200);
                } else {
                    if(un != NULLCHAR){
                        usprintf(control,"USER %s\n",un);
                        resp = getresp(&ftp,200);
                    } else {
                        tputs("No username sent\n");
                        resp = 200;     /* dummy */
                    }
                }
            } else {
                usprintf(control,"USER %s\n",cp);
                free(cp);
                resp = getresp(&ftp,200);
            }
        } else if(resp == 331){
            if(ftp.password == NULLCHAR){
                /* turn off echo */
                sp->ttystate.echo = 0;
                if (getline(sp,"Password: ",buf,LINELEN) == -1) break;
                tputc('\n');
                /* Turn echo back on */
                sp->ttystate.echo = 1;
                /* Send the command only if the user response
                 * was non-null
                 */
                if(buf[0] != '\n'){
                    usprintf(control,"PASS %s",buf);
                    resp = getresp(&ftp,200);
                } else {
                    tputs("Password must be provided.\nLogin failed.\n");
                    resp = 200;     /* dummy */
                }
            } else {
                usprintf(control,"PASS %s\n",ftp.password);
                resp = getresp(&ftp,200);
                free(ftp.password);
                ftp.password = NULLCHAR;        /* clean up */
            }
        } else if(resp == 230) {    /* Successful login */
            /* Find out what type of system we're talking to */
            tprintf("ftp> syst\n");
            usprintf(control,"SYST\n");
            resp = getresp(&ftp,200);
        } else if(resp == 215) {    /* Response to SYST command */
            cp = strchr(ftp.line,' ');
            if(cp != NULLCHAR && strnicmp(cp+1,System,strlen(System)) == 0){
                ftp.type = IMAGE_TYPE;
                tputs("Defaulting to binary mode transfers\n");
            }
            resp = 200; /* dummy */
#ifdef DES_AUTH
        } else if(resp == 399) {        /* Encrypted password login */
            if(ftp.password == NULLCHAR){
                if (getline(sp,"Key ? --> ",buf,LINELEN) == -1) break;
                /* Send the command only if the user response
                 * was non-null
                 */
                if(buf[0] != '\n'){
                    cp = strchr(ftp.line,':');
                    cp += 2;
                      /*
                    epass(htol(cp),buf,l);
                      */
                    l[16] = '\0';
                    free(ftp.line);
                    ftp.line = NULLCHAR;
                    usprintf(control,"PASS %s\n",l);
                    resp = getresp(&ftp,200);
                } else {
                    tputs("Password must be provided.\nLogin failed.\n");
                    resp = 200;     /* dummy */
                }
            } else {
                cp = strchr(ftp.line,':');
                cp += 2;
                /*
                epass(htol(cp),ftp.password,l);
                */
                l[16] = '\0';
                free(ftp.line);
                ftp.line = NULLCHAR;
                usprintf(control,"PASS %s\n",l);
                resp = getresp(&ftp,200);
                free(ftp.password);
                ftp.password = NULLCHAR;        /* clean up */
            }
#endif
        } else {
            /* Test the control channel first */
            if(sockstate(control) == NULLCHAR)
                break;
            if(argc > 2){
                if(fgets(buf,LINELEN,fp1) == NULLCHAR)
                    goto quit;
            } else
                if (getline(sp,"ftp> ",buf,LINELEN) == -1) break;
  
            /* Copy because cmdparse modifies the original */
            bufsav = strdup(buf);
            if((resp = cmdparse(Ftpcmds,buf,&ftp)) != -1){
                /* Valid command, free buffer and get another */
                free(bufsav);
            } else {
                /* Not a local cmd, send to remote server */
                usputs(control,bufsav);
                free(bufsav);
  
                /* Enable display of server response */
                vsave = ftp.verbose;
                ftp.verbose = V_NORMAL;
                resp = getresp(&ftp,200);
                ftp.verbose = vsave;
            }
        }
    }
quit:
    free(buf);
    cp = sockerr(control);
    tprintf("FTP session %u closed: %s\n",(unsigned)(sp - Sessions),
    cp != NULLCHAR ? cp : "EOF");
  
    free(ftp.line);      /* fixed memory leak - K5JB */

    if(ftp.fp != NULLFILE && ftp.fp != stdout)
        fclose(ftp.fp);
    if(ftp.data != -1)
        close_s(ftp.data);
    if(ftp.control != -1)
        close_s(ftp.control);
    if(argc < 3)
        keywait(NULLCHAR,1);
    else
        fclose(fp1);
    if(ftp.session != NULLSESSION)
        freesession(ftp.session);
    free_dirs(&dirs);
    return 0;
}
  
#ifdef LZW
int
doftpslzw(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&Ftpslzw,"FTP server lzw",argc,argv);
}
  
int
doftpclzw(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&Ftpclzw,"FTP client lzw",argc,argv);
}
  
static int
dolocftpclzw(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    int result;

/* We'd like to keep ftp.uselzw in sync with Ftpclzw (just so we can turn
   lzw on and off within an ftp session ... is it really worth it?).
   We need a separate proc since the ftpclzw cmd exists. --N5KNX */

    result = setbool(&Ftpclzw,"FTP client lzw",argc,argv);
    if((ftp = (struct ftpcli *)p) != NULLFTP &&
       (result == 0)) ftp->uselzw = Ftpclzw;  /* keep in sync with changes */
    return result;
}
#endif
  
/* Control verbosity level */
static int
doverbose(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    if((ftp = (struct ftpcli *)p) == NULLFTP)
        return -1;
    return setshort(&ftp->verbose,"Verbose",argc,argv);
}
/* Enable/disable command batching */
static int
dobatch(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    if((ftp = (struct ftpcli *)p) == NULLFTP)
        return -1;
    return setbool(&ftp->batch,"Command batching",argc,argv);
}
/* Set verbosity to high (convenience command) */
static int
dohash(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    if((ftp = (struct ftpcli *)p) == NULLFTP)
        return -1;
    tputs("Hash Printing ");
    if (ftp->verbose==V_HASH){
        ftp->verbose = V_HASH+1;
        tputs("Off\n");
    } else {
        tputs("On\n");
        ftp->verbose = V_HASH;
    }
    return 0;
}
  
/* Close session */
static int
doquit(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP)
        return -1;
    usputs(ftp->control,"QUIT\n");
  
    getresp(ftp,200);       /* Get the closing message */
    getresp(ftp,200);       /* Wait for the server to close */
    return -1;
}
  
/* Translate 'cd' to 'cwd' for convenience */
static int
doftpcd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP)
        return -1;
    usprintf(ftp->control,"CWD %s\n",argv[1]);
    return getresp(ftp,200);
}
/* Translate 'mkdir' to 'xmkd' for convenience */
static int
domkdir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP)
        return -1;
    usprintf(ftp->control,"XMKD %s\n",argv[1]);
    return getresp(ftp,200);
}
/* Translate 'rmdir' to 'xrmd' for convenience */
static int
dormdir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP)
        return -1;
    usprintf(ftp->control,"XRMD %s\n",argv[1]);
    return getresp(ftp,200);
}
static int
dobinary(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *args[2];
  
    args[1] = "I";
    return dotype(2,args,p);
}
static int
doascii(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *args[2];
  
    args[1] = "A";
    return dotype(2,args,p);
}
  
/* Handle "type" command from user */
static int
dotype(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP)
        return -1;
    if(argc < 2){
        switch(ftp->type){
            case IMAGE_TYPE:
                tputs("Image\n");
                break;
            case ASCII_TYPE:
                tputs("Ascii\n");
                break;
            case LOGICAL_TYPE:
                tprintf("Logical bytesize %u\n",ftp->logbsize);
                break;
        }
        return 0;
    }
    switch(*argv[1]){
        case 'i':
        case 'I':
        case 'b':
        case 'B':
            ftp->type = IMAGE_TYPE;
            break;
        case 'a':
        case 'A':
            ftp->type = ASCII_TYPE;
            break;
        case 'L':
        case 'l':
            ftp->type = LOGICAL_TYPE;
            ftp->logbsize = atoi(argv[2]);
            break;
        default:
            tprintf("Invalid type %s\n",argv[1]);
            return 1;
    }
    return 0;
}
  
/* Handle "ftype" command */
int
doftype(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    if(argc < 2){
        tputs("Ftp initial TYPE is ");
        switch(Ftp_type){
            case IMAGE_TYPE:
                tputs("Image\n");
                break;
            case ASCII_TYPE:
                tputs("Ascii\n");
                break;
            case LOGICAL_TYPE:
                tprintf("Logical bytesize %u\n",Ftp_logbsize);
                break;
        }
        return 0;
    }
    switch(*argv[1]){
        case 'i':
        case 'I':
        case 'b':
        case 'B':
            Ftp_type = IMAGE_TYPE;
            break;
        case 'a':
        case 'A':
            Ftp_type = ASCII_TYPE;
            break;
        case 'L':
        case 'l':
            Ftp_type = LOGICAL_TYPE;
            Ftp_logbsize = atoi(argv[2]);
            break;
        default:
            tprintf("Invalid type %s\n",argv[1]);
            return 1;
    }
    return 0;
}
  
/* View added to jnos1.08 by Simon G1FHY _ mod by Paul@wolf.demon.co.uk */
/* Start view transfer. Syntax: view <remote name> */
static int
doview(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *remotename;
    register struct ftpcli  *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    remotename = argv[1];
  
    getsub(ftp,"RETR",remotename,NULLCHAR);
    return 0;
}
/* Start receive transfer. Syntax: get <remote name> [<local name>] */
static int
doget(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *remotename,*localname;
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    remotename = argv[1];
    if(argc < 3)
        localname = remotename;
    else
        localname = argv[2];
  
    getsub(ftp,"RETR",remotename,localname);
    return 0;
}
/* Get a collection of files */
static int
domget(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    FILE *files, *filel;
    char tmpname[80];
    char *buf, *local;
    int i, inlist;
    long r;
#ifdef MSDOS
    char *c;
#endif
  
    if((ftp = (struct ftpcli *)p) == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    tmpnam(tmpname);
    buf = mallocw(DIRBUF);
    ftp->state = RECEIVING_STATE;
    for(i=1;i<argc;i++){
        if(argv[i][0] == '@'){
            inlist = 1;
            if((filel = fopen(make_fname(ftp->curdirs->dir,&argv[i][1]), "r")) == NULLFILE){
                tprintf("Can't open listfile: %s\n", &argv[i][1]);
                continue;
            }
            if((files = fopen(tmpname, "w")) == NULLFILE){
                tprintf("Can't open tempfile: %s\n", tmpname);
                continue;
            }
            while(fgets(buf,DIRBUF,filel) != NULLCHAR){
                fputs(buf,files);
            }
            fclose(files);
            fclose(filel);
            if((files = fopen(tmpname, "r")) == NULLFILE){
                tprintf("Can't open tempfile: %s\n", tmpname);
                continue;
            }
        } else {
            inlist = 0;
            r = getsub(ftp,"NLST",argv[i],tmpname);
            if(ftp->abort)
                break;  /* Aborted */
            if(r == -1 || (files = fopen(tmpname,"r")) == NULLFILE){
                tprintf("Can't NLST %s\n",argv[i]);
                unlink(tmpname);
                continue;
            }
        }
        /* The tmp file now contains a list of the remote files, so
         * go get 'em. Break out if the user signals an abort.
         */
        while(fgets(buf,DIRBUF,files) != NULLCHAR){
            rip(buf);
            local = strdup(buf);
#ifdef  MSDOS
            if(inlist){
                strrev(local);
                strtok(local, "\\/[]<>,?#~()&%");
                strrev(local);
            }
            if((c = strstr(local, ".")) != NULLCHAR) {
                c++;
                c = strtok(c, ".");     /* remove 2nd period if any*/
            }
#endif
            getsub(ftp,"RETR",buf,local);
            free(local);
            if(ftp->abort){
                /* User abort */
                ftp->abort = 0;
                fclose(files);
                unlink(tmpname);
                free(buf);
                ftp->state = COMMAND_STATE;
                return 1;
            }
        }
        fclose(files);
        unlink(tmpname);
    }
    free(buf);
    ftp->state = COMMAND_STATE;
    ftp->abort = 0;
    return 0;
}
/* Resume interrupted file transfer. Syntax: resume <remote name> [<local name>] */
static int
doresume(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *remotename,*localname;
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    remotename = argv[1];
    if(argc < 3)
        localname = remotename;
    else
        localname = argv[2];
  
    getsub(ftp,"RSME",remotename,localname);
    return 0;
}
/* List remote directory. Syntax: dir <remote files> [<local name>] */
static int
dolist(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *remotename,*localname;
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    remotename = argv[1];
    if(argc > 2)
        localname = argv[2];
    else
        localname = NULLCHAR;
  
    getsub(ftp,"LIST",remotename,localname);
    return 0;
}
/* Remote directory list, short form. Syntax: ls <remote files> [<local name>] */
static int
dols(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *remotename,*localname;
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    remotename = argv[1];
    if(argc > 2)
        localname = argv[2];
    else
        localname = NULLCHAR;
  
    getsub(ftp,"NLST",remotename,localname);
    return 0;
}
/* Common code to LIST/NLST/RETR/RSME and mget
 * Returns number of bytes received if successful
 * Returns -1 on error
 */
static long
getsub(ftp,command,remotename,localname)
register struct ftpcli *ftp;
char *command,*remotename,*localname;
{
    long total;
    FILE *fp;
    int cnt,resp,i,control,savmode;
    char *mode;
    struct sockaddr_in lsocket;
    struct sockaddr_in lcsocket;
    int32 startclk,rate;
    int vsave;
    int typewait = 0, portwait = 0;
    int prevstate;
    long starting;
#ifdef  LZW
    int lzwmode, lzwbits;
    int rcode;
    extern int16 Lzwbits;
    extern int Lzwmode;
#endif
  
    if(ftp == NULLFTP)
        return -1;
    control = ftp->control;
    savmode = ftp->type;
  
    switch(ftp->type){
        case IMAGE_TYPE:
        case LOGICAL_TYPE:
            if(strcmp(command,"RSME") == 0)
                mode = APPEND_BINARY;
            else
                mode = WRITE_BINARY;
            break;
        case ASCII_TYPE:
            if(strcmp(command,"RSME") == 0)
                mode = APPEND_TEXT;
            else
                mode = WRITE_TEXT;
            break;
    }
    /* Open the file */
    if(localname == NULLCHAR){
        fp = NULLFILE;
    } else if((fp = fopen(make_fname(ftp->curdirs->dir,localname),mode)) == NULLFILE){
        tprintf("Can't write %s: %s\n",localname,sys_errlist[errno]);
        return -1;
    }
    /* Open the data connection */
    ftp->data = socket(AF_INET,SOCK_STREAM,0);
    listen(ftp->data,0);    /* Accept only one connection */
    prevstate = ftp->state;
    ftp->state = RECEIVING_STATE;
  
    /* Send TYPE message, if necessary */
    if(strcmp(command,"LIST") == 0 || strcmp(command,"NLST") == 0){
        /* Directory listings are always in ASCII */
        ftp->type = ASCII_TYPE;
    }
    if(ftp->typesent != ftp->type){
        switch(ftp->type){
            case ASCII_TYPE:
                usputs(control,"TYPE A\n");
                break;
            case IMAGE_TYPE:
                usputs(control,"TYPE I\n");
                break;
            case LOGICAL_TYPE:
                usprintf(control,"TYPE L %d\n",ftp->logbsize);
                break;
        }
        ftp->typesent = ftp->type;
        if(!ftp->batch){
            resp = getresp(ftp,200);
            if(resp == -1 || resp > 299)
                goto failure;
        } else
            typewait = 1;
    }
    /* Send the PORT message. Use the IP address
     * on the local end of our control connection.
     */
    i = SOCKSIZE;
    getsockname(ftp->data,(char *)&lsocket,&i); /* Get port number */
    i = SOCKSIZE;
    getsockname(ftp->control,(char *)&lcsocket,&i);
    lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
    sendport(control,&lsocket);
    if(!ftp->batch){
        /* Get response to PORT command */
        resp = getresp(ftp,200);
        if(resp == -1 || resp > 299)
            goto failure;
    }
    else portwait=1;
#ifdef LZW
    if(ftp->uselzw && ftp->type == ASCII_TYPE) {
        /* Send XLZW string */
        usprintf(control, "XLZW %d %d\n", Lzwbits, Lzwmode);
        if(ftp->batch){
            /* Get response to TYPE command, if sent */
            if(typewait){
                resp = getresp(ftp,200);
                if(resp == -1 || resp > 299){
                    goto failure;
                }
                typewait=0;
            }
            /* Get response to PORT command, if needed */
            if(portwait){
                resp = getresp(ftp,200);
                if(resp == -1 || resp > 299){
                    goto failure;
                }
                portwait=0;
            }
        }
        resp = getresp(ftp, 200);
        if (resp == -1 || resp > 299)
            ftp->uselzw=0;    /* Command not supported. */
        else {
            /* strip off any garbage. */
            rip(ftp->line);
            rcode = lzwmode = lzwbits = 0;
            /* Get bits from the XLZW response. */
            sscanf(ftp->line, "%d %d %d", &rcode, &lzwbits, &lzwmode);
            if ((rcode >= 200) && (rcode < 300)) {
                if (lzwmode != Lzwmode || lzwbits != Lzwbits) {
                    lzwmode = LZWCOMPACT;
                    lzwbits = LZWBITS;
                }
            }
            /* Turn on the LZW stuff. */
            lzwinit(ftp->data,lzwbits,lzwmode);
        }
    }
#endif
    /* Generate the command to start the transfer */
    if(remotename != NULLCHAR)
        usprintf(control,"%s %s\n",command,remotename);
    else
        usprintf(control,"%s\n",command);
  
    if(ftp->batch){
        /* Get response to TYPE command, if sent */
        if(typewait){
            resp = getresp(ftp,200);
            if(resp == -1 || resp > 299)
                goto failure;
        }
        /* Get response to PORT command, if needed */
        if(portwait){
            resp = getresp(ftp,200);
            if(resp == -1 || resp > 299)
                goto failure;
        }
    }
    /* Get the intermediate "150" response */
    resp = getresp(ftp,100);
    if(resp == -1 || resp >= 400)
        goto failure;
  
    /* Wait for the server to open the data connection */
    cnt = 0;
    accept(ftp->data,NULLCHAR,(int *)NULL);
    startclk = msclock();
  
    /* If output is to the screen, temporarily disable hash marking */
    vsave = ftp->verbose;
    if(vsave >= V_HASH && fp == NULLFILE)
        ftp->verbose = V_NORMAL;
  
    if(strcmp(command,"RSME") == 0){
        if((starting = getsize(fp)) == -1)
            starting = 0L;
        usprintf(control,"%ld %lu\n",starting,checksum(fp,starting));
        usflush(control);
        fseek(fp,starting,SEEK_SET);
    }
    total = recvfile(fp,ftp->data,ftp->type,(ftp->verbose >= V_HASH) ? ftp->verbose : 0);
    /* Immediately close the data connection; some servers (e.g., TOPS-10)
     * wait for the data connection to close completely before returning
     * the completion message on the control channel
     */
    close_s(ftp->data);
    ftp->data = -1;
  
    if(fp != NULLFILE && fp != stdout)
        fclose(fp);
    if(remotename == NULLCHAR)
        remotename = "";
    if(total == -1){
        tprintf("%s %s: Error/abort during data transfer\n",command,remotename);
    } else if(ftp->verbose >= V_SHORT){
        startclk = msclock() - startclk;
        rate = 0;
        if(startclk != 0){      /* Avoid divide-by-zero */
            if(total < 4294967L){
                rate = (total*1000)/startclk;
            } else {        /* Avoid overflow */
                rate = total/(startclk/1000);
            }
        }
        tprintf("%s %s: %lu bytes in %lu sec (%lu/sec)\n",
        command,remotename, total,startclk/1000,rate);
    }
    /* Get the "Sent" message */
    getresp(ftp,200);
  
    ftp->state = prevstate;
    ftp->verbose = vsave;
    ftp->type = savmode;
    return total;
  
    failure:
    /* Error, quit */
    if(fp != NULLFILE && fp != stdout)
        fclose(fp);
    close_s(ftp->data);
    ftp->data = -1;
    ftp->state = prevstate;
    ftp->type = savmode;
    return -1;
}
/* Send a file. Syntax: put <local name> [<remote name>] */
static int
doput(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    char *remotename,*localname;
  
    if((ftp = (struct ftpcli *)p) == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    localname = argv[1];
    if(argc < 3)
        remotename = localname;
    else
        remotename = argv[2];
  
    putsub(ftp,remotename,localname,0);
    return 0;
}
/* Put a collection of files */
static int
domput(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    FILE *files;
    int i,j;
    char tmpname[80];
    char *buf,*file;
  
    if((ftp = (struct ftpcli *)p) == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    tmpnam(tmpname);
    if((files = fopen(tmpname,"w+")) == NULLFILE){
        tputs("Can't list local files\n");
        unlink(tmpname);
        return 1;
    }
  
    for(i=1;i<argc;i++) {
    /* Use the path in the ftp client struct, since user may have done
     * a lcd command to change dir !
     */
        file = pathname(ftp->curdirs->dir,argv[i]);
    /* Shift everything back one byte, pathname returns with a leading '/'! */
    /*
    for(j=1;j<=strlen(file);j++)
        file[j-1] = file[j];
    */
        getdir(file,0,files);
        free(file);
    }
    rewind(files);
    buf = mallocw(DIRBUF);
    ftp->state = SENDING_STATE;
    while(fgets(buf,DIRBUF,files) != NULLCHAR){
        rip(buf);
        putsub(ftp,buf,buf,0);
        if(ftp->abort)
            break;          /* User abort */
    }
    fclose(files);
    unlink(tmpname);
    free(buf);
    ftp->state = COMMAND_STATE;
    ftp->abort = 0;
    return 0;
}
/* Put a file, appending data to it - iw0cnb */
static int
dorput(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    char *remotename,*localname;
  
    if((ftp = (struct ftpcli *)p) == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    localname = argv[1];
    if(argc < 3)
        remotename = localname;
    else
        remotename = argv[2];
  
    putsub(ftp,remotename,localname,1);
    return 0;
}
/* Common code to put, mput, rput.
 * Returns number of bytes sent if successful
 * Returns -1 on error
 */
static long
putsub(ftp,remotename,localname,putr)
register struct ftpcli *ftp;
char *remotename,*localname;
int putr;       /* Flag: 0 if standard put, 1 if put with resume */
{
    char *mode;
    int i,resp,control;
    long total;
    FILE *fp;
    struct sockaddr_in lsocket,lcsocket;
    int32 startclk,rate;
    int typewait = 0, portwait = 0;
    int prevstate;
    char *line;
    long starting;
    unsigned long check, local_check;
#ifdef  LZW
    int lzwmode, lzwbits;
    int rcode;
    extern int16 Lzwbits;
    extern int Lzwmode;
#endif
  
    control = ftp->control;
    if(ftp->type == IMAGE_TYPE)
        mode = READ_BINARY;
    else
        mode = READ_TEXT;
  
    /* Open the file */
    if((fp = fopen(make_fname(ftp->curdirs->dir,localname),mode)) == NULLFILE){
        tprintf("Can't read %s: %s\n",localname,sys_errlist[errno]);
        return -1;
    }
    if(ftp->type == ASCII_TYPE && isbinary(fp)){
        tprintf("Warning: type is ASCII and %s appears to be binary\n",localname);
    }
    /* Open the data connection */
    ftp->data = socket(AF_INET,SOCK_STREAM,0);
    listen(ftp->data,0);
    prevstate = ftp->state;
    ftp->state = SENDING_STATE;
  
    /* Send TYPE message, if necessary */
    if(ftp->typesent != ftp->type){
        switch(ftp->type){
            case ASCII_TYPE:
                usputs(control,"TYPE A\n");
                break;
            case IMAGE_TYPE:
                usputs(control,"TYPE I\n");
                break;
            case LOGICAL_TYPE:
                usprintf(control,"TYPE L %d\n",ftp->logbsize);
                break;
        }
        ftp->typesent = ftp->type;
  
        /* Get response to TYPE command */
        if(!ftp->batch){
            resp = getresp(ftp,200);
            if(resp == -1 || resp > 299){
                goto failure;
            }
        } else
            typewait = 1;
    }
    /* Send the PORT message. Use the IP address
     * on the local end of our control connection.
     */
    i = SOCKSIZE;
    getsockname(ftp->data,(char *)&lsocket,&i);
    i = SOCKSIZE;
    getsockname(ftp->control,(char *)&lcsocket,&i);
    lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
    sendport(control,&lsocket);
    if(!ftp->batch){
        /* Get response to PORT command */
        resp = getresp(ftp,200);
        if(resp == -1 || resp > 299){
            goto failure;
        }
    }
    else portwait=1;
#ifdef LZW
    if(ftp->uselzw && ftp->type == ASCII_TYPE) {
        /* Send XLZW string */
        usprintf(control, "XLZW %d %d\n", Lzwbits, Lzwmode);
        if(ftp->batch){
            /* Get response to TYPE command, if sent */
            if(typewait){
                resp = getresp(ftp,200);
                if(resp == -1 || resp > 299){
                    goto failure;
                }
                typewait=0;
            }
            /* Get response to PORT command, if needed */
            if(portwait){
                resp = getresp(ftp,200);
                if(resp == -1 || resp > 299){
                    goto failure;
                }
                portwait=0;
            }
        }
        resp = getresp(ftp, 200);
        if (resp == -1 || resp > 299)
            ftp->uselzw=0;    /* Command not supported. */
        else {
            /* strip off any garbage. */
            rip(ftp->line);
            rcode = lzwmode = lzwbits = 0;
            /* Get bits from the XLZW response. */
            sscanf(ftp->line, "%d %d %d", &rcode, &lzwbits, &lzwmode);
            if ((rcode >= 200) && (rcode < 300)) {
                if (lzwmode != Lzwmode || lzwbits != Lzwbits) {
                    lzwmode = LZWCOMPACT;
                    lzwbits = LZWBITS;
                }
            }
            /* Turn on the LZW stuff. */
            lzwinit(ftp->data,lzwbits,lzwmode);
        }
    }
#endif
    /* Generate the command to start the transfer */
    if(putr == 1)
        usprintf(control,"RPUT %s\n",remotename);
    else
        usprintf(control,"STOR %s\n",remotename);
  
    if(ftp->batch){
        /* Get response to TYPE command, if sent */
        if(typewait){
            resp = getresp(ftp,200);
            if(resp == -1 || resp > 299){
                goto failure;
            }
        }
        /* Get response to PORT command, if needed */
        if(portwait){
            resp = getresp(ftp,200);
            if(resp == -1 || resp > 299){
                goto failure;
            }
        }
    }
    /* Get the intermediate "150" response */
    resp = getresp(ftp,100);
    if(resp == -1 || resp >= 400){
        goto failure;
    }
  
    /* Wait for the data connection to open. Otherwise the first
     * block of data would go out with the SYN, and this may confuse
     * some other TCPs
     */
    accept(ftp->data,NULLCHAR,(int *)NULL);
  
    startclk = msclock();
    if(putr == 1){          /* Wait for file offset and checksum */
        line=mallocw(40);
        if (recvline(control,line,40) == -1) goto failure;
        starting=atol(line);
        check = (unsigned long)atol(strchr(line,' '));
        free(line);
        local_check = checksum(fp,starting);
        if(ftp->verbose >= V_HASH)
            tprintf("Remote checksum: %lu Local checksum: %lu Offset: %ld\n",check,local_check,starting);
        check -= local_check;
        if(check != 0){
            tprintf("Can't send %s: files are different\n",localname);
            shutdown(ftp->data,1);
            getresp(ftp,200);
            goto failure;
        }
    }
  
    total = sendfile(fp,ftp->data,ftp->type,(ftp->verbose >= V_HASH) ? ftp->verbose : 0,NULL);
    close_s(ftp->data);
    ftp->data = -1;
    fclose(fp);
  
    /* Wait for control channel ack before calculating transfer time;
     * this accounts for transmitted data in the pipe.
     */
    getresp(ftp,200);
  
    if(total == -1){
        tprintf("STOR %s: Error/abort during data transfer\n",remotename);
    } else if(ftp->verbose >= V_SHORT){
        startclk = msclock() - startclk;
        rate = 0;
        if(startclk != 0){      /* Avoid divide-by-zero */
            if(total < 4294967L){
                rate = (total*1000)/startclk;
            } else {        /* Avoid overflow */
                rate = total/(startclk/1000);
            }
        }
        tprintf("STOR %s: %lu bytes in %lu sec (%lu/sec)\n",
        remotename,total,startclk/1000,rate);
    }
    ftp->state = prevstate;
    return total;
  
    failure:
    /* Error, quit */
    fclose(fp);
    close_s(ftp->data);
    ftp->data = -1;
    ftp->state = prevstate;
    return -1;
}
/* Abort a GET or PUT operation in progress. Note: this will leave
 * the partial file on the local or remote system
 */
int
doabort(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct session *sp;
    register struct ftpcli *ftp;
  
    sp = (struct session *)p;
    if(sp == NULLSESSION)
        return -1;
  
    /* Default is the current session, but it can be overridden with
     * an argument.
     */
    if(argc > 1)
        sp = sessptr(argv[1]);
  
    if(sp == NULLSESSION || sp->type != FTP){
        tputs("Not an active FTP session\n");
        return 1;
    }
    ftp = sp->cb.ftp;
    switch(ftp->state){
        case COMMAND_STATE:
            tputs("No active transfer\n");
            return 1;
        case SENDING_STATE:
        /* Send a premature EOF.
         * Unfortunately we can't just reset the connection
         * since the remote side might end up waiting forever
         * for us to send something.
         */
            shutdown(ftp->data,1);  /* Note fall-thru */
            ftp->abort = 1;
            break;
        case RECEIVING_STATE:
        /* Just blow away the receive socket */
            shutdown(ftp->data,2);  /* Note fall-thru */
            ftp->abort = 1;
            break;
    }
    return 0;
}
/* send PORT message */
static void
sendport(s,socket)
int s;
struct sockaddr_in *socket;
{
    /* Send PORT a,a,a,a,p,p message */
    usprintf(s,"PORT %u,%u,%u,%u,%u,%u\n",
    hibyte(hiword(socket->sin_addr.s_addr)),
    lobyte(hiword(socket->sin_addr.s_addr)),
    hibyte(loword(socket->sin_addr.s_addr)),
    lobyte(loword(socket->sin_addr.s_addr)),
    hibyte(socket->sin_port),
    lobyte(socket->sin_port));
}
  
/* Wait for, read and display response from FTP server. Return the result code.
 */
static int
getresp(ftp,mincode)
struct ftpcli *ftp;
int mincode;    /* Keep reading until at least this code comes back */
{
    register char *line;
    int rval;
  
    usflush(ftp->control);
    line = mallocw(LINELEN);
    for(;;){
        /* Get line */
        if(recvline(ftp->control,line,LINELEN) == -1){
            rval = -1;
            break;
        }
        rip(line);              /* Remove cr/lf */
        rval = atoi(line);
        if(rval >= 400 || ftp->verbose >= V_NORMAL)
            tprintf("%s\n",line);   /* Display to user */
  
        /* Messages with dashes are continued */
        if(line[3] != '-' && rval >= mincode)
            break;
    }
#ifdef LZW
    if((rval == 215) || (rval == 299) || (rval == 399)) {  /* Return encrypted password */
        ftp->line = line;         /* caller must free it */
        line = NULLCHAR;
    }
#else
    if((rval == 215) || (rval == 399)) {          /* Return encrypted password */
        ftp->line = line;         /* caller must free it */
        line = NULLCHAR;
    }
#endif
    free(line);
    return rval;
}
  
/* Issue a prompt and read a line from the user */
static int
getline(sp,prompt,buf,n)
struct session *sp;
char *prompt;
char *buf;
int n;
{
    int rtn;  /* K5JB */

    /* If there's something already there, don't issue prompt */
    if(socklen(sp->input,0) == 0)
        tputs(prompt);
  
    usflush(sp->output);
    rtn = recvline(sp->input,buf,n);
    /* This added so our commands added to record file -- K5JB */
    if(sp->ttystate.echo && sp->record != NULLFILE)
           fputs(buf,sp->record);
    return rtn;
}
  
/* Attempt to log in the user whose name is in ftp->username and password
 * in pass
 */
static char *
ftpcli_login(ftp,host)
struct ftpcli *ftp;
char *host; {
  
    char buf[80],*cp,*cp1;
    FILE *fp;
  
    extern char *Hostfile;  /* List of user names and permissions */
  
    if((fp = fopen(Hostfile,"r")) == NULLFILE){
        return NULLCHAR;
    }
    while(fgets(buf,sizeof(buf),fp),!feof(fp)){
        buf[strlen(buf)-1] = '\0';      /* Nuke the newline */
        if(buf[0] == '#')
            continue;       /* Comment */
        if((cp = strpbrk(buf," \t")) == NULLCHAR)
            /* Bogus entry */
            continue;
        *cp++ = '\0';           /* Now points to user name */
        if(strcmp(host,buf) == 0)
            break;          /* Found host name */
    }
    if(feof(fp)){
        /* User name not found in file */
        fclose(fp);
        return NULLCHAR;
    }
    fclose(fp);
    /* Look for space after user field in file */
    cp = skipwhite(cp);
    if((cp1 = strpbrk(cp," \t")) == NULLCHAR)
        /* if not there then we'll prompt */
        ftp->password = NULLCHAR;
    else
        *cp1++ = '\0';          /* Now points to password */
    cp1 = skipwhite(cp1);
    if(strcmp(cp,"*") == 0)
        cp1 = "anonymous";
    ftp->password = strdup(cp1);
    return strdup(cp);
}
  
int
dolcd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
  
    if(argc > 1){
        if (!dir_ok(argv[1],ftp->curdirs)) {
  
            tprintf("Invalid Drive/Directory - %s\n",argv[1]);
            return 1;
        }
  
    }
    tprintf("Local Directory - %s\n",ftp->curdirs->dir);
  
    return 0;
}
  
  
int
doldir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    char ** margv;
  
    margv=(char **)callocw(2,sizeof(char*));
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
  
    tputc('\n');
    margv[1]=strdup(make_dir_path(argc,argv[1],ftp->curdirs->dir));
    dircmd(2,margv,p);
    free(margv[1]);
    free(margv);
    tputc('\n');
    return 0;
}
  
int
dolmkdir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    char *buf;
  
    ftp = (struct ftpcli *)p;
    if(ftp == NULLFTP){
        tputs(Notsess);
        return 1;
    }
    undosify(argv[1]);
    buf=strdup(make_fname(ftp->curdirs->dir,argv[1]));
#ifdef UNIX
    if (mkdir(buf, 0777) == -1)
#else
        if (mkdir(buf) == -1)
#endif
            tprintf("Can't make %s: %s\n",buf,sys_errlist[errno]);
        else
            tprintf("Directory %s Created\n",buf);
    free(buf);
    return 0;
}
  
#endif /* FTPSESSION */
  
