/***************************************************************************
 *                                                                         *
 *   GALTCPAT.C                                                            *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   TCP/IP outdialing module, emulating the ATDT command over a Telnet    *
 *   connection.                                                           *
 *                                                                         *
 *                                        - RNStein  3/13/95               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "fsd.h"
#include "ftscope.h"
#include "tcpip.h"
#include "tno.h"
#include "dns.h"
#include "telnetd.h"
#include "telnet.h"
#include "phasedbg.h"
#include "galtcpat.h"

#define FILREV "$Revision: 17 $"

static USHORT swtat(struct datstm far *dsp);
static VOID dmtat(struct datstm far *dsp,USHORT nactual);
static VOID tatdnsup(struct tatusr *tat);
static VOID tatcbk(struct dns *dnsptr);
static VOID tatcon(struct tatusr *tat);
static VOID rspstg(INT unum,CHAR *msg,...);
VOID tatdmv(struct datstm far *dsp,USHORT nactual);
USHORT far tatmit(struct datstm far *dsp,CHAR far *srcloc,
                        USHORT nwant);
static VOID keycnv(INT unum,CHAR *bytes,USHORT nbytes);
static USHORT moviad(struct datstm far *dsp,CHAR far *srcloc,USHORT nwant);
VOID tatsnd(struct tcpipinf *tip);
VOID tatrdt(struct tcpipinf *tip);
static INT tatops(INT code, INT parm);
static VOID sndop3(CHAR c1,CHAR c2,CHAR c3);
static VOID tatlsc(INT unum);
static VOID tatsys(VOID);
static VOID tatrst(VOID);

static struct datstm tatdsk={      /* template, dialing-mode output sink   */
     swtat,                        /* return how much room sink has now    */
     hviad,                        /* pass one byte to the sink            */
     moviad,                       /* sink moves bytes (nactual is fedback)*/
     dmtat,                        /* source reports bytes moved to snkloc */
     NULL,                         /* snkwin output: where source can store*/
     0,                            /* snkwin output: maximum eventual room */
     0                             /* DSTOVF=sink reports overflow         */
};

static struct bufstm tatcsk={{     /* template, connected-mode output sink */
          tntskw,                  /* return how much room sink has now    */
          hobufs,                  /* pass one byte to the sink            */
          tatmit,                  /* sink moves bytes (nactual is fedback)*/
          tatdmv,                  /* source reports bytes moved to snkloc */
          NULL,                    /* snkwin output: where source can store*/
          0,                       /* snkwin output: maximum eventual room */
          0},                      /* DSTOVF=sink reports overflow         */
     NULL,                         /* accumulation buffer (tip->outstg)    */
     TCPOSZ,                       /* size of buffer                       */
     0                             /* buffer count, 0..size-1              */
};

#define LSCWAIT 16*5               /* input timeout after lost carrier     */

struct tatusr **tatusr=NULL;       /* pointers to tatusrs (TCP/IP only)*/
CHAR *tatpfx1="ATDT";              /* outdialing prefix #1                 */
CHAR *tatpfx2="ATDP";              /* outdialing prefix #2                 */
INT tatdnu=-1;                     /* user number using *tatdns (-1=none)  */
struct dns *tatdns;                /* DNS resolver (one at a time)         */
struct tatusr *tatptr;             /* global for tatrdt(), tatops(), etc.  */
VOID (*oldrst)(VOID);              /* saved (*hdlrst)() vector             */
VOID (*oldsys)(VOID);              /* saved (*syscyc)() vector             */

INT tatrng=0;                      /* respond RING CALLING <ipaddr>?       */
     /* Note:  Dial-Out and Entertainment Teleconference will probably not */
     /* work over Telnet channels if tatrng == 1.                          */

VOID EXPORT
init__galtcpat(VOID)               /* Initialize TCP/IP AT dialout         */
{
     INT unum;

     init__tcpip();
     init__galtntd();
     tatusr=(struct tatusr **)alczer(nterms*sizeof(struct tatusr *));
     tatdns=(struct dns *)alczer(sizeof(struct dns));
     for (unum=0 ; unum < nterms ; unum++) {
          if (grtype[grpnum[unum]] == GTOTHER
           && sameas(grtsub[grpnum[unum]],"TCP/IP")) {
               tatusr[unum]=(struct tatusr *)alczer(sizeof(struct tatusr));
               tatusr[unum]->unum=unum;
          }
          tcpipinf[unum].server=NULL;
          tcpipinf[unum].socket=-1;
     }
     oldrst=hdlrst;
     hdlrst=tatrst;
     oldsys=syscyc;
     syscyc=tatsys;
}

VOID EXPORT
initwc__galtcpat(VOID)
{
     init__galtcpat();
}

static USHORT
swtat(                             /* snkwin() for TCP/IP outdialing mode  */
struct datstm far *dsp)            /* DataStream structure                 */
{
     if (tatdnu == -1) {
          dsp->snkloc=tatdns->name;
          return(dsp->snkmax=sizeof(tatdns->name)-1);
     }
     else {
          return(0);               /* one channel at a time, so hold up    */
     }                             /* output while looking up domain       */
}

/* The following routine takes advantage of the knowledge that calling     */
/* btuxct() when the output buffer is empty will never result in split     */
/* bytes in the circular GSBL output buffer, and thus all bytes will be    */
/* passed in a single moveit() to the output sink (via mviad() to dmtat()).*/

static VOID
dmtat(                             /* didmov() for TCP/IP outdialing mode  */
struct datstm far *dsp,            /* DataStream structure                 */
USHORT nactual)                    /* this many were moved by the source   */
{
     struct tatusr *tat;
     struct tcpipinf *tip;

     if (tatdnu == -1 && nactual > 0) {
          tip=(struct tcpipinf *)dsp;
          ASSERT(nactual <= sizeof(tatdns->name)-1);
          ASSERTM(tip->unum >= 0
               && tip->unum < nterms,spr("tip->unum=%d",tip->unum));
          ASSERTM(tip == &tcpipinf[tip->unum],spr("%p %d %p",tip,tip->unum,&tcpipinf[tip->unum]));
          tat=tatusr[tip->unum];
          tatdns->name[memstp(tatdns->name,nactual,'\0')]='\0';
          if (strchr(tatdns->name,'\r') != NULL) {
               strstp(tatdns->name,'\r');
               strstp(tatdns->name,'\n');
               if (sameto(tatpfx1,tatdns->name)) {
                    strcpy(tatdns->name,skpwht(tatdns->name+strlen(tatpfx1)));
                    tatdnsup(tat);
               }
               else if (sameto(tatpfx2,tatdns->name)) {
                    strcpy(tatdns->name,skpwht(tatdns->name+strlen(tatpfx2)));
                    tatdnsup(tat);
               }
               else if (sameto("AT",tatdns->name)) {
                    rspstg(tip->unum,"OK\r\n");
               }
               else if (tatdns->name[0] == '\0') {
               }
               else if (!sameto("ERROR BAD COMMAND",tatdns->name)) {
                    rspstg(tip->unum,"ERROR BAD COMMAND: \"%0.96s\"\r\n",
                           tatdns->name);
               }
          }
     }
     else {
          ASSERT(nactual == 0);
     }
}

static VOID
tatdnsup(                          /* initiate DNS lookup for TCP/IP AT    */
struct tatusr *tat)
{
     CHAR *cp,*dname;
     INT nnames=0;
     INT nports=0;
     INT unmsav;
     INT stxerr;

     tat->flags=0;
     tat->port=TNTPORT;
     dname=ipaddr;
     stxerr=0;
     for (cp=strtok(tatdns->name," ") ; cp != NULL ; cp=strtok(NULL," ")) {
          if (*cp == '-' || *cp == '/') {
               if (sameas(cp+1,"a")) {
                    tat->flags|=TFLASC;
               }
               else if (sameas(cp+1,"bs")) {
                    tat->flags|=TFLBKS;
               }
               else if (sameas(cp+1,"rk")) {
                    tat->flags|=TFLKYR;
               }
               else if (sameas(cp+1,"rs")) {
                    tat->flags|=TFLSCR;
               }
               else if (sameas(cp+1,"nb")) {
                    tat->flags|=TFLNOB;
               }
               else {
                    stxerr=1;
               }
          }
          else if (alldgs(cp)) {
               tat->port=(UINT)atol(cp);
               if (++nports > 1) {
                    stxerr=1;
               }
          }
          else {
               dname=cp;
               if (++nnames > 1) {
                    stxerr=1;
               }
          }
          if (stxerr) {
               rspstg(tat->unum,"ERROR INVALID SYNTAX STARTING WITH "
                                "\"%0.80s\"\r\n",cp);
               break;
          }
     }
     if (!stxerr) {
          strcpy(tatdns->name,dname);
          tatdns->numaddr=1;
          tatdns->callbk=tatcbk;
          unmsav=usrnum;
          tatdnu=tat->unum;
          curusr(tatdnu);
          dnsn2a(tatdns);
          curusr(unmsav);
     }
}

static VOID
tatcbk(                            /* callback vector after DNS done       */
struct dns *dnsptr)                /* (curusr() is in effect)              */
{
     struct tatusr *tat;
     struct tcpipinf *tip;
     INT rc;

     tat=tatusr[usrnum];
     tip=&tcpipinf[usrnum];
     if (dnsptr->status < 0) {
          rspstg(usrnum,"ERROR DNS LOOKUP (%d): %0.90s\r\n",dnsptr->status,dnsemg);
     }
     else if ((rc=tcpdial(dnsptr->inaddr[0],htons(tat->port),0,
                          &tip->socket)) < 0) {
          rspstg(usrnum,"ERROR DIALING (%d) %d: %0.86s\r\n",
                        rc,tcpip_errno,tcpErrStg(tcpip_errno));
          if (tip->socket != -1) {
               clsskt(tip->socket);
               tip->socket=-1;
          }
     }
     else {
          if (tat->flags&TFLSCR) {
               tat->rbempt/=2;
               tat->rbhyst=tat->rbempt/2;
          }
          setrcvwin(tip->socket,tat->rcvwin=tat->rbempt);
          sktnfy(TNFCONN,tip->socket,tatcon,tat,usrnum);
          movmem(&dnsptr->inaddr[0],&tip->inaddr,sizeof(struct in_addr));
          if (tatrng) {
               rspstg(usrnum,"RING CALLING %s\r\n",
                             inet_ntoa(dnsptr->inaddr[0]));
          }
     }
     tatdnu=-1;
}

static VOID
tatcon(                            /* connected to remote Telnet server    */
struct tatusr *tat)                /* tatusr information                   */
{
     CHAR onechar;
     struct tcpipinf *tip;

     tip=&tcpipinf[usrnum];
     if (recv(tip->socket,&onechar,0,0) < 0 && tcpip_errno != EWOULDBLOCK) {
          rspstg(usrnum,"ERROR CONNECTING: %s\r\n",tcpErrStg(tcpip_errno));
          clsskt(tip->socket);
          tip->socket=-1;
          return;
     }
     oobinline(tip->socket);
     sktcnc(TNFCONN,tip->socket);
     sktnfy(TNFRECV,tip->socket,tatrdt,tip,usrnum);
     movmem(&tatcsk,&tip->outsnk,sizeof(struct bufstm));
     tip->outsnk.buffer=tip->outstg;
     tip->cdidst=btucdi(usrnum,&tip->outsnk.datstm);
     if (tip->cdidst == NULL) {
          rspstg(usrnum,"%d-USER LICENSE EXCEEDED\r\n",btusrs);
          clsskt(tip->socket);
          tip->socket=-1;
          return;
     }
     ASSERT(tip->cdidst != NULL);
     tip->server=NULL;
     usrptr->tckonl=lngtck;
     tnoscb=&tat->tnoscb;
     tnoini(TNATSIZ,tatops);
     if (!(tat->flags&TFLASC)) {
          sndop3(IAC,DO,TNTOPT_SGA);
          sndop3(IAC,DO,TNTOPT_ECHO);
          tnoscb->flags|=TNAECH;
          if (!(tat->flags&TFLNOB)) {
               sndop3(IAC,DO,TNTOPT_BINARY);
               sndop3(IAC,WILL,TNTOPT_BINARY);
               tnoscb->flags|=TNASBN+TNACBN;
          }
          tatsnd(tip);
     }
     rspstg(usrnum,"CONNECT %s TO %s\r\n",l2as(usrptr->baud),
                                          inet_ntoa(tip->inaddr));
}

static VOID
rspstg(                            /* sim modem-like response to dialing   */
INT unum,                          /* user number (GSBL channel)           */
CHAR *msg,                         /* text message, ala printf()           */
...)
{
     struct datstm *dsp;
     CHAR *cp,buf[256];
     USHORT nwant,nmoved,nactual;
     INT oldicx;
     va_list args;

     va_start(args,msg);
     vsprintf(buf,msg,args);
     va_end(args);
     dsp=tcpipinf[unum].cdidst;
     nwant=strlen(cp=buf);
     nmoved=0;
     oldicx=iskopen();
     while ((nactual=dsp->moveit(dsp,cp+nmoved,nwant-nmoved)) > 0
         && (nmoved+=nactual) < nwant) {
     }
     iskclose(oldicx);
}

/*--- The following routine is a clone of tntdmv() in TELNETD.C, but    ---*/
/*--- first it handles the -bs and -rk options.                         ---*/

VOID
tatdmv(                            /* didmov() for Telnet outdialed data   */
struct datstm far *dsp,            /* DataStream structure                 */
USHORT nactual)                    /* this many were moved by the source   */
{
     struct tcpipinf *tip;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     keycnv(tip->unum,dsp->snkloc,nactual);
     tip->outsnk.bufcnt+=iacenc(dsp->snkloc,nactual,TCPOSZ-tip->outsnk.bufcnt);
     tatsnd(tip);
     restore_cdi_interrupts;
}

/*--- The following routine is a clone of tntmit() in TELNETD.C.  The   ---*/
/*--- primary differences are in error handling and -bs -rk handling.   ---*/

USHORT far
tatmit(                            /* moveit() for Telnet outdialed data   */
struct datstm far *dsp,            /* (pointer to sink's datstm structure) */
CHAR far *srcloc,                  /* source specifies the location        */
USHORT nwant)                      /* source wants sink to move this many  */
                                   /* returns actual number of bytes moved */
{
     CHAR *sp;                     /* scanning source pointer (unencoded)  */
     CHAR *bp;                     /* scanning buffer pointer (encoded)    */
     USHORT i;                     /* scanning source character counter    */
     USHORT nffnot;                /* number of continuous non-FF bytes    */
     INT nactual;                  /* number of bytes actually send()'d    */
     INT nroom;                    /* room in output staging buffer        */
     USHORT nleft;                 /* number of bytes send() left behind   */
     USHORT nsave;                 /* number of bytes saved in outstg[]    */
     struct tcpipinf *tip;
     INT unmsav;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     nffnot=scanch(srcloc,IAC,nwant);
     keycnv(tip->unum,srcloc,nwant);
     if (nffnot == nwant
      && tip->outsnk.bufcnt == 0
      && nwant > 0
      && !(tip->flags&TCPDSC)) {
          if ((nactual=send(tip->socket,srcloc,nwant,0)) == SOCKET_ERROR) {
               if (tcpip_errno != EWOULDBLOCK) {
                    unmsav=usrnum;
                    usrnum=tip->unum;
                    shocst("GALTCPAT: TCP/IP SEND ERROR",
                           "tatmit() error %d: %s",
                           tcpip_errno,tcpErrStg(tcpip_errno));
                    tatlsc(usrnum);
                    usrnum=unmsav;
               }
               nactual=0;
          }
          else if ((nleft=nwant-nactual) > 0) {
               nsave=min(TCPOSZ,nleft);
               movmem(srcloc+nactual,tip->outstg,nsave);
               tip->outsnk.bufcnt=nsave;
               sktnfy(TNFSEND,tip->socket,tatsnd,tip,tip->unum);
               nactual+=nsave;
          }
          restore_cdi_interrupts;
          return(nactual);
     }
     sp=srcloc;
     bp=tip->outstg+tip->outsnk.bufcnt;
     nroom=TCPOSZ-tip->outsnk.bufcnt;
     for (i=0 ; i < nwant ; ) {
          if ((nsave=min(nroom,nffnot)) > 0) {
               movmem(sp,bp,nsave);
               i+=nsave;
               sp+=nsave;
               bp+=nsave;
               nroom-=nsave;
          }
          if (i >= nwant || nroom < 2) {
               break;
          }
          if (*sp == IAC) {        /* (precautionary check)                */
               i++;
               sp++;
               *bp++=IAC;
               *bp++=IAC;
               nroom-=2;
          }
          nffnot=scanch(sp,IAC,nwant-i);
     }
     tip->outsnk.bufcnt=TCPOSZ-nroom;
     tatsnd(tip);
     restore_cdi_interrupts;
     return((USHORT)(sp-srcloc));
}

static VOID
keycnv(                            /* Keystroke conversion options         */
INT unum,                          /* user number                          */
CHAR *bytes,                       /* outbound keystrokes (mod in-place)   */
USHORT nbytes)                     /* number of bytes                      */
{
     INT flags;
     USHORT i;

     flags=tatusr[unum]->flags;
     if (nbytes > 0) {
          if (flags&TFLBKS) {
               while ((i=scanch(bytes,'\b',nbytes)) != nbytes) {
                    bytes[i]='\x7F';
               }
          }
          if (flags&(TFLKYR+TFLASC)) {
               while ((i=scanch(bytes,'\r',nbytes)) != nbytes) {
                    bytes[i]='\n';
               }
          }
     }
}

/*-- The following routine displaces mviad() in DATSTM.C (PHGCOMM.LIB).  --*/
/*-- Someday, it may be appropriate to remove this one and use that one. --*/

static USHORT
moviad(                            /* moveit() stub via didmov() & snkwin()*/
struct datstm far *dsp,            /* DataStream structure                 */
CHAR far *srcloc,                  /* source location                      */
USHORT nwant)                      /* number of bytes desired to move      */
{                                  /* returns actual number of bytes moved */
     USHORT nroom,nactual,ntotal;
     INT tryit;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;
     ntotal=0;
     for (tryit=0 ; tryit <= 2 && nwant > 0 ; tryit++) {
          nroom=dsp->snkwin(dsp);
          if (nroom == 0) {
               break;
          }
          nactual=min(nwant,nroom);
          movmem(srcloc,dsp->snkloc,nactual);
          dsp->didmov(dsp,nactual);
          srcloc+=nactual;
          nwant-=nactual;
          ntotal+=nactual;
     }
     restore_cdi_interrupts;
     return(ntotal);
}

VOID
tatsnd(                            /* try to send staging buffer contents  */
struct tcpipinf *tip)              /* tatusr session info                  */
{                                  /* (tatsnd() doesn't rely on usrnum)    */
     INT unmsav;

     switch(sndmgr(tip->outstg,&tip->outsnk.bufcnt,tip->socket)) {
     case -1:
          unmsav=usrnum;
          usrnum=tip->unum;
          shocst("GALTCPAT: TCP/IP SEND ERROR",
                 "tatsnd() error %d: %s",tcpip_errno,tcpErrStg(tcpip_errno));
          tatlsc(usrnum);
          usrnum=unmsav;
     case 0:
          sktcnc(TNFSEND,tip->socket);
          break;
     case 1:
          sktnfy(TNFSEND,tip->socket,tatsnd,tip,tip->unum);
          break;
     }
     if (sndact == 0) {
          actdet=0;
     }
}

VOID
tatrdt(                            /* read Telnet-coded data to GCDI chan  */
struct tcpipinf *tip)              /* user's TCP/IP channel info           */
{                                  /* application:  sktnfy(,,tatrdt,tip,)  */
     struct datstm *dsp;
     INT ibw;
     UINT nroom;
     INT nactual;
     INT tryit;
     INT i;
     INT oldicx;

     tntunm=usrnum;
     tatptr=tatusr[usrnum];
     dsp=tip->cdidst;
     tnoscb=&tatptr->tnoscb;
     if ((ibw=btuibw(usrnum)) == 0) {           /* force empty circular    */
          btucli(usrnum);                       /* buffer to be continuous */
     }
     nroom=tatptr->rbempt-ibw;
     if (tatptr->flags&TFLSCR) {
          nroom/=2;
     }
     rwnmgr(tip->socket,nroom,&tatptr->rcvwin);
     for (tryit=0 ; tryit < 10 ; tryit++) {
          oldicx=iskopen();
          nroom=dsp->snkwin(dsp);
          if (tatptr->flags&TFLSCR) {
               nroom/=2;                         /* save room for LF->CRLF */
          }
          if (nroom == 0) {
               if (tryit == 0) {
                    actdet=0;
               }
               iskclose(oldicx);
               break;
          }
          if ((nactual=recv(tip->socket,dsp->snkloc,nroom,0)) < 0) {
               switch (tcpip_errno) {
               case ECONNRESET:         /* benign disconnect               */
                    tatlsc(usrnum);
                    break;
               case EWOULDBLOCK:        /* no more polling necessary       */
                    break;
               default:                 /* error condition                 */
                    shocst("GALTCPAT: TCP/IP RECEIVE ERROR",
                           "tatrdt() error %d: %s",
                           tcpip_errno,tcpErrStg(tcpip_errno));
                    tatlsc(usrnum);
                    break;
               }
               /* nactual=0; never referred to again */
               iskclose(oldicx);
               break;
          }
          if (nactual == 0) {
               if (tryit == 0) {
                    tatlsc(usrnum);
               }
               iskclose(oldicx);
               break;
          }
          nactual=iacdec(dsp->snkloc,nactual);
          if (tatptr->flags&TFLSCR) {
               for (i=0 ;
                    nactual < nroom
                 && (i+=scanch(dsp->snkloc+i,'\n',nactual-i)) < nactual ;
                    i++) {
                    movmem(dsp->snkloc+i,dsp->snkloc+i+1,nactual-i);
                    dsp->snkloc[i]='\r';
                    nactual++;
                    i++;
               }
          }
          dsp->didmov(dsp,nactual);
          iskclose(oldicx);
          if (tip->outsnk.bufcnt > 0) {
               tatsnd(tip);
          }
          if (nactual < nroom) {   /* recv() didn't exhaust sink's window, */
               break;              /* no point in trying to recv() again   */
          }
     }
}

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

     ftsops(code,parm);
     switch (code) {
     case TNCWIWO:                 /* WILL (parm=1) or WONT (parm=0) recd  */
          switch (tnoscb->opid) {
          case TNTOPT_SGA:
               if (!parm && tatptr->port == TNTPORT) {
                    shocst("TELNET DIALOUT - NON-SGA SERVER",
                           "Server at %s does not support SGA",
                           inet_ntoa(tcpipinf[usrnum].inaddr));
               }
               break;
          case TNTOPT_ECHO:
               tnoyes(TNAECH);
               break;
          case TNTOPT_BINARY:
               if (tatptr->flags&TFLASC) {
                    tnono(TNASBN);
               }
               else {
                    tnoyes(TNASBN);
               }
               break;
          default:
               tnono(0);
               break;
          }
          break;
     case TNCDODO:                 /* DO (parm=1) or DONT (parm=0) recd    */
          switch (tnoscb->opid) {
          case TNTOPT_SGA:
               tnoyes(TNACSG);
               break;
          case TNTOPT_BINARY:
               if (tatptr->flags&TFLASC) {
                    tnono(TNACBN);
               }
               else {
                    tnoyes(TNACBN);
               }
               break;
          default:
               tnono(0);
               break;
          }
          break;
     case TNCOTHR:                 /* IAC <other> recd (parm=<other>)      */
          break;
     case TNCSEND:
          sndop3(IAC,parm,tnoscb->opid);
          break;
     }
     return(rc);
}

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

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

static VOID
tatlsc(                            /* TCP/IP AT outdialer -- lost carrier  */
INT unum)                          /* user number (must be TCP/IP chan)    */
{

     ASSERT(tatusr[unum] != NULL);
     tatusr[unum]->lsctim=(UINT)(hrtval()>>12);
     tatusr[unum]->flags2|=TATLSC;
     ASSERT(tcpipinf[unum].socket != -1);
     sktcnc(TNFRECV,tcpipinf[unum].socket);
     sktcnc(TNFSEND,tcpipinf[unum].socket);
     sktcnc(TNFCONN,tcpipinf[unum].socket);
}

static VOID
tatsys(VOID)                       /* TCP/IP dialout system cycle          */
{
     struct tcpipinf *tip;
     struct tatusr *tat;
     UINT nroom;
     INT unum;

     BEG_PHASE("System cycle/TCP dialout",0);
     for (unum=0,tip=&tcpipinf[0] ; unum < nterms ; unum++,tip++) {
          if (tip->server == NULL
           && tip->socket != -1
           && !(usroff(unum)->flags&NOHDWE)
           && (tat=tatusr[unum]) != NULL) {
               if (tat->rcvwin < tat->rbempt) {
                    nroom=tat->rbempt-btuibw(unum);
                    if (tat->flags&TFLSCR) {
                         nroom/=2;
                    }
                    rwnadj(tip->socket,tat->rbempt,nroom,tat->rbhyst,
                                                        &tat->rcvwin);
               }
               if (tat->flags2&TATLSC) {
                    if (btuibw(unum) == 0
                     || (UINT)(hrtval()>>12)-tat->lsctim > LSCWAIT) {
                         btuinj(unum,LOST2C);
                         tat->flags2&=~TATLSC;
                    }
               }
          }
     }
     END_PHASE("System cycle/TCP dialout",0);
     (*oldsys)();
}

static VOID
tatrst(VOID)                       /* intercepted reset vector, TCP/IP AT  */
{
     struct tcpipinf *tip;

     if (tatdnu == usrnum) {
          dnsabt();
     }
     tip=&tcpipinf[usrnum];
     if (tatusr[usrnum] != NULL) {
          if (tip->server == NULL
           && tip->socket != -1
           && !(usroff(usrnum)->flags&NOHDWE)) {
               btucdi(usrnum,NULL);
               clsskt(tip->socket);
               tip->socket=-1;
          }
          setmem(tatusr[usrnum],0,sizeof(struct tatusr));
     }
     (*oldrst)();
     if (usroff(usrnum)->usrcls == VACANT
      && tatusr[usrnum] != NULL
      && !(usroff(usrnum)->flags&NOHDWE)) {
          movmem(&tatdsk,&tip->outsnk.datstm,sizeof(struct datstm));
          tip->unum=usrnum;
          tip->server=NULL;
          tip->socket=-1;
          tatusr[usrnum]->rbempt=OUTSIZ/2-1;         /* assumes app does   */
          tatusr[usrnum]->rbhyst=OUTSIZ/4;           /* btubsz(,OUTSIZ/2,) */
          usroff(usrnum)->flags|=IS2698;
          btuinj(usrnum,CMN2OK);
          btupcc(usrnum,CDIQAP);
          tip->cdidst=btucdi(usrnum,&tip->outsnk.datstm);
          tip->cdidst->moveit=moviad;           /* replaces flawed mviad() */
     }
}
