/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                  raw_PTD.c

A demonstrator for CGIplus RawSocket scripts.  Also see rawLIB.c source.

This essentially behaves as a telnet server and expects a client to connect
using a telnet application.  The RawSocket interfaces with the system using
Pseudo-Terminal Driver.  This is not intended to be used in production.

Any resemblence to any DCLinabox, living or dead, is purely coincidental :-}

Example configuration:

  # WASD_CONFIG_SERVICE
  [[http:*:1234]]
  [ServiceRawSocket]  enabled

  # WASD_CONFIG_MAP
  [[*:1234]]
  map * /cgiplus-bin/raw_ptd

Then to test:

  telnet server-host-name 1234

As a telnet client is the expected end-user application RAW_PTD.C implements
a constrained set of telnet commands and associated options.


COPYRIGHT
---------
Copyright (C) 2016-2021 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY
---------------
07-DEC-2016  MGD  v1.0.0, initial development
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#define SOFTWAREVN "1.0.0"
#define SOFTWARENM "RAW_PTD"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <descrip.h>
#include <dvidef.h>
#include <lnmdef.h>
#include <ptddef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <ttdef.h>
#include <tt2def.h>

#include "rawlib.h"

#define DC$_TERM 6

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define Debug 0

#define FI_LI "RAW_PTD", __LINE__
#define EXIT_FI_LI(status) { printf ("[%s:%d]", FI_LI); exit(status); }

/* basically the page size on the architecture */
#ifdef VAX
#define PTD_READ_SIZE 512
#define PTD_WRITE_SIZE 512
#else
#define PTD_READ_SIZE 8192
#define PTD_WRITE_SIZE 8192
#endif

#define TELNET_SE   240  /* F0 */
#define TELNET_NOP  241  /* F1 */
#define TELNET_DM   242  /* F2 */
#define TELNET_BRK  243  /* F3 */
#define TELNET_IP   244  /* F4 */
#define TELNET_AO   245  /* F5 */
#define TELNET_AYT  246  /* F6 */
#define TELNET_EC   247  /* F7 */
#define TELNET_EL   248  /* F8 */
#define TELNET_GA   249  /* F9 */
#define TELNET_SB   250  /* FA */
#define TELNET_WILL 251  /* FB */
#define TELNET_WONT 252  /* FC */
#define TELNET_DO   253  /* FD */
#define TELNET_DONT 254  /* FE */
#define TELNET_IAC  255  /* FF */

int  ConnectedCount,
     UsageCount;

struct PtdClient {

   /* keep these adjacent and aligned on a page boundary */
   char  PtdReadBuffer [PTD_READ_SIZE],
         PtdWriteBuffer [PTD_WRITE_SIZE];

   int  PtdQueuedRead,
        PtdQueuedWrite,
        PtdReadCount,
        PtdWriteCount,
        XoffRx;

   unsigned long  ClientCount,
                  IdleCount,
                  IdleTime,
                  WriteCount;

   unsigned short  ptdchan;

   char  *WritePtr;

   char  ClientReadBuffer [PTD_WRITE_SIZE],
         PtdDevName [64];

   struct dsc$descriptor_s  PtdDevNameDsc;

   struct RawLibStruct  *RawLibPtr;
};

long  PtdClientPages = (sizeof(struct PtdClient) / 512 ) + 1;

long  CharBuf [3];

/* function prototypes */
void AddClient ();
int PtdOpen (struct PtdClient*);
void PtdClose (struct PtdClient*);
int PtdCrePrc (struct PtdClient*);
void PtdRead (struct PtdClient*);
void PtdReadClient (struct RawLibStruct*);
void PtdRemoveClient (struct RawLibStruct *rawptr);
void PtdReadAst (struct PtdClient*);
void PtdReadWriteAst (struct RawLibStruct*);
void PtdReadWriteDelay (struct RawLibStruct*);
void PtdXoffAst (struct PtdClient*);
void PtdXonAst (struct PtdClient*);
void PtdWrite (struct PtdClient*, char*, int);
void PtdWriteAst (struct PtdClient*);
char* SysTrnLnm (char*, char*, int);
uchar* TelnetCommand (struct RawLibStruct*, uchar*, int);
void TelnetLineMode (struct RawLibStruct*);

/*****************************************************************************/
/*
AST delivery is disabled during client acceptance and the add-client function
is deferred using an AST to help minimise the client setup window with a
potentially busy RawSocket application.
*/

main (int argc, char *argv[])

{
   int  len;
   char  *aptr, *cptr, *sptr, *zptr,
         *wxh;

   /*********/
   /* begin */
   /*********/

   /* don't want the C-RTL fiddling with the carriage control */
   stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin");

   RawLibInit ();

   /* set the terminal characteristics */
   CharBuf[0] = (80 << 16) | (TT$_LA100 << 8) | DC$_TERM;
   CharBuf[1] = (24 << 24) |
                TT$M_EIGHTBIT | TT$M_SCOPE | TT$M_WRAP |
                TT$M_MECHTAB | TT$M_LOWER | TT$M_TTSYNC;
   CharBuf[2] = TT2$M_EDIT | TT2$M_DRCS | TT2$M_EDITING | TT2$M_HANGUP;

   /* no clients is two minutes in seconds */
   RawLibSetLifeSecs (2*60);

   for (;;)
   {
      RawLibCgiVar ("");

      if (!RawLibIsCgiPlus())
      {
         /* must be CGIplus */
         fprintf (stdout, "Status: 500\r\n\r\nMust be CGIplus!\n");
         exit (SS$_NORMAL);
      }

      sys$setast (0);
      UsageCount++;
      AddClient ();
      sys$setast (1);

      RawLibCgiPlusEof ();
   }

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Allocate a client structure and add it to the head of the list.  Establish the
RawSocket IPC, create the user terminal (and process if SSO) and begin
processing.
*/

void AddClient ()

{
   int  status;
   struct PtdClient  *clptr;

   /*********/
   /* begin */
   /*********/

   /* PTD$ buffers must be page aligned */
   status = lib$get_vm_page (&PtdClientPages, &clptr);
   if (VMSnok(status)) EXIT_FI_LI (status);
   memset (clptr, 0, sizeof(struct PtdClient));

   /* create a RawSocket library structure for the client */
   if (!(clptr->RawLibPtr = RawLibCreate (clptr, PtdRemoveClient)))
   {
      /* failed, commonly on some RawSocket protocol issue */
      status = lib$free_vm_page (&PtdClientPages, &clptr);
      if (VMSnok(status)) EXIT_FI_LI (status);
      return;
   }

   /* open the IPC to the RawSocket (mailboxes) */
   status = RawLibOpen (clptr->RawLibPtr);
   if (VMSnok(status)) EXIT_FI_LI(status);

   status = PtdOpen (clptr);
   if (VMSnok(status)) EXIT_FI_LI (status);
                      
   /* terminate the session after two minutes idle */
   RawLibSetIdleSecs (clptr->RawLibPtr, 60 * 2);

   RawLibWatchScript (clptr->RawLibPtr, FI_LI, "!AZ", SOFTWAREID);

   TelnetLineMode (clptr->RawLibPtr);

   /* queue an asynchronous read into dynamic buffer from the client */
   RawLibRead (clptr->RawLibPtr, clptr->ClientReadBuffer, 
               sizeof(clptr->ClientReadBuffer), PtdReadClient);

   ConnectedCount++;
}

/*****************************************************************************/
/*
Remove the client structure from the list and free the memory.
*/

void PtdRemoveClient (struct RawLibStruct *rawptr)

{
   int  status;
   struct PtdClient  *clptr;

   /*********/
   /* begin */
   /*********/

   clptr = RawLibGetUserData(rawptr);

   if (clptr->ptdchan) status = ptd$delete (clptr->ptdchan);

   status = lib$free_vm_page (&PtdClientPages, &clptr);
   if (VMSnok(status)) EXIT_FI_LI (status);

   if (ConnectedCount) ConnectedCount--;
}

/*****************************************************************************/
/*
Create the pseudo-terminal and begin reading from it.
*/

int PtdOpen (struct PtdClient *clptr)

{
   int  status;
   long  InAdr [2];

   /*********/
   /* begin */
   /*********/

   InAdr[0] = (int)(clptr->PtdReadBuffer);
   InAdr[1] = (int)(clptr->PtdReadBuffer +
                    sizeof(clptr->PtdReadBuffer) +
                    sizeof(clptr->PtdWriteBuffer)-1);

   status = ptd$create (&clptr->ptdchan, 0, CharBuf, sizeof(CharBuf),
                        0, 0, 0, InAdr);
   if (VMSnok (status)) return (status);

   status = ptd$set_event_notification (clptr->ptdchan, &PtdXonAst, clptr, 0,
                                        PTD$C_SEND_XON);
   if (VMSnok (status)) return (status);

   status = ptd$set_event_notification (clptr->ptdchan, &PtdXoffAst, clptr, 0,
                                        PTD$C_SEND_XOFF);
   if (VMSnok (status)) return (status);

   /* unsolicited input to get LOGINOUT to prompt for username/password */
   clptr->PtdWriteBuffer[sizeof(ushort)+sizeof(ushort)] = '\r';
   ptd$write (clptr->ptdchan, 0, 0, clptr->PtdWriteBuffer, 1, 0, 0);

   clptr->PtdQueuedRead++;
   status = ptd$read (0, clptr->ptdchan, &PtdReadAst, clptr,
                      clptr->PtdReadBuffer, sizeof(clptr->PtdReadBuffer));

   return (status);
}

/*****************************************************************************/
/*
Cancel any outstanding terminal I/O.
*/

void PtdClose (struct PtdClient *clptr)

{
   /*********/
   /* begin */
   /*********/

   if (clptr->PtdQueuedRead || clptr->PtdQueuedWrite)
   {
      ptd$cancel (clptr->ptdchan);
      return;
   }

   RawLibClose (clptr->RawLibPtr);
}

/*****************************************************************************/
/*
Data has been read from the PTD (i.e. from the system).
*/

void PtdReadAst (struct PtdClient *clptr)

{
   int  bcnt, status;
   char  *bptr, *cptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (clptr->PtdQueuedRead) clptr->PtdQueuedRead--;

   status = *(ushort*)clptr->PtdReadBuffer;
   if (VMSok(status))
   {
      bptr = clptr->PtdReadBuffer + sizeof(ushort)+sizeof(ushort);
      bcnt = *(ushort*)(clptr->PtdReadBuffer + sizeof(ushort));
      RawLibWrite (clptr->RawLibPtr, bptr, bcnt, PtdReadWriteAst);
   }
   else
      PtdClose (clptr);
}

/*****************************************************************************/
/*
Data read from the PTD (system) has been written to the RawSocket client. 
Check status and if OK queue another read from the PTD.
*/

void PtdReadWriteAst (struct RawLibStruct *rawptr)

{
   int  status;
   struct PtdClient  *clptr;

   /*********/
   /* begin */
   /*********/

   clptr = RawLibGetUserData(rawptr);

   status = RawLibWriteStatus (rawptr);

   if (VMSok (status))
   {
      clptr->PtdQueuedRead++;
      ptd$read (0, clptr->ptdchan, &PtdReadAst, clptr,
                clptr->PtdReadBuffer,
                sizeof(clptr->PtdReadBuffer)-sizeof(ushort)-sizeof(ushort));
   }
   else
      RawLibClose (rawptr);
}

/*****************************************************************************/
/*
Asynchronous read from a RawSocket client has concluded.
*/

void PtdReadClient (struct RawLibStruct *rawptr)

{
   int  cnt;
   uchar  *cptr, *czptr, *sptr;
   uchar  buf [PTD_WRITE_SIZE];
   struct PtdClient  *clptr;

   /*********/
   /* begin */
   /*********/

   if (VMSnok (RawLibReadStatus(rawptr)))
   {
      /* WEBSOCKET_INPUT read error (can be EOF) */
      RawLibClose (rawptr);
      return;
   }

   clptr = RawLibGetUserData(rawptr);

   cnt = RawLibReadCount (rawptr);
   cptr = (uchar*)RawLibReadData (rawptr);

   sptr = buf;
   czptr = cptr + cnt;
   while (cptr < czptr)
   {
      if (*cptr == TELNET_IAC)
      {
         /* telnet interpret-as-command */
         cptr = TelnetCommand (rawptr, cptr, czptr - cptr);
         if (cptr >= czptr) break;
         if (*cptr != TELNET_IAC) continue;
      }
      if (*cptr == '\r' && (cptr+1) < czptr && *(cptr+1) == '\0')
      {
         /* telnet <CR><NUL> */
         *sptr++ = *cptr++;
         cptr++;
      }
      else
         *sptr++ = *cptr++;
   }
   cnt = sptr - buf;

   if (cnt)
      PtdWrite (clptr, (char*)buf, cnt);
   else
      RawLibRead (clptr->RawLibPtr, clptr->ClientReadBuffer,
                  sizeof(clptr->ClientReadBuffer), PtdReadClient);

   /* keep track of client input (for idle timeout) */
   clptr->ClientCount++;
}

/*****************************************************************************/
/*
Write the supplied data to the PTD (i.e. to the system).
*/

void PtdWrite
(
struct PtdClient *clptr,
char *DataPtr,
int DataCount
)
{
   int  cnt, status;
   uchar  *bptr, *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   cptr = (uchar*)(clptr->WritePtr = DataPtr);
   cnt = clptr->WriteCount = DataCount;

   sptr = bptr = (uchar*)clptr->PtdWriteBuffer + sizeof(ushort)+sizeof(ushort);
   zptr = sptr + sizeof(clptr->PtdWriteBuffer) - sizeof(ushort)-sizeof(ushort);

   while (cnt && sptr < zptr)
   {
      *sptr++ = *cptr++;
      cnt--;
   }
   clptr->PtdWriteCount = sptr - bptr;

   clptr->PtdQueuedWrite++;
   ptd$write (clptr->ptdchan, PtdWriteAst, clptr,
              clptr->PtdWriteBuffer, clptr->PtdWriteCount, 0, 0);
}

/*****************************************************************************/
/*
PTD write (to system) has completed.  If OK continue with remaining data or
read from the RawSocket client.
*/

void PtdWriteAst (struct PtdClient *clptr)

{
   int  cnt, status;

   /*********/
   /* begin */
   /*********/

   if (clptr->PtdQueuedWrite) clptr->PtdQueuedWrite--;

   status = *(ushort*)clptr->PtdWriteBuffer;
   cnt = *(ushort*)(clptr->PtdWriteBuffer+sizeof(ushort));

   /* adjust buffer window according to actual write */
   clptr->WritePtr += cnt;
   clptr->WriteCount -= cnt;

   if (VMSok(status) ||
       status == SS$_DATAOVERUN ||
       status == SS$_DATALOST)
   {
      if (status == SS$_DATAOVERUN)
         RawLibWatchScript (clptr->RawLibPtr, FI_LI, "DATAOVERUN");
      else
      if (status == SS$_DATALOST)
      {
         RawLibWatchScript (clptr->RawLibPtr, FI_LI, "DATALOST");
         /* not much point continuing with this */
         clptr->WriteCount = 0;
      }

      if (clptr->XoffRx) return;

      if (clptr->WriteCount)
         PtdWrite (clptr, clptr->WritePtr, clptr->WriteCount);
      else
         RawLibRead (clptr->RawLibPtr, clptr->ClientReadBuffer,
                     sizeof(clptr->ClientReadBuffer), PtdReadClient);
   }
   else
      PtdClose (clptr);
}

/*****************************************************************************/
/*
Flow control - stop/suspend data writes.
*/

void PtdXoffAst (struct PtdClient *clptr)

{
   /*********/
   /* begin */
   /*********/

   RawLibWatchScript (clptr->RawLibPtr, FI_LI, "XOFF");

   clptr->XoffRx = 1;
}

/*****************************************************************************/
/*
Flow control - start/resume data writes.
*/

void PtdXonAst (struct PtdClient *clptr)

{
   /*********/
   /* begin */
   /*********/

   RawLibWatchScript (clptr->RawLibPtr, FI_LI, "XON");

   /* ignore anything out-of-order */
   if (!clptr->XoffRx) return;

   clptr->XoffRx = 0;

   if (clptr->WriteCount)
      PtdWrite (clptr, clptr->WritePtr, clptr->WriteCount);
   else
      RawLibRead (clptr->RawLibPtr, clptr->ClientReadBuffer,
                  sizeof(clptr->ClientReadBuffer), PtdReadClient);
}

/*****************************************************************************/
/*
Simple-minded example assumes the command and options are all in-buffer.  Just
ignore all that can be gotten away with.  Returns a pointer to the next
available character (if any).
*/

uchar* TelnetCommand
(
struct RawLibStruct *rawptr,
uchar *DataPtr,
int DataLength
)
{
   uchar  cmd;
   uchar  *cptr, *czptr;

   /*********/
   /* begin */
   /*********/

   /* IAC is *always* followed by a command (so we always need two) */
   czptr = (cptr = DataPtr) + DataLength - 1;

   while (cptr < czptr && *cptr == TELNET_IAC)
   {
      cptr++;
      cmd = *cptr;
      if (cmd == TELNET_IAC) return (cptr);
      cptr++;
      if (cmd == TELNET_AYT)
      {
         RawLibWrite (rawptr, "\a", 1, RAWLIB_ASYNCH);
         continue;
      }
      if (cmd == TELNET_SB)
      {
         while (cptr < czptr)
         {
            if (*cptr == TELNET_IAC && *(cptr+1) == TELNET_SE)
            {
               cptr += 2;
               break;
            }
            cptr++;
         }
      }
      else
      {
         /* step over the option */
         if (cmd == TELNET_DO ||
             cmd == TELNET_DONT ||
             cmd == TELNET_WILL ||
             cmd == TELNET_WONT) cptr++;
      }
   }

   return (cptr);
}

/*****************************************************************************/
/*
Request the client provides each character as entered (disable client line
buffer/edit) and the server will echo.
*/

void TelnetLineMode (struct RawLibStruct *rawptr)

{
#define OPT_ECHO 1
#define OPT_LINEMODE 34

   static char DoCharBinary [] = { TELNET_IAC, TELNET_DO, OPT_LINEMODE,
                                   TELNET_IAC, TELNET_WILL, OPT_ECHO };

   /*********/
   /* begin */
   /*********/

   RawLibWrite (rawptr, DoCharBinary, sizeof(DoCharBinary), RAWLIB_ASYNCH);
}

/*****************************************************************************/
/*
Translate a logical name using LNM$FILE_DEV.  Returns a pointer to the value
string, or NULL if the name does not exist.  If 'LogValue' is supplied the
logical name is translated into that (assumed to be large enough), otherwise
it's translated into an internal static buffer.  'IndexValue' should be zero
for a 'flat' logical name, or 0..127 for interative translations.
*/

char* SysTrnLnm
(
char *LogName,
char *LogValue,
int IndexValue
)
{
   static unsigned short  ValueLength;
   static unsigned long  LnmAttributes,
                         LnmIndex;
   static char  StaticLogValue [256];
   static $DESCRIPTOR (LogNameDsc, "");
   static $DESCRIPTOR (LnmTableDsc, "LNM$FILE_DEV");
   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   } LnmItems [] =
   {
      { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 },
      { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 },
      { 255, LNM$_STRING, 0, &ValueLength },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   LnmIndex = IndexValue;

   LogNameDsc.dsc$a_pointer = LogName;
   LogNameDsc.dsc$w_length = strlen(LogName);
   if (LogValue)
      cptr = LnmItems[2].buf_addr = LogValue;
   else
      cptr = LnmItems[2].buf_addr = StaticLogValue;

   status = sys$trnlnm (0, &LnmTableDsc, &LogNameDsc, 0, &LnmItems);
   if (!(status & 1) || !(LnmAttributes & LNM$M_EXISTS))
   {
      if (LogValue) LogValue[0] = '\0';
      return (NULL);
   }

   cptr[ValueLength] = '\0';
   return (cptr);
}

/*****************************************************************************/

