/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* DCLinabox.c JavaScript based VT102 terminal emulation using WASD WebSocket communication with a pseudo-terminal connected VMS process. Based on JavaScript code from the ShellInABox project: Copyright (C) 2008-2009 Markus Gutschke . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. http://code.google.com/p/shellinabox/ For all things VT100 (et.al.) ... http://vt100.net For all things VT testing ... http://invisible-island.net/vttest/vttest.html DCLinabox may be proctored into existence. # WASD_CONFIG_GLOBAL [DclScriptProctor] 1 /cgiplus-bin/dclinabox /cgiplus-bin/dclinabox CAUTION ------- This WebSocket application allows remote login from a Web browser to the server system. This could be a security issue and so the script disables itself by default. Logical name value DCLINABOX_ENABLE controls whether this script can be used. Define this system-wide using a value of "*" to simply allow experimentation. Alternatively provide one or more comma-separated, dotted-decimal IP address to specify one or more hosts allowed to use the script, and/or one or more comman-separated IP addresses and CIDR subnet mask to specify a range of hosts. IPv4 only! For example $ DEFINE /SYSTEM DCLINABOX_ENABLE "*" $ DEFINE /SYSTEM DCLINABOX_ENABLE "192.168.1.2" $ DEFINE /SYSTEM DCLINABOX_ENABLE "192.168.1.2,192.168.1.3" $ DEFINE /SYSTEM DCLINABOX_ENABLE "192.168.1.0/24" $ DEFINE /SYSTEM DCLINABOX_ENABLE "192.168.1.0/24,192.168.2.2" By default the WebSocket, and hence all traffic to and from the DCLinabox login and session, is only allowed over Secure Sockets Layer. To allow access via clear-text connections add "ws:" somewhere in the logical name value. $ DEFINE /SYSTEM DCLINABOX_ENABLE "ws:,*" $ DEFINE /SYSTEM DCLINABOX_ENABLE "192.168.1.0/24,ws:,192.168.2.2" ASYNCHRONOUS PROCESSING ----------------------- There are multiple sources of asynchronous events in this code. 1) The pseudo-terminal driver reads and writes asynchronously from the terminal channel. Other events, such as flow control, can be delivered at any time. 2) The process created to host the terminal session has a termination mailbox that can be delivered at any time, during terminal I/O, server-script I/O, and host name lookup. 3) The ACCPORNAM data can use TCP/IP Services host name from IP address ACPCONTROL QIOs. While this action is undertaken before commencing terminal I/O it would be possible for the terminal process to terminate very early and have the associated AST deliver during host name lookup processing. ACCPORNAM and CMKRNL -------------------- From The System Services Reference manual: DVI$_TT_ACCPORNAM Returns the name of the remote access port associated with a channel number or with a physical or virtual terminal device number. If you specify a device that is not a remote terminal or a remote type that does not support this feature, $GETDVI returns a null string. The $GETDVI service returns the access port name as a string. HP recommends a buffer size of 64 bytes to return the name of the remote access port. The $GETDVI service returns the name in the format of the remote system. If the remote system is a LAT terminal server, $GETDVI returns the name as server_name/port_name. The names are separated by the slash (/) character. If the remote system is an X.29 terminal, the name is returned as network.remote_DTE. For devices using TCP/IP, the name is returned in the format Host: 192.168.1.100 Port: 1. When writing applications, use the string returned by DVI$_ACCPORNAM (instead of the physical device name) to identify remote terminals. DCLinabox optionally supports the setting of this field, with "inabox/:" Optional build via macro ACCPORNAM_BUILD (defaiult enabled), and also optional when built that way via CMKRNL privilege. When DCLINABOX.EXE is INSTALLed with CMKRNL, kernel-mode mode is used to modify a data structure (UCB) associated with the terminal device to contain such a value. If not INSTALLed with CMKRNL this is not performed. DCLinabox behaviour is not otherwise modified. To help monitor potential leakage of NPP, when the image exits DCLinabox emits an OPCOM message giving three numbers, the number of times the ACCPORNAM code was accssed, the number of times successfully set and the number of times resets. Ideally, the second and third should be identical and the first the sum of the two. Whenever kernel-mode code is executed there is a small risk to the system, and that risk grows exponentially with the less experienced author. The code in DCLinabox has been put together by MGD who has had no previous internals experience. Should your system go down in a screaming heap and it be analysed to be due to DCLinabox please refer... This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ...and remove CMKRNL from the DCLinabox executable. LOGIN PROMPT ------------ By default the DCLinabox CONNECT dialogue has username and password fields associated with it. This can be disabled by configuration. These fields and associated form structure are commonly recognised by browsers' password management capabilities and may be used to supply login credentials to the VMS system. DCLINABOX.C detects the system's login Username: and Password: prompts and for each supplies an escape sequence to the JavaScript front-end requesting the supply of any corresponding fields supplied with the CONNECT. If available, and on the first occurance, these simple strings are returned via the data stream just as if typed into the VT emulation. Voila! SINGLE SIGN-ON -------------- By default, DCLinabox terminal sessions prompt for a username and password. For sites where WASD SYSUAF authentication is available, or where the authenticated remote user string is the equivalent of a VMS username, DCLinabox can use that authenticated VMS username to establish a terminal session without further credential input (i.e. the terminal just displays the DCL prompt, ready to go). THIS IS OBVIOUSLY VERY POWERFUL AND SHOULD ONLY BE USED WITH DUE CAUTION! The functionality is enabled using the DCLINABOX_SSO logical name. This logical name is multi-valued, allowing considerable granularity in establishing allowed use of the facility. Each value begins with the name of the realm associated with authentication of the VMS username. This is separated by an equate symbol and then one or more comma-separated usernames and/or wildcard allowed to single sign-on. Preceding a username with a '!' (exclamation point) disallows the matching username to SSO. All string matches are case-insensitive. Account restrictions (e.g. times) are not evaluated. If a specific username matches it is permitted regardless of the account privileges. If a '**' (double asterisk) is specified any username is permitted regardless of the account privileges. If a '*' (single asterisk) is specified any non-privileged account is permitted to SSO. If '!*' (exclamation point then asterisk) is specified DCLinabox cannot be used except if permitted by SSO. For example, the following authentication rule ["VMS credentials"=WASD_VMS_RW=id] /cgi*-bin/dclinabox* r+w,https: would require the logical name defintion $ DEFINE /SYSTEM DCLINABOX_SSO "WASD_VMS_RW=*" to allow any such non-privileged authenticated user to create a logged-in terminal session, while the logical name definition $ DEFINE /SYSTEM DCLINABOX_SSO "WASD_VMS_RW=REN,STIMPY" would allow only users REN and STIMPY to do so. The logical name definition $ DEFINE /SYSTEM DCLINABOX_SSO "WASD_VMS_RW=**" would allow any account (privileged or non-privileged) to SSO, and $ DEFINE /SYSTEM DCLINABOX_SSO "WASD_VMS_RW=REN,!STIMPY,*" allows (perhaps privileged) REN but not STIMPY, and then any other non-privileged account. If a matching authentication realm is not present, or a matching username in a matched realm is not found, or a disabling account status, then single sign-on does not occur and the terminal session just prompts for credentials as usual. Of course, even if the logical name does not allow SSO, the access to DCLinabox is still controlled by the web server authentication and authorisation. The logical name DCLINABOX_ANNOUNCE allows an SSO session establishment announcement to be displayed in the terminal window. This multi-valued logical name appends carriage-control to each value displaying it as separate line. Single sign-on requires the executable image to be installed with privileges to allow UAI and persona services to be used. System startup requires (includes WORLD required for process name update) $ INSTALL ADD CGI-BIN:[000000]DCLINABOX.EXE /AUTH=(DETACH,SYSPRV,WORLD) and on executable image update $ INSTALL REPLACE CGI-BIN:[000000]DCLINABOX.EXE TERMINAL TITLE -------------- By default the terminal title bar displays the DCLinabox host name, VMS node name and username. To display the process name in addition (periodically updated if changes) the executable image needs to be installed with WORLD privilege. System startup requires $ INSTALL ADD CGI-BIN:[000000]DCLINABOX.EXE /AUTH=(WORLD) and on executable image update $ INSTALL REPLACE CGI-BIN:[000000]DCLINABOX.EXE IDLE SESSION ------------ An idle session is one not having terminal input for a given period. By default idle sessions are disconnected after two hours with a five minute warning. The logical name DCLINABOX_IDLE allows the number of minutes before client disconnection to be specified, the number of minutes warning (delivered in a browser alert), and the warning message (allowing language customisation). Each of these elements is delimited by a comma. Idle session management may be changed at any time and is propagated to new and existing sessions. Define to -1 to to disable idle disconnection: $ DEFINE /SYSTEM DCLINABOX_IDLE -1 To specify a one hour idle period with 5 minute warning: $ DEFINE /SYSTEM DCLINABOX_IDLE "60,5" To specify a six hour idle period with ten minute warning and local warning message (which may contain just one "%d" to substitute the minutes warning): $ DEFINE /SYSTEM DCLINABOX_IDLE - "360,10,WARNING - disconnection in %d minutes!" SESSION ANNOUNCEMENT -------------------- The logical name DCLINABOX_ALERT results in an announcement being displayed in a browser alert dialog. This alert will be delivered at session establishment if it exists at the time, perhaps as a permanent announcement, otherwise will be alerted within a minute of it first being defined. If an ephemeral announcement it should be undefined when no longer relevant. For example $ DEFINE /SYSTEM DCLINABOX_ALERT - "*** DCLinabox restart shortly - PLEASE LOG OFF ***" RENAMING THE INTERFACE ----------------------- The DCLINABOX.EXE file may be alternately named. With a public system this may be useful for reducing nuisance-value access attempts and/or an obvious attack vector by obscuring the web interface. Just use an obscured (or access-controlled) HTML terminal page, a different script executable file name, and add that script name to the .. section of the HTML terminal file as with other per-terminal configuration vardisableds. The script accessed will then be /cgiplus-bin/anexample. The logical names used would then be ANEXAMPLE_ENABLE, etc. COPYRIGHT --------- Copyright (C) 2011-2024 Mark G.Daniel This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. http://www.gnu.org/licenses/gpl.txt VERSION HISTORY --------------- 04-JAN-2024 MGD v1.7.3, bump version for JavaScript changes to paste functionality 26-OCT-2021 MGD v1.7.2, builds under IA64-hosted X86 cross-compiler 17-APR-2021 MGD v1.7.1, allow for v12.0 and later script process nomenclature 29-MAR-2021 MGD v1.7.0, can now be proctored into being set terminal TT_ACCPORNAM (requires CMKRNL) JavaScript enhancements (virtual keyboard) 03-OCT-2020 MGD v1.6.0, Allow F1..F12 on PC-style keyboards, and option-F1..F12 with option-F16..F19 on Mac extended, to be available as VT220-style function keys Long Line Edit (LLE) facility client control of hex dump terminal input and system output 19-NOV-2016 MGD v1.5.0, login credential escapes VT100.js and DCLinabox.js enhancements 18-JUL-2016 MGD v1.4.0, support DEC SHDW, DHDW, DECELR and DECSLE as VT100.js enhancements add version-dependent query-string to .css and .js resource loads in an effort to make resource updates less painful (works with big 4; Chrome, Firefox, Safari, Edge (MSIE)) 12-MAY-2015 MGD v1.3.1, keep-alive (help prevent proxy disconnect) 17-MAR-2015 MGD v1.3.0, allow executable to initiate session allow for use within the WASD acme environment 20-DEC-2013 MGD v1.2.1, bugfix; VT100.JS for Firefox [-_] keycode 14-JUL-2013 MGD v1.2.0, XON/XOFF flow control to accomodate bulk pastes make input buffer dynamic (for paste) DCLINABOX.JS paste portal 10-NOV-2012 MGD v1.1.1, bugfix; SessionManagement() NULL pointer 01-OCT-2012 MGD v1.1.0, single sign-on (no-password required terminal) dynamic terminal resize set terminal title to include logon detail DCLINABOX.EXE/JavaScript IPC 'escape' sequences refine idle session management refine process termination signaling bugfix; PtdClose() queued read and *write* 21-JUL-2012 MGD v1.0.1, bugfix; ptd$delete() during client removal bugfix; AddClient() lib$free_vm_page() memory 04-DEC-2011 MGD v1.0.0, initial development */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #define SOFTWAREVN "1.7.3" /* ^^^^^ and correspondingly update DCLINABOX.JS and DCLINABOX.HTML and LOADINABOX.JS */ #define SOFTWARENM "DCLINABOX" #define SOFTWARECR "Copyright (C) 2011-2021 Mark G.Daniel" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # error VAX no longer implemented #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #define VERSIONQS "?" SOFTWAREVN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wslib.h" #define DC$_TERM 6 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define dbug 0 /* enables more developer OPCOM messages */ #ifndef OPCOM_DBUG # define OPCOM_DBUG 0 #endif #ifdef __x86_64 # define ACCPORNAM_BUILD 0 #endif #ifndef ACCPORNAM_BUILD # define ACCPORNAM_BUILD 1 /* for experimentation do not deallocate NPP */ # define ACCPORNAM_LEAK 0 #endif #define ACCPORNAM_PREFIX "inabox/" #define FI_LI "DCLINABOX", __LINE__ #define EXIT_FI_LI(status) { printf ("[%s:%d]", FI_LI); exit(status); } /* an unlikely sequence for end-use terminal output (avoid nulls) */ #define DCLINABOX_ESCAPE "\r\x02" "DCLinabox\x03\r\\" #define DCLINABOX_ESCAPE4 '\x0d\x02\x44\x43' #define CONTROL_HUH "Huh?" #define HEXDUMP_SIZE ((PTD_READ_SIZE * 4) + 1) #define HEX_DIGITS "0123456789ABCDEF" /* basically the page size on the architecture */ #define PTD_READ_SIZE 8192 #define PTD_WRITE_SIZE 8192 #define DEFAULT_IDLE_MINS 120 #define DEFAULT_WARN_MINS 5 #define DEFAULT_WARN_MESSAGE "This idle terminal will be disconnected " \ "in %d minutes!" int AccPorNamCount, AccPorNamSetCount, AccPorNamResetCount, ConnectedCount, ProctoredScript = -1, UsageCount, VmsVersionInteger = 720; unsigned long ScriptPid, ScriptUic; char AlertLogicalName [128], AnnounceLogicalName [128], EnableLogicalName [128], IdleLogicalName [128], SingleLogicalName [128]; char AccPorNamPrefix [] = ACCPORNAM_PREFIX, DCLinaboxEscape [] = DCLINABOX_ESCAPE, ConsoleEscape [] = DCLINABOX_ESCAPE "F", /* + any old string */ AlertEscape [] = DCLINABOX_ESCAPE "A", /* + message string */ DelayEscape [] = DCLINABOX_ESCAPE "9", /* + integer string */ KeepAliveEscape [] = DCLINABOX_ESCAPE "8", LogoutEscape [] = DCLINABOX_ESCAPE "7", PasswordEscape [] = DCLINABOX_ESCAPE "6", TermSizeEscape [] = DCLINABOX_ESCAPE "5", /* + WxH string */ TerminateEscape [] = DCLINABOX_ESCAPE "4", TitleEscape [] = DCLINABOX_ESCAPE "3", /* +title string */ UsernameEscape [] = DCLINABOX_ESCAPE "2", VersionEscape [] = DCLINABOX_ESCAPE "1" SOFTWAREVN; struct PtdClient { /* keep these adjacent and aligned on a page boundary */ char PtdReadBuffer [PTD_READ_SIZE], PtdWriteBuffer [PTD_WRITE_SIZE]; int Alerted, IdleMins, LogoutResponse, OnToSystem, ProcessPid, PtdQueuedRead, PtdQueuedWrite, PtdReadCount, PtdWriteCount, WatchScript, WarnMins, WriteDelayStatus, XoffRx; unsigned long ClientCount, DviOwnUic, DviPid, IdleCount, IdleTime, TermMbxUnit, WarnTime, WriteCount; unsigned long WriteDelay[2]; unsigned short ptdchan, TermMbxChannel; char *ClientHex, *SystemHex, *WritePtr; char AccPorNam [1+64], /* counted string */ DviHostName [8+1], HttpHost [64], JpiPrcNam [15+1], OwnIdent [31+1], PtdDevName [64], RemoteAddr [31+1], RemoteHost [127+1], RemotePort [15+1], VmsUserName [12]; char TermAccount [ACC$C_TERMLEN]; struct dsc$descriptor_s PtdDevNameDsc; struct WsLibStruct *WsLibPtr; }; long PtdClientPages = (sizeof(struct PtdClient) / 512 ) + 1; long CharBuf [3]; /* function prototypes */ void AddClient (); void AdviseClientTermSize (struct PtdClient*); void ClientEscape (struct PtdClient*, char*, int); void ConsoleCallout (struct PtdClient*, char*, int, char*, ...); int DCLinaboxEnable (); void DCLinaboxExit (); int DCLinaboxSingleSignOn (struct PtdClient*); void LookupHostName (void*, void*, char*, char*); int MinimumWASD (char*); void OpcomMessage (char*, ...); void PtdBegin (struct PtdClient*); void PtdClose (struct PtdClient*); int PtdCrePrc (struct PtdClient*); void PtdCrePrcTerminateAst (struct PtdClient*); void PtdRead (struct PtdClient*); void PtdReadClient (struct WsLibStruct*); void PtdRemoveClient (struct WsLibStruct *wsptr); void PtdTerminateAst (struct PtdClient*); void PtdTerminateFree (struct PtdClient*); void PtdReadAst (struct PtdClient*); void PtdReadWriteAst (struct WsLibStruct*); void PtdReadWriteDelay (struct WsLibStruct*); void PtdXoffAst (struct PtdClient*); void PtdXonAst (struct PtdClient*); void PtdWrite (struct PtdClient*, char*, int); void PtdWriteAst (struct PtdClient*); void ScriptCallout (char*, ...); void SessionManagement (); char* SysTrnLnm (char*, char*, int); int SetAccPorNam (ushort, char[]); void SetClientAccPorNam (struct PtdClient*); int KrnlSetAccPorNam (int, char[], __int32*); /*****************************************************************************/ /* 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 WebSocket application. */ main (int argc, char *argv[]) { static unsigned long JpiPidItem = JPI$_PID, JpiPrcNamItem = JPI$_PRCNAM, JpiUicItem = JPI$_UIC; int ilen, status; ushort slen; char *aptr, *cptr, *sptr, *zptr, *wxh; char PrcNam [15+1]; $DESCRIPTOR (PrcNamDsc, PrcNam); /*********/ /* begin */ /*********/ if (argc > 1) { if (strcasecmp(argv[1],"/version") == 0) { fprintf (stdout, "%%DCLINABOX-I-VERSION, %s %s\n", SOFTWAREID, WsLibVersion()); exit (SS$_NORMAL); } } /* note the scripting account's PID and UIC */ lib$getjpi (&JpiPidItem, 0, 0, &ScriptPid, 0, 0); lib$getjpi (&JpiPrcNamItem, 0, 0, 0, &PrcNamDsc, &slen); PrcNam[slen] = '\0'; lib$getjpi (&JpiUicItem, 0, 0, &ScriptUic, 0, 0); #if ACCPORNAM_BUILD OpcomMessage ("!AZ begin !8XL", SOFTWAREID, ScriptPid); #endif /* don't want the C-RTL fiddling with the carriage control */ stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin"); WsLibInit (); /* 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; /* parse out the executable file name */ for (aptr = argv[0]; *aptr; aptr++); while (aptr > argv[0] && *aptr != ']') aptr--; if (*aptr++ != ']') EXIT_FI_LI (SS$_BUGCHECK); /* if the excutable is DCLINABOX.EXE then use "DCLinabox" */ if (strncasecmp (aptr, cptr = "DCLinabox", 9)) cptr = aptr; if (PrcNam[0] != '/') { /* not using v12 script process nomenclature */ for (sptr = PrcNam; *cptr && sptr < PrcNam+10; *sptr++ = *cptr++); sprintf (sptr, "_%4.4X", ScriptPid & 0xffff); PrcNamDsc.dsc$w_length = strlen(PrcNam); if (!(status = sys$setprn (&PrcNamDsc) & 1)) EXIT_FI_LI (status); } /* generate the logical names from the executable file name */ zptr = (sptr = AlertLogicalName) + sizeof(AlertLogicalName)-16; for (cptr = aptr; *cptr && *cptr != '.' && sptr < zptr; *sptr++ = toupper(*cptr++)); ilen = sptr - AlertLogicalName; strcpy (AlertLogicalName+ilen, "_ALERT"); strncpy (AnnounceLogicalName, AlertLogicalName, ilen); strcpy (AnnounceLogicalName+ilen, "_ANNOUNCE"); strncpy (EnableLogicalName, AlertLogicalName, ilen); strcpy (EnableLogicalName+ilen, "_ENABLE"); strncpy (IdleLogicalName, AlertLogicalName, ilen); strcpy (IdleLogicalName+ilen, "_IDLE"); strncpy (SingleLogicalName, AlertLogicalName, ilen); strcpy (SingleLogicalName+ilen, "_SSO"); /* no clients is five minutes in seconds */ WsLibSetLifeSecs (5*60); atexit (&DCLinaboxExit); SessionManagement (); for (;;) { WsLibCgiVar (""); if (!MinimumWASD ("10.1.0")) { fprintf (stdout, "Status: 500\r\n\r\nMinimum WASD v10.1.0!\n"); exit (SS$_NORMAL); } if (ProctoredScript < 0) { /**************/ /* proctored? */ /**************/ ProctoredScript = !*WsLibCgiVar ("REMOTE_ADDR") && !*WsLibCgiVar ("SERVER_ADDR"); if (ProctoredScript) { ScriptCallout ("!LIFETIME: DO-NOT-DISTURB\n"); fprintf (stdout, "Status: 204\n\n"); WsLibCgiPlusEof (); continue; } } if (!WsLibIsCgiPlus()) { /*******************/ /* must be CGIplus */ /*******************/ if (strncasecmp (cptr = WsLibCgiVar("REQUEST_URI"), "/cgi-bin/", 9)) { /* not the "standard" path */ fprintf (stdout, "Status: 500\r\n\r\nMust be CGIplus!\n"); } else { /* redirect to "standard" CGIplus path */ fprintf (stdout, "Status: 302\r\n\ Location: /cgiplus-bin/%s\r\n\ \r\n\ Must be CGIplus!\n", cptr+9); } exit (SS$_NORMAL); } if (WsLibCgiVarNull ("WEBSOCKET_INPUT")) { /*****************/ /* WebSocket IPC */ /*****************/ sys$setast (0); UsageCount++; if (DCLinaboxEnable()) AddClient (); sys$setast (1); } else { /*********************/ /* initiate terminal */ /*********************/ if (!strncmp(WsLibCgiVar("QUERY_STRING"),"frame=",6)) cptr = "acme"; else cptr = "loadinabox"; sptr = WsLibCgiVar("SCRIPT_NAME"); fprintf (stdout, "Content-Type: text/html\r\n\ \r\n\ \n\ \n\ \n\ DCLinabox\n\ \n\ \n\ \n\ \n\ \n\ \n\ DCLinabox v%s \n\ \n\ \n", SOFTWARECR, SOFTWAREVN, sptr, cptr, VERSIONQS, SOFTWAREVN); } WsLibCgiPlusEof (); } exit (SS$_NORMAL); } /*****************************************************************************/ /* */ void DCLinaboxExit () { struct PtdClient *clptr; struct WsLibStruct *wsptr = NULL; /*********/ /* begin */ /*********/ #if ACCPORNAM_BUILD OpcomMessage ("!AZ end !8XL !UL/!UL/!UL", SOFTWAREID, ScriptPid, AccPorNamCount, AccPorNamSetCount, AccPorNamResetCount); while (WsLibNext(&wsptr)) { clptr = WsLibGetUserData(wsptr); if (clptr->ptdchan) if (clptr->AccPorNam[0]) SetAccPorNam (clptr->ptdchan, ""); OpcomMessage ("!UL/!UL/!UL", AccPorNamCount, AccPorNamSetCount, AccPorNamResetCount); } #endif sys$delprc (0, 0, 0); } /*****************************************************************************/ /* Allocate a client structure and add it to the head of the list. Establish the WebSocket IPC, create the user terminal (and process if SSO) and begin processing. */ void AddClient () { int idx, len, sso, status; short int slen; char *aptr, *cptr, *sptr, *zptr; char AlertMsg [sizeof(AlertEscape)+256], AnnounceLine [256+2]; struct PtdClient *clptr; $DESCRIPTOR (AlertMsgDsc, AlertMsg); /*********/ /* begin */ /*********/ #if OPCOM_DBUG OpcomMessage ("S/AddClient() !UL/!UL/!UL", AccPorNamCount, AccPorNamSetCount, AccPorNamResetCount); #endif /* 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)); if (cptr = WsLibCgiVarNull("HTTP_HOST")) { zptr = (sptr = clptr->HttpHost) + sizeof(clptr->HttpHost)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } strcpy (clptr->OwnIdent, ""); /* create a WebSocket library structure for the client */ if (!(clptr->WsLibPtr = WsLibCreate (clptr, PtdRemoveClient))) { /* failed, commonly on some WebSocket protocol issue */ status = lib$free_vm_page (&PtdClientPages, &clptr); if (VMSnok(status)) EXIT_FI_LI (status); return; } /* open the IPC to the WebSocket (mailboxes) */ status = WsLibOpen (clptr->WsLibPtr); if (VMSnok(status)) EXIT_FI_LI(status); clptr->WatchScript = (WsLibCgiVarNull ("WATCH_SCRIPT") != NULL); if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "AddClient() %d %s %d (%d/%d/%d)", clptr->ptdchan, SOFTWAREID, ConnectedCount, AccPorNamCount, AccPorNamSetCount, AccPorNamResetCount); status = PtdCrePrc (clptr); /* inform the JavaScript which version executable it's dealing with */ WsLibWrite (clptr->WsLibPtr, VersionEscape, sizeof(VersionEscape)-1, WSLIB_ASYNCH); if (VMSnok (status)) { /* unsuccessful terminal alert */ zptr = (sptr = AlertMsg) + sizeof(AlertMsg)-1; for (cptr = AlertEscape; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\"'; AlertMsgDsc.dsc$a_pointer = sptr; AlertMsgDsc.dsc$w_length = sizeof(AlertMsg) - (sptr - AlertMsg); sys$getmsg (status, &slen, &AlertMsgDsc, 1, 0); sptr += slen; if (sptr < zptr) *sptr++ = '\"'; WsLibWrite (clptr->WsLibPtr, AlertMsg, sptr-AlertMsg, WSLIB_ASYNCH); WsLibClose (clptr->WsLibPtr, 0, NULL); return; } else if (aptr = SysTrnLnm (AlertLogicalName, NULL, 0)) { /* successful terminal alert */ zptr = (sptr = AlertMsg) + sizeof(AlertMsg)-1; for (cptr = AlertEscape; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++); WsLibWrite (clptr->WsLibPtr, AlertMsg, sptr-AlertMsg, WSLIB_ASYNCH); clptr->Alerted = 1; } if (VMSok (status) && clptr->VmsUserName[0]) { /* single sign-on terminal */ for (idx = 0; idx <= 127; idx++) { if (!SysTrnLnm (AnnounceLogicalName, AnnounceLine, idx)) break; slen = strlen(AnnounceLine); AnnounceLine[slen++] = '\r'; AnnounceLine[slen++] = '\n'; WsLibWrite (clptr->WsLibPtr, AnnounceLine, slen, WSLIB_ASYNCH); } } /* queue an asynchronous read into dynamic buffer from the client */ WsLibRead (clptr->WsLibPtr, NULL, 0, PtdReadClient); ConnectedCount++; } /*****************************************************************************/ /* Remove the client structure from the list and free the memory. */ void PtdRemoveClient (struct WsLibStruct *wsptr) { int status; struct PtdClient *clptr; /*********/ /* begin */ /*********/ clptr = WsLibGetUserData(wsptr); if (clptr->ptdchan) status = ptd$delete (clptr->ptdchan); } /*****************************************************************************/ /* Open a pseudo-terminal attached to a detached process created as the specified VMS user account. */ #define ISS$C_ID_NATURAL 1 #define IMP$M_ASSUME_SECURITY 1 #define IMP$M_ASSUME_DEFPRIV 8 int PtdCrePrc (struct PtdClient *clptr) { static unsigned long CrePrcFlagsLogin = PRC$M_DETACH | PRC$M_INTER, CrePrcFlagsSSO = PRC$M_DETACH | PRC$M_INTER | PRC$M_NOPASSWORD, DevNamItem = DVI$_DEVNAM, DviUnitItem = DVI$_UNIT, UnitItem = DVI$_UNIT; static unsigned long NeedPrvMask [2] = { PRV$M_SYSPRV | PRV$M_DETACH, 0 }; static $DESCRIPTOR (LoginOutDsc, "SYS$SYSTEM:LOGINOUT.EXE"); static char UnitNumber [16]; static $DESCRIPTOR (UnitNumberDsc, UnitNumber); int ptatus, status, PersonaHandle; unsigned long flags; short sLength; long InAdr [2]; $DESCRIPTOR (PrcNamDsc, clptr->PtdDevName); $DESCRIPTOR (PtdDevNameDsc, clptr->PtdDevName); $DESCRIPTOR (UserNameDsc, clptr->VmsUserName); /*********/ /* begin */ /*********/ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdCrePrc() %d", clptr->ptdchan); if (clptr->VmsUserName[0]) { /******************/ /* assume persona */ /******************/ UserNameDsc.dsc$w_length = strlen(clptr->VmsUserName); ptatus = sys$setprv (1, &NeedPrvMask, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); PersonaHandle = 0; status = sys$persona_create (&PersonaHandle, &UserNameDsc, ISS$M_CREATE_AUTHPRIV, 0, 0); if (VMSok (status)) { if (VmsVersionInteger >= 720) status = sys$persona_assume (&PersonaHandle, 0, 0, 0); else status = sys$persona_assume (&PersonaHandle, IMP$M_ASSUME_SECURITY, 0, 0); } flags = CrePrcFlagsSSO; } else { status = SS$_NORMAL, flags = CrePrcFlagsLogin; } /**************************/ /* create pseudo terminal */ /**************************/ if (VMSok (status)) { 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 (VMSok (status)) { status = lib$getdvi (&DevNamItem, &clptr->ptdchan, 0, 0, &PtdDevNameDsc, &sLength); if (VMSok (status)) { clptr->PtdDevName[PtdDevNameDsc.dsc$w_length = sLength] = '\0'; if ((PrcNamDsc.dsc$w_length = sLength) > 15) PrcNamDsc.dsc$w_length = 15; } } } /******************/ /* create process */ /******************/ if (VMSok (status)) { /* create a termination mailbox */ status = sys$crembx (0, &clptr->TermMbxChannel, sizeof(clptr->TermAccount), sizeof(clptr->TermAccount), /* login user termination unknown!! */ 0x0000, 0, 0, CMB$M_READONLY); if (VMSok (status)) { /* get the termination mailbox unit number */ status = lib$getdvi (&UnitItem, &clptr->TermMbxChannel, 0, 0, &UnitNumberDsc, &sLength); if (VMSok (status)) { UnitNumber[sLength] = '\0'; clptr->TermMbxUnit = atoi(UnitNumber); } } if (VMSok (status)) { /* queue a read from the termination mailbox */ status = sys$qio (0, clptr->TermMbxChannel, IO$_READLBLK, 0, PtdCrePrcTerminateAst, clptr, &clptr->TermAccount, sizeof(clptr->TermAccount), 0, 0, 0, 0); } #if OPCOM_DBUG OpcomMessage ("S/TermMbx !UL %X!8XL !UL", clptr->TermMbxChannel, status, clptr->TermMbxUnit); #endif if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "TermMbx %d %%X%08.08X |%s| %d", clptr->TermMbxChannel, status, UnitNumber, clptr->TermMbxUnit); } if (VMSok (status)) { status = sys$creprc (&clptr->ProcessPid, &LoginOutDsc, &PtdDevNameDsc, &PtdDevNameDsc, &PtdDevNameDsc, 0, 0, &PrcNamDsc, 4, 0, clptr->TermMbxUnit, flags, 0, 0); #if OPCOM_DBUG OpcomMessage ("S/$creprc() !8XL %X!8XL", clptr->ProcessPid, status); #endif } if (clptr->VmsUserName[0]) { /******************/ /* revert persona */ /******************/ ptatus = sys$persona_delete (&PersonaHandle); if (VMSnok(ptatus)) WsLibWatchScript (clptr->WsLibPtr, FI_LI, "$PERSONA_DELETE %X!8XL", ptatus); PersonaHandle = ISS$C_ID_NATURAL; if (VmsVersionInteger >= 720) ptatus = sys$persona_assume (&PersonaHandle, 0, 0, 0); else ptatus = sys$persona_assume (&PersonaHandle, IMP$M_ASSUME_SECURITY, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); ptatus = sys$setprv (0, &NeedPrvMask, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); } if (VMSok (status)) { #if ACCPORNAM_BUILD SetClientAccPorNam (clptr); #else PtdBegin (clptr); #endif } return (status); } /*****************************************************************************/ /* This can be called directly from above or from SetClientAccPorNam(). */ void PtdBegin (struct PtdClient *clptr) { int status; /*********/ /* begin */ /*********/ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdTerminateAst() %d %d|%s|", clptr->ptdchan, clptr->AccPorNam[0], clptr->AccPorNam+1); status = ptd$set_event_notification (clptr->ptdchan, &PtdXonAst, clptr, 0, PTD$C_SEND_XON); if (VMSok (status)) status = ptd$set_event_notification (clptr->ptdchan, &PtdXoffAst, clptr, 0, PTD$C_SEND_XOFF); #if 0 if (VMSok (status)) { if (!clptr->VmsUserName[0]) { /* unsolicited input to get LOGINOUT to prompt for username/password */ clptr->PtdWriteBuffer[sizeof(short)+sizeof(short)] = '\r'; ptd$write (clptr->ptdchan, 0, 0, clptr->PtdWriteBuffer, 1, 0, 0); } } #endif if (VMSok (status)) { clptr->PtdQueuedRead++; status = ptd$read (0, clptr->ptdchan, &PtdReadAst, clptr, clptr->PtdReadBuffer, sizeof(clptr->PtdReadBuffer)); } if (VMSnok (status)) ptd$delete (clptr->ptdchan); } /*****************************************************************************/ /* Whenever the $CREPRC process terminates this AST is called. We're not interested in the accounting data, just that the process is no longer there. This eems more reliable than the PTD's terminate AST and not sure exactly why. Escape character sequence is detected by DCLINABOX.JS. */ void PtdCrePrcTerminateAst (struct PtdClient *clptr) { int status; /*********/ /* begin */ /*********/ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdCrePrcTerminateAst() %d %d|%s| %%X%08.08X", clptr->ptdchan, clptr->AccPorNam[0], clptr->AccPorNam+1, 1); sys$dassgn (clptr->TermMbxChannel); if (clptr->ptdchan) status = ptd$delete (clptr->ptdchan); OpcomMessage ("DCLinabox logout !8XL !AZ !AZ", clptr->ProcessPid, clptr->OwnIdent, clptr->AccPorNam + sizeof(AccPorNamPrefix)); #if ACCPORNAM_BUILD status = SetAccPorNam (clptr->ptdchan, ""); memset (clptr->AccPorNam, 0, sizeof(clptr->AccPorNam)); #endif #if OPCOM_DBUG OpcomMessage ("S/PtdCrePrcTerminateAst() !UL/!UL/!UL", AccPorNamCount, AccPorNamSetCount, AccPorNamResetCount); #endif if (clptr->LogoutResponse) WsLibWrite (clptr->WsLibPtr, LogoutEscape, sizeof(LogoutEscape)-1, WSLIB_ASYNCH); else WsLibWrite (clptr->WsLibPtr, TerminateEscape, sizeof(TerminateEscape)-1, WSLIB_ASYNCH); /* non-empty acts as a flag indicating a lookup is in progress */ if (clptr->RemotePort[0]) clptr->RemotePort[0] = '\0'; else PtdTerminateFree (clptr); } /*****************************************************************************/ /* Free the memory allocated for the client. */ void PtdTerminateFree (struct PtdClient *clptr) { int status; /*********/ /* begin */ /*********/ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdTerminateFree()"); status = lib$free_vm_page (&PtdClientPages, &clptr); if (VMSnok(status)) EXIT_FI_LI (status); if (ConnectedCount) ConnectedCount--; } /*****************************************************************************/ /* Called when the process terminates. All the real work is performed by PtdCrePrcTerminateAst() because PtdTerminateAst() does not seem to be called when the WebSocket connection is [DISCONNECT] and not sure why. */ void PtdTerminateAst (struct PtdClient *clptr) { /*********/ /* begin */ /*********/ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdTerminateAst() %d %d|%s|", clptr->ptdchan, clptr->AccPorNam[0], clptr->AccPorNam+1); } /*****************************************************************************/ /* Cancel any outstanding terminal I/O. */ void PtdClose (struct PtdClient *clptr) { /*********/ /* begin */ /*********/ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdClose() %d", clptr->ptdchan); if (clptr->PtdQueuedRead || clptr->PtdQueuedWrite) { ptd$cancel (clptr->ptdchan); return; } WsLibClose (clptr->WsLibPtr, 0 , NULL); } /*****************************************************************************/ /* Data has been read from the PTD (i.e. from the system). */ void PtdReadAst (struct PtdClient *clptr) { int bcnt, status; char *bptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (clptr->PtdQueuedRead) clptr->PtdQueuedRead--; status = *(short*)clptr->PtdReadBuffer; if (VMSok(status)) { bptr = clptr->PtdReadBuffer + sizeof(short)+sizeof(short); bcnt = *(short*)(clptr->PtdReadBuffer + sizeof(short)); if (clptr->SystemHex) { /* perform hex dump debug */ zptr = (sptr = clptr->SystemHex) + HEXDUMP_SIZE; *sptr++ = '!'; while (bcnt && sptr < zptr) { *sptr++ = 'x'; *sptr++ = HEX_DIGITS[((*bptr & 0xf0) >> 4)]; *sptr++ = HEX_DIGITS[(*bptr & 0x0f)]; bptr++; bcnt--; } bptr = clptr->SystemHex; bcnt = sptr - clptr->SystemHex; /* write this back to the client to be displayed */ if (clptr->WriteDelay[1]) WsLibWrite (clptr->WsLibPtr, bptr, bcnt, PtdReadWriteDelay); else WsLibWrite (clptr->WsLibPtr, bptr, bcnt, PtdReadWriteAst); return; } /* Check if it looks like a LOGOUT response. e.g. "\r SYSTEM logged out at 21-JUL-2012 22:03:31.08\r" or "\r SYSTEM logged out at 21-JUL-2012 22:03\r" */ if (bcnt == 48 || bcnt == 54) { zptr = (cptr = bptr) + bcnt; if (*cptr == '\r' || *cptr == '\n') cptr++; while (cptr < zptr && *cptr == ' ') cptr++; while (cptr < zptr && *cptr != ' ') cptr++; while (cptr < zptr && *cptr == ' ') cptr++; if (cptr == bptr+16 && !strncmp (cptr, "logged out at", 13)) { cptr += 13; while (cptr < zptr && *cptr == ' ') cptr++; if (cptr < zptr && isdigit(*cptr)) cptr++; if (cptr < zptr && isdigit(*cptr)) cptr++; if (cptr < zptr && *cptr == '-') cptr++; if (cptr < zptr && isalpha(*cptr)) cptr++; if (cptr < zptr && isalpha(*cptr)) cptr++; if (cptr < zptr && isalpha(*cptr)) cptr++; if (cptr < zptr && *cptr == '-') cptr++; if (cptr < zptr && isdigit(*cptr)) cptr++; if (cptr < zptr && isdigit(*cptr)) cptr++; if (cptr < zptr && isdigit(*cptr)) cptr++; if (cptr < zptr && isdigit(*cptr)) cptr++; while (cptr < zptr && *cptr == ' ') cptr++; while (cptr < zptr && (isdigit(*cptr) || *cptr == ':' || *cptr == '.')) cptr++; /* if termination does not happen 'immediately' this gets reset */ if (cptr == zptr-1 && (*cptr == '\r' || *cptr == '\n')) clptr->LogoutResponse = 10; } } /* The JavaScript front-end destroys the credentials once supplied and so subsequent matching strings in output may trigger the escape sequences but the front-end will not have a corresponding string to respond with, meaning a one-shot mechanism is not required here. */ if (bcnt == 12) if (!memcmp (bptr, "\n\rUsername: ", 12)) WsLibWrite (clptr->WsLibPtr, UsernameEscape, sizeof(UsernameEscape)-1, WSLIB_ASYNCH); if (bcnt == 11) if (!memcmp (bptr, "\rPassword: ", 11)) WsLibWrite (clptr->WsLibPtr, PasswordEscape, sizeof(PasswordEscape)-1, WSLIB_ASYNCH); if (clptr->WriteDelay[1]) WsLibWrite (clptr->WsLibPtr, bptr, bcnt, PtdReadWriteDelay); else WsLibWrite (clptr->WsLibPtr, bptr, bcnt, PtdReadWriteAst); } else { if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdReadAst() %d %%X%08.08X", clptr->ptdchan, *(short*)clptr->PtdReadBuffer); PtdClose (clptr); } } /*****************************************************************************/ /* Introduce the specified delay to the writing of any PTD data to the JavaScript emulator. This is a development capability for when the processing of terminal data needs to be slowed down for observation. */ void PtdReadWriteDelay (struct WsLibStruct *wsptr) { int status; struct PtdClient *clptr; /*********/ /* begin */ /*********/ /* wsLIB clears the status once the AST is delivered so buffer */ clptr = WsLibGetUserData(wsptr); clptr->WriteDelayStatus = WsLibWriteStatus(wsptr); status = sys$setimr (0, &clptr->WriteDelay, PtdReadWriteAst, wsptr, 0); if (VMSnok(status)) EXIT_FI_LI (status); } /*****************************************************************************/ /* Data read from the PTD (system) has been written to the WebSocket client. Check status and if OK queue another read from the PTD. */ void PtdReadWriteAst (struct WsLibStruct *wsptr) { int status; struct PtdClient *clptr; /*********/ /* begin */ /*********/ clptr = WsLibGetUserData(wsptr); status = WsLibWriteStatus (wsptr); /* if cleared status then use the delayed buffer value */ if (!status) status = clptr->WriteDelayStatus; if (VMSok (status)) { clptr->PtdQueuedRead++; ptd$read (0, clptr->ptdchan, &PtdReadAst, clptr, clptr->PtdReadBuffer, sizeof(clptr->PtdReadBuffer)-sizeof(short)-sizeof(short)); } else { if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdReadWriteAst() %d %%X%08.08X", clptr->ptdchan, status); WsLibClose (wsptr, 0, NULL); } } /*****************************************************************************/ /* Asynchronous read from a WebSocket client has concluded. */ void PtdReadClient (struct WsLibStruct *wsptr) { int bcnt, cnt; char *bptr, *cptr, *sptr, *zptr; struct PtdClient *clptr; /*********/ /* begin */ /*********/ clptr = WsLibGetUserData(wsptr); if (VMSnok (WsLibReadStatus(wsptr))) { /* WEBSOCKET_INPUT read error (can be EOF) */ if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdReadClient() %d %%X%08.08X", clptr->ptdchan, WsLibReadStatus(wsptr)); WsLibClose (wsptr, 0, NULL); return; } /* use data from the dynamic buffer available during the AST */ if (bcnt = cnt = WsLibReadCount(wsptr)) { if (!(bptr = cptr = WsLibReadData (wsptr))) { WsLibClose (wsptr, 0, NULL); return; } /* is it a DCLinabox escape sequence */ if (bcnt >= sizeof(DCLinaboxEscape)-1 && *(ulong*)bptr == DCLINABOX_ESCAPE4 && !memcmp (bptr, DCLinaboxEscape, sizeof(DCLinaboxEscape)-1)) { ClientEscape (clptr, bptr, bcnt); WsLibRead (wsptr, NULL, 0, PtdReadClient); } else PtdWrite (clptr, bptr, bcnt); /* keep track of client input (for idle timeout) */ clptr->ClientCount++; /* reset on continued client (keyboard) input */ if (clptr->LogoutResponse) clptr->LogoutResponse--; } } /*****************************************************************************/ /* Write the supplied data to the PTD (i.e. to the system). Generally this will be keystroke-by-keystroke but if the paste portal is used there will be an influx of slabs of data, potentially tens or hundreds of kbytes. So we need flow control. Such big slabs will necessarily and effectively block real keyboard input. Consider only to be end-of-line and try to convert what might be equivalent sequences. */ void PtdWrite ( struct PtdClient *clptr, char *DataPtr, int DataCount ) { int cnt, status; char *bptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ cptr = clptr->WritePtr = DataPtr; cnt = clptr->WriteCount = DataCount; sptr = bptr = clptr->PtdWriteBuffer + sizeof(short)+sizeof(short); zptr = sptr + sizeof(clptr->PtdWriteBuffer) - sizeof(short)-sizeof(short); while (cnt && sptr < zptr) { if (cnt >= 2 && *cptr == '\r' && *(cptr+1) == '\n') { *sptr++ = *cptr++; cptr++; cnt -= 2; continue; } if (*cptr == '\n') { *sptr++ = '\r'; cptr++; cnt--; continue; } *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 WebSocket client. */ void PtdWriteAst (struct PtdClient *clptr) { int cnt, status; /*********/ /* begin */ /*********/ if (clptr->PtdQueuedWrite) clptr->PtdQueuedWrite--; status = *(short*)clptr->PtdWriteBuffer; cnt = *(short*)(clptr->PtdWriteBuffer+sizeof(short)); /* 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) WsLibWatchScript (clptr->WsLibPtr, FI_LI, "DATAOVERUN"); else if (status == SS$_DATALOST) { WsLibWatchScript (clptr->WsLibPtr, 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 WsLibRead (clptr->WsLibPtr, NULL, 0, PtdReadClient); } else { if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "PtdWriteAst() %d %%X%08.08X", clptr->ptdchan, *(short*)clptr->PtdWriteBuffer); PtdClose (clptr); } } /*****************************************************************************/ /* Flow control - stop/suspend data writes. */ void PtdXoffAst (struct PtdClient *clptr) { /*********/ /* begin */ /*********/ WsLibWatchScript (clptr->WsLibPtr, FI_LI, "XOFF"); clptr->XoffRx = 1; } /*****************************************************************************/ /* Flow control - start/resume data writes. */ void PtdXonAst (struct PtdClient *clptr) { /*********/ /* begin */ /*********/ WsLibWatchScript (clptr->WsLibPtr, 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 WsLibRead (clptr->WsLibPtr, NULL, 0, PtdReadClient); } /*****************************************************************************/ /* Client has sent a DCLinabox escape sequence. */ void ClientEscape ( struct PtdClient *clptr, char *DataPtr, int DataCount ) { unsigned int cols, rows, mSec; char *cptr, *zptr; /*********/ /* begin */ /*********/ zptr = (cptr = DataPtr) + DataCount; if (!memcmp (cptr, TermSizeEscape, sizeof(TermSizeEscape)-1)) { /* resize terminal sequence */ cols = rows = -1; cptr += sizeof(TermSizeEscape)-1; cols = atoi(cptr); while (isdigit(*cptr) && cptr < zptr) cptr++; if (*cptr == 'x') cptr++; rows = atoi(cptr); while (isdigit(*cptr) && cptr < zptr) cptr++; if (*cptr) cols = rows = -1; if (cols < 48 || cols > 511) cols = (unsigned int)-1; if (rows < 10 || rows > 255) rows = (unsigned int)-1; ptd$decterm_set_page_size (clptr->ptdchan, rows, cols); AdviseClientTermSize (clptr); } else if (!memcmp (cptr, DelayEscape, sizeof(DelayEscape)-1)) { /* non-zero to enable, zero to disable */ cptr += sizeof(DelayEscape)-1; mSec = atoi(cptr); if (mSec) { if (mSec > 10000) mSec = 10000; clptr->WriteDelay[0] = -1000; clptr->WriteDelay[1] = -1; lib$mult_delta_time (&mSec, &clptr->WriteDelay); } else clptr->WriteDelay[0] = clptr->WriteDelay[1] = 0; } } /*****************************************************************************/ /* GETDVI the terminal width and height and advise the client using the appropriate DCLinabox escape sequence. */ void AdviseClientTermSize (struct PtdClient *clptr) { static unsigned long DevBufSizItem = DVI$_DEVBUFSIZ, TtPageItem = DVI$_TT_PAGE; int cnt; unsigned long DevBufSiz, TtPage; char *cptr, *sptr, *zptr; char TermSize [sizeof(TermSizeEscape)+32]; /*********/ /* begin */ /*********/ lib$getdvi (&TtPageItem, &clptr->ptdchan, 0, &TtPage, 0, 0); lib$getdvi (&DevBufSizItem, &clptr->ptdchan, 0, &DevBufSiz, 0, 0); zptr = (sptr = TermSize) + sizeof(TermSize)-32; for (cptr = TermSizeEscape; *cptr && sptr < zptr; *sptr++ = *cptr++); sptr += sprintf (sptr, "%dx%d", DevBufSiz, TtPage); WsLibWrite (clptr->WsLibPtr, TermSize, sptr-TermSize, WSLIB_ASYNCH); } /*****************************************************************************/ /* Logical name value DCLINABOX_ENABLE controls whether this script can be used. Make the value "*" to allow all remote hosts. Alternatively provide one or more comma-separated, dotted-decimal IP address to specify one or more hosts allowed to use the script, and/or one or more comman-separated IP addresses and CIDR subnet mask to specify a range of hosts. IPv4 only! */ int DCLinaboxEnable () { unsigned int IPaddr, IPnet, mask; char ch; char *aptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (!(cptr = SysTrnLnm (EnableLogicalName, NULL, 0))) { fprintf (stdout, "Status: 403 \"%s\" undefined\r\n\r\n", EnableLogicalName); exit (1); } if (!(aptr = WsLibCgiVarNull("REMOTE_ADDR"))) return (0); /* inet_aton() is not available on VMS V7.2 */ IPaddr = inet_addr (aptr); if (IPaddr == 0xffffffff) return (0); if (!strstr (cptr, "ws:")) { if (!(sptr = WsLibCgiVarNull ("REQUEST_SCHEME"))) return (0); if (strcmp(sptr,"wss:") && strcmp(sptr,"https:")) { fprintf (stdout, "Status: 403 Must be SSL\r\n\r\n"); return (0); } } if (strchr (cptr, '*')) return (1); while (*cptr && *sptr) { while (*cptr && *cptr != ',') cptr++; if (ch = *cptr) *cptr = '\0'; if (zptr = strchr (sptr, '/')) { /* subnet mask */ *zptr = '\0'; /* inet_aton() is not available on VMS V7.2 */ IPnet = inet_addr (sptr); if (IPnet == 0xffffffff) return (0); mask = atoi(zptr+1); mask = 0xffffffff >> (32 - mask); /* if both addresses are valid and masked client address matches */ if (IPaddr && IPnet && (IPnet == (IPaddr & mask))) return (1); *zptr = '/'; } else if (!strcmp (sptr, aptr)) return (1); if (*cptr = ch) cptr++; sptr = cptr; } fprintf (stdout, "Status: 403 Not Permitted\r\n\r\n"); return (0); } /*****************************************************************************/ /* See description in code prologue. Returns SS$_NORMAL if single sign-on has been validated and should be performed, SS$_NOMOREITEMS (still a success status) if DCLinabox without SSO is permitted, and SS$_INVLOGIN if DCLinabox usage is not permitted without SSO. */ int DCLinaboxSingleSignOn (struct PtdClient *clptr) { static unsigned long SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; static unsigned long UaiFlags; static unsigned long UaiPriv [2]; static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } UaiItems [] = { { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 }, { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 }, { 0,0,0,0 } }; static $DESCRIPTOR (UserNameDsc, ""); int idx, ptatus, status, NotUserName = 0; char *cptr, *sptr, *zptr, *AuthRealm, *RemoteUser; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ if (WsLibCgiVarNull("WWW_PAPI_ASSERT")) { /* PAPI SSO environment */ if (!(AuthRealm = WsLibCgiVarNull("WWW_PAPI_CN"))) return (SS$_NOMOREITEMS); while (*AuthRealm && *AuthRealm != '@') AuthRealm++; if (*AuthRealm) AuthRealm++; if (!*AuthRealm) return (SS$_NOMOREITEMS); } else { if (!(AuthRealm = WsLibCgiVarNull("WWW_AUTH_REALM"))) return (SS$_NOMOREITEMS); if (!*AuthRealm) return (SS$_NOMOREITEMS); } if (!(RemoteUser = WsLibCgiVarNull("WWW_REMOTE_USER"))) return (SS$_NOMOREITEMS); if (!*RemoteUser) return (SS$_NOMOREITEMS); UserNameDsc.dsc$a_pointer = RemoteUser; UserNameDsc.dsc$w_length = strlen(RemoteUser); wsptr = clptr->WsLibPtr; for (idx = 0; idx <= 127; idx++) { if (!(cptr = SysTrnLnm (SingleLogicalName, NULL, idx))) break; WsLibWatchScript (wsptr, FI_LI, "\"!AZ\"", cptr); for (sptr = cptr; *sptr && *sptr != '='; sptr++); if (*sptr) *sptr++ = '\0'; /* if the realm name does not match then look for the next */ WsLibWatchScript (wsptr, FI_LI, "\"!AZ\" \"!AZ\"", AuthRealm, cptr); if (!*cptr || strcasecmp (cptr, AuthRealm)) continue; while (*cptr) { for (cptr = sptr; *sptr && *sptr != ','; sptr++); if (!*cptr) break; if (*sptr) *sptr++ = '\0'; if (!*cptr) continue; WsLibWatchScript (wsptr, FI_LI, "\"!AZ\" \"!AZ\"", RemoteUser, cptr); if (*cptr == '!') cptr++; if (strcasecmp (cptr, RemoteUser) && *cptr != '*') continue; /* check the account status */ ptatus = sys$setprv (1, &SysPrvMask, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); status = sys$getuai (0, 0, &UserNameDsc, &UaiItems, 0, 0, 0); ptatus = sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); if (VMSnok (status)) { WsLibWatchScript (wsptr, FI_LI, "$GETUAI %X!8XL", status); return (SS$_NOMOREITEMS); } if (UaiFlags & UAI$M_DISACNT) { WsLibWatchScript (wsptr, FI_LI, "UAI flags !8XL", UaiFlags); return (SS$_NOMOREITEMS); } /* wildcard match (allows privileged) */ if (*(USHORTPTR)cptr == '**') break; /* if the user name matches */ if (!strcasecmp (cptr, RemoteUser)) { if (*(cptr-1) == '!') { NotUserName = 1; continue; } NotUserName = 0; break; } /* check for vanilla user */ if ((UaiPriv[0] & ~(PRV$M_NETMBX | PRV$M_TMPMBX)) || UaiPriv[1]) { WsLibWatchScript (wsptr, FI_LI, "UAI priv !8XL !8XL", UaiPriv[0], UaiPriv[1]); return (SS$_NOMOREITEMS); } break; } if (*cptr) break; } if (cptr && *cptr && *(cptr-1) != '!' && !NotUserName) { zptr = (sptr = clptr->VmsUserName) + sizeof(clptr->VmsUserName)-1; for (cptr = RemoteUser; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr > zptr) { /* hmmm, something's askew! */ clptr->VmsUserName[0] = '\0'; return (SS$_RESULTOVF); } *sptr = '\0'; return (SS$_NORMAL); } if (cptr && *(USHORTPTR)(cptr-1) == '!*') { /* only available to SSO */ return (SS$_INVLOGIN); } return (SS$_NOMOREITEMS); } /*****************************************************************************/ /* Timer-driven function, called once every ten seconds to 1) set the title of any new terminal window(s) and any idle timeout, 2) every minute check the process name associated with the terminal and reset the title if necessary (if INSTALLed with WORLD privilege), and 3) manage idle terminals (if configured). */ void SessionManagement () { /* fifteen seconds */ static unsigned long TimerDelta [2] = { -100000000, -1 }; static unsigned long WorldMask [2] = { PRV$M_WORLD, 0 }; static unsigned long DviOwnUic, DviPid; static int AlertMsgLen, HasWorld = 1, IdleMins, PrevMin, WarnMins; static unsigned short DviHostNameLen, JpiPrcNamLen; static char *WarnMsgPtr; static char AlertMsg [sizeof(AlertEscape)+256], DviDevNam [64+1], DviHostName [8+1], IdleLogicalValue [256], IdentString [64], JpiPrcNam [15+1]; static $DESCRIPTOR (UicFaoDsc, "!%I\0"); static $DESCRIPTOR (IdentStringDsc, IdentString); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } DviItems [] = { { sizeof(DviPid), DVI$_PID, &DviPid, 0 }, { sizeof(DviOwnUic), DVI$_OWNUIC, &DviOwnUic, 0 }, { sizeof(DviHostName), DVI$_HOST_NAME, &DviHostName, &DviHostNameLen }, { 0,0,0,0 } }, JpiItems [] = { { sizeof(JpiPrcNam)-1, JPI$_PRCNAM, &JpiPrcNam, &JpiPrcNamLen }, { 0,0,0,0 } }; int len, ptatus, status, NewTitle, Time32; int64 Time64; unsigned short NumTime[7]; char *aptr, *cptr, *sptr, *zptr; char EscapeBuffer [sizeof(DCLinaboxEscape)+256+16]; struct PtdClient *clptr; struct WsLibStruct *wsptr = NULL; /*********/ /* begin */ /*********/ sys$gettim (&Time64); Time32 = decc$fix_time (&Time64); sys$numtim (&NumTime, &Time64); if (NumTime[4] != PrevMin) { /****************/ /* every minute */ /****************/ /* idle session management can be changed at any point */ IdleMins = WarnMins = 0; WarnMsgPtr = NULL; if (cptr = SysTrnLnm (IdleLogicalName, IdleLogicalValue, 0)) { IdleMins = atoi(cptr); while (*cptr && *cptr != ',') cptr++; if (*cptr) cptr++; WarnMins = atoi(cptr); while (*cptr && *cptr != ',') cptr++; if (*cptr) cptr++; if (!*(WarnMsgPtr = cptr)) WarnMsgPtr = DEFAULT_WARN_MESSAGE; } /* defining idle minutes to -1 disables idle session management */ if (IdleMins >= 0) { if (!IdleMins) IdleMins = DEFAULT_IDLE_MINS; if (!WarnMins) WarnMins = DEFAULT_WARN_MINS; if (IdleMins <= WarnMins) IdleMins = WarnMins + DEFAULT_WARN_MINS; if (!WarnMsgPtr) WarnMsgPtr = DEFAULT_WARN_MESSAGE; } /* check for the presence of an ALERT logical name and value */ if (aptr = SysTrnLnm (AlertLogicalName, NULL, 0)) { if (!AlertMsg[0] || strcmp (aptr, AlertMsg+sizeof(AlertEscape)-1)) { /* value has been defined/changed since last time */ while (WsLibNext(&wsptr)) { /* if not a new session reset alerted flag */ clptr = WsLibGetUserData(wsptr); if (clptr->DviOwnUic) clptr->Alerted = 0; } zptr = (sptr = AlertMsg) + sizeof(AlertMsg)-1; for (cptr = AlertEscape; *cptr && sptr < zptr; *sptr++ = *cptr++); while (*aptr && sptr < zptr) *sptr++ = *aptr++; *sptr = '\0'; AlertMsgLen = sptr - AlertMsg; } } else AlertMsg[0] = '\0'; } /****************/ /* all sessions */ /****************/ while (WsLibNext(&wsptr)) { clptr = WsLibGetUserData(wsptr); if (!clptr->DviOwnUic) { /***************/ /* new session */ /***************/ status = sys$getdviw (0, clptr->ptdchan, 0, &DviItems, 0, 0, 0, 0, 0); if (VMSnok (status)) continue; /* for a LOGINOUT terminal, ownership is changed after login */ if (DviOwnUic == ScriptUic) continue; /* provoke as an immediate title change as we can */ PrevMin = -1; clptr->DviOwnUic = DviOwnUic; clptr->DviPid = DviPid; DviHostName[DviHostNameLen] = '\0'; strcpy (clptr->DviHostName, DviHostName); sys$fao (&UicFaoDsc, 0, &IdentStringDsc, clptr->DviOwnUic); zptr = (sptr = clptr->OwnIdent) + sizeof(clptr->OwnIdent)-1; if (strchr (IdentString, ',')) for (cptr = IdentString; *cptr && sptr < zptr; *sptr++ = *cptr++); else { /* strip the [] from the identifier */ if (*(cptr = IdentString) == '[') cptr++; while (*cptr && *cptr != ']' && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; OpcomMessage ("DCLinabox login !8XL !AZ !AZ", clptr->ProcessPid, clptr->OwnIdent, clptr->AccPorNam + sizeof(AccPorNamPrefix)); } /*****************/ /* session title */ /*****************/ if (NumTime[4] != PrevMin) { JpiPrcNam[0] = '\0'; if (HasWorld) { ptatus = sys$setprv (1, &WorldMask, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); if (ptatus == SS$_NOTALLPRIV) HasWorld = 0; } if (HasWorld) { status = sys$getjpiw (0, &clptr->ProcessPid, 0, &JpiItems, 0, 0, 0); ptatus = sys$setprv (0, &WorldMask, 0, 0); if (VMSnok (ptatus)) EXIT_FI_LI(ptatus); if (VMSok(status)) JpiPrcNam[JpiPrcNamLen] = '\0'; } if (JpiPrcNam[0] && strcasecmp (JpiPrcNam, clptr->JpiPrcNam)) { strcpy (clptr->JpiPrcNam, JpiPrcNam); len = snprintf (EscapeBuffer, sizeof(EscapeBuffer), "%sDCLinabox: %s %s::%s%s%s%s", TitleEscape, clptr->HttpHost, DviHostName, clptr->OwnIdent, clptr->JpiPrcNam[0] ? " \"" : "", clptr->JpiPrcNam, clptr->JpiPrcNam[0] ? "\"" : ""); if (len > 0) WsLibWrite (wsptr, EscapeBuffer, len, WSLIB_ASYNCH); } } /****************/ /* idle session */ /****************/ if (IdleMins != clptr->IdleMins || WarnMins != clptr->WarnMins) { /* (re)set and (re)calculate */ clptr->IdleMins = IdleMins; clptr->WarnMins = WarnMins; if (IdleMins > 0) { clptr->IdleTime = Time32 + (clptr->IdleMins * 60); clptr->WarnTime = clptr->IdleTime - (clptr->WarnMins * 60); clptr->IdleCount = clptr->ClientCount; } else clptr->IdleTime = clptr->WarnTime = 0; } else if (clptr->IdleTime && clptr->ClientCount > clptr->IdleCount) { /* there has been client input since last time - reset timeout */ clptr->IdleCount = clptr->ClientCount; clptr->IdleTime = Time32 + (clptr->IdleMins * 60); clptr->WarnTime = clptr->IdleTime - (clptr->WarnMins * 60); } else if (clptr->IdleTime && clptr->IdleTime < Time32) { clptr->IdleTime = clptr->WarnTime = 0; clptr->Alerted = 1; WsLibClose (wsptr, 0, NULL); /* avoid trying to bang out an alert message after closure */ continue; } else if (clptr->WarnTime && clptr->WarnTime < Time32) { clptr->WarnTime = 0; zptr = (sptr = EscapeBuffer) + sizeof(EscapeBuffer)-16; for (cptr = AlertEscape; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = WarnMsgPtr; *cptr && *(USHORTPTR)cptr != '%d' && sptr < zptr; *sptr++ = *cptr++); if (*(USHORTPTR)cptr == '%d') { cptr += 2; sprintf (sptr, "%d", WarnMins); while (*sptr && sptr < zptr) sptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } WsLibWrite (wsptr, EscapeBuffer, sptr-EscapeBuffer, WSLIB_ASYNCH); /* avoid banging out an alert message at the same time */ continue; } /*****************/ /* alert message */ /*****************/ if (AlertMsg[0] && !clptr->Alerted) { clptr->Alerted = 1; WsLibWrite (clptr->WsLibPtr, AlertMsg, AlertMsgLen, WSLIB_ASYNCH); } /**************/ /* keep-alive */ /**************/ /* every minute send a keep-alive to (help) prevent proxy disconnect */ if (NumTime[4] != PrevMin) WsLibWrite (clptr->WsLibPtr, KeepAliveEscape, sizeof(KeepAliveEscape)-1, WSLIB_ASYNCH); } PrevMin = NumTime[4]; status = sys$setimr (0, &TimerDelta, SessionManagement, 0, 0); if (VMSnok(status)) EXIT_FI_LI (status); } /*****************************************************************************/ /* Provide [X]DCL + [X]Script WATCHable data exactly inside the data stream to and from the JavaScript. */ void ConsoleCallout ( struct PtdClient *clptr, char* module, int line, char *fmt, ... ) { char buf [256]; char *sptr; va_list ap; // if (!clptr->WatchScript) return; sptr = buf; /* add an explicit UTF-8 character to force WSLib to buffer the data */ sptr += sprintf (sptr, "%s%c%s:%d ", ConsoleEscape, 0xd7, module, line); va_start (ap, fmt); sptr += vsprintf (sptr, fmt, ap); va_end (ap); if (sptr-buf >= sizeof(buf)) EXIT_FI_LI (SS$_BUGCHECK); WsLibWrite (clptr->WsLibPtr, buf, sptr-buf, WSLIB_ASYNCH); } /*****************************************************************************/ /* Provide a callout to the server. Must be a '!' (no response callout). */ void ScriptCallout (char *fmt, ...) { int retval; va_list ap; /* must be received as records */ fflush (stdout); fprintf (stdout, "%s\n", SysTrnLnm("CGIPLUSESC", NULL, 0)); fflush (stdout); va_start (ap, fmt); retval = vfprintf (stdout, fmt, ap); va_end (ap); fflush (stdout); fprintf (stdout, "%s\n", SysTrnLnm("CGIPLUSEOT", NULL, 0)); fflush (stdout); } /****************************************************************************/ /* Getting developer information out of this application can sometimes be a royal PITA. $FAO formatted print statement to OPCOM. A fixed-size, internal buffer of 986 bytes maximum is used and the result output as an OPCOM message. If the format string begins with "S/" it is sent to SOFTWARE, otherwise CENTRAL. */ void OpcomMessage ( char *FormatString, ... ) { static $DESCRIPTOR (FaoDsc, ""); static $DESCRIPTOR (OpcomDsc, ""); static $DESCRIPTOR (OpcomMsgDsc, ""); int status, argcnt, target; unsigned short ShortLength; unsigned long *vecptr; unsigned long FaoVector [31+1]; va_list argptr; struct { unsigned long TargetType; unsigned long RequestId; char MsgText [986+1]; } OpcomMsg; /*********/ /* begin */ /*********/ va_count (argcnt); if (dbug) fprintf (stdout, "OpcomMessage() |%s| %d\n", FormatString, argcnt); if (argcnt > 31+1) exit (SS$_OVRMAXARG); vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 1; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); *vecptr = 0; if (*(USHORTPTR)FormatString == 'S/') { target = OPC$M_NM_SOFTWARE; FormatString += 2; } else target = OPC$M_NM_CENTRL; FaoDsc.dsc$a_pointer = FormatString; FaoDsc.dsc$w_length = strlen(FormatString); OpcomMsgDsc.dsc$a_pointer = (char*)&OpcomMsg.MsgText; OpcomMsgDsc.dsc$w_length = sizeof(OpcomMsg.MsgText)-1; status = sys$faol (&FaoDsc, &ShortLength, &OpcomMsgDsc, &FaoVector); if (VMSnok (status)) EXIT_FI_LI(status); OpcomMsg.MsgText[ShortLength] = '\0'; if (dbug) fprintf (stdout, "%d |%s|\n", ShortLength, OpcomMsg.MsgText); OpcomMsg.TargetType = OPC$_RQ_RQST + ((target & 0xffffff) << 8); OpcomMsg.RequestId = 0; OpcomDsc.dsc$a_pointer = (char*)&OpcomMsg; OpcomDsc.dsc$w_length = ShortLength + 8; status = sys$sndopr (&OpcomDsc, 0); if (VMSnok (status)) EXIT_FI_LI(status); } /*****************************************************************************/ /* Return true or false depending on whether the server is an equal or later release to the supplied version string. */ int MinimumWASD (char *vstring) { int major[2], minor[2], tweak[2]; char *cptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "MinimumWASD() %s\n", vstring); major[0] = minor[0] = tweak[0] = major[1] = minor[1] = tweak[1] = 0; if (sscanf (vstring, "%d.%d.%d", &major[0], &minor[0], &tweak[0]) < 3) return (0); if (!(cptr = strstr (WsLibCgiVar("SERVER_SOFTWARE"), "WASD/"))) return (0); if (sscanf (cptr+5, "%d.%d.%d", &major[1], &minor[1], &tweak[1]) < 3) return (0); if (major[1] > major[0]) return (1); if (major[1] == major[0]) { if (minor[1] > minor[0]) return (1); if (minor[1] == minor[0] && tweak[1] >= tweak[0]) return (1); } return (0); } /*****************************************************************************/ /* 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); } /*****************************************************************************/ /* Get the IP name using asynchronous address-to-name lookup. Despite the implied IPv6 functionality TCP/IP Services lookup only supports IPv4 (sigh, one day perhaps). When initiating a lookup |astp| must be NULL, |astf| must specify the AST function, |addr| the IP address as a string, and |name| a pointer to a buffer 128 bytes capacity. The AST function is called with NULL parameter if the host name is not resolved, or with a pointer to a string that must be freed when no longer required. There's a bit of sleight of hand going on here. When this function calls itself as an AST the |astp| (AST parameter) points to the internal LookupStruct below. When called to lookup an address it points to an opaque object used by the lookup AST function. How to tell the difference? A couple of magic numbers sandwiching the internal structure! */ void LookupHostName ( void *astp, void *astf, char *addr, char *name ) { #define RETRY_ATTEMPTS 5 #define INETACP$C_TRANS 2 #define INETACP_FUNC$C_GETHOSTBYNAME 1 #define INETACP_FUNC$C_GETHOSTBYADDR 2 struct LookupStruct { int64 sleight1; int RetryCount; ushort HostNameLength; ulong Ip4Address; ulong Ip6Address [4]; char HostName [127+1], IpAddrStr [31+1]; char *NamePtr; struct dsc$descriptor HostAddressDsc; struct dsc$descriptor HostNameDsc; struct _iosb LookupIOsb; void *AstFunction; void *AstParam; int64 sleight2; }; static $DESCRIPTOR (TcpIpDeviceDsc, "UCX$DEVICE"); static unsigned char ControlSubFunction [4] = { INETACP_FUNC$C_GETHOSTBYADDR, INETACP$C_TRANS, 0, 0 }; static struct dsc$descriptor ControlSubFunctionDsc = { 4, DSC$K_DTYPE_T, DSC$K_CLASS_S, (char*)&ControlSubFunction }; static int CacheUsed; static unsigned short LookupChannel; int status; char *cptr, *sptr, *zptr; struct LookupStruct *luptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "LookupHostName()\n"); if (!LookupChannel) { /* assign a channel to the internet template device */ status = sys$assign (&TcpIpDeviceDsc, &LookupChannel, 0, 0); if (VMSnok (status)) exit (status); } luptr = (struct LookupStruct*)astp; if (luptr->sleight1 != 0xbadc0ffee0ddf00d || luptr->sleight2 != 0xbadc0ffee0ddf00d) { /**************************/ /* lookup this IP address */ /**************************/ if (!(astf && addr && name)) EXIT_FI_LI (SS$_BUGCHECK); luptr = (struct LookupStruct*)calloc (1, sizeof(struct LookupStruct)); if (!luptr) { status = sys$dclast (astf, astp, 0); if (VMSnok (status)) EXIT_FI_LI (status); return; } luptr->sleight1 = luptr->sleight2 = 0xbadc0ffee0ddf00d; luptr->AstFunction = astf; luptr->AstParam = astp; luptr->NamePtr = name; luptr->RetryCount = RETRY_ATTEMPTS; luptr->HostNameDsc.dsc$b_class = DSC$K_CLASS_S; luptr->HostNameDsc.dsc$b_dtype = DSC$K_DTYPE_T; luptr->HostNameDsc.dsc$w_length = sizeof(luptr->HostName)-1; luptr->HostNameDsc.dsc$a_pointer = luptr->HostName; luptr->HostAddressDsc.dsc$b_class = DSC$K_CLASS_S; luptr->HostAddressDsc.dsc$b_dtype = DSC$K_DTYPE_T; if (strchr (addr, ':')) { if (inet_pton (AF_INET6, addr, &luptr->Ip6Address) != 1) { free (luptr); status = sys$dclast (astf, astp, 0); if (VMSnok (status)) EXIT_FI_LI (status); return; } luptr->Ip4Address = 0; } else if (inet_pton (AF_INET, addr, &luptr->Ip4Address) != 1) { free (luptr); status = sys$dclast (astf, astp, 0); if (VMSnok (status)) EXIT_FI_LI (status); return; } zptr = (sptr = luptr->IpAddrStr) + sizeof(luptr->IpAddrStr)-1; for (cptr = addr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (luptr->Ip4Address) { luptr->HostAddressDsc.dsc$w_length = sizeof(luptr->Ip4Address); luptr->HostAddressDsc.dsc$a_pointer = (char*)&luptr->Ip4Address; } else { luptr->HostAddressDsc.dsc$w_length = sizeof(luptr->Ip6Address); luptr->HostAddressDsc.dsc$a_pointer = (char*)&luptr->Ip6Address; } } else { /**************/ /* lookup AST */ /**************/ status = luptr->LookupIOsb.iosb$w_status; if (dbug) fprintf (stdout, "sys$qio() %%X%08.08X\n", status); if (VMSok (status)) { luptr->HostName[luptr->HostNameLength] = '\0'; if (dbug) fprintf (stdout, "|%s|\n", luptr->HostName); strcpy (luptr->NamePtr, luptr->HostName); astf = luptr->AstFunction; astp = luptr->AstParam; free (luptr); status = sys$dclast (astf, astp, 0); if (VMSnok (status)) EXIT_FI_LI (status); return; } if (status == SS$_ENDOFFILE || !luptr->RetryCount--) { astf = luptr->AstFunction; astp = luptr->AstParam; free (luptr); status = sys$dclast (astf, astp, 0); if (VMSnok (status)) EXIT_FI_LI (status); return; } luptr->RetryCount--; } status = sys$qio (0, LookupChannel, IO$_ACPCONTROL | IO$M_EXTEND, &luptr->LookupIOsb, LookupHostName, luptr, &ControlSubFunctionDsc, &luptr->HostAddressDsc, &luptr->HostNameLength, &luptr->HostNameDsc, 0, 0); if (dbug) fprintf (stdout, "sys$qio() %%X%08.08X\n", status); if (VMSnok (status)) EXIT_FI_LI (status); } /*****************************************************************************/ /* Generate an access port name from the remote host and port. The presence of a non-empty ->AccPorNam string indicates that this should be undone during terminal/client delete (otherwise non-paged-pool will surely leak). */ #if ACCPORNAM_BUILD void SetClientAccPorNam (struct PtdClient *clptr) { int status; char *cptr, *sptr, *zptr; if (!clptr->RemoteAddr[0]) { if (!(cptr = WsLibCgiVarNull("REMOTE_ADDR"))) cptr = "?"; zptr = (sptr = clptr->RemoteAddr) + sizeof(clptr->RemoteAddr)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* non-empty acts as a flag indicating a lookup is in progress */ if (!(cptr = WsLibCgiVarNull("REMOTE_PORT"))) cptr = "?"; zptr = (sptr = clptr->RemotePort) + sizeof(clptr->RemotePort)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (!(cptr = WsLibCgiVarNull("REMOTE_HOST"))) cptr = "?"; if (!strcasecmp (cptr, clptr->RemoteAddr)) { /* the host and address are the same so WASD lookup is not enabled */ LookupHostName (clptr, SetClientAccPorNam, cptr, clptr->RemoteHost); return; } /* otherwise just use the already looked up name */ zptr = (sptr = clptr->RemoteHost) + sizeof(clptr->RemoteHost)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } if (!clptr->RemotePort[0]) { /* empty indicates the process has terminated during lookup */ PtdTerminateFree (clptr); return; } if (!clptr->RemoteHost[0]) strcpy (clptr->RemoteHost, clptr->RemoteAddr); zptr = (sptr = clptr->AccPorNam) + sizeof(clptr->AccPorNam)-1; sptr++; for (cptr = AccPorNamPrefix; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = clptr->RemoteHost; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = clptr->RemotePort; *cptr && sptr < zptr; *sptr++ = *cptr++); clptr->AccPorNam[0] = sptr - (clptr->AccPorNam + 1); status = SetAccPorNam (clptr->ptdchan, clptr->AccPorNam); if (clptr->WatchScript) ConsoleCallout (clptr, FI_LI, "SetAccPortNam() %d %d|%s| %%X%08.08X", clptr->ptdchan, clptr->AccPorNam[0], clptr->AccPorNam+1, status); if (VMSnok(status)) memset (clptr->AccPorNam, 0, sizeof(clptr->AccPorNam)); /* reset the flag indicating a lookup is in progress */ clptr->RemotePort[0] = '\0'; PtdBegin (clptr); } #endif /*****************************************************************************/ /* Call the kernel-mode function to set an otherwise empty terminal ACCPORNAM into a DCLinabox ACCPORNAM. */ #if ACCPORNAM_BUILD int SetAccPorNam (ushort channel, char cstring[]) { static unsigned long CmKrnlMask [2] = { PRV$M_CMKRNL, 0 }; __int32 nppsize; int status, kstatus; int arglist [4] = { 3, channel, (int)cstring, (int)&nppsize }; status = sys$setprv (1, &CmKrnlMask, 0, 0); /* if not installed with CMKRNL then might as well quit here */ if (status == SS$_NOTALLPRIV) return (status); AccPorNamCount++; status = lib$lock_image (0); if (VMSnok(status)) EXIT_FI_LI (status); kstatus = sys$cmkrnl (&KrnlSetAccPorNam, &arglist); sys$setprv (0, &CmKrnlMask, 0, 0); if (VMSnok(kstatus)) if (kstatus != SS$_NOPRIV) EXIT_FI_LI (kstatus); status = lib$unlock_image (0); if (VMSnok(status)) EXIT_FI_LI (status); if (VMSok(kstatus)) if (cstring[0]) AccPorNamSetCount++; #if !(ACCPORNAM_LEAK) else AccPorNamResetCount++; #endif #if OPCOM_DBUG if (cstring[0]) OpcomMessage ("S/SetAccPorNam() nppsize:!UL", nppsize); #endif return (kstatus); } #endif /*****************************************************************************/ /* Basic access into the UCB came from Jim Duff's code http://www.eight-cubed.com/blog/archives/001289.html Also useful for some general principals are Writing VMS Privileged Code by Hunter Goatley and Edward A. Heinrich, originally articles published in VAX Professional during 1993 and 1994. https://hunter.goatley.com/writing-vms-privileged-code/ And again, Hunter Goatley, longtime VMS enthusiast and software engineer, specifically for his suggestions and assistance with aspects of privileged mode programming. Any over-arching clunkiness belongs entirely to yours truly. The function will only set the terminal ACCPORNAM if ->ucb$l_tt_accpornam is currently not set. Returns SS$_FISH if already set. If cstring is empty then undo all the good work. */ #if ACCPORNAM_BUILD #define ACCPORNAM_SIZE 65 #include #include #include #include #include #include #include #include #include #include int KrnlSetAccPorNam (int channel, char cstring[], __int32* size_p) { extern PCB *CTL$GL_PCB; static __int32 cnt, retval, status; static char *npp_p; static MUTEX *mutex; static PCB *pcb_p; static CCB *ccb_p; static TTY_UCB *ucb_p; if (channel == 0 || cstring == NULL || cstring[0] < 0 || cstring[0] > 64) return (SS$_BADPARAM); retval = SS$_NORMAL; /* get the address of the process control block */ pcb_p = CTL$GL_PCB; /* lock I/O database mutex for write */ mutex = sch_std$iolockw (pcb_p); /* get the channel control block */ status = ioc$chan_to_ccb (channel, &ccb_p); if (status & 1) { /* valid channel so set the unit control block */ ucb_p = (TTY_UCB*)ccb_p->ccb$l_ucb; if (!cstring[0]) { /* reset the ->ucb$l_tt_accpornam */ if (!(npp_p = ucb_p->ucb$l_tt_accpornam)) retval = SS$_FISH; else { #if !(ACCPORNAM_LEAK) /* first check this has been set by DCLinabox */ for (cnt = 0; AccPorNamPrefix[cnt]; cnt++) if (npp_p[cnt+1] != AccPorNamPrefix[cnt]) break; if (!AccPorNamPrefix[cnt]) { /* the datum is no longer in use (?) */ ucb_p->ucb$w_tt_prtctl &= ~TTY$M_PC_ACCPORNAM; retval = exe_std$deanonpgdsiz (npp_p, ACCPORNAM_SIZE); if (retval & 1) ucb_p->ucb$l_tt_accpornam = 0; } else retval = SS$_FISH; #endif } *size_p = 0; } else /* if there already is an ACCPORNAM set then do not proceed */ if (ucb_p->ucb$l_tt_accpornam) retval = SS$_FISH; else { /* allocate non-paged memory */ status = exe_std$alononpaged (ACCPORNAM_SIZE, size_p, (void*)&npp_p); if (status & 1) { /* flag the datum is in use (?) */ ucb_p->ucb$w_tt_prtctl |= TTY$M_PC_ACCPORNAM; /* copy in the counted string */ for (cnt = 0; cnt < ACCPORNAM_SIZE; cnt++) npp_p[cnt] = cstring[cnt]; ucb_p->ucb$l_tt_accpornam = npp_p; } else retval = status; } } else retval = status; sch_std$iounlock (pcb_p); return (retval); } #endif /*****************************************************************************/