/*****************************************************************************/ /* 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 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 (update SOFTWAREVN as well!) --------------- 13-FEB-2017 MGD v1.0.0, initial 08-APR-2015 MGD pre-history */ /*****************************************************************************/ #define SOFTWAREVN "1.0.0" /* ^^^^^ don't neglect [.RUNTIME]INTRUSPECT.JS compliance! */ #define SOFTWARENM "INTRUSPECT" #define SOFTWARECR "Copyright (C) 2017 Mark G.Daniel" #ifdef __ALPHA char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN; #endif #ifdef __ia64 char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN; #endif #ifdef __VAX char SoftwareID [] = SOFTWARENM " VAX-" SOFTWAREVN; #endif /* standard C header files */ #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 "wslib.h" /** #define USE_INT64 0 **/ #include "simpler64.h" /* by default all structures on non-VAX are not member aligned */ #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__ #define EXIT_FI_LI(status) \ { printf ("Status:500\r\n\r\n[%s:%d]\r\n", FI_LI); 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 "\"\"\"" #define FREEGEOIP_SERVICE "freegeoip.net" /*******************/ /* data structures */ /*******************/ typedef struct STRUCT_IOSBLK { ushort iosb$w_status; ushort iosb$w_bcnt; uint iosb$l_reserved; } IOSBLK; 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 *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; ulong CollectBinTime [2], CurrentBinTime [2], StartBinTime [2], ProvideBinTime [2]; ushort CurrentNumTime [7]; float DeltaSeconds = 999999.0; char *CgiHttpRefererPtr, *CgiHttpUserAgentPtr, *CgiRemoteAddrPtr, *CgiRemoteUserPtr, *CgiRequestUriPtr, *CgiServerSoftwarePtr, *CgiQueryStringPtr, *ConfigJavaScriptPtr; char HttpdVersion [32], RefererHost [128]; CLIENT_DATA CgiClientData; SYSTEM_DATA SystemData; #if !defined(__VAX) && !defined(__ALPHA) /* may produce a "%LINK-I-UDFSYM, GETTIM_PREC" which can safely be ignored */ int (*GetTimFunction)() = sys$gettim_prec; #else int (*GetTimFunction)() = sys$gettim; #endif ulong SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; #ifndef __VAX ulong NeedPrivMask [2] = { PRV$M_SYSPRV, (PRV$M_SECURITY >> 32) }; #else ulong NeedPrivMask [2] = { PRV$M_SYSPRV, 0x40 }; #endif /**************/ /* prototypes */ /**************/ void AccessControl (CLIENT_DATA*); int sys$setprv (__unknown_params); void JsonAlert (CLIENT_DATA*, char*, ...); char* JsonNumber (int, void*); void JsonIntruData (); void JsonRuntime (); void JsonServerSoftware (); void JsonStaticData (); char* LookupHostName (ulong); char* LoadFile (char*); void ProvideData (); char* MungeTime (ulong *btptr, ...); void SetDebug (int); void AddClient (); int HavePriv (ulong*); int MinimumWASD (char*); void ReadClient (struct WsLibStruct*); void RemoveClient (struct WsLibStruct*); char* SysTrnLnm (char*, char*, int); int WebSockAvailable (); void XhrResponse (); #ifdef __VAX #define VAX_VSNPRINTF 1 int vax_snprintf (char*, size_t, const char*, ...); int vax_vsnprintf (char*, size_t, const char*, va_list); #define snprintf vax_snprintf #define vsnprintf vax_vsnprintf #endif /*****************************************************************************/ /* */ 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; } #if !defined(_VAX) && !defined(__ALPHA) /* cater for "%LINK-I-UDFSYM, GETTIM_PREC" on unsupported platform */ if (GetTimFunction == NULL) GetTimFunction = sys$gettim; #endif GetTimFunction (&StartBinTime); 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 ("INTRUSPECT_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; } } SetDebug (1); IsCgiPlus = WsLibIsCgiPlus (); GetTimFunction (&CurrentBinTime); InitTimeStamp = decc$fix_time (&CurrentBinTime); 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\r\n\ \r\n\ \n\ \n\ \n\ INTRUspect\n\ \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: text/html\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, &CurrentBinTime); 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 OUTBUF_CHUNK 2048 /* bytes */ static const long cvtf_mode = LIB$K_DELTA_SECONDS_F; static ulong PrevTimeStamp; static ulong PrevBinTime [2], SecondsDelta [2]; int status; ulong fltf; /* generic 32 bits for the VAX float */ ulong DiffBinTime [2]; char *cptr, *sptr, *zptr; struct WsLibStruct *wsctx, *wsptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "\nProvideData()\n"); GetTimFunction (&CurrentBinTime); sys$numtim (&CurrentNumTime, &CurrentBinTime); TimeStamp = decc$fix_time (&CurrentBinTime); 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; if (QUAD_ZERO(&PrevBinTime)) { PUT_QUAD_QUAD (&CurrentBinTime, &PrevBinTime); if (Debug) fprintf (stdout, "%d %s\n", SystemData.StaticBufferLength, SystemData.StaticBuffer); } /* calculate a floating point delta time */ status = lib$sub_times (&CurrentBinTime, &PrevBinTime, &DiffBinTime); if (VMSnok (status)) EXIT_FI_LI (status); PUT_QUAD_QUAD (&CurrentBinTime, &PrevBinTime); #ifdef __ia64 status = lib$cvts_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffBinTime); #endif #ifdef __ALPHA /* Alpha 7.3-1 does not have lib$cvts_..() */ status = lib$cvtf_from_internal_time (&cvtf_mode, &fltf, &DiffBinTime); /* 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); #endif #ifdef __VAX status = lib$cvtf_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffBinTime); #endif if (VMSnok (status)) EXIT_FI_LI (status); if (Debug) fprintf (stdout, "DeltaSeconds: %f\n", DeltaSeconds); if (DataViaCGI) clptr = &CgiClientData; JsonIntruData (); GetTimFunction (&CollectBinTime); 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); } 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++); } /* 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 (QUAD_ZERO (SecondsDelta)) { PUT_LONG_QUAD (UpdateSeconds, SecondsDelta); MUL_LONG_QUAD (-10000000, SecondsDelta); } status = sys$setimr (0, &SecondsDelta, ProvideData, NULL, 0); if (VMSnok(status)) EXIT_FI_LI (status); ProvideDataSeconds += UpdateSeconds; GetTimFunction (&ProvideBinTime); 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. With VAX clock granularity, durations may fail to register if sub-10mS. This data is not available to the end-user, is for developer and technical insight purposes, and can be seen from the data stream. */ void JsonRuntime () { static ulong CollectTotalTime [2], ProvideTotalTime [2]; 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 }, #ifndef __VAX { sizeof(JpiExecTim), JPI$_EXECTIM, &JpiExecTim, 0 }, { sizeof(JpiKrnlTim), JPI$_KRNLTIM, &JpiKrnlTim, 0 }, { sizeof(JpiSuprTim), JPI$_SUPRTIM, &JpiSuprTim, 0 }, { sizeof(JpiUserTim), JPI$_USERTIM, &JpiUserTim, 0 }, #endif {0,0,0,0} }; int status; ulong CpuUSR, CpuSEK, CpuTIM; ulong RunBinTime [2], DiffBinTime [2]; char *sptr, *zptr; float fcount; IOSBLK 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; } SUB_QUAD_QUAD (CurrentBinTime, CollectBinTime); ADD_QUAD_QUAD (CollectBinTime, CollectTotalTime); SUB_QUAD_QUAD (CurrentBinTime, ProvideBinTime); ADD_QUAD_QUAD (ProvideBinTime, ProvideTotalTime); MINUS_QUAD_QUAD (CurrentBinTime, StartBinTime, RunBinTime); CpuTIM = JpiCpuTim - CpuTimInit; CpuUSR = JpiUserTim - CpuUsrInit; CpuSEK = JpiSuprTim + JpiExecTim + JpiKrnlTim - CpuSekInit; zptr = (sptr = SystemData.RuntimeBuffer) + sizeof(SystemData.RuntimeBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"runtime\",\ \"cpu\":%f,\ \"cpuTIM\":%f,", (float)JpiCpuTim / 100.0, (float)CpuTIM / 100.0 / fcount); #ifndef __VAX sptr += snprintf (sptr, zptr-sptr, "\"cpuUSR\":%f,\ \"cpuSEK\":%f,", (float)CpuUSR / 100.0 / fcount, (float)CpuSEK / 100.0 / fcount); #endif sptr += snprintf (sptr, zptr-sptr, "\"run\":\"%s\",\ \"collect\":%f,\ \"provide\":%f,\ \"refresh\":%d", MungeTime(RunBinTime), FLOAT_FROM_QUAD(CollectTotalTime) / 10000000.0 / fcount, FLOAT_FROM_QUAD(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 () { #define INTRUBUF_CHUNK 2048 /* bytes */ #define MAX_TRACK_BUFFER 255 /* characters */ #define MAX_NODE_LIST 100 /* maximum cluster nodes */ 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 IntruBufferLen, OutputListLen, TotalRecordCount; static char IntruBuffer [1058+1]; static $DESCRIPTOR (IntruAllDsc, "*"); static $DESCRIPTOR (IntruBufferDsc, IntruBuffer); 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; ulong time [2]; } BreakinBlock; BOOL WasNegTim; int cnt, ip, idx, status, BreakinClass, BreakinCount, BreakinType, ConcurrentCount = 0, ExcessRecordCount = 0, IntruCtx = 0, RecordCount = 0, ScsNodeCount = 0; ulong ExpiresTimeStamp; ulong ExpiresBinTime [2], UpBinTime [2]; char *aptr, *cptr, *sptr, *zptr, *ClassPtr, *HostNamePtr, *IntruBufferPtr, *ScsNodePtr, *TypePtr; char IdentBuffer [256], NetworkBuffer [256], SourceBuffer [256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "JsonIntruData()\n"); CallCount++; if (!SystemData.IntruBufferPtr) { /* initial buffer space */ SystemData.IntruBufferSize = INTRUBUF_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, &IntruBufferDsc, &IntruBufferLen, &BreakinBlock, ClusterIntruFlags, &IntruCtx); } else status = sys$show_intrusion (&IntruAllDsc, &IntruBufferDsc, &IntruBufferLen, &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 + ExcessRecordCount >= IntruRecordsMax) { /* more than we're prepared to deal with - just count 'em up */ ExcessRecordCount++; continue; } IntruBuffer[IntruBufferLen] = '\0'; if (Debug) fprintf (stdout, "type: 0x%08.08X (0x%02X) %d |%s|\n", BreakinBlock.type, (BreakinBlock.type & 0xfc), OutputListLen, IntruBuffer); 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; } IntruBufferPtr = IntruBuffer; BreakinClass = BreakinBlock.type & 0x03; BreakinType = BreakinBlock.type & 0xfc; PUT_QUAD_QUAD (BreakinBlock.time, ExpiresBinTime); ExpiresTimeStamp = decc$fix_time (&ExpiresBinTime); /****************************/ /* process intrusion record */ /****************************/ HostNamePtr = NULL; NetworkBuffer[0] = IdentBuffer[0] = SourceBuffer[0] = '\0'; 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 : { /* "[:]::"? */ /* "::"? e.g. TELNET */ /* "::::"? e.g. IMAP */ /* ":::"? e.g. MultiNet SSH */ /* consistency? what a dog's breakfast! */ ClassPtr = "NETW"; /* scan (backwards) for a component */ for (cptr = IntruBufferPtr; *cptr; cptr++); while (cptr > IntruBufferPtr && *(USHORTPTR)cptr != '::') cptr--; if (*(USHORTPTR)cptr == '::') { /* got one! does it contain an IPv6 address? */ aptr = cptr - 1; while (aptr > IntruBufferPtr && *(USHORTPTR)aptr != '::') aptr--; if (*(USHORTPTR)aptr == '::') { /* looks like IPv6! just copy the whole thing */ zptr = (sptr = NetworkBuffer) + sizeof(NetworkBuffer)-1; for (aptr = IntruBufferPtr; aptr < cptr && sptr < zptr; *sptr++ = *aptr++); *sptr = '\0'; } else { /* not IPv6! may have preceding component */ aptr = (cptr -= 1); while (aptr > IntruBufferPtr && *aptr != ':') aptr--; if (*aptr == ':') { /* has preceding */ zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; for (cptr = IntruBufferPtr; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (*cptr) cptr++; } else cptr = IntruBufferPtr; zptr = (sptr = NetworkBuffer) + sizeof(NetworkBuffer)-1; while (*cptr && *(USHORTPTR)cptr != '::' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } /* step over the '::' */ cptr += 2; } /* scan forward looking for a delimitting ':' */ for (aptr = cptr; *aptr && *aptr != ':'; aptr++); if (*aptr) { /* : */ zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (*cptr) cptr++; zptr = (sptr = IdentBuffer) + sizeof(IdentBuffer)-1; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } else if (NetworkBuffer[0]) { /* :: */ zptr = (sptr = IdentBuffer) + sizeof(IdentBuffer)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } else { /* only */ zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } if (NetworkBuffer[0]) { /* look for something "networky" */ for (cptr = NetworkBuffer; *cptr && *cptr != '.' && *(USHORTPTR)cptr != '::'; cptr++); if (*cptr == '.') { /* might be an IP(v4) address */ cptr = NetworkBuffer; if (isdigit(*cptr)) if (sscanf (cptr, "%d.%d.%d.%d", &ip,&ip,&ip,&ip) == 4) HostNamePtr = LookupHostName((ulong)cptr); } else if (*cptr != ':') { /* non-network looking component (e.g. node name) */ zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; for (cptr = NetworkBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; NetworkBuffer[0] = '\0'; } } break; } case CIA$M_TERMINAL : { /* " on "? */ ClassPtr = "TERM"; zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; for (cptr = IntruBufferPtr; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* the " on " */ while (*cptr == ' ' || *cptr == 'o' || *cptr == 'n') cptr++; zptr = (sptr = NetworkBuffer) + sizeof(NetworkBuffer)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; break; } case CIA$M_TERM_USER : { /* ": on "? */ ClassPtr = "TE_US"; /* the */ zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; for (cptr = IntruBufferPtr; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; *sptr = '\0'; /* the */ zptr = (sptr = IdentBuffer) + sizeof(IdentBuffer)-1; for (cptr++; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* the " on " */ while (*cptr == ' ' || *cptr == 'o' || *cptr == 'n') cptr++; zptr = (sptr = NetworkBuffer) + sizeof(NetworkBuffer)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; break; } case CIA$M_USERNAME : { ClassPtr = "USER"; zptr = (sptr = IdentBuffer) + sizeof(IdentBuffer)-1; for (cptr = IntruBufferPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; break; } default : { ClassPtr = "BUGCHECK"; zptr = (sptr = SourceBuffer) + sizeof(SourceBuffer)-1; for (cptr = IntruBufferPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } } /*************************/ /* JSON intrusion record */ /*************************/ zptr = SystemData.IntruBufferPtr + SystemData.IntruBufferSize; sptr = SystemData.IntruBufferPtr + SystemData.IntruBufferLength; if (sptr > (zptr - (SystemData.IntruBufferSize/4))) { /* down to 25% of buffer space */ SystemData.IntruBufferLength = sptr - SystemData.IntruBufferPtr; SystemData.IntruBufferSize += INTRUBUF_CHUNK; SystemData.IntruBufferPtr = realloc (SystemData.IntruBufferPtr, SystemData.IntruBufferSize); if (!SystemData.IntruBufferPtr) EXIT_FI_LI (vaxc$errno); zptr = SystemData.IntruBufferPtr + SystemData.IntruBufferSize; sptr = SystemData.IntruBufferPtr + SystemData.IntruBufferLength; } if (!RecordCount++) sptr += snprintf (sptr, zptr-sptr, ",\"records\":["); sptr += snprintf (sptr, zptr-sptr, "%s{\"type\":\"%s\"\ ,\"class\":\"%s\"\ ,\"count\":%d\ ,\"expires\":\"%s\"\ ,\"expiresTimestamp\":%u", RecordCount > 1 ? "," : "", TypePtr, ClassPtr, BreakinCount, MungeTime(ExpiresBinTime,1), ExpiresTimeStamp); if (HostNamePtr && isalnum(*HostNamePtr)) sptr += snprintf (sptr, zptr-sptr, ",\"dns\":\"%s\"", HostNamePtr); if (IdentBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"ident\":\"%s\"", IdentBuffer); if (NetworkBuffer[0]) sptr += snprintf (sptr, zptr-sptr, ",\"net\":\"%s\"", NetworkBuffer); 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++ = '}'; SystemData.IntruBufferLength = sptr - SystemData.IntruBufferPtr; } /***************/ /* end records */ /***************/ if (ExcessRecordCount) { /* alter only the once when exceeded */ if (AlertExcessRecords) { JsonAlert (NULL, "!Only first %d of actual %d records reported!", IntruRecordsMax, RecordCount + ExcessRecordCount); AlertExcessRecords = FALSE; } } else AlertExcessRecords = TRUE; zptr = SystemData.IntruBufferPtr + SystemData.IntruBufferSize; sptr = SystemData.IntruBufferPtr + SystemData.IntruBufferLength; sptr += snprintf (sptr, zptr-sptr, "%s,\ \"recordCount\":%d,\ \"excessRecordCount\":%d,\ \"timestamp\":%u,\ \"vmsTime\":\"%s\"}", RecordCount ? "]" : "", RecordCount, ExcessRecordCount, TimeStamp, MungeTime(CurrentBinTime)); if (Debug) fprintf (stdout, "|%s|\n", SystemData.IntruBufferPtr); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.IntruBufferLength = sptr - SystemData.IntruBufferPtr; } /*****************************************************************************/ /* 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 () { ulong Size64 [2]; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "JsonStaticData()\n"); PUT_LONG_QUAD (SystemData.MemSize, Size64); MUL_LONG_QUAD (SystemData.PageSize, Size64); 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\":%s", JsonNumber(64,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; IOSBLK 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 ( ulong *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 NumTime [7]; ulong BinTime [2]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MungeTime()\n"); va_count (argcnt); BufferDsc.dsc$a_pointer = Buffer[BufferIdx++ % 4]; if (btptr == (ulong*)NULL) sys$gettim (btptr = (ulong*)&BinTime); if (argcnt > 1) sys$numtim (&NumTime, btptr); if (argcnt > 1 && !(*(btptr+1) & 0x80000000) && NumTime[0] == CurrentNumTime[0] && NumTime[1] == CurrentNumTime[1] && NumTime[2] == CurrentNumTime[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); } /*****************************************************************************/ /* Convert the 32 bit integer, or 64 bit integer pointed to, into an ASCII number string. Can be used up to BUFFER_MAX times in the one operation (e.g. printf()). */ char* JsonNumber ( int bits, void *numptr ) { #undef BUFFER_MAX #define BUFFER_MAX 4 static BufferIdx = 0; static char NumBuffer [BUFFER_MAX][48]; static $DESCRIPTOR (NumBufferDsc, NumBuffer[0]); static $DESCRIPTOR (Value32FaoDsc, "!UL\0"); static $DESCRIPTOR (Value64FaoDsc, "!@SQ\0"); int status; /*********/ /* begin */ /*********/ NumBufferDsc.dsc$a_pointer = NumBuffer[BufferIdx++ % BUFFER_MAX]; if (bits > 32) #ifndef __VAX status = sys$fao (&Value64FaoDsc, 0, &NumBufferDsc, numptr); #else { /* a bit of a kludge but apparently ok all the way up to 53 bits */ double dValue; dValue = (double)((ulong*)numptr)[0]; dValue += (double)((ulong*)numptr)[1] * 4294967296.0; sprintf (NumBufferDsc.dsc$a_pointer, "%.0f", dValue); status = SS$_NORMAL; } #endif else status = sys$fao (&Value32FaoDsc, 0, &NumBufferDsc, *((ulong*)numptr)); if (VMSok (status)) return (NumBufferDsc.dsc$a_pointer); return ("NaN"); } /*****************************************************************************/ /* 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 60 #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; IOSBLK 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; ulong ThisBinTime [2]; 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 (&ThisBinTime); ThisTimeStamp = decc$fix_time (&ThisBinTime); 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 (&ThisBinTime); ThisTimeStamp = decc$fix_time (&ThisBinTime); 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); } /*****************************************************************************/ /* 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"); } /*****************************************************************************/ /* A little mitigation without a whole lot of work. */ #if VAX_VSNPRINTF int vax_snprintf ( char *aptr, size_t nbyt, const char *format, ... ) { int cnt; va_list argptr; va_start (argptr, format); cnt = vax_vsnprintf (aptr, nbyt, format, argptr); va_end (argptr); return (cnt); } int vax_vsnprintf ( char *aptr, size_t nbyt, const char *format, va_list argptr ) { static int bcnt; static char *bptr; int cnt; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (!bcnt || nbyt > bcnt) { if (!(bcnt = nbyt)) bcnt = 256; if (bptr) free (bptr); if ((bptr = calloc (1, bcnt*3)) == NULL) EXIT_FI_LI (vaxc$errno); } cnt = vsprintf (bptr, format, argptr); if (cnt >= bcnt*2) EXIT_FI_LI (SS$_RESULTOVF); if (!nbyt || !aptr) return (cnt); sptr = aptr; cptr = bptr; for (cnt = nbyt; *cptr && --cnt; *sptr++ = *cptr++); *sptr = '\0'; return (sptr-aptr); } #endif /* VAX_VSNPRINTF */ /****************************************************************************/