/***************************************************************************
 *                                                                         *
 *   FTP.C                                                                 *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   File Transfer Protocol client.                                        *
 *                                                                         *
 *                                              - R. Stein  7/20/94        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "fsd.h"
#include "ftscope.h"
#include "filexfer.h"
#include "tcpip.h"
#include "tno.h"
#include "crnul.h"
#include "telnetd.h"
#include "alias.h"
#include "chandir.h"
#include "dns.h"
#include "ftp.h"
#include "galftp.h"

#define FILREV "$Revision: 25 $"

#ifdef GCWINNT
#define ESHUTDOWN 57
#endif

static INT globalftp(VOID);
static GBOOL ftpinp(VOID);
static VOID hdlcmd(VOID);
static INT ftpcdi(CHAR *cmd);
static INT cmdonoff(INT uflmsk,INT onmsg,INT offmsg);
static INT fupftp(INT fupcod);
static VOID numchk(VOID);
static INT numftp(VOID);
static VOID hlpout(INT hdrmsg,INT fstmsg);
static VOID savpar(VOID);
static INT prtprep(INT nxtstp,INT rwin);
static INT ascprep(INT nxtstp,INT rwin);
static VOID abtget(INT arcv);
static VOID abtput(VOID);
static VOID cmdunk(CHAR *cmd);
static VOID pmtnow(VOID);
static VOID ftpcbk(struct dns *dnsptr);
static INT ftpcall(VOID);
static VOID bgncon(struct ftpusr *fpu);
static VOID abtcon(VOID);
static VOID endcon(VOID);
static VOID ftpsts(VOID);
static VOID ftphup(VOID);
static VOID ftpsnd(struct ftpusr *fpu);
static VOID ftprcv(struct ftpusr *fpu);
static VOID endcrcv(struct ftpusr *fpu,INT errmsg);
static VOID hdllins(VOID);
static VOID hdllin(CHAR *reply);
static VOID hdlrpl(CHAR a,INT code);
static VOID howdnl(VOID);
static VOID fstfile(VOID);
static VOID nxtfile(VOID);
static INT nlsnxt(VOID);
static VOID mgprompt(VOID);
static VOID nxtspec(VOID);
static INT mgtout(VOID);
static VOID sw2dat(VOID (*lsnrou)());
static INT tshftp(INT tshcod);
static INT retr(CHAR *lclreq,INT nxtstt);
static VOID filcls(VOID);
static VOID datlsn(struct ftpusr *fpu);
static VOID datrcv(struct ftpusr *fpu);
static VOID endrcv(struct ftpusr *fpu);
static VOID cycleme(VOID);
static INT okfput(CHAR *fname);
static INT opnput(VOID);
static CHAR *ftppath(CHAR *filespec);
static VOID putlsn(struct ftpusr *fpu);
static VOID datsnd(struct ftpusr *fpu);
static VOID autoals(VOID);
static INT ftpops(INT code,INT parm);
static VOID sndop3(CHAR c1,CHAR c2,CHAR c3);
static VOID sndopc(CHAR *bytes,INT nbytes);
static VOID passcmd(CHAR *svrcmd);
static INT sndcmd(CHAR *cmd,CHAR *parm,INT newstt);
static CHAR *femaddr(VOID);
static VOID ret2ftp(VOID);
static INT ephport(VOID);
static INT ephlstn(VOID);
static INT ephaccp(VOID);
static VOID ephclos(VOID);
static VOID dskerr(CHAR *type,INT err);
static CHAR *tvar_ftp_room(VOID);
static CHAR *tvar_ftp_remserv(VOID);
static CHAR *tvar_ip_remserv(VOID);

INT ftpstt;                        /* FTP client module state number       */
struct module ftpmodule={          /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     ftpinp,                       /*    input routine if selected         */
     ftpsts,                       /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     ftphup,                       /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL                          /*    finish-up (sys shutdown) routine  */
};

#define FTPEXIT  -1                /* substate to trigger FTP client exit  */

#define ALLWAITS -10               /* threshhold for wait-for-svr states   */
#define CONWAIT -10                /* wait for hello after first connect   */
#define UIDWAIT -11                /* wait for server to proc USER <userid>*/
#define PSWWAIT -12                /* wait for server to proc PASS <passwd>*/
#define RPLWAIT -13                /* wait for server to process command   */
#define DIRWAIT -14                /* wait for svr to proc dir's PORT cmd  */
#define LSTWAIT -15                /* wait for svr to proc dir's LIST cmd  */
#define AUDWAIT -16                /* wait for anonymous user login        */
#define ABTWAIT -17                /* wait for svr to process ABOR cmd     */
#define GETWAIT -18                /* wait for svr to proc get's PORT cmd  */
#define GRCWAIT -19                /* wait for svr to proc get's RETR cmd  */
#define MORWAIT -20                /* wait for svr to proc more's PORT cmd */
#define MRCWAIT -21                /* wait for svr to proc more's RETR cmd */
#define LSWAIT  -22                /* wait for svr to proc ls's PORT cmd   */
#define PUTWAIT -23                /* wait for svr to proc put's PORT cmd  */
#define PSTWAIT -24                /* wait for svr to proc put's STOR cmd  */
#define PUTOVER -25                /* file put over, wait for svr's 2XX    */
#define TPAWAIT -26                /* waiting for response to "TYPE A"     */
#define RNFWAIT -27                /* waiting for resp to RNFR command     */
#define GETFNSH -28                /* get all done except final report     */
#define MGPWAIT -29                /* wait for server to proc mget's PORT  */
#define MGNWAIT -30                /* wait for server to proc mget's NLST  */
#define MGIWAIT -31                /* wait for server to proc mget's TYPEI */
#define MGGWAIT -32                /* wait for mget's PORT before RETR     */
#define MGTWAIT -33                /* wait for svr to proc mget's RETR     */
#define GETOVER -34                /* wait for final info in file gotten   */
#define QITWAIT -35                /* wait for connection to close         */
#define ARCWAIT -36                /* wait for one last reply before TYPE I*/
#define ABAWAIT -37                /* wait for ABOR reply before TYPE I    */

#define CCMDSIZ 8                  /* max length+1 of client commands      */

static
CHAR ftpcmds[][CCMDSIZ]={          /*--- list of FTP client commands ------*/
     "?",                          /* Must be in perfect sync with the two */
     "account",                    /* sets of level 6 options QUEST - QUOTE*/
     "ascii",                      /* and HQUEST - HQUOTE in GALFTP.MSG.   */
     "binary",
     "bye",
     "cd",
     "cdup",
     "delete",
     "dir",
     "exit",
     "get",
     "help",
     "image",
     "ls",
     "mget",
     "mkdir",
     "mode",
     "more",
     "prompt",
     "put",
     "pwd",
     "quit",
     "quote",
     "rename",
     "rhelp",
     "rmdir",
     "rstat",
     "status",
     "system",
     "type",
     "user",
     "verbose",
     "version",
     "x"
};
#define NCMDS (sizeof(ftpcmds)/sizeof(ftpcmds[0]))
#define NONE  -1                   /* ftpcdi() return value, no exact match*/
INT nmatch;                        /* ftpcdi() sets to no. of cmd matches  */

#ifndef UNIX
#if HQUEST-QUEST != NCMDS
#error Wrong number of SHORT-help messages somewhere after QUEST in GALFTP.MSG
#endif

#if HEND-HQUEST != NCMDS
#error Wrong number of LONG-help messages somewhere after HQUEST in GALFTP.MSG
#endif
#endif

INT rwdisp;                        /* receive window when displaying stuff */
INT outsiz14;                      /* 1/4 of OUTSIZ                        */
INT outsiz12;                      /* 1/2 of OUTSIZ                        */
INT outsiz34;                      /* 3/4 of OUTSIZ                        */

static HMCVFILE ftpmb;             /*--- for options in GALFTP.MCV --------*/
CHAR ftpgo;                        /* can users type "/FTP" command?       */
INT contwt;                        /* dialing out patience, 1/16 sec units */
CHAR *maildmn;                     /* mail domain name to use in /ftpa     */
CHAR *ftppmt;                      /* ftp client prompt                    */
CHAR audcon;                       /* audit ftp connects                   */
CHAR auddsc;                       /* audit ftp disconnects                */
CHAR audget;                       /* record file get's (& tag for dnl)    */
CHAR auddnl;                       /* record download of gotten files      */
CHAR audput;                       /* record file put's                    */
INT maxftp;                        /* max simultaneous outgoing ftps       */
INT maxfer;                        /* max simultaneous ftp client xfers    */
INT ftpchg;                        /* surcharge per minute for FTP client  */
LONG ftpkchg;                      /* charge / 1K bytes traffic via FTP    */

CHAR *sesflags;                    /* array of session-life user flags     */
#define UFLANON 0x01               /* user typed "/ftpa" instead of "/ftp" */
#define UFLTAGI 0x02               /* user given tag instructions already  */
#define UFLVBOS 0x04               /* verbose setting                      */
#define UFLPRMT 0x08               /* prompting for each mgotten file      */
#define UFLPSKP 0x10               /* skip prompting rest of this mget only*/

CHAR abosq1[]={IAC,IP,IAC};        /* abort sequence #1 (urgent mode)      */
CHAR abosq2[]={DM};                /* abort sequence #2 (non-urgent mode)  */

VOID EXPORT
init__galftp(VOID)                 /* FTP client initialization            */
{
     init__tcpip();
     init__galtntd();
     stzcpy(ftpmodule.descrp,gmdnam("galftp.mdf"),MNMSIZ);
     ftpstt=register_module(&ftpmodule);
     ftpmb=opnmsg("galftp.mcv");
     maxftp=numopt(MAXFTP,0,250);
     maxfer=numopt(MAXFER,0,250);
     ftpgo=ynopt(FTPGO);
     ftpchg=numopt(FTPCHG,-32767,32767);
     ftpkchg=lngopt(FTPKCHG,-2000000000L,2000000000L);
     contwt=numopt(CONTWT,-1,2047)<<4;
     maildmn=stgopt(MAILDMN);
     ftppmt=stgopt(FTPPMT);
     audcon=ynopt(AUDCON);
     auddsc=ynopt(AUDDSC);
     audget=ynopt(AUDGET);
     auddnl=ynopt(AUDDNL);
     audput=ynopt(AUDPUT);
     globalcmd(globalftp);
     outsiz14=OUTSIZ/4;
     outsiz12=OUTSIZ/2;
     outsiz34=outsiz12+outsiz14;
     rwdisp=min(rcvwin,outsiz12-1);
     dclvda(outsiz34+1);
     dclvda(sizeof(struct ftpusr));
     sesflags=alczer(nterms);
     register_textvar("FTP_ROOM",tvar_ftp_room);
     register_textvar("FTP_REMSERV",tvar_ftp_remserv);
     register_textvar("IP_REMSERV",tvar_ip_remserv);
}

VOID EXPORT
initwc__galftp(VOID)
{
     init__galftp();
}

static INT
globalftp(VOID)                    /* global "/ftp <host>" command         */
{
     CHAR *cp;
     INT rc;

     if (margc >= 1 && sameto("/ftp",margv[0]) && ftpgo) {
          if (sameas("/ftp",margv[0])) {
               sesflags[usrnum]&=~UFLANON;
               cp=margv[0];
               rstrin();
               movmem(cp,cp+3,(UINT)(INPSIZ-(cp-input)-3));
               movmem("/go ",cp,4);
          }
          else if (sameas("/ftpa",margv[0])) {
               sesflags[usrnum]|=UFLANON;
               cp=margv[0];
               rstrin();
               movmem(cp,cp+2,(UINT)(INPSIZ-(cp-input)-3));
               movmem("/go ftp",cp,7);
          }
          else {
               return(0);
          }
          input[INPSIZ-1]='\0';
          parsin();
          rc=globalgo();
          if (usrptr->state != ftpstt) {
               sesflags[usrnum]&=~UFLANON;
               if (usrptr->flags&MASTER) {
                    setmbk(ftpmb);
                    prfmsg(FTPNOPAG);
                    outprf(usrnum);
                    rstmbk();
               }
          }
          return(rc);
     }
     return(0);
}

static GBOOL
ftpinp(VOID)                       /* FTP client input handler             */
{
     GBOOL rc=TRUE;
     CHAR *cp;
     INT i;

     setmbk(ftpmb);
     if (margc == 1 && sameas(margv[0],"X")) {
          if (usrptr->flags&MONHID) {
               shomal();
               usrptr->flags&=~MONHID;
          }
          switch (usrptr->substt) {
          case 0:
          case FTPHOST:
               rc=FALSE;
               break;
          case GETALR:
          case GETBNG:
          case GETRSV:
          case MGPROMPT:
               ret2ftp();
               outprf(usrnum);
               break;
          default:
               dnsabt();
               filcls();
               ephclos();
               abtcon();
               prfmsg(XEXIT,ftpptr->dns.name);
               echon();
               outprf(usrnum);
               rc=FALSE;
               break;
          }
          rstrxf();
          sesflags[usrnum]&=~UFLANON;
          return(rc);
     }
     do {
          bgncnc();
          switch (usrptr->substt) {
          case 0:
               cncchr();
               if (numonl(ftpstt) > maxftp) {
                    prfmsg(maxftp == 0 ? FTPZMANY : FTPMANY,maxftp);
                    rc=FALSE;
                    break;
               }
               setmem(ftpptr,sizeof(struct ftpusr),0);
               ftpptr->socket=
               ftpptr->lsnsok=
               ftpptr->datsok=-1;
               usrptr->crdrat=mmucrr+ftpchg;
               chdmak();
               MKDIR(ftppath(NULL));
               prfmsg(usrptr->substt=FTPHOST);
               break;
          case FTPHOST:
               parsin();
               for (i=0 ; i < margc ; i++) {
                    if (sameas(margv[i],"-a")
                     || sameas(margv[i],"/a")) {
                         sesflags[usrnum]|=UFLANON;
                         rstrin();
                         if (i+1 < margc) {
                              strcpy(margv[i],margv[i+1]);
                         }
                         else {
                              margv[i][0]='\0';
                         }
                         parsin();
                         break;
                    }
               }
               rstrin();
               if (*(cp=cncall()) == '\0' || sameas(cp,"?")) {
                    prfmsg(HOSTHELP);
                    dnshelp();
                    prfmsg(FTPHOST);
               }
               else {
                    ftpptr->port=htons(FTPCPRT);
                    stzcpy(ftpptr->dns.name,cp,DNSNSZ);
                    ftpptr->dns.numaddr=1;
                    ftpptr->dns.callbk=ftpcbk;
                    if (!dnsn2a(&ftpptr->dns)) {
                         prfmsg(usrptr->substt=LOOKING,cp);
                    }
               }
               break;
          case ASKUID:
               if (margc == 0) {
                    prfmsg(ASKUID);
               }
               else {
                    sndcmd("USER",cncall(),UIDWAIT);
               }
               break;
          case ASKPSW:
               prf("\r");
               if (margc == 0) {
                    prfmsg(ASKPSW);
               }
               else {
                    sndcmd("PASS",cncall(),PSWWAIT);
                    usrptr->flags&=~MONHID;
                    strcpy(input,"<password>");
               }
               outprf(usrnum);
               shomal();
               break;
          case ASKACT:
               if (margc == 0) {
                    prfmsg(ASKACT);
               }
               else {
                    sndcmd("ACCT",cncall(),RPLWAIT);
               }
               break;
          case FTPPMT:
               parsin();
               hdlcmd();
               cncall();
               break;
          default:                 /* all other XXXWAIT states go here     */
               if (!(usrptr->flags&INJOIP)) {
                    prfmsg(PLSSBY);
               }
               break;
          case LSTWAIT:
          case MRCWAIT:
          case MGNWAIT:
               if ((usrptr->flags&(INJOIP+ABOIP)) != INJOIP) {
                    abtget(1);
               }
               break;
          case GRCWAIT:
          case MGIWAIT:
          case MGTWAIT:
               if ((usrptr->flags&(INJOIP+ABOIP)) != INJOIP) {
                    abtget(0);
               }
               break;
          case PSTWAIT:
               if ((usrptr->flags&(INJOIP+ABOIP)) != INJOIP) {
                    abtput();
               }
               break;
          case GETFNSH:
               if ((usrptr->flags&(INJOIP+ABOIP)) != INJOIP) {
                    filcls();
                    btuclo(usrnum);
                    ephclos();
                    switch (ftpptr->lnkstt) {
                    case LSTWAIT:
                    case MRCWAIT:
                    case MGNWAIT:
                         usrptr->substt=ARCWAIT;
                         break;
                    default:
                         usrptr->substt=RPLWAIT;
                         break;
                    }
                    ftpptr->flags|=FCFTRV;
                    if (!(usrptr->flags&ABOIP)) {
                         prf("\r");
                    }
               }
               break;
          case SHORTHLP:
          case LONGHLP:
               cncall();
               if ((usrptr->flags&(INJOIP+ABOIP)) == INJOIP) {
                    prf("\r");
               }
               else {
                    pmtnow();
               }
               break;
          case GETALR:
          case GETBNG:
          case GETRSV:
               if (margc == 0) {
                    if (usrptr->flags&INJOIP) {
                         prfmsg(usrptr->substt,ftpptr->lclnam,ftpptr->sndbuf);
                    }
                    else {
                         switch(ftpptr->lnkstt) {
                         case MGTWAIT:
                              nxtfile();
                              break;
                         case GRCWAIT:
                              ret2ftp();
                              break;
                         }
                    }
               }
               else {
                    retr(cncwrd(),ftpptr->lnkstt);
               }
               break;
          case MGPROMPT:
               switch(cncyesno()) {
               case 'A':
                    sesflags[usrnum]|=UFLPSKP;
               case 'Y':
                    prtprep(MGGWAIT,rcvwin);
                    break;
               default:
                    nxtfile();
                    break;
               }
               break;
          case LOOKING:
          case CONNING:
               cncall();
               if ((usrptr->flags&(INJOIP+ABOIP)) == INJOIP) {
                    break;
               }
               dnsabt();
          case FTPEXIT:
               rstrxf();
               ephclos();
               abtcon();
               cncall();
               echon();
               rc=FALSE;
               break;
          }
     } while (!endcnc());
     outprf(usrnum);
     if (!rc) {
          sesflags[usrnum]&=~UFLANON;
     }
     return(rc);
}

static VOID
hdlcmd(VOID)                       /* handle FTP client command            */
{                                  /* assumes parsin():  margc, margv      */
     CHAR *cp;

     if (margc == 0) {
          if ((usrptr->flags&(INJOIP+ABOIP)) == INJOIP) {
               prf("\r");
               ret2ftp();
          }
          else {
               pmtnow();
          }
          return;
     }
     switch (ftpcdi(margv[0])) {
     case QUEST:
          hlpout(SHORTHLP,QUEST);
          break;
     case ACCOUNT:
          passcmd("ACCT");
          break;
     case ASCII:
          sndcmd("TYPE","A",RPLWAIT);
          ftpptr->flags&=~FCFIMG;
          break;
     case BINARY:
     case IMAGE:
          sndcmd("TYPE","I",RPLWAIT);
          ftpptr->flags|=FCFIMG;
          break;
     case CD:
          passcmd("CWD");
          break;
     case CDUP:
          sndcmd("CDUP","",RPLWAIT);
          break;
     case DELETE1:
          passcmd("DELE");
          break;
     case LS:
          if (margc < 2 || !sameas(margv[1],"-l")) {
               savpar();
               ascprep(LSWAIT,rwdisp);
               break;
          }
          strcpy(input,margc > 2 ? spr("dir %s",margv[2]) : "dir");
          parsin();
     case DIR:
          savpar();
          ascprep(DIRWAIT,rwdisp);
          break;
     case GET:
          switch (margc) {
          default:
               prfmsg(GETBAD);
               ret2ftp();
               break;
          case 4:
               if (sameas(margv[2],"|") && sameas(margv[3],"more")) {
                    margc=2;
                    savpar();
                    ascprep(MORWAIT,rwdisp);
                    numchk();
               }
               else {
                    prfmsg(GETBAD);
                    ret2ftp();
               }
               break;
          case 3:
               if (sameas(margv[2],"|more")) {
                    margc=2;
                    savpar();
                    ascprep(MORWAIT,rwdisp);
                    numchk();
               }
               else {
                    savpar();
                    prtprep(GETWAIT,rcvwin);
                    numchk();
               }
               break;
          case 2:
               savpar();
               prtprep(GETWAIT,rcvwin);
               numchk();
               break;
          }
          if (usrptr->substt == GETWAIT) {
               prfmsg(GETWARN,commas(l2as(ftpptr->room=chdroom())));
               prfmsg(XFRABHOW);
               ftpptr->nbytes=0L;
               ftpptr->rbytes=0L;
          }
          break;
     case HELP:
          hlpout(LONGHLP,HQUEST);
          break;
     case MGET1:
          if (margc < 2) {
               prfmsg(MGETBAD);
               ret2ftp();
          }
          else {
               savpar();
               if (ascprep(MGPWAIT,rcvwin)) {
                    prfmsg(MGETWARN,commas(l2as(ftpptr->room=chdroom())));
                    prfmsg(XFRABHOW);
                    btuxnf(usrnum,0,19);
                    sesflags[usrnum]&=~UFLPSKP;
               }
          }
          break;
     case MKDIR1:
          passcmd("MKD");
          break;
     case MODE:
          if (margc != 2 || !sameas(margv[1],"S")) {
               prfmsg(MODBAD);
               ret2ftp();
          }
          else {
               sndcmd("MODE","S",RPLWAIT);
          }
          break;
     case MORE:
          if (margc == 2) {
               savpar();
               ascprep(MORWAIT,rwdisp);
               numchk();
          }
          else {
               prfmsg(MORBAD);
               ret2ftp();
          }
          break;
     case PROMPT:
          cmdonoff(UFLPRMT,PMTON,PMTOFF);
          ret2ftp();
          break;
     case PUT:
          ftpptr->flags&=~FCFUGT;
          cp="";
          switch (margc) {
          case 3:
               if (!valupc(cp=margv[2])) {
                    prfmsg(PUTBAD);
                    ret2ftp();
                    break;
               }
          case 2:
               fileparts(GCPART_FNAM,margv[1],ftpptr->lclnam,FNAMSIZ);
               if (okfput(ftpptr->lclnam)) {
                    strcpy(ftpptr->cmdpar,margv[1]);
                    fileup(margv[1],cp,fupftp);
                    ftpptr->flags|=FCFUIP;
                    ftpptr->flags&=~FCFUAB;
                    ftpptr->nbytes=0L;
                    ftpptr->rbytes=0L;
                    numchk();
               }
               else {
                    prfmsg(PUTBNG,margv[1]);
                    ret2ftp();
               }
               break;
          default:
               prfmsg(PUTBAD);
               ret2ftp();
               break;
          }
          break;
     case PWD:
          sndcmd("PWD","",RPLWAIT);
          break;
     case BYE:
     case EXIT:
     case QUITIT:
          sndcmd("QUIT","",QITWAIT);
          break;
     case QUOTE:
          if (margc <= 1) {
               prfmsg(HQUOTE);
               ret2ftp();
          }
          else {
               rstrin();
               sndcmd(margv[1],"",RPLWAIT);
          }
          break;
     case RENAME:
          if (margc != 3) {
               prfmsg(RENBAD);
               ret2ftp();
          }
          else {
               stzcpy(ftpptr->cmdpar,margv[2],FTPPSIZ);
               sndcmd("RNFR",margv[1],RNFWAIT);
          }
          break;
     case RHELP:
          passcmd("HELP");
          break;
     case RMDIR:
          passcmd("RMD");
          break;
     case RSTAT:
          passcmd("STAT");
          break;
     case STAT:
          prfmsg(LCLSTAT,ftpptr->flags&FCFIMG ? "BINARY/IMAGE" : "ASCII");
          ret2ftp();
          break;
     case SYSTEM:
          passcmd("SYST");
          break;
     case TYPE:
          passcmd("TYPE");
          break;
     case USER:
          if (margc <= 1) {
               prfmsg(usrptr->substt=ASKUID);
          }
          else {
               rstrin();
               sndcmd("USER",margv[1],UIDWAIT);
          }
          break;
     case VERBOSE:
          cmdonoff(UFLVBOS,VBSON,VBSOFF);
          ret2ftp();
          break;
     case VERSION:
          prfmsg(SHOVSN);
          ret2ftp();
          break;
     case NONE:
          cmdunk(margv[0]);
          ret2ftp();
          break;
     }
}

static INT
ftpcdi(                            /* FTP command index                    */
CHAR *cmd)                         /* command name                         */
{                                  /* returns command index (see above)    */
     INT i;
     INT rc;

     nmatch=0;
     for (i=0 ; i < NCMDS ; i++) {
          if (sameto(cmd,ftpcmds[i])) {
               rc=i+QUEST;
               if (sameas(cmd,ftpcmds[i])) {
                    nmatch=1;
                    break;
               }
               else {
                    nmatch++;
               }
          }
     }
     if (nmatch == 1) {
          return(rc);
     }
     return(NONE);
}

static INT
cmdonoff(                          /* process on/off/toggle command        */
INT uflmsk,                        /* mask of sesflags[] array             */
INT onmsg,                         /* message when option ends up on       */
INT offmsg)                        /* message when option ends up off      */
{                                  /* returns 1=on, 0=off                  */
     INT fnow;

     fnow=sesflags[usrnum]&uflmsk;
     if (margc == 1) {
          fnow=!fnow;
     }
     else if (margc == 2) {
          if (sameas(margv[1],"on")) {
               fnow=1;
          }
          else if (sameas(margv[1],"off")) {
               fnow=0;
          }
          else {
               switch (lingyn(margv[1][0])) {
               case 'Y':
                    fnow=1;
                    break;
               case 'N':
                    fnow=0;
                    break;
               }
          }
     }
     if (fnow) {
          sesflags[usrnum]|=uflmsk;
          prfmsg(onmsg);
     }
     else {
          sesflags[usrnum]&=~uflmsk;
          prfmsg(offmsg);
     }
     return(fnow);
}

static INT
fupftp(                            /* file upload for FTP                  */
INT fupcod)                        /* upload handler function codes (FUP.H)*/
{
     INT rc=0;

     setmbk(ftpmb);
     switch (fupcod) {
     case FUPBEG:
          strcpy(ftfbuf,ftppath(ftpptr->lclnam));
          ftfscb->maxbyt=chdroom();
          rc=1;
          break;
     case FUPEND:
          strcpy(ftfbuf,ftppath(ftpptr->lclnam));
          ftpptr->flags|=FCFUGT;
          break;
     case FUPSKP:
          unlink(ftppath(ftpptr->lclnam));
          break;
     case FUPFIN:
          usrptr->state=ftpstt;
          ftpptr->flags&=~FCFUIP;
          if (ftpptr->flags&FCFUAB) {
               if (ftpptr->errnum >= 0) {         /* recv() error          */
                    abtcon();
                    prfmsg(ftpptr->errmsg,ftpptr->errnum,
                                tcpErrStg(ftpptr->errnum));
                    outprf(usrnum);
                    clrprf();
                    endcon();
               }
               else {                             /* server reply 400-599  */
                    prfmsg(ftpptr->errmsg,ftpptr->sndbuf);
                    ret2ftp();
               }
          }
          else if (ftpptr->flags&FCFUGT) {
               if (prtprep(PUTWAIT,rcvwin)) {
                    prfmsg(XFRABHOW);
               }
          }
          else {
               ret2ftp();
          }
          rc=1;
          break;
     }
     return(rc);
}

static VOID
numchk(VOID)                       /* check how many FTP transfers in prog */
{
     if (usrptr->substt != FTPPMT) {
          if (numftp() >= maxfer) {
               prfmsg(XFRMANY,maxfer);
               ret2ftp();
          }
          else {
               ftpptr->flags|=FCFXFR;
          }
     }
}

static INT
numftp(VOID)                       /* users engaged in FTP transfers       */
{
     INT unum;
     INT num=0;
     struct user *u;

     for (unum=0 ; unum < nterms ; unum++) {
          u=usroff(unum);
          if (u->usrcls == ACTUSR
           && u->state == ftpstt
           && (ftpupt(unum)->flags&FCFXFR)) {
               num++;
          }
     }
     return(num);
}

static VOID
hlpout(                            /* process HELP and ? client commands   */
INT hdrmsg,                        /* header message, eg SHORTHLP, LONGHLP */
INT fstmsg)                        /* first help message, eg QUEST, HQUEST */
{
     INT idx;

     if (margc <= 1) {
          prfmsg(usrptr->substt=hdrmsg);
          ftpptr->hlpctr=0;
          cycleme();
     }
     else {
          if ((idx=ftpcdi(margv[1])) != NONE) {
               prfmsg(fstmsg-QUEST+idx);
               prf("\r");
               if (hdrmsg == SHORTHLP) {
                    prf("\r");
               }
          }
          else {
               cmdunk(margv[1]);
          }
          ret2ftp();
     }
}

static VOID
savpar(VOID)                       /* save command parameters in cmdpar    */
{
     if (margc > 1) {
          rstrin();
          stzcpy(ftpptr->cmdpar,margv[1],FTPPSIZ);
     }
     else {
          ftpptr->cmdpar[0]='\0';
     }
}

static INT
prtprep(                           /* PORT preparations                    */
INT nxtstp,                        /* substate if you can send PORT cmd    */
INT rwin)                          /* desired receive window               */
{
     ftpptr->drcwin=rwin;
     if (ephport()) {
          ftpptr->flags|=FCFTRV;
          return(sndcmd("PORT",encanp(ipaddrb,ftpptr->datprt),nxtstp));
     }
     else {
          ret2ftp();
          return(0);
     }
}

static INT
ascprep(                           /* process that req an ASCII transfer   */
INT nxtstp,                        /* next step after ASCII mode, if any   */
INT rwin)                          /* desired receive window               */
{
     ftpptr->drcwin=rwin;
     if (ftpptr->flags&FCFIMG) {
          ftpptr->lnkstt=nxtstp;
          ftpptr->flags|=FCFTRV;
          return(sndcmd("TYPE","A",TPAWAIT));
     }
     else {
          return(prtprep(nxtstp,rwin));
     }
}

static INT
ascrcov(                           /* recover from temporary ASCII mode    */
INT nxtstt)                        /* state after "TYPE I"                 */
{                                  /* returns 1=recovering 0=don't need to */
     if (ftpptr->flags&FCFIMG) {
          ftpptr->flags|=FCFTRV;
          sndcmd("TYPE","I",nxtstt);
          return(1);
     }
     return(0);
}

static VOID
abtget(                            /* abort a get or list                  */
INT arcv)                          /* 1=may need TYPE I recovery, 0=don't  */
{
     filcls();
     if (recvbw(ftpptr->socket) == 0L) {
          ftpptr->byttfc+=send(ftpptr->socket,abosq1,sizeof(abosq1),MSG_OOB);
          ftpptr->byttfc+=send(ftpptr->socket,abosq2,sizeof(abosq2),0);
     }
     btuclo(usrnum);
     if (!sndcmd("ABOR","",arcv ? ABAWAIT : ABTWAIT)) {
          ephclos();
     }
     else if (ftpptr->datsok != -1) {
          setrcvwin(ftpptr->datsok,ftpptr->drcwin=rcvwin);
          send(ftpptr->datsok,"",0,0);  /* force recv window advertisement */
     }
     ftpptr->flags|=FCFTRV;
     if (!(usrptr->flags&ABOIP)) {
          prf("\r");
     }
}

static VOID
abtput(VOID)                       /* abort a put                          */
{
     filcls();
     ftpptr->byttfc+=send(ftpptr->socket,abosq1,sizeof(abosq1),MSG_OOB);
     ftpptr->byttfc+=send(ftpptr->socket,abosq2,sizeof(abosq2),0);
     sndcmd("ABOR","",ABTWAIT);
     ephclos();
     ftpptr->flags|=FCFTRV;
     if (!(usrptr->flags&ABOIP)) {
          prf("\r");
     }
}

static VOID
cmdunk(
CHAR *cmd)
{
     INT i;

     if (nmatch == 0) {
          prfmsg(CMDUNK,cmd);
     }
     else {
          prfmsg(CMDNUQ,cmd);
          for (i=0 ; i < NCMDS ; i++) {
               if (sameto(cmd,ftpcmds[i])) {
                    prfmsg(LSTNUQ,ftpcmds[i]);
               }
          }
          prfmsg(PSTNUQ);
     }
}

static VOID
pmtnow(VOID)                       /* show "ftp>" prompt now (abort output */
{                                  /* if you have to)                      */
     if (btuoba(usrnum) != OUTSIZ-1) {
          btuclo(usrnum);
          prf("\r");
     }
     ret2ftp();
}

static VOID
ftpcbk(                            /* FTP address lookup callback vector   */
struct dns *dnsptr)
{
     INT goahead;

     setmbk(ftpmb);
     usrptr->substt=CONNING;
     if (dnsptr->status >= 0) {
          prfmsg(CONNING);
          goahead=ftpcall();
     }
     else {
          prfmsg(LUPERR,dnsemg);
          goahead=0;
     }
     outprf(usrnum);
     clrprf();
     if (!goahead) {
          usrptr->substt=FTPEXIT;
          btuinj(usrnum,CRSTG);
     }
}

static INT
ftpcall(VOID)                      /* try to connect to FTP server         */
                                   /* returns 1=working 0=problem reported */
                                   /* ftpptr->dns.inaddr[0], ftpptr->port, */
{                                  /* implicit inputs, expects curusr() too*/
     INT rc;

     switch (rc=tcpdial(ftpptr->dns.inaddr[0],ftpptr->port,0,
                        &ftpptr->socket)) {
     case DLERRS:
          prfmsg(SOCKERR,tcpip_errno,tcpErrStg(tcpip_errno));
          break;
     case DLERRI:
          prfmsg(IOCLERR,tcpip_errno,tcpErrStg(tcpip_errno));
          break;
     default:
          prfmsg(CONNERR,rc,tcpip_errno,tcpErrStg(tcpip_errno));
          break;
     case DLCNOW:
     case DLCING:
          sktnfy(TNFCONN,ftpptr->socket,bgncon,ftpptr,usrnum);
          ftpptr->sttime=(USHORT)(hrtval()>>12);
          cycleme();
          ftpptr->tckonl=lngtck;
          ftpptr->byttfc=0L;
          return(1);
     }
     ftpptr->socket=-1;
     return(0);
}

static VOID
bgncon(                            /* Connection has begun                 */
struct ftpusr *fpu)                /* FTP client info structure (vda)      */
{
     CHAR onechar;

     fpu->tckonl=lngtck;
     fpu->byttfc=0L;
     if (recv(fpu->socket,&onechar,0,0) < 0 && tcpip_errno != EWOULDBLOCK) {
          abtcon();
          setmbk(ftpmb);
          prfmsg(IMMERR,fpu->dns.name,tcpErrStg(tcpip_errno));
          outprf(usrnum);
          clrprf();
          endcon();
          return;
     }
     if (audcon) {
          shocst("FTP CLIENT CONNECT","to %s",fpu->dns.name);
     }
     oobinline(fpu->socket);
     setmbk(ftpmb);
     prfmsg(CONNED);
     usrptr->substt=CONWAIT;
     outprf(usrnum);
     rstmbk();
     sktcnc(TNFCONN,fpu->socket);
     sktnfy(TNFRECV,fpu->socket,ftprcv,fpu,usrnum);
     fpu->flags|=FCFCMD;
     ftpptr->rplcnt=0;
     btuech(usrnum,0);
     tnoscb=&fpu->tnoscb;
     tnoini(0,ftpops);
}

static VOID
abtcon(VOID)                       /* Abort connection to the server       */
{
     if (ftpptr->socket != -1) {
          tfcchg(ftpptr->byttfc,ftpkchg);
          if (auddsc) {
               shocst("FTP CLIENT DISCONNECT",
                      "from %s, %s seconds, %s bytes traffic",
                      inet_ntoa(ftpptr->dns.inaddr[0]),
                      ul2as(lngtck-ftpptr->tckonl),
                      ul2as(ftpptr->byttfc));
          }
          clsskt(ftpptr->socket);
          ftpptr->flags&=~FCFCMD;
          ftpptr->socket=-1;
     }
     usrptr->substt=FTPEXIT;
}

static VOID
endcon(VOID)                       /* End of connection                    */
{
     usrptr->substt=FTPEXIT;
     btuinj(usrnum,CRSTG);
}

static VOID
ftpsts(VOID)                       /* FTP client status handler            */
{
     INT more=1;
     USHORT stnow;
     INT oba;
     INT obachk=0;                 /* 1=only btuoba changes imply activity */

     if (status != CYCLE) {
          dfsthn();
          return;
     }
     setmbk(ftpmb);
     stnow=(USHORT)(hrtval()>>12);
     oba=btuoba(usrnum);
     switch (usrptr->substt) {
     case CONNING:
          if (stnow-ftpptr->sttime > contwt && contwt >= 0) {
               abtcon();
               prfmsg(CONTMO,contwt>>4);
               outprf(usrnum);
               clrprf();
               endcon();
               more=0;
          }
          break;
     case SHORTHLP:
          if (oba > 1000) {
               if (ftpptr->hlpctr < NCMDS) {
                    prfmsg(QUEST+ftpptr->hlpctr++);
               }
               else {
                    prfmsg(SHORTEND);
                    ret2ftp();
                    more=0;
               }
               outprf(usrnum);
          }
          else {
               obachk=1;
          }
          break;
     case LONGHLP:
          if (oba > 2000) {
               if (ftpptr->hlpctr < NCMDS) {
                    prfmsg(HQUEST+ftpptr->hlpctr++);
               }
               else {
                    prfmsg(LONGEND);
                    ret2ftp();
                    more=0;
               }
               outprf(usrnum);
          }
          else {
               obachk=1;
          }
          break;
     case MRCWAIT:
     case LSTWAIT:
          if (ftpptr->datsok != -1) {
               rwnadj(ftpptr->datsok,outsiz12-1,oba-outsiz12,outsiz14,
                     &ftpptr->drcwin);
          }
          obachk=1;
          break;
     case GETFNSH:
          if (oba == OUTSIZ-1) {
               usrptr->substt=ftpptr->lnkstt;
               ephclos();
               ftpptr->flags|=FCFTRV;
               switch (usrptr->substt) {
               case GRCWAIT:
               case MGTWAIT:
                    fclose(ftpptr->fp);
                    ftpptr->fp=NULL;
                    usrptr->substt=GETOVER;
                    prfmsg(GETFIN,commas(l2as(ftpptr->nbytes)));
                    outprf(usrnum);
                    break;
               }
               hdllins();
               more=0;
          }
          obachk=1;
          break;
     default:
          more=0;
          break;
     }
     if (more) {
          if (obachk && ftpptr->lstoba == oba) {
               actdet=0;
          }
          else {
               ftpptr->lstoba=oba;
          }
          btuinj(usrnum,CYCLE);
     }
     else {
          ftpptr->flags&=~FCFCYC;
     }
}

static VOID
ftphup(VOID)
{
     struct ffblk fb;

     if (usrptr->state == ftpstt) {
          ephclos();
          abtcon();
     }
     if (fnd1st(&fb,ftppath(STAR),0)) {
          do {
               unlink(ftppath(fb.ff_name));
          } while (fndnxt(&fb));
     }
     sesflags[usrnum]=0;
}

static VOID
ftpsnd(                            /* try to send data in buffer to server */
struct ftpusr *fpu)                /* FTP client session info              */
{
     if (tnfclosed) {
          abtcon();
          setmbk(ftpmb);
          prfmsg(SNDERR,ESHUTDOWN,tcpErrStg(ESHUTDOWN));
          outprf(usrnum);
          clrprf();
          endcon();
          return;
     }
     switch (sndmgr(fpu->cmdlin,&fpu->cmdcnt,fpu->socket)) {
     case -1:
          abtcon();
          setmbk(ftpmb);
          prfmsg(SNDERR,tcpip_errno,tcpErrStg(tcpip_errno));
          outprf(usrnum);
          clrprf();
          endcon();
          break;
     case 0:
          sktcnc(TNFSEND,fpu->socket);
          break;
     case 1:
          sktnfy(TNFSEND,fpu->socket,ftpsnd,fpu,usrnum);
          break;
     }
     fpu->byttfc+=sndact;
}

/*--- Command stream receiver routines, including process sequencing ---*/

static VOID
ftprcv(                            /* FTP client receive-line-from-server  */
struct ftpusr *fpu)                /* dummy pointer (usrnum is enough)     */
{
     INT nroom,nactual;
     CHAR *radr;

     setmbk(ftpmb);
     ASSERT(fpu->rplcnt <= FTPRSIZ);
     if ((nroom=FTPRSIZ-1-fpu->rplcnt) <= 0) {  /* ck: long line in buf? */
          fpu->rpllin[FTPRSIZ-1]='\0';
          hdllin(fpu->rpllin);
          fpu->rplcnt=0;
          nroom=FTPRSIZ-1;
     }                                  /* control connection not throttled */
     if ((nactual=recv(fpu->socket,radr=
                       fpu->rpllin+fpu->rplcnt,nroom,0)) <= 0) {
          endcrcv(fpu,(nactual == 0) ? SVREND
                                     : (tcpip_errno == ECONNRESET ? SVRDISC
                                                                  : RCVERR));
     }
     else {
          ASSERT(nactual <= nroom);
          fpu->byttfc+=nactual;
          setmbk(ftpmb);
          tnoscb=&fpu->tnoscb;
          tntunm=usrnum;
          fpu->rplcnt+=iacdec(radr,nactual);
          fpu->rplcnt=memstp(fpu->rpllin,fpu->rplcnt,'\0');
          hdllins();
     }
}

static VOID
endcrcv(                           /* end control connection while recving */
struct ftpusr *fpu,
INT errmsg)
{
     CHAR *cp;

     if (fpu->flags&FCFUIP) {
          abtcon();
          setftu();
          ftuptr->flags|=FUPAFN;
          ftfabt("FTP server error");
          fpu->flags|=FCFUAB;
          fpu->errmsg=errmsg;
          fpu->errnum=tcpip_errno;
     }
     else {
          if (fpu->rplcnt > 0) {
               fpu->rpllin[fpu->rplcnt]='\0';
               for (cp=fpu->rpllin ; *cp == '\n' ; cp++) {
               }
               if (*cp != '\0') {
                    prfmsg(RPLLINL,cp);
               }
          }
          abtcon();
          setmbk(ftpmb);
          prfmsg(errmsg,tcpip_errno,tcpErrStg(tcpip_errno));
          outprf(usrnum);
          clrprf();
          rstmbk();
          endcon();
     }
}

static VOID
hdllins(VOID)                      /* handle waiting reply lines fm server */
{
     CHAR *cp;

     while (ftpptr->rplcnt > 0
         && (cp=(CHAR *)memchr(ftpptr->rpllin,'\r',ftpptr->rplcnt)) != NULL
         && ftpptr->flags&FCFCMD) {
          *cp='\0';
          hdllin(ftpptr->rpllin);
          if ((ftpptr->rplcnt-=(INT)(cp+1-ftpptr->rpllin)) > 0) {
               movmem(cp+1,ftpptr->rpllin,ftpptr->rplcnt);
          }
     }
}

static VOID
hdllin(                            /* handle a reply line from the server  */
CHAR *reply)                       /* reply line, NUL-term, CR-stripped    */
{
     CHAR a,b,c;
     INT code;

     if (*reply == '\0') {
          return;
     }
     if (*reply == '\n') {
          reply++;
     }
     if (ftpptr->flags&FCFUIP) {
          if (*reply > '2') {
               setftu();
               ftuptr->flags|=FUPAFN;
               ftfabt("FTP server interruption");
               stzcpy(ftpptr->sndbuf,reply,80);
               ftpptr->flags|=FCFUAB;
               ftpptr->errmsg=SVRIRP;
               ftpptr->errnum=-1;
          }
          return;
     }
     clrprf();
     if (usrptr->substt > ALLWAITS) {
          prfmsg(RPLLINU,reply);
          echon();
          ret2ftp();
     }
     else {
          if (!(ftpptr->flags&FCFTRV)
           || (sesflags[usrnum]&UFLVBOS)
           || reply[0] > '2') {
               prfmsg(reply[0] == '4'
                   || reply[0] == '5' ? RPLLINE : RPLLIN,reply);
          }
          ftpptr->flags&=~FCFTRV;
          if (strlen(reply) >= 4
           && reply[3] == ' '
           && isdigit(a=reply[0])
           && isdigit(b=reply[1])
           && isdigit(c=reply[2])) {
               code=a*100+b*10+c-('0'*100+'0'*10+'0');
               hdlrpl(a,code);
          }
     }
     outprf(usrnum);
     clrprf();
}

static VOID
hdlrpl(                            /* handle reply from server             */
CHAR a,                            /* first digit, '1' through '5'         */
INT code)                          /* 3-digit code, 110-55x                */
{                                  /* (outprf called by caller)            */
     CHAR *cp;

     echon();
     switch (usrptr->substt) {
     case CONWAIT:
          if (sesflags[usrnum]&UFLANON) {
               sesflags[usrnum]&=~UFLANON;
               prfmsg(ASKUID);
               prf("anonymous\r");
               sndcmd("USER","anonymous",AUDWAIT);
          }
          else {
               prfmsg(usrptr->substt=ASKUID);
          }
          break;
     case UIDWAIT:
          switch (a) {
          case '3':
               prfmsg(usrptr->substt=ASKPSW);
               echsec(secchr,128);
               usrptr->flags|=MONHID;
               break;
          default:
               ret2ftp();
               break;
          }
          break;
     case AUDWAIT:
          switch (a) {
          case '3':
               autoals();
               break;
          default:
               ret2ftp();
               break;
          }
          break;
     case PSWWAIT:
          switch (a) {
          case '3':
               prfmsg(usrptr->substt=ASKACT);
               break;
          default:
               ret2ftp();
               break;
          }
          break;
     case QITWAIT:
          break;
     case ARCWAIT:
          if (!ascrcov(RPLWAIT)) {
               ret2ftp();
          }
          break;
     case RPLWAIT:
          ret2ftp();
          break;
     case TPAWAIT:
          prtprep(ftpptr->lnkstt,ftpptr->drcwin);
          break;
     case LSWAIT:
          if (a == '2' && ephlstn()) {
               if (sndcmd("NLST",ftpptr->cmdpar,LSTWAIT)) {
                    ftpptr->flags|=FCFTRV;
                    cycleme();
               }
          }
          else {
               if (!ascrcov(RPLWAIT)) {
                    ret2ftp();
               }
          }
          break;
     case DIRWAIT:
          if (a == '2' && ephlstn()) {
               if (sndcmd("LIST",ftpptr->cmdpar,LSTWAIT)) {
                    ftpptr->flags|=FCFTRV;
                    cycleme();
               }
          }
          else {
               if (!ascrcov(RPLWAIT)) {
                    ret2ftp();
               }
          }
          break;
     case MGPWAIT:
          if (a == '2' && ephlstn()) {
               ftpptr->sbfcnt=0;
               if (sndcmd("NLST",firstwd(ftpptr->cmdpar),MGNWAIT)) {
                    ftpptr->flags|=FCFTRV;
               }
          }
          else {
               ret2ftp();
          }
          break;
     case MGNWAIT:
          switch (a) {
          case '1':
               if (ftpptr->lsnsok != -1) {
                    sw2dat(datlsn);
                    break;
               }
          case '2':
               if (!ascrcov(MGIWAIT)) {
                    fstfile();
               }
               break;
          default:
               if (code == 550 || code == 450) {
                    prfmsg(NLSTNOT,firstwd(ftpptr->cmdpar));
                    ftpptr->sbfcnt=0;
                    if (!ascrcov(MGIWAIT)) {
                         fstfile();
                    }
               }
               else if (!ascrcov(RPLWAIT)) {
                    ret2ftp();
               }
               break;
          }
          break;
     case MGIWAIT:
          if (a == '2') {
               fstfile();
          }
          else {
               ret2ftp();
          }
          break;
     case MGGWAIT:
          if (a == '2' && ephlstn()) {
               retr(mkdosn(ftpptr->sndbuf),MGTWAIT);
          }
          else {
               ret2ftp();
          }
          break;
     case MGTWAIT:
          switch (a) {
          case '1':
               if (ftpptr->lsnsok != -1) {
                    sw2dat(datlsn);
                    break;
               }
          default:
               filcls();
               nxtfile();
               break;
          }
          break;
     case GETWAIT:
          if (a == '2' && ephlstn()) {
               strcpy(ftpptr->sndbuf,strtok(ftpptr->cmdpar," "));
               if ((cp=strtok(NULL," ")) == NULL) {    /* sndbuf=svr path  */
                    cp=ftpptr->sndbuf;                 /* cp=local name    */
               }
               retr(cp,GRCWAIT);
          }
          else {
               ret2ftp();
          }
          break;
     case MORWAIT:
          if (a == '2' && ephlstn()) {
               if (sndcmd("RETR",ftpptr->cmdpar,MRCWAIT)) {
                    ftpptr->flags|=FCFTRV;
                    cycleme();
               }
          }
          else {
               if (!ascrcov(RPLWAIT)) {
                    ret2ftp();
               }
          }
          break;
     case LSTWAIT:
     case MRCWAIT:
          switch (a) {
          case '1':
               if (ftpptr->lsnsok != -1) {
                    sw2dat(datlsn);
                    prfmsg(ASCHDR);
                    break;
               }
          default:
               if (!ascrcov(RPLWAIT)) {
                    ret2ftp();
               }
               break;
          }
          break;
     case GRCWAIT:
          switch (a) {
          case '1':
               if (ftpptr->lsnsok != -1) {
                    sw2dat(datlsn);
                    break;
               }
          default:
               ret2ftp();
               break;
          }
          break;
     case GETOVER:
          if (a == '2') {
               if (ftgnew() == 0) {
                    ftgsbm("");
                    ret2ftp();
               }
               else {
                    if (audget) {
                         shocst("FTP CLIENT FILE \"GET\"",
                                "FTP file %s from %s",
                                ftpptr->lclnam,ftpptr->dns.name);
                    }
                    fnmcse(strcpy(ftgptr->tagspc,ftpptr->lclnam));
                    ftgptr->tshndl=tshftp;
                    ftgptr->flags=FTGABL;
                    ftgsbm("TQ");
                    switch (ftpptr->lnkstt) {
                    case GRCWAIT:
                         howdnl();
                         prfmsg(DROOM);
                         usrptr->substt=FTPPMT; /* (to save file fm filcls) */
                         ret2ftp();
                         break;
                    case MGTWAIT:
                         usrptr->substt=FTPPMT; /* (to save file fm filcls) */
                         nxtfile();
                         break;
                    }
               }
          }
          else {
               prf("\r");
               ret2ftp();
          }
          break;
     case PUTWAIT:
          if (a == '2' && ephlstn() && opnput()) {
               ftpptr->sbfcnt=0;
               if (sndcmd("STOR",ftpptr->cmdpar,PSTWAIT)) {
                    ftpptr->flags|=FCFTRV;
               }
          }
          else {
               ret2ftp();
          }
          break;
     case PSTWAIT:
          switch (a) {
          case '1':
               if (ftpptr->lsnsok != -1) {
                    sw2dat(putlsn);
                    break;
               }
          default:
               ret2ftp();
               break;
          }
          break;
     case PUTOVER:
          if (a == '2') {
               if (audput) {
                    shocst("FTP CLIENT FILE \"PUT\"",
                           "FTP file %s to %s",
                           ftpptr->lclnam,ftpptr->dns.name);
               }
          }
          ret2ftp();
          break;
     case RNFWAIT:
          if (a == '3') {
               sndcmd("RNTO",ftpptr->cmdpar,RPLWAIT);
          }
          else {
               ret2ftp();
          }
          break;
     case ABTWAIT:
          btuech(usrnum,0);
          ftpptr->flags|=FCFTRV;
          usrptr->substt=RPLWAIT;
          break;
     case ABAWAIT:
          btuech(usrnum,0);
          ftpptr->flags|=FCFTRV;
          usrptr->substt=ARCWAIT;
          break;
     }
}

static VOID
howdnl(VOID)                       /* tell how to download once/session    */
{
     if (!(sesflags[usrnum]&UFLTAGI)) {
          sesflags[usrnum]|=UFLTAGI;
          prfmsg(HOWDNL);
     }
}

static VOID
fstfile(VOID)                      /* get first file in NLIST, if any      */
{                                  /* (replaces '\n' with '\0' throughout) */
     CHAR *cp;
     INT i;

     ftpptr->sndbuf[ftpptr->sbfcnt]='\0';
     if ((cp=strrchr(ftpptr->sndbuf,'\n')) == NULL) {
          if (ftpptr->sbfcnt > 0) {
               prfmsg(MGNLERR,ftpptr->sndbuf);
          }
          nxtspec();
     }
     else if (!mgtout()) {
          ftpptr->sbfcnt=(INT)(cp-ftpptr->sndbuf)+1;
          for (i=0,cp=ftpptr->sndbuf ; i < ftpptr->sbfcnt ; i++,cp++) {
               if (*cp == '\n') {
                    *cp='\0';
               }
          }
          mgprompt();
     }
}

static VOID
nxtfile(VOID)                      /* advance to next file in mget spec(s) */
{
     if (!mgtout()) {
          if (nlsnxt()) {
               mgprompt();
          }
          else {
               nxtspec();
          }
     }
}

static VOID
nxtspec(VOID)                      /* advance to next mget file spec       */
{
     CHAR *cp;

     cp=skpwht(skpwrd(ftpptr->cmdpar));
     movmem(cp,ftpptr->cmdpar,strlen(cp)+1);
     if (ftpptr->cmdpar[0] != '\0') {
          ascprep(MGPWAIT,rcvwin);
     }
     else {
          howdnl();
          prfmsg(DROOM);
          ret2ftp();
     }
}

static INT
nlsnxt(VOID)                       /* is there another file in NLIST?      */
{                                  /* (shifts it to beginning of sndbuf)   */
     INT n;

     if (ftpptr->sbfcnt > 0) {
          n=strlen(ftpptr->sndbuf);
          if (ftpptr->sbfcnt > n+1) {
               ftpptr->sbfcnt-=n+1;
               movmem(ftpptr->sndbuf+n+1,ftpptr->sndbuf,ftpptr->sbfcnt);
               return(1);
          }
          else {
               ftpptr->sbfcnt=0;
          }
     }
     return(0);
}

static VOID
mgprompt(VOID)                     /* prompt or notify of new mgotten file */
{
     ftpptr->nbytes=0L;
     ftpptr->rbytes=0L;
     if ((sesflags[usrnum]&(UFLPRMT+UFLPSKP)) == UFLPRMT) {
          prfmsg(usrptr->substt=MGPROMPT,ftpptr->sndbuf);
     }
     else {
          prfmsg(MGNOTICE,ftpptr->sndbuf);
          prtprep(MGGWAIT,rcvwin);
     }
}

static INT
mgtout(VOID)                       /* has user run out of tags for mget?   */
{
     if (ftgnew() == 0) {
          prfmsg(MGTOUT);
          while (nlsnxt()) {
               prfmsg(MGTLST,ftpptr->sndbuf);
          }
          prfmsg(MGTSPC,skpwht(skpwrd(ftpptr->cmdpar)));
          ret2ftp();
          return(1);
     }
     return(0);
}

static VOID
sw2dat(                            /* command channel off, data channel on */
VOID (*lsnrou)())                  /* new data channel listen routine      */
{                                  /* (command comes on again in ephclos())*/
     btuech(usrnum,0);
     usrptr->flags|=NOINJO;
     sktcnc(TNFRECV,ftpptr->socket);
     ftpptr->flags&=~FCFCMD;
     sktnfy(TNFACCP,ftpptr->lsnsok,lsnrou,ftpptr,usrnum);
}

static INT
tshftp(                            /* tagspec handler for FTP client       */
INT tshcod)                        /* tagspec handler func codes (FTG.H)   */
{
     INT rc=0;
     FILE *fp;

     setmbk(ftpmb);
     switch (tshcod) {
     case TSHDSC:
          sprintf(tshmsg,"FTP file %s",ftgptr->tagspc);
          break;
     case TSHVIS:
          if ((fp=fopen(ftppath(ftgptr->tagspc),FOPRB)) != NULL) {
               fread(tshmsg,1,TSHLEN,fp);
               fclose(fp);
               rc=1;
          }
          break;
     case TSHBEG:
          strcpy(tshmsg,ftppath(ftgptr->tagspc));
          strcpy(ftfscb->fname,ftgptr->tagspc);
          rc=1;
          break;
     case TSHEND:
          if (auddnl) {
               shocst("FTP CLIENT FILE DOWNLOADED","%s",ftgptr->tagspc);
          }
          break;
     case TSHUNT:
          unlink(ftppath(ftgptr->tagspc));
          break;
     case TSHFIN:
          usrptr->state=ftpstt;
          break;
     }
     return(rc);
}

static INT
retr(                              /* Initiate retrieval of a file         */
CHAR *lclreq,                      /* requested local name for file        */
INT nxtstt)                        /* state for handling RETR responses    */
                                   /* remote name implicit: ftpptr->sndbuf */
                                   /* 1=opened & ready, 0=off on tangent   */
{
     INT errmsg;

     fileparts(GCPART_FNAM,lclreq,ftpptr->lclnam,FNAMSIZ);
     if (rsvnam(ftpptr->lclnam)) {
          errmsg=GETRSV;
     }
     else {
          if ((ftpptr->fp=fopen(ftppath(ftpptr->lclnam),FOPRB)) != NULL) {
               fclose(ftpptr->fp);
               ftpptr->fp=NULL;
               errmsg=GETALR;
          }
          else if ((ftpptr->fp=fopen(ftppath(ftpptr->lclnam),FOPWB)) == NULL) {
               errmsg=GETBNG;
          }
          else {
               if (sndcmd("RETR",ftpptr->sndbuf,nxtstt)) {
                    ftpptr->flags|=FCFTRV;
                    return(1);
               }
               return(0);
          }
     }
     prfmsg(usrptr->substt=errmsg,ftpptr->lclnam,ftpptr->sndbuf);
     ftpptr->lnkstt=nxtstt;
     return(0);
}

static VOID
filcls(VOID)                       /* close the data file, if open         */
{
     if (ftpptr->fp != NULL) {
          fclose(ftpptr->fp);
          if (usrptr->substt == GRCWAIT
           || usrptr->substt == MGTWAIT
           || usrptr->substt == PSTWAIT
           || usrptr->substt == GETFNSH && (ftpptr->lnkstt == GRCWAIT ||
                                            ftpptr->lnkstt == MGTWAIT)) {
               unlink(ftppath(ftpptr->lclnam));
          }
          ftpptr->fp=NULL;
     }
     if (usrptr->substt == GETOVER) {
          unlink(ftppath(ftpptr->lclnam));
     }
}

static VOID
datlsn(                            /* called when server opens data conn   */
struct ftpusr *fpu)                /* FTP client context                   */
{
     setmbk(ftpmb);
     if (!ephaccp()) {
          ret2ftp();
          outprf(usrnum);
     }
     else {
          sktcnc(TNFACCP,fpu->datsok);
          sktnfy(TNFRECV,fpu->datsok,datrcv,fpu,usrnum);
     }
}

static VOID
datrcv(                            /* called when data available from svr  */
struct ftpusr *fpu)                /* FTP client context                   */
{
     INT nroom;                    /* room for recv()able data             */
     INT nactual;                  /* bytes actually recv()ed              */

     switch (usrptr->substt) {
     case LSTWAIT:
     case MRCWAIT:
          nroom=btuoba(usrnum)-outsiz14;     /* receive:  max 3/4 buffer   */
          rwnmgr(fpu->datsok,nroom-outsiz14,&fpu->drcwin);
          if (nroom <= 0) {                  /* window:  max 1/2 buffer    */
               actdet=0;
               return;                       /* (room for CR/LF expansion) */
          }
          break;
     default:
          nroom=vdasiz;
          break;
     }
     setmbk(ftpmb);
     if ((nactual=recv(fpu->datsok,vdatmp,nroom,0)) <= 0) {
          if (nactual == 0 || tcpip_errno == ECONNRESET) {
               endrcv(fpu);
          }
          else {
               dskerr("RECEIVE",0);
               switch (usrptr->substt) {
               case GRCWAIT:
                    prf("\r");
                    break;
               }
               ret2ftp();
               outprf(usrnum);
          }
     }
     else {
          fpu->byttfc+=nactual;
          switch(usrptr->substt) {
          case ABTWAIT:
          case ABAWAIT:
               break;
          case GRCWAIT:
          case MGTWAIT:
               if ((fpu->room-=nactual) < 0L) {
                    prfmsg(GETBUST);
                    abtget(0);
                    outprf(usrnum);
               }
               else if (fwrite(vdatmp,1,nactual,fpu->fp) != nactual) {
                    catastro("Disk is full while %s writing %s!",
                             usaptr->userid,fpu->lclnam);
               }
               else {
                    fpu->nbytes+=nactual;
                    if (btuoba(usrnum) == OUTSIZ-1
                     && fpu->rbytes != fpu->nbytes) {
                         prfmsg(GETPROG,commas(l2as(fpu->rbytes=fpu->nbytes)));
                         prf("\r");
                         if (!(usaptr->ansifl&ANSON)) {
                              stpans(prfbuf);
                         }
                         powprf();
                    }
               }
               break;
          case MRCWAIT:
          case LSTWAIT:
               vdatmp[memstp(vdatmp,nactual,'\0')]='\0';
               btuxmt(usrnum,strrpl(strstp(vdatmp,'\r'),'\n','\r'));
               break;
          case MGNWAIT:
               nactual=memstp(vdatmp,memstp(vdatmp,nactual,'\0'),'\r');
               if (FTPSSIZ-1-fpu->sbfcnt < nactual) {
                    nactual=FTPSSIZ-1-fpu->sbfcnt;
               }
               if (nactual > 0) {
                    movmem(vdatmp,fpu->sndbuf+fpu->sbfcnt,nactual);
                    fpu->sbfcnt+=nactual;
               }
               break;
          }
     }
}

static VOID
endrcv(
struct ftpusr *fpu)
{
     sktcnc(TNFRECV,fpu->datsok);
     fpu->lnkstt=usrptr->substt;
     usrptr->substt=GETFNSH;
     cycleme();
}

static VOID
cycleme(VOID)                      /* inject a CYCLE (if not one already)  */
{
     if (!(ftpptr->flags&FCFCYC)) {
          btuinj(usrnum,CYCLE);
          ftpptr->flags|=FCFCYC;
     }
}

static INT
okfput(                            /* is file name ok for uplding & putting*/
CHAR *fname)
{                                  /* 1=ok, 0=reserved or exists already   */
     FILE *fp;

     if (rsvnam(fname)) {
          return(0);
     }
     if ((fp=fopen(ftppath(fname),FOPRB)) != NULL) {
          fclose(fp);
          return(0);
     }
     return(1);
}

static INT
opnput(VOID)                       /* get file ready for local storage     */
{                                  /* returns 1=file rdy, 0=off on tangent */
     CHAR *cp;

     if ((ftpptr->fp=fopen(cp=ftppath(ftpptr->lclnam),FOPRB)) == NULL) {
          unlink(cp);
          prfmsg(PUTFND,ftpptr->lclnam);
          return(0);
     }
     return(1);
}

static CHAR *
ftppath(                           /* path of file in the FTP subdirectory */
CHAR *filespec)                    /* file name, wildcard, or NULL=dir name*/
{
     CHAR buffer[40];

     if (filespec == NULL) {
          return(chdpath(FTPSUB));
     }
     else {
          sprintf(buffer,"%s"SLS"%s",FTPSUB,filespec);
          return(chdpath(buffer));
     }
}

static VOID
putlsn(                            /* called when server opens data conn   */
struct ftpusr *fpu)                /* FTP client context                   */
{
     setmbk(ftpmb);
     if (!ephaccp()) {
          ret2ftp();
          outprf(usrnum);
     }
     else {
          sktcnc(TNFACCP,fpu->datsok);
          sktnfy(TNFSEND,fpu->datsok,datsnd,fpu,usrnum);
     }
}

static VOID
datsnd(                            /* called when data available from svr  */
struct ftpusr *fpu)                /* FTP client context                   */
{
     INT nroom,nactual;

     setmbk(ftpmb);
     if (tnfclosed) {
          dskerr("SEND",ESHUTDOWN);
          prf("\r");
          ret2ftp();
          outprf(usrnum);
          return;
     }
     if ((nroom=FTPSSIZ-fpu->sbfcnt) > 0) {
          nactual=(INT)fread(fpu->sndbuf+fpu->sbfcnt,1,nroom,fpu->fp);
          if (nactual > 0) {
               fpu->sbfcnt+=nactual;
          }
          else {
               nactual=0;
          }
     }
     switch(sndmgr(fpu->sndbuf,&fpu->sbfcnt,fpu->datsok)) {
     case -1:
          dskerr("SEND",0);
          prf("\r");
          ret2ftp();
          outprf(usrnum);
          break;
     case 0:
          if (nroom > 0 && nactual == 0 && btuoba(usrnum) == OUTSIZ-1) {
               fpu->nbytes+=sndact;
               fpu->byttfc+=sndact;
               ephclos();
               filcls();           /* file deleted off WGS at this point   */
               usrptr->substt=PUTOVER;
               prfmsg(PUTFIN,commas(l2as(fpu->nbytes)));
               outprf(usrnum);
               break;
          }
     case 1:
          if (sndact > 0) {
               fpu->nbytes+=sndact;
               fpu->byttfc+=sndact;
          }
          else {
               actdet=0;
          }
          if (btuoba(usrnum) == OUTSIZ-1 && fpu->rbytes != fpu->nbytes) {
               prfmsg(PUTPROG,commas(l2as(fpu->rbytes=fpu->nbytes)));
               prf("\r");
               if (!(usaptr->ansifl&ANSON)) {
                    stpans(prfbuf);
               }
               powprf();
          }
          break;
     }
}

static VOID
autoals(VOID)                      /* send E-mail address as password      */
{
     CHAR *cp;

     prfmsg(ASKPSW);
     prf("%s\r",cp=femaddr());
     sndcmd("PASS",cp,PSWWAIT);
}

static INT
ftpops(                            /* FTP client option handler            */
INT code,                          /* see TNCXXXX codes in TNO.H           */
INT parm)                          /* see TNCXXXX codes in TNO.H           */
{                                  /* usrnum & ftpptr implicit inputs      */
     INT rc=-1;

     ftsops(code,parm);
     switch (code) {
     case TNCWIWO:                 /* WILL (parm=1) or WONT (parm=0) recd  */
          switch (tnoscb->opid) {
          case TNTOPT_SGA:
               tnoyes(FTPSSG);
               break;
          default:
               tnono(0);
               break;
          }
          break;
     case TNCDODO:                 /* DO (parm=1) or DONT (parm=0) recd    */
          switch (tnoscb->opid) {
          case TNTOPT_SGA:
               tnoyes(FTPCSG);
               break;
          default:
               tnono(0);
               break;
          }
          break;
     case TNCOTHR:                 /* IAC <other> recd (parm=<other>)      */
          break;
     case TNCSEND:
          sndop3(IAC,parm,tnoscb->opid);
          break;
     }
     return(rc);
}

static VOID
sndop3(                            /* send a 3-character option string     */
CHAR c1,                           /* 3 characters                         */
CHAR c2,                           /* (ftpptr (vdaptr) is implicit input)  */
CHAR c3)                           /* (opovfl may be incremented)          */
{
     CHAR buffer[3];

     buffer[0]=c1;
     buffer[1]=c2;
     buffer[2]=c3;
     sndopc(buffer,3);
     if (whomon == usrnum) {
          ftscope(FTSCMT,spr("XTNO  %s %s",cmdname(c2),optname(c3)));
     }
}

static VOID
sndopc(                            /* FTP client sends an option string    */
CHAR *bytes,                       /* binary string of bytes (may incl \0) */
INT nbytes)                        /* number of bytes                      */
{                                  /* (ftpptr (vdaptr) is implicit input)  */
     if (nbytes > 0 && ftpptr->cmdcnt+nbytes <= FTPCSIZ) {
          movmem(bytes,ftpptr->cmdlin+ftpptr->cmdcnt,nbytes);
          ftpptr->cmdcnt+=nbytes;
     }
}

/*--- Command formatting and reply parsing routines ---*/

static VOID
passcmd(                           /* pass possible arguments to server cmd*/
CHAR *svrcmd)                      /* server command, e.g. "STAT"          */
{
     if (margc > 1) {
          rstrin();
          sndcmd(svrcmd,margv[1],RPLWAIT);
     }
     else {
          sndcmd(svrcmd,"",RPLWAIT);
     }
}

static INT
sndcmd(                            /* send a command to the FTP server     */
CHAR *cmd,                         /* command                              */
CHAR *parm,                        /* parameter(s) (or "" if none)         */
INT newstt)                        /* new reply state (e.g. RPLWAIT)       */
{
     INT nc,np,nb;
     CHAR *cp;

     nc=strlen(cmd);
     np=strlen(parm);
     nb=nc+(np > 0 ? 1+np : 0);
     if (nb > FTPCSIZ-3-ftpptr->cmdcnt) {
          prfmsg(CMDOVF);
          ret2ftp();
          return(0);
     }
     else {
          strcpy(cp=ftpptr->cmdlin+ftpptr->cmdcnt,cmd);
          if (np > 0) {
               cp[nc]=' ';
               strcpy(cp+nc+1,parm);
          }
          cp[nb]='\r';
          cp[nb+1]='\n';
          ftpptr->cmdcnt+=nb+2;
          ftpsnd(ftpptr);
          usrptr->substt=newstt;
          btuech(usrnum,0);
          return(1);
     }
}

static CHAR *
femaddr(VOID)                      /* format E-mail address as password    */
{
     static CHAR emaddr[UIDSIZ+1+59+1];

     stzcpy(emaddr,(*alsofusr)(usaptr->userid),UIDSIZ+1);
     strcat(emaddr,"@");
     stzcat(emaddr,maildmn,UIDSIZ+1+59+1);
     return(emaddr);
}

static VOID
ret2ftp(VOID)                      /* return to the "ftp>" prompt          */
{
     rstrxf();
     ftpptr->flags&=~(FCFTRV|FCFXFR|FCFUIP);
     filcls();
     ephclos();
     usrptr->substt=FTPPMT;
     prf(ftppmt);
}

/*--- Ephemeral port routines ---*/

static INT
ephport(VOID)                      /* bind to an ephemeral port            */
                                   /* set ftpptr->lsnsok, datprt, ret 1=ok */
{                                  /* returns 1=ok, else explains          */
     struct sockaddr_in myaddr;
     INT nlen;

     if (ftpptr->lsnsok != -1) {
          clsskt(ftpptr->lsnsok);
     }
     if ((ftpptr->lsnsok=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
                                                            == INVALID_SOCKET) {
          dskerr("SOCKET",0);
          return(0);
     }
     setsndwin(ftpptr->lsnsok,sndwin);
     setrcvwin(ftpptr->lsnsok,ftpptr->drcwin);
     nonblock(ftpptr->lsnsok);
     setmem(&myaddr,sizeof(myaddr),0);
     myaddr.sin_family=AF_INET;
     myaddr.sin_addr.s_addr=ipaddrb;
     nlen=sizeof(myaddr);
     if (bind(ftpptr->lsnsok,(struct sockaddr *)&myaddr,nlen) == SOCKET_ERROR) {
          dskerr("BIND",0);
          ephclos();
          return(0);
     }
     if (getsockname(ftpptr->lsnsok,(struct sockaddr *)&myaddr,&nlen)
                                                            == SOCKET_ERROR) {
          dskerr("NAME",0);
          ephclos();
          return(0);
     }
     ftpptr->datprt=myaddr.sin_port;
     return(1);
}

static INT
ephlstn(VOID)                      /* listen on the ephemeral port         */
{                                  /* return 1=ok, else explains           */
     if (listen(ftpptr->lsnsok,1) == SOCKET_ERROR) {
          dskerr("LISTEN",0);
          ephclos();
          return(0);
     }
     return(1);
}

static INT
ephaccp(VOID)                      /* accept new call on ephemeral port    */
                                   /* set ftpptr->datsok                   */
{                                  /* returns 1=ok, else explains          */
     INT nb;
     struct sockaddr_in svaddr;    /* server info for data connection      */

     nb=sizeof(svaddr);
     if (ftpptr->datsok != -1) {
          clsskt(ftpptr->datsok);
     }
     if ((ftpptr->datsok=accept(ftpptr->lsnsok,(struct sockaddr *)&svaddr,&nb))
                                                            == INVALID_SOCKET) {
          dskerr("ACCEPT",0);
          ephclos();
          return(0);
     }
     nonblock(ftpptr->datsok);
     setsndwin(ftpptr->datsok,sndwin);
     setrcvwin(ftpptr->datsok,ftpptr->drcwin);
     if (!clsskt(ftpptr->lsnsok)) {
          dskerr("CLOSE",0);
          ftpptr->lsnsok=-1;
          ephclos();
          return(0);
     }
     ftpptr->lsnsok=-1;
     return(1);
}

static VOID
ephclos(VOID)                      /* close up the ephemeral port          */
{                                  /* start listening on command port again*/
     if (ftpptr->lsnsok != -1) {
          clsskt(ftpptr->lsnsok);
          ftpptr->lsnsok=-1;
     }
     if (ftpptr->datsok != -1) {
          clsskt(ftpptr->datsok);
          ftpptr->datsok=-1;
     }
     if (ftpptr->socket != -1) {
          usrptr->flags&=~NOINJO;
          sktnfy(TNFRECV,ftpptr->socket,ftprcv,ftpptr,usrnum);
          ftpptr->flags|=FCFCMD;
     }
}

static VOID
dskerr(                            /* data socket error                    */
CHAR *type,                        /*   type (e.g. "LISTEN")               */
INT err)                           /*   override (if 0 use tcpip_errno)    */
{
     if (err == 0) {
          err=tcpip_errno;
     }
     prfmsg(DSKERR,type,err,tcpErrStg(err));
}

/*--- text variables for FTP client ---*/

static CHAR *
tvar_ftp_room(VOID)                /* text variable:  chan dir room avail  */
{
     return(commas(l2as(chdroom())));
}

static CHAR *
tvar_ftp_remserv(VOID)             /* text variable:  remote server name   */
{
     if (usrptr->state == ftpstt
      && usrptr->substt != 0
      && usrptr->substt != FTPHOST) {
          return(ftpptr->dns.name);
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ip_remserv(VOID)              /* text variable:  remote server IP addr*/
{
     if (usrptr->state == ftpstt
      && usrptr->substt != 0
      && usrptr->substt != FTPHOST) {
          return(inet_ntoa(ftpptr->dns.inaddr[0]));
     }
     else {
          return("N/A");
     }
}
