/***************************************************************************
 *                                                                         *
 *   FTPD.C                                                                *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   File Transfer Protocol server daemon.                                 *
 *                                                                         *
 *   Includes glue for getting:  FTP Server Daemon / Virtual Directory     *
 *   API to live with:  File Libraries / File Reservations Kernel.         *
 *   This is the only source file with references to both systems.         *
 *                                                                         *
 *                                              - R. Stein  7/11/94        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "vdir.h"
#include "vdiros.h"
#include "vdirbbs.h"
#include "majorbbs.h"
#include "remote.h"
#include "tcpip.h"
#include "tno.h"
#include "telnetd.h"
#include "ftpd.h"
#include "ftscope.h"
#include "reserve.h"
#include "filexfer.h"
#include "galfilh.h"
#include "galftpd.h"
#define FILREV "$Revision: 32 $"

#ifdef GCWINNT
#define ESHUTDOWN 57
#endif

#define DIR_INCOMING 0             /* data, client -> server               */
#define DIR_OUTGOING 1             /* data, server ->client                */
#define DIR_INFO     2             /* information only                     */
#define DIR_AUDIT    3             /* audit trail post                     */

#if defined(EXTRA_LOGGING)

#define shocst sholog
#define byendl logbye

VOID vlogftp(INT chan,INT dir,CHAR const * fmt,va_list ap);
VOID logftp(INT chan,INT dir,CHAR const * fmt,...);
VOID sholog(CHAR const * desc,CHAR const * detail,...);
VOID logbye(INT msgnum,...);

#else

#define logftp (void)

#endif /* EXTRA_LOGGING */


static VOID ftpincall(INT gotchn);
static VOID ftpdchi(VOID);
static VOID byeftp(INT msgnum,...);
static VOID outftpd(VOID);
static INT ftpdops(INT code,INT parm);
static VOID sndop3(CHAR c1,CHAR c2,CHAR c3);
static GBOOL ftpdinp(VOID);
static GBOOL ftpdhdl(INT cmdidx);
static VOID hdlusr(VOID);
static INT hdlpsw(VOID);
static INT pchanc(INT msgnum,...);
static VOID hdlrnm(VOID);
static VOID hdlstat(VOID);
static INT hdlpsv(VOID);
static VOID hdlrmd(VOID);
static INT numanon(VOID);
static INT numftpd(VOID);
static INT bgnxfr(INT begmsg,...);
static VOID dtaprep(VOID);
static INT NOS(CHAR *str);
VOID dtapsv(struct ftpdusr *ftpd);
VOID dtacon(struct ftpdusr *ftpd);
static VOID lstmor(CHAR *bdy,INT newstt);
static VOID getmor(VOID);
VOID ftpdrcv(struct ftpdusr *ftpd);
VOID ftpdsnd(struct ftpdusr *ftpd);
static VOID endrcv(VOID);
static VOID ftpdsts(VOID);
static VOID cycleme(VOID);
static VOID pasvabt(VOID);
static VOID dtaqabt(VOID);
static VOID dtaabt(INT msgnum,...);
static VOID delsfrg(VOID);
static VOID dtacls(INT msgnum,...);
static VOID filcls(VOID);
static VOID prfdta(INT msgnum,...);
static INT outstg(CHAR *string);
static INT outdta(CHAR *bytes,INT nbytes);
static VOID ftpaud(INT doit,CHAR *action1,CHAR *action2);
static CHAR *ftpdname(VOID);
static VOID decftpd(VOID);
static VOID ftpdzap(VOID);
static VOID ftpdhup(VOID);
static VOID ftpdrst(VOID);
static INT ftplof(VOID);
static INT anonlof(VOID);
static LONG ftpdtfc(VOID);
static INT amrftpd(CHAR *grp,CHAR *fil);
static VOID ftpdfin(VOID);
static GBOOL rsverr(VOID);
static CHAR *setsls(CHAR *stg);
static CHAR *tvar_ftp_name(VOID);
static CHAR *tvar_ftp_pfix(VOID);
static CHAR *tvar_ftp_size(VOID);
static CHAR *tvar_ftp_time(VOID);
static CHAR *tvar_ftp_date(VOID);
static CHAR *tvar_ftp_desc(VOID);
static CHAR *tvar_ftp_who(VOID);
static CHAR *tvar_ftp_uxmode(VOID);
static CHAR *tvar_ftp_uxtime(VOID);
static CHAR *tvar_ftp_uxsize(VOID);
static CHAR *tvar_ftp_uxwho(VOID);
static CHAR *tvar_ftp_uxgrp(VOID);
static CHAR *tvar_ftpanon_email(VOID);
static CHAR *tvar_ftpdir_name(VOID);
static CHAR *tvar_ftpdir_desc(VOID);
static CHAR *tvar_ftp_bytes(VOID);


INT ftpdstt;                       /* FTP server module state number       */
struct module ftpdmodule={         /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     ftpdinp,                      /*    input routine if selected         */
     ftpdsts,                      /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     NULL,                         /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     ftpdfin                       /*    finish-up (sys shutdown) routine  */
};

#define DIRCHUNK 128               /* max room for 1 line of LIST / NLST   */
#define MAXCHUNKS 5                /* max lines of LIST / NLST per cycle   */
#define LSTWAIT (60*5)             /* seconds to wait during LIST / NLST   */
#define XFRWAIT (60*5)             /* seconds to wait during RETR/STOR/APPE*/
#define MAXBADUID 3                /* max consec bad User-ID's b4 logoff   */
#define MAXBADPSW 3                /* max consec bad passwords b4 logoff   */

CHAR ftpcmds[][5]={                /*--- list of FTP command names      ---*/
     "USER",                       /* This array must be in perfect sync   */
     "PASS",                       /* with FTP command indexes in FTPD.H.  */
     "ACCT",
     "XCWD",
     "CWD",
     "XCUP",
     "CDUP",
     "QUIT",
     "PORT",
     "PASV",
     "TYPE",
     "STRU",
     "MODE",
     "RETR",
     "STOR",
     "APPE",
     "RNFR",
     "RNTO",
     "ABOR",
     "DELE",
     "XRMD",
     "RMD",
     "XMKD",
     "MKD",
     "XPWD",
     "PWD",
     "LIST",
     "NLST",
     "STAT",
     "SYST",
     "HELP",
     "NOOP",
     "MDTM",
     "SIZE",
     "ALLO"
};

static VOID (*oldrst)(VOID);       /* for recording (*hdlrst)() vector     */
static VOID (*oldhup)(VOID);       /* for recording module[0].huprou vector*/
static struct sockaddr_in cdaddr;  /* for connect()ing data to client      */
static INT cmdidx;                 /* FTP command index (see FTP.H)        */
static VOID (*olddec)(VOID);       /* for recording (*decusr)() vector     */
static VOID (*oldzap)(VOID);       /* for intercepting channel idle zap    */
static CHAR tvbuff[80];            /* buffer for text variables            */

INT (*amrold)(CHAR *grp,CHAR *fil);/* to save old reserve->amreading() vec */

HMCVFILE ftpdmb;                   /* options from GALFTPD.MSG             */
CHAR maxftpd;                      /* max number of connections to FTP svr */
CHAR maxanon;                      /* max number of anonymous FTP users    */
CHAR maxdfer;                      /* max FTP server file transfers        */
CHAR ftponl;                       /* 1=FTP server online, 0=not           */
CHAR ftprej;                       /* (ftponl=0) 1=reject, 0=ignore calls  */
CHAR *dftdir;                      /* default directory for FTP callers    */
CHAR prfsls;                       /* Preferred slash CHARacter            */
CHAR *listbdy;                     /* body line of LIST listing            */
CHAR *nlstbdy;                     /* body line of NLST listing            */
CHAR fdalona;                      /* record anonymous login?              */
CHAR fdaget;                       /* record get's in audit trail?         */
CHAR fdaput;                       /* record put's in audit trail?         */
CHAR *ftpkey;                      /* key to FTP                           */
CHAR fdafop;                       /* record other file ops in audit trail?*/
CHAR *galfanon=NULL;               /* record anonymous info in text file?  */
CHAR fdaabt;                       /* record time & bytes, anon user?      */
CHAR fdasbt;                       /* record time & bytes, secured user?   */
CHAR anon;                         /* 1=anonymous login allowed            */
CHAR *anonuid;                     /* anonymous userid (usu "ANONYMOUS")   */
CHAR *anoncls;                     /* class for anonymous users            */
INT ftpdchg;                       /* surcharge per minute for secured FTP */
LONG ftpkchg;                      /* charge / 1K bytes traffic via FTP    */
UINT ftpdport;                     /* port for ftp server to listen on     */
INT fanchk;                        /* checking on anonymous FTP e-mail addr*/
#define FANCHKNONE   1             /* FANCHK is set to NONE                */
#define FANCHKSYNTAX 2             /* FANCHK is set to SYNTAX              */

/*

Note:  RFC 959 says the FTP control connection should follow the Telnet
protocol.  This FTP server does properly decode IAC's (hex FF characters) and
CR-NUL's in commands that it receives on the control connection (via
tnordt()), but makes no attempt to encode them in replies that it sends.  This
is justified because the text blocks for the replies do not contain any IAC's
and will not lead to any CR's without LF's, and it's assumed no variable text
will either (e.g. system name, domain names, file names).  We impose these
limitations upon Sysops too (e.g. that they do not insert any IAC characters
into text blocks) in order to ensure compliance with RFC 959.

*/

VOID EXPORT
init__galftpd(VOID)                /* FTP server initialization            */
{
     init__tcpip();
     init__galtntd();
     ftpdmb=opnmsg("galftpd.mcv");
     ftponl=ynopt(FTPONL);
     ftprej=ynopt(FTPREJ);
     ftpkey=stgopt(FTPKEY);
     if (ftponl || ftprej) {
          stzcpy(ftpdmodule.descrp,gmdnam("galftpd.mdf"),MNMSIZ);
          ftpdstt=register_module(&ftpdmodule);
          maxftpd=numopt(MAXFTPD,0,250);
          maxanon=numopt(MAXANON,0,250);
          maxdfer=numopt(MAXDFER,0,250);
          osvkey=stgopt(DOSVKEY);
          ospkey=stgopt(DOSPKEY);
          osgkey=stgopt(DOSGKEY);
          libkey=stgopt(LIBKEY);
#         ifdef UNIX
               unixpath=stgopt(UNIXPATH);
#         else
               ospath=strupr(stgopt(DOSFDRV));
#         endif
          dftdir=stgopt(DFTDIR);
          fdalona=ynopt(FDALONA);
          fdaget=ynopt(FDAGET);
          fdaput=ynopt(FDAPUT);
          fdafop=ynopt(FDAFOP);
          fdaabt=ynopt(FDAABT);
          fdasbt=ynopt(FDASBT);
          ftpdchg=numopt(FTPDCHG,-32767,32767);
          ftpkchg=lngopt(FTPKCHG,-2000000000L,2000000000L);
          if ((fanchk=tokopt(FANCHK,"NONE","SYNTAX",NULL)) == 0) {
               catastro("Configuration option FANCHK has an invalid setting");
          }
          if (*(galfanon=skpwht(getmsg(GALFANON))) == '\0') {
               galfanon=NULL;
          }
          else {
               galfanon=alcdup(galfanon);
          }
          listbdy=alcdup(stpans(getasc(LISTBDY)));
          nlstbdy=alcdup(stpans(getasc(NLSTBDY)));
          vdircase=tokopt(FDCASE,"LOWER","NONE","UPPER",NULL)-2;
          prfsls=tokopt(FDSLASH,"UNIX",NULL) ? UNIXSL : DOSSL;
#         ifdef UNIX
               vdirunix.dirpfx=slashstg(UNIXPFX);
#         else
               vdiros.dirpfx=slashstg(DOSPFX);
#         endif
          vdirlib.dirpfx=slashstg(LIBPFX);
          anon=ynopt(ANON);
          anonuid=stgopt(ANONUID);
          anoncls=stgopt(ANONCLS);
          dclvda(sizeof(struct ftpdusr));
          ftpdport=numopt(FTPDPORT,1,32767);
          regtcpsvr(FTPNAME,ftpdport,FTPBACKLOG,ftpincall);
          cdaddr.sin_family=AF_INET;
          setmem(cdaddr.sin_zero,8,0);
          oldrst=hdlrst;
          hdlrst=ftpdrst;
          oldhup=module[0]->huprou;
          module[0]->huprou=ftpdhup;
          olddec=decusr;
          decusr=decftpd;
          oldzap=hdlzap;
          hdlzap=ftpdzap;
          register_textvar("FTP_NAME",tvar_ftp_name);
          register_textvar("FTP_PFIX",tvar_ftp_pfix);
          register_textvar("FTP_SIZE",tvar_ftp_size);
          register_textvar("FTP_TIME",tvar_ftp_time);
          register_textvar("FTP_DATE",tvar_ftp_date);
          register_textvar("FTP_DESC",tvar_ftp_desc);
          register_textvar("FTP_WHO",tvar_ftp_who);
          register_textvar("FTP_UXMODE",tvar_ftp_uxmode);
          register_textvar("FTP_UXWHO",tvar_ftp_uxwho);
          register_textvar("FTP_UXGRP",tvar_ftp_uxgrp);
          register_textvar("FTP_UXTIME",tvar_ftp_uxtime);
          register_textvar("FTP_UXSIZE",tvar_ftp_uxsize);
          register_textvar("FTPANON_EMAIL",tvar_ftpanon_email);
          register_textvar("FTPDIR_NAME",tvar_ftpdir_name);
          register_textvar("FTPDIR_DESC",tvar_ftpdir_desc);
          register_textvar("FTP_BYTES",tvar_ftp_bytes);
          initlibrsv();
          amrold=reserve->amreading;
          reserve->amreading=amrftpd;
     }
     else {
          clsmsg(ftpdmb);
     }

}

VOID EXPORT
initwc__galftpd(VOID)
{
     init__galftpd();
}

static VOID
ftpincall(                         /* begin incoming FTP session           */
INT gotchn)                        /* 1=chan (curusr) assigned, 0=not avail*/
{                                  /* implicit:  clskt=socket to client    */
     setmbk(ftpdmb);
     clrprf();
     if (!gotchn) {
          sprintf(prfbuf,xlttxv(stpans(getasc(kilipg || errcod != 1 ?
                                BBSSHUT : BBSFULL)),mxmssz),numcdi("TCP/IP"));
          send(clskt,prfbuf,strlen(prfbuf),0);
     }
     else {
          oobinline(clskt);
          setftpd();
          tnoscb=&tntinf[usrnum].tnoscb;
          tnoini(TNMSIZ,ftpdops);
          tiptr=&tcpipinf[tntunm=usrnum];
          btuech(usrnum,0);
          btuxnf(usrnum,0,0);
          setmem(ftpdptr,sizeof(struct ftpdusr),0);
          ftpdptr->inaddr.s_addr=tcpipinf[usrnum].inaddr.s_addr;
          ftpdptr->port=tcpipinf[usrnum].port;
          ftpdptr->lssock=-1;
          ftpdptr->clsock=-1;
          vdsdir(SLS);
          ftpdchi();
          if (!ftponl) {
               byeftp(FTPNOTON);
               rejinc(FTPNAME,"FTP disabled");
          }
          else if (numonl(ftpdstt) > maxftpd) {
               byeftp(FTPDMANY,maxftpd);
               rejinc(FTPNAME,spr("%d FTPing in",maxftpd));
          }
          else {
               shochl("Incoming FTP contact",'',0x1F);
               logftp(usrnum,DIR_INFO,"Incoming FTP contact");
               prfmsg(FTPHELLO);
               sktnfy(TNFRECV,clskt,tnordt,tiptr,usrnum);
          }
          outftpd();
     }
}

VOID
setftpd(VOID)                      /* prepare for FTP server handling      */
{                                  /* curusr() expected to be in effect    */
     setmbk(ftpdmb);
     vdirscb=&ftpdptr->vdirscb;
}

static VOID
ftpdchi(VOID)                      /* FTP server specific channel init     */
{                                  /* (first connect, or relogging on)     */
     usrptr->usrcls=BBSPRV;
     usrptr->state=ftpdstt;
     usrptr->substt=FTPHELLO;
     usrptr->flags|=NOGLOB+NOINJO;
     if (!vdsdir(dftdir)) {
          vdsdir(SLS);
     }
     ftpdptr->flags=0;
     ftpdptr->emladr[0]='\0';
}

static VOID
byeftp(                            /* log-off w/message for FTP server     */
INT msgnum,                        /* same parameters as prfmsg()          */
...)
{
     CHAR *p1,*p2,*p3;
     va_list ap;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     p3=va_arg(ap,CHAR *);
     va_end(ap);

     btulok(usrnum,1);
     btucli(usrnum);
     btuclo(usrnum);
     btuoes(usrnum,1);
     prfmsg(msgnum,p1,p2,p3);
     outftpd();
     clrprf();
     usroff(usrnum)->flags|=BYEBYE;
}

static VOID
outftpd(VOID)                      /* send ANSI-free reply over cmd port   */
{
#if defined(EXTRA_LOGGING)
     CHAR * cp;
     CHAR c;
#endif

     stpans(prfbuf);
     outprf(usrnum);
#if defined(EXTRA_LOGGING)
     cp=NULL;
     if (samend(prfbuf,"\r\n")) {
          cp=prfbuf+(strlen(prfbuf)-2);
     }
     else if (samend(prfbuf,"\r") || samend(prfbuf,"\n")) {
          cp=prfbuf+(strlen(prfbuf)-1);
     }
     if (cp != NULL) {
          c=*cp;
          *cp='\0';
     }
     logftp(usrnum,DIR_OUTGOING,"%s",prfbuf);
     if (cp != NULL) {
          *cp=c;
     }
#endif
}

static INT
ftpdops(                           /* FTP server option exit point handler */
INT code,                          /* see TNCXXXX codes in TNO.H           */
INT parm)                          /* see TNCXXXX codes in TNO.H           */
{
     INT rc=-1;

     ftsops(code,parm);
     switch (code) {
     case TNCWIWO:                 /* WILL (parm=1) or WONT (parm=0) recd  */
          tnono(0);
          break;
     case TNCDODO:                 /* DO (parm=1) or DONT (parm=0) recd    */
          tnono(0);
          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,                           /* (tiptr is implicit input)            */
CHAR c3)                           /* (opovfl may be incremented)          */
{
     CHAR buffer[3];
     INT rc;

     buffer[0]=c1;
     buffer[1]=c2;
     buffer[2]=c3;
     rc=sndop(tiptr,buffer,3);
     logftp(tiptr->unum,DIR_OUTGOING,"%c%c%c",c1,c2,c3);
     if (whomon == tiptr->unum) {
          ftscope(FTSCMT,spr(rc ? "XTNO  %s %s"
                                : "(XTNO %s %s)",cmdname(c2),optname(c3)));
     }
}

static GBOOL
ftpdinp(VOID)                      /* FTP server test-line input handler   */
{
     GBOOL rc=TRUE;

     setftpd();
     if (margc == 0) {
          return(1);
     }
#if defined(EXTRA_LOGGING)
     rstrin();
     logftp(usrnum,DIR_INCOMING,"%s",input);
     parsin();
#endif
     switch (usrptr->substt) {
     case FTPHELLO:
          switch (cmdidx=ftpcdi(margv[0])) {
          case HELP:
          case USER:
          case PASS:
          case SYST:
          case NOOP:
          case NONE:
          case QUIT:
               rc=ftpdhdl(cmdidx);
               break;
          default:
               prfmsg(NEEDLOG);
               break;
          }
          break;
     case USEROK:
          switch (cmdidx=ftpcdi(margv[0])) {
          case PASS:
               hdlpsw();
               usrptr->flags&=~MONHID;
               break;
          case HELP:
          case USER:
          case SYST:
          case NOOP:
          case QUIT:
          case NONE:
               if (usrptr->flags&MONHID) {
                    rstrin();
                    shomal();
                    parsin();
                    usrptr->flags&=~MONHID;
               }
               usrptr->substt=FTPHELLO;
               rc=ftpdhdl(cmdidx);
               break;
          default:
               if (usrptr->flags&MONHID) {
                    rstrin();
                    shomal();
                    usrptr->flags&=~MONHID;
               }
               usrptr->substt=FTPHELLO;
               prfmsg(NEEDLOG);
               break;
          }
          break;
     default:
          rc=ftpdhdl(cmdidx=ftpcdi(margv[0]));
          break;
     case RNFROK:
          switch (cmdidx=ftpcdi(margv[0])) {
          case RNTO:
               if (margc > 2) {
                    rstrin();
                    margc=2;
               }
               hdlrnm();
               break;
          default:
               rc=ftpdhdl(cmdidx);
               break;
          }
          donewrit(0);
          usrptr->substt=PASSOK;
          break;
     }
     outftpd();
     return(rc);
}

static GBOOL
ftpdhdl(                           /* handle command from FTP client       */
INT cmdidx)                        /* index of command (see FTPD.H)        */
{
     GBOOL rc=TRUE;
     INT alr;
     INT dat,tim;

     switch (cmdidx) {
     case XCWD:
     case CWD:
     case RETR:
     case STOR:
     case APPE:
     case RNFR:
     case RNTO:
     case DELE:
     case XRMD:
     case RMD:
     case MKD:
     case XMKD:
     case MDTM:
     case SIZE:
          if (margc > 2) {
               rstrin();
               margc=2;
          }
          break;
     default:
          break;
     }

     switch (cmdidx) {
     case USER:
          pasvabt();
          dtaqabt();
          hdlusr();
          break;
     case PASS:
          prfmsg(PASSQ);
          break;
     case ACCT:
          prfmsg(ACCTOK);
          break;
     case XCWD:
     case CWD:
          dtaqabt();
          if (margc < 2) {
               prfmsg(CWD0,setsls(vdirscb->curdir));
          }
          else if (margc > 2) {
               prfmsg(CWDA);
          }
          else if (vdsdir(margv[1])) {
               prfmsg(CWDOK,setsls(vdirscb->curdir));
          }
          else {
               prfmsg(CWDD);
          }
          break;
     case XCUP:
     case CDUP:
          dtaqabt();
          if (margc != 1) {
               prfmsg(CDUPA);
          }
          else if (vdsdir("..")) {
               prfmsg(CDUPOK,setsls(vdirscb->curdir));
          }
          else {
               prfmsg(CDUPD);
          }
          break;
     case QUIT:
          pasvabt();
          if (ftpdptr->flags&XFRFIL) {
               dtaabt(ABTQUT);
          }
          byendl(QUITOK);
          break;
     case PORT:
          pasvabt();
          dtaqabt();
          if (margc == 2 && decanp(margv[1],&ftpdptr->inaddr,
                                            &ftpdptr->port)) {
               prfmsg(PORTOK,inet_ntoa(ftpdptr->inaddr),ntohs(ftpdptr->port));
          }
          else {
               prfmsg(PORTE);
          }
          break;
     case PASV:
          pasvabt();
          dtaqabt();
          hdlpsv();
          break;
     case TYPE:
          if (margc == 1) {
               prfmsg(TYPEN);
          }
          else if (sameas(margv[1],"A")) {
               prfmsg(TYPEA);
          }
          else if (sameas(margv[1],"L") && margc == 3
                && sameas(margv[2],"8")) {
               prfmsg(TYPEL);
          }
          else if (sameas(margv[1],"I")) {
               prfmsg(TYPEI);
          }
          else {
               prfmsg(TYPEN);
          }
          break;
     case STRU:
          if (margc != 2) {
               prfmsg(STRUA);
          }
          else if (sameas(margv[1],"F")) {
               prfmsg(STRUOK);
          }
          else {
               prfmsg(STRUN);
          }
          break;
     case MODE:
          if (margc != 2) {
               prfmsg(MODEA);
          }
          else if (sameas(margv[1],"S")) {
               prfmsg(MODEOK);
          }
          else {
               prfmsg(MODEN);
          }
          break;
     case RETR:
          dtaqabt();
          if (margc != 2) {
               prfmsg(RETRA);
          }
          else if (rsverr()) {
          }
          else if (strchr(margv[1],'*') != NULL
                || strchr(margv[1],'?') != NULL) {
               prfmsg(RETRW);
          }
          else if (!vds1st(margv[1],0) || vdirscb->fb.ff_attrib&FAMDIR) {
               prfmsg(vdirbad ? RETRD : RETRN);
          }
          else if (!(vdirscb->flags&VDPREAD)) {
               prfmsg(RETRP);
          }
          else if (numftpd() >= maxdfer) {
               prfmsg(XFRMANY,maxdfer);
          }
          else if ((ftpdptr->fp=fopen(vdspth(),FOPRB)) == NULL) {
               prfmsg(RETRF);
          }
          else {
               bgnxfr(RETRBEG);
          }
          break;
     case STOR:
          dtaqabt();
          if (margc != 2) {
               prfmsg(STORA);
          }
          else if (rsverr()) {
          }
          else if (strchr(margv[1],'*') != NULL
                || strchr(margv[1],'?') != NULL) {
               prfmsg(STORW);
          }
          else if ((alr=vds1st(margv[1],VDSITW)) != 0
                && vdirscb->fb.ff_attrib&FAMDIR) {
               prfmsg(STORD);
          }
          else if (!(vdirscb->flags&VDPWRIT)) {
               prfmsg(alr ? STORO : STORC);
          }
          else if (numftpd() >= maxdfer) {
               prfmsg(XFRMANY,maxdfer);
          }
          else if ((ftpdptr->fp=fopen(vdspth(),FOPWB)) == NULL) {
               prfmsg(STORF);
          }
          else {
               if (!bgnxfr(vdirroom == MAXROOM ? STORBEGU : STORBEG,
                           spr("%s",commas(ul2as(vdirroom))))) {
                    delsfrg();
               }
               break;
          }
          donewrit(0);
          break;
     case APPE:
          dtaqabt();
          if (margc != 2) {
               prfmsg(APPEA);
          }
          else if (rsverr()) {
          }
          else if (strchr(margv[1],'*') != NULL
                || strchr(margv[1],'?') != NULL) {
               prfmsg(APPEW);
          }
          else if (vds1st(margv[1],VDSITW) && vdirscb->fb.ff_attrib&FAMDIR) {
               prfmsg(APPED);
          }
          else if (!(vdirscb->flags&VDPWRIT)) {
               prfmsg(APPEP);
          }
          else if (numftpd() >= maxdfer) {
               prfmsg(XFRMANY,maxdfer);
          }
          else if ((ftpdptr->fp=fopen(vdspth(),FOPAB)) == NULL) {
               prfmsg(APPEF);
          }
          else {
               bgnxfr(APPEBEG);
               break;
          }
          donewrit(0);
          break;
     case RNFR:
          dtaqabt();
          if (margc != 2) {
               prfmsg(RNFRA);
          }
          else if (rsverr()) {
          }
          else if (!vds1st(margv[1],VDSITW) || vdirscb->fb.ff_attrib&FAMDIR) {
               prfmsg(RNFRN);
          }
          else if (!(vdirscb->flags&VDPWRIT)) {
               prfmsg(RNFRP);
          }
          else {
               prfmsg(usrptr->substt=RNFROK,setsls(vdspth()));
               break;
          }
          donewrit(0);
          break;
     case RNTO:
          prfmsg(RNTOQ);
          break;
     case ABOR:
          if (ftpdptr->flags&XFRFIL) {
               dtaabt(ABTCMD);
               prfmsg(ABORY);
          }
          else {
               pasvabt();
               prfmsg(ABORN);
          }
          break;
     case DELE:
          dtaqabt();
          if (margc != 2) {
               prfmsg(DELEA);
          }
          else if (rsverr()) {
          }
          else if (!vds1st(margv[1],VDSITW) || vdirscb->fb.ff_attrib&FAMDIR) {
               prfmsg(DELEN);
          }
          else if (!(vdirscb->flags&VDPWRIT)) {
               prfmsg(DELEP);
          }
          else {
               usrptr->substt=DELEOK;
               cycleme();
               break;
          }
          donewrit(0);
          break;
     case XRMD:
     case RMD:
          dtaqabt();
          hdlrmd();
          break;
     case XMKD:
     case MKD:
          dtaqabt();
          if (margc != 2) {
               prfmsg(MKDA);
          }
          else if (rsverr()) {
          }
          else if (vds1st(margv[1],VDSITW)) {
               prfmsg(vdirscb->fb.ff_attrib&FAMDIR ? MKDD : MKDF,
                      setsls(vdspth()));
          }
          else if (!vdsmkd()) {
               prfmsg(MKDE);
          }
          else {
               prfmsg(MKDOK,setsls(vdspth()));
               ftpaud(fdafop,"FTP SERVER DIRECTORY MADE","made");
          }
          donewrit(0);
          break;
     case XPWD:
     case PWD:
          dtaqabt();
          if (margc != 1) {
               prfmsg(PWDA);
          }
          else {
               vdsdir(vdirscb->curdir);
               prfmsg(PWDOK,setsls(vdirscb->curdir));
          }
          break;
     case LIST:
		  
		  
          dtaqabt();
          if (margc > 2) {
               prfmsg(LISTA);
          }
          else if (NOS(margv[1])>10) {
			      byetcp(0);
			      break;
          }
          else if (margc == 2 && rsverr()) {
          }
          else if (!vds1st(margc == 2 ? margv[1] : "",VDSASD+VDSAWE+VDSWLD)) {
               prfmsg(vdirbad ? LISTD : (margc == 2 ? LISTN : LISTZ));
          }
          else {
               bgnxfr(LISTBEG);
          }
          break;
     case NLST:
          dtaqabt();
          if (margc > 2) {
               prfmsg(NLSTA);
          }
          else if (margc == 2 && rsverr()) {
          }
          else if (!vds1st(margc == 2 ? margv[1] : "",VDSASD+VDSAWE+VDSWLD)) {
               prfmsg(vdirbad ? NLSTD : (margc == 2 ? NLSTN : NLSTZ));
          }
          else {
               fileparts(GCPART_PATH,margv[1],ftpdptr->nlpfix,VDRPFIX+1);
               /* old one
               stzcpy(ftpdptr->nlpfix,margv[1],
                      (INT)(fnwext(margv[1])-margv[1]+1));
               */
               bgnxfr(NLSTBEG);
          }
          break;
     case STAT:
          hdlstat();
          break;
     case SYST:
          prfmsg(SYSTOK);
          break;
     case HELP:
          prfmsg(HELPOK);
          break;
     case NOOP:
          prfmsg(NOOPOK);
          break;
     case MDTM:
          dtaqabt();
          if (margc != 2) {
               prfmsg(MDTMA);
          }
          else if (rsverr()) {
          }
          else if (!vds1st(margv[1],0)) {
               prfmsg(MDTMN);
          }
          else {
               dat=vdirscb->fb.ff_fdate;
               tim=vdirscb->fb.ff_ftime;
               prfmsg(MDTMOK,ddyear(dat),ddmon(dat),ddday(dat),
                             dthour(tim),dtmin(tim),dtsec(tim));
          }
          break;
     case SIZE:
          dtaqabt();
          if (margc != 2) {
               prfmsg(SIZEA);
          }
          else if (rsverr()) {
          }
          else if (!vds1st(margv[1],0) || vdirscb->fb.ff_attrib&FAMDIR) {
               prfmsg(SIZEN);
          }
          else {
               prfmsg(SIZEOK,l2as(vdirscb->fb.ff_fsize));
          }
          break;
     case ALLO:
          prfmsg(ALLONOT);
          break;
     default:
     case NONE:
          prfmsg(CMDE);
          break;
     }
     return(rc);
}

static VOID
hdlusr(VOID)                       /* handle USER command                  */
{
     CHAR *cp;
     INT isanon;

     if (margc == 1) {
          prfmsg(USER0);
          return;
     }
     donewrit(0);
     if (ftplof()) {
          usaptr->usedat=today();
          updacc();
     }
     anonlof();
     (*hdlrlg)();
     ftpdchi();
     usrptr->substt=FTPHELLO;
     rstrin();
     isanon=0;
     if (anon) {
          for (cp=firstwd(anonuid) ; *cp != '\0' ; cp=nextwd()) {
               if (sameas(cp,margv[1])) {
                    isanon=1;
                    break;
               }
          }
     }
     if (isanon) {
          if (numanon() >= maxanon) {
               byendl(ANONMNY,numanon());
               return;
          }
          setmem(usaptr,sizeof(struct usracc),0);
          stzcpy(usaptr->curcls,anoncls,KEYSIZ);
          sprintf(usaptr->userid,"(%s) anon ftp",inet_ntoa(tcpipinf[usrnum].inaddr));
          if ((usrptr->cltptr=fndcls(anoncls)) == NULL) {
               byendl(ANONCNO,anoncls);
               return;
          }
          shochl("Anonymous FTP logon...",'f',0x1F);
          logftp(usrnum,DIR_INFO,"Anonymous FTP logon...");
          loadkeys(anoncls);
          prfmsg(USERANON);
          usrptr->substt=USEROK;
          ftpdptr->baduid=0;
          ftpdptr->flags|=ANONYM;
          return;
     }
     switch ((*chkuid)(margv[1])) {
     case LONALR:
     case LONOCK:                  /* ftp users don't get to knock ghost   */
          prfmsg(USERALR);
          break;
     case LONDBT:
          byendl(USERDBT);
          break;
     case LONSUS:
          byendl(USERSUS);
          break;
     case LONUNK:
          if (++ftpdptr->baduid < MAXBADUID) {
               prfmsg(USERUNK);
          }
          else {
               byendl(USERBAD);
          }
          break;
     case LONOK:
          if (!haskey(ftpkey) && !(usaptr->flags&HASMST)) {
               prfmsg(FTPCANT);
          }
          else {
               shochl("FTP logon...",'F',0x1F);
               logftp(usrnum,DIR_INFO,"FTP logon...");
               prfmsg(usrptr->substt=USEROK);
               usrptr->flags|=MONHID;
               ftpdptr->baduid=0;
          }
          break;
     }
     btutsw(usrnum,0);
}

static INT
hdlpsw(VOID)                       /* handle PASS[word] command            */
{
     rstrin();
     if (ftpdptr->flags&ANONYM) {
          if (fanchk == FANCHKSYNTAX
          && (margc != 2
           || strchr(margv[1],'@') == NULL
           || strchr(margv[1],'.') == NULL
           || strlen(margv[1]) < 6)) {
               pchanc(ANONPNO);
          }
          else if (pfnlvl >= 2) {
               pchanc(ANONPFN);
          }
          else {
               stzcpy(ftpdptr->emladr,margv[1],EMLLEN+1);
               strcpy(usaptr->userid,"(");
               stzcat(usaptr->userid,ftpdptr->emladr,UIDSIZ);
               if (strlen(usaptr->userid) < UIDSIZ-1) {
                    strcat(usaptr->userid,")");
               }
               else {
                    strcpy(usaptr->userid+UIDSIZ-3,"*)");
               }
               shochl(usaptr->userid,'f',0x1F);
               usrptr->tckonl=lngtck;
               if (fdalona) {
                    shocst("ANONYMOUS FTP LOGON","%s from %s",
                           ftpdptr->emladr,inet_ntoa(tcpipinf[usrnum].inaddr));
               }
               usrptr->crdrat=0;
               prfmsg(PASSANON,numanon(),min(maxanon,numcdi("TCP/IP")));
               usrptr->substt=PASSOK;
               ftpdptr->badpsw=0;
               return(1);
          }
     }
     else if (margc > 1 && (*chkpsw)(margv[1])) {
          shochl(usaptr->userid,'F',0x1F);
          strcpy(input,"<password>");
          shomal();
          lngswt();
          btuxnf(usrnum,0,0);          /* 'cause lngswt() may turn on NQC! */
          clrprf();
          switch ((*hdlbump)(TRUE)) {
          case BMPTIM:
               byendl(PASSOOT);
               break;
          case BMPLNV:
               byendl(PASSIMB);
               break;
          case BMPDEL:
               byendl(PASSDEL);
               break;
          case BMPOTH:
               byendl(PAMSG);
               break;
          case BMPAOK:
               usrflags();
               if ((*vallon)()) {
                    usrptr->tckonl=lngtck;
                    if (lonaud) {
                         shocst("USER LOGON VIA FTP","User-ID: %s, from %s",
                                 usaptr->userid,inet_ntoa(tcpipinf[usrnum].inaddr));
                    }
                    usrptr->crdrat=mmucrr;
                    usrptr->usrcls=ACTUSR;
                    prfmsg(usrptr->substt=PASSOK);
                    ftpdptr->badpsw=0;
                    return(1);
               }
               else {
                    strins(prfbuf,getasc(PASSB1),PFBSIZ);
                    byendl(PASSB2);
               }
          }
     }
     else {
          strcpy(input,"<invalid password>");
          shomal();
          if (!pchanc(PASSE)) {
               shocst("INVALID PASSWORD ATTEMPT","via FTP on \"%s\" from %s",
                      usaptr->userid,inet_ntoa(tcpipinf[usrnum].inaddr));
          }
     }
     setmem(usaptr,sizeof(struct usracc),0);
     return(0);
}

static INT                         /* returns 1=another chance, 0=logoff   */
pchanc(                            /* another chance to enter password?    */
INT msgnum,                        /* error message if so                  */
...)
{
     CHAR *p1,*p2;
     va_list ap;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     va_end(ap);
     setmem(usaptr,sizeof(struct usracc),0);
     if (++ftpdptr->badpsw < MAXBADPSW) {
          prfmsg(msgnum,p1,p2);
          return(1);
     }
     else {
          byendl(PASSBAD);
          return(0);
     }
}

static VOID
hdlrnm(VOID)                       /* handle file renaming (RNFR,RNTO)     */
{
     if (margc != 2) {
          prfmsg(RNTOA);
     }
     else if (rsverr()) {
     }
     else if (strchr(margv[1],UNIXSL) != NULL
           || strchr(margv[1],DOSSL) != NULL) {
          prfmsg(RNTOE);
     }
     else if (!vdsrnm(margv[1])) {
          prfmsg(RNTOF,caseset(margv[1]));
     }
     else {
          prfmsg(RNTOOK,caseset(margv[1]));
          ftpaud(fdafop,spr("FTP SERVER RENAMED %-0.12s",margv[1]),"renamed");
     }
}

static VOID
hdlstat(VOID)                      /* handle STAT command                  */
{
     INT statmsg;

     prfmsg(STATOK);
     switch (usrptr->substt) {
     case LISTBEG:
     case LISTHDR:
     case LISTFTR:
     case NLSTBEG:
     case NLSTHDR:
     case NLSTFTR:
          statmsg=STATLST;
          break;
     case RETRBEG:
     case RETRING:
     case RETRDON:
          statmsg=STATGET;
          break;
     case STORBEG:
     case STORBEGU:
     case STORING:
          statmsg=STATPUT;
          break;
     case APPEBEG:
     case APPEING:
          statmsg=STATAPP;
          break;
     default:
          statmsg=STATNML;
          break;
     }
     prfmsg(statmsg,commas(ul2as(ftpdptr->numbyt)));
}

static INT
hdlpsv(VOID)                       /* handle PASV command                  */
{
     struct sockaddr_in myaddr;
     INT nlen;

     if ((ftpdptr->lssock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
                                                            == INVALID_SOCKET) {
          dtacls(PASVERR,"SOCKET",tcpip_errno,tcpErrStg(tcpip_errno));
          return(0);
     }
     setsndwin(ftpdptr->lssock,sndwin);
     setrcvwin(ftpdptr->lssock,rcvwin);
     nonblock(ftpdptr->lssock);
     setmem(&myaddr,sizeof(myaddr),0);
     myaddr.sin_family=AF_INET;
     myaddr.sin_addr.s_addr=ipaddrb;
     nlen=sizeof(myaddr);
     if (bind(ftpdptr->lssock,(struct sockaddr *)&myaddr,nlen) == SOCKET_ERROR) {
          dtacls(PASVERR,"BIND",tcpip_errno,tcpErrStg(tcpip_errno));
          return(0);
     }
     if (getsockname(ftpdptr->lssock,(struct sockaddr *)&myaddr,&nlen)
                                                            == SOCKET_ERROR) {
          dtacls(PASVERR,"NAME",tcpip_errno,tcpErrStg(tcpip_errno));
          return(0);
     }
     if (listen(ftpdptr->lssock,1) == SOCKET_ERROR) {
          dtacls(PASVERR,"LISTEN",tcpip_errno,tcpErrStg(tcpip_errno));
          return(0);
     }
     prfmsg(PASVOK,encanp(ipaddrb,myaddr.sin_port));
     ftpdptr->flags|=PASVMD;
     return(1);
}

static VOID
hdlrmd(VOID)                       /* handle removing a directory          */
{
     static CHAR origdir[VDRPFIX+1];

     strcpy(origdir,vdirscb->curdir);
     if (margc != 2) {
          prfmsg(RMDA);
     }
     else if (rsverr()) {
     }
     else if (!vdsdir(margv[1])) {
          if (vds1st(margv[1],0)) {
               prfmsg(RMDF,setsls(vdspth()));
          }
          else {
               prfmsg(RMDN);
          }
     }
     else {
          if (!vdsrmd()) {
               prfmsg(RMDE);
          }
          else {
               prfmsg(RMDOK,setsls(vdirscb->curdir));
               vdsdir("..");
               if (fdafop) {
                    shocst("FTP SERVER DIRECTORY REMOVED",
                           "User %s removed %s",
                           usaptr->userid,setsls(vdirscb->curdir));
               }
          }
     }
     vdsdir(origdir);
}

static INT
numanon(VOID)                      /* number of anonymous FTP users        */
{
     INT unum;
     INT num=0;
     struct user *u;

     for (unum=0 ; unum < nterms ; unum++) {
          u=usroff(unum);
          if ((u->usrcls == ACTUSR || u->usrcls == BBSPRV)
            && u->state == ftpdstt
            && (ftpdunm(unum)->flags&ANONYM)) {
               num++;
          }
     }
     return(num);
}

static INT
numftpd(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->usrcls == BBSPRV)
            && u->state == ftpdstt
            && (ftpdunm(unum)->flags&XFRFIL)) {
               num++;
          }
     }
     return(num);
}

static INT
bgnxfr(                            /* Begin data connection for file xfer  */
INT begmsg,                        /* message to print if successful       */
...)                               /* parameters, ala prfmsg()             */
{                                  /* returns 1=begun, 0=called off        */
     CHAR *p1,*p2;
     va_list ap;

     va_start(ap,begmsg);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     va_end(ap);
     if (ftpdptr->lssock == -1) {
          switch (tcpdial(ftpdptr->inaddr,
                          ftpdptr->port,FTPDPRT,&ftpdptr->clsock)) {
          case DLERRS:
               dtacls(DIALERR,"SOCKET",tcpip_errno,tcpErrStg(tcpip_errno));
               break;
          case DLERRI:
               dtacls(DIALERR,"I/O CONTROL",tcpip_errno,tcpErrStg(tcpip_errno));
               break;
          case DLERRB:
               dtacls(DIALERR,"BIND",tcpip_errno,tcpErrStg(tcpip_errno));
               break;
          case DLERRR:
               dtacls(DIALERR,"REUSE ADDRESS",tcpip_errno,tcpErrStg(tcpip_errno));
               break;
          default:
               dtacls(DIALERR,"CONNECT",tcpip_errno,tcpErrStg(tcpip_errno));
               break;
          case DLCING:
          case DLCNOW:
               dtaprep();
               sktnfy(TNFCONN,ftpdptr->clsock,dtacon,ftpdptr,usrnum);
               prfmsg(usrptr->substt=begmsg,p1,p2);
               cycleme();
               return(1);
          }
     }
     else {
          dtaprep();
          sktnfy(TNFACCP,ftpdptr->lssock,dtapsv,ftpdptr,usrnum);
          prfmsg(usrptr->substt=begmsg,p1,p2);
          cycleme();
          return(1);
     }
     return(0);
}

static VOID
dtaprep(VOID)                      /* prepare for imminent data connection */
{                                  /* expects recent curusr() & vds1st()   */
     ftpdptr->flags|=XFRFIL;
     ftpdptr->bufcnt=0;
     ftpdptr->numbyt=0L;
     ftpdptr->roombyt=vdirroom;
     ftpdptr->numfil=0;
     ftpdptr->sttime=(USHORT)(hrtval()>>12);
}

static VOID
dtapsv(                            /* passive data connection made, xfr now*/
struct ftpdusr *ftpd)
{
     struct sockaddr_in dcaddr;    /* client info for data connection      */
     INT nb;

     ASSERT(nfyskt == ftpd->lssock);
     setftpd();
     nb=sizeof(dcaddr);
     if (ftpd->clsock != -1) {
          clsskt(ftpd->clsock);
     }
     if ((ftpd->clsock=accept(ftpd->lssock,(struct sockaddr *)&dcaddr,&nb))
                                                            == INVALID_SOCKET) {
          dtaabt(PASVERR,"ACCEPT",tcpip_errno,tcpErrStg(tcpip_errno));
          return;
     }
     nonblock(ftpd->clsock);
     setsndwin(ftpd->clsock,sndwin);
     setrcvwin(ftpd->clsock,rcvwin);
     if (!clsskt(ftpd->lssock)) {
          ftpdptr->lssock=-1;
          dtaabt(PASVERR,"CLOSE",tcpip_errno,tcpErrStg(tcpip_errno));
          return;
     }
     ftpdptr->lssock=-1;
     sktnfy(TNFSEND,ftpdptr->clsock,dtacon,ftpdptr,usrnum);
}

static VOID
dtacon(                            /* data connection made, start transfer */
struct ftpdusr *ftpd)
{
     setftpd();

     ASSERT(nfyskt == ftpd->clsock);
     sktcnc(TNFCONN,nfyskt);
     sktcnc(TNFSEND,nfyskt);
     switch (usrptr->substt) {
     case NLSTBEG:
          prfdta(usrptr->substt=NLSTHDR);
          sktnfy(TNFSEND,ftpd->clsock,ftpdsnd,ftpd,usrnum);
          break;
     case LISTBEG:
          prfdta(usrptr->substt=LISTHDR);
          sktnfy(TNFSEND,ftpd->clsock,ftpdsnd,ftpd,usrnum);
          break;
     case RETRBEG:
          usrptr->substt=RETRING;
          sktnfy(TNFSEND,ftpd->clsock,ftpdsnd,ftpd,usrnum);
          break;
     case STORBEG:
     case STORBEGU:
          usrptr->substt=STORING;
          sktcnc(TNFSEND,ftpd->clsock);
          sktnfy(TNFRECV,ftpd->clsock,ftpdrcv,ftpd,usrnum);
          break;
     case APPEBEG:
          usrptr->substt=APPEING;
          sktcnc(TNFSEND,ftpd->clsock);
          sktnfy(TNFRECV,ftpd->clsock,ftpdrcv,ftpd,usrnum);
          break;
     default:
          ASSERT(0);
          clsskt(nfyskt);
          break;
     }
     ftpd->sttime=(USHORT)(hrtval()>>12);
}

VOID
ftpdsnd(                           /* sending data out the data connection */
struct ftpdusr *ftpd)
{
     INT more,i;

     ASSERT(nfyskt == ftpd->clsock);
     setftpd();
     ftpd->sttime=(USHORT)(hrtval()>>12);
     if (tnfclosed) {
          dtaabt(SNDERR,ESHUTDOWN,tcpErrStg(ESHUTDOWN));
          return;
     }
     for (i=0,more=1 ; i < MAXCHUNKS
                    && more
                    && FTPSNDSIZ-ftpdptr->bufcnt >= DIRCHUNK ; i++) {
          switch (usrptr->substt) {
          case LISTHDR:
               lstmor(listbdy,LISTFTR);
               break;
          case NLSTHDR:
               lstmor(nlstbdy,NLSTFTR);
               break;
          case RETRING:
               getmor();
               break;
          default:
               more=0;
               break;
          }
     }
     if (!(ftpd->flags&XFRFIL)) {
          return;
     }
     switch (sndmgr(ftpd->buffer,&ftpd->bufcnt,ftpd->clsock)) {
     case -1:
          dtaabt(SNDERR,tcpip_errno,tcpErrStg(tcpip_errno));
          break;
     case 0:
          if (!more) {
               sktcnc(TNFSEND,ftpd->clsock);
               break;
          }
     case 1:
          if (sndact == 0) {
               actdet=0;
          }
          break;
     }
     ASSERT(ftpdptr->bufcnt <= FTPSNDSIZ);
     ftpd->numbyt+=sndact;
     tcpipinf[usrnum].bytcnt+=sndact;
}

static VOID
lstmor(                            /* output one line of LIST or NLST      */
CHAR *bdy,
INT newstt)
{
     outstg(xlttxv(strcpy(vdatmp,bdy),vdasiz));
     ftpdptr->numfil++;
     if (!vdsnxt()) {
          prfdta(usrptr->substt=newstt);
     }
}

static VOID
getmor(VOID)                       /* get some more data file -> buffer    */
{
     INT nroom,nactual;

     if ((nroom=FTPSNDSIZ-ftpdptr->bufcnt) > 0) {
          ASSERT(ftpdptr->fp != NULL);
          nactual=fread(ftpdptr->buffer+ftpdptr->bufcnt,1,nroom,ftpdptr->fp);
          if (nactual > 0) {
               ftpdptr->bufcnt+=nactual;
          }
          ASSERT(ftpdptr->bufcnt <= FTPSNDSIZ);
          if (ftpdptr->bufcnt == 0) {
               if (ferror(ftpdptr->fp) || nactual < 0) {
                    dtaabt(ABTERR,errno,ftpdname());
                    if (fdaget) {
                         shocst("FTP SERVER FILE ERROR",
                                "Error %d getting %s",errno,ftpdname());
                    }
               }
               else {
                    usrptr->substt=RETRDON;
                    cycleme();
               }
          }
     }
}

VOID
ftpdrcv(                           /* Receiving file on data connection    */
struct ftpdusr *ftpd)              /* pointer to info for this channel     */
{
     INT nactual;

     ASSERT(nfyskt == ftpd->clsock);
     (VOID)ftpd;
     setftpd();
     ftpdptr->sttime=(USHORT)(hrtval()>>12);
     if ((nactual=recv(ftpdptr->clsock,vdatmp,vdasiz,0)) < 0) {
          dtaabt(RCVERR,tcpip_errno,tcpErrStg(tcpip_errno));
     }
     else if (nactual <= 0) {      /* recv()==0:  normal end */
          endrcv();
     }
     else {
          tcpipinf[usrnum].bytcnt+=nactual;
          if (fwrite(vdatmp,1,nactual,ftpdptr->fp) != nactual) {
               catastro("Disk full while %s writing to %s!",
                         usaptr->userid,ftpdname());
          }
          if ((ftpdptr->numbyt+=nactual) > ftpdptr->roombyt) {
               dtaabt(ROOMERR,spr("%s",commas(ul2as(ftpdptr->roombyt))));
          }
     }
}

static VOID
endrcv(VOID)                       /* end of FTPD rcv session (normal eof) */
{                                  /* (expects setftpd() to be in effect)  */
     switch (usrptr->substt) {
     case STORING:
          filcls();
          if (vdslog("")) {
               ftpaud(fdaput,"FTP SERVER FILE STORED","put");
               dtacls(STORDON,commas(l2as(ftpdptr->numbyt)));
          }
          else {
               CHAR *fspec=vdspth();
               if (fspec != NULL) {
                    unlink(fspec);
               }
               ftpaud(fdaput,"FTP SERVER FILE STORAGE REJECTED","can't put");
               dtacls(STORREJ);
          }
          break;
     case APPEING:
          filcls();
          if (vdslog("")) {
               ftpaud(fdaput,"FTP SERVER FILE APPEND","appended");
               dtacls(APPEDON,commas(l2as(ftpdptr->numbyt)));
          }
          else {
               ftpaud(fdaput,"FTP SERVER FILE APPEND REJECTED","tried to append");
               dtacls(APPEREJ);
          }
          break;
     default:
          dtacls(0);
          break;
     }
}

static VOID
ftpdsts(VOID)                      /* FTP server status handler            */
{
     USHORT stnow;
     INT morcyc=1;

     if (status != CYCLE) {
          if (usrptr->usrcls == ACTUSR) {
               dfsthn();
          }
          else {
               switch (status) {
               case RING:
                    rstchn();
                    break;
               }
          }
          return;
     }
     setftpd();
     stnow=(USHORT)(hrtval()>>12);
     switch (usrptr->substt) {
     case RETRDON:
          ftpaud(fdaget,"FTP SERVER FILE RETRIEVED","got");
          vdsrcd(1,ftpdptr->numbyt);
          dtacls(RETRDON,commas(l2as(ftpdptr->numbyt)));
          morcyc=0;
          break;
     case RETRBEG:
     case RETRING:
     case STORBEG:
     case STORBEGU:
     case STORING:
     case APPEBEG:
     case APPEING:
          if (stnow-ftpdptr->sttime > XFRWAIT*16) {
               dtaabt(XFRTMO);
               morcyc=0;
          }
          break;
     case LISTFTR:
          if (ftpdptr->bufcnt == 0) {
               dtacls(LISTEND,ftpdptr->numfil);
               morcyc=0;
               break;
          }
     case LISTBEG:
     case LISTHDR:
          if (stnow-ftpdptr->sttime > LSTWAIT*16) {
               dtacls(LISTTMO);
               morcyc=0;
          }
          break;
     case NLSTFTR:
          if (ftpdptr->bufcnt == 0) {
               dtacls(NLSTEND,ftpdptr->numfil);
               morcyc=0;
               break;
          }
     case NLSTBEG:
     case NLSTHDR:
          if (stnow-ftpdptr->sttime > LSTWAIT*16) {
               dtacls(NLSTTMO);
               morcyc=0;
          }
          break;
     case DELEOK:
          switch (vdsdel()) {
          case 0:
               prfmsg(DELEE,setsls(vdspth()));
               donewrit(0);
               outftpd();
               morcyc=0;
               break;
          case 1:
               prfmsg(DELEOK,setsls(vdspth()));
               ftpaud(fdafop,"FTP SERVER FILE DELETED","deleted");
               donewrit(0);
               outftpd();
               morcyc=0;
               break;
          case 2:
               break;
          }
          break;
     case STORFRG:
          switch (vdsdel()) {
          case 0:
          case 1:
               morcyc=0;
               btulok(usrnum,0);
               unlink(vdspth());   /* delete new fragment (vs overwritten) */
               break;
          case 2:
               break;
          }
          break;
     default:
          morcyc=0;
          break;
     }
     if (morcyc) {
          btuinj(usrnum,CYCLE);
     }
     else {
          ftpdptr->flags&=~CYCLED;
     }
}

static VOID
cycleme(VOID)
{
     if (!(ftpdptr->flags&CYCLED)) {
          btuinj(usrnum,CYCLE);
          ftpdptr->flags|=CYCLED;
     }
}

static VOID
pasvabt(VOID)                      /* quietly abort passive mode if inited */
{
     if (ftpdptr->lssock != -1) {
          clsskt(ftpdptr->lssock);
          ftpdptr->lssock=-1;
     }
     ftpdptr->flags&=~PASVMD;
}

static INT 
NOS(CHAR *str) {
	int i;
	int j;
	j=0;
	for (i=0;i<strlen(str);i++) {
		if (str[i]=='*') j++;
	}

	return j;
}

static VOID
dtaqabt(VOID)                      /* quietly abort data conn if in progr  */
{
     if (ftpdptr->flags&XFRFIL) {
          dtaabt(0);
     }
}

static VOID
dtaabt(                            /* Data transfer abort                  */
INT msgnum,                        /* abort reply                          */
...)                               /* usrnum, etc. implicit input          */
{
     INT delfrag=0;
     CHAR *p1,*p2,*p3;
     va_list ap;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     p3=va_arg(ap,CHAR *);
     va_end(ap);

     switch (usrptr->substt) {
     case RETRBEG:
     case RETRING:
     case RETRDON:
          ftpaud(fdaget,"FTP SERVER FILE RETRIEVAL ABORT","didn't get");
          vdsrcd(0,ftpdptr->numbyt);
          break;
     case STORBEG:
     case STORBEGU:
     case STORING:
          ftpaud(fdaput,"FTP SERVER FILE STORAGE ABORT","didn't put");
          delfrag=1;
          break;
     case APPEBEG:
     case APPEING:
          ftpaud(fdaput,"FTP SERVER FILE APPEND ABORT","incompletely appended");
          break;
     }
     dtacls(msgnum,p1,p2,p3);
     if (delfrag) {
          delsfrg();
     }
}

static VOID
delsfrg(VOID)                      /* delete fragment from aborted STOR    */
{
     usrptr->substt=STORFRG;
     cycleme();
     btulok(usrnum,1);
}

static VOID
dtacls(                            /* close the data connection            */
INT msgnum,                        /* message number or 0=silent           */
...)                               /* message parameters                   */
{
     CHAR *p1,*p2,*p3;
     va_list ap;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     p3=va_arg(ap,CHAR *);
     va_end(ap);
                        /* usrnum, etc. implicit input          */
     if (msgnum > 0) {
          clrprf();
          prfmsg(msgnum,p1,p2,p3);
          outftpd();
     }
     if (ftpdptr->clsock != -1) {
          clsskt(ftpdptr->clsock);
          ftpdptr->clsock=-1;
     }
     pasvabt();
     filcls();
     ftpdptr->flags&=~XFRFIL;
     donewrit(0);
     usrptr->substt=PASSOK;
}

static VOID
filcls(VOID)                       /* close the data file                  */
{
     if (ftpdptr->fp != NULL) {
          fclose(ftpdptr->fp);
          ftpdptr->fp=NULL;
     }
}

static VOID
prfdta(                            /* format message for data channel      */
INT msgnum,                        /* same arguments as prfmsg()           */
...)                               /* caution! uses & corrupts vdatmp      */
{
     CHAR *p1,*p2;
     va_list ap;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     va_end(ap);
     sprintf(vdatmp,xlttxv(stpans(getasc(msgnum)),mxmssz),p1,p2);
     outstg(vdatmp);
}

static INT
outstg(                            /* send a string on the data connection */
CHAR *string)                      /* NUL-terminated string                */
{                                  /* (returns actual number of bytes sent)*/
#if defined(EXTRA_LOGGING)
     CHAR * cp;
     CHAR c;

     cp=NULL;
     if (samend(string,"\r\n")) {
          cp=string+(strlen(string)-2);
     }
     else if (samend(string,"\r") || samend(string,"\n")) {
          cp=string+(strlen(string)-1);
     }
     if (cp != NULL) {
          c=*cp;
          *cp='\0';
     }
     logftp(usrnum,DIR_OUTGOING,"%s",string);
     if (cp != NULL) {
          *cp=c;
     }
#endif
     return(outdta(string,strlen(string)));
}

static INT
outdta(                            /* send some bytes on the data path     */
CHAR *bytes,                       /* bytes                                */
INT nbytes)                        /* number of bytes                      */
{                                  /* (returns actual number sent)         */
     INT nroom,nactual;

     nroom=FTPSNDSIZ-ftpdptr->bufcnt;
     nactual=min(nroom,nbytes);
     if (nactual > 0) {
          movmem(bytes,ftpdptr->buffer+ftpdptr->bufcnt,nactual);
          ftpdptr->bufcnt+=nactual;
          ASSERT(ftpdptr->bufcnt <= FTPSNDSIZ);
     }
     return(nactual);
}

static VOID
ftpaud(                            /* audit trail report for FTP server    */
INT doit,                          /* sysop option 1=report 0=silent       */
CHAR *action1,                     /* title (eg "FTP FILE GET")            */
CHAR *action2)                     /* verb between user & file (eg "got")  */
{
     if (doit) {
          shocst(action1,"User %s %s %s",usaptr->userid,action2,ftpdname());
     }
}

static CHAR *
ftpdname(VOID)                     /* target file name                     */
{
     return(samsls(spr("%s"SLS"%s"SLS"%s",vdirscb->trgsvc->dirpfx,
                                                    vdirscb->trgdir,
                                                    vdirscb->fb.ff_name),
                                                    prfsls));
}

static VOID
decftpd(VOID)                      /* FTP server accounting                */
{
     if (decusp->state == ftpdstt) {
          switch (decusp->usrcls) {
          case ACTUSR:             /* secured FTP access                   */
               deccst+=(ftpdchg>>2)+((decusp->minut4&3) < (ftpdchg&3));
               break;
          case BBSPRV:             /* anonymous FTP, or just logging in    */
               switch (decusp->substt) {
               case FTPHELLO:
               case USEROK:
                    if (!declog()) {
                         curusr(decusn);
                         setmbk(ftpdmb);
                         byendl(FTPZLG);
                    }
                    break;
               default:
                    switch (deccal()) {
                    case -1:
                         curusr(decusn);
                         setmbk(ftpdmb);
                         byendl(FTPZCL,decusp->cltptr->limcal);
                         break;
                    }
                    dec15s();
                    deccrd(0L);
                    break;
               }
               break;
          }
     }
     (*olddec)();
}

static VOID
ftpdzap(VOID)                      /* handle zapping an idle FTP channel   */
{
     if ((usrptr->usrcls == BBSPRV
       || usrptr->usrcls == ACTUSR) && usrptr->state == ftpdstt) {
          setmbk(ftpdmb);
          byendl(FTPZAP);
     }
     else {
          (*oldzap)();
     }
}

static VOID
ftpdhup(VOID)                      /* Secured FTP server hangup stuff      */
{
     if (usrptr->usrcls == ACTUSR && usrptr->state == ftpdstt) {
          curusr(usrnum);
          setftpd();
          pasvabt();
          dtaqabt();
          ftplof();
     }
     (*oldhup)();
}

static VOID
ftpdrst(VOID)                      /* Anonymous FTP server reset stuff     */
{
     if (usrptr->usrcls == BBSPRV && usrptr->state == ftpdstt) {
          curusr(usrnum);
          setftpd();
          pasvabt();
          dtaqabt();
          anonlof();
     }
     (*oldrst)();
}

static INT
ftplof(VOID)                       /* end of a secured FTP session         */
{
     LONG bytcnt;

     if (!(ftpdptr->flags&ANONYM) && usrptr->substt != FTPHELLO
                                  && usrptr->substt != USEROK) {
          bytcnt=ftpdtfc();
          tfcchg(bytcnt,ftpkchg);
          if (fdasbt) {
               audtcptfc("FTP SERVER SESSION ENDED",bytcnt);
          }
          return(1);
     }
     return(0);
}

static INT
anonlof(VOID)                      /* end of an anonymous FTP session      */
{
     LONG bytcnt;
     FILE *fp;

     if (ftpdptr->flags&ANONYM && usrptr->substt != FTPHELLO
                               && usrptr->substt != USEROK) {
          bytcnt=ftpdtfc();
          if (fdaabt) {
               audtcptfc("ANONYMOUS FTP SESSION ENDED",bytcnt);
          }
          if (galfanon != NULL && (fp=fopen(galfanon,FOPAB)) != NULL) {
               fprintf(fp,"%s",xlttxv(getasc(GALFREC),mxmssz));
               fclose(fp);
          }
          return(1);
     }
     return(0);
}

static LONG
ftpdtfc(VOID)                      /* calculate bytes traffic since logon  */
{
     LONG newcnt,bytcnt;

     newcnt=bturep(usrnum,CNTCHR)+tcpipinf[usrnum].bytcnt;
     bytcnt=newcnt-ftpdptr->bytlon;
     ftpdptr->bytlon=newcnt;
     return(bytcnt);
}

static INT
amrftpd(                           /* FTP read-locker of Library files     */
CHAR *grp,                         /* group (Library name)                 */
CHAR *fil)                         /* file name                            */
{                                  /* (see amreading() field in RESERVE.H) */
     INT unum;
     struct user *uptr;
     struct ftpdusr *ftpd;

     for (unum=0 ; unum < nterms ; unum++) {
          uptr=usroff(unum);
          if (uptr->state == ftpdstt
           && (uptr->usrcls == ACTUSR
            || uptr->usrcls == BBSPRV)
           && (uptr->substt == RETRBEG
            || uptr->substt == RETRING
            || uptr->substt == RETRDON)) {
               ftpd=ftpdunm(unum);
               if (ftpd->vdirscb.trgsvc == &vdirlib
                && sameas(grp,ftpd->vdirscb.trgdir)
                && sameas(fil,ftpd->vdirscb.fb.ff_name)) {
                    return(unum);
               }
          }
     }
     return((*amrold)(grp,fil));
}


static VOID
ftpdfin(VOID)                      /* FTP server finalization              */
{
}

/*--- Public Utilities ---*/

CHAR *
slashstg(                          /* stgopt() enforcing beginning slash   */
INT pfxopt)                        /* option from .MSG file                */
{
     CHAR *cp;
     INT n;

     n=strlen(cp=samsls(rawmsg(pfxopt),SL));
     if (*cp != SL) {
          movmem(cp,cp+1,n+1);
          *cp=SL;
     }
     return(alcdup(cp));
}

INT
ftpcdi(                            /* FTP command index                    */
CHAR *cmd)                         /* command name                         */
{                                  /* returns command index (see FTP.H)    */
     INT i;

     for (i=0 ; i < NCMD ; i++) {
          if (sameas(cmd,ftpcmds[i])) {
               return(i);
          }
     }
     return(NONE);
}

/*--- Local Utilities ---*/

static GBOOL                       /*   returns TRUE=error, FALSE=ok       */
rsverr(VOID)                       /* check for DOS reserved device name   */
{
     CHAR filename[GCMAXFNM];

     if (rsvnam(margv[1])) {
          fileparts(GCPART_FILE,margv[1],filename,GCMAXFNM);
          prfmsg(RSVNAM,caseset(filename));
          return(TRUE);
     }
     return(FALSE);
}

static CHAR *
setsls(                            /* set slashes to preferred slash       */
CHAR *stg)                         /* string, converted in-place           */
{                                  /* caution!  uses & corrupts vdatmp     */
     return(samsls(strcpy(vdatmp,stg),prfsls));
}

/*--- Text Variables ---*/

static CHAR *
tvar_ftp_name(VOID)                /* text variable:  file name            */
{
     if (ftpdptr->flags&XFRFIL) {
          return(vdirscb->fb.ff_name);
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_pfix(VOID)                /* text variable:  NLST path prefix     */
{
     if (usrptr->substt == NLSTHDR) {
          return(ftpdptr->nlpfix);
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_size(VOID)                /* text variable:  file size            */
{
     if (ftpdptr->flags&XFRFIL) {
          return(commas(l2as(vdirscb->fb.ff_fsize)));
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_time(VOID)                /* text variable:  file time            */
{
     if (ftpdptr->flags&XFRFIL) {
          if (vdirscb->fb.ff_ftime == 0) {
               return("");
          }
          else {
               return((CHAR *)nctime(vdirscb->fb.ff_ftime));
          }
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_date(VOID)                /* text variable:  file date            */
{
     if (ftpdptr->flags&XFRFIL) {
          if (vdirscb->fb.ff_fdate == 0) {
               return("");
          }
          else {
               return((CHAR *)ncdate(vdirscb->fb.ff_fdate));
          }
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_desc(VOID)                /* text variable:  file description     */
{
     if (ftpdptr->flags&XFRFIL) {
          return(vdsinf(VDFDSC));
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_who(VOID)                 /* text variable:  who put file there   */
{
     if (ftpdptr->flags&XFRFIL) {
          return(vdsinf(VDFWHO));
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_uxmode(VOID)              /* text variable:  unix file mode       */
{
     static CHAR mode[10+1]="----------";

     if (ftpdptr->flags&XFRFIL) {
          mode[0]=(vdirscb->fb.ff_attrib&FAMDIR) ? 'd' : '-';
          mode[1]=mode[4]=mode[7]=(vdirscb->flags&VDPREAD) ? 'r' : '-';
          mode[2]=mode[5]=mode[8]=(vdirscb->flags&VDPWRIT) ? 'w' : '-';
          mode[3]=mode[6]=mode[9]=(vdirscb->fb.ff_attrib&FAMDIR) ? 'x' : '-';
          return(mode);
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_uxtime(VOID)              /* text variable:  unix file time&date  */
{
     if (ftpdptr->flags&XFRFIL) {
          if (vdirscb->fb.ff_ftime == 0
           && vdirscb->fb.ff_fdate == 0) {
               return("Jan  1  1970");
          }
          else {
               return((CHAR *)ncudnt(vdirscb->fb.ff_fdate,vdirscb->fb.ff_ftime));
          }
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_uxsize(VOID)              /* text var: unix file size (no ',')    */
{
     if (ftpdptr->flags&XFRFIL) {
          return(l2as(vdirscb->fb.ff_fsize));
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_uxwho(VOID)               /* text variable:  Unix owner           */
{                                  /* always exactly one word, no white    */
     CHAR *cp;

     if (ftpdptr->flags&XFRFIL) {
          stzcpy(tvbuff,vdsinf(VDFWHO),sizeof(tvbuff));
          for (cp=tvbuff ; *cp != '\0' ; cp++) {
               if (isspace(*cp)) {
                    *cp='_';
               }
          }
          return(tvbuff[0] == '\0' ? "." : tvbuff);
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftp_uxgrp(VOID)               /* text variable:  Unix group           */
{                                  /* always exactly one word, no white    */
     CHAR *cp;

     if (ftpdptr->flags&XFRFIL) {
          stzcpy(tvbuff,vdsinf(VDFGRP),sizeof(tvbuff));
          for (cp=tvbuff ; *cp != '\0' ; cp++) {
               if (isspace(*cp)) {
                    *cp='_';
               }
          }
          return(tvbuff[0] == '\0' ? "." : tvbuff);
     }
     else {
          return("N/A");
     }
}

static CHAR *
tvar_ftpanon_email(VOID)           /* text variable:  Anonymous email addr */
{
     return(ftpdptr->emladr);
}

static CHAR *
tvar_ftpdir_name(VOID)             /* text variable:  directory name       */
{
     return(vdsinf(VDDNAM));
}

static CHAR *
tvar_ftpdir_desc(VOID)             /* text variable:  directory description*/
{
     return(vdsinf(VDDDSC));
}

static CHAR *
tvar_ftp_bytes(VOID)               /* text variable:  FTP svr bytes xfer'd */
{
     return(ul2as(ftpdptr->numbyt));
}

#if defined(EXTRA_LOGGING)

VOID
vlogftp(
INT chan,
INT dir,
CHAR const * fmt,
va_list ap)
{
     FILE * fp;
     CHAR const * dirstr;

     if ((fp=fopen(spr("galftpd.%03d",chan),FOPAA)) == NULL) {
          return;
     }
     switch (dir) {
     case DIR_INCOMING:
          dirstr="<--";
          break;
     case DIR_OUTGOING:
          dirstr="-->";
          break;
     case DIR_INFO:
          dirstr="---";
          break;
     case DIR_AUDIT:
          dirstr="***";
          break;
     default:
          dirstr="???";
          break;
     }
     fprintf(fp,"%s %s: %s",ncdate(today()),nctime(now()),dirstr);
     vfprintf(fp,fmt,ap);
     fprintf(fp,"\n");
     fclose(fp);
}


VOID
logftp(
INT chan,
INT dir,
CHAR const * fmt,
...)
{
     va_list ap;
     va_start(ap,fmt);
     vlogftp(chan,dir,fmt,ap);
     va_end(ap);
}

VOID
sholog(
CHAR const * desc,
CHAR const * detail,
...)
{
     va_list ap;
     VOID * p1;
     VOID * p2;
     VOID * p3;
     VOID * p4;

     va_start(ap,detail);
     vlogftp(usrnum,DIR_AUDIT,spr("%s: %s",desc,detail),ap);
     p1=va_arg(ap,VOID *);
     p2=va_arg(ap,VOID *);
     p3=va_arg(ap,VOID *);
     p4=va_arg(ap,VOID *);
#undef shocst
     shocst(desc,detail,p1,p2,p3,p4);
     va_end(ap);
}

VOID
logbye(
INT msgnum,
...)
{
     va_list ap;
     VOID * p1;
     VOID * p2;
     VOID * p3;
     VOID * p4;

     va_start(ap,msgnum);
     p1=va_arg(ap,VOID *);
     p2=va_arg(ap,VOID *);
     p3=va_arg(ap,VOID *);
     p4=va_arg(ap,VOID *);
     va_end(ap);
     prfmsg(msgnum,p1,p2,p3,p4);
     stpans(prfbuf);
     if (samend(prfbuf,"\n")) {
          prfbuf[strlen(prfbuf)-1]='\0';
     }
     if (samend(prfbuf,"\r")) {
          prfbuf[strlen(prfbuf)-1]='\0';
     }
     logftp(usrnum,DIR_OUTGOING,"%s",prfbuf);
#undef byendl
     byendl(msgnum,p1,p2,p3,p4);
}

#endif /* EXTRA_LOGGING */

