/***************************************************************************
 *                                                                         *
 *   TELNETD.C                                                             *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   Telnet daemon for Worldgroup.                                         *
 *                                                                         *
 *   Also includes some semi-generic routines.                             *
 *                                                                         *
 *                                        - RNStein  5/27/94               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "remote.h"
#include "tcpip.h"
#include "tcport.h"                /* to take advantage of sendba()        */
#include "crnul.h"
#include "tno.h"
#include "telnetd.h"
#include "ftscope.h"
#include "galtntd.h"

#define FILREV "$Revision: 18 $"

static GBOOL tntlon(VOID);
static VOID tntrlg(VOID);
static VOID tntrdt(struct tcpipinf *tip);
static INT tntops(INT code,INT parm);
static VOID sndop3(CHAR c1,CHAR c2,CHAR c3);
static VOID tnthup(VOID);
static VOID tntrst(VOID);
static VOID tntlat(VOID);
static VOID dectntd(VOID);
static VOID tntsesend(VOID);
#ifdef TCPIP_CAN_SENDBA
static USHORT moviad(struct datstm *dsp,CHAR *srcloc,USHORT nwant);
#endif

static VOID (*oldhup)(VOID);       /* for recording module[0]->hangup vectr*/
static VOID (*oldrst)(VOID);       /* for recording (*hdlrst)() vector     */
static VOID (*oldlat)(VOID);       /* for logon audit trail message        */
static VOID (*olddec)(VOID);       /* for recording (*decusr)() vector     */
static VOID (*oldrlg)(VOID);       /* for intercepting user relog          */
static GBOOL (*oldlon)(VOID);      /* for intercepting (*vallon)()         */
static UINT opovfl;                /* tntrdt() & subs: output option ovfls */
static CHAR irupt;                 /* tntrdt() & subs:1=<IAC IP> received  */

struct tntinf *tntinf;             /* array of user/telnet info            */
struct tntinf *tntusr;             /* global pointer to current user's info*/
INT tntunm;                        /* tnordt() & subs: user number         */
struct tcpipinf *tiptr;            /* tnordt() & subs: tcpip info for user */
INT rdtact;                        /* set by tnordt(), actual # bytes recd */
                                   /* (before all decoding)                */

#define MCBSIZ 1024                /* size of mcbuff[]                     */
char *mcbuff;                      /* tntmit()'s buffer for crnenc()ing    */

HMCVFILE tntmb;                    /* for GALTNTD.MSG options              */
INT maxtntd;                       /* max number of telnets at once        */
CHAR tntonl;                       /* 1=Telnet daemon online, 0=not        */
CHAR tntrej;                       /* (tntonl=0) 1=reject, 0=ignore calls  */
INT tntchg;                        /* telnet server surcharge creds/min    */
LONG tntkchg;                      /* charge / 1K bytes traffic via Telnet */
CHAR tntdbt;                       /* audit time & bytes of Telnet session */
static GBOOL didInit=FALSE;        /* have we been initialized yet?        */

VOID
init__galtntd(VOID)
{
     if (didInit) {
          return;
     }
     didInit=TRUE;
     init__tcpip();
     tntmb=opnmsg("galtntd.mcv");
     tntonl=ynopt(TNTONL);
     tntrej=ynopt(TNTREJ);
     maxtntd=numopt(MAXTNTD,0,250);
     tntchg=numopt(TNTCHG,-32767,32767);
     tntkchg=lngopt(TNTKCHG,-2000000000L,2000000000L);
     tntdbt=ynopt(TNTDBT);
     tntinf=(struct tntinf *)alczer(nterms*sizeof(struct tntinf));
     mcbuff=alcmem(MCBSIZ);
     /* check for module disabled */
     if ((tntonl || tntrej) && isfile("galtntd.mdf")) {
          oldrst=hdlrst;
          hdlrst=tntrst;
          oldhup=module[0]->huprou;
          module[0]->huprou=tnthup;
          oldlat=loncdi;
          loncdi=tntlat;
          olddec=decusr;
          decusr=dectntd;
          oldlon=vallon;
          vallon=tntlon;
          oldrlg=hdlrlg;
          hdlrlg=tntrlg;
          regtcpsvr(TNTNAME,TNTPORT,TNTBACKLOG,tntincall);
     }
     else {
          clsmsg(tntmb);
     }
}

VOID EXPORT
initwc__galtntd(VOID)
{
     init__galtntd();
}

VOID
tntincall(                         /* begin incoming Telnet session        */
INT gotchn)                        /* 1=chan (curusr) assigned, 0=not avail*/
{                                  /* implicit:  clskt=socket to client    */
     INT rjmg;

     setmbk(tntmb);
     clrprf();
     if (!gotchn) {
          switch (ijreas) {
          case IJRSHUT:
          default:
               rjmg=SVRSHUT;
               break;
          case IJRFULL:
               rjmg=SVRFULL;
               break;
          case IJRDENY:
               rjmg=SVRDENY;
               break;
          }
          sprintf(prfbuf,xlttxv(stpans(getasc(rjmg)),mxmssz),numcdi("TCP/IP"));
          send(clskt,prfbuf,strlen(prfbuf),0);
     }
     else {
          tntusr=&tntinf[usrnum];
          tnoscb=&tntusr->tnoscb;
          tnoini(TNMSIZ,tntops);
          tiptr=&tcpipinf[tntunm=usrnum];
          tiptr->outsnk.datstm.snkwin=tntskw;
          tiptr->outsnk.datstm.didmov=tntdmv;
#ifdef TCPIP_CAN_SENDBA
               tiptr->outsnk.datstm.moveit=moviad;
#else
               tiptr->outsnk.datstm.moveit=tntmit;
#endif
          if (!tntonl) {
               byendl(TNTNOTON);
               usrptr->state=W4KILL;
               rejinc(TNTNAME,"Telnet disabled");
          }
          else if (tntdcnt() >= maxtntd) {
               byendl(TNTDMANY,maxtntd);
               usrptr->state=W4KILL;
               rejinc(TNTNAME,spr("%d Telneting in",maxtntd));
          }
          else {
               bbscon(usrnum);
               sktnfy(TNFRECV,clskt,tntrdt,tiptr,usrnum);
               sndop3(IAC,WILL,TNTOPT_SGA);
               sndop3(IAC,WILL,TNTOPT_ECHO);
               sndop3(IAC,WILL,TNTOPT_BINARY);
               sndop3(IAC,DO,TNTOPT_BINARY);
               tcpsnd(tiptr);
               tnoscb->flags|=TNTECH+TNTSBN+TNTCBN;
               tntinf[usrnum].flags|=TNTCHN;
          }
     }
}

INT
tntdcnt(VOID)                      /* count of telnet server connections   */
{
     INT i,n=0;

     for (i=0 ; i < nterms ; i++) {
          if (tntinf[i].flags&TNTCHN) {
               n++;
          }
     }
     return(n);
}

static GBOOL
tntlon(VOID)                       /* Telnet server intercepting user logon*/
{
     GBOOL ok;

     ok=(*oldlon)();
     if (ok) {
          if (tntinf[usrnum].flags&TNTCHN) {
               setmbk(tntmb);
               if (!hasmkey(TNTKEY)) {
                    prfmsg(TNTCANT);
                    ok=FALSE;
               }
               rstmbk();
          }
     }
     return(ok);
}

static VOID
tntrlg(VOID)                       /* Telnet server intercepting user relog*/
{
     tntsesend();
     (*oldrlg)();
}

static VOID
tntrdt(                            /* read from Telnet client to GCDI chan */
struct tcpipinf *tip)              /* user's TCP/IP channel info           */
{                                  /* application:  sktnfy(,,tntrdt,tiptr,)*/
     struct datstm *dsp;
     INT oldicx;
     static CHAR irupts[]="\x18\x18\x18\x18\x18\x18\x18\x18"
                           "\b\b\b\b\b\b\b\b\b\b\b\b"
                           "X\r"
                           "\b";

     actdet=0;
     irupt=0;
     opovfl=0;
     tnordt(tip);
     if (1 <= rdtact && rdtact <= 5) {
          btuopl(usrnum);                                   /* snappy echo */
     }
     if (irupt) {
          dsp=tip->cdidst;
          oldicx=iskopen();
          if (dsp->snkwin(dsp) >= sizeof(irupts-1)) {
               movmem(irupts,dsp->snkloc,sizeof(irupts)-1);
               dsp->didmov(dsp,sizeof(irupts)-1);
          }
          iskclose(oldicx);
     }
     if (opovfl > 0) {
          setmbk(tntmb);
          prfmsg(OPOVFL,opovfl);
          outprf(tntunm);
          rstmbk();
     }
}

VOID
tnordt(                            /* read Telnet-coded data to GCDI chan  */
struct tcpipinf *tip)              /* user's TCP/IP channel info           */
                                   /* application:  sktnfy(,,tnordt,tiptr,)*/
                                   /* rdtact set to number of bytes read   */
{
     struct datstm *dsp;
     UINT nroom;
     UINT nbytes;
     INT attempt;
     INT oldicx;

     tiptr=tip;
     tntunm=usrnum;
     dsp=tiptr->cdidst;
     tntusr=&tntinf[tntunm];
     tnoscb=&tntusr->tnoscb;
     ASSERT(tiptr->socket == nfyskt);
     for (attempt=0 ; attempt < 10 ; attempt++) {
          oldicx=iskopen();
          if ((nroom=dsp->snkwin(dsp)) == 0) {
               if (attempt == 0) {
                    actdet=0;
               }
               iskclose(oldicx);
               break;
          }
          if ((rdtact=recv(tiptr->socket,dsp->snkloc,nroom,0)) < 0) {
               switch (tcpip_errno) {
               case ECONNRESET:         /* benign disconnect               */
                    sktcnc(TNFRECV,tiptr->socket);
                    sktcnc(TNFSEND,tiptr->socket);
                    bbsdsc(tntunm);
                    break;
               case EWOULDBLOCK:        /* no more polling necessary       */
                    break;
               default:                 /* error condition                 */
                    svrfail(tntunm,"RECEIVE",tiptr->server->port);
                    break;
               }
               rdtact=0;
               iskclose(oldicx);
               break;
          }
          if (rdtact == 0) {
               if (attempt == 0) {
                    sktcnc(TNFRECV,tiptr->socket);
                    sktcnc(TNFSEND,tiptr->socket);
                    bbsdsc(tntunm);
               }
               iskclose(oldicx);
               break;
          }
          nbytes=iacdec(dsp->snkloc,rdtact);
          if (!(tntusr->tnoscb.flags&TNTCBN)) {
               nbytes=crndec(&tntusr->crnul,dsp->snkloc,nbytes);
          }
          dsp->didmov(dsp,nbytes);
          iskclose(oldicx);
          if (tip->outsnk.bufcnt > 0) {    /* \ These lines appear useless */
               tcpsnd(tip);                /*  >for echo, but we're too    */
          }                                /* / timid to remove them.      */
          if (rdtact < nroom) {    /* recv() didn't exhaust sink's window, */
               break;              /* no point in trying to recv() again   */
          }
     }
}

INT
ftsops(                            /* Monitor Telnet ops using FTSCOPE     */
INT code,                          /* see TNCXXXX codes in TNO.H           */
INT parm)                          /* see TNCXXXX codes in TNO.H           */
{                                  /* tnoscb, tntunm are implicit inputs   */
                                   /* (returns -1 for passing to tnoini()) */
     INT nbytes=0;                 /* also counts bytes received           */

     switch (code) {
     case TNCWIWO:                 /* WILL (parm=1) or WONT (parm=0) recd  */
          if (whomon == tntunm) {
               ftscope(FTSCMT,spr("RTNO  %s %s",parm ? "WILL" : "WONT",
                                  optname(tnoscb->opid)));
          }
          nbytes=3;
          break;
     case TNCDODO:                 /* DO (parm=1) or DONT (parm=0) recd    */
          if (whomon == tntunm) {
               ftscope(FTSCMT,spr("RTNO  %s %s",parm ? "DO  " : "DONT",
                                  optname(tnoscb->opid)));
          }
          nbytes=3;
          break;
     case TNCSBSE:                 /* IAC SB ... IAC SE recd, see opid     */
          if (whomon == tntunm) {
               ftscope(FTSCMT,spr("RTNO  SB %s (%d bytes follow) SE",
                                  optname(tnoscb->opid),tnoscb->stgcnt));
               ftscope(FTSINS,tnoscb->stg,tnoscb->stgcnt);
          }
          nbytes=5+tnoscb->stgcnt;
          break;
     case TNCOTHR:                 /* IAC <other> recd (parm=<other>)      */
          if (whomon == tntunm) {
               ftscope(FTSCMT,spr("RTNO  %s",cmdname(parm)));
          }
          nbytes=2;
          break;
     }
     tcpipinf[tntunm].bytcnt+=nbytes;
     return(-1);
}

static INT
tntops(                            /* Telnet option exit poINT handler     */
INT code,                          /* see TNCXXXX codes in TNO.H           */
INT parm)                          /* see TNCXXXX codes in TNO.H           */
{
     INT rc=-1;
     static CHAR synch1[]={IAC,IP,IAC};
     static CHAR synch2[]={DM};
     CHAR *cp;

     ftsops(code,parm);
     switch (code) {
     case TNCWIWO:                 /* WILL (parm=1) or WONT (parm=0) recd  */
          switch (tnoscb->opid) {
          case TNTOPT_SGA:
               tnoyes(TNTCSG);
               break;
          case TNTOPT_BINARY:      /* issue of sending BINARY to client    */
               tnoyes(TNTCBN);
               break;
          default:
               tnono(0);           /* ignore other WONTs, deny other WILLs */
               break;
          }
          break;
     case TNCDODO:                 /* DO (parm=1) or DONT (parm=0) recd    */
          switch (tnoscb->opid) {
          case TNTOPT_SGA:
               if (!parm) {
                    shocst("TELNET SERVER - NON-SGA CLIENT",
                           "Client at %s does not support SGA",
                           cp=inet_ntoa(tiptr->inaddr));
                    setmbk(tntmb);
                    prfmsg(NOSGA,cp);
                    outprf(tntunm);
                    rstmbk();
               }
               else {
                    tnoyes(TNTSSG);
               }
               break;
          case TNTOPT_ECHO:
               tnoyes(TNTECH);
               break;
          case TNTOPT_BINARY:      /* issue of receiving BINARY from client*/
               tnoyes(TNTSBN);
               break;
          default:                 /* ignore other DONTs, deny other DOs   */
               tnono(0);
               break;
          }
          break;
     case TNCOTHR:                 /* IAC <other> recd (parm=<other>)      */
          switch (parm) {
          case AYT:
               btuclo(tntunm);
               btucli(tntunm);
               btuolk(tntunm,0);
               setmbk(tntmb);
               prfmsg(AYTREPL);
               outprf(tntunm);
               rstmbk();
               break;
          case EC:
               rc='\b';
               break;
          case AO:
               btuclo(tntunm);
               btucli(tntunm);
               btuolk(tntunm,0);
               if (send(tiptr->socket,synch1,sizeof(synch1),MSG_OOB) !=
                                             sizeof(synch1)
                || send(tiptr->socket,synch2,sizeof(synch2),0) !=
                                             sizeof(synch2)) {
                    opovfl++;
               }
               if (whomon == tntunm) {
                    ftscope(FTSCMT,"XOOB  <IAC IP> <IAC DM>");
               }
               rc='O'&0x1F;
               break;
          case IP:
               irupt=1;
               break;
          }
          break;
     case TNCSEND:
          sndop3(IAC,parm,tnoscb->opid);
          break;
     }
     return(rc);
}

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

     buffer[0]=c1;
     buffer[1]=c2;
     buffer[2]=c3;
     if (!sndop(tiptr,buffer,3)) {
          opovfl++;
     }
     if (whomon == tiptr->unum) {
          ftscope(FTSCMT,spr("XTNO  %s %s",cmdname(c2),optname(c3)));
     }
}

static VOID
tnthup(VOID)                       /* Telnet-specific hangup handling      */
{
     tntsesend();
     (*oldhup)();
}

static VOID
tntrst(VOID)                       /* Telnet-specific rstchn() handling    */
{
     setmem(&tntinf[usrnum],sizeof(struct tntinf),0);
     (*oldrst)();
}

static VOID
tntlat(VOID)                       /* Telnet-specific lonstf w/lonaud      */
{
     if (tcpipinf[usrnum].server != NULL
      && sameas(tcpipinf[usrnum].server->name,TNTNAME)) {
          shocst("USER LOGON VIA TELNET","User-ID: %s, from %s",
                  usaptr->userid,inet_ntoa(tcpipinf[usrnum].inaddr));
     }
     (*oldlat)();
}

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

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     if (!(tntinf[tip->unum].tnoscb.flags&TNTSBN)) {    /* non-BINARY case */
          nmove=min(nwant,MCBSIZ/2);
          if (nmove > 0) {
               movmem(srcloc,mcbuff,nmove);
               crnenc(mcbuff,nmove,MCBSIZ);
               srcloc=mcbuff;
               nwant=nmove;
          }
     }
     nffnot=scanch(srcloc,IAC,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) {
                    svrfail(tip->unum,"SEND",tip->server->port);
               }
               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,tcpsnd,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;
     tcpsnd(tip);
     restore_cdi_interrupts;
     return((UINT)(sp-srcloc));
}

USHORT
tntskw(                            /* snkwin() for Telnet sessions         */
struct datstm *dsp)                /* DataStream structure                 */
{                                  /* returns 1/2 room in buffer (leaving  */
                                   /* room to double IAC's if necessary)   */
     unsigned int rc;
     dealwith_cdi_interrupts;
#ifdef TCPIP_CAN_SENDBA
     struct tcpipinf *tip;

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     dsp->snkloc=tcposb;
     dsp->snkmax=sndwin/2;
     rc=min(sendba(tip->socket),sndwin)/2;
     restore_cdi_interrupts;
#else
     struct tcpipinf *tip;
     INT nroom;

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     dsp->snkloc=tip->outsnk.buffer+tip->outsnk.bufcnt;
     dsp->snkmax=TCPOSZ/2;
     nroom=TCPOSZ/2-tip->outsnk.bufcnt;
     rc=nroom < 0 ? 0 : nroom;
     restore_cdi_interrupts;
#endif
     return(rc);
}

VOID
tntdmv(                            /* didmov() for Telnet sessions         */
struct datstm *dsp,                /* DataStream structure                 */
USHORT nactual)                    /* this many were moved by the source   */
{
     dealwith_cdi_interrupts;

#ifdef TCPIP_CAN_SENDBA
     struct tcpipinf *tip;
     INT nsent;

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     if (nactual > 0 && !(tip->flags&TCPDSC)) {
          if (!(tntinf[tip->unum].tnoscb.flags&TNTSBN)) {
               nactual=crnenc(tcposb,nactual,sndwin);
          }
          nactual=iacenc(tcposb,nactual,sndwin);
          nsent=send(tip->socket,tcposb,nactual,0);
          if (nsent == -1) {
               svrfail(tip->unum,"SEND",tip->server->port);
          }
          else if (nsent != nactual) {
#ifdef DEBUG
               catastro("tntdmv() failure:  only sent %u out of %u bytes",
                         nsent,nactual);
#else
               shocst("TNTDMV() FAILURE","Only sent %u out of %u bytes",
                       nsent,nactual);
#endif
          }
     }
     restore_cdi_interrupts;
#else
          struct tcpipinf *tip;

          allow_cdi_interrupts;
          tip=(struct tcpipinf *)dsp;
          if (!(tntinf[tip->unum].tnoscb.flags&TNTSBN)) {
               nactual=crnenc(dsp->snkloc,nactual,TCPOSZ-tip->outsnk.bufcnt);
          }
          nactual=iacenc(dsp->snkloc,nactual,TCPOSZ-tip->outsnk.bufcnt);
          tip->outsnk.bufcnt+=nactual;
          tcpsnd(tip);
          restore_cdi_interrupts;
#endif
}

/*--- Semi-generic routines (used by various ICO modules) ---*/

INT
iacenc(                            /* Encode IAC -> <IAC IAC> in place     */
CHAR *buffer,                      /* input unencoded data, output encoded */
INT nchars,                        /* number of unencoded bytes input      */
INT size)                          /* max room in buffer (precautionary)   */
{                                  /* returns number of encoded bytes      */
     INT nexp,i,nffnot;
     CHAR *cp;

     nexp=nchars;
     cp=buffer;
     for (i=0 ; i < nchars ; i++) {
          nffnot=scanch(cp,IAC,nchars-i);
          i+=nffnot;
          cp+=nffnot;
          if (i >= nchars || nexp >= size) {
               break;
          }
          movmem(cp,cp+1,nchars-i);
          cp+=2;
          nexp++;
     }
     return(nexp);
}

INT
iacdec(                            /* Decode Telnet <IAC>-prefixed codes   */
CHAR *buffer,                      /* input encoded data, output decoded   */
INT nchars)                        /* number of encoded bytes input        */
                                   /* returns num bytes after decoding     */
                                   /* tnoscb is implicit input             */
{                                  /* calls tnoini()-designated handler    */
     CHAR *ap;                     /* ahead ptr, reading undecoded data    */
     CHAR *bp;                     /* behind ptr, writing decoded data     */
     INT tch;
     UINT n;
     INT i;

     for (ap=bp=buffer,i=0 ; i < nchars ; i++) {
          if (tnoscb->state == TNOQSC && *ap != IAC) {
               if ((n=scanch(ap,IAC,nchars-i)) > 0 && ap != bp) {
                    movmem(ap,bp,n);
               }
               bp+=n;
               ap+=n;
               i+=n-1;
          }
          else {
               switch (tch=tnohose(*ap++)) {
               case -1:
                    break;
               default:
                    *bp++=(CHAR)tch;
                    break;
               }
          }
     }
     return((INT)(bp-buffer));
}

INT
decanp(                            /* decode addr & port h1,h2,h3,h4,p1,p2 */
CHAR *anp,                         /* address & port (destroyed in process)*/
struct in_addr *inaddr,            /* address where to put host address    */
USHORT *port)                      /* address where to put port            */
                                   /* output is in "n"=network byte order  */
{                                  /* returns 1=ok, 0=bad format           */
     CHAR *cp,*dp;
     INT i;

     cp=strtok(anp,",");
     dp=(CHAR *)inaddr;
     for (i=0 ; i < 4 ; i++) {
          if (cp == NULL) {
               return(0);
          }
          *dp++=(CHAR)atoi(cp);
          cp=strtok(NULL,",");
     }
     dp=(CHAR *)port;
     for (i=0 ; i < 2 ; i++) {
          if (cp == NULL) {
               return(0);
          }
          *dp++=(CHAR)atoi(cp);
          cp=strtok(NULL,",");
     }
     return(cp == NULL);
}

CHAR *
encanp(                            /* encode address & port for PORT cmd   */
ULONG naddr,                       /* IP address, network order            */
USHORT nport)                      /* TCP port, network order              */
{                                  /* returns formatted string             */
     CHAR *a,*p;
     static CHAR buffer[24];

     a=(CHAR *)&naddr;
     p=(CHAR *)&nport;
     sprintf(buffer,"%d,%d,%d,%d,%d,%d",a[0],a[1],a[2],a[3],p[0],p[1]);
     return(buffer);
}

/*--- Telnet server-specific accounting and session end ---*/

static VOID
dectntd(VOID)                      /* Telnet server online accounting      */
{
     if (tntinf[decusn].flags&TNTCHN) {
          deccst+=(tntchg>>2)+((decusp->minut4&3) < (tntchg&3));
     }
     (*olddec)();
}

static VOID
tntsesend(VOID)                    /* end of a Telnet server session       */
{
     LONG newcnt,bytcnt;

     if (tntinf[usrnum].flags&TNTCHN) {
          newcnt=bturep(usrnum,CNTCHR)+tcpipinf[usrnum].bytcnt;
          bytcnt=newcnt-tntinf[usrnum].bytlon;
          tntinf[usrnum].bytlon=newcnt;
          if (usrptr->tckonl > tcpipinf[usrnum].tckcon) {
               tfcchg(bytcnt,tntkchg);
               if (tntdbt) {
                    audtcptfc("TELNET SERVER SESSION ENDED",bytcnt);
               }
          }
     }
}

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

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

     allow_cdi_interrupts;
     ntotal=0;
     for (attempt=0 ; attempt <= 2 && nwant > 0 ; attempt++) {
          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);
}
#endif
