/***************************************************************************
 *                                                                         *
 *   TCPIP.C                                                               *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   TCP/IP for the Internet Connectivity Option for Worldgroup,           *
 *   based upon Pacific Softworks' Fusion, or Ipswitch's Piper/IP          *
 *   TCP/IP protocol kernel.                                               *
 *                                                                         *
 *   Ipswitch's Piper/IP support          - RNStein  5/25/94               *
 *   Pacific Softworks' Fusion port       - RNStein  4/11/95               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "remote.h"
#include "tcpip.h"
#include "filtcp.h"
#include "phasedbg.h"
#include "galtcpip.h"
#include "tcport.h"
#if !defined(UNIX) && !defined(GCWINNT)
#include "galpkt.h"
#include "crit.h"                  /* HACK */
#endif // !UNIX && !NT
#include "mimectyp.h"

#ifdef GCWINNT
#include "socklst.h"
#endif

#ifdef PACSOFT
#include "galhpac.h"
#endif

#define FILREV "$Revision: 75 $"

static VOID findip(VOID);
#ifndef GCWINNT
static VOID findpv(VOID);
#endif // GCWINNT
static INT lsnsvr(struct svrinf *sip);
static VOID newcall(struct svrinf *sip);
static VOID cycleCall(VOID);
static GBOOL getCall(struct svrinf *sip,INT *psocket);
static GBOOL callReadiness(struct svrinf *sip,INT *ijr,INT *unum);
static VOID dispatchCall(struct svrinf *sip,INT ijr,INT unum,INT skt);
static VOID bustCall(struct svrinf *sip,INT skt,CHAR *why);
static INT qtyCall(struct svrinf *sip);
static VOID aqin(struct callque *cque,struct callinf *cinf);
static VOID aqout(struct callque *cque,struct callinf *cinf);
static GBOOL aqempty(struct callque *cque);
static GBOOL aqfull(struct callque *cque);
static VOID tcpsys(VOID);
#ifdef PACSOFT
static INT cdlsock(u16 flags,INT skt);
#endif // PACSOFT
#ifdef UNIX
static VOID tcpipserv(INT idx);
#endif // IPSWITCH
static VOID hdlsock(INT idx,INT skt);
static GBOOL dfthstalw(ULONG hostip);
static VOID tcprst(VOID);
static VOID uscauto(INT unum);
static VOID tcpfin(VOID);
static CHAR *tvar_ipserver(VOID);
static CHAR *tvar_ipclient(VOID);
static VOID init_cdi_interrupts(VOID);
#ifdef GCWINNT
// static VOID findNTIPAddr(VOID);  HACK remove
// static VOID findWin95IPAddr(VOID);
static VOID findWin32IPAddr(VOID);
#endif // GCWINNT

HMCVFILE tcpmg=NULL;               /*--- galtcpip.mcv stuff             ---*/
CHAR *ipaddr;                      /* this BBS's IP address                */
ULONG ipaddrb;                     /* IPADDR, binary form, netw byte order */
ULONG iproutb;                     /* IPROUT, (router IP), netw byte order */
ULONG netmaskb;                    /* NETMASK, (perh derived from IPADDR)  */
UINT netmtu;                       /* NETMTU, MTU of primary Internet conn */
CHAR audinc;                       /* Audit Trail record incoming calls?   */
CHAR audrej;                       /* Audit Trail reject calls due to busy?*/
CHAR audtrm;                       /* Audit Trail termination of calls?    */
CHAR audsverr;                     /* Audit Trail report server TCP/IP errs*/
INT sndwin;                        /* SO_SNDBUF socket option              */
INT rcvwin;                        /* SO_RCVBUF socket option              */
CHAR *hostdeny;                    /* file of IP's to deny access          */

                                   /*--- Global vars for fd_scanxxx()'s ---*/
#ifdef GCWINNT
fd_set *fdscptr;
#else
UINT *fdscptr;                     /* pointer to an fd_set from select()   */
#endif
UINT fdscbits;                     /* 16 bits at a time  from fdscptr[i]   */
UINT fdscmsk;                      /* shifts through 16 mask values        */
CHAR fdscj;                        /* counts 0..15 and back again          */
INT fdscskt;                       /* counts sockets, 0..FD_SETSIZE-1      */

                                   /*--- other public vars               ---*/
struct svrinf *svrhead=NULL;       /* head of linked svr list (NULL=none)  */
struct tcpipinf *tcpipinf;         /* array[nterms] of TCP/IP-channel info */
struct sockaddr_in claddr;         /* client address (when incall()'ing)   */
INT clskt;                         /* socket to client (server may use to  */
                                   /* send message when incall(0))         */
INT nfyskt;                        /* socket you're being notified about   */
INT sndact;                        /* sndmgr() records no. of bytes sent   */
CHAR *tcposb=NULL;                 /* PACSOFT: global output staging buffer*/
ULONG selewb=0UL;                  /* count select()/nselect() EWOULDBLOCKs*/
ULONG accewb=0UL;                  /* count accept() EWOULDBLOCKs          */

struct sktmap *sktmap;             /* maps socket numbers -> event handlers*/
                                   /* This "virtual 2D" array is also      */
                                   /* indexed by TNFNUM -- see the         */
                                   /* sktmapped() macro in TCPORT.H.       */

#ifdef GCWINNT
INT *sktarr;                       /* array of sockets currently in use    */
#endif
INT *sktunm;                       /* maps socket number -> user number    */

#define TNFRA 0                    /* socket map for RECV and ACCEPT       */
#define TNFSC 1                    /* socket map for SEND and CONNECT      */
#define TNFNUM 2                   /* possible indices in sktmap[][] array */

INT tnfidx[TNFQUAN]={              /* map tnftype -> inner sktmap[][] index*/
     TNFRA,                        /* TNFRECV -> TNFRA                     */
     TNFSC,                        /* TNFSEND -> TNFSC                     */
     TNFRA,                        /* TNFACCP -> TNFRA                     */
     TNFSC,                        /* TNFCONN -> TNFSC                     */
};
INT tnfclosed=0;                   /* notifies event handler of peer close */
                                   /* (set before sktnfy()'s evthdl is     */
                                   /* called, should be 0 all other times) */

GBOOL (*hstalw)(                   /* host allowable vector (TRUE=allowed) */
      ULONG hostip)=dfthstalw;
INT ijreas;                        /* elaborates on ->incall(0)            */

#ifdef PACSOFT                     /*--- Pacific Softworks specific vars --*/
CHAR tcpip_kid[]="PACSOFT";        /* TCP/IP kernel implementation ID      */
INT numnfy=0;                      /* number of sockets interested in      */
struct sel *sktsel;                /* sockets of interest (for nselect())  */

INT selflags[TNFQUAN]={            /* map TNFXXXX to nselect()'s flags     */
     READ_NOTIFY,                  /*   TNFRECV -> READ_NOTIFY             */
     WRITE_NOTIFY,                 /*   TNFSEND -> WRITE_NOTIFY            */
     ACCEPT_NOTIFY,                /*   TNFACCP -> ACCEPT_NOTIFY           */
     CONNECT_NOTIFY,               /*   TNFCONN -> CONNECT_NOTIFY          */
};
INT sktcompress=0;            /* 1=sktsel[] table needs weeding of    */
                              /* unused entries (inflags=BASE_NOTIFY) */
CHAR *imeth;                  /* Internet interface method (ETHER,etc)*/
#endif // PACSOFT

#ifdef IPSWITCH                    /*--- Ipswitch specific variables ------*/
CHAR tcpip_kid[]="IPSWITCH";       /* TCP/IP kernel implementation ID      */
#ifndef  GCWINNT
struct fd_set
     sktfds[TNFNUM],               /* socket bitmaps for socket notificatn */
     skttmp[TNFNUM];               /* copies for passing to select()       */
#endif // !GCWINNT

static CHAR protoerr=0;            /* TCP/IP stack missing?  (EPROTOTYPE)  */
INT selflags[TNFQUAN];             /* (stub)                               */
VOID *sktsel=NULL;                 /* (stub)                               */
INT numnfy=0;                      /* (stub)                               */
#endif // IPSWITCH

#ifdef GCWINNT
static VOID
CreateTcpWindow(VOID);             /* Create Asynchronous sockets Window   */

static VOID
SelectSetup(                       /* Setup the socket to select on        */
INT iSocket);                      /*   Socket to select on                */

LRESULT CALLBACK                   /*   Return window status               */
tcpwproc(                          /* TCP/IP Windows Procedure             */
HWND hWnd,                         /*   Handle to TCP/IP Window            */
UINT message,                      /*   Windows message to process         */
WPARAM wParam,                     /*   wParam == Socket #                 */
LPARAM lParam);                    /*   lParam == Events/Errors            */

#define TCPCNM "TCPWC"             /* name of TCP/IP window class          */

#define WM_SOCKET_NOTIFY      (WM_USER+500)       /* Windows message to receive      */

static WNDCLASS tcpwc;
static CHAR wcname[]=TCPCNM;
static HWND tcpwin=NULL;
static HANDLE tcpinst=NULL;

INT tnftrans[TNFQUAN]={            /* map tnftype -> inner sktmap[][] index*/
     FD_READ,                      /* TNFRECV -> FD_READ                   */
     FD_WRITE,                     /* TNFSEND -> FD_WRITE                  */
     FD_ACCEPT,                    /* TNFACCP -> FD_ACCEPT                 */
     FD_CONNECT,                   /* TNFCONN -> FD_CONNECT                */
};
#endif    // defined(IPSWITCH && GCWINNT)


/*--- local vars ---*/
static struct sockaddr_in myaddr;  /* local server address                 */
static struct sockaddr_in rhaddr;  /* remote host being connect()ed to     */
static INT reuseaddr=1;            /* 1=quicker down-up cycle 0=TIME_WAIT  */
static VOID (*oldrst)(VOID);       /* for recording (*hdlrst)() vector     */
static VOID (*oldsys)(VOID);       /* for recording (*syscyc)() vector     */
static VOID (*oldfin)(VOID);       /* old value of module[0]->finrou       */
static GBOOL tcpinit=FALSE;        /* init__tcpip() called?                */
static INT everrst=0;              /* (*hdlrst)() ever called?  (initdn?)  */
#ifdef TCPIP_CAN_SENDBA
static struct bufstm tplosk={{     /* template for output sink bufstm      */
          tcpskw,                  /* return how much room sink has now    */
          hviad,                   /* pass one byte to the sink            */
          tcpmit,                  /* sink moves bytes (nactual is fedback)*/
          tcpdmv,                  /* 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              */
};
#else
static struct bufstm tplosk={{     /* template for output sink bufstm      */
          swbufs,                  /* return how much room sink has now    */
          hobufs,                  /* pass one byte to the sink            */
          tcpmit,                  /* sink moves bytes (nactual is fedback)*/
          cdi_dmbufs,              /* 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              */
};
#endif // TCPIP_CAN_SENDBA

#ifdef GCWINNT
static GBOOL fWinSockInit=FALSE;
#endif // GCWINNT

#ifdef GCWINNT
struct linger lingno={             /* linger structure fed to setsockopt   */
     1,0                           /* for unconditional port closure       */
};
ULONG asno=1;                      /* variable passed to ioctlsocket       */
#endif

#ifdef UNIX
#define RELPRIV  relpriv()
#define GETPRIV  getpriv()
#else
#define RELPRIV
#define GETPRIV
#endif // UNIX


// For dynamic Hostdeny.txt

#define SMLBLK   16                /* # items to alloc in small array      */
struct iprange {                   /* IP range tracking structure          */
     ULONG low;                    /*   low end of range                   */
     ULONG high;                   /*   high end of range                  */
};

struct timestamp {                 /* time stamp structure                 */
     USHORT date;                  /*   DOS packed date                    */
     USHORT time;                  /*   DOS packed time                    */
};

CHAR *iprLocalFile=NULL;           /* file w/list of local IP ranges       */
struct iprange *iprLocal=NULL;     /* array of local IP ranges             */
INT iprLocalNum=0;                 /* number of local IP ranges in array   */
struct timestamp iprLocalStamp={0,0}; /* time local IP range file loaded   */
CHAR *iprBlockFile=NULL;           /* file w/list of blocked IP ranges     */
struct iprange *iprBlock=NULL;     /* array of blocked IP ranges           */
INT iprBlockNum=0;                 /* number of blocked IP ranges in array */
struct timestamp iprBlockStamp={0,0}; /* time blocked IP range file loaded */

VOID iprCheckFile(const CHAR *fileName,struct iprange **ppRange,INT *pNumber,
                  struct timestamp *pStamp);
GBOOL iprCheckIP(struct iprange *rangeArr,INT nRanges,ULONG ip);
GBOOL iprReadFile(const CHAR *fileName,struct iprange **ppRange,INT *pNumber);
GBOOL iprReadLine(CHAR *buf,size_t bufSiz,FILE *fp);
GBOOL iprProcLine(CHAR *rangeLine,struct iprange *rangeBuf);
GBOOL iprDecodeIP(ULONG *pIP,const CHAR *ipStr);
INT compStamp(struct timestamp stamp1,struct timestamp stamp2);
#include "gme.h"


VOID EXPORT
init__galtcpip(VOID)
{
     init__tcpip();
}

VOID EXPORT
init__tcpip(VOID)                  /* Initialize TCP/IP (ICO add-ons MUST  */
                                   /* call at top of their init__xxx()'s)  */
{
     INT i;
#ifdef PACSOFT
     CHAR *cp;
     CHAR *devnam;
#endif

     if (tcpinit) {
          return;
     }
     tcpinit=TRUE;
     tcpmg=opnmsg("galtcpip.mcv");
#ifdef PACSOFT
          imeth=strdup(lastwd(getmsg(IMETH)));
          if (sameas(imeth,"ETHER")) {
               devnam="pekd";
          }
          else if (sameas(imeth,"SLIP")) {
               devnam="slip0";
          }
          else if (sameas(imeth,"CSLIP")) {
               devnam="cslip0";
          }
          else if (sameas(imeth,"PPP")) {
               devnam="ppp0";
          }
          else {
               catastro("Configuration option IMETH "
                        "is set to an invalid value: %s",imeth);
          }
     so_cnt=numopt(MAXSOCK,10,250);
#endif
     hostdeny=stgopt(HOSTDENY);
     audinc=ynopt(AUDINC);
     audrej=ynopt(AUDREJ);
     audtrm=ynopt(AUDTRM);
     audsverr=ynopt(AUDSVERR);
     sndwin=numopt(SNDWIN,1,32767);
     rcvwin=numopt(RCVWIN,1,32767);
#if !defined(UNIX) && !defined(GCWINNT)
     sockmem=numopt(SOCKMEM,1,32767);
#if defined(IPSWITCH)
     _soisfd=0;
#endif
#endif
     myaddr.sin_family=AF_INET;
     setmem(myaddr.sin_zero,sizeof(myaddr.sin_zero),0);
     rhaddr.sin_family=AF_INET;
     setmem(rhaddr.sin_zero,sizeof(rhaddr.sin_zero),0);
#ifdef PACSOFT
     shuttmo=numopt(SHUTTMO,-1,32767);
     sktsel=(struct sel *)alczer(NUMSOCKETS*sizeof(struct sel));
     tcposb=alcmem(sndwin);
#endif
#ifdef IPSWITCH
#ifndef GCWINNT
     FD_ZERO(&sktfds[TNFRECV]);
     FD_ZERO(&sktfds[TNFSEND]);
#endif // !GCWINNT
#endif // IPSWITCH
     sktmap=(struct sktmap *)alczer(TNFNUM*NUMSOCKETS*sizeof(struct sktmap));
#    ifdef GCWINNT
          sktarr=(INT *)alcmem(NUMSOCKETS*sizeof(INT));
#    endif // GCWINNT
     sktunm=(INT *)alcmem(NUMSOCKETS*sizeof(INT));
     ipaddr=getmsg(IPADDR);
     findip();
     oldfin=module[0]->finrou;
     module[0]->finrou=tcpfin;
#if !defined(UNIX) && !defined(GCWINNT) && defined (PACSOFT)
     if (sameas(imeth,"ETHER")) {
          if (sameas(cp=getmsg(NETMASK),"AUTO")) {
               if (!dftnetmask(ipaddrb,&netmaskb)) {
                    catastro("Option NETMASK cannot be set to \"AUTO\" when\n"
                             "option IPADDR is not a class A, B, or C address.");
               }
          }
          else {
               if ((netmaskb=inet_addr(cp)) == INET_ADDR_ERR) {
                    catastro("Configuration option NETMASK is not a valid\n"
                             "IP address: \"%s\".",cp);
               }
          }
          if (!chknetmask(netmaskb)) {
               catastro("Configuration option NETMASK is not a valid\n"
                        "network mask: \"%s\".",cp);
          }
          if ((iproutb=inet_addr(cp=getmsg(IPROUT))) == INET_ADDR_ERR) {
               catastro("Configuration option IPROUT is not a valid\n"
                        "IP address: \"%s\".",cp);
          }
          if ((iproutb&netmaskb) != (ipaddrb&netmaskb)) {
               catastro("Configuration option IPROUT specifies an IP address\n"
                        "that's not on your network: %s",cp);
          }
          findpv();
     }
     else {
          iproutb=ipaddrb;
          netmaskb=0UL;
     }
     netmtu=numopt(NETMTU,46,1500);
     dbgtcp=ynopt(DBGTCP);
     dbgtcpfn=stgopt(DBGTCPFN);
     do_check_heap=ynopt(DBGTCPHP);
     timskep=ynopt(DBGTSKEP);
#endif
     for (i=0 ; i < NUMSOCKETS ; i++) {
#         ifdef GCWINNT
               sktarr[i]=-1;
#         endif
          sktunm[i]=-1;
     }
     oldrst=hdlrst;
     hdlrst=tcprst;
     tcpipinf=(struct tcpipinf *)alczer(nterms*sizeof(struct tcpipinf));
     oldsys=syscyc;
     syscyc=tcpsys;
     register_textvar("IP_BBS",tvar_ipserver);
     register_textvar("IP_SERVER",tvar_ipserver);
     register_textvar("IP_CLIENT",tvar_ipclient);
     init_cdi_interrupts();
     tcpkinit();
#if !defined(UNIX) && !defined(GCWINNT) && defined(PACSOFT)
     if ((tcpip_errno=ll_config(devnam,ipaddrb,netmaskb,netmtu)) != 0) {
          catastro("Network configuration error %d: %s",
                   tcpip_errno,tcpErrStg(tcpip_errno));
     }
     if ((tcpip_errno=ll_route(devnam,iproutb,1)) != 0) {
          catastro("Network routing error %d: %s",
                   tcpip_errno,tcpErrStg(tcpip_errno));
     }
#endif
     initfiltcp();
#ifdef TCPKFIN_EXISTS
     hook_shutdown(tcpkfin);
#endif
     lmimctyp(getmsg(CNTFIL));
#ifdef GCWINNT
     CreateTcpWindow();
#endif // GCWINNT
}

VOID EXPORT
initwc__galtcpip(VOID)
{
     init__tcpip();
}


#ifdef PACSOFT
static VOID
findip(VOID)                       /* find out our IP address (PACSOFT)    */
                                   /* (ipaddr implicit input/output)       */
{                                  /* (ipaddrb implicit output)            */
     struct in_addr ipaddri;

     if ((ipaddrb=inet_addr(ipaddr)) == INET_ADDR_ERR) {
          catastro("Configuration option IPADDR is not a valid\n"
                   "IP address: \"%s\".",ipaddr);
     }
     ipaddri.s_addr=ipaddrb;
     ipaddr=strdup(inet_ntoa(ipaddri));
}
#endif

#ifdef IPSWITCH
static VOID
findip(VOID)                       /* find out our IP address (IPSWITCH)   */
                                   /* (ipaddr implicit input/output)       */
{                                  /* (ipaddrb implicit output)            */
#ifdef GCWINNT
     if (!fWinSockInit) {
          catastro("WinSock DLL has not been initialized");
     }
     findWin32IPAddr();
#else
     INT skt;
     CHAR *ifc=NULL;
     struct ifreq ifaddr;
     struct in_addr ipaddri;
#if defined(UNIX)
     static CHAR ifaces[]="le0 ex0 eth0 tu0 eeC0 we0";
#else
     static CHAR ifaces[]="pk0 sl0 pk1 sl1";
#endif // UNIX
                                   /* interfaces to check when IPADDR=AUTO */
     if (sameas(ipaddr,"AUTO")) {
          if ((skt=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) == INVALID_SOCKET) {
               if (tcpip_errno == EPROTOTYPE) {
                    if (!protoerr) {
                         shocst("TCP/IP STACK NOT INSTALLED",
                                "SOCKET error %d",EPROTOTYPE);
                         protoerr=1;
                    }
                    ipaddrb=INADDR_NONE;
               }
               else {
                    catastro("TCP/IP SOCKET error %d\n%s",
                             tcpip_errno,tcpErrStg(tcpip_errno));
               }
          }
          else {
               ifc=strtok(ifaces," ");
               for ( ; ifc != NULL ; ifc=strtok(NULL," ")) {
                    stzcpy(ifaddr.ifr_name,ifc,IFNAMSIZ);
                    if (s_ioctl(skt,SIOCGIFADDR,(CHAR *)&ifaddr) == -1) {
                         if (tcpip_errno == EADDRNOTAVAIL) {
                              catastro("Configuration option IPADDR "
                                       "should not be set to AUTO.");
                         }
                         if (tcpip_errno != ENXIO) {
                              catastro("TCP/IP I/O CONTROL error %d\n%s",
                                       tcpip_errno,tcpErrStg(tcpip_errno));
                         }
                    }
                    else {
                         ipaddrb=((struct sockaddr_in *)
                                  (&ifaddr.ifr_addr))->sin_addr.s_addr;
                         break;
                    }
               }
               clsskt(skt);
               if (ifc == NULL) {
                    ipaddrb=INADDR_NONE;
               }
          }
     }
     else {
          if ((ipaddrb=inet_addr(ipaddr)) == INET_ADDR_ERR) {
               catastro("Configuration option IPADDR is neither AUTO\n"
                        "nor a valid IP address: \"%s\".",ipaddr);
          }
     }
     ipaddri.s_addr=ipaddrb;
     ipaddr=strdup(inet_ntoa(ipaddri));
#endif // GCWINNT
}
#endif // IPSWITCH

#ifdef GCWINNT
static VOID
findWin32IPAddr(VOID)
{
     CHAR hostName[256];
     ULONG cnfIP;
     ULONG ip;
     HOSTENT *pHostEnt;
     struct in_addr ipaddri;
     INT i;
     GBOOL fGotMatch;

     if (gethostname(hostName,sizeof(hostName)) == SOCKET_ERROR
      || (pHostEnt=gethostbyname(hostName)) == NULL) {
          catastro("gethostxxx() failed. Unable to get IP address.\n"
                   "Use CONTROL PANEL/NETWORK to install TCP/IP support");
     }
     for (i=0 ; pHostEnt->h_addr_list[i] != '\0'; i++) {
     }
     if (i == 0) {
          catastro("Unable to get IP address.\n"
                   "Use CONTROL PANEL/NETWORK to install TCP/IP support");
     }
     if (sameas(ipaddr,"AUTO")) {
          if (i == 1) {
               ip=ulngval(pHostEnt->h_addr_list[0]);
               fGotMatch=TRUE;
          }
          else {
               catastro("AUTO is not a valid setting for configuration option\n"
                        "IPADDR for a computer with multiple IP addresses.\n"
                        "Enter a valid IP address.");
          }
     }
     else {
          if ((cnfIP=inet_addr(ipaddr)) == INET_ADDR_ERR) {
               catastro("%s is not a valid IP address.",ipaddr);
          }
          for (i=0,fGotMatch=FALSE ;
                  pHostEnt->h_addr_list[i] != '\0' && fGotMatch == FALSE; i++) {
               ip=ulngval(pHostEnt->h_addr_list[i]);
               if (ip == cnfIP) {
                    fGotMatch=TRUE;
               }
          }
          // gethosbyname() can not get more than ~40 IP addresses
          // so it is ok if we can not validate IP when we have too many
          if (i > 20 && !fGotMatch) {
               ip=cnfIP;
               fGotMatch=TRUE;
               shocst("TCP/IP USES NON-VALIDATED IP","IP Address: %s",ipaddr);
          }
     }
     if (fGotMatch) {
          ipaddrb=ip;
          ipaddri.s_addr=ipaddrb;
          ipaddr=strdup(inet_ntoa(ipaddri));
     }
     else {
          catastro("Configuration option IPADDR does not match\n"
                    "Windows TCP/IP setup");
     }
}
#endif // GCWINNT

#ifndef GCWINNT
static VOID
findpv(VOID)                       /* find packet driver vector, pkt_int   */
{
#ifdef PACSOFT
          CHAR *cp;
          INT pvbad;
          INT ethmeth;

          if ((ethmeth=tokopt(ETHMETH,"ODI","PACKET","INSTALL",NULL)) == 0) {
               catastro("Invalid setting for Configuration option ETHMETH");
          }
          if (ethmeth == 3) {
               catastro("Installation failure:  Configuration option ETHMETH"
                        " has\nnot been set yet.");
          }
          cp=lastwd(getmsg(ethmeth == 1 ? ODIVEC : PKTVEC));
          if (ethmeth == 2 && sameas(cp,"AUTO")) {
               pkt_int=PDVAUTO;
               return;
          }
          if (!isxdigit('6')) {
               catastro("Internal Error with isxdigit() macro.  (Most\n"
                        "likely, your WGSERVER.EXE is older than Worldgroup\n"
                        "1.01, or it was compiled with a Borland compiler\n"
                        "older than V4.5.)");
          }
          if (sameto("0x",cp) && allhex(cp+2)) {
               pvbad=sscanf(cp+2,"%x",&pkt_int) != 1;
          }
          else if (allhex(cp)) {
               pvbad=sscanf(cp,"%x",&pkt_int) != 1;
          }
          else {
               pvbad=1;
          }
          if (pvbad || pkt_int < PDVFIRST || pkt_int > PDVLAST) {
               switch (ethmeth) {
               case 1:
                    catastro("Configuration option ODIVEC is invalid."
                             "  It must be\na hexadecimal number"
                             " between 20 and FF.");
               case 2:
                    catastro("Configuration option PKTVEC is invalid."
                             "  It must be\n AUTO or a hexadecimal number"
                             " between 20 and FF.");
               }
          }
#endif // PACSOFT
}
#endif // !GCWINNT

INT
regtcpsvr(                         /* register a TCP server                */
CHAR *name,                        /* name of service (e.g. FTP, Telnet)   */
INT port,                          /* port number (e.g. 23=telnet, 21=ftp) */
INT backlog,                       /* max # of colliding incoming calls    */
VOID (*incall)(                    /* incoming call handler                */
     INT gotchn))                  /* 1=chan (curusr) assigned, 0=not avail*/
{                                  /* returns 1=installed, 0=no stack      */
     struct svrinf *sip;

     ASSERT(tcpinit);
     if (numcdi("TCP/IP") == 0) {
          return(0);
     }
     sip=(struct svrinf *)alczer(sizeof(struct svrinf));
     sip->name=name;
     sip->next=svrhead;
     sip->port=port;
     sip->backlog=backlog;
     sip->incall=incall;
     sip->maxstt=nterms;
     if (lsnsvr(sip)) {
          svrhead=sip;
          return(1);
     }
     return(0);
}

VOID
svrMaxque(                         /* give server an incoming call queue   */
INT maxque)                        /*   max number of calls to queue       */
                                   /*   (call only immed after regtcpsvr() */
{                                  /*   returns 1)                         */
     ASSERT(svrhead->callque.maxque == 0);
     svrhead->callque.maxque=maxque;
     if (maxque > 0) {
          svrhead->callque.que=alczer(maxque*sizeof(struct callinf));
     }
}

VOID
svrMaxstt(                         /* impose limit on server connections   */
INT maxstt)                        /*   max number simul server connections*/
                                   /*   (call only immed after regtcpsvr() */
{                                  /*   returns 1)                         */
     svrhead->maxstt=maxstt;
}

static INT                         /*   returns 1=installed, 0=no stack    */
lsnsvr(                            /* prepare server socket (listen, etc.) */
struct svrinf *sip)                /*   server info                        */
{
     GETPRIV;
     if ((sip->svrskt=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
                                                            == INVALID_SOCKET) {
          RELPRIV;
          if (tcpip_errno == EPROTOTYPE) {
               return(0);
          }
          svrcatast("SOCKET",sip->port);
     }
     nonblock(sip->svrskt);
#ifndef GCWINNT  // do not allow port reuse under NT since other services can be running
     if (setsockopt(sip->svrskt,SOL_SOCKET,SO_REUSEADDR,
                    (CHAR *)&reuseaddr,sizeof(reuseaddr)) == SOCKET_ERROR) {
          RELPRIV;
          svrcatast("REUSED ADDRESS",sip->port);
     }
#endif // GCWINNT
     myaddr.sin_port=htons(sip->port);
#ifdef GCWINNT
     myaddr.sin_addr.s_addr=ipaddrb;
#else
     myaddr.sin_addr.s_addr=INADDR_ANY; // Bob had a reason to bind to ANY
#endif // GCWINNT
     if (bind(sip->svrskt,(struct sockaddr *)&myaddr,sizeof(myaddr))
                                                            == SOCKET_ERROR) {
          RELPRIV;
#ifdef GCWINNT
          if (WSAGetLastError() == WSAEADDRINUSE) {
               CHAR msgBuf[64],extMsgBuf[80];
               sprintf(msgBuf,"%s NOT INITIALIZED",sip->name);
               sprintf(extMsgBuf,"Port %d is used by another application",sip->port);
               shocst(msgBuf,extMsgBuf);
               return(0);
          }
#endif // GCWINNT
          svrcatast("BIND",sip->port);
     }
     if (listen(sip->svrskt,sip->backlog) == SOCKET_ERROR) {
          RELPRIV;
          svrcatast("LISTEN",sip->port);
     }
     setsndwin(sip->svrskt,sndwin);
     setrcvwin(sip->svrskt,rcvwin);
     sktnfy(TNFACCP,sip->svrskt,newcall,sip,-1);
     RELPRIV;
     return(1);
}

static VOID
newcall(                           /* handle incoming call on server socket*/
struct svrinf *sip)                /* info on that server                  */
{
     struct callinf cnew,cold;
     INT ijr;
     INT unum;

     if (!getCall(sip,&cnew.socket)) {
          return;
     }
     if (callReadiness(sip,&ijr,&unum)) {
          if (aqempty(&sip->callque)) {      /* fall thru empty queue      */
               dispatchCall(sip,ijr,unum,cnew.socket);
          }
          else {                             /* bubble thru non-empty queue*/
               aqout(&sip->callque,&cold);
               aqin(&sip->callque,&cnew);
               dispatchCall(sip,ijr,unum,cold.socket);
          }
     }
     else {                                  /* rejected due to full queue */
          if (aqfull(&sip->callque)) {
               dispatchCall(sip,ijr,unum,cnew.socket);
          }
          else {                             /* take place in non-full que */
               aqin(&sip->callque,&cnew);
          }
     }
}

static VOID
cycleCall(VOID)                    /* cyclic call-queue handling           */
{
     struct svrinf *sip;
     struct callinf cinf;
     INT ijr;
     INT unum;

     for (sip=svrhead ; sip != NULL ; sip=sip->next) {
          while (!aqempty(&sip->callque)) {
               if (!callReadiness(sip,&ijr,&unum)) {
                    break;
               }
               aqout(&sip->callque,&cinf);
               dispatchCall(sip,ijr,unum,cinf.socket);
          }
     }
}

/* Note that getCall() must deal with IJRDENY cases (A) so they can't jam  */
/* the queue, and (B) because they're call-specific and thus should never  */
/* even enter the queue.  The call queue is for calls held up because      */
/* the *system* can't take them, not because of any properties of the call */
/* itself (excepting the call's port, which specifies the server).         */

static GBOOL                       /*   returns TRUE=got, FALSE=didn't get */
getCall(                           /* get brand new incoming call          */
struct svrinf *sip,                /*   server info                        */
INT *psocket)                      /*   where to store connection's socket */
{
     INT nb;

     nb=sizeof(claddr);
     if ((*psocket=accept(sip->svrskt,(struct sockaddr *)&claddr,&nb))
                                                            == INVALID_SOCKET) {
          if (!tnfclosed && tcpip_errno != EWOULDBLOCK) {
               svrfail(-1,"ACCEPT",sip->port);
               clsskt(sip->svrskt);
               lsnsvr(sip);
          }
          else if (tcpip_errno == EWOULDBLOCK) {
               accewb++;             /* occasional EWOULDBLOCKs are benign */
          }
          return(FALSE);
     }
#ifdef GCWINNT
     SelectSetup(*psocket);
#endif
     if (!(*hstalw)(claddr.sin_addr.s_addr)) {
          dispatchCall(sip,IJRDENY,-1,*psocket);
          return(FALSE);
     }
     nonblock(*psocket);
     setsndwin(*psocket,sndwin);
     setrcvwin(*psocket,rcvwin);
     return(TRUE);
}

/* Notice that callReadiness() knows nothing about any specific            */
/* incoming call, except indirectly which server (TCP port) it's for.      */
/* The function simply reports the Worldgroup server's readiness for       */
/* incoming calls in general.  It has 3 outputs:  retval, *pijr, *punum.   */

GBOOL                              /*   TRUE=ready immed, FALSE=wait if pos*/
callReadiness(                     /* how ready are we to dispatch a call? */
struct svrinf *sip,                /*   server information call is for     */
INT *pijr,                         /*   reject reason (IJRXXXX in TCPIP.H) */
INT *punum)                        /*   usrnum (only if *pijr == IJRNONE)  */
{                                  /*   (caller is never req to use *punum)*/
     GBOOL retval=FALSE;

     if ((*punum=bbsfvc("TCP/IP")) == -1) {
          if (kilipg || errcod != 1) {
               *pijr=IJRSHUT;
               retval=TRUE;        /* shutdown, dispatch away all calls    */
          }
          else if (numcdi("TCP/IP") > numliccdi("TCP/IP")) {
               *pijr=IJRULIM;
          }
          else {
               *pijr=IJRFULL;
          }
     }
     else if (qtyCall(sip) >= sip->maxstt) {
          *punum=-1;
          *pijr=IJRALIM;
     }
     else {
          *pijr=IJRNONE;
          retval=TRUE;             /* ready and willing to take next call  */
     }
     return(retval);
}

static VOID
dispatchCall(                      /* dispatch call (reject OR assign chan)*/
struct svrinf *sip,                /*   server information                 */
INT ijr,                           /*   reject reason (fm callReadiness()) */
INT unum,                          /*   usrnum if IJRNONE (callReadiness())*/
INT skt)                           /*   socket for connection (either gets */
{                                  /*   closed, or responsibility passed)  */
     struct tcpipinf *tip;

     clskt=skt;
     switch (ijreas=ijr) {
     case IJRDENY:
          bustCall(sip,skt,"access denied to this host");
          break;
     case IJRSHUT:
          bustCall(sip,skt,"server shutting down");
          break;
     case IJRFULL:
          bustCall(sip,skt,spr("all %d lines busy",numcdi("TCP/IP")));
          break;
     case IJRULIM:
          bustCall(sip,skt,spr("%d-user license exceeded",btusrs));
          break;
     case IJRALIM:
          bustCall(sip,skt,spr("%d connections already",qtyCall(sip)));
          break;
     default:
          bustCall(sip,skt,"Unknown reason");
          break;
     case IJRNONE:
          curusr(unum);
          tip=&tcpipinf[usrnum];
          movmem(&tplosk,&tip->outsnk,sizeof(tip->outsnk));
          tip->outsnk.buffer=tip->outstg;
          tip->server=sip;
          movmem(&claddr.sin_addr,&tip->inaddr,sizeof(struct in_addr));
          tip->port=claddr.sin_port;
          tip->unum=usrnum;
          tip->socket=skt;
          tip->tckcon=lngtck;
          tip->cdidst=btucdi(usrnum,&tip->outsnk.datstm);
          ASSERT(tip->cdidst != NULL);
          if (audinc) {
               shocst(strupr(spr("%s CALL ACCEPTED",sip->name)),
                      "from %-16s port %u",inet_ntoa(claddr.sin_addr),
                                               ntohs(claddr.sin_port));
          }
          sip->incall(1);
     }
}

static VOID
bustCall(                          /* get rid of call                      */
struct svrinf *sip,                /*   server info                        */
INT skt,                           /*   socket of connection               */
CHAR *why)                         /*   reason to get rid of call          */
{
     usrnum=-1;
     sip->incall(0);
     clssvr(-1,skt,sip->port);
     usrnum=-1;
     rejinc(sip->name,why);
}

static INT                         /*   returns quantity, 0 to nterms      */
qtyCall(                           /* count calls assigned to a server     */
struct svrinf *sip)                /*   server info                        */
{
     INT unum;
     struct tcpipinf *tip;
     INT retval=0;

     for (unum=0,tip=tcpipinf ; unum < nterms ; unum++,tip++) {
          if (tip->server == sip) {
               retval++;
          }
     }
     return(retval);
}

static VOID
aqin(                              /* insert new call info into call que   */
struct callque *cque,              /*   call queue                         */
struct callinf *cinf)              /*   new call info                      */
{                                  /*   must never call if aqfull()        */
     ASSERT(!aqfull(cque));
     movmem(cinf,&cque->que[cque->numque++],sizeof(struct callinf));
}

static VOID
aqout(                             /* insert new call info into call que   */
struct callque *cque,              /*   call queue                         */
struct callinf *cinf)              /*   where to store call info           */
{                                  /*   must never call if aqempty()       */
     ASSERT(!aqempty(cque));
     movmem(&cque->que[0],cinf,sizeof(struct callinf));
     cque->numque--;
     if (cque->numque > 0) {
          movmem(&cque->que[1],
                 &cque->que[0],sizeof(struct callinf)*cque->numque);
     }
}

static GBOOL                       /*   TRUE=empty, FALSE=you may aqout()  */
aqempty(                           /* is call queue empty?                 */
struct callque *cque)
{
     ASSERT(cque->numque >= 0);
     return(cque->numque <= 0);
}

static GBOOL                       /*   TRUE=full, FALSE=you may aqin()    */
aqfull(                            /* is call queue full?                  */
struct callque *cque)
{
     ASSERT(cque->numque <= cque->maxque);
     return(cque->numque >= cque->maxque);
}

VOID
rejinc(                            /* reject an incoming TCP/IP call       */
CHAR *svr,                         /* name of that server                  */
CHAR *why)                         /* reason for rejection                 */
{
     if (audrej) {
          shocst(strupr(spr("%s CALL REJECTED",svr)),
                 "call from %s port %u while %s",
                 inet_ntoa(claddr.sin_addr),ntohs(claddr.sin_port),why);
     }
}

#ifdef PACSOFT
static VOID
tcpsys(VOID)                       /* TCP/IP system-cycle task:  I/O svc   */
{                                  /* (PACSOFT version)                    */
     INT idx,oldnum;
     struct sel *selptr;
     u32 nswait=0L;
     INT nfound;
     u16 outflags;

     BEG_PHASE("System cycle/TCP/IP",0);
     if (sktcompress) {
          for (idx=0,selptr=&sktsel[0] ; idx < numnfy ; idx++,selptr++) {
               if (selptr->se_inflags == BASE_NOTIFY) {
                    if (idx < numnfy-1) {
                         movmem(selptr+1,
                                selptr,(numnfy-idx-1)*sizeof(struct sel));
                    }
                    numnfy--;
               }
          }
          sktcompress=0;
     }
     pacsys1();
     nfound=nselect(sktsel,oldnum=numnfy,&nswait,(pfi_t)0,(u32)0,&tcpip_errno);
     if (nfound == -1) {
          if (tcpip_errno == EWOULDBLOCK) {
               selewb++;
          }
          else {
               catastro("TCP/IP NSELECT error %d: %s",
                        tcpip_errno,tcpErrStg(tcpip_errno));
          }
     }
     else if (nfound > 0) {
          for (idx=0,selptr=&sktsel[0] ; idx < oldnum ; idx++,selptr++) {
               if ((outflags=selptr->se_outflags) != 0) {
                    tnfclosed=(outflags&(RSHUTDOWN_NOTIFY
                                        +WSHUTDOWN_NOTIFY)) != 0;
                    if (!cdlsock(outflags,selptr->se_fd)) {
                         if ((outflags&BASE_NOTIFY) != 0) {
                              cdlsock(selptr->se_inflags,selptr->se_fd);
                         }
                    }
                    tnfclosed=0;
               }
          }
     }
     pacsys2(hrtval());
     cycleCall();
     END_PHASE("System cycle/TCP/IP",0);
     (*oldsys)();
}

static INT                         /*   returns 1=handled, 0=nothing to do */
cdlsock(                           /* conditionally handle socket's needs  */
u16 flags,                         /*   nselect()-style event-flags        */
INT skt)                           /*   socket handle                      */
{
     INT rc;

     if ((flags&(READ_NOTIFY+ACCEPT_NOTIFY)) != 0) {
          hdlsock(TNFRA,skt);
          rc=1;
     }
     else if ((flags&(WRITE_NOTIFY+CONNECT_NOTIFY)) != 0) {
          hdlsock(TNFSC,skt);
          rc=1;
     }
     else {
          rc=0;
     }
     return(rc);
}
#endif // PACSOFT

#ifdef IPSWITCH
#ifdef GCWINNT
static VOID
tcpsys(VOID)                       /* TCP/IP system-cycle task:  I/O svc   */
{                                  /* (IPSWITCH version)                   */
     static MSG* lpmsgMessages=NULL;
     static INT iMessageElements=0;

     INT iMessages;

     if (!protoerr && (tcpwin != NULL)) {
          INT iCount;
          MSG msg;

          UpdateRecalls();

          iMessages=0;
          while(PeekMessage(&msg,tcpwin,0,0,PM_REMOVE)) {

               /*  This for() loop is here to maintain backwards           */
               /*  compatability.  The possibility exists to receive       */
               /*  multiple read events on one socket.  This will weed     */
               /*  out those events.                                       */
               for (iCount=0; iCount < iMessages; iCount++) {
                    if (lpmsgMessages[iCount].wParam == msg.wParam
                     && WSAGETSELECTEVENT(lpmsgMessages[iCount].lParam)
                     == WSAGETSELECTEVENT(msg.lParam)) {
                         break;
                    }
               }
               if (iCount < iMessages) {
                    continue;
               }
               if (iMessages == iMessageElements) {
                    lpmsgMessages=(MSG*)alcrsz(lpmsgMessages,
                         sizeof(MSG)*iMessageElements,
                         sizeof(MSG)*(iMessageElements+10));
                    iMessageElements+=10;
               }
               lpmsgMessages[iMessages]=msg;
               iMessages++;
          }
          for (iCount=0; iCount < iMessages; iCount++) {
               DispatchMessage(&lpmsgMessages[iCount]);
          }
     }

     cycleCall();
     END_PHASE("System cycle/TCP/IP",0);
     (*oldsys)();
}
#else
/*
There's some trickiness below in tcpsys() that makes interactive character
echo snappier, servicing an echoed character in one tcpsys() cycle instead of
two.

The fd_setor() in tcpsys() says:  "I want to know each socket's sendability
when the application wants to know if it's either sendable OR receivable."

The fd_setand() in tcpsys() says:  "I only want to call the sendability
event handler on sockets that I already know are both sendable AND those
that the application is NOW interested in their sendability."

Due to interactive echo, the application's interest in sendability might
change after calling the receivability event handler, and tcpsys() neither
wants to call select() again, nor make the application wait until the next
tcpsys() to output the echoed bytes.  This trick allows tcpsys() to anticipate
the application's sudden need for sending that was caused by echo.

The PACSOFT version doesn't appear to support snappy echo.
*/
static VOID
tcpsys(VOID)                       /* TCP/IP system-cycle task:  I/O svc   */
{                                  /* (IPSWITCH version)                   */
     INT rc;
     static struct timeval immed;

     BEG_PHASE("System cycle/TCP/IP",0);
     immed.tv_sec=0L;
     immed.tv_usec=0L;
     movmem(sktfds,skttmp,sizeof(skttmp));
     fd_setor(&skttmp[TNFSEND],&skttmp[TNFRECV]);
     if (protoerr) {
     }
     else if ((rc=select(FD_SETSIZE,&skttmp[TNFRECV],
                                    &skttmp[TNFSEND],NULL,&immed))
                                                            == SOCKET_ERROR) {
          switch (tcpip_errno) {
          case EWOULDBLOCK:
#if !defined(UNIX) && !defined(GCWINNT)
               selwbk++;
#endif
               break;
#ifdef GCWINNT
          case WSAEINVAL:     // WinSock does not like calls to select() with no
               break;         // sockets in fd_set we just ignore the error
#endif
          case EPROTOTYPE:
               break;
          default:
               catastro("TCP/IP SELECT error %d: %s",
                        tcpip_errno,tcpErrStg(tcpip_errno));
          }
     }
     else if (rc > 0) {
          tcpipserv(TNFRA);
          fd_setand(&skttmp[TNFSEND],&sktfds[TNFSEND]);
          tcpipserv(TNFSC);
     }
     cycleCall();
     END_PHASE("System cycle/TCP/IP",0);
     (*oldsys)();
}

static VOID
tcpipserv(                         /* Service TCP/IP sockets               */
INT idx)                           /* Notification:  TNFRA or TNFSC        */
{
     for (fd_scanbeg(&skttmp[idx]) ; fd_scanmor() ; fd_scannxt()) {
          tnfclosed=0;
          hdlsock(idx,fdscskt);
     }
}
#endif // !GCWINNT
#endif // IPSWITCH

static VOID
hdlsock(                           /* handle servicing a socket            */
INT idx,                           /* Notification:  TNFRA or TNFSC        */
INT skt)                           /* socket descriptor                    */
{
     INT sktidx;
     struct sktmap *sptr;
     VOID (*evthdl)(VOID *ptr);

     sktidx=getSktIdx(skt);

     if (sktidx == -1) {           /*  Shouldn't happen, sanity checking   */
          return;
     }
     sptr=&sktmapped(idx,sktidx);
     if ((evthdl=sptr->evthdl) != NULL) {
          if ((usrnum=sktunm[sktidx]) == -1) {
               actdet=0;
          }
          else {
               ASSERTM(0 <= usrnum && usrnum < nterms, spr("usrnum=%d, nterms=%d, sktidx=%d",usrnum,nterms,sktidx));
               curusr(usrnum);
               actdet=1;
          }
          clrprf();
          nfyskt=skt;
          BEG_PHASE("TCP/IP servicing",evthdl);
          (*evthdl)(sptr->ptr);
          END_PHASE("TCP/IP servicing",evthdl);
          if (actdet) {
               usrptr->flags|=ACTIVE;
               usrptr->nazapc=0;
          }
     }
}

/*
Note: The sktnfy() call in tcpmit() picks up the ball when send() defers output
that comes from the output staging buffer.  (Output from elsewhere has other
means to try again soon:  GSBL output buffer contents await the next btuscn().)
*/

USHORT
tcpmit(                            /* TCP/IP moveit(), channel output sink */
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 */
     INT nactual;
     struct tcpipinf *tip;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;

     tip=(struct tcpipinf *)dsp;
     ASSERT(tip->socket != INVALID_SOCKET);
     ASSERT(tip->server != NULL);
     if (nwant > 0 && !(tip->flags&TCPDSC)) {
          if ((nactual=send(tip->socket,srcloc,nwant,0)) == SOCKET_ERROR) {
               if (tcpip_errno == EWOULDBLOCK) {
                    nactual=0;
               }
               else {
                    svrfail(tip->unum,"SEND",tip->server->port);
                    restore_cdi_interrupts;
                    return(0);
               }
          }
          ASSERTM(nactual >= 0 && nactual <= nwant,
                  spr("nwant=%u nactual=%d",nwant,nactual));
          if (tip->outsnk.bufcnt > 0 && nactual < nwant) {
               sktnfy(TNFSEND,tip->socket,tcpsnd,tip,tip->unum);
          }
          restore_cdi_interrupts;
          return((UINT)nactual);
     }
     restore_cdi_interrupts;
     return(0);
}

USHORT                             /*   returns room in send queue         */
tcpskw(                            /* TCP/IP snkwin() (relies on sendba()) */
struct datstm *dsp)                /*   pointer to sink's datstm structure */
{
     struct tcpipinf *tip;
     USHORT rc;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;

     tip=(struct tcpipinf *)dsp;
     ASSERT(tip->socket != INVALID_SOCKET);
     dsp->snkmax=sndwin;
     dsp->snkloc=tcposb;
     rc=min(sendba(tip->socket),sndwin);
     restore_cdi_interrupts;
     return(rc);
}

VOID
tcpdmv(                            /* TCP/IP didmov() (relies on sendba()) */
struct datstm *dsp,                /*   pointer to sink's datstm structure */
USHORT nactual)                    /*   number of bytes moved to tcposb    */
{
     struct tcpipinf *tip;
     INT nsent;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;
     if (nactual > 0) {
          tip=(struct tcpipinf *)dsp;
          if ((nsent=send(tip->socket,tcposb,nactual,0)) == SOCKET_ERROR) {
               svrfail(tip->unum,"SEND",tip->server->port);
          }
          else if (nsent != nactual) {
#ifdef DEBUG
            catastro("tcpdmv() failure:  only sent %u out of %u bytes",
               nsent,nactual);
#else
            shocst("TCPDMV() FAILURE","Only sent %u out of %u bytes",
                nsent,nactual);
#endif
          }
     }
     restore_cdi_interrupts;
}

VOID
tcpinc(                            /* read socket data into GCDI channel   */
struct tcpipinf *tip)              /* user's TCP/IP channel info           */
                                   /* application:  sktnfy(,,tcpinc,tip,)  */
{
     struct datstm *dsp;
     UINT nroom;
     INT nactual;
     INT attempt;
     INT oldicx;

     dsp=tip->cdidst;
     ASSERT(tip->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 ((nactual=recv(tip->socket,dsp->snkloc,nroom,0)) < 0) {
               switch (tcpip_errno) {
               case ECONNRESET:         /* benign disconnect               */
                    sktcnc(TNFRECV,tip->socket);
                    sktcnc(TNFSEND,tip->socket);
                    bbsdsc(usrnum);
                    break;
               case EWOULDBLOCK:        /* no more polling necessary       */
                    break;
               default:                 /* error condition                 */
                    svrfail(usrnum,"RECEIVE",tip->server->port);
                    break;
               }
               iskclose(oldicx);
               break;
          }
          if (nactual == 0) {
               if (attempt == 0) {
                    sktcnc(TNFRECV,tip->socket);
                    sktcnc(TNFSEND,tip->socket);
                    bbsdsc(usrnum);
               }
               iskclose(oldicx);
               break;
          }
          dsp->didmov(dsp,nactual);
          iskclose(oldicx);
          if (tip->outsnk.bufcnt > 0) {    /* \ These lines appear useless */
               tcpsnd(tip);                /*  >for echo, but we're too    */
          }                                /* / timid to remove them.      */
          if (nactual < nroom) {   /* recv() didn't exhaust sink's window, */
               break;              /* no point in trying to recv() again   */
          }
     }
}

VOID
tcpsnd(                            /* try to send staging buffer contents  */
struct tcpipinf *tip)              /* TCP/IP session info                  */
{
     if (tnfclosed) {
          sktcnc(TNFRECV,tip->socket);
          sktcnc(TNFSEND,tip->socket);
          bbsdsc(tip->unum);
          return;
     }
     switch(sndmgr(tip->outstg,&tip->outsnk.bufcnt,tip->socket)) {
     case -1:
          svrfail(tip->unum,"SEND",tip->server->port);
     case 0:
          sktcnc(TNFSEND,tip->socket);
          break;
     case 1:
          sktnfy(TNFSEND,tip->socket,tcpsnd,tip,tip->unum);
          break;
     }
}

INT
sndmgr(                            /* manage send()ing from a buffer       */
CHAR *buffer,                      /* buffer address                       */
UINT *bufcnt,                      /* where buffer count is stored         */
INT socket)                        /* socket handle                        */
                                   /* returns 1=not done, 0=done, -1=error */
{                                  /* sndact implicit output: # bytes sent */
     if (*bufcnt == 0) {
          sndact=0;
          return(0);
     }
     ASSERT(socket != INVALID_SOCKET);
     if ((sndact=send(socket,buffer,*bufcnt,0)) < 0) {
          sndact=0;
          if (tcpip_errno != EWOULDBLOCK) {
               return(-1);
          }
     }
     if (0 < sndact && sndact < *bufcnt) {
          movmem(buffer+sndact,buffer,*bufcnt-sndact);
     }
     ASSERT(sndact <= *bufcnt);
     if (sndact <= *bufcnt) {
          *bufcnt-=sndact;
     }
     return(*bufcnt > 0);
}

VOID
audtcptfc(                         /* Audit time and traffic for TCP server*/
CHAR *title,                       /* first line of Audit Trail message    */
LONG bytcnt)                       /* number of bytes traffic              */
{
     shocst(title,"%s, %s seconds, %s bytes traffic",
                  inet_ntoa(tcpipinf[usrnum].inaddr),
                  l2as(lngtck-usrptr->tckonl),l2as(bytcnt));
}

INT
tcpdial(                           /* initiate a TCP connection            */
struct in_addr inaddr,             /* host IP address                      */
UINT port,                         /* port number (network byte order)     */
UINT fromport,                     /* local port number (0=ephemeral)      */
INT *psocket)                      /* where to store socket handle (must   */
                                   /* be -1 whenever this socket not open) */
{                                  /* see DLXXXX return values in TCPIP.H  */
     INT serrno;

     GETPRIV;
     if (*psocket != INVALID_SOCKET) {
          clsskt(*psocket);
     }
     if ((*psocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET) {
          RELPRIV;
          return(DLERRS);
     }
     setsndwin(*psocket,sndwin);
     setrcvwin(*psocket,rcvwin);
     nonblock(*psocket);
     if (setsockopt(*psocket,SOL_SOCKET,SO_REUSEADDR,
                    (CHAR *)&reuseaddr,sizeof(reuseaddr)) == SOCKET_ERROR) {
          RELPRIV;
          serrno=tcpip_errno;
          clsskt(*psocket);
#ifdef GCWINNT
          WSASetLastError(serrno);
#else
          tcpip_errno=serrno;
#endif
          *psocket=-1;
          return(DLERRR);
     }
     myaddr.sin_port=htons(fromport);
     myaddr.sin_addr.s_addr=ipaddrb;
     if (bind(*psocket,(struct sockaddr *)&myaddr,sizeof(myaddr))
                                                            == SOCKET_ERROR) {
          RELPRIV;
          serrno=tcpip_errno;
          clsskt(*psocket);
#ifdef GCWINNT
          WSASetLastError(serrno);
#else
          tcpip_errno=serrno;
#endif
          *psocket=-1;
          return(DLERRB);
     }
     RELPRIV;
     rhaddr.sin_port=port;
     rhaddr.sin_addr.s_addr=inaddr.s_addr;
     if (connect(*psocket,(struct sockaddr *)&rhaddr,sizeof(rhaddr))
                                                            == SOCKET_ERROR) {
          if (tcpip_errno != EINPROGRESS          /* (Ipswitch's AOK) */
           && tcpip_errno != EWOULDBLOCK) {       /* (PacSoft's AOK)  */
               serrno=tcpip_errno;
               clsskt(*psocket);
#ifdef GCWINNT
          WSASetLastError(serrno);
#else
               tcpip_errno=serrno;
#endif
               *psocket=-1;
               return(DLERRC);
          }
          return(DLCING);
     }
     return(DLCNOW);
}

INT
udpdial(                           /* initiate a UDP connection            */
UINT fromport,                     /* local port number (0=ephemeral)      */
INT *psocket)                      /* where to store socket handle (must   */
                                   /* be -1 whenever this socket not open) */
{                                  /* see DLXXXX return values in TCPIP.H  */

     int serrno;

     if (*psocket != INVALID_SOCKET) {
          clsskt(*psocket);
     }
     if ((*psocket=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) == INVALID_SOCKET) {
          return(DLERRS);
     }
     setsndwin(*psocket,sndwin);
     setrcvwin(*psocket,rcvwin);
     nonblock(*psocket);
     if (setsockopt(*psocket,SOL_SOCKET,SO_REUSEADDR,
                    (CHAR *)&reuseaddr,sizeof(reuseaddr)) == SOCKET_ERROR) {
          serrno=tcpip_errno;
          clsskt(*psocket);
#ifdef GCWINNT
          WSASetLastError(serrno);
#else
          tcpip_errno=serrno;
#endif
          *psocket=-1;
          return(DLERRR);
     }
     myaddr.sin_port=htons(fromport);
     myaddr.sin_addr.s_addr=ipaddrb;
     GETPRIV;
     if (bind(*psocket,(struct sockaddr *)&myaddr,sizeof(myaddr))
                                                            == SOCKET_ERROR) {
          RELPRIV;
          serrno=tcpip_errno;
          clsskt(*psocket);
#ifdef GCWINNT
          WSASetLastError(serrno);
#else
          tcpip_errno=serrno;
#endif
          *psocket=-1;
          return(DLERRB);
     }
     RELPRIV;
     return(DLCNOW);
}

VOID
nonblock(                          /* set socket to non-blocking           */
INT skt)                           /* socket descriptor                    */
{
#ifdef PACSOFT
     nonblocking(skt);
#endif
#ifdef IPSWITCH
     INT async=1;

     s_ioctl(skt,FIONBIO,(VOID *)&async);
#endif
}

VOID
oobinline(                         /* mix OOB data with regular data       */
INT skt)                           /* socket descriptor                    */
{
#ifdef PACSOFT
     (VOID)skt;
#endif
#ifdef IPSWITCH
     INT oob=1;
     setsockopt(skt,SOL_SOCKET,SO_OOBINLINE,(CHAR *)&oob,sizeof(oob));
#endif
}

ULONG
recvbw(                            /* recv() bytes waiting (ret number of) */
INT skt)                           /* socket descriptor                    */
{
#if defined(UNIX) || defined(GCWINNT)
     (VOID)skt;
     return(0L);
#else
     ULONG nready;

#ifdef PACSOFT
     if (so_nread(skt,&nready) == 0) {
       return(nready);
     }
     return(0L);
#endif
#ifdef IPSWITCH
     if (s_ioctl(skt,FIONREAD,(CHAR *)&nready) != -1) {
          return(nready);
     }
     return(0L);
#endif
#endif
}

UINT
sendba(                            /* send() bytes able (ret number of)    */
INT skt)                           /* socket descriptor                    */
{
#ifdef PACSOFT
     ULONG nwrite;

     if (so_nwrite(skt,&nwrite) == 0) {
          return(nwrite > 0xFFFFUL ? 0xFFFFU : (UINT)nwrite);
     }
     return(0);
#endif
#ifndef TCPIP_CAN_SENDBA
     (VOID)skt;
     catastro("Cannot implement sendba()");
     return(0);
#endif
}

VOID
rwnmgr(                            /* receive window manager               */
INT socket,                        /* socket handle                        */
INT nroom,                         /* max room to advertise (- means 0)    */
INT *curwin)                       /* current advertised window variable   */
                                   /* rwnmgr() can only SHRINK window size.*/
{
     LONG newwin;                  /* window to advertise to remote sender */

     if ((newwin=min(nroom-recvbw(socket),rcvwin)) < 0L) {
          newwin=0L;
     }
     if (newwin < *curwin) {
          setrcvwin(socket,*curwin=(INT)newwin);
     }
}

INT
rwnadj(                            /* receive window adjuster              */
INT socket,                        /* socket handle                        */
INT bufsiz,                        /* incoming buffer capacity when empty  */
INT bufavl,                        /* incoming buffer capacity now         */
INT hyster,                        /* hysteresis on window adjustment      */
INT *curwin)                       /* current advertised window variable   */
{                                  /* returns 1=adjusted, 0=left alone     */
                                   /* An empty buffer means immediately    */
                                   /* advertise max window, else there's   */
                                   /* hysteresis on receive window growth. */
                                   /* rwnadj() can only GROW window size.  */
     if (bufavl > rcvwin) {
          bufavl=rcvwin;
     }
     if (bufavl > *curwin && bufavl == bufsiz
      || bufavl > *curwin+hyster) {
          setrcvwin(socket,*curwin=bufavl);
          send(socket,"",0,0);     /* forces receive window advertisement  */
          return(1);
     }
     return(0);
}

INT
setsndwin(                         /* set socket's send window size        */
INT socket,                        /* socket handle                        */
INT swin)                          /* send window (e.g. sndwin)            */
{                                  /* returns send window (same as swin)   */
#ifdef PACSOFT
     (VOID)socket;
     (VOID)swin;
     return(1);
#endif
#ifdef IPSWITCH
#if defined(UNIX) || defined(GCWINNT)
     (VOID)socket;
     (VOID)swin;
     return(1);
#else
     INT rc;

     rc=setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(CHAR *)&swin,sizeof(swin));
     return(rc == 0);
#endif
#endif
}

INT
setrcvwin(                         /* set socket's receive window size     */
INT socket,                        /* socket handle                        */
INT rwin)                          /* receive window (e.g. rcvwin)         */
{                                  /* returns 1=ok, 0=error (tcpip_errno)  */
#ifdef PACSOFT
     return((tcpip_errno=so_nrqmax(socket,rwin)) == 0);
#endif
#ifdef IPSWITCH
#if defined(UNIX) || defined(GCWINNT)
     (VOID)socket;
     (VOID)rwin;
     return(1);
#else
     INT rc;

     rc=setsockopt(socket,SOL_SOCKET,SO_RCVBUF,(CHAR *)&rwin,sizeof(rwin));
     return(rc == 0);
#endif
#endif
}

static GBOOL                       /*   returns TRUE=allowed, FALSE=denied */
dfthstalw(                         /* default (*hstalw)() vector           */
ULONG hostip)                      /*   host ip address, network order     */
{
     if (hostdeny != NULL && *hostdeny != '\0') {
        struct in_addr ia;
        iprCheckFile(hostdeny,&iprBlock,&iprBlockNum,&iprBlockStamp);
        /* ntohl will put it in host byte order */
        ia.s_addr = hostip;
        return(!iprCheckIP(iprBlock,iprBlockNum,ntohl(ia.s_addr)));
     }
     return(TRUE);
}

VOID
iprCheckFile(                      /* check range file, reload if necessary*/
const CHAR *fileName,              /*   IP range list file                 */
struct iprange **ppRange,          /*   ptr to ptr to array of ranges      */
INT *pNumber,                      /*   ptr to number of array elements    */
struct timestamp *pStamp)          /*   time/date stamp of current array   */
{
     struct ffblk fb;
     struct timestamp fstamp;

     if (fndfile(&fb,fileName,0)) {
          fstamp.date=fb.ff_fdate;
          fstamp.time=fb.ff_ftime;
          if (compStamp(fstamp,*pStamp) > 0
           && iprReadFile(fileName,ppRange,pNumber)) {
               pStamp->date=fb.ff_fdate;
               pStamp->time=fb.ff_ftime;
          }
     }
     else if (*ppRange != NULL) {
          free(*ppRange);
          *ppRange=NULL;
          *pNumber=0;
          pStamp->date=0;
          pStamp->time=0;
     }
}

GBOOL
iprCheckIP(                        /* check if IP is in array of ranges    */
struct iprange *rangeArr,          /*   array of IP ranges                 */
INT nRanges,                       /*   number of elements in array        */
ULONG ip)                          /*   IP address to check                */
{
     INT i;

     for (i=0 ; i < nRanges ; ++i) {
          if (ip >= rangeArr[i].low && ip <= rangeArr[i].high) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   returns FALSE if couldn't read     */
iprReadFile(                       /* read an IP range list file           */
const CHAR *fileName,              /*   IP range list file                 */
struct iprange **ppRange,          /*   ptr to ptr to array of ranges      */
INT *pNumber)                      /*   ptr to number of array elements    */
{
     FILE *fp;
     struct iprange tmpRange;
     CHAR buf[sizeof("255.255.255.255-255.255.255.255")];

     if ((fp=fopen(fileName,FOPRA)) != NULL) {
          if (*ppRange != NULL) {
               free(*ppRange);
               *ppRange=NULL;
          }
          *pNumber=0;
          while (iprReadLine(buf,sizeof(buf),fp)) {
               if (iprProcLine(buf,&tmpRange)) {
                    if (!alcarr((VOID **)ppRange,sizeof(struct iprange),
                                *pNumber,SMLBLK)) {
                         break;
                    }
                    (*ppRange)[*pNumber]=tmpRange;
                    ++(*pNumber);
               }
          }
          fclose(fp);
          return(TRUE);
     }
     return(FALSE);
}

GBOOL
iprReadLine(                       /* read a line from IP list file        */
CHAR *buf,                         /*   buffer to receive line             */
size_t bufSiz,                     /*   size of buffer                     */
FILE *fp)                          /*   file to read from                  */
{
     size_t i;
     INT c;

     if ((c=fgetc(fp)) == EOF) {
          return(FALSE);
     }
     i=0;
     do {
          if (c == ';' || c == '\n') {  /* comment or EOL */
               break;
          }
          else if (!isspace(c)) {       /* spaces ignored */
               if (i+1 < bufSiz) {
                    buf[i++]=c;
               }
               else {
                    break;
               }
          }
     } while ((c=fgetc(fp)) != EOF);
     ASSERT(i+1 <= bufSiz);
     buf[i]='\0';
     while (c != EOF && c != '\n') {
          c=fgetc(fp);
     }
     return(TRUE);
}

GBOOL
iprProcLine(                       /* process line from IP list file       */
CHAR *rangeLine,                   /*   line of text from file (modified)  */
struct iprange *rangeBuf)          /*   buffer to receive IP range         */
{
     CHAR *plo,*phi;
     ULONG iplo,iphi;

     if ((plo=strtok(rangeLine,"-")) != NULL) {
          if ((phi=strtok(NULL,"-")) == NULL) {
               phi=plo;
          }
          if (iprDecodeIP(&iplo,plo)
           && iprDecodeIP(&iphi,phi)
           && iplo <= iphi) {
               rangeBuf->low=iplo;
               rangeBuf->high=iphi;
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if valid IP string    */
iprDecodeIP(                       /* decode an IP address string          */
ULONG *pIP,                        /*   buffer to receive IP address       */
const CHAR *ipStr)                 /*   string form of IP address          */
{
     ULONG tmpIP;

     if (sameas("255.255.255.255",ipStr)) {
          *pIP=0xFFFFFFFFUL;       /* a.k.a. INET_ADDR_ERR */
          return(TRUE);
     }
     if ((tmpIP=inet_addr(ipStr)) != INET_ADDR_ERR) {
          *pIP=ntohl(tmpIP);
          return(TRUE);
     }
     return(FALSE);
}

INT                                /*   < 0 if stamp1 < stamp2, etc.       */
compStamp(                         /* compare time/date stamps             */
struct timestamp stamp1,           /*   first time/date stamp              */
struct timestamp stamp2)           /*   second time/date stamp             */
{
     if (stamp1.date == stamp2.date) {
          if (stamp1.time == stamp2.time) {
               return(0);
          }
          if (stamp1.time < stamp2.time) {
               return(-1);
          }
          return(1);
     }
     if (stamp1.date < stamp2.date) {
          return(-1);
     }
     return(1);
}

#ifdef IPSWITCH
/*--- Scanning channels for service -- looking for FD_ISSET() sockets ---*/
#ifndef GCWINNT
VOID
fd_scanbeg(                        /* expr1 in fd_set scanning for(;;) loop*/
fd_set *fdsp)                      /* &fd_set parm from a recent select()  */
{
     fdscptr=(UINT *)fdsp;
     fdscbits=*fdscptr;
     fdscmsk=0x0001;
     fdscj=fdscskt=0;
}

INT
fd_scanmor(VOID)                   /* expr2 in fd_set scanning for(;;) loop*/
{
     while (fdscskt < FD_SETSIZE) {
          if (fdscbits&fdscmsk) {
               return(1);
          }
          fd_scannxt();
     }
     return(0);
}

VOID
fd_scannxt(VOID)                   /* expr3 in fd_set scanning for(;;) loop*/
{
     fdscskt++;
     if (++fdscj >= sizeof(fdscbits)*NBBY) {
          fdscptr++;
          fdscbits=*fdscptr;
          fdscmsk=0x0001;
          fdscj=0;
     }
     else {
          fdscmsk<<=1;
     }
}

VOID
fd_setor(                          /* bit-wise OR two fd_set's together    */
struct fd_set *oree,               /* input and output                     */
struct fd_set *orer)               /* input                                */
{
     INT i;

     for (i=0 ; i < sizeof(skttmp[TNFSEND])/sizeof(UINT) ; i++) {
          *((UINT *)oree)++|=*((UINT *)orer)++;
     }
}

VOID
fd_setand(                         /* bit-wise AND two fd_set's together   */
struct fd_set *andee,              /* input and output                     */
struct fd_set *ander)              /* input                                */
{
     INT i;

     for (i=0 ; i < sizeof(skttmp[TNFSEND])/sizeof(UINT) ; i++) {
          *((UINT *)andee)++&=*((UINT *)ander)++;
     }
}
#endif // !GCWINNT
#endif // IPSWITCH

/*--- utilities ---*/

INT                                /*   returns 1=valid, 0=non-class A,B,C */
dftnetmask(                        /* default network mask                 */
ULONG ipaddr,              /*   IP addresss (network byte order)   */
ULONG *netmask)            /*   where to put netmask (n byte order)*/
{
     CHAR msbyte;

     msbyte=(CHAR)(((ULONG)ntohl(ipaddr))>>24);
     if (msbyte < 128) {
          *netmask=htonl(0xFF000000UL);      /* class A netmask */
     }
     else if (msbyte < 192) {
          *netmask=htonl(0xFFFF0000UL);      /* class B netmask */
     }
     else if (msbyte < 224) {
          *netmask=htonl(0xFFFFFF00UL);      /* class C netmask */
     }
     else {
          return(0);
     }
     return(1);
}

INT                                /*   returns 1=valid, 0=not             */
chknetmask(                        /* is netmask valid?                    */
ULONG netmask)                     /*   netmask, network byte order        */
                                   /*   Netmask must allow at least 4 hosts*/
                                   /*   and in binary start with 1's and   */
{                                  /*   end with 0's.                      */
     ULONG uln;
     ULONG siznet;

     siznet=(~ntohl(netmask))+1;
     for (uln=4UL ; uln != siznet ; uln<<=1) {
          if (uln == 0UL || uln > siznet) {
               return(0);
          }
     }
     return(1);
}

INT
sndop(                             /* send an option string, all or nothing*/
struct tcpipinf *tip,              /* TCP/IP info for channel              */
CHAR *bytes,                       /* binary string of bytes (may incl \0) */
INT nbytes)                        /* number of bytes                      */
{                                  /* returns 0=no room, 1=ok              */
     if (tip->outsnk.bufcnt+nbytes > TCPOSZ) {
          return(0);
     }
     else {
          movmem(bytes,tip->outstg+tip->outsnk.bufcnt,nbytes);
          tip->outsnk.bufcnt+=nbytes;
          tip->bytcnt+=nbytes;
          return(1);
     }
}

VOID
sktnfy(                            /* request notification on socket event */
INT tnftype,                       /* TNFRECV, TNFSEND, TNFACCP, TNFCONN   */
INT skt,                           /* socket number                        */
VOID (*evthdl)(),                  /* event handler routine                */
VOID *ptr,                         /* app-specific ptr (passed to evthdl)  */
INT unum)                          /* associated user number (curusr() set */
{                                  /* before evthdl) or -1=none            */
     INT sktidx;
     struct sktmap *sptr;
     INT mapidx;
#ifdef PACSOFT
     struct sel *selptr;
     INT idx;
     extern so_notified();
#endif

     sktidx=addSktIdx(skt);
     ASSERT(tnftype >= 0 && tnftype < TNFQUAN);
     ASSERT(sktidx >= 0 && sktidx < NUMSOCKETS);
     mapidx=tnfidx[tnftype];
     sptr=&sktmapped(mapidx,sktidx);
     if (0 <= unum && unum < nterms) {
          if (sktunm[sktidx] != -1 && sktunm[sktidx] != unum) {
               catastro("Socket %d associated with multiple channels:\n%s ss=%d\n%s",
                        skt,spr("CH%02X st=%d evt=%p,%p",
                                channel[sktunm[sktidx]],
                                usroff(sktunm[sktidx])->state,
                                sktmapped(TNFRA,sktidx).evthdl,
                                sktmapped(TNFSC,sktidx).evthdl),
                                usroff(sktunm[sktidx])->substt,
                                spr("CH%02X st=%d evt=%p",
                                channel[unum],usroff(unum)->state,evthdl));
          }
          sktunm[sktidx]=unum;
     }
     else {
          sktunm[sktidx]=-1;
     }
     sptr->evthdl=evthdl;
     sptr->ptr=ptr;
#ifdef PACSOFT
          so_notified(skt,evthdl);
          for (idx=0,selptr=&sktsel[0] ; idx < numnfy ; idx++,selptr++) {
               if (selptr->se_fd == skt) {
                    break;
               }
          }
          if (idx == numnfy) {
               ASSERTM(numnfy >= 0 && numnfy < NUMSOCKETS,
                       spr("numnfy=%d",numnfy));
               selptr=&sktsel[numnfy];
               setmem(selptr,sizeof(struct sel),0);
               selptr->se_fd=skt;
               selptr->se_inflags=BASE_NOTIFY;
               numnfy++;
          }
          selptr->se_inflags|=selflags[tnftype];
#endif
#ifdef UNIX
          if (!FD_ISSET(skt,&sktfds[mapidx])) {
               FD_SET(skt,&sktfds[mapidx]);
          }
#endif
#ifdef GCWINNT
          slTrack(skt,tnftrans[tnftype]);
          SelectSetup(skt);
#endif
}

VOID
sktcnc(                            /* cancel notification on socket events */
INT tnftype,                       /* TNFRECV, TNFSEND, TNFACCP, TNFCONN   */
INT skt)                           /* socket number                        */
{
#ifdef PACSOFT
     struct sel *selptr;
     INT idx;
#endif
#ifdef UNIX
     INT idx;
#endif
     INT sktidx;

     ASSERT(tnftype >= 0 && tnftype < TNFQUAN);
     sktidx=getSktIdx(skt);
     if (sktidx >= 0) {
          setmem(&sktmapped(tnfidx[tnftype],sktidx),sizeof(struct sktmap),0);
     }
#ifdef PACSOFT
     for (idx=0,selptr=&sktsel[0] ; idx < numnfy ; idx++,selptr++) {
          if (selptr->se_fd == skt) {
               selptr->se_inflags&=~selflags[tnftype];
               selptr->se_outflags&=~selflags[tnftype];
                                   /* (in case nselect() in progress) */
               if (selptr->se_inflags == BASE_NOTIFY) {
                    sktcompress=1;      /* defer removal of sel entry */
               }
               break;
          }
     }
#endif
#ifdef UNIX
     idx=tnfidx[tnftype];
     while (FD_ISSET(skt,&sktfds[idx])) {
          FD_CLR(skt,&sktfds[idx]);
     }
     while (FD_ISSET(skt,&skttmp[idx])) {
          FD_CLR(skt,&skttmp[idx]);
     }
#endif
#ifdef GCWINNT
     slUntrack(skt,tnftrans[tnftype]);
     SelectSetup(skt);
#endif
}

VOID
clssvr(                            /* close a server's socket to his client*/
INT unum,                          /* user number (or -1=not applicable)   */
INT skt,                           /* socket                               */
INT port)                          /* server's port number (for error msg) */
{
     if (!clsskt(skt) && tcpip_errno != ENOTCONN) {
          /* svrfail(unum,"CLOSE",port); close may return bad error codes */
          (VOID)unum;
          (VOID)port;
     }
}

INT
clsskt(                            /* close a socket                       */
INT skt)                           /* socket number                        */
{                                  /* returns 1=worked 0=failed (see errno)*/
     INT sktidx;

     sktcnc(TNFRECV,skt);
     sktcnc(TNFSEND,skt);
     sktcnc(TNFACCP,skt);
     sktcnc(TNFCONN,skt);
     sktidx=getSktIdx(skt);
     if (sktidx >= 0) {
          sktunm[sktidx]=-1;
          rmvSktIdx(skt);
     }
#ifdef GCWINNT
     if (kilipg != 0) {
          setsockopt(skt,SOL_SOCKET,SO_LINGER,(char*)&lingno,sizeof(lingno));
          ioctlsocket(skt,FIONBIO,&asno);
     }
#endif
     return(socketclose(skt) != -1);
}

VOID
byetcp(                            /* log-off a TCP/IP user w/message      */
INT msgnum,                        /* same parameters as prfmsg()          */
...)                               /* usrnum is an implicit input          */
                                   /* (for backward compatibility only;    */
{                                  /* for new code, use byendl())          */
     va_list ap;
     CHAR *p1,*p2,*p3;

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

/*--- failure modes and shutdown ---*/

VOID
svrcatast(                         /* system-fatal error with TCP/IP svr   */
CHAR *type,                        /* such as "BIND" or "LISTEN"           */
INT port)                          /* TCP port number                      */
{
     catastro("TCP/IP %s error %d (server port %u)\n%s",
              type,tcpip_errno,port,tcpErrStg(tcpip_errno));
}

VOID
svrfail(                           /* session-fatal error with TCP/IP svr  */
INT unum,                          /* user number (or -1=not applicable)   */
CHAR *type,                        /* such as "SEND" or "RECEIVE"          */
INT port)                          /* TCP port number                      */
{
     tcpfail(unum,"%s error %d (port %u) %s",
             type,tcpip_errno,port,tcpErrStg(tcpip_errno));
}

VOID
tcpfail(                           /* session-fatal error with TCP/IP      */
INT unum,                          /* user number or -1=non-specific       */
CHAR *fmt,...)                     /* format string, ala printf            */
{
     CHAR buffer[120];
     INT unmsav;
     struct tcpipinf *tip;
     va_list ap;

     va_start(ap,fmt);
     vsprintf(buffer,fmt,ap);
     va_end(ap);
     if (unum >= 0) {
          tip=&tcpipinf[unum];
          if (tip->flags&TCPDSC) {
               btuinj(unum,RING);  /* (avoid storm of error messages)      */
               return;
          }
          tip->flags|=TCPDSC;
          if (tip->server != NULL) {
               send(tip->socket,buffer,strlen(buffer),0);
               send(tip->socket,"\r\n",2,0);
          }
          sktcnc(TNFRECV,tip->socket);
          sktcnc(TNFSEND,tip->socket);
          sktcnc(TNFACCP,tip->socket);
          sktcnc(TNFCONN,tip->socket);
          bbsdsc(unum);
     }
     if (audsverr) {
          unmsav=usrnum;
          usrnum=unum;
          shocst("TCP/IP ERROR",buffer);
          usrnum=unmsav;
     }
}

/* Note:  If ICO add-ons religiously call init__tcpip() at the top of      */
/* their own init__xxx() routines, then tcprst() is guaranteed to be       */
/* called AFTER all of the ICO add-ons' xxxrst() routines.                 */

static VOID
tcprst(VOID)                       /* TCP/IP channel reset hook (rstchn()) */
{
     struct tcpipinf *tip;

     tip=&tcpipinf[usrnum];
     if (tip->server != NULL) {
          btucdi(usrnum,NULL);
          clssvr(usrnum,tip->socket,tip->server->port);
          if (audtrm) {
               shocst(strupr(spr("%s CALL TERMINATED",tip->server->name)),
                      "connected to %s for %s seconds",
                      inet_ntoa(tcpipinf[usrnum].inaddr),
                      l2as(lngtck-tip->tckcon));
          }
     }
     setmem(tip,sizeof(struct tcpipinf),0);
     uscauto(usrnum);
     everrst=1;
     (*oldrst)();
}

static VOID
uscauto(INT unum)                  /* automatic user socket close          */
{
     INT sktidx;
     INT skt;

     for (sktidx=0 ; sktidx < NUMSOCKETS ; sktidx++) {
          if (sktunm[sktidx] == unum) {
               skt=getIdxSkt(sktidx);
               ASSERT(skt != INVALID_SOCKET);
               clsskt(skt);
          }
     }
}

static VOID
tcpfin(VOID)                       /* TCP/IP final shutdown                */
{                                  /*   (before hupall(), finrou's, gsbldn)*/
     struct svrinf *sip;

     for (sip=svrhead ; sip != NULL ; sip=sip->next) {
          clsskt(sip->svrskt);
     }
#ifdef TCPKFIN_EXISTS
     if (!everrst) {               /* (Before MAJORBBS.C sets initdn, it   */
       tcpkfin();                  /* will never call the shutdown hooks,  */
     }                             /* so this ensures a kernel shutdown.)  */
#endif
#ifdef GCWINNT
     if (fWinSockInit) {
          if (WSACleanup() == SOCKET_ERROR) {
               shocst("GALTCP: WSACleanup Error","Error #%x",WSAGetLastError());
          }
          fWinSockInit=FALSE;
     }
#endif // GCWINNT
     (*oldfin)();
}

/*--- text variables ---*/

static CHAR *
tvar_ipserver(VOID)                /* text var:  WG Server's IP address   */
{
     return(ipaddr);
}

static CHAR *
tvar_ipclient(VOID)                /* text variable:  client IP address    */
{
     return(inet_ntoa(tcpipinf[usrnum].inaddr));
}


#if !defined(UNIX) && !defined(GCWINNT)
/*--- GCDI interrupts (candidates for somewhere more general-purpose).  ---*/
/*--- These could be used by other GCDI implementations, but DATSTM.C   ---*/
/*--- is too general purpose (it's not specific to GSBL for example).   ---*/

#pragma inline

static SEL sel_cdixfn;             /* writable selector for cdixfn in GSBL */
static int *ptr_btuicx;            /* writable pointer to btuicx           */

static void
init_cdi_interrupts(void)          /* prep xxx_cdi_interrupts() & iskxxx() */
{
     int rc;

     if ((rc=DosCreateDSAlias(FP_SEG(&btuicx),&sel_cdixfn)) != 0) {
          catastro("Internal init_cdi_interrupt() failure (selector %04X) %d\n"
                   "You may need to update your Connectivity Disk.",
                   FP_SEG(&btuicx),rc);
     }
     ptr_btuicx=MK_FP(sel_cdixfn,FP_OFF(&btuicx));
}

int                                /*   value to pass to iskclose()        */
iskopen(void)                      /* permit GCDI channel input sink use   */
{
     int oldicx;

     oldicx=*ptr_btuicx;
     *ptr_btuicx=1;
     return(oldicx);
}

void
iskclose(                          /* end of GCDI channel input sink use   */
int oldicx)                        /*   from corresponding iskopen()       */
{
     *ptr_btuicx=oldicx;
}

int
__allow_cdi_interrupts(void)       /* guts of allow_cdi_interrupts macro   */
{
     asm  pushf
     asm  pop  ax
     asm  and  ax,0200H            /* ah is 2 if sti in effect, 0 if cli   */
     asm  mov  cx,seg sel_cdixfn
     asm  mov  es,cx
     asm  mov  cx,es:sel_cdixfn
     asm  mov  es,cx
     asm  mov  al,byte ptr es:btuicx+6  /* save cdixfn value               */
     asm  sti                      /* enable interrupts                    */
     return(_AX);
}

int
__prevent_cdi_interrupts(void)     /* guts of prevent_cdi_interrupts macro */
{
     asm  pushf
     asm  cli                      /* disable interrupts                   */
     asm  pop  ax
     asm  and  ax,0200H            /* ah is 2 if sti in effect, 0 if cli   */
     asm  mov  cx,seg sel_cdixfn
     asm  mov  es,cx
     asm  mov  cx,es:sel_cdixfn
     asm  mov  es,cx
     asm  mov  al,byte ptr es:btuicx+6  /* save cdixfn value               */
     return(_AX);
}

void
__restore_cdi_interrupts(          /* guts of restore_cdi_interrupts macro */
int savifl)
{
     if ((savifl&0x0200) != 0) {
          asm  mov  al,byte ptr savifl
          asm  mov  cx,seg sel_cdixfn
          asm  mov  es,cx
          asm  mov  cx,es:sel_cdixfn
          asm  mov  es,cx
          asm  mov  byte ptr es:btuicx+6,al  /* restore cdixfn value       */
          asm  sti                 /* restore interrupt flag on            */
     }
     else {
          asm  cli                 /* restore interrupt flag off           */
          asm  mov  al,byte ptr savifl
          asm  mov  cx,seg sel_cdixfn
          asm  mov  es,cx
          asm  mov  cx,es:sel_cdixfn
          asm  mov  es,cx
          asm  mov  byte ptr es:btuicx+6,al  /* restore cdixfn value       */
     }
}

VOID
cdi_dmbufs(                        /* dmbufs() with interrupts enabled     */
struct datstm far *dsp,            /* DataStream structure (inside bufstm) */
USHORT nactual)                    /* this many were moved by the source   */
{
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;
     dmbufs(dsp,nactual);
     restore_cdi_interrupts;
}
#else
static VOID
init_cdi_interrupts(VOID)          /* prep xxx_cdi_interrupts() & iskxxx() */
{
}

INT                                /*   value to pass to iskclose()        */
iskopen(VOID)                      /* permit GCDI channel input sink use   */
{
     return(0);
}

VOID
iskclose(                          /* end of GCDI channel input sink use   */
INT oldicx)                        /*   from corresponding iskopen()       */
{
     (VOID)oldicx;
}

INT
__allow_cdi_interrupts(VOID)       /* guts of allow_cdi_interrupts macro   */
{
     return(0);
}

INT
__prevent_cdi_interrupts(VOID)     /* guts of prevent_cdi_interrupts macro */
{
     return(0);
}

VOID
__restore_cdi_interrupts(          /* guts of restore_cdi_interrupts macro */
INT savifl)
{
     (VOID)savifl;
}

VOID
cdi_dmbufs(                        /* dmbufs() with interrupts enabled     */
struct datstm far *dsp,            /* DataStream structure (inside bufstm) */
USHORT nactual)                    /* this many were moved by the source   */
{
     dmbufs(dsp,nactual);
}
#endif

#ifdef GCWINNT
BOOL WINAPI
DllEntryPoint(
HINSTANCE  hinstDLL,               /* handle of DLL module                 */
DWORD  fdwReason,                  /* reason for calling function          */
LPVOID  lpvReserved)               /* reserved                             */
{
     WORD verRequested;
     WSADATA wsaData;

     (VOID)hinstDLL;
     (VOID)lpvReserved;
     if (fdwReason == DLL_PROCESS_ATTACH) {
          verRequested=MAKEWORD(1,1);
          if (WSAStartup(verRequested,&wsaData) == 0) {
               fWinSockInit=TRUE;
          }
     }
     else if (fdwReason == DLL_PROCESS_DETACH) {
          if (fWinSockInit) {
               WSACleanup();
               fWinSockInit=FALSE;
          }
     }
     return(TRUE);
}
#endif // GCWINNT

CHAR *                             /*   returns pointer to error message   */
tcpErrStg(                         /* convert tcpip_errno to text message  */
INT tcperrno)                      /*   TCP/IP error number                */
{
#ifndef PACSOFT
     static CHAR errbuf[BUFSIZ];

     switch (tcperrno) {
#if defined(IPSWITCH) && !defined(GCWINNT)
     case EWOULDBLOCK:
          return(strcpy(errbuf,"Operation would block"));
     case EINPROGRESS:
          return(strcpy(errbuf,"Operation now in progress"));
     case EALREADY:
          return(strcpy(errbuf,"Operation already in progress"));
     case ENOTSOCK:
          return(strcpy(errbuf,"Socket operation on non-socket"));
     case EDESTADDREQ:
          return(strcpy(errbuf,"Destination address required"));
     case EMSGSIZE:
          return(strcpy(errbuf,"Message too long"));
     case EPROTOTYPE:
          return(strcpy(errbuf,"Protocol wrong type for socket"));
     case ENOPROTOOPT:
          return(strcpy(errbuf,"Protocol not available"));
     case EPROTONOSUPPORT:
          return(strcpy(errbuf,"Protocol not supported"));
     case ESOCKTNOSUPPORT:
          return(strcpy(errbuf,"Socket type not supported"));
     case EOPNOTSUPP:
          return(strcpy(errbuf,"Operation not supported on socket"));
     case EPFNOSUPPORT:
          return(strcpy(errbuf,"Protocol family not supported"));
     case EAFNOSUPPORT:
          return(strcpy(errbuf,
                        "Address family not supported by protocol family"));
     case EADDRINUSE:
          return(strcpy(errbuf,"Address already in use"));
     case EADDRNOTAVAIL:
          return(strcpy(errbuf,"Cannot assign requested address"));
     case ENETDOWN:
          return(strcpy(errbuf,"Network is down"));
     case ENETUNREACH:
          return(strcpy(errbuf,"Network is unreachable"));
     case ENETRESET:
          return(strcpy(errbuf,"Network dropped connection on reset"));
     case ECONNABORTED:
          return(strcpy(errbuf,"Software caused connection abort"));
     case ECONNRESET:
          return(strcpy(errbuf,"Connection reset by peer"));
     case ENOBUFS:
          return(strcpy(errbuf,"No buffer space available"));
     case EISCONN:
          return(strcpy(errbuf,"Socket is already connected"));
     case ENOTCONN:
          return(strcpy(errbuf,"Socket is not connected"));
     case ESHUTDOWN:
          return(strcpy(errbuf,"Cannot send after socket shutdown"));
     case ETIMEDOUT:
          return(strcpy(errbuf,"Connection timed out"));
     case ECONNREFUSED:
          return(strcpy(errbuf,"Connection refused"));
     case EPFNOSUPPORT:
          return(strcpy(errbuf,"Protocol family not supported"));
     case EHOSTDOWN:
          return(strcpy(errbuf,"Host is down"));
     case EHOSTUNREACH:
          return(strcpy(errbuf,"Host is unreachable"));
#else
     case WSAEINTR:
          return(strcpy(errbuf,"Interrupted function call"));
     case WSAEBADF:
          return(strcpy(errbuf,"Bad file number"));
     case WSAEACCES:
          return(strcpy(errbuf,"Permission denied"));
     case WSAEINVAL:
          return(strcpy(errbuf,"Invalid argument"));
     case WSAEMFILE:
          return(strcpy(errbuf,"Too many open files"));
     case WSAEWOULDBLOCK:
          return(strcpy(errbuf,"Operation would block"));
     case WSAEINPROGRESS:
          return(strcpy(errbuf,"Operation now in progress"));
     case WSAEALREADY:
          return(strcpy(errbuf,"Operation already in progress"));
     case WSAENOTSOCK:
          return(strcpy(errbuf,"Socket operation on non-socket"));
     case WSAEDESTADDRREQ:
          return(strcpy(errbuf,"Destination address required"));
     case WSAEMSGSIZE:
          return(strcpy(errbuf,"Message too long"));
     case WSAEPROTOTYPE:
          return(strcpy(errbuf,"Protocol wrong type for socket"));
     case WSAENOPROTOOPT:
          return(strcpy(errbuf,"Protocol not available"));
     case WSAEPROTONOSUPPORT:
          return(strcpy(errbuf,"Protocol not supported"));
     case WSAESOCKTNOSUPPORT:
          return(strcpy(errbuf,"Socket type not supported"));
     case WSAEOPNOTSUPP:
          return(strcpy(errbuf,"Operation not supported on socket"));
     case WSAEPFNOSUPPORT:
          return(strcpy(errbuf,"Protcol family not supported"));
     case WSAEAFNOSUPPORT:
          return(strcpy(errbuf,
                        "Address family not supported by protocol family\n"
                        "Make sure you have TCP/IP installed on your system"));
     case WSAEADDRINUSE:
          return(strcpy(errbuf,"Address already in use"));
     case WSAEADDRNOTAVAIL:
          return(strcpy(errbuf,"Cannot assign requested address"));
     case WSAENETDOWN:
          return(strcpy(errbuf,"Network is down"));
     case WSAENETUNREACH:
          return(strcpy(errbuf,"Network is unreachable"));
     case WSAENETRESET:
          return(strcpy(errbuf,"Network dropped connection on reset"));
     case WSAECONNABORTED:
          return(strcpy(errbuf,"Software caused connection abort"));
     case WSAECONNRESET:
          return(strcpy(errbuf,"Connection reset by peer"));
     case WSAENOBUFS:
          return(strcpy(errbuf,"No buffer space available"));
     case WSAEISCONN:
          return(strcpy(errbuf,"Socket is already connected"));
     case WSAENOTCONN:
          return(strcpy(errbuf,"Socket is not connected"));
     case WSAESHUTDOWN:
          return(strcpy(errbuf,"Cannot send after socket shutdown"));
     case WSAETOOMANYREFS:
          return(strcpy(errbuf,"Too many references; cannot splice"));
     case WSAETIMEDOUT:
          return(strcpy(errbuf,"Connection timed out"));
     case WSAECONNREFUSED:
          return(strcpy(errbuf,"Connection refused"));
     case WSAELOOP:
          return(strcpy(errbuf,"Too many levels of symbolic links"));
     case WSAENAMETOOLONG:
          return(strcpy(errbuf,"File name too long"));
     case WSAEHOSTDOWN:
          return(strcpy(errbuf,"Host is down"));
     case WSAEHOSTUNREACH:
          return(strcpy(errbuf,"Host is unreachable"));
     case WSAENOTEMPTY:
          return(strcpy(errbuf,"Directory not empty"));
     case WSAEPROCLIM:
          return(strcpy(errbuf,"Too many processes"));
     case WSAEUSERS:
          return(strcpy(errbuf,"Too many users"));
     case WSAEDQUOT:
          return(strcpy(errbuf,"Disk quota exceeded"));
     case WSAESTALE:
          return(strcpy(errbuf,"Stale NFS file handle"));
     case WSAEREMOTE:
          return(strcpy(errbuf,"Too many levels of remote in path"));
     case WSASYSNOTREADY:
          return(strcpy(errbuf,"Network subsystem is unusable"));
     case WSAVERNOTSUPPORTED:
          return(strcpy(errbuf,"WINSOCK.DLL cannot support application"));
     case WSANOTINITIALISED:
          return(strcpy(errbuf,"WSAStartup() not successfully called"));
     case WSAHOST_NOT_FOUND:
          return(strcpy(errbuf,"Host not found"));
     case WSATRY_AGAIN:
          return(strcpy(errbuf,"Non-authoritative host found"));
     case WSANO_RECOVERY:
          return(strcpy(errbuf,"Non-recoverable query error"));
     case WSANO_DATA:
          return(strcpy(errbuf,"Valid name, no data of that type"));
     }
     return(strcpy(errbuf,"Unknown error"));
#endif // IPSWITCH && !GCWINNT
#else
     return(strerror(tcperrno));
#endif // PACSOFT
}

INT                                /*   return index number of socket      */
addSktIdx(                         /* add socket index to socket array     */
INT skt)                           /*   socket to add to array             */
{
#ifdef GCWINNT
     INT i;
     INT emptyIdx,matchIdx;

     for (i=0,emptyIdx=-1,matchIdx=-1 ; i < NUMSOCKETS ; i++) {
          if (sktarr[i] == -1) {
               if (emptyIdx == -1) {
                    emptyIdx=i;
               }
          }
          else if (sktarr[i] == skt) {
               if (matchIdx == -1) {
                    matchIdx=i;
               }
          }
     }
     if (matchIdx != -1) {
          return(matchIdx);
     }
     if (emptyIdx != -1) {
          sktarr[emptyIdx]=skt;
          return(emptyIdx);
     }
     return(-1);
#else
     return(skt);
#endif                             /* GCWINNT                              */
}

INT                                /*   return index number of socket      */
getSktIdx(                         /* get socket index from socket array   */
INT skt)                           /*   socket number to look up           */
{
#ifdef GCWINNT
     INT i;

     for (i=0 ; i < NUMSOCKETS ; i++) {
          if (sktarr[i] == skt) {
               return(i);
          }
     }
     return(-1);
#else
     return(skt);
#endif                             /* GCWINNT                              */
}

INT                                /*   return socket                      */
getIdxSkt(                         /* get socket fron index number         */
INT idx)                           /*   index number to look up            */
{
#ifdef GCWINNT
     if (idx >= 0 && idx < NUMSOCKETS) {
          return(sktarr[idx]);
     }
     else {
          return(INVALID_SOCKET);
     }
#else
     return(idx);
#endif                             /* GCWINNT                              */
}

VOID
rmvSktIdx(                         /* removes socket from socket array     */
INT skt)                           /*   socket to remove from socket array */
{
#ifdef GCWINNT
     INT i;

     for (i=0 ; i < NUMSOCKETS ; i++) {
          if (sktarr[i] == skt) {
               sktarr[i]=-1;
          }
     }
#else
     (VOID)skt;
#endif                             /* GCWINNT                              */
}

GBOOL                              /*   returns TRUE if TCP/IP channel     */
getTCPConnectInfo(                 /* get IP address and port number       */
INT usrnum,                        /*   requested usrnum information       */
CHAR *ipAddress,                   /*   IP address of this usrnum          */
INT *portNumber)                   /*   port number of this usrnum         */
{
     INT i,socket,mylen;
     struct sockaddr_in myaddr;

     for (i=0 ; i < NUMSOCKETS ; i++) {
          if (sktunm[i] == usrnum) {
               if ((socket=getIdxSkt(i)) != INVALID_SOCKET) {
                    setmem(&myaddr,sizeof(struct sockaddr_in),0);
                    myaddr.sin_family=AF_INET;
                    mylen=sizeof(myaddr);
                    if (getsockname(socket,(struct sockaddr *)&myaddr,&mylen)
                                                            != SOCKET_ERROR) {
                         *portNumber=ntohs(myaddr.sin_port);
                         setmem(&myaddr,sizeof(struct sockaddr_in),0);
                         myaddr.sin_family=AF_INET;
                         mylen=sizeof(myaddr);
                         if (getpeername(socket,(struct sockaddr *)&myaddr,&mylen)
                                                            != SOCKET_ERROR) {
                              strcpy(ipAddress,inet_ntoa(myaddr.sin_addr));
                              return(TRUE);
                         }
                    }
               }
               break;
          }
     }
     return(FALSE);
}

#ifdef GCWINNT
LRESULT CALLBACK                   /*   Return window status               */
tcpwproc(                          /* TCP/IP Windows Procedure             */
HWND hWnd,                         /*   Handle to TCP/IP Window            */
UINT message,                      /*   Windows message to process         */
WPARAM wParam,                     /*   wParam == Socket #                 */
LPARAM lParam)                     /*   lParam == Events/Errors            */
{
     LONG lEvents;

     switch (message) {
     case WM_SOCKET_NOTIFY:
          tnfclosed=0;

          switch (WSAGETSELECTEVENT(lParam)) {
          case FD_CLOSE:
               lEvents=slEvents((INT)wParam);
               if ((lEvents&FD_ACCEPT) || (lEvents&FD_READ)) {
                    // These events in HandleSock do not matter.  It is just
                    //  to get the function to call hdlsock() with the
                    //  proper paramaters
                    HandleSocket(FD_ACCEPT,(INT)wParam);
               }
               else if ((lEvents&FD_CONNECT) || (lEvents&FD_WRITE)) {
                    // These events in HandleSock do not matter.  It is just
                    //  to get the function to call hdlsock() with the
                    //  proper paramaters
                    HandleSocket(FD_CONNECT,(INT)wParam);
               }
               break;

          case FD_CONNECT:
               /*  This is here to maintain backwards compatability with the */
               /*  old TCP/IP kernel.  All of the old add-ons do not want    */
               /*  any notification if the socket did not connect            */
               if (WSAGETSELECTERROR(lParam) != 0) {
                    break;
               }

          case FD_ACCEPT:
          case FD_READ:
          case FD_WRITE:
               HandleSocket(WSAGETSELECTEVENT(lParam),(INT)wParam);
               break;

#ifdef DEBUG
          default:
               shocst("GALTCPIP: Unhandled Event","Event=%lx",WSAGETSELECTEVENT(lParam));
               break;
#endif
          }
          return(NULL);
     }
     return(DefWindowProc(hWnd,message,wParam,lParam));
}

VOID
HandleSocket(                      /* Handle a socket from the list        */
LONG lEvent,                       /*   Event flags to look for            */
INT iSocket)                       /*   Socket to handle                   */
{
     CHAR onechar;

     switch (lEvent) {
     case FD_READ:
          // This line is here to maintain backwards compatibilty with the
          //  old TCP/IP kernel.  RECV needs to be called in order to
          //  trigger any other FD_READ events.  There are circumstantces
          //  that could happen when calling hdlsock() that would prevent
          //  recv from being called.  Most notably in GALTNT, GALRLGN, and
          //  GALFING there are lines that check for space in the output
          //  buffer and return before RECV can be called.  On the old system
          //  if this happened, it would wait till the next system cycle to
          //  to take over.  This is needed to insure that the new stack
          //  will send us another FD_READ event.
          if (getSktIdx(iSocket) == -1) {
               break;
          }
          if (recv(iSocket,&onechar,1,MSG_PEEK) < 0 && tcpip_errno == WSAEWOULDBLOCK) {
               break;
          }

     case FD_ACCEPT:
          hdlsock(TNFRA,iSocket);
          break;

     case FD_WRITE:
          hdlsock(TNFSC,iSocket);
          // Just to keep the old kernel happy
          if (slEvents(iSocket)&FD_WRITE) {
               RecallAdd(FD_WRITE,iSocket);
          }
          break;

     case FD_CONNECT:
          hdlsock(TNFSC,iSocket);
          // Just to keep the old kernel happy
          if (slEvents(iSocket)&FD_CONNECT) {
               RecallAdd(FD_CONNECT,iSocket);
          }
          break;

#ifdef DEBUG
     default:
          catastro("GALTCPIP: Unhandled socket event: Socket=%d, Event=%lx",
           iSocket,lEvent);
#endif
     }
}

static VOID
SelectSetup(                       /* Setup the socket to select on        */
INT iSocket)                       /*   Socket to select on                */
{
     LONG lEvents=slEvents(iSocket);

     if (lEvents != 0) {
          lEvents|=FD_CLOSE;
     }
     WSAAsyncSelect(iSocket,tcpwin,((lEvents == 0) ? 0 : WM_SOCKET_NOTIFY),
       lEvents);
}

static VOID
CreateTcpWindow(VOID)              /*  Create window for message trapping  */
{
     setmem(&tcpwc,sizeof(WNDCLASS),0);
     tcpwc.lpfnWndProc=tcpwproc;
     tcpinst=tcpwc.hInstance=GetModuleHandle(NULL);
     tcpwc.lpszClassName=wcname;
     if (RegisterClass(&tcpwc) == 0) {
          catastro("RegisterClass() Failed!");
     }
     tcpwin=CreateWindow(wcname,"TCPWIN",0L,0,0,0,0,NULL,NULL,tcpinst,NULL);
     if (tcpwin == NULL) {
          DWORD dwError;

          dwError=GetLastError();
          catastro("GALTCPIP: Failed to create TCP/IP Window! GetLastError() returned %lx",dwError);
     }
}

#endif    // GCWINNT


