/*****************************************************************************/ #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 */ *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); } /*****************************************************************************/