/*
**	$VER: rlhandler.c 1.1 (08.02.94)
**
**	rlogin.service handler
**
**	© Copyright 1994 by Norbert Püschel
**	All Rights Reserved
*/

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#include <proto/multiuser.h>
#include <clib/accounts_protos.h>
#include <pragmas/accounts_pragmas.h>
#include <clib/nipc_protos.h>
#include <pragmas/nipc_pragmas.h>
#include <clib/alib_stdio_protos.h>

#include <envoy/errors.h>
#include <exec/alerts.h>
#include <exec/memory.h>
#include <dos/var.h>
#include <dos/dostags.h>
#include <dos/dosextens.h>
#include <devices/conunit.h>

#include <string.h>

#include <debug.h>

#define GVB GVF_LOCAL_ONLY|GVF_BINARY_VAR|GVF_DONT_NULL_TERM

#define tp_size sizeof(struct Task *)
#define pp_size sizeof(struct MsgPort *)

#define req(trans,n) (void *)(((UBYTE *)(trans->trans_RequestData))+(n))
#define resp(trans,n) (void *)(((UBYTE *)(trans->trans_ResponseData))+(n))

#define OFFS_CONUNIT  0
#define SIZE_CONUNIT  14*sizeof(WORD)
#define OFFS_RES1     OFFS_CONUNIT+SIZE_CONUNIT
#define SIZE_RES1     sizeof(LONG)
#define OFFS_RES2     OFFS_RES1+SIZE_RES1
#define SIZE_RES2     sizeof(LONG)
#define OFFS_RESPDATA OFFS_RES2+SIZE_RES2

#define OFFS_ARG1     0
#define SIZE_ARG1     sizeof(LONG)
#define OFFS_ARG2     OFFS_ARG1+SIZE_ARG1
#define SIZE_ARG2     sizeof(LONG)
#define OFFS_REQDATA  OFFS_ARG2+SIZE_ARG2

#define CONUNIT(ci) (APTR)&(((struct ConUnit *)((ci)->ci_IOStdReq->io_Unit))->cu_XCP)

extern volatile ULONG NumProcs;

#define CFGBUFFLEN 256
#define CBUFFLEN   2*CFGBUFFLEN+80

/* rshell startup packet:

   Arg1 = user name
   Arg2 = password
   Arg3 = host name
   Arg4 = handler port

*/

void __saveds rshell_func(void)

{
  struct muBase *muBase;
  struct MsgPort *handler_port;

  struct FileHandle *fh;
  BPTR bfh;

  UBYTE cbuff[CBUFFLEN];
  UBYTE cfg[CFGBUFFLEN];

  struct Message *msg;
  struct DosPacket *dp;

  Forbid();
  NumProcs++;
  Permit();

  handler_port = &(((struct Process *)FindTask(0))->pr_MsgPort);

  WaitPort(handler_port);
  msg = GetMsg(handler_port); /* get startup message */

  if(msg == 0 || (dp = (struct DosPacket *)(msg->mn_Node.ln_Name)) == 0) {
    Alert(AN_Unknown);
    Wait(0); /* not much that I can do here */
  }

  handler_port = (struct MsgPort *)(dp->dp_Arg4);

  strcpy(cbuff,"Execute S:Shell-Startup\n");

  muBase = (struct muBase *)OpenLibrary(MULTIUSERNAME,MULTIUSERVERSION);
  if(muBase) {
    if(muGetTaskOwner(0)) {
      muLogout(muT_All,TRUE,muT_Quiet,TRUE,TAG_DONE);
    }
    if(Stricmp((STRPTR)(dp->dp_Arg1),"Nobody")) {
      if(muLogin(muT_UserID,(STRPTR)(dp->dp_Arg1),
                 muT_Password,(STRPTR)(dp->dp_Arg2),
                 TAG_DONE)) {
        bfh = muGetConfigDirLock();
        if(bfh) {
          if(NameFromLock(bfh,cfg,CFGBUFFLEN) &&
             AddPart(cfg,muProfile_FileName,CFGBUFFLEN)) {
            strcat(cbuff,"If EXISTS ");
            strcat(cbuff,cfg);
            strcat(cbuff,"\nExecute ");
            strcat(cbuff,cfg);
            strcat(cbuff,"\nEndIf\n");
          }
          UnLock(bfh);
        }
      }
    }
    CloseLibrary((struct Library *)muBase);
  }

  fh = 0;

  if(SetVar("REMOTE",(STRPTR)(dp->dp_Arg3),-1,0)) {
    fh = (struct FileHandle *)AllocDosObjectTags(DOS_FILEHANDLE,TAG_END);
    if(fh) {
      dp->dp_Res1 = DOSTRUE;
      PutMsg(dp->dp_Port,msg);

      fh->fh_Type = handler_port;
      bfh = MKBADDR(fh);
      if(DoPkt3(handler_port,ACTION_FINDOUTPUT,bfh,0,0)) {
        Execute(cbuff,bfh,0);
        DoPkt1(handler_port,ACTION_END,fh->fh_Arg1);
      }
      FreeDosObject(DOS_FILEHANDLE,fh);
    }
  }
  if(fh == 0) {
    dp->dp_Res1 = DOSFALSE;
    dp->dp_Res2 = ENVOYERR_NORESOURCES;
    PutMsg(dp->dp_Port,msg);
  }
  Forbid();
  NumProcs--;
}

struct ConInfo {
  struct MinList   ci_Actions;
  LONG             ci_OpenCnt;
  LONG             ci_NextKey;
  struct Entity   *ci_Dest;
  struct Entity   *ci_Source;
  struct MsgPort  *ci_Handler;
  struct MsgPort  *ci_Break;
  struct IOStdReq *ci_IOStdReq;
};
  
void do_packet(struct Library *NIPCBase,
               struct ConInfo *ci,
               struct DosPacket *dp);

void do_transaction(struct Library *NIPCBase,
                    struct ConInfo *ci,
                    struct Transaction *trans);

/* handler startup packet:

   Arg1 = user name
   Arg2 = password
   Arg3 = host name
   Arg4 = entity name buffer

*/

void __saveds handler_func(void)

{
  struct Library *NIPCBase;

  struct ConInfo ci;

  struct Process *rshell;

  ULONG entsignum;
  struct Transaction *trans;

  struct Message *msg;
  struct DosPacket *dp;

  UBYTE uname[32];
  UBYTE pwd[16];
  UBYTE hname[128];

  ULONG mask;

  Forbid();
  NumProcs++;
  Permit();

  ci.ci_Handler = &(((struct Process *)FindTask(0))->pr_MsgPort);

  WaitPort(ci.ci_Handler);
  msg = GetMsg(ci.ci_Handler); /* get startup message */

  if(msg == 0 || (dp = (struct DosPacket *)(msg->mn_Node.ln_Name)) == 0) {
    Alert(AN_Unknown);
    Wait(0); /* not much that I can do here */
  }

  strcpy(uname,(STRPTR)(dp->dp_Arg1));
  strcpy(pwd,(STRPTR)(dp->dp_Arg2));
  strcpy(hname,(STRPTR)(dp->dp_Arg3));

  debug("Handler\n");

  ci.ci_Source = 0; /* because we use it as flag */

  NIPCBase = OpenLibrary("nipc.library",39);
  if(NIPCBase) {
    ci.ci_Handler = CreateMsgPort();
    if(ci.ci_Handler) {
      sprintf((STRPTR)(dp->dp_Arg4),"RHandler_%ld",FindTask(0));
      ci.ci_Source = CreateEntity(ENT_Name,dp->dp_Arg4,
                                  ENT_Public,TRUE,
                                  ENT_AllocSignal,&entsignum,
                                  TAG_DONE);
      if(ci.ci_Source) {   
        debug("Handler Entity created\n"); /* now we have to reply */
        dp->dp_Res1 = DOSTRUE;
        PutMsg(dp->dp_Port,msg);
        debug2("Waiting for Startup-Transaction: %ld\n",entsignum);
        do {
          Wait(1L<<entsignum); /* wait for startup transaction */
          trans = GetTransaction(ci.ci_Source);
        } while(trans == 0);
        if(trans->trans_Command > 1) {
          trans->trans_Error = ENVOYERR_CMDUNKNOWN;
          ci.ci_Dest = 0;
        }
        else {
          if(trans->trans_Command == 1) {
            ci.ci_IOStdReq = (struct IOStdReq *)
              AllocMem(sizeof(struct IOStdReq),MEMF_PUBLIC|MEMF_CLEAR);
            if(ci.ci_IOStdReq) {
              ci.ci_IOStdReq->io_Unit = (struct Unit *)
                AllocMem(sizeof(struct ConUnit),MEMF_PUBLIC|MEMF_CLEAR);
              if(ci.ci_IOStdReq->io_Unit) {
                CopyMem(req(trans,OFFS_CONUNIT),CONUNIT(&ci),SIZE_CONUNIT);
              }
              else {
                FreeMem(ci.ci_IOStdReq,sizeof(struct IOStdReq));
                ci.ci_IOStdReq = 0;
              }
            }
          }
          else ci.ci_IOStdReq = 0;

          debug2("IOStdReq: %ld\n",(LONG)(ci.ci_IOStdReq));
          ci.ci_Dest = FindEntity(*hname ? hname : 0,
            (STRPTR)req(trans,SIZE_CONUNIT),ci.ci_Source,
            &(trans->trans_Error));
        }

        ReplyTransaction(trans);

        if(ci.ci_Dest) {
          debug("Remote Entity detected\n");
          rshell = CreateNewProcTags(NP_Entry,rshell_func,
                                     NP_Name,"Remote Shell Executor",
                                     NP_Priority,0,
                                     NP_WindowPtr,-1,
                                     NP_Cli,TRUE,
                                     TAG_DONE);    
          if(rshell) {
            debug("Remote Shell Created\n");
            if(DoPkt4(&(rshell->pr_MsgPort),0,(LONG)uname,(LONG)pwd,
               (LONG)hname,(LONG)ci.ci_Handler)) {
              NewList((struct List *)&(ci.ci_Actions));
              ci.ci_OpenCnt = 0;
              ci.ci_NextKey = 0;
              ci.ci_Break = &(rshell->pr_MsgPort);
              debug("Starting Loop\n");
              mask = (1L<<ci.ci_Handler->mp_SigBit)|(1L<<entsignum);
    
              do {
                Wait(mask);
                do {
                  if(trans = GetTransaction(ci.ci_Source)) {
                    do_transaction(NIPCBase,&ci,trans);
                  }
                  if(msg = GetMsg(ci.ci_Handler)) {
                    dp = (struct DosPacket *)(msg->mn_Node.ln_Name);
                    do_packet(NIPCBase,&ci,dp);
                  }
                } while(msg != 0 || trans != 0);
              } while(ci.ci_OpenCnt > 0 ||
                      !IsListEmpty((struct List *)&(ci.ci_Actions)));
              debug("Ending Loop\n");
              trans = AllocTransaction(TAG_DONE);
              if(trans) {
                trans->trans_Command = 1;
                DoTransaction(ci.ci_Dest,ci.ci_Source,trans);
                FreeTransaction(trans);
              }
            }
          }
          LoseEntity(ci.ci_Dest);
        }
        if(ci.ci_IOStdReq) {
          if(ci.ci_IOStdReq->io_Unit) {
            FreeMem(ci.ci_IOStdReq->io_Unit,sizeof(struct ConUnit));
          }
          FreeMem(ci.ci_IOStdReq,sizeof(struct IOStdReq));
        }
        DeleteEntity(ci.ci_Source);
      }
      DeleteMsgPort(ci.ci_Handler);
    }
    CloseLibrary(NIPCBase);
  }
  debug("Ending Handler\n");
  if(ci.ci_Source == 0) {
    dp->dp_Res1 = DOSFALSE;
    dp->dp_Res2 = ENVOYERR_NORESOURCES;
    PutMsg(dp->dp_Port,msg);
  }
  Forbid();
  NumProcs--;
}

struct ActionNode {
  struct MinNode      an_Node;
  struct DosPacket   *an_Packet;
  struct Transaction *an_Trans;
};

void do_packet(struct Library *NIPCBase,
               struct ConInfo *ci,
               struct DosPacket *dp)

{
  struct ActionNode *an;
  struct Transaction *trans;
  struct FileHandle *fh;
  struct MsgPort *rport;
  LONG temp;

  debug2("Received Packet: %ld\n",dp->dp_Type);

  switch(dp->dp_Type) {
    case ACTION_FINDUPDATE:
    case ACTION_FINDINPUT:
    case ACTION_FINDOUTPUT:
      fh = BADDR(dp->dp_Arg1);
      fh->fh_Port = (struct MsgPort *)DOSTRUE;
      fh->fh_Arg1 = ci->ci_NextKey++;
      ci->ci_OpenCnt++;
      dp->dp_Res1 = DOSTRUE;
      ci->ci_Break = dp->dp_Port;
      dp->dp_Port = ci->ci_Handler;
      PutMsg(ci->ci_Break,dp->dp_Link);
      debug2("Open: %d\n",ci->ci_OpenCnt);
      return;
    case ACTION_END:
      debug2("Close: %ld\n",dp->dp_Arg1);
      an = (struct ActionNode *)(ci->ci_Actions.mlh_Head);
      while(an->an_Node.mln_Succ) {
        temp = an->an_Packet->dp_Type;
        if(temp != ACTION_SCREEN_MODE && temp != ACTION_WAIT_CHAR &&
           an->an_Packet->dp_Arg1 == dp->dp_Arg1) {
          AbortTransaction(an->an_Trans);
          WaitTransaction(an->an_Trans);
          FreeTransaction(an->an_Trans);
          if(temp == ACTION_READ || temp == ACTION_WRITE) {
            an->an_Packet->dp_Res1 = -1;
          }
          else {
            an->an_Packet->dp_Res1 = DOSFALSE;
          }
          an->an_Packet->dp_Res2 = ERROR_INVALID_LOCK;
          rport = an->an_Packet->dp_Port;
          an->an_Packet->dp_Port = ci->ci_Handler;
          PutMsg(rport,an->an_Packet->dp_Link);
          temp = (LONG)(an->an_Node.mln_Succ);
          Remove((struct Node *)an);
          FreeMem(an,sizeof(struct ActionNode));
          an = (struct ActionNode *)temp;
        }
        else {
          an = (struct ActionNode *)(an->an_Node.mln_Succ);
        }
      }
      ci->ci_OpenCnt--;
      dp->dp_Res1 = DOSTRUE;
      rport = dp->dp_Port;
      dp->dp_Port = ci->ci_Handler;
      PutMsg(rport,dp->dp_Link);
      return;
    case ACTION_READ:
      an = (struct ActionNode *)AllocMem(sizeof(struct ActionNode),0);
      if(an) {
        trans = AllocTransaction(TRN_AllocReqBuffer,
                                 OFFS_REQDATA,
                                 TRN_AllocRespBuffer,
                                 OFFS_RESPDATA+dp->dp_Arg3,
                                 TAG_DONE);    
        if(trans) {
          *(LONG *)req(trans,OFFS_ARG1) = ACTION_READ;
          *(LONG *)req(trans,OFFS_ARG2) = dp->dp_Arg3;
          BeginTransaction(ci->ci_Dest,ci->ci_Source,trans);
          an->an_Trans = trans;
          an->an_Packet = dp;
          AddTail((struct List *)&(ci->ci_Actions),(struct Node *)an);
          return;
        }
        FreeMem(an,sizeof(struct ActionNode));
      }
      dp->dp_Res1 = -1;
      goto nomem2;
    case ACTION_WRITE:
    case 2001: /* ACTION_FORCE */
    case 2002: /* ACTION_STACK */
    case 2003: /* ACTION_QUEUE */
      an = (struct ActionNode *)AllocMem(sizeof(struct ActionNode),0);
      if(an) {
        trans = AllocTransaction(TRN_AllocReqBuffer,
                                 OFFS_REQDATA+dp->dp_Arg3,
                                 TRN_AllocRespBuffer,
                                 OFFS_RESPDATA,
                                 TAG_DONE);    
        if(trans) {
          *(LONG *)req(trans,OFFS_ARG1) = dp->dp_Type;
          *(LONG *)req(trans,OFFS_ARG2) = dp->dp_Arg3;
          CopyMem((APTR)(dp->dp_Arg2),req(trans,OFFS_REQDATA),dp->dp_Arg3);
          BeginTransaction(ci->ci_Dest,ci->ci_Source,trans);
          an->an_Trans = trans;
          an->an_Packet = dp;
          AddTail((struct List *)&(ci->ci_Actions),(struct Node *)an);
          return;
        }
        FreeMem(an,sizeof(struct ActionNode));
      }
      if(dp->dp_Type == ACTION_WRITE) {
        dp->dp_Res1 = -1;
        goto nomem2;
      }
      break;
    case ACTION_SEEK:
      dp->dp_Res1 = -1;
      dp->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
      rport = dp->dp_Port;
      dp->dp_Port = ci->ci_Handler;
      PutMsg(rport,dp->dp_Link);
      return;
    case ACTION_WAIT_CHAR:
    case ACTION_SCREEN_MODE:
    case 2004: /* ACTION_DROP */
      an = (struct ActionNode *)AllocMem(sizeof(struct ActionNode),0);
      if(an) {
        trans = AllocTransaction(TRN_AllocReqBuffer,
                                 OFFS_REQDATA,
                                 TRN_AllocRespBuffer,
                                 OFFS_RESPDATA,
                                 TAG_DONE);    
        if(trans) {
          *(LONG *)req(trans,OFFS_ARG1) = dp->dp_Type;
          *(LONG *)req(trans,OFFS_ARG2) = dp->dp_Arg1;
          BeginTransaction(ci->ci_Dest,ci->ci_Source,trans);
          an->an_Trans = trans;
          an->an_Packet = dp;
          AddTail((struct List *)&(ci->ci_Actions),(struct Node *)an);
          return;
        }
        FreeMem(an,sizeof(struct ActionNode));
      }
      break;
    case ACTION_CHANGE_SIGNAL:
      dp->dp_Res1 = DOSTRUE;
      dp->dp_Res2 = (LONG)(ci->ci_Break);
      if(dp->dp_Arg2) {
        ci->ci_Break = (struct MsgPort *)dp->dp_Arg2;
      }
      rport = dp->dp_Port;
      dp->dp_Port = ci->ci_Handler;
      PutMsg(rport,dp->dp_Link);
      return;
    case ACTION_DISK_INFO:
      if(ci->ci_IOStdReq) {
        struct InfoData *id;
        ULONG i;

        id = (struct InfoData *)BADDR(dp->dp_Arg1);
        for(i = 0;i < sizeof(struct InfoData);i++) ((UBYTE *)id)[i] = 0;
        id->id_InUse = (LONG)(ci->ci_IOStdReq);
        dp->dp_Res1 = DOSTRUE;
        rport = dp->dp_Port;
        dp->dp_Port = ci->ci_Handler;
        PutMsg(rport,dp->dp_Link);
        return;
      }
    default:
      dp->dp_Res1 = DOSFALSE;
      dp->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
      rport = dp->dp_Port;
      dp->dp_Port = ci->ci_Handler;
      PutMsg(rport,dp->dp_Link);
      return;
  }
  dp->dp_Res1 = DOSFALSE;
nomem2:
  dp->dp_Res2 = ERROR_NO_FREE_STORE;
  rport = dp->dp_Port;
  dp->dp_Port = ci->ci_Handler;
  PutMsg(rport,dp->dp_Link);
}

void do_transaction(struct Library *NIPCBase,
                    struct ConInfo *ci,
                    struct Transaction *trans)

{
  struct ActionNode *an;
  struct DosPacket *dp;

  debug3("Transaction: Type: %ld, Com: %ld\n",trans->trans_Type,
         trans->trans_Command);

  if(trans->trans_Type != TYPE_RESPONSE) {
    if(trans->trans_Command > 15) {
      trans->trans_Error = ENVOYERR_CMDUNKNOWN;
    }
    else {
      debug2("BREAK: %ld\n",trans->trans_Command);
      if((ci->ci_Break->mp_Flags & PF_ACTION) == PA_SIGNAL) {
        Signal(ci->ci_Break->mp_SigTask,((ULONG)(trans->trans_Command))<<12);
        debug("BREAK done\n");
      }
    }
    ReplyTransaction(trans);
  }
  else {
    an = (struct ActionNode *)(ci->ci_Actions.mlh_Head);
    while(an->an_Node.mln_Succ) {
      if(an->an_Trans == trans) break;
      an = (struct ActionNode *)(an->an_Node.mln_Succ);
    }
    if(an->an_Node.mln_Succ) {
      dp = an->an_Packet;
      if(trans->trans_Error) {
        if(dp->dp_Type == ACTION_READ || dp->dp_Type == ACTION_WRITE) {
          dp->dp_Res1 = -1;
        }
        else {
          dp->dp_Res1 = DOSFALSE;
        }
        dp->dp_Res2 = ERROR_NO_FREE_STORE;
      }
      else {
        if(ci->ci_IOStdReq) {
          CopyMem(resp(trans,OFFS_CONUNIT),CONUNIT(ci),SIZE_CONUNIT);
        }
        dp->dp_Res1 = *(LONG *)resp(trans,OFFS_RES1);
        dp->dp_Res2 = *(LONG *)resp(trans,OFFS_RES2);
        if(dp->dp_Type == ACTION_READ && dp->dp_Res1 > 0) {
          CopyMem(resp(trans,OFFS_RESPDATA),(APTR)(dp->dp_Arg2),dp->dp_Res1);
        }
      }
      ci->ci_Break = dp->dp_Port;
      dp->dp_Port = ci->ci_Handler;
      PutMsg(ci->ci_Break,dp->dp_Link);
      Remove((struct Node *)an);
      FreeMem(an,sizeof(struct ActionNode));
    }      
    FreeTransaction(trans);
  }
}
