/*****************************************************************************/ /* INTRUspect.c INTRUspect (pronounced "in-truh-spect") allows a suitably authenticated client to inspect the current intrusion database as tabled data. INTRUspect communicates via WebSocket (or XMLHttpRequest) and provides/updates data to client JavaScript using JSON. The browser application is JavaScript driven and found in the files of the [.runtime] directory. The runtime aspects, both for the back-end executable and the client JavaScript front-end, is provided via a text file located using INTRUSPECT_CONFIG logical name. This is designed to contain JavaScript and is incorporated into the appropriate INTRUspect HTML page to provide site-specific configuration. Only two configuration parameters apply to the executable and must be provided in the specific format configRecordsMax(); configUpdateSecs(); so the configuation approach uniform and complies with JavaScript requirements. See READMORE.HTML for an explanation of application configuration. BUILD DETAILS ------------- See BUILD_INTRUSPECT.COM procedure. $ @BUILD_INTRUSPECT [LINK] INSTALLED IMAGE --------------- Security privilege is required to access the intrusion database. $ INSTALL ADD INTRUSPECT.EXE /PRIV=(SECURITY,SYSPRV) INSTALLATION ------------ Copy the executable to the CGI_EXE: directory. Map the JavaScript resources into web-space using (assumes the application is located in the source tree): # HTTPD$MAP pass /intruspect/-/* /wasd_root/src/intruspect/runtime/* Script MUST be subject to authorisation or INTRUspect aborts. For WASD an authorisation rule such as the following might be used. # WASD_CONFIG_AUTH ["System Admin"=SYSADMIN=ID] /cgi*-bin/intruspect* r+w,https: COPYRIGHT --------- Copyright (C) 2017-2025 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 (update SOFTWAREVN as well!) --------------- 28-FEB-2025 MGD v2.0.0, integrate geolocation 64 bit native data (eliminate VAX dependencies) 25-FEB-2017 MGD v1.0.1, bugfix; JsonIntruData() CIA$M_USERNAME parse "[ on ]" 13-FEB-2017 MGD v1.0.0, initial 08-APR-2015 MGD pre-history */ /*****************************************************************************/ #define SOFTWAREVN "2.0.0" /* ^^^^^ don't neglect [.RUNTIME]INTRUSPECT.JS compliance! */ #define SOFTWARENM "INTRUSPECT" #define SOFTWARECR "Copyright (C) 2017-2025 Mark G.Daniel" #ifdef __ALPHA char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN; #endif #ifdef __ia64 char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN; #endif #ifdef __x86_64 char SoftwareID [] = SOFTWARENM " X86-" SOFTWAREVN; #endif /* standard C header files */ #include #include #include #include #include #include #include #include #include #include #include /* VMS-related header files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wslib.h" #pragma __nomember_alignment /* for development enable cluster processing on non-clustered system */ #define FAUX_CLUSTER 0 #define BOOL int #define TRUE 1 #define FALSE 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define FI_LI "INTRUSPECT", __LINE__ /* generate "not a strict CGI response" report */ #define EXIT_FI_LI(status) \ { printf ("BOOM!! [%s:%d] %%X%08.08X\r\n", FI_LI, status); exit(status); } #define UPDATE_SECS_DEFAULT 2 /* seconds */ #define UPDATE_SECS_MAX 60 /* seconds */ #define UPDATE_SECS_MIN 2 /* seconds */ #define UPDATE_ALERT 5 /* five times longer than should be */ #define INTRU_RECORDS_MAX 10000 #define INTRU_RECORDS_MIN 10 #define INTRU_RECORDS_DEFAULT 250 /* exit after fifteen minutes without a WebSocket connection */ #define EXIT_AFTER_SECONDS 15 * 60; /* puts three successive quotations into the stream - impossible JSON data */ #define JSON_SENTINAL "\"\"\"" /* mainly to allow easy use of the __unaligned directive */ #define UINTPTR __unaligned unsigned int* #define ULONGPTR __unaligned unsigned long* #define USHORTPTR __unaligned unsigned short* #define INT64PTR __unaligned __int64* #define MATCH2(ptr1,ptr2) (*(USHORTPTR)(ptr1) == *(USHORTPTR)(ptr2)) #define MATCH6(ptr1,ptr2) ((*(INT64PTR)(ptr1) & 0x0000ffffffffffff) == \ (*(INT64PTR)(ptr2) & 0x0000ffffffffffff)) #define MATCH8(ptr1,ptr2) (*(INT64PTR)(ptr1) == *(INT64PTR)(ptr2)) #ifndef INTRUSPECT_GEOLOCATE #define INTRUSPECT_GEOLOCATE #endif #ifdef INTRUSPECT_GEOLOCATE #include "geolocate.h" int Debug; #endif #define LOGNAM_CONFIG "INTRUSPECT_CONFIG" #define LOGNAM_GEOLOCATE "INTRUSPECT_GEOLOCATE" #define LOGNAM_INSIGHT "INTRUSPECT_INSIGHT" #define INTRU_BUFFER_CHUNK 4096 /* bytes */ #define MAX_NODE_LIST 100 /* maximum cluster nodes */ /*******************/ /* data structures */ /*******************/ typedef struct STRUCT_SYSTEM_DATA { int ClusterMember, IntruBufferLength, IntruBufferSize, RuntimeBufferLength, StaticBufferLength, UsageCount; char ArchName [15+1], HwName [60+1], NodeName [15+1], VmsVersion [8+1]; ulong ActiveCpuCount, AvailCpuCount, MemSize, PageSize; char *DebugInfoPtr, *IntruBufferPtr; char RuntimeBuffer [1024], StaticBuffer [1024]; } SYSTEM_DATA; /* used for multiple per-WebSocket client and CGI client (for convenience) */ typedef struct STRUCT_CLIENT_DATA { BOOL Initialised; int OutputBufferLength, OutputBufferSize; char *OutputBufferPtr; char InputBuffer [256]; struct WsLibStruct *WsLibPtr; } CLIENT_DATA; /******************/ /* global storage */ /******************/ BOOL CliDebug, DataViaCGI, Debug, HttpdApache, HttpdOSU, HttpdWASD; int ConnectedCount, IsCgiPlus, IntruTrackCount, IntruRecordsMax = INTRU_RECORDS_DEFAULT, UpdateSeconds = UPDATE_SECS_DEFAULT, UsageCount; uint ProvideDataSeconds, ExitTimeStamp, InitTimeStamp, SpiEfn, TimeStamp; int64 CollectTime64, CurrentTime64, StartTime64, ProvideTime64; ushort CurrentTime7 [7]; float DeltaSeconds = 999999.0; char *CgiHttpRefererPtr, *CgiHttpUserAgentPtr, *CgiRemoteAddrPtr, *CgiRemoteUserPtr, *CgiRequestUriPtr, *CgiServerSoftwarePtr, *CgiQueryStringPtr, *ConfigJavaScriptPtr, *GeoLocatePtr; char HttpdVersion [32], RefererHost [128]; CLIENT_DATA CgiClientData; SYSTEM_DATA SystemData; ulong SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; ulong NeedPrivMask [2] = { PRV$M_SYSPRV, (PRV$M_SECURITY >> 32) }; /**************/ /* prototypes */ /**************/ void AccessControl (CLIENT_DATA*); int sys$setprv (__unknown_params); void JsonAlert (CLIENT_DATA*, char*, ...); void JsonIntruData (); void JsonRuntime (); void JsonServerSoftware (); void JsonStaticData (); char* LookupHostName (ulong); char* LoadFile (char*); void ProvideData (); char* EscapeUtf8 (char*); char* IPv4Parse (char*); char* MungeTime (int64 *btptr, ...); void SetDebug (int); void AddClient (); int HavePriv (ulong*); char* IdentifyNetwork (char*); int MinimumWASD (char*); void ReadClient (struct WsLibStruct*); void RemoveClient (struct WsLibStruct*); char* SysTrnLnm (char*, char*, int); int WebSockAvailable (); void XhrResponse (); /*****************************************************************************/ /* */ int main (int argc, char *argv[]) { int status, NodesAcross, ViewWidth; char *cptr, *sptr, *zptr, *c1ptr, *c2ptr, *c3ptr; char NodeList [512]; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (argc > 1) { if (!strcasecmp (argv[1], "/VERSION")) { fprintf (stdout, "%%INTRUSPECT-I-VERSION, %s %s\n", SoftwareID, WsLibVersion()); exit (SS$_NORMAL); } if (!strcasecmp (argv[1], "/DBUG")) CliDebug = TRUE; } sys$gettim (&StartTime64); if (VMSnok (status = lib$get_ef (&SpiEfn))) EXIT_FI_LI (status); if (!HavePriv (NeedPrivMask)) EXIT_FI_LI (SS$_NOPRIV); HttpdApache = (getenv ("APACHE$SHARED_SOCKET") != NULL || getenv ("APACHE$PARENT_PID") != NULL || getenv ("APACHE$SERVER_TAG") != NULL); HttpdOSU = (getenv("WWWEXEC_RUNDOWN_STRING") != NULL); HttpdWASD = (getenv("HTTP$INPUT") != NULL); if (cptr = SysTrnLnm (LOGNAM_CONFIG, NULL, 0)) { sys$setprv (1, &SysPrvMask, 0, 0); ConfigJavaScriptPtr = sptr = LoadFile (cptr); sys$setprv (0, &SysPrvMask, 0, 0); if (MATCH2 (sptr, "%X")) { fprintf (stdout, "Status: 500\r\n\r\nLoad %s %s\n", cptr, sptr); exit (SS$_NORMAL); } if (cptr = strstr (sptr, "configRecordsMax(")) { IntruRecordsMax = atoi(cptr+17); if (IntruRecordsMax < INTRU_RECORDS_MIN || IntruRecordsMax > INTRU_RECORDS_MAX) IntruRecordsMax = INTRU_RECORDS_DEFAULT; } if (cptr = strstr (sptr, "configUpdateSecs(")) { UpdateSeconds = atoi(cptr+18); if (UpdateSeconds < UPDATE_SECS_MIN || UpdateSeconds > UPDATE_SECS_MAX) UpdateSeconds = UPDATE_SECS_DEFAULT; } } if (cptr = SysTrnLnm (LOGNAM_GEOLOCATE, NULL, 0)) { strcpy (GeoLocatePtr = malloc (strlen(cptr)+1), cptr); /* convert previous JavaScript URL to default GEOLOCATE.C */ if (strstr (GeoLocatePtr, ".js")) GeoLocatePtr = "*"; } SetDebug (1); IsCgiPlus = WsLibIsCgiPlus (); sys$gettim (&CurrentTime64); InitTimeStamp = decc$fix_time (&CurrentTime64); memset (&SystemData, 0, sizeof(SystemData)); CollectSyiStatic (); if (Debug = CliDebug) { DataViaCGI = TRUE; ProvideData (); for(;;) sys$hiber(); } JsonStaticData (); ProvideData (); for (;;) { /* with CGIplus this call will block waiting for the next request */ WsLibCgiVar (""); SetDebug (1); /********************/ /* request received */ /********************/ UsageCount++; CgiHttpRefererPtr = WsLibCgiVar("HTTP_REFERER"); CgiHttpUserAgentPtr = WsLibCgiVar("HTTP_USER_AGENT"); CgiQueryStringPtr = WsLibCgiVar("QUERY_STRING"); CgiRemoteAddrPtr = WsLibCgiVar("REMOTE_ADDR"); CgiRemoteUserPtr = WsLibCgiVar("REMOTE_USER"); CgiRequestUriPtr = WsLibCgiVar("REQUEST_URI"); if (!CgiServerSoftwarePtr) JsonServerSoftware (); /* derive the URL scheme and host from the HTTP referrer field */ zptr = (sptr = RefererHost) + sizeof(RefererHost)-1; cptr = CgiHttpRefererPtr; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (!CgiRemoteUserPtr[0]) fprintf (stdout, "Status: 500\r\n\r\nUnauthorised!\n"); else if (!strncmp (CgiQueryStringPtr, "data=1", 6)) { /********/ /* data */ /********/ if (DataViaCGI = (WsLibCgiVarNull ("WEBSOCKET_INPUT") == NULL)) { XhrResponse (); if (SystemData.StaticBufferLength) { fputs (SystemData.StaticBuffer, stdout); fputs (JSON_SENTINAL, stdout); if (Debug) fputs ("\n\n", stdout); } /* ProvideData() now drives it and should never ... */ for(;;) sys$hiber(); } if (!WebSockAvailable()) { fprintf (stdout, "Status: 500\r\n\r\nWebSocket not available!\n"); exit (SS$_NORMAL); } if (!IsCgiPlus) { fprintf (stdout, "Status: 500\r\n\r\nMust be CGIplus!\n"); exit (SS$_NORMAL); } AddClient (); } else { /********/ /* page */ /********/ if (WebSockAvailable()) { if (!IsCgiPlus) { if (!strncasecmp (cptr = CgiRequestUriPtr, "/cgi-bin/", 9)) { /* 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); } else { /* not the "standard" path */ fprintf (stdout, "Status: 500\r\n\r\nMust be CGIplus!\n"); } exit (SS$_NORMAL); } } if (MATCH6 (CgiQueryStringPtr, "frame=")) { c1ptr = "function configINTRUspect() {\ntry {\n"; if (!(c2ptr = ConfigJavaScriptPtr)) c2ptr = "// no config\n"; c3ptr = "} catch (err) { alert(err); }\n}\n"; } else c1ptr = c2ptr = c3ptr = ""; fprintf (stdout, "Content-Type: text/html; charset=utf-8\r\n\ \r\n\ \n\ \n\ \n\ INTRUspect\n\ \n\ \n\ \n\ \n\ \n\ INTRUspect v%s \n\ \n\ \n", SOFTWARECR, SOFTWAREVN, WsLibCgiVar("SCRIPT_NAME"), WebSockAvailable() ? "true" : "false", c1ptr, c2ptr, c3ptr, SOFTWAREVN); } SetDebug (0); if (!IsCgiPlus) break; memset (&CgiClientData, 0, sizeof(CgiClientData)); WsLibCgiPlusEof (); } exit (SS$_NORMAL); } /*****************************************************************************/ /* Allocate a client structure and add it to the head of the list. Establish the WebSocket IPC and begin processing. */ void AddClient () { int status; char *sptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (!(clptr = calloc (1, sizeof(CLIENT_DATA)))) EXIT_FI_LI (vaxc$errno); /* create a WebSocket library structure for the client */ if (!(clptr->WsLibPtr = WsLibCreate (clptr, RemoveClient))) { /* failed, commonly on some WebSocket protocol issue */ return; } /* open the IPC to the WebSocket (mailboxes) */ status = WsLibOpen (clptr->WsLibPtr); if (VMSnok (status)) EXIT_FI_LI (status); WsLibWatchScript (clptr->WsLibPtr, FI_LI, "!AZ", SoftwareID); /* provide the static system data */ WsLibWrite (clptr->WsLibPtr, SystemData.StaticBuffer, SystemData.StaticBufferLength, WSLIB_ASYNCH); clptr->Initialised = TRUE; /* queue an asynchronous read from the client */ WsLibRead (clptr->WsLibPtr, clptr->InputBuffer, sizeof(clptr->InputBuffer)-1, ReadClient); ConnectedCount++; } /*****************************************************************************/ /* Remove the client structure from the list and free the memory. */ void RemoveClient (struct WsLibStruct *wsptr) { CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ clptr = WsLibGetUserData(wsptr); if (ConnectedCount) ConnectedCount--; } /*****************************************************************************/ /* Read WebSocket write from the browser. */ void ReadClient (struct WsLibStruct *wsptr) { char *cptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReadClient() %%X%08.08X\n", WsLibReadStatus (wsptr)); if (VMSnok (WsLibReadStatus (wsptr))) { WsLibClose (wsptr, 0, NULL); return; } clptr = WsLibGetUserData (wsptr); /* ensure it's null-terminated */ clptr->InputBuffer[WsLibReadCount(wsptr)] = '\0'; /* read next */ WsLibRead (wsptr, clptr->InputBuffer, sizeof(clptr->InputBuffer)-1, ReadClient); } /*****************************************************************************/ /* For an XMLHttpRequest() HTTP request. The response body will stream chunks of JSON data to be parsed by the browser JavaScript receiving it. Do the best we can to disable proxy caching ensuring a timely flow of response data. */ void XhrResponse () { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "XhrResponse()\n"); fprintf (stdout, "Status: 200\n\ Content-Type: application/json\n\ Script-Control: X-stream-mode=1\n\ Script-Control: X-content-encoding-gzip=0\n\ Pragma: no-cache\n\ Cache-Control: no-cache, no-store, private\n\ \n"); fflush (stdout); CgiClientData.Initialised = TRUE; ConnectedCount++; } /*****************************************************************************/ /* Send a JSON data structure containing a message to be displayed as an alert. */ void JsonAlert ( CLIENT_DATA *ClientPtr, char *FormatString, ... ) { static char CurTime [24]; static $DESCRIPTOR (CurTimeDsc, CurTime); static $DESCRIPTOR (CurTimeFaoDsc, "!20%D\0"); int len; char jsonbuf [512], msgbuf [256]; va_list argptr; CLIENT_DATA *clptr; struct WsLibStruct *wsctx, *wsptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "JsonAlert()\n"); va_start (argptr, FormatString); len = vsnprintf (msgbuf, sizeof(msgbuf), FormatString, argptr); if (len >= sizeof(msgbuf)) EXIT_FI_LI (SS$_RESULTOVF); sys$fao (&CurTimeFaoDsc, 0, &CurTimeDsc, &CurrentTime64); if (CurTime[0] == ' ') CurTime[0] = '0'; len = snprintf (jsonbuf, sizeof(jsonbuf), "{\"$data\":\"alert\",\"time\":\"%s\",\"message\":\"%s\"}", CurTime, msgbuf); if (len >= sizeof(jsonbuf)) EXIT_FI_LI (SS$_RESULTOVF); if (DataViaCGI) { if (CgiClientData.Initialised) { fputs (jsonbuf, stdout); fputs (JSON_SENTINAL, stdout); fflush (stdout); } } wsctx = NULL; while (wsptr = WsLibNext(&wsctx)) { clptr = WsLibGetUserData (wsptr); if (!clptr->Initialised) continue; if (ClientPtr && clptr != ClientPtr) continue; /* set and forget; buffer each message internally */ WsLibSetBuffer (clptr->WsLibPtr); WsLibWrite (wsptr, jsonbuf, len, WSLIB_ASYNCH); /* revert to requiring an external, persistent buffer */ WsLibSetNoBuffer (clptr->WsLibPtr); } } /*****************************************************************************/ /* Indepdendent, timer AST driven update of the intrusion database. This data is then written to connected clients. */ void ProvideData () { #define DELTA64_ONE_MSEC ((int64)-10000) #define OUTBUF_CHUNK 2048 /* bytes */ static const long cvtf_mode = LIB$K_DELTA_SECONDS_F; static ulong PrevTimeStamp; static int64 PrevTime64, SecondsDelta; int status; ulong fltf; /* generic 32 bits for the VAX float */ int64 DiffTime64; char *aptr, *cptr, *sptr, *zptr; struct WsLibStruct *wsctx, *wsptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "\nProvideData()\n"); sys$gettim (&CurrentTime64); sys$numtim (&CurrentTime7, &CurrentTime64); TimeStamp = decc$fix_time (&CurrentTime64); if (ExitTimeStamp && TimeStamp > ExitTimeStamp) exit (SS$_NORMAL); if (ConnectedCount) ExitTimeStamp = TimeStamp + EXIT_AFTER_SECONDS; /* alert if the update period has been overly long */ if (TimeStamp > PrevTimeStamp + (UpdateSeconds * UPDATE_ALERT)) if (PrevTimeStamp) JsonAlert (NULL, "!Refresh lasted %d seconds!", TimeStamp - PrevTimeStamp); PrevTimeStamp = TimeStamp; /* calculate a floating point delta time */ if (!PrevTime64) PrevTime64 = CurrentTime64; DiffTime64 = PrevTime64 - CurrentTime64; if (DiffTime64 >= 0) DiffTime64 = DELTA64_ONE_MSEC; PrevTime64 = CurrentTime64; #ifdef __ALPHA /* Alpha 7.3-1 does not have lib$cvts_..() */ status = lib$cvtf_from_internal_time (&cvtf_mode, &fltf, &DiffTime64); /* convert VAX to IEEE float */ if (VMSok (status)) status = cvt$convert_float (&fltf, CVT$K_VAX_F, &DeltaSeconds, CVT$K_IEEE_S, CVT$M_ROUND_TO_NEAREST); #else status = lib$cvts_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffTime64); #endif if (VMSnok (status)) EXIT_FI_LI (status); if (Debug) fprintf (stdout, "DeltaSeconds: %f\n", DeltaSeconds); if (DataViaCGI) clptr = &CgiClientData; JsonIntruData (); sys$gettim (&CollectTime64); if (DataViaCGI) { /*******/ /* CGI */ /*******/ if (SystemData.IntruBufferLength) { fputs (SystemData.IntruBufferPtr, stdout); fputs (JSON_SENTINAL, stdout); if (Debug) fputs ("\n\n", stdout); } if (SystemData.RuntimeBufferLength) { fputs (SystemData.RuntimeBuffer, stdout); fputs (JSON_SENTINAL, stdout); } if (SysTrnLnm (LOGNAM_INSIGHT, NULL, 0)) { if (cptr = GeoLocateResponse (NULL, NULL, NULL)) { fputs (cptr, stdout); fputs (JSON_SENTINAL, stdout); free (cptr); } } fflush (stdout); } wsctx = NULL; while (wsptr = WsLibNext(&wsctx)) { /*************/ /* WebSocket */ /*************/ clptr = WsLibGetUserData (wsptr); if (!clptr->OutputBufferSize) { /* initial allocation */ clptr->OutputBufferSize = OUTBUF_CHUNK; clptr->OutputBufferPtr = calloc (1, clptr->OutputBufferSize); if (!clptr->OutputBufferPtr) EXIT_FI_LI (vaxc$errno); } for (;;) { zptr = (sptr = clptr->OutputBufferPtr) + clptr->OutputBufferSize-1; if (SystemData.IntruBufferLength) { for (cptr = SystemData.IntruBufferPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (SystemData.RuntimeBufferLength) { for (cptr = SystemData.RuntimeBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (SysTrnLnm (LOGNAM_INSIGHT, NULL, 0)) { if (aptr = GeoLocateResponse (NULL, NULL, NULL)) { for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); free (aptr); } } /* unless we fell short of buffer space leave this loop */ if (sptr < zptr) break; /* need a bigger buffer and then just redo the copies */ clptr->OutputBufferSize += OUTBUF_CHUNK; free (clptr->OutputBufferPtr); clptr->OutputBufferPtr = calloc (1, clptr->OutputBufferSize); if (!clptr->OutputBufferPtr) EXIT_FI_LI (vaxc$errno); } WsLibWrite (wsptr, clptr->OutputBufferPtr, sptr - clptr->OutputBufferPtr, WSLIB_ASYNCH); } /*************/ /* and again */ /*************/ /* just multiply by interval (one to sixty seconds) */ if (!SecondsDelta) { SecondsDelta = UpdateSeconds; SecondsDelta *= -10000000; } status = sys$setimr (0, &SecondsDelta, ProvideData, NULL, 0); if (VMSnok(status)) EXIT_FI_LI (status); ProvideDataSeconds += UpdateSeconds; sys$gettim (&ProvideTime64); JsonRuntime (); } /*****************************************************************************/ /* Provide some runtime data. Average duration of each data collection and total monitor cycle (to differentiate the time required to write the data to the client(s)), average CPU consumed, average user and non-user CPU modes. All in seconds. */ void JsonRuntime () { static int64 CollectTotalTime, ProvideTotalTime; static long CpuSekInit, CpuTimInit, CpuUsrInit, JpiCpuTim, JpiExecTim, JpiKrnlTim, JpiSuprTim, JpiUserTim, StatCount; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemListJpi[] = { { sizeof(JpiCpuTim), JPI$_CPUTIM, &JpiCpuTim, 0 }, { sizeof(JpiExecTim), JPI$_EXECTIM, &JpiExecTim, 0 }, { sizeof(JpiKrnlTim), JPI$_KRNLTIM, &JpiKrnlTim, 0 }, { sizeof(JpiSuprTim), JPI$_SUPRTIM, &JpiSuprTim, 0 }, { sizeof(JpiUserTim), JPI$_USERTIM, &JpiUserTim, 0 }, {0,0,0,0} }; int status; ulong CpuUSR, CpuSEK, CpuTIM; int64 RunTime64, DiffTime64; char *sptr, *zptr; float fcount; struct _iosb IOsb; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "\nJsonRuntime()\n"); status = sys$getjpiw ( SpiEfn, /* efn */ 0, /* pidaddr */ 0, /* prcnam */ &ItemListJpi, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (status); if ((fcount = (float)(StatCount++)) == 0.0) { /* track and offset initial setup overheads */ CpuTimInit = JpiCpuTim; CpuUsrInit = JpiUserTim; CpuSekInit = JpiSuprTim + JpiExecTim + JpiKrnlTim; return; } CollectTime64 -= CurrentTime64; CollectTime64 += CurrentTime64; ProvideTime64 -= CurrentTime64; ProvideTime64 += ProvideTotalTime; RunTime64 = StartTime64 - CurrentTime64; CpuTIM = JpiCpuTim - CpuTimInit; CpuUSR = JpiUserTim - CpuUsrInit; CpuSEK = JpiSuprTim + JpiExecTim + JpiKrnlTim - CpuSekInit; zptr = (sptr = SystemData.RuntimeBuffer) + sizeof(SystemData.RuntimeBuffer)-1; sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"runtime\",\ \"cpu\":%f,\ \"cpuTIM\":%f,\ \"cpuUSR\":%f,\ \"cpuSEK\":%f,\ \"run\":\"%s\",\ \"collect\":%f,\ \"provide\":%f,\ \"refresh\":%d", (float)JpiCpuTim / 100.0, (float)CpuTIM / 100.0 / fcount, (float)CpuUSR / 100.0 / fcount, (float)CpuSEK / 100.0 / fcount, MungeTime(&RunTime64), (float)CollectTotalTime / 10000000.0 / fcount, (float)ProvideTotalTime / 10000000.0 / fcount, UpdateSeconds); if (HttpdWASD) { sptr += snprintf (sptr, zptr-sptr, ",\"usage\":%d,\ \"connected\":%d,\ \"CGIplus\":%s}", UsageCount, ConnectedCount, IsCgiPlus ? "true" : "false"); } else sptr += snprintf (sptr, zptr-sptr, "}"); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.RuntimeBufferLength = sptr - SystemData.RuntimeBuffer; } /*****************************************************************************/ /* Scan the intrusion database generating the JSON data. */ void JsonIntruData () { static BOOL AlertExcessRecords = TRUE; static int CallCount = 0; static ulong IntruFlags = CIA$M_SUSPECTS | CIA$M_INTRUDERS; static ulong ClusterIntruFlags = CIA$M_SUSPECTS | CIA$M_INTRUDERS | CIA$M_ITEMLIST | CIA$M_SHOW_NODE; static ulong RecordBufferLen, OutputListLen, TotalRecordCount; static char RecordBuffer [1058+4+2]; static $DESCRIPTOR (IntruAllDsc, "*"); static $DESCRIPTOR (RecordBufferDsc, RecordBuffer); static struct { char node [8]; ulong count; } OutputList[MAX_NODE_LIST]; /* initialize structure for CIA items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } CiaItemList[] = { { sizeof(IntruAllDsc), CIA$_USER_CRITERIAL, &IntruAllDsc, 0 }, { sizeof(OutputList), CIA$_OUTPUT_LIST, &OutputList, &OutputListLen }, {0,0,0,0} }; static struct BreakinBlockStruct { ulong type, count; int64 time; } BreakinBlock; BOOL WasNegTim; int cnt, idx, status, BreakinClass, BreakinCount, BreakinType, ConcurrentCount = 0, ExcessCount = 0, IntruCtx = 0, RecordCount = 0, ScsNodeCount = 0, LastIdx; ulong ExpiresTimeStamp; int64 ExpiresTime64, UpTime64; char *aptr, *cptr, *geoptr, *lenptr, *sptr, *zptr, *ClassPtr, *HostNamePtr, *ScsNodePtr, *TypePtr; char IdentBuffer [128], IpHostBuffer [128], IpAddrBuffer [128], NetworkBuffer [256], SourceBuffer [128]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "JsonIntruData()\n"); CallCount++; if (!SystemData.IntruBufferSize) { SystemData.IntruBufferSize = INTRU_BUFFER_CHUNK; SystemData.IntruBufferPtr = calloc (1, SystemData.IntruBufferSize); if (!SystemData.IntruBufferPtr) EXIT_FI_LI (vaxc$errno); } zptr = (sptr = SystemData.IntruBufferPtr) + SystemData.IntruBufferSize; sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"intrusion\""); SystemData.IntruBufferLength = sptr - SystemData.IntruBufferPtr; for (;;) { /*************************/ /* read intrusion record */ /*************************/ /* in a cluster each node will have a list entry */ if (!ScsNodeCount) { OutputListLen = 0; if (SystemData.ClusterMember) { memset (&OutputList, 0, sizeof(OutputList)); status = sys$show_intrusion (&CiaItemList, &RecordBufferDsc, &RecordBufferLen, &BreakinBlock, ClusterIntruFlags, &IntruCtx); } else status = sys$show_intrusion (&IntruAllDsc, &RecordBufferDsc, &RecordBufferLen, &BreakinBlock, IntruFlags, &IntruCtx); if (Debug) fprintf (stdout, "sys$show_intrusion() %%X%08.08X\n", status); if (status == SS$_NOMOREITEMS || status == SECSRV$_CIADBEMPTY || status == SECSRV$_NOSUCHINTRUDER) break; if (VMSnok (status)) EXIT_FI_LI (status); if (RecordCount + ExcessCount >= IntruRecordsMax) { /* more than we're prepared to deal with - just count 'em up */ ExcessCount++; continue; } RecordBuffer[RecordBufferLen] = '\0'; if (Debug) fprintf (stdout, "type: 0x%08.08X (0x%02X) %d |%s|\n", BreakinBlock.type, (BreakinBlock.type & 0xfc), OutputListLen, RecordBuffer); if (SystemData.ClusterMember) { /* 8 bytes for the SCS node name and 4 bytes for the count */ ScsNodeCount = OutputListLen / 12; if (!ScsNodeCount) EXIT_FI_LI (SS$_BUGCHECK); } else ScsNodeCount = 0; } /* populate the working data */ if (SystemData.ClusterMember) { /* in a cluster each node will have a list entry */ ScsNodePtr = OutputList[ScsNodeCount-1].node; BreakinCount = OutputList[ScsNodeCount-1].count; ScsNodeCount--; } else { ScsNodePtr = SystemData.NodeName; BreakinCount = BreakinBlock.count; } BreakinClass = BreakinBlock.type & 0x03; BreakinType = BreakinBlock.type & 0xfc; ExpiresTime64 = BreakinBlock.time; ExpiresTimeStamp = decc$fix_time (&ExpiresTime64); /****************************/ /* process intrusion record */ /****************************/ /* "[:]::"? */ /* "::"? e.g. TELNET */ /* "::::"? e.g. IMAP */ /* ":::"? e.g. MultiNet SSH */ /* "::" set host */ /* consistency? what a dog's breakfast! */ HostNamePtr = NULL; IpAddrBuffer[0] = IpHostBuffer[0] = IdentBuffer[0] = NetworkBuffer[0] = SourceBuffer[0] = '\0'; /* look backwards for ":" delimited string */ for (cptr = RecordBuffer; *cptr; *cptr++); while (*(cptr-1) != ':' && cptr > RecordBuffer) cptr--; /* terminate at the leading ":" and copy */ if (*(cptr-2) == ':' && cptr > RecordBuffer) *(cptr-2) = '\0'; else *(cptr-1) = '\0'; zptr = (sptr = IdentBuffer) + sizeof(IdentBuffer)-1; for (; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; for (cptr = RecordBuffer; *cptr && *cptr != '/'; *cptr++); if (*cptr) { /* e.g. "ssh/" */ zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; for (cptr = RecordBuffer; *cptr && *cptr != '/' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; cptr++; } else cptr = RecordBuffer; zptr = (sptr = NetworkBuffer) + sizeof(NetworkBuffer)-1; for (; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (*cptr) { if (*cptr == ':') cptr++; if (*cptr == ':') cptr++; zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } if (!(cptr = IdentifyNetwork (aptr = NetworkBuffer))) cptr = IdentifyNetwork (aptr = SourceBuffer); if (cptr) { /* back-to-back strings */ zptr = (sptr = IpAddrBuffer) + sizeof(IpAddrBuffer)-1; while (*cptr && *cptr != '|' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; cptr++; zptr = (sptr = IpHostBuffer) + sizeof(IpHostBuffer)-1; if (isalnum(*cptr)) while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (aptr == SourceBuffer) { /* swap network and source buffer */ char buf[sizeof(NetworkBuffer)]; strcpy (buf, NetworkBuffer); strcpy (NetworkBuffer, SourceBuffer); strcpy (SourceBuffer, buf); } } if (sptr = strstr (IdentBuffer, " on ")) { *sptr = '\0'; strcpy (NetworkBuffer, sptr + 4); } switch (BreakinClass) { case CIA$M_INTRUDER : TypePtr = "INTRU"; break; case CIA$M_SUSPECT : TypePtr = "SUSP"; break; default : TypePtr = "BUGCHECK"; } switch (BreakinType) { case CIA$M_NETWORK : { ClassPtr = "NETW"; break; } case CIA$M_TERMINAL : { /* " on "? */ ClassPtr = "TERM"; break; } case CIA$M_TERM_USER : { /* ": on "? */ ClassPtr = "TE_US"; break; } case CIA$M_USERNAME : { ClassPtr = "USER"; /* the */ if (!IdentBuffer[0]) strcpy (IdentBuffer, RecordBuffer); break; } default : { ClassPtr = "BUGCHECK"; zptr = (sptr = RecordBuffer) + sizeof(RecordBuffer)-1; for (cptr = RecordBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } } /* as portions may have been nulled restore any intervening ':' */ for (zptr = (sptr = RecordBuffer) + RecordBufferLen; sptr < zptr; sptr++) if (!*sptr) *sptr = ':'; /*********************/ /* memory management */ /*********************/ if (SystemData.IntruBufferSize - SystemData.IntruBufferLength < 1024) { SystemData.IntruBufferSize += INTRU_BUFFER_CHUNK; SystemData.IntruBufferPtr = realloc (SystemData.IntruBufferPtr, SystemData.IntruBufferSize); if (!SystemData.IntruBufferPtr) EXIT_FI_LI (vaxc$errno); } zptr = (sptr = SystemData.IntruBufferPtr) + SystemData.IntruBufferSize; sptr += SystemData.IntruBufferLength; /*************************/ /* JSON intrusion record */ /*************************/ if (!RecordCount++) sptr += snprintf (sptr, zptr-sptr, ",\"records\":["); sptr += snprintf (sptr, zptr-sptr, "%s{\"record\":\"%s\"", RecordCount > 1 ? "," : "", RecordBuffer); if (SystemData.DebugInfoPtr) sptr += snprintf (sptr, zptr-sptr, ",\"debug\":\"%s\"", SystemData.DebugInfoPtr); sptr += snprintf (sptr, zptr-sptr, ",\"type\":\"%s\"\ ,\"class\":\"%s\"\ ,\"count\":%d\ ,\"expires\":\"%s\"\ ,\"exstamp\":%u", TypePtr, ClassPtr, BreakinCount, MungeTime(&ExpiresTime64,1), ExpiresTimeStamp); if (IdentBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"ident\":\"%s\"", IdentBuffer); if (NetworkBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"net\":\"%s\"", NetworkBuffer); if ((aptr = IpAddrBuffer)[0] || (aptr = IpHostBuffer)[0]) { if (IpAddrBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"addr\":\"%s\"", IpAddrBuffer); if (IpHostBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"host\":\"%s\"", IpHostBuffer); #ifdef INTRUSPECT_GEOLOCATE if (GeoLocatePtr && GeoLocatePtr[0] && GeoLocatePtr[0] != '!') { GeoLocateNoDelay (1); GeoLocateUtf8 (1); if (isalnum(GeoLocatePtr[0])) geoptr = GeoLocate (GeoLocatePtr, aptr); else if (isalnum(GeoLocatePtr[1])) geoptr = GeoLocate (GeoLocatePtr+1, aptr); else geoptr = GeoLocate (NULL, aptr); sptr += snprintf (sptr, zptr-sptr, ",\"raw\":\"%s\"", geoptr); if (*geoptr && *geoptr != '(' && *geoptr != '[') geoptr = GeoLocateData (geoptr, 0); sptr += snprintf (sptr, zptr-sptr, ",\"geo\":\"%s\"", geoptr); } #endif /* INTRUSPECT_GEOLOCATE */ } if (SystemData.ClusterMember) sptr += snprintf (sptr, zptr-sptr, ",\"node\":\"%s\"", ScsNodePtr); if (SourceBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"src\":\"%s\"", SourceBuffer); if (sptr < zptr) *sptr++ = '}'; if (sptr < zptr) *sptr = '\0'; SystemData.IntruBufferLength = sptr - SystemData.IntruBufferPtr; } /***************/ /* end records */ /***************/ if (ExcessCount) { /* alter only the once when exceeded */ if (AlertExcessRecords) { JsonAlert (NULL, "!Only first %d of actual %d records reported!", IntruRecordsMax, RecordCount + ExcessCount); AlertExcessRecords = FALSE; } } else AlertExcessRecords = TRUE; sptr += snprintf (sptr, zptr-sptr, "%s,\ \"size\":%d,\ \"length\":%d,\ \"recordCount\":%d,\ \"excessCount\":%d,\ \"status\":\"%%X%08.08X\",\ \"timestamp\":%u,\ \"vmsTime\":\"%s\"}", RecordCount ? "]" : "", SystemData.IntruBufferSize, SystemData.IntruBufferLength, RecordCount, ExcessCount, status, TimeStamp, MungeTime(&CurrentTime64)); if (Debug) fprintf (stdout, "|%s|\n", SystemData.IntruBufferPtr); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.IntruBufferLength = sptr - SystemData.IntruBufferPtr; } /*****************************************************************************/ /* Check the string for periods. If not found return a NULL, otherwise attempt to resolve an IPv4 address. Return a string retpresnting that address and host name if resolved. And yes, back-to-back '|'-separated strings. */ char* IdentifyNetwork (char *string) { static char buf [256]; int cnt, ip1, ip2, ip3, ip4; char *aptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IdentifyNetwork() |%s|\n", string); cnt = 0; for (aptr = cptr = string; *cptr; cptr++) if (*cptr == '.') cnt++; if (!cnt) return (NULL); zptr = (sptr = buf) + sizeof(buf)-1; for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = '|'; if (cnt == 3) if (sscanf (aptr, "%d.%d.%d.%d", &ip1,&ip2,&ip3,&ip4) == 4) for (cptr = LookupHostName((ulong)aptr); *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; return (buf); } /*****************************************************************************/ /* Append the server and version to the static system buffer. This can't be done until the CGI variables are first available. */ void JsonServerSoftware () { char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "JsonServerSoftware()\n"); sptr = SystemData.StaticBuffer + SystemData.StaticBufferLength; zptr = SystemData.StaticBuffer + sizeof(SystemData.StaticBuffer); if (*(--sptr) != '}') EXIT_FI_LI (SS$_BUGCHECK); cptr = WsLibCgiVar("SERVER_SOFTWARE"); CgiServerSoftwarePtr = calloc (1, strlen(cptr)+1); strcpy (CgiServerSoftwarePtr, cptr); if (HttpdApache) { if (cptr = strstr (cptr, "Apache/")) cptr += 7; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"Apache v"); } else if (HttpdOSU) { if (cptr = strstr (cptr, "OSU/")) cptr += 4; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"OSU v"); } else if (HttpdWASD) { if (cptr = strstr (cptr, "WASD/")) cptr += 5; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"WASD v"); } else { cptr = NULL; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"HTTPd? v"); } if (cptr) while (*cptr && !isspace(*cptr) && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++; else if (sptr < zptr) *sptr++ = '?'; for (cptr = "\"}"; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.StaticBufferLength = sptr - SystemData.StaticBuffer; } /*****************************************************************************/ /* Create a JSON data structure in a buffer representing the per-boot characteristics of the system. */ void JsonStaticData () { int64 Size64; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "JsonStaticData()\n"); Size64 = SystemData.MemSize; Size64 *= SystemData.PageSize; zptr = (sptr = SystemData.StaticBuffer) + sizeof(SystemData.StaticBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"static\""); sptr += snprintf (sptr, zptr-sptr, ",\"archName\":\"%s\"", SystemData.ArchName); sptr += snprintf (sptr, zptr-sptr, ",\"clusterMember\":%s", SystemData.ClusterMember ? "true" : "false"); sptr += snprintf (sptr, zptr-sptr, ",\"cpuAvail\":%d", SystemData.AvailCpuCount); sptr += snprintf (sptr, zptr-sptr, ",\"hwName\":\"%s\"", SystemData.HwName); sptr += snprintf (sptr, zptr-sptr, ",\"memSize\":%lld", Size64); sptr += snprintf (sptr, zptr-sptr, ",\"nodeName\":\"%s\"", SystemData.NodeName); sptr += snprintf (sptr, zptr-sptr, ",\"vmsVersion\":\"%s\"", SystemData.VmsVersion); sptr += snprintf (sptr, zptr-sptr, "}"); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.StaticBufferLength = sptr - SystemData.StaticBuffer; } /*****************************************************************************/ /* Per-boot system data. */ int CollectSyiStatic () { static ulong ActiveCpuCnt, AvailCpuCount, MemSize, PageSize; static ushort HwNameLength; static unsigned char ClusterMember; static char ArchName [15+1], HwName [1+60], NodeName [15+1], VmsVersion [8+1]; /* initialize structure for SYI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } SyiItemList[] = { { sizeof(AvailCpuCount), SYI$_AVAILCPU_CNT, &AvailCpuCount, 0 }, { sizeof(ActiveCpuCnt), SYI$_ACTIVECPU_CNT, &ActiveCpuCnt, 0 }, { sizeof(ArchName), SYI$_ARCH_NAME, &ArchName, 0 }, { sizeof(ClusterMember), SYI$_CLUSTER_MEMBER, &ClusterMember, 0 }, { sizeof(HwName), SYI$_HW_NAME, &HwName, &HwNameLength }, { sizeof(MemSize), SYI$_MEMSIZE, &MemSize, 0 }, { sizeof(NodeName), SYI$_SCSNODE, &NodeName, 0 }, { sizeof(PageSize), SYI$_PAGE_SIZE, &PageSize, 0 }, { sizeof(VmsVersion), SYI$_VERSION, &VmsVersion, 0 }, {0,0,0,0} }; int status; char *cptr, *sptr, *zptr; struct _iosb IOsb; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "CollectSyiStatic()\n"); status = sys$getsyiw ( 0, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &SyiItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (Debug) fprintf (stdout, "sys$getsyi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSnok (status)) return (status); SystemData.AvailCpuCount = AvailCpuCount; SystemData.ActiveCpuCount = ActiveCpuCnt; #if FAUX_CLUSTER SystemData.ClusterMember = 1; #else SystemData.ClusterMember = ClusterMember; #endif SystemData.MemSize = MemSize; SystemData.PageSize = PageSize; zptr = (sptr = SystemData.ArchName) + sizeof(SystemData.ArchName)-1; for (cptr = ArchName; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; HwName[HwNameLength] = '\0'; zptr = (sptr = SystemData.HwName) + sizeof(SystemData.HwName)-1; for (cptr = HwName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; NodeName[sizeof(NodeName)-1] = '\0'; zptr = (sptr = SystemData.NodeName) + sizeof(SystemData.NodeName)-1; for (cptr = NodeName; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; zptr = (sptr = SystemData.VmsVersion) + sizeof(SystemData.VmsVersion)-1; for (cptr = VmsVersion; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|%s|%s|%s|\n", SystemData.ArchName, SystemData.HwName, SystemData.NodeName, SystemData.VmsVersion); return (status); } /*****************************************************************************/ /* Make a binary time into an null-terminated ASCII string, absorbing non-significant leading characters, and truncating hundredths. If a second argument is supplied (anything) the date of an absolute time is suppressed if it's the same as the current date. Up to four concurrent times are stored in static storage. */ char* MungeTime ( int64 *btptr, ... ) { static int BufferIdx; static char Buffer [4][32]; static $DESCRIPTOR (BufferDsc, Buffer[0]); static $DESCRIPTOR (DateFaoDsc, "!%D\0"); static $DESCRIPTOR (TimeFaoDsc, "!%T\0"); int argcnt; char *cptr, *sptr; ushort Time7 [7]; int64 Time64; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MungeTime()\n"); va_count (argcnt); BufferDsc.dsc$a_pointer = Buffer[BufferIdx++ % 4]; if (btptr == NULL) sys$gettim (btptr = &Time64); if (argcnt > 1) sys$numtim (&Time7, btptr); if (argcnt > 1 && !(*(btptr+1) & 0x80000000) && Time7[0] == CurrentTime7[0] && Time7[1] == CurrentTime7[1] && Time7[2] == CurrentTime7[2]) { sys$fao (&TimeFaoDsc, 0, &BufferDsc, btptr); cptr = BufferDsc.dsc$a_pointer; } else { sys$fao (&DateFaoDsc, 0, &BufferDsc, btptr); for (cptr = BufferDsc.dsc$a_pointer; (*cptr == ' ' || *cptr == '0' || *cptr == ':') && *(cptr+1) != '.'; cptr++); } for (sptr = cptr; *sptr && *sptr != '.'; sptr++); *sptr = '\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). There will always be a delay in the lookup because this function is called during AST delivery, so buffer the IP addresses and host names. Returns a pointer to the host name, to '?' during lookup, or '!' if not resolvable. */ char* LookupHostName (ulong arg1) { #define CACHE_MAX 128 /* absolute max is 1024 */ #define EXPIRE_SECONDS 300 #define RETRY_ATTEMPTS 5 #define RETRY_SECONDS 5 #define INETACP$C_TRANS 2 #define INETACP_FUNC$C_GETHOSTBYNAME 1 #define INETACP_FUNC$C_GETHOSTBYADDR 2 struct LookupCacheStruct { int RetryCount; ushort HostNameLength; ulong ExpiresStamp; ulong Ip4Address; ulong Ip6Address [4]; char HostName [127+1], IpAddrStr [31+1]; struct dsc$descriptor HostAddressDsc; struct dsc$descriptor HostNameDsc; struct _iosb LookupIOsb; }; static struct LookupCacheStruct LookupCache [CACHE_MAX]; 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 idx, status; ulong ThisTimeStamp; int64 ThisTime64; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LookupHostName() %d\n", arg1); if (!LookupChannel) { /* assign a channel to the internet template device */ status = sys$assign (&TcpIpDeviceDsc, &LookupChannel, 0, 0); if (VMSnok (status)) EXIT_FI_LI (status); /* initialise the descriptors */ for (idx = 0; idx < CACHE_MAX; idx++) { LookupCache[idx].HostNameDsc.dsc$b_class = DSC$K_CLASS_S; LookupCache[idx].HostNameDsc.dsc$b_dtype = DSC$K_DTYPE_T; LookupCache[idx].HostNameDsc.dsc$w_length = sizeof(LookupCache[idx].HostName)-1; LookupCache[idx].HostNameDsc.dsc$a_pointer = LookupCache[idx].HostName; LookupCache[idx].HostAddressDsc.dsc$b_class = DSC$K_CLASS_S; LookupCache[idx].HostAddressDsc.dsc$b_dtype = DSC$K_DTYPE_T; } } if (arg1 > 1023) { /**************************/ /* lookup this IP address */ /**************************/ sys$gettim (&ThisTime64); ThisTimeStamp = decc$fix_time (&ThisTime64); cptr = (char*)arg1; if (Debug) fprintf (stdout, "->|%s|\n", cptr); /* search the cache */ for (idx = 0; idx < CacheUsed; idx++) { if (!MATCH8 (cptr, LookupCache[idx].IpAddrStr)) continue; if (strcmp (cptr, LookupCache[idx].IpAddrStr)) continue; if (Debug) fprintf (stdout, "|%s|->\n", LookupCache[idx].HostName); return (LookupCache[idx].HostName); } /* look for an unused or expired entry */ for (idx = 0; idx < CACHE_MAX; idx++) { if (!LookupCache[idx].IpAddrStr[0]) break; if (ThisTimeStamp >= LookupCache[idx].ExpiresStamp) break; } /* otherwise look for an unresolveable entry */ if (idx >= CACHE_MAX) for (idx = 0; idx < CACHE_MAX; idx++) if (LookupCache[idx].IpAddrStr[0] == '!') break; /* otherwise just cycle through the cache array entries */ if (idx >= CACHE_MAX) idx = 0; if (idx > CacheUsed) CacheUsed = idx; if (Debug) fprintf (stdout, "idx %d %d\n", idx, CacheUsed); LookupCache[idx].IpAddrStr[0] = LookupCache[idx].HostName[1] = '\0'; if (strchr (cptr, ':')) { if (inet_pton (AF_INET6, cptr, &LookupCache[idx].Ip6Address) != 1) { LookupCache[idx].HostName[0] = '!'; LookupCache[idx].ExpiresStamp = ThisTimeStamp + RETRY_SECONDS; return (LookupCache[idx].HostName); } LookupCache[idx].Ip4Address = 0; } else if (inet_pton (AF_INET, cptr, &LookupCache[idx].Ip4Address) != 1) { LookupCache[idx].HostName[0] = '!'; LookupCache[idx].ExpiresStamp = ThisTimeStamp + RETRY_SECONDS; return (LookupCache[idx].HostName); } LookupCache[idx].HostName[0] = '?'; LookupCache[idx].ExpiresStamp = ThisTimeStamp + EXPIRE_SECONDS; zptr = (sptr = LookupCache[idx].IpAddrStr) + sizeof(LookupCache[idx].IpAddrStr)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (LookupCache[idx].Ip4Address) { LookupCache[idx].HostAddressDsc.dsc$w_length = sizeof(LookupCache[idx].Ip4Address); LookupCache[idx].HostAddressDsc.dsc$a_pointer = (char*)&LookupCache[idx].Ip4Address; } else { LookupCache[idx].HostAddressDsc.dsc$w_length = sizeof(LookupCache[idx].Ip6Address); LookupCache[idx].HostAddressDsc.dsc$a_pointer = (char*)&LookupCache[idx].Ip6Address; } LookupCache[idx].RetryCount = RETRY_ATTEMPTS; } else { /**************/ /* lookup AST */ /**************/ idx = arg1; status = LookupCache[idx].LookupIOsb.iosb$w_status; if (Debug) fprintf (stdout, "sys$qio() %%X%08.08X\n", status); if (VMSok (status)) { LookupCache[idx].HostName[LookupCache[idx].HostNameLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", LookupCache[idx].HostName); if (!LookupCache[idx].HostName[0]) { LookupCache[idx].HostName[0] = '!'; LookupCache[idx].HostName[1] = '\0'; } return (LookupCache[idx].HostName); } if (status == SS$_ENDOFFILE || !LookupCache[idx].RetryCount--) { sys$gettim (&ThisTime64); ThisTimeStamp = decc$fix_time (&ThisTime64); LookupCache[idx].ExpiresStamp = ThisTimeStamp + RETRY_SECONDS; LookupCache[idx].HostName[0] = '!'; LookupCache[idx].HostName[1] = '\0'; return (LookupCache[idx].HostName); } LookupCache[idx].RetryCount--; } status = sys$qio (0, LookupChannel, IO$_ACPCONTROL | IO$M_EXTEND, &LookupCache[idx].LookupIOsb, LookupHostName, idx, &ControlSubFunctionDsc, &LookupCache[idx].HostAddressDsc, &LookupCache[idx].HostNameLength, &LookupCache[idx].HostNameDsc, 0, 0); if (Debug) fprintf (stdout, "[%d] sys$qio() %%X%08.08X\n", idx, status); if (VMSnok(status)) LookupCache[idx].IpAddrStr[0] = '\0'; return (LookupCache[idx].HostName); } /*****************************************************************************/ /* Open the file and read the content into allocated memory. This must be freed by the caller. Return static storage containing a VMS status value as a string should there be an error. */ char* LoadFile (char *fname) { #define FAB$C_UDF 0 #define FAB$C_FIX 1 #define FAB$C_VAR 2 #define FAB$C_VFC 3 #define FAB$C_STM 4 #define FAB$C_STMLF 5 #define FAB$C_STMCR 6 static char errbuf [32]; uint size, rfm; char *cptr, *sptr; stat_t statbuf; FILE *ifptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LoadFile() %s\n", fname); if ((ifptr = fopen (fname, "r", "shr=get")) == NULL) { sprintf (errbuf, "%%X%08.08X", vaxc$errno); return (errbuf); } if (fstat (fileno(ifptr), &statbuf) < 0) { sprintf (errbuf, "%%X%08.08X", vaxc$errno); fclose (ifptr); return (errbuf); } size = statbuf.st_size; rfm = statbuf.st_fab_rfm; if (!(cptr = sptr = calloc (1, size))) { sprintf (errbuf, "%%X%08.08X", vaxc$errno); return (errbuf); } /* read the entire file into memory */ if (rfm & FAB$C_VAR || rfm & FAB$C_VFC) { /* record-by-record (plus one allows the EOF to be encountered) */ while (size-(sptr-cptr) && fgets ((char*)sptr, size-(sptr-cptr)+1, ifptr)) while (*sptr) sptr++; } else { /* in one fell swoop (hopefully faster and more efficient) */ if (!fread (sptr, 1, size, ifptr)) { sprintf (errbuf, "%%X%08.08X", vaxc$errno); fclose (ifptr); return (errbuf); } } fclose (ifptr); return (cptr); } /*****************************************************************************/ /* If a UTF-8 code point in string return an allocated null string with all chars converted to escaped unicode points. Otherwise return duplicated string. Caller free()s returned string when ready. */ char* EscapeUtf8 (char* utfptr) { char *aptr, *cptr, *sptr; for (cptr = utfptr; *cptr; cptr++) if (*cptr & 0x80) break; if (!*cptr) return (strdup (utfptr)); /* contains a UTF-8 code point */ while (*cptr) cptr++; aptr = sptr = calloc (1, ((cptr-utfptr)*6)+1); for (cptr = utfptr; *cptr; cptr++) sptr += sprintf (sptr, "\\u%04x", (uchar)*cptr); *sptr = '\0'; return (aptr); } /*****************************************************************************/ /* UTF-8 null string is munged into an obvious character. */ static void ToUtf8 (char* buf) { uint ch; char *aptr, *cptr, *sptr; if (Debug) fprintf (stdout, "GeoLocateToUtf8() |%s|\n", buf); aptr = sptr = buf; while (*aptr) { if (*aptr < 0x80) *sptr++ = *aptr++; else if ((*(aptr+1) & 0xF0) < 0xE0) *sptr++ = ((*aptr++) & 0xE0) << 3; else if ((*aptr & 0xF0) == 0xE0) { *sptr++ = '~'; aptr += 2; } else { *sptr++ = '~'; aptr += 3; } } *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", buf); } /*****************************************************************************/ /* 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 ushort ValueLength; static ulong 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; ushort *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); } /*****************************************************************************/ /* 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 (Debug) 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); } /*****************************************************************************/ /* If WASD and the version >= 10.1 then WebSocket is potentially available. The multi-valued logical name INTRUSPECT_WEBSOCKET allows this to be overridden by defining the first value to be zero, or one. Further, additional values can represent partial strings from the user-agent field. A match returns the complement of the first value. So, 0,"Firefox" would disable WebSocket for all but user-agents containing the string "Firefox". If the leading character is '!' then negates the match, so 0,"!Windows NT","Firefox" would disable WebSocket for all but user-agents containing the string "Firefox" unless it also contained the string "Windows NT". Could be used with the first value defined as 1, in which case it would function to exclude usage by specified WebSocket agent strings. With CGIplus-enabled WebSocket, as it is directly accessing a CGI variable, can only be used before the request setup has completed and the asynchronous WebSocket I/O has begun. Bit of a kludge but allows the flexibility currently required. */ int WebSockAvailable () { static long LnmIndex = -1, LnmAttributes; static unsigned short LogValueLen; static char LogValue [255+1]; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } LnmItems [] = { { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 }, { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 }, { sizeof(LogValue)-1, LNM$_STRING, &LogValue, &LogValueLen }, { 0,0,0,0 } }; static $DESCRIPTOR (LogNameDsc, "INTRUSPECT_WEBSOCKET"); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); int cnt, status, WebSocketOk = 0; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "WebSockAvailable()\n"); if (MinimumWASD ("10.1.0")) WebSocketOk = 1; if (!WebSocketOk) return (WebSocketOk); for (LnmIndex = 0; LnmIndex <= 127; LnmIndex++) { status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (VMSnok (status) || !(LnmAttributes & LNM$M_EXISTS)) break; LogValue[LogValueLen] = '\0'; if (!LnmIndex) WebSocketOk = atoi(LogValue); else if (LogValue[0] == '!') { if (strstr (CgiHttpUserAgentPtr, LogValue+1)) return (WebSocketOk); } else if (strstr (CgiHttpUserAgentPtr, LogValue)) return (!WebSocketOk); } return (WebSocketOk); } /*****************************************************************************/ /* */ int HavePriv (ulong *PrivMask) { static ulong JpiImagPriv [2], JpiProcPriv [2]; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } JpiItemList[] = { { sizeof(JpiImagPriv), JPI$_IMAGPRIV, &JpiImagPriv, 0 }, { sizeof(JpiProcPriv), JPI$_PROCPRIV, &JpiProcPriv, 0 }, {0,0,0,0} }; int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "HavePriv() %08.08X%08.08X\n", PrivMask[0], PrivMask[1]); status = sys$getjpiw (0, 0, 0, &JpiItemList, 0, 0, 0); if (VMSnok (status)) EXIT_FI_LI(status); return ((((JpiImagPriv[0] & PrivMask[0]) == PrivMask[0]) && ((JpiImagPriv[1] & PrivMask[1]) == PrivMask[1])) || (((JpiProcPriv[0] & PrivMask[0]) == PrivMask[0]) && ((JpiProcPriv[1] & PrivMask[1]) == PrivMask[1]))); } /*****************************************************************************/ /* Control debug characteristics to allow use in a CGIplus environment. Set to record if debug enabled or binary if not. */ void SetDebug (int DebugOn) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SetDebug()\n"); if (!DebugOn) { Debug = 0; return; } else if (Debug) return; if (getenv ("INTRUSPECT$DBUG")) Debug = 1; if (Debug) fprintf (stdout, "Status: 200\n\ Content-Type: text/plain\n\ Script-Control: X-content-encoding-gzip=0\n\ \r\n"); if (Debug || CliDebug || getenv("WWWEXEC_RUNDOWN_STRING")) stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=rec"); else stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin"); } /****************************************************************************/