/***************************************************************************
 *                                                                         *
 *   FILEXFER.C                                                            *
 *                                                                         *
 *   Copyright (c) 1992-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This is the Worldgroup interface into the FTF file transfer routines. *
 *                                                                         *
 *   For file tagging and downloading, see FTG.H                           *
 *   For file uploading, see FUP.H                                         *
 *                                                                         *
 *                                                 - Bob Stein  1/23/92    *
 *                                                                         *
 ***************************************************************************/

#include <errno.h>
#include "gcomm.h"
#include "majorbbs.h"
#include "filexfer.h"
#include "ftfascii.h"
#include "ftfxymd.h"
#include "ftfzmod.h"
#include "ftfkerm.h"
#include "ftfview.h"
#include "ftscope.h"
#include "wgsftf.h"

#define FILREV "$Revision: 11 $"

INT ftfstt;                        /* FILEXFER module state number         */

static GBOOL ftfinp(VOID);
static GBOOL ftflof(VOID);
static VOID ftfsth(VOID);
static VOID ftfhup(VOID);
static VOID ftfcls(VOID);

static VOID tcflof(VOID);
static INT tshbeg(INT repvis);
static INT opnbeg(VOID);

struct module filexfer={           /* module interface block               */
     "",                           /*    name used to refer to this module */
     ftfinp,                       /*    user logon supplemental routine   */
     ftfinp,                       /*    input routine if selected         */
     ftfsth,                       /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     ftflof,                       /*    user logoff supplemental routine  */
     ftfhup,                       /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     ftfcls                        /*    finish-up (sys shutdown) routine  */
};

struct ftg *ftgptr;                               /* global tagspec pointer */

struct ftuser *ftuser,                         /* FILEXFER.C array x nterms */
              *ftuptr;                 /* FILEXFER.C stuff for current user */

static VOID *ftgblok;              /*  pointer for use with ptrblok()       */

struct ftg *ftgusr;                       /* global ptr to user's ftg array */

INT whomon=-1;                      /* channel being monitored by GALFTSC.C */
VOID (*ftscope)(INT,...)=NULL;      /* GALFTSC.C entry point, if any */

INT maxtags;                     /* maximum number of tagged files per user */
CHAR alwait;                                 /* auto log-off waiting period */
USHORT binzap;        /* binary trans timeout, 1/16 seconds, 0xFFFF=forever */
USHORT asczap;         /* ASCII trans timeout, 1/16 seconds, 0xFFFF=forever */

USHORT ftftck;                        /* 16 Hertz counter for FTF stuff     */
INT ftfomt;                         /* exact output buffer size for FTF     */

#define  OBATMO (5*16)            /* timeout waiting for transmit to finish */
#define  FX3TMO (5*16)           /* timeout waiting to transmit X.29 string */
#define  BRWTMO (4)           /* delay after output buf empty before btubsz */

/*--- extra, non-interactive usrptr->substt state codes ---*/
#define RCVING -1                                 /* receiving now underway */
#define SNDING -2                                   /* sending now underway */
#define SNDWLD -3                         /* breaking up multi-file tagspec */
#define SNDETG -4                        /* about to delete current tagspec */
#define XFRDON -5                           /* transfer is done & sucessful */
#define XFRABT -7                                       /* transfer aborted */
#define X3PRCV -9                    /* sending X.3 prefix before file recv */
#define X3PSND -10                   /* sending X.3 prefix before file send */
#define SRTRCV -11             /* protocol-specific initiation of file recv */
#define SRTSND -12             /* protocol-specific initiation of file send */
#define SPCXIT -13      /* special exit, with return to original state code */
#define BRWAIT -14         /* wait 4 chars to REALLY go out at start of rcv */
#define XFRARP -15        /* for RIP users, wait RIPADL seconds after abort */

static HMCVFILE ftfmb=NULL;                     /* FTF message file pointer */
CHAR *fprlock;                                         /* "F" protocol lock */
INT x25tmo;                                  /* X.25 minimum timeout period */

INT eclscbbas;                 /* scb array base selector if ECLIPSE active */
static VOID *scbblok;                    /* array of session control blocks */

CHAR tshmsg[TSHLEN+1];          /* universal global Tagspec Handler message */
             /* used for passing data to/from the tagspec handler routines, */
      /* e.g.: tagspec description, subtagspec, reject or abort explanation */

#define FIECHUNK 1024    /* bytes to read at a time for file im/export copy */

struct ftfpsp ftpfir={  /* Copying from any DOS path to a "maintained" file */
     NULL,
     "F",                                  /* 1-3 code letters for protocol */
     "File Import (existing file)",                     /* name of protocol */
     FTFASC+FTF7BT+FTFXTD,                     /* protocol capability flags */
     sizeof(struct ftfscb),        /* total length of session control block */
     0,         /* .byttmo                             default byte timeout */
     0,         /* .paktmo                           default packet timeout */
     0,         /* .retrys                              default max retries */
     0L,        /* .window   max window size (packets/bytes as appropriate) */
     0,         /* .paksiz                        packet size 0=auto-figure */
     NULL,      /* .initze()    Initialize this protocol (recompute scblen) */
     NULL,      /* .start()                                Start a transfer */
     NULL,      /* .contin()              Continuously call, 1=more, 0=done */
     NULL,      /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     NULL,      /* .term()        Initiate graceful termination of transfer */
     NULL,      /* .abort()  Immediately unconditionally abort the transfer */
     NULL,      /* .hdlinb()              Handle an array of incoming bytes */
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpfex={  /* Copying from a "maintained" file to any DOS path */
     NULL,
     "F",                                  /* 1-3 code letters for protocol */
     "File Export (to any path)",                       /* name of protocol */
     FTFXMT+FTFASC+FTF7BT+FTFAFN+FTFXTD,       /* protocol capability flags */
     sizeof(struct ftfscb),        /* total length of session control block */
     0,         /* .byttmo                             default byte timeout */
     0,         /* .paktmo                           default packet timeout */
     0,         /* .retrys                              default max retries */
     0L,        /* .window   max window size (packets/bytes as appropriate) */
     0,         /* .paksiz                        packet size 0=auto-figure */
     NULL,      /* .initze()    Initialize this protocol (recompute scblen) */
     NULL,      /* .start()                                Start a transfer */
     NULL,      /* .contin()              Continuously call, 1=more, 0=done */
     NULL,      /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     NULL,      /* .term()        Initiate graceful termination of transfer */
     NULL,      /* .abort()  Immediately unconditionally abort the transfer */
     NULL,      /* .hdlinb()              Handle an array of incoming bytes */
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

VOID anspmu(VOID);                                    /* ANSI protocol menu */
VOID rippmu(VOID);                                     /* RIP protocol menu */
VOID dsppmu(VOID);                         /* send appropriate menu to user */
VOID (*hdlpmu)(VOID)=dsppmu;             /* Handler chain for protocol menu */

CHAR pmcare,pmvalu;      /* globals for prtmnu(), hdlpmu() and subordinates */

struct fexdat {              /* data fields used for ASCII receive/transmit */
     struct ftfscb scb;              /* standard session control block data */
     CHAR path[TSHLEN+1];                                    /* path buffer */
};
#define fexscb ((struct fexdat *)ftfscb)

struct ftglst {               /* TAGSPEC structure for ASCII file listing  */
     CHAR *path;
     VOID (*whndun)();        /* pointer to when-done routine (arg=ok)     */
     HMCVFILE mbk;            /* listingq()'s curmbk value                 */
     INT msgno;               /* listingq()'s message number               */
     INT state;               /* listingq()'s caller's usrptr->state       */
};

#define lstptr ((struct ftglst *)(ftgptr->tagspc))

static
VOID (*tmprou)(VOID)=NULL;    /* temporary retrou holder for ftgsbm()      */

/* RIP-related items */

#define MAXPROT 12            /* maximum # of protocols to be displayed    */
#define MXLWID 80             /* maximum width of a button label           */
#define PROTFNT "|Y000001LD"  /* default font for protocol buttons         */
#define PROTBSY "|1B0000030N1E03010F0F080700000F07000000"
                              /* default style for protocol buttons        */

CHAR protbuts[MAXPROT][MXLWID+1], /* protocol button commands              */
     fstyle[RFNTSZ+1],        /* font style for protocol buttons           */
     bstyle[RBSYSZ+1];        /* button style for protocol buttons         */

INT ripadl=0;                 /* delay time for aborted transfers for RIP  */

VOID getbuts(VOID);

typedef VOID (*WHNDUN)(INT);

/*--- Initialization ---*/

VOID
iniftf1(VOID)              /* FTF initialization, phase 1 (before alcvda()) */
{
     stzcpy(filexfer.descrp,gmdnam("wgsftf.mdf"),MNMSIZ);
     ftfstt=register_module(&filexfer);
     ftfmb=opnmsg("wgsftf.mcv");
     maxtags=numopt(MAXTAGS,2,999);
     alwait=(CHAR)numopt(ALWAIT,0,255);
     if ((binzap=(UINT)numopt(BINZAP,-1,4000)) != 0xFFFFu) {
          binzap<<=4;
     }
     if ((asczap=(UINT)numopt(ASCZAP,-1,4000)) != 0xFFFFu) {
          asczap<<=4;
     }
     ftuser=(struct ftuser *)alczer(nterms*sizeof(struct ftuser));
     ftgblok=alcblok(nterms,maxtags*sizeof(struct ftg));
     ftfomt=OUTSIZ-1;
                                    /* inserting protocols in REVERSE order */
     fprlock=ftpfir.secur=stgopt(FIMLOCK);
     ftpfex.secur=stgopt(FEXLOCK);
     ftplogh(&ftpfir);                                        /* FILEXFER.C */
     ftplogh(&ftpfex);
     ftpvew.secur=stgopt(VEWLOCK);
     ftplogh(&ftpvew);                                         /* FTFVIEW.C */
     ftpkmx.window=numopt(KTXWIN,1,31);
     ftpkmr.window=numopt(KRCWIN,1,10);
     ftpkmx.paktmo=numopt(KTXTMO,0,32767);
     ftpkmr.paktmo=numopt(KRCTMO,0,32767);
     ftpkmx.retrys=numopt(KTXRETRY,0,32767);
     ftpkmr.retrys=numopt(KRCRETRY,0,32767);
     ftpkmx.secur=stgopt(KMDLOCK);
     ftpkmr.secur=stgopt(KMULOCK);
     ftplogh(&ftpkmx);                                         /* FTFKERM.C */
     ftplogh(&ftpkmr);
     ftpzrx.secur=ftpzmx.secur=stgopt(ZMDLOCK);
     ftplogh(&ftpzrx);                                         /* FTFZMOD.C */
     ftplogh(&ftpzmx);
     ftpzrx.window=ftpzmx.window=lngopt(ZXWINDOW,0L,1000000000L);
     ftpzmr.secur=stgopt(ZMULOCK);
     ftplogh(&ftpzmr);
     ftpygx.secur=stgopt(YGDLOCK);
     ftpygr.secur=stgopt(YGULOCK);
     ftpybx.secur=stgopt(YMDLOCK);
     ftpybr.secur=stgopt(YMULOCK);
     ftpxmx.secur=ftpxcx.secur=ftpx1x.secur=stgopt(XMDLOCK);
     ftpxmr.secur=ftpxcr.secur=ftpx1r.secur=stgopt(XMULOCK);
     ftpxmr.byttmo=
     ftpxmx.byttmo=
     ftpxcr.byttmo=
     ftpxcx.byttmo=
     ftpx1r.byttmo=
     ftpx1x.byttmo=
     ftpybr.byttmo=
     ftpybx.byttmo=
     ftpygr.byttmo=
     ftpygx.byttmo=numopt(XBYTTO,1,32767)*16;
     ftpxmr.paktmo=
     ftpxmx.paktmo=
     ftpxcr.paktmo=
     ftpxcx.paktmo=
     ftpx1r.paktmo=
     ftpx1x.paktmo=
     ftpybr.paktmo=
     ftpybx.paktmo=
     ftpygr.paktmo=
     ftpygx.paktmo=numopt(XBLKTO,1,32767)*16;
     ftpxmr.retrys=
     ftpxmx.retrys=
     ftpxcr.retrys=
     ftpxcx.retrys=
     ftpx1r.retrys=
     ftpx1x.retrys=
     ftpybr.retrys=
     ftpybx.retrys=
     ftpygr.retrys=
     ftpygx.retrys=numopt(XRETRY,-1,32767);
     ftplogh(&ftpygx);                                         /* FTFXYMD.C */
     ftplogh(&ftpygr);
     ftplogh(&ftpybx);
     ftplogh(&ftpybr);
     ftplogh(&ftpx1x);
     ftplogh(&ftpx1r);
     ftplogh(&ftpxcx);
     ftplogh(&ftpxcr);
     ftplogh(&ftpxmx);
     ftplogh(&ftpxmr);
     ftpasr.secur=stgopt(ASULOCK);
     ftpasx.secur=stgopt(ASDLOCK);
     ftplsx.secur=stgopt(LSDLOCK);
     ftplogh(&ftpasr);                                        /* FTFASCII.C */
     ftplogh(&ftpasx);
     ftplogh(&ftplsx);
     scbblok=alcblok(nterms,scbmax);
     dclvda(fbleng);                    /* ftfbuf[fbleng] resides at vdatmp */
     dclvda(FIECHUNK);
     ztzone=lngopt(ZTZONE,-86400L,86400L);
     x25tmo=numopt(X25TMO,-1,32767);
     if (ripdfd) {
          getbuts();
          ripadl=numopt(RIPADL,1,30)*16;
     }
}

VOID
getbuts(VOID)              /* read in protocol buttons, style, font style */
{
     INT svcl,sl,loop,nfnd=0;
     CHAR *cp,*cp2;

     strcpy(fstyle,PROTFNT);
     strcpy(bstyle,PROTBSY);
     svcl=clingo;
     clingo=ripidx;
     cp=rawmsg(PRTRCHC);
     sl=strlen(cp);
     for (loop=0 ; (loop < sl) && (strncmp(cp,RIP_BUTTON,BCSIZ) != 0)
        ; loop++,cp++) {
          if (strncmp(cp,RIP_FONT_STYLE,FSSIZ) == 0) {
               stzcpy(fstyle,cp,RFNTSZ+1);
          }
          else if (strncmp(cp,RIP_BUTTON_STYLE,BSSIZ) == 0) {
               stzcpy(bstyle,cp,RBSYSZ+1);
          }
     }
     while (*cp != '\0' && strlen(cp) >= RBUTSZ && nfnd < MAXPROT) {
          if (strncmp(cp,RIP_BUTTON,BCSIZ) == 0) {
               if ((cp2=strstr(cp,"<>")) == NULL) {
                    break;
               }
               stzcpy(protbuts[nfnd],cp,((INT)(cp2-cp))+1);
               nfnd++;
               cp=*cp2 == '\r' ? cp2+1 : cp2;
          }
          else {
               cp++;
          }
     }
     clingo=svcl;
     if (nfnd < MAXPROT) {
          catastro("FILEXFER: Error in level 6 option %d! "
                   "(RIP)=%d",PRTRCHC,nfnd);
     }
}

VOID
iniftf2(VOID)               /* FTF initialization, phase 2 (after alcvda()) */
{
     ftfbuf=vdatmp;
}

/*--- FILEXFER private functions  ---*/

VOID
setftu(VOID)                    /* set up lots of globals for file transfer */
{
     ftftck=(USHORT)(hrtval()>>12);
     ftuptr=&ftuser[usrnum];
     ASSERT(usrptr == usroff(usrnum));
     if (usrptr->state == ftfstt && usrptr->substt == SNDWLD) {
          ftgptr=&ftuptr->ftg;       /* sub-tagspec of current wild tagspec */
     }
     else {
          ftgptr=ftuptr->ftgptr;                /* current non-wild tagspec */
     }
     ftfscb=(struct ftfscb *)ptrblok(scbblok,usrnum);
     ftfpsp=ftuptr->ftfpsp;
     ftgusr=(struct ftg *)ptrblok(ftgblok,usrnum);
     ftfomt=ftuptr->oubsiz;
}

INT
ftgnum(VOID)                        /* number of tagged files for this user */
{
     return(ftuser[usrnum].numftg);
}

VOID
ftgsho(VOID)         /* list out the tagged files (caller calls outprf()) */
{
     INT i;
     INT blimit;

     setftu();
     setmbk(ftfmb);
     prfmsg(TAGSFA);
     blimit=btuoba(usrnum)-1000;
     for (i=0,ftgptr=ftgusr ; i < ftuptr->numftg ; i++,ftgptr++) {
          if (prfptr-prfbuf > blimit) {
               prfmsg(TSHBUST,ftuptr->numftg-i);
               break;
          }
          ftgptr->tshndl(TSHDSC);
          setmbk(ftfmb);
          prfmsg(TSHLST,i+1,tshmsg);
     }
     setftu();
}

static INT
fndprt(                                 /* find the protocol spec structure */
CHAR *code,                         /* protocol code (may modify & restore) */
CHAR flcare,                                    /* what flags we care about */
CHAR flvalu)                              /* what we want their value to be */
{                 /* if found, returns 1 and sets ftfpsp and ftuptr->ftfpsp */
            /* sets FTBANG flag to represent "!" auto logoff after transfer */
     struct ftfpsp *ftp;
     INT rc=0;
     INT n;
     CHAR c='\0';

     if ((n=strlen(code)) > 1 && code[n-1] == '!') {
          c='!';
          code[--n]='\0';
          ftuptr->flags|=FTBANG;
     }
     else {
          ftuptr->flags&=~FTBANG;
     }
     for (ftp=fthead ; ftp != NULL ; ftp=ftp->next) {
          if ((ftp->flags&flcare) == flvalu
           && sameas(code,ftp->code)
           && (!(ftp->flags&FTFXTD) || haskey(ftp->secur))) {
               ftfpsp=ftuptr->ftfpsp=ftp;
               rc=1;
               break;
          }
     }
     code[n]=c;
     return(rc);
}

INT
ispvis(                                             /* is protocol visible? */
struct ftfpsp *ftp)                                             /* protocol */
{                                             /* pmcare/pmvalu are implicit */
     return(ftp != NULL
         && (ftp->flags&pmcare) == pmvalu
         && (!(ftp->flags&FTFXTD) || haskey(ftp->secur))
         && (!sameas(ftp->code,"V") || ftuptr->filid != UNKFILE));
}

static CHAR *
pdesc(                                              /* protocol description */
struct ftfpsp *ftp)
{
     if ((CHAR *)ftp->code == "V") {
          return(spr("View contents of %0.9s file",
                     arcdsc[(SHORT)ftuptr->filid]));
     }
     else {
          return(ftp->name);
     }
}

static VOID
prtmnu(                                               /* show protocol menu */
CHAR flcare,                                    /* what flags we care about */
CHAR flvalu)                              /* what we want their value to be */
{
     pmcare=flcare;
     pmvalu=flvalu;
     (*hdlpmu)();
}

VOID
dsppmu(VOID)
{
     isripu() ? rippmu() : anspmu();
}

VOID
anspmu(VOID)                 /* ANSI version of file transfer protocol menu */
{                              /* ftuptr,pmcare,pmvalue are implicit inputs */
     struct ftfpsp *ftp1,*ftp2;
     INT i,n;

     setmbk(ftfmb);
     for (n=0,ftp1=fthead ; ftp1 != NULL ; ftp1=ftp1->next) {
          if (ispvis(ftp1)) {
               n++;
          }
     }
     if (n == 0) {
          prfmsg(NOPROTS);
          return;
     }
     n=((n+1)>>1);
     for (ftp1=fthead ; ftp1 != NULL && !ispvis(ftp1) ; ftp1=ftp1->next) {
     }
     for (i=0,ftp2=ftp1 ; i < n && ftp2 != NULL ; i++) {
          while ((ftp2=ftp2->next) != NULL && !ispvis(ftp2)) {
          }
     }
     for (i=0 ; i < n ; i++) {
          if (ftp2 != NULL) {
               prfmsg(PRTTWO,ftp1->code,pdesc(ftp1),ftp2->code,pdesc(ftp2));
               while ((ftp2=ftp2->next) != NULL && !ispvis(ftp2)) {
               }
          }
          else {
               prfmsg(PRTCHC,ftp1->code,pdesc(ftp1));
          }
          while ((ftp1=ftp1->next) != NULL && !ispvis(ftp1)) {
          }
     }
}

VOID
rippmu(VOID)
{
     struct ftfpsp *ftp;
     INT nbut=0,prfsty=1;

     setmbk(ftfmb);
     for (ftp=fthead ; ftp != NULL ; ftp=ftp->next) {
          if (ispvis(ftp)) {
               if (prfsty) {
                    prf("\r!%s%s",bstyle,fstyle);
                    prfsty=0;
               }
               prf("\r!%s<>%s<>[0:]%s",protbuts[nbut++],pdesc(ftp),ftp->code);
          }
     }
     prfmsg(FTGASKB);
     if (nbut == 0) {
          clrprf();
          prfmsg(NOPROTS);
          return;
     }
}

static VOID
setftg(                                 /* set the tag pointer for download */
INT stagn)                   /* starting tag number (0 to ftuptr->numftg-1) */
{
     ASSERT(ftuptr != NULL);
     ASSERT(0 <= stagn && stagn < maxtags);
     ASSERT(stagn <= ftuptr->numftg);
     ftgptr=ftuptr->ftgptr=&ftgusr[stagn];
}

static VOID
stxfer(VOID)                                   /* start a file transfer now */
                                           /* assumes caller calls outprf() */
{
     CHAR xmt,asc;
     INT n;

     xmt=(ftfpsp->flags&FTFXMT) != 0;
     asc=(ftfpsp->flags&FTFASC) != 0;
     if (sameas(ftfpsp->code,"F")) {
          prfmsg(usrptr->substt=(xmt ? FEXPATH : FIRPATH));
          btumil(usrnum,xmt ? TSHLEN-13 : TSHLEN);
          return;
     }
     usrptr->substt=(xmt ? BGNSND : BGNRCV);
     ftuptr->flags&=~FTFBYE;
     if (xmt) {
          if (asc) {
               prfmsg(BGNSAS,ftfpsp->name);
          }
          else if ((n=ftuptr->numftg-(INT)(ftgptr-ftgusr)) > 1) {
               prfmsg(BGNSMANY,ftfpsp->name,n);
          }
          else {
               ftgptr->tshndl(TSHDSC);
               setmbk(ftfmb);
               prfmsg(BGNSND,ftfpsp->name,tshmsg);
          }
     }
     else {
          if (asc) {
               prfmsg(BGNRAS);
          }
          else if (ftuptr->flags&FUPMUL) {
               prfmsg(BGNRMANY,ftfpsp->name);
          }
          else {
               prfmsg(BGNRCV,ftfpsp->name,ftfscb->fname);
          }
     }
     ftuptr->msgbuf[0]='\0';
     ftuptr->stchg=ftftck;
     ftuptr->oubsiz=(xmt ? OUTSIZ-1 : INPSIZ-1);
     usrptr->flags|=NOINJO;
     if (!asc) {
          btutrg(usrnum,OUTSIZ);
     }
     btuinj(usrnum,CYCLE);
}

static VOID
sndprp(VOID)          /* some last minute preparation before sending a file */
{
     CHAR *code;

     code=ftfpsp->code;
     if (sameas(code,"Z")
      || sameas(code,"ZR")) {                /* bps adjust packet size */
          ftfscb->paksiz=( usrptr->baud >= 4800L ? 1024 :
                          (usrptr->baud == 2400L ? 512 : 256));
          if (ftfscb->paksiz*2+ZBOVHEAD >= OUTSIZ) {
               ftfscb->paksiz=512;
          }
          zmxscb->flags|=ZRESUM1;
     }
     else if (sameas(code,"A")) {                   /* non-pause ASCII */
          btuxnf(usrnum,0,19);
          btupbc(usrnum,0);
          btutsw(usrnum,0);
          if (!(ftuptr->flags&ASEARCH)) {
               ascscb->target[0]='\0';
               ascscb->alttrg[0]='\0';
          }
     }
     else if (sameas(code,"L")) {
          if (!(ftuptr->flags&ASEARCH)) {
               ascscb->target[0]='\0';
               ascscb->alttrg[0]='\0';
          }
     }
     else if (sameas(code,"V")) {                  /* View compressed files */
          btutsw(usrnum,0);
     }
     ftuptr->flags&=~ASEARCH;
}

static VOID
rcvprp(VOID)        /* some last minute preparation before receiving a file */
{
     if (sameas(ftfpsp->code,"A")) {
          btumil(usrnum,DFTIMX);
     }
}


/*--- APPLICATION --> FILEXFER:  FTG File Tag Handling ---*/

CHAR *
tagths(               /* return tagspec of othusn/othusp if handler is same */
INT (*tshndl)(INT tshcod))                         /* return NULL otherwise */
{
     struct ftg *ftgp;

     if (othusp->state == ftfstt) {
          switch(othusp->substt) {
          case SNDING:
          case SNDETG:
               ftgp=ftuser[othusn].ftgptr;
               break;
          case SNDWLD:
               ftgp=&ftuser[othusn].ftg;
               break;
          default:
               return(NULL);
          }
          return(ftgp->tshndl == tshndl ? ftgp->tagspc : NULL);
     }
     return(NULL);
}

INT
ftgnew(VOID)                                 /* Issue a new tagspec pointer */
                                  /* output is ftgptr, returns 0 if no room */
                     /* call ftgnew(), fill up ftgptr->, then call ftgsbm() */
                   /* Note: ftgnew() doesn't reserve or increment anything. */
        /* You can call ftgnew() just to find out whether there is any room */
  /* for tagging or downloading.  Calling ftgnew() twice will have the same */
                                              /* effect as calling it once. */
{
     INT n,i;

     setftu();
     if ((n=maxtags-ftuptr->numftg) > 0) {
          i=ftuptr->numftg;
          setmem(&ftgusr[i],sizeof(struct ftg),0);
     }
     else {
          i=maxtags-1;            /* (emergency placement into last tagspec */
     }                            /* in case illegal use of ftgptr follows) */
     ftgptr=&ftgusr[i];
     ftuptr->ftgptr=ftgptr;
     return(n);
}

static VOID
ftgask(                                        /* ask for download protocol */
INT first)
{
     CHAR xflags;

     xflags=(ftgptr->flags&FTGWLD ? FTFXMT+FTFMUL : FTFXMT);
     if (!first) {
          ftgptr->tshndl(TSHDSC);
          setmbk(ftfmb);
          prfmsg(FTGASK,tshmsg);
     }
     usrptr->substt=FTGASK;
     prf("\r");
     prtmnu(xflags,xflags);
     if ((ftgptr->flags&FTGABL) && ftuptr->numftg < maxtags-1) {
          prfmsg(FTGASKT);           /* (don't offer to tag very last slot) */
     }
     if (ftgptr->flags&FTGWLD) {
          prfmsg(FTGASKB);
     }
     prfmsg((pmvalu&FTFXMT) ? FPFOOTD : FPFOOTU);
}

static VOID
tagafl(VOID)                                                  /* Tag a file */
{                                   /* tagspec is already at ftgptr->tagspc */
     if (ftuptr->numftg < maxtags) {
          ftuptr->numftg++;
     }
}

VOID
untag(                             /* notify application of file un-tagging */
INT tagnum)                                /* number, 0 to ftuptr->numtag-1 */
{                                      /* (expects setftu() to be in effect */
     struct ftg *ftgsav;
     HMCVFILE mbksav;

     mbksav=curmbk;
     ftgsav=ftgptr;
     ftgptr=&ftgusr[tagnum];
     ftgptr->tshndl(TSHUNT);
     ftgptr=ftgsav;
     curmbk=mbksav;
}

VOID
numftg(    /* perform untag notification to the extent needed by new numftg */
INT newnum)                                 /* new value for ftuptr->numftg */
{                                      /* (expects setftu() to be in effect */
     INT oldnum;

     for (oldnum=ftuptr->numftg-1 ; oldnum >= newnum ; oldnum--) {
          untag(oldnum);
     }
     ftuptr->numftg=newnum;
}

static VOID
repbrf(VOID)                           /* brief report of tagging of a file */
{
     prfmsg(BRFTAG);
     ftgptr->tshndl(TSHDSC);
     setmbk(ftfmb);
     prfmsg(TSHLST,ftuptr->numftg,tshmsg);
}

static VOID
tshfin(VOID)              /* exit the File Transfer service, back to caller */
{
     setmbk(ftfmb);
     prfmsg(FTFTRL1);
     prfmsg(FTFTRL2);
     usrptr->substt=FTFLOST;                              /* (just in case) */
     if (ftuptr->retrou != NULL) {
          ftuptr->retrou();
     }
     else if (ftuptr->flags&TSHIPG) {
          ftuptr->flags&=~TSHIPG;
          if ((ftuptr->flags&VIATAG) != 0 && ftuptr->numftg > 0) {
               setftg(ftuptr->numftg-1);     /* MULJOIN attempt aborted    */
          }
          if (ftgptr->tshndl != NULL) {
               if (!ftgptr->tshndl(TSHFIN)) {
                    ftuptr->stchg=usrptr->state;
                    usrptr->state=ftfstt;
                    usrptr->substt=SPCXIT;
                    btuinj(usrnum,CRSTG);
               }
          }
     }
     if (ftuptr->savetags > 0 && ftuptr->numftg+ftuptr->savetags <= maxtags) {
          movmem(&ftgusr[maxtags-ftuptr->savetags],&ftgusr[ftuptr->numftg],
             ftuptr->savetags*sizeof(struct ftg));
          ftuptr->numftg+=ftuptr->savetags;
     }
     ftuptr->savetags=0;
     if (usrptr->state == ftfstt && usrptr->substt == FTFLOST) {
          setmbk(ftfmb);
          prfmsg(FTFLOST);
     }
}

static VOID
tshxit(VOID)             /* exit file download/tagging, untag working files */
{
     INT newnum;

     newnum=(INT)(ftuptr->ftgptr-ftgusr);
     if ((ftuptr->flags&VIATAG) != 0
      && ftuptr->retrou == NULL
      && ftuptr->numftg > 0) {
          setftg(ftuptr->numftg-1);          /* MULJOIN attempt aborted    */
     }
     numftg(newnum);
     tshfin();
}

 /* after calling ftgnew() > 0, fill in ftgptr-> fields, then call ftgsbm() */

INT
ftgsbm(                                    /* Submit a tagspec for download */
CHAR *prot)  /* Protocol code: single-file for immediate download:  1 M C V */
                      /* multi-file for immediate download:  L A B G Z ZR K */
                                           /* tag for later download:  T TQ */
                                         /* or ask for download options:  ? */
                                   /* assumes that you just called ftgnew() */
                                           /* assumes caller calls outprf() */
            /* returns 1=usurped state/substate, will call TSHFIN or TSHHUP */
            /* returns 0=state/substate unchanged, or TSHFIN already called */
{
     CHAR xflags;
     INT n;
     INT rc=1;

     setftu();
     ftuptr->savetags=0;
     ftuptr->retrou=tmprou;
     ftuptr->flags&=~(VIATAG+TSHIPG+ASEARCH);
     xflags=(ftgptr->flags&FTGWLD ? FTFXMT+FTFMUL : FTFXMT);
     if (ftuptr->numftg >= maxtags) {
          setmbk(ftfmb);
          prfmsg(FTFDNHDR);
          prfmsg(TAGFULL,maxtags);                          /* (precaution) */
          prfmsg(FTFTRL1);
          prfmsg(FTFTRL2);
          rstmbk();
          rc=0;
     }
     else if (sameas(prot,"T")) {                         /* T = tag a file */
          setmbk(ftfmb);
          prfmsg(FTFDNHDR);
          if (ftgptr->flags&FTGABL) {
               tagafl();
               repbrf();
          }
          else {
               prfmsg(NOWTAG);
          }
          prfmsg(FTFTRL1);
          prfmsg(FTFTRL2);
          rstmbk();
          rc=0;
     }
     else if (sameas(prot,"TQ")) {      /* TQ = "silent tag" (no reporting) */
          if (ftgptr->flags&FTGABL) {
               tagafl();
          }
          rc=0;
     }
     else if (fndprt(prot,xflags,xflags)) {   /* <prot code> = download now */
          setmbk(ftfmb);
          prfmsg(FTFDNHDR);
          n=ftuptr->numftg;
          if (ftgptr->flags&FTGABL) {
               tagafl();
          }
          usrptr->state=ftfstt;
          if (tmprou == NULL) {
               ftuptr->flags|=TSHIPG;
          }
          setftg(n);
          stxfer();
     }
     else {
          setmbk(ftfmb);
          prfmsg(FTFDNHDR);
          if (!sameas(prot,"?")                  /* "?" = ask what protocol */
           && !sameas(prot,"")) {                        /* "" = same thing */
               prfmsg(BADCHC,prot);               /* trash input = admonish */
          }
          usrptr->state=ftfstt;
          if (tmprou == NULL) {
               ftuptr->flags|=TSHIPG;
          }
          if (!(ftgptr->flags&FTGWLD)) {
               if (!ftgptr->tshndl(TSHVIS)) {
                    ftgptr->tshndl(TSHDSC);
                    setmbk(ftfmb);
                    prfmsg(CANTFIND,tshmsg);
                    tshfin();
                    return(0);
               }
               setmbk(ftfmb);
               ftuptr->filid=zafilid(tshmsg);   /* (chk if compressed file) */
          }
          else {
               ftuptr->filid=UNKFILE;
          }
          setmem(ftfscb,sizeof(*ftfscb),0);
          ftgask(1);
     }
     return(rc);
}

INT
ftgdn1(     /* ftgsbm() replacement for copies of files already in tag list */
CHAR *prot,  /* Protocol code: single-file for immediate download:  1 M C V */
VOID (*retrou)())                           /* when done, call this routine */
                      /* multi-file for immediate download:  L A B G Z ZR K */
                                           /* tag for later download:  T TQ */
                                         /* or ask for download options:  ? */
                                   /* assumes that you just called ftgnew() */
                                           /* assumes caller calls outprf() */
            /* returns 1=usurped state/substate, will call TSHFIN or TSHHUP */
            /* returns 0=state/substate unchanged, or TSHFIN already called */
{
     INT retval;

     tmprou=retrou;
     retval=ftgsbm(prot);
     tmprou=NULL;
     return(retval);
}

static INT
tshlst(INT tshcod)        /* Handle tagspecs for your listing an ASCII file */
{                           /* implicit inputs: ftgptr,usrnum,usrptr,usaptr */
                             /* for TSHFIN and TSHHUP, vdaptr is also valid */
                                  /* return value meaning depends on tshcod */
                             /* implicit input/output in many cases: tshmsg */
                                     /* expect caller to do outprf() if any */
     INT rc=0;

     setmbk(ftfmb);
     switch(tshcod) {
     case TSHDSC:                            /* Describe tagspec in English */
          sprintf(tshmsg,"ASCII file %s",lstptr->path);
          break;
     case TSHVIS:                                  /* Visible to this user? */
          rc=1;
          break;
     case TSHBEG:              /* Begin download, check permission, reserve */
          strcpy(tshmsg,lstptr->path);
          rc=1;
          break;
     case TSHFIN:                           /* Finish file transfer session */
          ((WHNDUN)lstptr->whndun)((INT)ftfscb->actfil);
          rc=1;
          break;
     }
     return(rc);
}

VOID
listing(                         /* list an ASCII file to the user's screen */
CHAR *path,             /* path of the file (WARNING: must be non-volatile) */
VOID (*whndun)())              /* restore state & substate, prompt the user */
     /* optional argument to whndun routine: 1=list completed 0=interrupted */
/* IMPORTANT - path must remain valid, e.g. literal, vdaptr, malloc(nterms) */
{
     if (ftgnew() > 0) {
          ftgptr->tshndl=tshlst;
          ftgptr->flags=0;
          lstptr->path=path;
          lstptr->whndun=whndun;
          ftgsbm("L");
     }
     else {
          ftgsbm("");
          ((WHNDUN)whndun)(0);
     }
}

static VOID
lqprompt(VOID)                           /* whndun() routine for listingq() */
{
     setmbk(lstptr->mbk);
     usrptr->state=lstptr->state;
     prfmsg(usrptr->substt=lstptr->msgno);
}

VOID
listingq(           /* E-Z-Quick ASCII file listing, with prompt & substate */
CHAR *path,            /*  path of the file (WARNING: must be non-volatile) */
INT msgno)               /* msg number, under curmbk, for prompt & substate */
/* IMPORTANT - path must remain valid, e.g. literal, vdaptr, malloc(nterms) */
{
     if (ftgnew() > 0) {
          listing(path,lqprompt);
          lstptr->mbk=curmbk;
          lstptr->msgno=msgno;
          lstptr->state=usrptr->state;
     }
     else {
          ftgsbm("");
          prfmsg(usrptr->substt=msgno);
     }
}

INT
valdpc(                               /* Is this a valid download protocol? */
CHAR *prot)                         /* protocol code (may modify & restore) */
{                                                     /* returns 1=yes 0=no */
     setftu();
     return(fndprt(prot,FTFXMT,FTFXMT) || sameas(prot,"T"));
}

static VOID
tpchc(                                     /* list choices for tagged files */
INT untagi)       /* 1=show filespecs & untag instructions 0=just protocols */
{
     if (ftuptr->numftg == 0) {
          prfmsg(TPUNLAST);
          tshfin();
          return;
     }
     if (untagi) {
          ftgsho();
          prfmsg(TAGPRTI);
     }
     else {
          prfmsg(TAGPRT,ftuptr->numftg);
     }
     prtmnu(FTFXMT+FTFMUL,FTFXMT+FTFMUL);
     prfmsg(FTGASKB);
     if (untagi) {
          prfmsg(ftuptr->numftg > 1 ? TPCHCN : TPCHCS,ftuptr->numftg);
          prfmsg(ftuptr->retrou == tcflof ? TPCHCL : TPCHCP);
     }
     else {
          prfmsg(TPCHC,ftuptr->numftg);
     }
     usrptr->substt=TPCHC;
}

VOID
rmvtag(                                          /* handle command to untag */
CHAR *untcmd)                    /* the command, e.g. "-1" "-2" "-3" "-ALL" */
{                                      /* (expects setftu() to be in effect */
     INT n;

     n=atoi(untcmd+1);
     if (sameas(untcmd,"-ALL")) {
          numftg(0);
     }
     else if (isdigit(untcmd[1]) && 1 <= n && n <= ftuptr->numftg) {
          untag(n-1);
          if (n < ftuptr->numftg) {
               movmem(ftgusr+n,ftgusr+n-1,
                      (ftuptr->numftg-n)*sizeof(struct ftg));
          }
          ftuptr->numftg--;
     }
     else {
          prfmsg(BADCHC,untcmd);
     }
     tpchc(1);                       /* untagged 1, gotta show all tags now */
}

VOID
ftgdnl(                       /* download all tagged files (or untag, etc.) */
CHAR *prot,                /* protocol for download (or ""=ask or "?"=list) */
VOID (*retrou)(VOID))             /* what to call when you're done handling */
                                            /* assumes caller does outprf() */
{
     setftu();
     setmbk(ftfmb);
     prfmsg(FTFTGHDR);
     ftuptr->retrou=retrou;
     ftuptr->flags|=VIATAG;
     if (ftuptr->numftg == 0) {
          prfmsg(NOTAGS);
          rstmbk();
          tshfin();
          return;
     }
     usrptr->state=ftfstt;
     setmem(ftfscb,sizeof(*ftfscb),0);
     if (prot[0] == '\0') {
          tpchc(0);
     }
     else if (sameas(prot,"?")) {
          tpchc(1);
     }
     else if (prot[0] == '-') {
          rmvtag(prot);
     }
     else {
          if (fndprt(prot,FTFXMT+FTFMUL,FTFXMT+FTFMUL)) {
               setftg(0);
               stxfer();
          }
          else {
               prfmsg(BADCHC,prot);
               tpchc(0);
          }
     }
}


/*--- APPLICATION --> FILEXFER:  FUP File Upload Handling ---*/

static VOID
fupfin(VOID)              /* exit the File Transfer service, back to caller */
{
     setmbk(ftfmb);
     prfmsg(FTFTRL1);
     prfmsg(FTFTRL2);
     usrptr->substt=FTFLOST;                              /* (just in case) */
     if (ftuptr->flags&FUPIPG) {
          ftuptr->flags&=~FUPIPG;
          if (ftuptr->fuphdl != NULL) {
               if (!ftuptr->fuphdl(FUPFIN)) {
                    ftuptr->stchg=usrptr->state;
                    usrptr->state=ftfstt;
                    usrptr->substt=SPCXIT;
                    btuinj(usrnum,CRSTG);
                    return;
               }
          }
     }
     if (usrptr->state == ftfstt) {
          setmbk(ftfmb);
          prfmsg(FTFLOST);
     }
}

static VOID
fupask(VOID)                                        /* upload protocol menu */
{
     CHAR xflags;

     if (ftuptr->flags&FUPMUL) {
          xflags=FTFMUL;
          prfmsg(FUPASK,"these files");
     }
     else {
          xflags=0;
          prfmsg(FUPASK,
                 ftfscb->fname[0] == '\0' ? "this file" : ftfscb->fname);
     }
     usrptr->substt=FUPASK;
     prtmnu(FTFXMT+xflags,xflags);
     prfmsg(FTGASKB);
     prfmsg(FPFOOTU);
}

VOID
fileup(                                        /* Begin file upload session */
CHAR *fname,      /* proposed "visible" file name, or "" for multiple files */
CHAR *prot,  /* Protocol code, single-file: A 1 M C, multi-file: B G Z ZR K */
INT (*fuphdl)(                      /* application's upload handler routine */
     INT fupcod))               /* upload handler function code (see above) */
       /* if single-file upload, put the file name (or "") in ftfscb->fname */
{
     setftu();
     usrptr->state=ftfstt;
     setmbk(ftfmb);
     if (fname[0] != '\0') {
          ftuptr->flags&=~FUPMUL;
     }
     else {
          ftuptr->flags|=FUPMUL;
     }
     ftuptr->fuphdl=fuphdl;
     ftuptr->flags|=FUPIPG;
     ftuptr->flags&=~FUPAFN;
     setmem(ftfscb,sizeof(*ftfscb),0);
     stzcpy(ftfscb->fname,fname,8+1+3+1);
     prfmsg(FTFUPHDR);
     if (fndprt(prot,FTFXMT,0)
      && (fname[0] != '\0' || (ftfpsp->flags&FTFMUL))) {
          stxfer();
     }
     else {
          if (!sameas(prot,"?") && prot[0] != '\0') {
               prfmsg(BADCHC,prot);               /* trash input = admonish */
          }
          fupask();
     }
     ftuptr->flags&=~FTFREF;           /* assume unless specified otherwise */
}

INT
valupc(                                 /* Is this a valid upload protocol? */
CHAR *prot)                         /* protocol code (may modify & restore) */
{                                                     /* returns 1=yes 0=no */
     setftu();
     return(fndprt(prot,FTFXMT,0));
}


/*--- Worldgroup --> FILEXFER module-structure interface functions ---*/

static VOID
muljoin(VOID)                   /* offer to download tagged & new files now */
{
     prfmsg(usrptr->substt=MULJOIN,ftuptr->numftg,ftfpsp->name);
}

static VOID
rcvchn(VOID)                         /* recover channel to interactive mode */
{
     if (whomon == usrnum) {
          ftscope(FTSEND);
     }
     if (!(ftfpsp->flags&FTFASC)) {
          btutrg(usrnum,0);
          btuclo(usrnum);
          btucli(usrnum);
     }
     usrptr->flags&=~NOINJO;
     if (!(ftfpsp->flags&FTFXMT)) {
          btubsz(usrnum,INPSIZ,OUTSIZ);
     }
     if (sameas(ftfpsp->code,"A")) {                     /* non-pause ASCII */
          rstrxf();
     }
     btutsw(usrnum,usaptr->scnwid);
     btuolk(usrnum,0);
     clrprf();
}

static INT
fx3rdy(                             /* File transfer, X.3 programming done? */
INT msg)                          /* WGSFTF.MSG message ID FXPREF or FXSUFF */
{                          /* WARNING: desroys input[] and prfbuf[] buffers */
     INT n,rc;

     if ((usrptr->flags&ISX25)
      && (n=fmtx3(getmsg(msg))) > 1
      && (rc=btux29(usrnum,n,prfbuf)) != 0) {
          if (rc != X25CLO) {
               btuinj(usrnum,ERRX29);   /* can't transmit, error status 231 */
               return(1);                                  /* (and give up) */
          }
          return(0);                      /* xmit window full, try again... */
     }
     return(1);                                  /* done (or don't need to) */
}

static VOID
hdlina(                                               /* handle ASCII input */
CHAR *line)                                   /* line of input (CR implied) */
{
     VOID (*hisptr)(CHAR *);

     if (whomon == usrnum) {
          ftscope(FTSINS,line,strlen(line));
          ftscope(FTSINS,"\r",1);
     }
     ftfscb->tckbyt=ftftck;
     ftfscb->tckact=ftftck;
     if ((hisptr=ftfpsp->hdlins) == NULL) {
          ftfabt("Operator abort.");
     }
     else if (sameas(line,"X")) {
          ftfabt("Operator entered \"X\".");
     }
     else if (usrptr->flags&ABOIP) {
          (*hisptr)(NULL);
     }
     else {
          (*hisptr)(line);
     }
}

static INT
hdlinb(VOID)                                         /* handle binary input */
{                              /* returns 1=handled some 0=nothing received */
     INT inbyts;

     if ((inbyts=btuica(usrnum,prfbuf,PFBSIZ)) > 0) {
          if (whomon == usrnum) {
               ftscope(FTSINS,prfbuf,inbyts);
          }
          ftfinb(prfbuf,inbyts);
          ftfscb->tckbyt=ftftck;
          ftfscb->tckact=ftftck;
          return(1);
     }
     return(0);
}

static VOID
fiecls(VOID)                                   /* close file in/export file */
{
     if (ftuptr->fiep != NULL) {
          fclose(ftuptr->fiep);
          ftuptr->fiep=NULL;
     }
}

static VOID
fupref(VOID)                               /* file import by reference ok'd */
{
     stzcpy(ftfbuf,ftuptr->msgbuf,TSHLEN+1);
     ftuptr->fuphdl(FUPREF);
     setmbk(ftfmb);
     prfmsg(usrptr->substt=FIRREF,ftfbuf);
     ftfrcl(1);
     ftfscb->isopen=0;
     ftfscb->actfil++;
     btuinj(usrnum,CYCLE);
}

VOID
firabt(VOID)                                           /* abort file import */
{
     if (ftfscb->isopen) {
          ftfrcl(0);
          ftfscb->isopen=0;
     }
     fiecls();
     if (!(ftfpsp->flags&FTFAFN)) {
          prfmsg(FIRAGN);
          usrptr->substt=RRETRY;
     }
     else {
          fupfin();
     }
}

static VOID
sretry(VOID)                                   /* display send-retry prompt */
{
     ftgptr->tshndl(TSHDSC);
     setmbk(ftfmb);
     prfmsg(usrptr->substt,tshmsg);
}

static GBOOL
ftfinp(VOID)                       /* FTF "get protocol" status 3 handler  */
{
     CHAR *cp,*fnp;
     INT i,n;
     GBOOL rc=TRUE;
     CHAR xflags;

     setftu();
     setmbk(ftfmb);
     do {
          bgncnc();
          switch(usrptr->substt) {
          case SPCXIT:
               usrptr->state=ftuptr->stchg;
          case 0:
               rc=FALSE;
               cncall();
               break;
          case BGNRCV:
          case BGNSND:
               btuclo(usrnum);
               cncall();
               break;
          case FTGASK:
               cp=cncwrd();
               if (usrptr->flags&INJOIP) {
                    prfmsg((pmvalu&FTFXMT) ? FPFOOTD : FPFOOTU);
               }
               else if (sameas(cp,"?") || *cp == '\0') {
                    cncall();
                    prfmsg(FTGASKH);
                    if (ftuptr->filid != UNKFILE) {
                         prfmsg(FTGASKVH,arcdsc[(SHORT)ftuptr->filid]);
                    }
                    if (ftgptr->flags&FTGABL) {
                         prfmsg(FTGASKTH);
                    }
                    ftgask(0);
               }
               else if (sameas(cp,"X")) {
                    prf("");
                    tshfin();
               }
               else if (sameas(cp,"T")) {
                    cncall();
                    if (ftgptr->flags&FTGABL) {
                         tagafl();
                         repbrf();
                         if (ftuptr->numftg >= maxtags) {
                              prfmsg(usrptr->substt=LASTTAG);
                         }
                         else {
                              prfmsg(TAGWDO);
                              tshfin();
                         }
                    }
                    else {
                         prfmsg(NOWTAG);
                         ftgask(0);
                    }
               }
               else {
                    xflags=(ftgptr->flags&FTGWLD ? FTFXMT+FTFMUL : FTFXMT);
                    if (fndprt(cp,xflags,xflags)) {
                         if ((ftfpsp->flags&FTFMUL)
                          && ftuptr->numftg > 0
                          && ftgptr->flags&FTGABL) {
                              muljoin();
                         }
                         else {
                              cncall();
                              n=ftuptr->numftg;
                              if (ftgptr->flags&FTGABL) {
                                   tagafl();
                              }
                              setftg(n);
                              stxfer();
                         }
                    }
                    else {
                         cncall();
                         prfmsg(BADCHC,cp);
                         ftgask(0);
                    }
               }
               break;
          case LASTTAG:
               switch (cncyesno()) {
               case 'Y':
                    tpchc(0);
                    break;
               case '\0':
                    if (usrptr->flags&INJOIP) {
                         prfmsg(LASTTAG);
                         break;
                    }
               case 'N':
               case 'X':
                    cncall();
                    prfmsg(FULLWRN);
                    tshfin();
                    break;
               default:
                    cncall();
                    ftgsho();
                    prfmsg(LASTTAG);
                    break;
               }
               break;
          case MULJOIN:
               switch (cncyesno()) {
               case 'Y':
                    ftuptr->flags|=VIATAG;
                    tagafl();
                    setftg(0);
                    stxfer();
                    break;
               case '\0':
                    if (usrptr->flags&INJOIP) {
                         muljoin();
                         break;
                    }
               case 'N':
                    prfmsg(MJNO);
                    n=ftuptr->numftg;
                    tagafl();
                    setftg(n);
                    stxfer();
                    break;
               case 'X':
                    prf("");
                    tshfin();
                    break;
               default:
                    ftgptr->tshndl(TSHDSC);
                    setmbk(ftfmb);
                    prfmsg(MJBARF,tshmsg);
                    muljoin();
                    break;
               }
               cncall();
               break;
          case TPCHC:
               cp=cncall();
               if (usrptr->flags&INJOIP) {
                    prfmsg(TPCHCA);
                    break;
               }
               if (cp[0] == '-') {
                    rmvtag(cp);
               }
               else if (sameas(cp,"X")) {
                    prf("");
                    tshfin();
               }
               else if (*cp == '\0' || sameas(cp,"?")) {
                    tpchc(1);
               }
               else {
                    if (ftuptr->retrou == tcflof && sameas(cp,"R")) {
                         rc=-1;
                    }
                    else if (fndprt(cp,FTFXMT+FTFMUL,FTFXMT+FTFMUL)) {
                         setftg(0);
                         stxfer();
                    }
                    else {
                         prfmsg(BADCHC,cp);
                         tpchc(1);
                    }
               }
               break;
          case FUPASK:
               cp=cncall();
               if (usrptr->flags&INJOIP) {
                    prfmsg(FPFOOTU);
               }
               else if (sameas(cp,"?") || *cp == '\0') {
                    prfmsg(FUPASKH);
                    fupask();
               }
               else if (sameas(cp,"X")) {
                    prf("");
                    fupfin();
               }
               else {
                    xflags=(ftuptr->flags&FUPMUL ? FTFMUL : 0);
                    if (fndprt(cp,FTFXMT+xflags,xflags)) {
                         stxfer();
                    }
                    else {
                         prfmsg(BADCHC,cp);
                         fupask();
                    }
               }
               break;
          case RCVING:
          case SNDING:
          case SNDWLD:
          case SNDETG:
               prf("");
               hdlina(input);
               cncall();
               break;
          case SRETRY:
          case SRETRYV:
               switch(cncyesno()) {
               case '\0':
                    if (usrptr->flags&INJOIP) {
                         sretry();
                         break;
                    }
               case 'Y':
                    if (ftuptr->flags&VIATAG) {
                         tpchc(0);
                    }
                    else {
                         numftg((INT)(ftuptr->ftgptr-ftgusr));
                         ftgask(0);
                    }
                    break;
               case 'N':
               case 'X':
                    cncall();
                    tshxit();
                    break;
               default:
                    cncall();
                    sretry();
                    break;
               }
               break;
          case RRETRY:
               switch(cncyesno()) {
               case '\0':
                    if (usrptr->flags&INJOIP) {
                         prfmsg(RRETRY);
                         break;
                    }
               case 'Y':
                    fupask();
                    break;
               case 'N':
               case 'X':
                    cncall();
                    fupfin();
                    break;
               default:
                    cncall();
                    prfmsg(RRETRY);
                    break;
               }
               break;
          case BANGOFF2:
               cncall();
               if (!(usrptr->flags&INJOIP)) {
                    prfmsg(BANGAVT);
                    echon();
                    ftfpsp->flags&FTFXMT ? tshfin() : fupfin();
               }
               break;
          case FIRPATH:
               cp=cncall();
               if (cp[0] == '\0') {
                    prfmsg(FIRPATH);
                    break;
               }
               if (sameas(cp,"?")) {
                    prfmsg(FIRHELP);
                    break;
               }
               if (sameas(cp,"X")) {
                    firabt();
                    break;
               }
               if ((ftuptr->fiep=fopen(cp,FOPRB)) == NULL) {
                    prfmsg(FIRNOT);
                    break;
               }
               n=strlen(cp);
               for (fnp=cp+n,i=0 ; n > 0 && i <= 12 ; fnp--,n--,i++) {
                    if (isvalds(fnp[-1])) {
                         break;
                    }
               }
               setmem(ftfscb,sizeof(*ftfscb),0);
               strcpy(ftfscb->fname,fnp);
               ftfscb->estbyt=filelength(fileno(ftuptr->fiep));
               ftfscb->unxtim=getFileGMT(cp);
               if (ftfrop(0,0,0) != 0) {
                    prfmsg(FIRBOMB,ftfscb->abwhy);
                    fiecls();
                    fupfin();
                    break;
               }
               ftfsrt();
               stzcpy(ftuptr->msgbuf,cp,TSHLEN+1);
               if (!(ftuptr->flags&FTFREF)) {
                    prfmsg(usrptr->substt=FIRCPY,ftfbuf);
                    btuinj(usrnum,CYCLE);
               }
               else if (strchr(cp,':') == NULL) {
                    fupref();
               }
               else {
                    prfmsg(usrptr->substt=FIRASK,ftfbuf,cp,ftfbuf,cp);
               }
               break;
          case FIRASK:
               switch(cncyesno()) {
               case 'X':
                    firabt();
                    break;
               case 'Y':
                    prfmsg(FIRECPY);
                    usrptr->substt=FIRCPY;
                    btuinj(usrnum,CYCLE);
                    break;
               case 'N':
                    fupref();
                    break;
               default:
                    prfmsg(FIREASK);
                    break;
               }
               break;
          case FIRCPY:
               cncall();
               if (!(usrptr->flags&INJOIP)) {
                    prfmsg(FIRCEX);
                    firabt();
               }
               break;
          case FEXPATH:
               if (usrptr->flags&INJOIP) {
                    prfmsg(FEXPATH);
                    break;
               }
               if ((n=strlen(cp=cncall())) > TSHLEN-13) {
                    prfmsg(FEXLONG,n-TSHLEN+13);
                    break;
               }
               if (n == 0 || sameas(cp,"?")) {
                    prfmsg(FEXHELP);
                    break;
               }
               if (sameas(cp,"X")) {
                    tshxit();
                    break;
               }
               strcpy(fexscb->path,cp);
               cp=fexscb->path;
               setmem(ftfscb,sizeof(*ftfscb),0);
               if (tshbeg(1) == 1 && opnbeg()) {
                    ftfsrt();
                    if ((ftuptr->fiep=fopen(cp,FOPWB)) == NULL) {
                         if (n > 0 && !isvalds(cp[n-1])) {
                              strcpy(cp+n++,SLS);
                         }
                         strcpy(cp+n,ftfscb->fname);
                         if ((ftuptr->fiep=fopen(cp,FOPWB)) == NULL) {
                              prfmsg(FEXEROP,cp);
                              ftfxcl(0);
                              ftfscb->isopen=0;
                              tshxit();
                              break;
                         }
                    }
                    prfmsg(usrptr->substt=FEXING,ftfscb->fname,cp);
                    btuinj(usrnum,CYCLE);
               }
               else {
                    prf("\r%s\r",ftuptr->msgbuf);
                    tshxit();
               }
               break;
          case FEXING:
               cncall();
               if (!(usrptr->flags&INJOIP)) {
                    fiecls();
                    ftfxcl(0);
                    ftfscb->isopen=0;
                    prfmsg(FEXCEX,fexscb->path);
                    unlink(fexscb->path);
                    tshxit();
               }
               break;
          case FTFLOST:
               cncall();
               prfmsg(FTFLOST);
               rc=FALSE;
               break;
          default:
               cncall();
               break;
          }
     } while (!endcnc());
     outprf(usrnum);
     return(rc);
}

static VOID
tcflof(VOID)         /* return to FTF logoff after downloading tagged files */
{
     usrptr->state=ftfstt;
     usrptr->substt=0;
     setftu();
     numftg(0);
     btuinj(usrnum,CRSTG);
}

static GBOOL
ftflof(VOID)                           /* File Transfer log-off entry point */
{
     switch (usrptr->substt) {
     case 0:
          setftu();
          setmbk(ftfmb);
          if (ftuptr->numftg > 0) {
               prfmsg(LOFICP);
               ftgdnl("?",tcflof);
               outprf(usrnum);
               return(TRUE);
          }
          return(FALSE);
     default:
          return(ftfinp());
     }
}

static INT
tageat(VOID)                            /* tag fully processed, toss it out */
                  /* returns 0=no more available 1=tossed, but there's more */
{
     INT n;

     if ((n=ftuptr->numftg) <= 0 || (n-=(INT)(ftuptr->ftgptr-ftgusr)) <= 0) {
          return(0);
     }
     untag((INT)(ftuptr->ftgptr-ftgusr));
     ftuptr->numftg--;                           /* current tagspec deleted */
     n--;
     if (n <= 0) {
          return(0);                                /* last tagspec deleted */
     }
     movmem(ftuptr->ftgptr+1,ftuptr->ftgptr,n*sizeof(struct ftg));
     return(1);
}

static VOID
xfrctn(VOID)                                     /* transferring continues? */
{

     if (usrptr->flags&BYEBYE && !(ftuptr->flags&FTFBYE)) {
          ftfpsp->term("system shutdown");
          ftuptr->flags|=FTFBYE;
     }
     switch (ftfpsp->contin()) {
     case 1:
          if (ftftck-ftfscb->tckact > ((ftfpsp->flags&FTFASC) ? asczap
                                                              : binzap)
           && ftfscb->state >= 0) {
               ftfabt("inactivity timeout");
          }
          break;
     case 0:
          if (usrptr->substt == SNDETG) {
               tageat();          /* useful at end of single-file downloads */
          }
          usrptr->substt=XFRDON;
          rcvchn();
          break;
     case -1:
          usrptr->substt=XFRABT;
          rcvchn();
          break;
     }
}

static VOID
xfrept(                                          /* report on file transfer */
CHAR *way)                                                /* "UP" or "DOWN" */
{
     if (ftfscb->actfil == ftfscb->tryfil) {
          if (ftfpsp->flags&FTFASC) {
               prf("");
          }
          else {
               prfmsg(ftfscb->actfil == 0 ? ZERXFR : FINXFR,way);
          }
     }
     else {
          if (ftfscb->actfil == 0) {
               prfmsg(ZERXFR,way);
          }
          else {
               prfmsg(SHTXFR,way,ftfscb->actfil,ftfscb->tryfil);
          }
          if (ftuptr->msgbuf[0] != '\0') {
               if (ftfscb->actfil >= ftfscb->tryfil-1) {
                    prf("\r%s\r",ftuptr->msgbuf);
               }
               else {
                    prfmsg(LSTWHY,ftuptr->msgbuf);
               }
          }
     }
}

static INT
banging(VOID)         /* check to see if this is a transfer with a "!" bang */
{
     if (ftuptr->flags&FTBANG) {
          prf(getasc(alwait == 0 ? BANGFAST : BANGOFF2),alwait);
          usrptr->substt=BANGOFF2;
          ftuptr->stchg=ftftck;
          btuech(usrnum,0);
          powprf();
          return(1);
     }
     return(0);
}

static VOID
ftfsth(VOID)                       /* FTF status (other than 3) handler    */
{
     CHAR more;
     INT n;
     static INT indfs=0;   /* avoids infinite loop via dfsthn-loscar-stsrou */
     INT rc;

     if (indfs) {
          ftfhup();
          ftfzer();
          rstchn();
          return;
     }
     if (status != CYCLE) {
          indfs=1;
          dfsthn();
          indfs=0;
          return;
     }
     setftu();
     setmbk(ftfmb);
     more=1;
     switch (usrptr->substt) {
        case BGNRCV:
          if (btuoba(usrnum) == OUTSIZ-1
              || ftftck-ftuptr->stchg > OBATMO) {
               ftuptr->stchg=ftftck;
               usrptr->substt=BRWAIT;
          }
          break;
        case BRWAIT:
          if (ftftck-ftuptr->stchg > BRWTMO) {
               btubsz(usrnum,OUTSIZ,INPSIZ);
               usrptr->substt=ftfpsp->flags&FTFASC ? SRTRCV : X3PRCV;
               ftuptr->stchg=ftftck;
          }
          break;
        case BGNSND:
          if (btuoba(usrnum) == OUTSIZ-1
              || ftftck-ftuptr->stchg > OBATMO) {
               usrptr->substt=ftfpsp->flags&FTFASC ? SRTSND : X3PSND;
               ftuptr->stchg=ftftck;
          }
          break;
        case X3PRCV:
          if (fx3rdy(FXPREF) || ftftck-ftuptr->stchg > FX3TMO) {
               usrptr->substt=SRTRCV;
          }
          break;
        case X3PSND:
          if (fx3rdy(FXPREF) || ftftck-ftuptr->stchg > FX3TMO) {
               usrptr->substt=SRTSND;
          }
          break;
        case SRTRCV:
          if (whomon == usrnum) {
               ftscope(FTSSRT);
          }
          usrptr->substt=RCVING;
          ftfpsp->start();
          if ((usrptr->flags&ISX25) != 0 && (ftfpsp == &ftpxmr
                                             || ftfpsp == &ftpxcr
                                             || ftfpsp == &ftpx1r
                                             || ftfpsp == &ftpybr
                                             || ftfpsp == &ftpygr)) {
               xymscb->toxtra=x25tmo*16;
          }
          ftfscb->tckact=ftftck;
          rcvprp();
          break;
        case SRTSND:
          if (whomon == usrnum) {
               ftscope(FTSSRT);
          }
          usrptr->substt=SNDING;
          ftfpsp->start();
          ftfscb->tckact=ftftck;
          sndprp();
          break;
        case RCVING:
        case SNDING:
        case SNDWLD:
        case SNDETG:
          if (ftfpsp->flags&FTFASC) {
               clrprf();
               xfrctn();
               if (prfbuf[0] != '\0') {
                    outprf(usrnum);
               }
          }
          else {
               if (!hdlinb()) {
                    xfrctn();
               }
          }
          break;
        case XFRDON:
          if (fx3rdy(FXSUFF)) {
               clrprf();
               if (ftfpsp->flags&FTFXMT) {
                    if (sameas(ftfpsp->code,"V")) {
                         prfmsg(VEWAGN1,
                                arcdsc[(SHORT)(ftuptr->filid=vewscb->filid)]);
                         usrptr->substt=SRETRYV;
                         more=0;
                    }
                    else {
                         xfrept("DOWN");
                         if (!banging()) {
                              tshfin();
                              more=0;
                         }
                    }
               }
               else {
                    xfrept("UP");
                    if (!banging()) {
                         fupfin();
                         more=0;
                    }
               }
               outprf(usrnum);
          }
          break;
        case BANGOFF2:
          if (btuoba(usrnum) == OUTSIZ-1 && ftftck-ftuptr->stchg > alwait*16) {
               btuinj(usrnum,RING);
               usrptr->substt=BANGNOW;
               if (alwait != 0) {
                    prfmsg(BANGNOW);
               }
               if (ftuptr->flags&TSHIPG) {
                    prf("\r");
                    tshfin();
               }
               if (ftuptr->flags&FUPIPG) {
                    prf("\r");
                    fupfin();
               }
               outprf(usrnum);
               more=0;
          }
          break;
        case BANGNOW:
          break;
        case XFRABT:
          if (fx3rdy(FXSUFF)) {
               usrptr->substt=XFRARP;
               ftuptr->stchg=ftftck;
          }
          break;
        case XFRARP:
          if (!isripu() || ftftck-ftuptr->stchg > ripadl) {
               clrprf();
               if (ftfpsp->flags&FTFXMT) {
                    if (sameas(ftfpsp->code,"V")) {
                         if (ftfscb->abwhy[0] != '\0') {
                              prfmsg(ABTASC,ftfscb->abwhy);
                         }
                         prfmsg(VEWAGN1,arcdsc[(SHORT)(ftuptr->filid=vewscb->filid)]);
                         usrptr->substt=SRETRYV;
                    }
                    else if (!(ftfpsp->flags&FTFAFN)) {
                         ftgptr->tshndl(TSHDSC);
                         setmbk(ftfmb);
                         prfmsg(ABTSND,ftfscb->abwhy,tshmsg);
                         usrptr->substt=SRETRY;
                    }
                    else if (ftfscb->abwhy[0] != '\0') {
                         prfmsg(ABTASC,ftfscb->abwhy);
                         tshxit();         /* chuck aborted ASCII downloads */
                    }
                    else {
                         prf("\r");
                         tshxit();         /* chuck aborted ASCII downloads */
                    }
               }
               else {
                    if (!(ftfpsp->flags&FTFAFN)
                     && !(ftuptr->flags&FUPAFN)) {
                         prfmsg(ftfscb->fname[0] != '\0' ? ABTRCVF : ABTRCV,
                                ftfscb->abwhy,ftfscb->fname);
                         usrptr->substt=RRETRY;
                         ftfscb->fname[0]='\0';
                    }
                    else {
                         prfmsg(ABTASC,ftfscb->abwhy);
                         ftfscb->fname[0]='\0';
                         fupfin();
                    }
               }
               outprf(usrnum);
               more=0;
          }
          break;
        case FIRCPY:
          n=fread(ftfbuf,1,FIECHUNK,ftuptr->fiep);
          if (n < 0) {
               prfmsg(FIRDOSR,errno);
               firabt();
               outprf(usrnum);
               break;
          }
          if (n > 0) {
               if ((rc=ftfrwr(ftfbuf,n)) != n) {
                    prfmsg(rc < 0 ? FIRDOSW : FIRFULL,errno);
               }
               ftfscb->actbyt+=n;
               prf(".");
          }
          if (n < FIECHUNK) {
               usrptr->substt=FIRREF;
               ftfrcl(1);
               ftfscb->isopen=0;
               ftfscb->actfil++;
               prf("\r");
          }
          outprf(usrnum);
          break;
     case FIRREF:
          fiecls();
          fupfin();
          outprf(usrnum);
          more=0;
          break;
        case FEXING:
          n=ftfxrd(ftfbuf,FIECHUNK);
          if (n < 0) {
               prfmsg(FEXDOSR,errno);
               fiecls();
               ftfxcl(0);
               ftfscb->isopen=0;
               unlink(fexscb->path);
               tshxit();
               more=0;
               outprf(usrnum);
               break;
          }
          if (n > 0) {
               if ((rc=fwrite(ftfbuf,1,n,ftuptr->fiep)) != n) {
                    prfmsg(rc < 0 ? FEXDOSW : FEXFULL,errno);
                    fiecls();
                    ftfxcl(0);
                    ftfscb->isopen=0;
                    unlink(fexscb->path);
                    tshxit();
                    more=0;
                    outprf(usrnum);
                    break;
               }
               ftfscb->actbyt+=n;
               prf(".");
          }
          if (n < FIECHUNK) {
               fiecls();
               ftfxcl(1);
               ftfscb->isopen=0;
               ftfscb->actfil++;
               setFileTm(fexscb->path,ftfscb->dostim,ftfscb->dosdat);
               prf("\r");
               more=0;
               tshxit();
          }
          outprf(usrnum);
          break;
        default:
          indfs=1;
          dfsthn();
          indfs=0;
          more=0;
     }
     if (more) {
          btuinj(usrnum,CYCLE);
     }
}

static VOID
ftfhup(VOID)                       /* FTF lost carrier routine             */
{
     setftu();
     if (usrptr->state == ftfstt) {
          setmbk(ftfmb);
          switch(usrptr->substt) {
          case BGNSND:
          case X3PSND:
          case SNDING:
          case SNDWLD:
          case SNDETG:
          case BGNRCV:
          case X3PRCV:
          case RCVING:
               ftfpsp->abort();
               rcvchn();
               break;
          case FIRCPY:
          case FIRASK:
               ftfrca();
               break;
          case FEXING:
               ftfxca();
               break;
          }
          if (ftuptr->flags&TSHIPG && ftgptr->tshndl != NULL) {
               ftgptr->tshndl(TSHHUP);
          }
          if (ftuptr->flags&FUPIPG && ftuptr->fuphdl != NULL) {
               ftuptr->fuphdl(FUPHUP);
          }
          ftuptr->flags&=~(TSHIPG+FUPIPG);
     }
     fiecls();
}

VOID
ftfzer(VOID)                  /* zero out FTF user structure (numftg, etc.) */
{                                                   /* (usrnum is implicit) */
     setftu();
     setmem(ftuptr,sizeof(struct ftuser),0);
}

static VOID
ftfcls(VOID)                       /* FTF system shutdown routine          */
{
     if (ftfmb != NULL) {
          clsmsg(ftfmb);
          ftfmb=NULL;
     }
}

/*--- FILEXFER <-- FTF exit point functions ---*/

VOID
ftfout(           /* Transmit binary bytes (used during non-ASCII sessions) */
CHAR *data,
INT nbytes)
{
     if (whomon == usrnum) {
          ftscope(FTSOUS,data,nbytes);
     }
     if (nbytes > 0) {
          btuxct(usrnum,nbytes,data);
     }
}

VOID
ftfous(               /* Transmit ASCII string (used during ASCII sessions) */
CHAR *string)                                      /* NUL-terminated string */
{
     if (whomon == usrnum) {
          ftscope(FTSOUS,string,strlen(string));
     }
     btuxmt(usrnum,string);
}

INT
ftfoba(VOID)                    /* Output buffer bytes available, how many? */
{
     return(btuoba(usrnum));
}

VOID
ftfcli(VOID)                                          /* Clear input buffer */
{
     btucli(usrnum);
     ftfcib();
}

VOID
ftfclo(VOID)                                         /* Clear output buffer */
{
     btuclo(usrnum);
}

static VOID
ftferr(                 /* record a (usu recoverable) error in xfer session */
CHAR *errmsg,            /* (must limit output length to TSHLEN characters) */
...)
{
     va_list ap;
     va_start(ap,errmsg);
     vsprintf(ftuptr->msgbuf,errmsg,ap);
     va_end(ap);
}

static VOID
cntmis(VOID)                                          /* count missing file */
{
     ftfscb->tryfil++;
     ftgptr->tshndl(TSHDSC);
     setmbk(ftfmb);
     ftferr("Cannot find the %0.64s.",tshmsg);
}

static INT
tshbeg(                   /* begin transfer of an individual tagspec'd file */
INT repvis)                   /* 1=report invisibility 0=don't give it away */
{                 /* returns 1=had permission, 2=didn't, -1=not ready "yet" */
     if (!ftgptr->tshndl(TSHVIS)) {
          setmbk(ftfmb);
          if (repvis) {
               cntmis();
          }
          return(2);                            /* file not visible -- shhh */
     }
     switch (ftgptr->tshndl(TSHBEG)) {
     case 0:
          setmbk(ftfmb);
          ftfscb->tryfil++;
          ftferr("%0.80s",tshmsg);
          return(2);             /* file visible but not accessible -- why? */
     case -1:
          setmbk(ftfmb);
          ftfscb->tryfil++;
          ftferr("%0.80s",tshmsg);
          return(-1);           /* file visible but not ready "yet" -- why? */
     }
     setmbk(ftfmb);
     return(1);                 /* file visible and accessible -- here goes */
}

static INT
tshsub(          /* handle next tagspec in tshmsg derived from wild tagspec */
INT sncode)                                             /* TSHSCN or TSHNXT */
{               /* returns 1=begun, 2=skip & call again, -1=not ready "yet" */
     struct ftg *ftgp;
     INT sc;

     sc=ftgptr->tshndl(sncode);
     setmbk(ftfmb);
     switch (sc) {
     case 1:                  /* here's a sub-tagspec, now stored in tshmsg */
          ftgp=&ftuptr->ftg;
          movmem(tshmsg,ftgp->tagspc,TSLENG);
          ftgp->tshndl=ftgptr->tshndl;
          ftgp->flags=ftgptr->flags&~FTGWLD;
          usrptr->substt=SNDWLD;
          ftgptr=ftgp;
          return(tshbeg(0));
     case 2:                              /* no sub-tagspec here, try again */
          usrptr->substt=SNDWLD;
          return(2);
     case 0:           /* no more sub-tagspecs, will delete current tagspec */
          ftgptr=ftuptr->ftgptr;
          usrptr->substt=SNDETG;
          return(2);
     }
     return(0);
}

static INT
gimafl(VOID)                              /* Gimme another file to transmit */
/* return codes: 1=found 2=not found but there's still hope 0=no more files */
                                /* or -1=not ready "yet" but may be someday */
          /* if returns 1, tshmsg contains:  protocol name\0DOS path name\0 */
{
     switch (usrptr->substt) {
     case SNDETG:
          usrptr->substt=SNDING;
          if (!tageat()) {                       /* delete previous tagspec */
               return(0);
          }
     case SNDING:
          if (ftgptr->flags&FTGWLD) {             /* break down multi-file  */
               return(tshsub(TSHSCN));         /* tagspec into sub-tagspecs */
          }
          usrptr->substt=SNDETG;
          return(tshbeg(1));              /* begin this single-file tagspec */
     case SNDWLD:
          return(tshsub(TSHNXT));         /* continue handling sub-tagspecs */
     }
     return(0);
}

static INT
opnbeg(VOID)                               /* open tagged file for download */
{
     ftuptr->fp=fopen(tshmsg,ftfpsp->flags&FTFASF ? FOPRA : FOPRB);
     if (ftuptr->fp != NULL) {
          ftfscb->estbyt=filelength(fileno(ftuptr->fp));
          ftfscb->unxtim=getFileGMT(tshmsg);
          getFileTm(tshmsg,&ftfscb->dostim,&ftfscb->dosdat);
          if (ftfpsp->flags&FTFASC) {
               prfmsg(ASCHDR,ftfscb->fname);
               outprf(usrnum);
               clrprf();
          }
          return(1);
     }
     cntmis();
     ftgptr->tshndl(TSHSKP);
     setmbk(ftfmb);
     return(0);
}

INT
ftfxop(VOID)                      /* Open file for reading and transmitting */
       /* optional implicit outputs: fname,dosdat,dostim,unxtim,estbyt flds */
                                  /* returns 0=ok & opened, 1=no more files */
      /* ftfxcl() will be called exactly once for each ftfxop() returning 0 */
{
     static struct ftg ftgsave;      /* temp storage for savetags */

     while (1) {
          switch(gimafl()) {
          case -1:
               movmem(ftuptr->ftgptr,&ftgsave,sizeof(struct ftg));
               if (tageat()) {
                    usrptr->substt=SNDING;
               }
               movmem(&ftgsave,&ftgusr[maxtags-++ftuptr->savetags],
                  sizeof(struct ftg));
               break;
          case 0:
               return(1);               /* no more tagged files to download */
          case 1:
               if (opnbeg()) {
                    return(0);
               }
               break;
          }
     }
}

VOID
ftfxsk(                     /* Seek a position in the file for transmission */
LONG pos)
{
     if (fseek(ftuptr->fp,pos,0) != 0) {
          ftfabt("File seek error.");
     }
}

INT
ftfxrd(                            /* Read bytes from file for transmitting */
CHAR *datbuf,                                              /* put them here */
INT nbytes)                                  /* try to read up to this many */
                                /* returns how many actually read, or 0=EOF */
               /* will usually return == nbytes, will never return > nbytes */
                           /* if 0 < return value < nbytes, EOF is imminent */
       /* after returning 0, must always return 0 until ftfxcl() & ftfxop() */
{
     return(fread(datbuf,1,nbytes,ftuptr->fp));
}

INT
ftfxrl(                  /* Read a line from an ASCII file for transmitting */
CHAR *datbuf,              /* where to put the bytes and the NUL terminator */
INT nbytes)                                  /* room for line, incl the NUL */
     /* returns how many actually read, not incl NUL, or 0=EOF, or -1=ERROR */
{
     if (fgets(datbuf,nbytes,ftuptr->fp) == NULL) {
          if (ferror(ftuptr->fp)) {
               return(-1);
          }
          return(0);
     }
     else {
          return(strlen(datbuf));
     }
}

INT
ftfxrb(VOID)                      /* Read 1 byte from file for transmitting */
                       /* returns EOF if no more in file, or character read */
   /* after returning EOF, must always return EOF until ftfxcl() & ftfxop() */
{
     return(getc(ftuptr->fp));
}

VOID
ftfxlk(                                                      /* lock output */
INT on)                        /* 1=locked (paused) 0=unlocked (proceeding) */
{
     btuolk(usrnum,on);
}

VOID
ftfxsq(                     /* report that a file will be gracfully skipped */
CHAR *reason)         /* reason skipped (must copy, if not used right away) */
{
     ftferr("%0.61s, file %s",reason,ftfscb->fname);
}

VOID
ftfxcl(                                    /* Close file after transmitting */
INT ok)                             /* 1=transmitted in entirety, 0=aborted */
         /* implicit inputs: fname,dat&tim fields,estbyt,actbyt from ftfscb */
{
     fclose(ftuptr->fp);
     ftgptr->tshndl(ok ? TSHEND : TSHSKP);
     setmbk(ftfmb);
}

INT
ftfrex(VOID)                 /* check if file to be received exists already */
       /* returns 0=doesn't exist, or we don't know/care & can overwrite it */
  /* returns 1=file exists, overwrite it unless protocol options say not to */
           /* returns 2=file fragment exists, resume it if protocol capable */
                 /* if returns non-zero, also must set these ftfscb fields: */
                  /* unxtim,dosdat,dostim,estbyt (to reflect existing file) */
                                           /* implicit input: ftfscb->fname */
                   /* ok to change name in ftfscb->fname to aVOID collision */
        /* ZMODEM will use time & size fields to handle skip/noskip options */
{
     INT rc;

     rc=ftuptr->fuphdl(FUPPTH);
     if (rc == 0 || (ftuptr->fp=fopen(ftfbuf,FOPRB)) == NULL) {
          return(0);
     }
     ftfscb->estbyt=filelength(fileno(ftuptr->fp));
     ftfscb->unxtim=getFileGMT(ftfbuf);
     getFileTm(ftfbuf,&ftfscb->dostim,&ftfscb->dosdat);
     fclose(ftuptr->fp);
     return(rc);
}

INT
ftfrop(                              /* Open file for reception and writing */
INT append,                             /* if exists, 1=append, 0=overwrite */
INT ascii,                                             /* 1=ascii, 0=binary */
INT resume)                                     /* 1=resume, 0=from scratch */
                                   /* implicit inputs: ftfscb->fname,estbyt */
/* input file time&date option 1: ftfscb->unxtim (ftfscb->dosdat,dostim==0) */
/* input file time&date option 2: ftfscb->dosdat,dostim (ftfscb->unxtim==0) */
/* input file time&date option 3: none (ftfscb->unxtim,dosdat,dostim == 0)  */
    /* FTF may choose any one of these 3 options, depending on the protocol */
                                                   /* returns 0=ok, 1=abort */
                      /* if returns nonzero, points ftfscb->abwhy to reason */
      /* ftfrcl() will be called exactly once for each ftfrop() returning 0 */
     /* multi-file protocol sessions may call ftfrop() as many times as the */
                                /* sender sends another file in the session */
{
     CHAR *fopopt;
     INT rc;
     char *pp;

     while ((pp=strstr(ftfscb->fname,"..")) != NULL) {
          pp[0]='_';
          pp[1]='_';
     }
     strrpl(ftfscb->fname,'\\','_');
     strrpl(ftfscb->fname,'/','_');
     strrpl(ftfscb->fname,':','_');
     ftfscb->maxbyt=GCMAXLONG;
     rc=ftuptr->fuphdl(FUPBEG);
     setmbk(ftfmb);
     if (!rc) {
          ftferr("%0.80s",ftfbuf);
          ftfscb->abwhy=ftuptr->msgbuf;
          return(1);
     }
     if (ftfscb->estbyt > ftfscb->maxbyt) {
          ftfscb->abwhy="File would be too big.";
          return(1);
     }
     fopopt=(resume ? FOPRWB : (append ? (ascii ? FOPAA : FOPAB)
                                       : (ascii ? FOPWA : FOPWB)));
     if ((ftuptr->fp=fopen(ftfbuf,fopopt)) == NULL) {
          ftfscb->abwhy=append ? "Cannot append to this file."
                               : "Cannot create this file.";
          return(1);
     }
     return(0);
}

INT
ftfrwr(                                   /* Write received bytes into file */
CHAR *data,
INT nbytes)
{                                                /* returns # bytes written */
     return(fwrite(data,1,nbytes,ftuptr->fp));
}

INT
ftfrwb(CHAR ch)                          /* Write 1 received byte into file */
{                                                   /* returns 1=good 0=bad */
     return(fputc(ch,ftuptr->fp) != EOF);
}

VOID
ftfrsk(           /* Seek a position in the file for receiving & writing to */
LONG pos)                                            /* (needed for resume) */
{
     ftfxsk(pos);
}

INT
ftfrrd(         /* Read bytes from file before receiving & overwriting them */
CHAR *datbuf,                                              /* put them here */
INT nbytes)                                        /* try to read this many */
                                /* returns how many actually read, or 0=EOF */
                           /* (needed for last-sector verify before resume) */
{                                  /* returns how many actually read, 0=EOF */
     return(fread(datbuf,1,nbytes,ftuptr->fp));
}

VOID
ftfrsq(                                              /* report file skipped */
CHAR *reason)
{
     ftfxsq(reason);
}

VOID
ftfrcl(                                       /* Close file after receiving */
INT ok)                                /* 1=received in entirety, 0=aborted */
                            /* implicit inputs: ftfscb->fname,estbyt,actbyt */
 /* also input are time&date in one of the optional formats as for ftfrop() */
    /* application can decide whether ASCII files should have ^Z terminator */
{
     fclose(ftuptr->fp);
     if (ok) {
          ftfbuf[0]='\0';
          ftuptr->fuphdl(FUPEND);
          if (ftfbuf[0] != '\0') {
               if (ftfscb->unxtim != 0L) {
                    setFileGMT(ftfbuf,ftfscb->unxtim);
               }
               else if (ftfscb->dosdat != 0 || ftfscb->dostim != 0) {
                    setFileTm(ftfbuf,ftfscb->dostim,ftfscb->dosdat);
               }
          }
     }
     else {
          ftuptr->fuphdl(FUPSKP);
     }
     setmbk(ftfmb);
}
