/***************************************************************************
 *                                                                         *
 *   RLOGIND.C                                                             *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   Rlogin daemon for Worldgroup.  Based on telnet daemon by RNStein.     *
 *                                                                         *
 *                                        - Craig Yap 5/23/95              *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "tcpip.h"
#include "rlogind.h"
#include "galrlgnd.h"

#define FILREV "$Revision: 20 $"

#ifndef ICO2
#define tcpip_errno ips_errno
#define TNFACCP TNFRECV
#define TNFCONN TNFSEND
#endif

static INT lgnuid(CHAR *userid);
static VOID lgnrlg(VOID);
static VOID lgnrdt(struct tcpipinf *tip);
static VOID lgnhup(VOID);
static VOID lgnrst(VOID);
static VOID lgnlon(VOID);
static VOID declgnd(VOID);
static VOID lgnsesend(VOID);
static VOID inilgnd(struct tcpipinf *);
static VOID conntusr(INT,struct lgndinf *);

static VOID (*oldhup)(VOID);       /* for recording module[0]->hangup vectr*/
static VOID (*oldrst)(VOID);       /* for recording (*hdlrst)() vector     */
static VOID (*oldlon)(VOID);       /* for logon audit trail message        */
static VOID (*olddec)(VOID);       /* for recording (*decusr)() vector     */
static VOID (*oldrlg)(VOID);       /* for intercepting user relog          */
static INT (*olduid)(CHAR *userid);/* for intercepting (*chkuid)()         */
struct lgndinf *lgndinf;           /* array of user/rlogin info            */
struct lgndinf *lgnusr;            /* global pointer to current user's info*/
static INT lgnunm;                 /* lgnrdt() & subs: user number         */
static struct tcpipinf *tiptr;     /* lgnrdt() & subs: tcpip info for user */

static HMCVFILE lgnmb;             /* for GALLGND.MSG options              */
static CHAR *lgndkey;              /* key required for rlogin access       */
static INT maxlgnd;                /* max number of rlogins at once        */
static CHAR lgnonl;                /* 1=Rlogin daemon online, 0=not        */
static CHAR lgnrej;                /* (lgnonl=0) 1=reject, 0=ignore calls  */
static SHORT lgndchg;              /* rlogin server surcharge creds/min    */
static LONG lgndkchg;              /* charge / 1K bytes traffic via Rlogin */
static CHAR lgndbt;                /* audit time & bytes of Rlogin session */

VOID EXPORT
init__galrlgnd(VOID)
{
     init__tcpip();
     lgnmb=opnmsg("galrlgnd.mcv");
     lgnonl=ynopt(LGNONL);
     lgnrej=ynopt(LGNREJ);
     if (lgnonl || lgnrej) {
          lgndkey=stgopt(LGNDKEY);
          lgndchg=numopt(LGNDCHG,-32767,32767);
          lgndkchg=lngopt(LGNDKCHG,-2000000000L,2000000000L);
          maxlgnd=numopt(MAXLGND,0,250);
          lgndbt=ynopt(LGNDBT);
          oldrst=hdlrst;
          hdlrst=lgnrst;
          oldhup=module[0]->huprou;
          module[0]->huprou=lgnhup;
          oldlon=loncdi;
          loncdi=lgnlon;
          olddec=decusr;
          decusr=declgnd;
          lgndinf=(struct lgndinf *)alczer(nterms*sizeof(struct lgndinf));
          regtcpsvr(LGNNAME,LGNPORT,LGNBACKLOG,lgnincall);
          olduid=chkuid;
          chkuid=lgnuid;
          oldrlg=hdlrlg;
          hdlrlg=lgnrlg;
     }
}

VOID EXPORT
initwc__galrlgnd(VOID)
{
     init__galrlgnd();
}

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

     setmbk(lgnmb);
     clrprf();
     if (!gotchn) {
          switch (ijreas) {
          case IJRSHUT:
          default:
               rjmg=SVRSHUT;
               break;
          case IJRFULL:
               rjmg=SVRFULL;
               break;
          case IJRDENY:
               rjmg=SVRDENY;
               break;
          }
          sprintf(prfbuf,xlttxv(stpans(getasc(rjmg)),mxmssz),numcdi("TCP/IP"));
          send(clskt,prfbuf,strlen(prfbuf),0);
     }
     else {
          tiptr=&tcpipinf[lgnunm=usrnum];
          tiptr->outsnk.datstm.snkwin=lgnskw;
          tiptr->outsnk.datstm.didmov=lgndmv;
          tiptr->outsnk.datstm.moveit=lgnmit;
          if (!lgnonl) {
               byetcp(LGNNOTON);
               usrptr->state=W4KILL;
               rejinc(LGNNAME,"Rlogin disabled");
          }
          else if (lgndcnt() > maxlgnd) {
               byetcp(LGNDMANY,maxlgnd);
               usrptr->state=W4KILL;
               rejinc(LGNNAME,spr("%d Rlogining in",maxlgnd));
          }
          else {
               setmem(&lgndinf[usrnum],sizeof(struct lgndinf),0);
               sktnfy(TNFRECV,clskt,inilgnd,tiptr,usrnum);
          }
     }
}

INT
lgndcnt(VOID)                      /* count of rlogin server connections   */
{
     INT i,n;

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

static INT
lgnuid(                            /* validates rlogin User-ID             */
CHAR *userid)
{
     INT rc;

     if ((rc=olduid(userid)) == LONOK) {
          if (lgndinf[usrnum].flags&LGNCHN) {
               setmbk(lgnmb);
               if (!haskey(lgndkey) && !(usaptr->flags&HASMST)) {
                    byetcp(LGNCANT);
                    rc=LONAPP;
               }
               rstmbk();
          }
     }
     return(rc);
}

static VOID
lgnrlg(VOID)                       /* Rlogin server intercepting user relog*/
{
     lgnsesend();
     (*oldrlg)();
}

static VOID
lgnrdt(                            /* read from Rlogin client to GCDI chan */
struct tcpipinf *tip)              /* user's TCP/IP channel info           */
{                                  /* application:  sktnfy(,,lgnrdt,tiptr,)*/
     struct datstm *dsp;
     UINT nroom;
     INT numtry,oldicx,rcvact;

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

static VOID
lgnhup(VOID)                       /* Rlogin-specific hangup handling      */
{
     lgnsesend();
     (*oldhup)();
}

static VOID
lgnrst(VOID)                       /* Rlogin-specific rstchn() handling    */
{
     setmem(&lgndinf[usrnum],sizeof(struct lgndinf),0);
     (*oldrst)();
}

static VOID
lgnlon(VOID)                       /* Rlogin-specific lonstf w/lonaud      */
{
     if (tcpipinf[usrnum].server != NULL
      && tcpipinf[usrnum].server->incall == lgnincall) {
          shocst("USER LOGON VIA RLOGIN","User-ID: %s, from %s",
                  usaptr->userid,inet_ntoa(tcpipinf[usrnum].inaddr));
     }
     (*oldlon)();
}

USHORT
lgnskw(                            /* snkwin() for Rlogin sessions         */
struct datstm *dsp)                /* DataStream structure                 */
{
     INT nroom;
     struct tcpipinf *tip;
     dealwith_cdi_interrupts;

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

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

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     tip->outsnk.bufcnt+=nactual;
     tcpsnd(tip);
     restore_cdi_interrupts;
}

USHORT
lgnmit(                            /* moveit() for Rlogin sessions         */
struct datstm *dsp,                /* (pointer to sink's datstm structure) */
UCHAR *srcloc,                     /* source specifies the location        */
USHORT nwant)                      /* source wants sink to move this many  */
                                   /* returns actual number of bytes moved */
{
     INT nactual;                  /* number of bytes actually send()'d    */
     INT nroom;                    /* room in output staging buffer        */
     INT nleft;                    /* number of bytes send() left behind   */
     INT nsave;                    /* number of bytes saved in outstg[]    */
     struct tcpipinf *tip;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;
     tip=(struct tcpipinf *)dsp;
     if (tip->outsnk.bufcnt == 0 && nwant > 0 && !(tip->flags&TCPDSC)) {
          if ((nactual=send(tip->socket,(CHAR *)srcloc,nwant,0))
                                                            == SOCKET_ERROR) {
               if (tcpip_errno != EWOULDBLOCK) {
                    svrfail(tip->unum,"SEND",tip->server->port);
               }
               nactual=0;
          }
          else if ((nleft=nwant-nactual) > 0) {
               nsave=min(TCPOSZ,nleft);
               movmem(srcloc+nactual,tip->outstg,nsave);
               tip->outsnk.bufcnt=nsave;
               sktnfy(TNFSEND,tip->socket,tcpsnd,tip,tip->unum);
               nactual+=nsave;
          }
          restore_cdi_interrupts;
          return((USHORT)nactual);
     }
     if ((nsave=min(nroom=TCPOSZ-tip->outsnk.bufcnt,nwant)) > 0) {
          movmem(srcloc,(UCHAR *)tip->outstg+tip->outsnk.bufcnt,nsave);
          nroom-=nsave;
     }
     tip->outsnk.bufcnt=TCPOSZ-nroom;
     tcpsnd(tip);
     restore_cdi_interrupts;
     return((USHORT)nsave);
}

static VOID
declgnd(VOID)                      /* Rlogin server online accounting      */
{
     if (lgndinf[decusn].flags&LGNCHN) {
          deccst+=(lgndchg>>2)+((decusp->minut4&3) < (lgndchg&3));
     }
     (*olddec)();
}

static VOID
lgnsesend(VOID)                    /* end of a Rlogin server session       */
{
     struct user *usr;
     LONG newcnt,bytcnt;

     usr=usroff(usrnum);
     if (lgndinf[usrnum].flags&LGNCHN) {
          newcnt=bturep(usrnum,CNTCHR)+tcpipinf[usrnum].bytcnt;
          bytcnt=newcnt-lgndinf[usrnum].bytlon;
          lgndinf[usrnum].bytlon=newcnt;
          if (usr->tckonl > tcpipinf[usrnum].tckcon) {
               tfcchg(bytcnt,lgndkchg);
               if (lgndbt) {
                    audtcptfc("RLOGIN SERVER SESSION ENDED",bytcnt);
               }
          }
     }
}

static VOID
inilgnd(                           /* pre-login callback routine           */
struct tcpipinf *tip)
{
     INT recvd,i;

     tiptr=tip;
     if ((recvd=recv(tip->socket,vdatmp,vdasiz,0)) < 0) {
          sktcnc(TNFRECV,tip->socket);
          sktcnc(TNFSEND,tip->socket);
          bbsdsc(usrnum);
     }
     else if (recvd == 0) {
          if (tcpip_errno != EWOULDBLOCK) {
               sktcnc(TNFRECV,tip->socket);
               sktcnc(TNFSEND,tip->socket);
               bbsdsc(usrnum);
          }
     }
     else {
          lgndinf[usrnum].nbsofar+=recvd;
          for (i=0 ; i < recvd ; i++) {
               if (vdatmp[i] == '\0') {
                    lgndinf[usrnum].nnuls++;
               }
          }
          if (lgndinf[usrnum].nnuls >= NNULSR) {
               send(tip->socket,"",1,0);
               conntusr(tip->socket,&lgndinf[usrnum]);
          }
          else if (lgndinf[usrnum].nbsofar > MAXB4N) {
               sktcnc(TNFRECV,tip->socket);
               sktcnc(TNFSEND,tip->socket);
               bbsdsc(usrnum);
          }
     }
}

static VOID
conntusr(                          /* connect/logs user on to system       */
INT sockt,
struct lgndinf *usr)
{
     lgnusr=usr;
     bbscon(usrnum);
     sktnfy(TNFRECV,sockt,lgnrdt,tiptr,usrnum);
     lgndinf[usrnum].flags|=LGNCHN;
}
