/*****************************************************************************/ /* mondOPA.c MonDeSi OPCOM monitor. Spawn a subprocess attached to a pseudo-terminal with OPCOM enabled and parse the stream of OPCOM messages. Requires MONDESI.EXE to be INSTALLed with OPER privilege for basic OPCOM messaging, additional SECURITY privilege for security OPCOM. Logical name MONDESI_OPCOM value can contain /ENABLE AND /DISABLE qualifiers plus keywords to specify which categories to monitor. Default is all. If the value contains an asterisk then messages are available irrespective of authentication (see MONDESI.C JsonOpcomData()). VERSION LOG ----------- 26-NOV-2020 MGD initial */ /*****************************************************************************/ #ifndef OPCOM_BUFFER_SIZE #error build procedure must define OPCOM_BUFFER_SIZE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DC$_TERM 6 #ifndef UNT64PTR /* 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 UINT64PTR __unaligned uint64* #define INT64PTR __unaligned int64* #endif #define PLAY 0 int dbug = 0, OpaAuditCount, OpaBufferLength, OpaOpcomCount, OpaNonAuditLength, OpaStatus, OpaReadStatus, OpaWriteStatus, OpaSpawnStatus; ulong OpaJpiPid, OpaSpawnPid; char *OpaBufferPtr, *OpaNonAuditPtr; char OpaDevName [64], OpaTime [64]; static uint EfnOpa; static ushort OpaChan; #define OPA_READ_SIZE 18 * 512 #define OPA_WRITE_SIZE 2 * 512 #define OPA_BUFFER_PAGES 20 static char *OpaReadBuffer, *OpaWriteBuffer; static char OpaOpcom [] = "%%%%%%%%%%% OPCOM "; int OpaSpawn (); static void OpaDelete (); static void OpaPoof (int); static void OpaProcess (char*, int); static void OpaRead (); static void OpaSpawnAst (void*); static int OpaWrite (char*); /*****************************************************************************/ /* */ int OpaSpawn (char *enable) { static unsigned long DevNamItem = DVI$_DEVNAM; /* nowait nolognam noclisym nokeypad nocontrol */ static unsigned long flags = 0x6f; static ulong inadr [2]; static ulong chbuf [3]; static $DESCRIPTOR (PromptDsc, "MONDOPA: "); static $DESCRIPTOR (DevNameDsc, OpaDevName); static $DESCRIPTOR (PrcNamDsc, ""); int status; unsigned short slen; char *aptr, *cptr, *sptr, *zptr; char dclbuf [256], PrcNam [32]; if (enable && enable[0] == '!') return (SS$_NORMAL); status = lib$get_ef (&EfnOpa); if (!(status & 1)) return (status); zptr = (sptr = dclbuf) + sizeof(dclbuf)-1; /* hmmm; the leading empty line suppresses some oddity */ for (cptr = "\nSET PROCESS/PRIVILEGE=ALL"; *cptr; *sptr++ = *cptr++); if (enable) { if (aptr = strstr (enable, "/ENABLE")) { for (cptr = "\nREPLY/"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (aptr++; *aptr && *aptr != '/' && sptr < zptr; *sptr++ = *aptr++); } else for (cptr = "\nREPLY/ENABLE"; *cptr && sptr < zptr; *sptr++ = *cptr++); if (aptr = strstr (enable, "/DISABLE")) { for (cptr = "\nREPLY/"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (aptr++; *aptr && *aptr != '/' && sptr < zptr; *sptr++ = *aptr++); } } else for (cptr = "\nREPLY/ENABLE"; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* if not trying to REstart the subprocess */ if (!inadr[0]) { status = sys$expreg (OPA_BUFFER_PAGES, &inadr, 0, 0); if (dbug) printf ("sys$expreg() %%X%08.08x\n", status); if (!(status & 1)) return (status); OpaReadBuffer = (char*)inadr[0]; OpaWriteBuffer = (char*)inadr[0] + OPA_READ_SIZE; /* set the terminal characteristics */ chbuf[0] = (132 << 16) | (TT$_LA100 << 8) | DC$_TERM; chbuf[1] = (24 << 24) | TT$M_EIGHTBIT | TT$M_SCOPE | TT$M_WRAP | TT$M_MECHTAB | TT$M_LOWER | TT$M_TTSYNC /* | TT$M_NOECHO */ ; chbuf[2] = TT2$M_EDIT | TT2$M_DRCS | TT2$M_EDITING | TT2$M_HANGUP; } memset ((char*)inadr[0], 0, (char*)inadr[1] - (char*)inadr[0]); OpaReadStatus = OpaSpawnPid = OpaSpawnStatus = OpaWriteStatus = 0; status = ptd$create (&OpaChan, 0, chbuf, sizeof(chbuf), 0, 0, 0, &inadr); if (dbug) printf ("ptd$create() %%X%08.08x\n", status); if (!(status & 1)) return (status); status = lib$getdvi (&DevNamItem, &OpaChan, 0, 0, &DevNameDsc, &slen); OpaDevName[DevNameDsc.dsc$w_length = slen] = '\0'; if (dbug) printf ("lib$getdvi() %d %%X%08.08x |%s|\n", OpaChan, status, OpaDevName); if (!(status & 1)) return (status); sprintf (PrcNam, "MondOPA_%4.4X", OpaJpiPid & 0xffff); PrcNamDsc.dsc$a_pointer = PrcNam; PrcNamDsc.dsc$w_length = strlen(PrcNam); status = lib$spawn (0, &DevNameDsc, &DevNameDsc, &flags, &PrcNamDsc, &OpaSpawnPid, &OpaSpawnStatus, 0, &OpaSpawnAst, 0, &PromptDsc, 0, 0); if (dbug) printf ("lib$spawn() %%X%08.08x\n", status); if (!(status & 1)) return (status); OpaRead (); for (int cnt = 5; cnt; cnt--) { if (OpaSpawnStatus) return (OpaSpawnStatus); sleep (1); if (OpaReadStatus & 1) break; } OpaWrite (dclbuf); return (status); } /*****************************************************************************/ /* Subprocess self-destruct. */ static void OpaPoof (int status) { ulong pid; OpaDelete (); if (!(pid = OpaSpawnPid)) return; OpaSpawnPid = 0; OpaStatus = status; sys$forcex (&pid, 0, status); } /*****************************************************************************/ /* Delete the pseudo-terminal. */ static void OpaDelete () { if (dbug) printf ("ptd$delete() %d %%X%08.08X %%X%08.08X\n", OpaChan, OpaReadStatus, OpaWriteStatus); if (OpaChan) ptd$delete (OpaChan); OpaChan = 0; } /*****************************************************************************/ /* Subpocess completion AST. */ static void OpaSpawnAst (void *vptr) { if (dbug) printf ("OpaSpawnAst() %%X%08.08X %d\n", OpaSpawnStatus, OpaChan); if (OpaChan) ptd$delete (OpaChan); OpaSpawnPid = 0; } /*****************************************************************************/ /* Expects a string containing newline separated, multiple DCL commands, and writes them successively to the subprocess' SYS$INPUT. */ static int OpaWrite (char *string) { static char *cmds, *next; int bcnt, status = 1; char *bptr, *cptr, *sptr, *zptr; bptr = sptr = OpaWriteBuffer; if (OpaWriteStatus = *(ushort*)bptr) { bptr += sizeof(ushort); bcnt = *(ushort*)bptr; if (dbug) printf ("ptd$write() %d %%X%08.08X\n", bcnt, OpaWriteStatus); if (!(OpaWriteStatus & 1)) OpaPoof (OpaWriteStatus); } if (!cmds) cmds = next = strdup (string); if (!*next) { next = ""; free (cmds); cmds = NULL; return (SS$_NORMAL); } bptr = sptr = OpaWriteBuffer; *(ulong*)sptr = 0; cptr = (sptr += sizeof(ushort) + sizeof(ushort)); zptr = bptr + OPA_WRITE_SIZE; while (*next && *next != '\n' && sptr < zptr) *sptr++ = *next++; *sptr++ = '\r'; *sptr = '\0'; bcnt = sptr - cptr; if (*next) next++; if (dbug) printf ("ptd$write() %d |%s|\n", bcnt, cptr); status = ptd$write (OpaChan, OpaWrite, 0, bptr, bcnt, 0, 0); if (dbug) printf ("ptd$write(3) %%X%08.08X\n", status); if (!(status & 1)) OpaPoof (status); return (status); } /*****************************************************************************/ /* Read subprocess' SYS$OUTPUT records providing each to OpaProcess(). Parse each record looking for OPCOM messages. */ static void OpaRead () { int bcnt, status; char *bptr, *cptr, *optr; bptr = OpaReadBuffer; if (dbug) printf ("OpaRead() %%X%08.08X %d\n", *(ushort*)bptr, *(ushort*)(bptr+2)); if (OpaReadStatus = *(USHORTPTR)bptr) { if (!(OpaReadStatus & 1)) OpaPoof (OpaReadStatus); bptr += sizeof(ushort); bcnt = *(USHORTPTR)bptr; bptr += sizeof(ushort); #if PLAY printf ("OpaRead() %d\n", bcnt); for (int cnt = 0; cnt < bcnt; cnt++) printf ("%c", bptr[cnt] >= 32 ? bptr[cnt] : '.'); puts (""); for (int cnt = 0; cnt < bcnt; cnt++) printf ("%u ", bptr[cnt]); puts ("\n----------"); #endif for (; bcnt-- > 0; bptr++) { if (*bptr == '%' && !memcmp (bptr, OpaOpcom, sizeof(OpaOpcom)-1)) { optr = bptr; for (; bcnt-- > 0; bptr++) if (*bptr == '\r' && *(ULONGPTR)bptr == '\r\n\0\0') break; if (bcnt > 0) OpaProcess (optr, bptr-optr); } } } *(ULONGPTR)OpaReadBuffer = 0; status = ptd$read (EfnOpa, OpaChan, OpaRead, 0, OpaReadBuffer, OPA_READ_SIZE-sizeof(ulong)-2); if (dbug) printf ("ptd$read() %%X%08.08X\n", status); if (!(status & 1)) OpaPoof (status); } /*****************************************************************************/ /* Called with the start and length of the OPCOM message. Note the time of the latest and then buffer the message, most recent at the top, then as many previous messages as buffer capacity allows. Buffering alternates between two. */ static void OpaProcess (char *msg, int length) { static int which1 = -1, which2 = -1; /* changing this then consider clptr->OutputBuffer size */ static char PrimaryBuffer [2][OPCOM_BUFFER_SIZE+64]; static char NonAuditBuffer [2][OPCOM_BUFFER_SIZE+64]; int audit = 0, len; char *aptr, *cptr, *sptr, *zptr; if (dbug) printf ("OpaProcess(%u,%d)\n", msg, length); #if PLAY printf ("OpaProcess(%u,%d)\n", msg, length); printf ("%*.*s", length, length, msg); #endif OpaOpcomCount++; /* copy the most recent timestamp */ zptr = (sptr = OpaTime) + sizeof(OpaTime)-1; cptr = msg + sizeof(OpaOpcom)-1; while (*cptr == ' ') cptr++; while (*cptr && *cptr != ' ' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == ' ' && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != ' ' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (dbug) printf ("OPCOM: %d %s\n", OpaOpcomCount, OpaTime); if (sptr >= zptr) OpaPoof (SS$_BUGCHECK); /******************/ /* primary buffer */ /******************/ if (++which1 > 1) which1 = 0; /* copy the most recent text, JSON-escaping the text */ zptr = (sptr = PrimaryBuffer[which1]) + OPCOM_BUFFER_SIZE; cptr = msg; for (len = length; len && sptr < zptr; len--) { if (*cptr == '\n') { *sptr++ = '\\'; *sptr++ = 'n'; cptr++; if (*cptr == 'M') if (*(ULONGPTR)cptr == 'Mess') if (!memcmp (cptr, "Message from user AUDIT$SERVER", 30)) audit = ++OpaAuditCount; } else if (*cptr < 32) cptr++; else { if (*cptr == '\"' || *cptr == '\\') *sptr++ = '\\'; *sptr++ = *cptr++; } } if (*(ULONGPTR)(sptr-4) != '\\n\\n') { *sptr++ = '\\'; *sptr++ = 'n'; } if (sptr >= zptr) OpaPoof (SS$_BUGCHECK); /* append as much as we can of the alternate (previous) buffer */ aptr = NULL; for (cptr = PrimaryBuffer[which1 ? 0 : 1]; *cptr && sptr < zptr; *sptr++ = *cptr++) { if (*cptr == '%') if (!memcmp (cptr, OpaOpcom, sizeof(OpaOpcom)-1)) for (aptr = sptr; *cptr == '%' && sptr < zptr; *sptr++ = *cptr++); } /* trim any trailing partial message */ if (sptr >= zptr && aptr) sptr = aptr; if (*(ULONGPTR)(sptr-4) == '\\n\\n') sptr -= 2; if (sptr >= zptr) OpaPoof (SS$_BUGCHECK); *sptr = '\0'; OpaBufferPtr = PrimaryBuffer[which1]; OpaBufferLength = sptr - PrimaryBuffer[which1]; /* if an AUDIT$SERVER OPCOM then quit here */ if (audit) return; /********************/ /* non audit buffer */ /********************/ if (++which2 > 1) which2 = 0; /* populate the audit-redacted buffer */ zptr = (sptr = NonAuditBuffer[which2]) + OPCOM_BUFFER_SIZE; cptr = msg; for (len = length; len && sptr < zptr; len--) { if (*cptr == '\n') { *sptr++ = '\\'; *sptr++ = 'n'; cptr++; } else if (*cptr < 32) cptr++; else { if (*cptr == '\"' || *cptr == '\\') *sptr++ = '\\'; *sptr++ = *cptr++; } } if (*(ULONGPTR)(sptr-4) != '\\n\\n') { *sptr++ = '\\'; *sptr++ = 'n'; } if (sptr >= zptr) OpaPoof (SS$_BUGCHECK); /* append as much as we can of the alternate (previous) buffer */ aptr = NULL; for (cptr = NonAuditBuffer[which2 ? 0 : 1]; *cptr && sptr < zptr; *sptr++ = *cptr++) { if (*cptr == '%') if (!memcmp (cptr, OpaOpcom, sizeof(OpaOpcom)-1)) for (aptr = sptr; *cptr == '%' && sptr < zptr; *sptr++ = *cptr++); } /* trim any trailing partial message */ if (sptr >= zptr && aptr) sptr = aptr; if (*(ULONGPTR)(sptr-4) == '\\n\\n') sptr -= 2; if (sptr >= zptr) OpaPoof (SS$_BUGCHECK); *sptr = '\0'; OpaNonAuditPtr = NonAuditBuffer[which2]; OpaNonAuditLength = sptr - NonAuditBuffer[which2]; } /*****************************************************************************/ /* Interactive development only. */ #if PLAY int main (int argc, char *argv[]) { OpaSpawn (""); sleep (30); } #endif /*****************************************************************************/