/***************************************************************************
 *                                                                         *
 *   FINGER.C                                                              *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.                             *
 *                                                                         *
 *   Finger client for access to system and user information on the        *
 *   Internet, per RFC 1288.                                               *
 *                                                                         *
 *                               2/23/95 - Bob Stein & Mahesh Neelakanta   *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "tcpip.h"
#include "dns.h"
#include "fingerd.h"
#include "finger.h"
#include "galfing.h"

#define FILREV "$Revision: 13 $"

static INT globalfng(VOID);
static GBOOL fnginp(VOID);
static VOID hdlfng(CHAR *request);
static VOID fngcbk(struct dns *dnsptr);
static INT fngcall(VOID);
static VOID bgncon(struct fnginf *fng);
static VOID abtcon(GBOOL done);
static VOID endcon(VOID);
static VOID fngrcv(struct fnginf *fng);
static VOID fngsnd(struct fnginf *fng);
static VOID fngsts(VOID);
static VOID fnghup(VOID);

INT fngstt;                        /* Finger client module state number    */
struct module fngmodule={          /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     fnginp,                       /*    input routine if selected         */
     fngsts,                       /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     fnghup,                       /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL,                         /*    finish-up (sys shutdown) routine  */
};

#define FNGEXIT -1                 /* substate to trigger Finger exit      */

HMCVFILE fngmb;                    /* --- options in GALFING.MCV --------- */
INT fngchg;                        /* charge per finger request            */
INT maxfng;                        /* max simultaneous finger client reqs  */
INT fnggo;                         /* allow /FINGER for /GO FINGER?        */
INT fngtwt;                        /* connection time to wait until abort  */
INT audfng;                        /* audit outgoing finger requests?      */

VOID EXPORT
init__galfing(VOID)                /* Finger client initialization         */
{
     init__tcpip();
     stzcpy(fngmodule.descrp,gmdnam("galfing.mdf"),MNMSIZ);
     fngstt=register_module(&fngmodule);
     fngmb=opnmsg("galfing.mcv");
     maxfng=numopt(MAXFNG,0,250);
     fnggo=ynopt(FNGGO);
     fngchg=numopt(FNGCHG,-32767,32767);
     fngtwt=numopt(FNGTWT,-1,2047);
     if (fngtwt >= 0) {
          fngtwt*=16;
     }
     audfng=ynopt(AUDFNG);
     globalcmd(globalfng);
     dclvda(sizeof(struct fnginf));
     dclvda(FNGISIZ);
}

VOID EXPORT
initwc__galfing(VOID)
{
     init__galfing();
}

static INT
globalfng(VOID)                    /* global "/finger ..." command         */
{
     CHAR *cp;
     INT rc;

     if (margc >= 1 && sameas(margv[0],"/finger") && fnggo) {
          cp=margv[0];
          rstrin();
          movmem(cp,cp+3,(UINT)(INPSIZ-(cp-input)-3));
          input[INPSIZ-1]='\0';
          movmem("/go ",cp,4);
          parsin();
          rc=globalgo();
          if (usrptr->state != fngstt && usrptr->flags&MASTER) {
               setmbk(fngmb);
               prfmsg(FNGNOPAG);
               outprf(usrnum);
               rstmbk();
          }
          return(rc);
     }
     return(0);
}

static GBOOL
fnginp(VOID)                       /* Finger client user input routine     */
{
     GBOOL rc=TRUE;

     setmbk(fngmb);
     if (margc == 1 && sameas(margv[0],"X") && usrptr->substt != CONNED) {
          dnsabt();
          abtcon(FALSE);
          return(0);
     }
     do {
          bgncnc();
          switch (usrptr->substt) {
          case 0:
               cncchr();
               setmem(fngptr,sizeof(struct fnginf),0);
               fngptr->socket=-1;
               if (fngchg > 0 && !tstcrd(fngchg)) {
                    prfmsg(NOCREDS);
                    howbuy();
                    rc=FALSE;
               }
               else if (numonl(fngstt) > maxfng) {
                    prfmsg(maxfng == 0 ? FNGZMANY : FNGMANY,maxfng);
                    rc=FALSE;
               }
               else {
                    prfmsg(usrptr->substt=FNGWHAT);
               }
               break;
          case FNGWHAT:
               if (usrptr->flags&ABOIP) {
                    cncall();
                    rc=FALSE;
                    break;
               }
               if (margc == 0 || sameas(margv[0],"?")) {
                    prfmsg(FNGHLP1);
                    dnshelp();
                    prfmsg(FNGHLP3);
               }
               else {
                    rstrin();
                    hdlfng(margv[0]);
               }
               cncall();
               break;
          case LOOKING:
          case CONNING:
               cncall();
               clrprf();
               if ((usrptr->flags&(INJOIP+ABOIP)) != INJOIP) {
                    dnsabt();
                    abtcon(FALSE);
                    rc=FALSE;
               }
               break;
          case CONNED:
               cncall();
               clrprf();
               if ((usrptr->flags&(INJOIP+ABOIP)) != INJOIP) {
                    abtcon(FALSE);
                    rc=FALSE;
               }
               break;
          case FNGEXIT:
               cncall();
               clrprf();
               rc=FALSE;
               break;
          }
     } while (!endcnc());
     outprf(usrnum);
     return(rc);
}

static VOID
hdlfng(                            /* initiate finger request (dns 1st)    */
CHAR *request)                     /* request string                       */
{
     CHAR *dmn;

     if ((dmn=strrchr(request,'@')) == NULL) {
          dmn=ipaddr;
     }
     else {
          *dmn++='\0';
          if (*dmn == '\0') {
               dmn=ipaddr;
          }
     }
     stzcpy(fngptr->dns.name,dmn,DNSNSZ);
     stzcpy(fngptr->sndbuf,request,FNGOSIZ-2);
     strcat(fngptr->sndbuf,"\r\n");
     fngptr->sbfcnt=strlen(fngptr->sndbuf);
     fngptr->dns.numaddr=1;
     fngptr->dns.callbk=fngcbk;
     if (!dnsn2a(&fngptr->dns)) {
          prfmsg(usrptr->substt=LOOKING,dmn);
     }
     else {
          prf("");
     }
}

static VOID
fngcbk(                            /* Finger address lookup callback vector*/
struct dns *dnsptr)
{
     INT goahead;

     setmbk(fngmb);
     if (dnsptr->status >= 0) {
          prfmsg(CONNING,inet_ntoa(dnsptr->inaddr[0]));
          goahead=fngcall();
     }
     else {
          prfmsg(LUPERR,dnsemg);
          goahead=0;
     }
     outprf(usrnum);
     clrprf();
     if (!goahead) {
          usrptr->substt=FNGEXIT;
          btuinj(usrnum,CRSTG);
     }
}

static INT
fngcall(VOID)                      /* try to connect to Finger server      */
                                   /* returns 1=working 0=problem reported */
                                   /* fngptr->dns.inaddr[0], implicit      */
{                                  /* inputs, expects curusr() too         */
     INT rc;
     INT rwsav;

     rwsav=rcvwin;
     fngptr->lstoba=btuoba(usrnum);
     rcvwin=fngptr->rcvwin=min(FNGISIZ,fngptr->lstoba/2);
     rc=tcpdial(fngptr->dns.inaddr[0],htons(FNGPORT),0,&fngptr->socket);
     rcvwin=rwsav;
     switch (rc) {
     default:
          prfmsg(CONNERR,rc,tcpip_errno,tcpErrStg(tcpip_errno));
          break;
     case DLCNOW:
     case DLCING:
          sktnfy(TNFCONN,fngptr->socket,bgncon,fngptr,usrnum);
          usrptr->substt=CONNING;
          btuinj(usrnum,CYCLE);
          fngptr->sttime=(USHORT)(hrtval()/4096U);
          return(1);
     }
     return(0);
}

static VOID
bgncon(                            /* Connection has begun                 */
struct fnginf *fng)                /* Finger client info structure (vda)   */
{
     CHAR onechar;

     setmbk(fngmb);
     if (recv(fng->socket,&onechar,0,0) < 0 && tcpip_errno != EWOULDBLOCK) {
          abtcon(FALSE);
          prfmsg(IMMERR,fng->dns.name,tcpErrStg(tcpip_errno));
          endcon();
          return;
     }
     fng->tckonl=lngtck;
     fng->byttfc=0L;
     prfmsg(usrptr->substt=CONNED,fng->dns.name);
     outprf(usrnum);
     sktcnc(TNFCONN,fng->socket);
     sktnfy(TNFRECV,fng->socket,fngrcv,fng,usrnum);
     sktnfy(TNFSEND,fng->socket,fngsnd,fng,usrnum);
}

static VOID
abtcon(                            /* Abort connection to the server       */
GBOOL done)
{
     if (fngptr->socket != -1) {
          if (done) {
               dedcrd(fngchg,1);
          }
          if (audfng) {
               shocst(done ? "FINGER CLIENT REQUEST"
                           : "FINGER CLIENT REQUEST ABORTED",
                      "to %s, %s seconds, %s bytes traffic",
                      inet_ntoa(fngptr->dns.inaddr[0]),
                      ul2as(lngtck-fngptr->tckonl),
                      ul2as(fngptr->byttfc));
          }
          clsskt(fngptr->socket);
          fngptr->socket=-1;
     }
}

static VOID
endcon(VOID)                       /* End of connection                    */
{
     prf("");
     outprf(usrnum);
     usrptr->substt=FNGEXIT;
     btuinj(usrnum,CRSTG);
}

static VOID
fngrcv(                            /* Finger client receive-from-server    */
struct fnginf *fng)                /* user's finger client info            */
{
     INT nroom,nactual;

     setmbk(fngmb);
     nroom=min(FNGISIZ,btuoba(usrnum)/2);
     rwnmgr(fng->socket,nroom,&fng->rcvwin);
     if (nroom == 0) {
          actdet=0;
          return;
     }
     if ((nactual=recv(fng->socket,vdatmp,nroom,0)) < 0) {
          if (tcpip_errno == ECONNRESET) {
               abtcon(TRUE);
               prfmsg(SVRDISC);
               endcon();
          }
          else {
               abtcon(FALSE);
               prfmsg(RCVERR,tcpip_errno,tcpErrStg(tcpip_errno));
               endcon();
          }
     }
     else if (nactual == 0) {
          abtcon(TRUE);
          prfmsg(SVREND);
          endcon();
     }
     else {
          fngptr->byttfc+=nactual;
          nactual=memstp(vdatmp,nactual,'\0');
          vdatmp[nactual]='\0';
          prf("%s",strrpl(strstp(vdatmp,'\r'),'\n','\r'));
          outprf(usrnum);
     }
}

static VOID
fngsnd(                            /* try to send data in buffer to server */
struct fnginf *fng)                /* Finger client session info           */
{
     setmbk(fngmb);
     switch (sndmgr(fng->sndbuf,&fng->sbfcnt,fng->socket)) {
     case -1:
          abtcon(FALSE);
          prfmsg(SNDERR,tcpip_errno,tcpErrStg(tcpip_errno));
          endcon();
          break;
     case 0:
          sktcnc(TNFSEND,fng->socket);
          break;
     case 1:
          sktnfy(TNFSEND,fng->socket,fngsnd,fng,usrnum);
          break;
     }
     fngptr->byttfc+=sndact;
}

static VOID
fngsts(VOID)                       /* Finger client status handler         */
{
     INT more=1;
     USHORT sttnow;
     INT oba;

     if (status != CYCLE) {
          dfsthn();
          return;
     }
     setmbk(fngmb);
     sttnow=(USHORT)(hrtval()/4096U);
     oba=btuoba(usrnum);
     switch(usrptr->substt) {
     case CONNING:
          actdet=0;
          if (fngtwt >= 0 && sttnow-fngptr->sttime > fngtwt) {
               abtcon(FALSE);
               prfmsg(CONTMO,fngtwt/16);
               endcon();
               more=0;
          }
          break;
     case CONNED:
          if (fngptr->lstoba == oba) {
               actdet=0;
          }
          else {
               fngptr->lstoba=oba;
          }
          rwnadj(fngptr->socket,min(FNGISIZ,(OUTSIZ-1)/2),min(FNGISIZ,oba/2),FNGISIZ/4,
                &fngptr->rcvwin);
          break;
     default:
          more=0;
          break;
     }
     if (more) {
          btuinj(usrnum,CYCLE);
     }
}

static VOID
fnghup(VOID)                       /* hang up on finger client user        */
{
     if (usrptr->state == fngstt) {
          abtcon(FALSE);
     }
}

