/*****************************************************************************/ /* Control.c This module implements the HTTPd command-line, and the distributed, to-all server processes (from command-line or Admin Menu), control functionality. Command-line server control commands: */ static char ControlHelp[] = "\n\ o ALIGN= START, STOP, ZERO with [,,]\n\ o AUTH reload authorization file\n\ AUTH=CHECK elementary check of authorization file\n\ (may require additional server command-line parameters)\n\ AUTH=LOAD reload authorization file\n\ AUTH=PURGE purge authentication records from cache\n\ o AUTH=SKELKEY=_:[:] temporary authorisation\n\ o CACHE=ON turn file caching on\n\ CACHE=OFF turn file caching off\n\ CACHE=PURGE purge all data cached\n\ o CONFIG=CHECK elementary check of all configuration files\n\ (may require additional server command-line parameters)\n\ o DCL=DELETE unconditionally delete all DCL script processes\n\ DCL=DELETE=FILE=\n\ DCL=DELETE=SCRIPT=\n\ DCL=DELETE=USER=\n\ o DCL=PROCTOR=APPLY apply any additional rule(s)\n\ DCL=PROCTOR=LOAD (re)load [DclScriptProctor] configuration\n\ (DCL=PURGE or DCL=DELETE to proactively change)\n\ o DCL=PURGE delete idle script processes, mark busy for later deletion\n\ DCL=PURGE=FILE=\n\ DCL=PURGE=SCRIPT=\n\ DCL=PURGE=USER=\n\ o DECNET=PURGE disconnect idle DECnet script tasks\n\ DECNET=DISCONNECT forceably disconnect all DECnet script tasks\n\ o EXIT exit after all current client activity complete\n\ EXIT=NOW exit right now regardless of connections\n\ o GLOBAL=CHECK elementary check of the global configuration file\n\ o HTTP2=PURGE purge idle HTTP/2 connections\n\ HTTP2=PURGE= this particular HTTP/2 connection number\n\ HTTP2=PURGE=ALL purge all HTTP/2 connections\n\ o INSTANCE=MAX|CPU| explicitly set the startup instance value\n\ INSTANCE=ACTIVE|STANDBY move between active and standby mode\n\ o LIST when used with /ALL just list all servers\n\ o LOG=CLOSE close the log file(s)\n\ LOG=FLUSH flush the log file(s)\n\ LOG=OPEN open the log file(s)\n\ LOG=REOPEN closes then reopens the log\n\ o MAP reload mapping rule file\n\ MAP=CHECK elementary check of the mapping rule file\n\ (may require additional server command-line parameters)\n\ MAP=LOAD reload mapping rule file\n\ o MSG=CHECK elementary check of the message file\n\ o NET=LIST list all network connections\n\ o NET=PURGE purge persistent (idle) connections\n\ NET=PURGE= purge this particular connection number\n\ NET=PURGE=ALL purge all (idle and non-idle) connections\n\ NET=PURGE=HTTP1 purge all (ditto) HTTP/1.n connections\n\ NET=PURGE=HTTP2 purge all (ditto) HTTP/2 connections\n\ NET=PURGE=URI= purge connections with matching request URI\n\ o NET=RESUME resume accepting connections (see suspend)\n\ o NET=SUSPEND stop accepting connections (see resume)\n\ NET=SUSPEND=NOW as for suspend and terminate those in-progress\n\ o NOTE= provide an admin note to the metacon rules\n\ o PROXY=ON proxy processing enabled\n\ PROXY=OFF proxy processing disabled\n\ o REQUEST=RUNDOWN= run down (terminate) the specified request\n\ REQUEST=RUNDOWN=ALL run down all current requests\n\ o RESTART effectively exit server image then re-activate\n\ RESTART=NOW restart NOW regardless of connections\n\ RESTART=QUIET restart if-and-when current requests reach zero\n\ o SERVICE=CHECK elementary check of service file\n\ o SSL=CA=LOAD reload the CA verification file\n\ SSL=CERT=LOAD reload all service Certificate files\n\ SSL=KEY=PASSWORD prompt/supply private key password\n\ o STATUS display the status of all instances\n\ STATUS=NOW instances immediately update their status\n\ STATUS=PURGE reset stale instance status information\n\ STATUS=RESET reset all instance status information\n\ o THROTTLE=RELEASE release all queued requests for processing\n\ THROTTLE=RELEASE=SCRIPT=\n\ THROTTLE=RELEASE=USER=\n\ THROTTLE=TERMINATE terminate all queued requests \n\ THROTTLE=TERMINATE=SCRIPT=\n\ THROTTLE=TERMINATE=USER=\n\ THROTTLE=ZERO zero throttle statistics\n\ o WEBSOCKET=DISCONNECT all WebSocket connections\n\ WEBSOCKET=DISCONNECT= this WebSocket connection number\n\ WEBSOCKET=DISCONNECT=SCRIPT= matching this script name\n\ WEBSOCKET=DISCONNECT=USER= matching this scripting user name\n\ o ZERO zero all accounting\n\ ZERO=NOTICED zero the \'errors noticed\' accounting\n\ ZERO=PROXY zero proxy accounting\n\ ZERO=STATUS clear any STATUS message as displayed by HTTPDMON\n\ \n"; /* o /NOTE= *INTERNAL* CLI /NOTE= to prcoess log o SSL=CERT=LOAD *INTERNAL* (re)load SSL server certificate(s) o TICKET=KEY\0<48-bytes> *INTERNAL* propagate session ticket key\n\ o LOG=OPEN= *OBSOLETE* open log file using the specified name o LOG=REOPEN= *OBSOLETE* log reopened using specified name o LOG=FORMAT= *OBSOLETE* set the log format o LOG=PERIOD= *OBSOLETE* set the log period o PROXY=STATISTICS *OBSOLETE* perform statistics scan of cache\n\ o PROXY=PURGE=BACKGROUND *OBSOLETE* background cache purge\n\ PROXY=PURGE=HOST *OBSOLETE* host name cache purge\n\ PROXY=PURGE=REACTIVE *OBSOLETE* reactive cache purge\n\ PROXY=PURGE=ROUTINE *OBSOLETE* routine cache purge\n\ o PROXY=STOP=SCAN *OBSOLETE* stop in-progress purge or statistics\n\ These commands are entered at the DCL command line (interpreted in the CLI.c module) in the following manner. $ HTTPD /DO=command If /ALL is added then the command is applied to all HTTPd server processes on the node or cluster (asuming there is more than one executing, requires SYSLCK). Due to architectural constraints, those commands marked "*" in the above list cannot be used with the /ALL qualifier. $ HTTPD /DO=command /ALL For example: $ HTTPD /DO=HELP display the above summary $ HTTPD /DO=EXIT the server exits if when clients connected $ HTTPD /DO=EXIT=NOW the server exists immediately $ HTTPD /DO=RESTART server exits and then restarts $ HTTPD /DO=RESTART/ALL all servers on node/cluster exit and restarts $ HTTPD /DO=DCL=PURGE delete all persistent DCL script processes $ HTTPD /DO=LOG=CLOSE close the log file $ HTTPD /DO=ZERO/ALL zero the accounting on all servers $ HTTPD /DO=LIST/ALL *special-case*, just list all servers Single node and cluster directives are implemented using cluster-wide locking resource names and shared memory. For single server /DO= control the lock-resource name serves to notify (all) server(s) that a directive has been written into the global section shared memory control buffer. All participating servers check this area, only the one with a current directive written into its shared-memory control buffer performs it. For multiple server /DO=/ALL control, the lock value block contains the actual directive and so participating servers act on that alone. The originating image requests a cluster-wide lock before originating a directive, effectively locking others out during the process (which is most commonly a very short period). Servers could potentially be grouped using the resource names. The /ALL qualifier accepts an optional string that will become part of the lock resource name. Hence when servers belonging to a particular group are started up the startup procedure would include something like "/ALL=1", and then Admin Menu directives would apply to only those in that group, and command-line directives would be used "/DO=command/ALL=1", etc. Note that the /ALL= parameter can be any alphanumeric string up to 8 characters. DCL PURGE/DELETE PARAMETERS --------------------------- On non-VAX systems under VMS V8.2 or later (and supported by the 64 byte lock value block) is is possible to supply further parameters refining the scope of the PURGE/DELETE. The following directive limits the PURGE/DELETE to script processes executing under the specified account. HTTPD/DO=DCL=PURGE=USER= HTTPD/DO=DCL=DELETE=USER= This variant acts only on script processes with script paths matching the specified pattern. HTTPD/DO=DCL=PURGE=SCRIPT= HTTPD/DO=DCL=DELETE=SCRIPT= This variant acts only on script processes with script VMS file specifications matching the specified pattern. HTTPD/DO=DCL=PURGE=FILE= HTTPD/DO=DCL=DELETE=FILE= These cannot be combined. THROTTLE RELEASE/TERMINATE PARAMETERS ------------------------------------- In a similar fashion to the DCL= parameters, on non-VAX systems under VMS V8.2 or later is is possible to supply further parameters refining the scope of the RELEASE/TERMINATE. The following directive limits the PURGE/DELETE to script processes executing under the specified account. HTTPD/DO=THROTTLE=RELEASE=USER= HTTPD/DO=THROTTLE=TERMINATE=USER= This variant acts only on script processes with script paths matching the specified pattern. HTTPD/DO=THROTTLE=RELEASE=SCRIPT= HTTPD/DO=THROTTLE=TERMINATE=SCRIPT= These cannot be combined. SERVER CONTROL LOCK ------------------- When a server starts up it uses InstanceNotifySet() to enqueue a CR (concurrent read) lock against the resource name using ControlHttpdLock(). This lock allows a "blocking" AST to be delivered (back to ControlHttpdLock()) indicating a CLI-command or other server is wishing to initiate a control action. It releases that original CR lock then immediately enqueues another CR so that it can read the lock value block subsequently written to by the initiator's now-granted EX mode lock (see immediately below). For VAX and pre-V8.2 Alpha and Itanium the lock value block will contain a maximum 15 character, null-terminated string (the 16 bytes) with the directive. For non-VAX V8.2 and later the lock value block has a maximum size of 64 bytes, allowing a 63 character string. The AST delivery will provide this value block to ControlHttpdAst() which calls the appropriate function to perform or ignore the directive. INITIATOR CONTROL LOCK ---------------------- To initiate some control activity on one or more servers the initiator (either at the command-line or from the Admin Menu) enqueues a CR (concurrent read) lock on the appropriate resource name. It then converts that lock to EX (exclusive). Those with original CR locks enqueued have a "blocking" AST delivered, release their locks allowing the initiator to obtain the requested EX lock. It then, using a sys$deq(), writes the command string into the lock value block. When the exclusive lock is dequeued the servers waiting on the subsequent CR locks have them delivered, allowing them to read the value block and they can then check the lock status block for a directive (which they may or may not act upon depending on the contents). VERSION HISTORY --------------- 11-SEP-2021 MGD /DO=NET=LIST /DO=NET=PURGE=HTTP1 /DO=NET=PURGE=HTTP2 27-DEC-2020 MGD /DO=DCL=PROCTOR=APPLY /DO=DCL=PROCTOR=LOAD 05-AUG-2020 MGD bugfix; ControlDoHelp() remove non-existant DISCONNECT=.. 26-FEB-2020 MGD /DO=SSL=RESET (careful! experimental) 20-AUG-2019 MGD /DO=NET=BREAK= see NetTestBreak() 30-JUL-2019 MGD bugfix; /DO=AUTH=SKELKEY=.. expiry (arghhh) 07-SEP-2018 MGD bugfix; /DO=AUTH=SKELKEY=.. cluster wide - yet again :-( 23-AUG-2018 MGD ControlHttpdAst() CONTROL_ZERO_STATUS 30-JUN-2018 MGD bugfix; /DO=AUTH=SKELKEY=.. cluster wide (yet again :-) 11-JUN-2018 MGD /REQUEST=RUNDOWN=.. to terminate (a) request(s) 03-JUN-2018 MGD SesolaControlReloadCert() undoes SesolaControlReloadService() because it didn't actually work :-/ so /DO=SSL=SERVICE=LOAD[=] no longer works 19-MAY-2018 MGD /DO=ADHOC=... intended for ad-hoc development commands 15-MAY-2018 MGD bugfix; /DO=AUTH=SKELKEY=.. cluster wide 28-APR-2018 MGD refactor Admin..() AST delivery 02-FEB-2018 MGD SesolaControlCertLoad() becomes SesolaControlServiceLoad() /DO=SSL=SERVICE=LOAD[=] SSL context reload 12-NOV-2017 MGD ControlZeroAccounting () save/restore instance status data 18-OCT-2017 MGD /DO=HELP brief summary of command-ine /DOs /NOTE= annotation to server process log /STATUS[=RESET] report on all instance status 12-JUN-2016 MGD /DO=TICKET=KEY to refresh TLS session ticket key 02-DEC-2015 MGD /DO=HTTP2=PURGE[=] 19-NOV-2014 MGD refine ControlMessage() for direct call bugfix; ControlCommand() exit after CONTROL_SSL_PKPASSWD 15-OCT-2011 MGD /DO=NET=PURGE[=..] expanded /DO=WEBSOCKET=DISCONNECT[=..] 12-MAR-2011 MGD /DO=ALIGN=.. 14-JUL-2010 MGD command-line checks of configuration files /DO=AUTH=CHECK /DO=CONFIG=CHECK (all configuration files) /DO=GLOBAL=CHECK /DO=MAP=CHECK /DO=MSG=CHECK /DO=SERVICE=CHECK 01-JUL-2010 MGD Mapurl_ControlReload() rather than Mapurl_Load() 15-JUL-2006 MGD DO=INSTANCE=ACTIVE|STANDBY DO=NET=PURGE[=NOW]|SUSPEND[=NOW]|RESUME 20-JUL-2005 MGD DO=ZERO=NOTICED to reset 'errors noticed' accounting 25-MAY-2005 MGD allow for VMS V8.2 64 byte lksb$b_valblk NOTE= to provide note to metacon rules 30-APR-2005 MGD AUTH=SKELKEY period is allowed to be zero 05-DEC-2004 MGD ControlHttpdAst() CONTROL_ZERO_PROXY 30-NOV-2004 MGD ControlZeroProxyAccounting() 31-DEC-2003 MGD ControlDelay() if multiple instances add a further, random delay to prevent mass suicide with reduced instances and a RESTART=NOW directive 06-JAN-2003 MGD bugfix; ControlEnqueueCommand() occasional race condition between InstanceLockList() and InstanceNotifyNow() AST disabling SYSLCK during list (happy birthday Naomi) 07-DEC-2002 MGD skeleton-key authentication 30-MAY-2002 MGD RESTART=QUIET restart when(-and-if) requests reach zero 21-MAY-2002 MGD ControlZeroAccounting() under instance supervisor control 04-APR-2002 MGD proxy maintenance STOP scan 29-SEP-2001 MGD significant changes in support of per-node instances 04-AUG-2001 MGD support module WATCHing 30-JUN-2001 MGD bugfix; missing log open code, strlen(.._AS), obsolete "log=open=" and "log=reopen=" 18-MAY-2001 MGD global section shared memory and DLM for control, provide SSL private key password directive 06-APR-2001 MGD allow WATCH to note control events 13-MAR-2001 MGD add THROTTLE=... 28-DEC-2000 MGD add SSL=CA=LOAD 03-DEC-2000 MGD bugfix; CachePurge() 18-JUN-2000 MGD add node/cluster-wide, sys$enq()-driven directives (/ALL) 12-JAN-2000 MGD rework control reporting, adding OPCOM messages 20-JAN-1999 MGD proxy serving controls 15-AUG-1998 MGD decnet=purge, decnet=disconnect 16-MAR-1998 MGD restart=now 05-OCT-1997 MGD cache and logging period control 28-SEP-1997 MGD reinstated some convenience commands (AUTH, DCL, and ZERO) 01-FEB-1997 MGD HTTPd version 4 (removed much of its previous functionality) 01-OCT-1996 MGD minor changes to authorization do commands 01-JUL-1996 MGD controls for path/realm-based authorization/authentication 01-DEC-1995 MGD HTTPd version 3 12-APR-1995 MGD support logging 03-APR-1995 MGD support authorization 20-DEC-1994 MGD initial development for multi-threaded version of HTTPd */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include /* application-related header files */ #include "wasd.h" #define WASD_MODULE "CONTROL" /******************/ /* global storage */ /******************/ BOOL ControlDoAllHttpd, ControlExitRequested, ControlRestartQuiet, ControlRestartRequested; ulong ControlPid; char *ControlCommandPtr; char ControlBuffer [256], ControlNodeName [16], ControlProcessName [16], ControlUserName [13]; /********************/ /* external storage */ /********************/ extern BOOL CacheEnabled, HttpdServerExecuting, HttpdTicking, InstanceNodeSupervisor, OperateWithSysPrv, ProxyServingEnabled; extern const int64 Delta01Sec, Delta100mSec; extern int CliServerPort, EfnWait, ExitStatus, HttpdGblSecPages, HttpdGblSecVersion, HttpdTickSecond, InstanceNumber, InstanceEnvNumber, InstanceNodeConfig, InstanceNodeCurrent, NetCurrentProcessing, OpcomMessages, ServerPort; extern ushort HttpdTime7[]; extern ulong GblSecPrvMask[], SysLckMask[], SysPrvMask[], WorldMask[]; extern char ErrorSanityCheck[], ErrorXvalNotValid[], LoggingFileName[], LoggingPeriodName[], SoftwareID[]; extern INSTANCE_LOCK InstanceLockTable[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern HTTPD_GBLSEC *HttpdGblSecPtr; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern SYS_INFO SysInfo; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* This is the primary function called when a /DO= is made at the command line. This function attempts to map the appropriate global section to access memory shared with the server. It then call the appropriate function to request the server perform the directive, either locally or if /ALL was used on the CLI across all servers in the group (cluster). NOTE: AST delivery is disabled when this function is called. */ int ControlCommand ( char *Command, BOOL ReportIt ) { int astatus, cnt, status, LockIndex, PeriodMinutes, ServerCount; char *cptr, *sptr, *zptr; char String [512]; char PrivateKeyPasswd [64]; META_CONFIG *mcptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlCommand()"); if (strsame (Command, CONTROL_HELP, -1)) { ControlDoHelp (NULL); return (SS$_NORMAL); } if (strsame (Command, CONTROL_CONFIG_CHECK, -1)) { status = ConfigLoad (&mcptr); cnt = ControlCheck (mcptr, "global", status); status = ServiceConfigLoad (&mcptr); cnt += ControlCheck (mcptr, "service", status); status = MapUrl_ConfigLoad (&mcptr); cnt += ControlCheck (mcptr, "map", status); status = AuthConfigLoad (&mcptr); cnt += ControlCheck (mcptr, "auth", status); status = MsgConfigLoad (&mcptr); cnt += ControlCheck (mcptr, "msg", status); if (cnt) FaoToStdout ("%HTTPD-F-CHECK, !UL fatal error!%s\n", cnt); else FaoToStdout ("%HTTPD-I-CHECK, 0 fatal errors \ (does not mean it will work as intended!!)\n"); return (SS$_NORMAL); } if (strsame (Command, CONTROL_AUTH_CHECK, -1)) { status = AuthConfigLoad (&mcptr); ControlCheck (mcptr, "auth", status); return (SS$_NORMAL); } if (strsame (Command, CONTROL_GLOBAL_CHECK, -1)) { status = ConfigLoad (&mcptr); ControlCheck (mcptr, "global", status); return (SS$_NORMAL); } if (strsame (Command, CONTROL_MAP_CHECK, -1)) { status = MapUrl_ConfigLoad (&mcptr); ControlCheck (mcptr, "map", status); return (SS$_NORMAL); } if (strsame (Command, CONTROL_MSG_CHECK, -1)) { status = MsgConfigLoad (&mcptr); ControlCheck (mcptr, "msg", status); return (SS$_NORMAL); } if (strsame (Command, CONTROL_SERVICE_CHECK, -1)) { status = ServiceConfigLoad (&mcptr); ControlCheck (mcptr, "service", status); return (SS$_NORMAL); } if (CliServerPort) ServerPort = CliServerPort; if (ServerPort < 1 || ServerPort > 65535) { FaoToStdout ("%HTTPD-E-PORT, IP port out-of-range\n"); exit (STS$K_ERROR | STS$M_INHIB_MSG); } if (strsame (Command, CONTROL_LIST, -1)) { /****************/ /* /DO=LIST/ALL */ /****************/ if (ControlDoAllHttpd) LockIndex = INSTANCE_CLUSTER_DO; else LockIndex = INSTANCE_NODE_DO; ServerCount = InstanceLockList (LockIndex, ", ", &cptr); FaoToStdout ("!UL server!%s; !AZ", ServerCount, ServerCount ? cptr : "?"); if (cptr) VmFree (cptr, FI_LI); return (SS$_NORMAL); } /***************************/ /* now need global section */ /***************************/ HttpdGblSecMap (); if (strsame (Command, CONTROL_AUTH_SKELKEY, sizeof(CONTROL_AUTH_SKELKEY)-1)) { /******************************/ /* authorisation skeleton key */ /******************************/ /* if parse error then cancel any/all other nodes */ if (VMSnok (ControlAuthSkelKey (Command, true))) strcpy (Command, "AUTH=SKELKEY=0"); #if !WATCH_MOD /* if single node then already configured in this one's global common */ if (InstanceNodeCurrent <= 1) return (SS$_NORMAL); #endif /* else drop through to enqueue the command */ } if (strsame (Command, CONTROL_STATUS, -1)) { /***************/ /* /DO=STATUS */ /**************/ InstanceStatusCliReport (NULL); return (SS$_NORMAL); } if (strsame (Command, CONTROL_SSL_PKPASSWD, -1)) { /************************/ /* private key password */ /************************/ stdin = freopen ("SYS$INPUT", "r", stdin, "ctx=rec", "rop=rne", "rop=tmo", "tmo=60"); if (!stdin) exit (vaxc$errno); memset (PrivateKeyPasswd, 0, sizeof(PrivateKeyPasswd)); fprintf (stdout, "Enter private key password []: "); fgets (PrivateKeyPasswd, sizeof(PrivateKeyPasswd), stdin); fputc ('\n', stdout); /* ensure it's null terminated */ PrivateKeyPasswd [sizeof(PrivateKeyPasswd)-1] = '\0'; /* remove any trailing newline */ if (PrivateKeyPasswd[0]) PrivateKeyPasswd [strlen(PrivateKeyPasswd)-1] = '\0'; /* enable SYSPRV to allow writing into the global section */ sys$setprv (1, &SysPrvMask, 0, 0); zptr = (sptr = HttpdGblSecPtr->PkPasswd) + sizeof(HttpdGblSecPtr->PkPasswd)-1; for (cptr = PrivateKeyPasswd; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; sys$setprv (0, &SysPrvMask, 0, 0); return (SS$_NORMAL); } /*********************/ /* need AST delivery */ /*********************/ if (ControlDoAllHttpd) { LockIndex = INSTANCE_CLUSTER_DO; sptr = "ALL"; } else { LockIndex = INSTANCE_NODE_DO; sptr = ""; } if (strsame (Command, CONTROL_NET_LIST, sizeof(CONTROL_NET_LIST)-1)) ReportIt = false; cptr = ControlEnqueueCommand (LockIndex, Command); if (cptr) { /* always report failure */ if (*cptr == '!') FaoToStdout ("%HTTPD-E-DO!AZ, !AZ\n", sptr, cptr+1); else if (ReportIt) FaoToStdout ("%HTTPD-I-DO!AZ, !AZ\n", sptr, cptr); } return (SS$_NORMAL); } /*****************************************************************************/ /* Some /DO=.. help. */ void ControlDoHelp (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlDoHelp()"); if (rqptr) { ResponseHeader (rqptr, 200, "text/plain", -1, NULL, NULL); NetWrite (rqptr, AdminEnd, ControlHelp, sizeof(ControlHelp)-1); } else fputs (ControlHelp, stdout); } /*****************************************************************************/ /* Provide output to the command-line check of the configuration file. */ int ControlCheck ( META_CONFIG *mcptr, char *config, int status ) { char *severity; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlCheck()"); if (VMSnok (status)) { FaoToStdout ("%HTTPD-F-CHECK, !AZ config\n!AZ\n!AZ\n", config, mcptr->LoadReport.FileName, mcptr->LoadReport.TextPtr); return (1); } if (mcptr->LoadReport.ErrorCount) severity = "E"; else if (mcptr->LoadReport.WarningCount) severity = "W"; else if (mcptr->LoadReport.InformCount) severity = "I"; else severity = "S"; FaoToStdout ("%HTTPD-!AZ-CHECK, !AZ config; \ !UL informational!%s, !UL warning!%s, !UL error!%s\n!AZ\n!AZ\n", severity, config, mcptr->LoadReport.InformCount, mcptr->LoadReport.WarningCount, mcptr->LoadReport.ErrorCount, mcptr->LoadReport.FileName, mcptr->LoadReport.TextPtr); return (0); } /*****************************************************************************/ /* Check that the command is known and can be done via a /ALL or via the ADMINISTRATION MENU. Enqueues a CR (concurrent read) lock on the specified resource name, then gets the number of current locks (i.e. other processes) currently with an interest in that lock. Then converts that lock to EX (exclusive) and using a sys$deq() writes the command string into the lock value block. When the exclusive lock is removed the other processes waiting on CR locks get them, reading the value block and executing the command therein. */ char* ControlEnqueueCommand ( int LockIndex, char *Command ) { static char String [256]; BOOL check16, dlm820; int cnt, clen, plen, status, ServerCount; char *cptr; char Command64 [64], CommandRedux [64]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlEnqueueCommand() !UL !&Z", LockIndex, Command); String[0] = '\0'; if (strsame (Command, CONTROL_NET_LIST, sizeof(CONTROL_NET_LIST)-1)) { /* append the current terminal to the command */ strcpy (CommandRedux, Command); strcat (CommandRedux, "="); strcat (CommandRedux, getenv("SYS$OUTPUT")); Command = CommandRedux; } /* length of command (up to any second equate symbol) */ for (cptr = Command; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; while (*cptr && *cptr != '=') cptr++; clen = cptr - Command; /* now the length of any parameter */ if (*cptr) cptr++; while (*cptr) cptr++; plen = cptr - Command - clen; check16 = false; if (!(strsame (Command, CONTROL_ADHOC, sizeof(CONTROL_ADHOC)-1) || strsame (Command, CONTROL_ALIGN_FAULT, sizeof(CONTROL_ALIGN_FAULT)-1) || strsame (Command, CONTROL_AUTH_LOAD1, -1) || strsame (Command, CONTROL_AUTH_LOAD2, -1) || strsame (Command, CONTROL_AUTH_PURGE, -1) || (check16 = strsame (Command, CONTROL_AUTH_SKELKEY, clen)) || strsame (Command, CONTROL_CACHE_ON, -1) || strsame (Command, CONTROL_CACHE_OFF, -1) || strsame (Command, CONTROL_CACHE_PURGE, -1) || strsame (Command, CONTROL_DCL_PROCTOR_APPLY, -1) || strsame (Command, CONTROL_DCL_PROCTOR_LOAD, -1) || (check16 = strsame (Command, CONTROL_DCL_DELETE, clen)) || (check16 = strsame (Command, CONTROL_DCL_PURGE, clen)) || strsame (Command, CONTROL_DECNET_PURGE, -1) || strsame (Command, CONTROL_DECNET_DISCONNECT, -1) || strsame (Command, CONTROL_EXIT, -1) || strsame (Command, CONTROL_EXIT_NOW, -1) || strsame (Command, CONTROL_HTTP2_PURGE_ALL, -1) || strsame (Command, CONTROL_HTTP2_PURGE, sizeof(CONTROL_HTTP2_PURGE)-1) || strsame (Command, CONTROL_INSTANCE, sizeof(CONTROL_INSTANCE)-1) || strsame (Command, CONTROL_LIST, -1) || strsame (Command, CONTROL_LOG_OPEN, -1) || strsame (Command, CONTROL_LOG_CLOSE, -1) || strsame (Command, CONTROL_LOG_FLUSH, -1) || strsame (Command, CONTROL_LOG_REOPEN, -1) || strsame (Command, CONTROL_MAP_LOAD1, -1) || strsame (Command, CONTROL_MAP_LOAD2, -1) || (check16 = strsame (Command, CONTROL_NET_LIST, sizeof(CONTROL_NET_LIST)-1)) || (check16 = strsame (Command, CONTROL_NET_PURGE_URI, sizeof(CONTROL_NET_PURGE_URI)-1)) || strsame (Command, CONTROL_NET_PURGE_ALL, -1) || strsame (Command, CONTROL_NET_PURGE_HTTP1, -1) || strsame (Command, CONTROL_NET_PURGE_HTTP2, -1) || strsame (Command, CONTROL_NET_PURGE, sizeof(CONTROL_NET_PURGE)-1) || strsame (Command, CONTROL_NET_RESUME, -1) || strsame (Command, CONTROL_NET_SUSPEND, -1) || strsame (Command, CONTROL_NET_SUSPEND_NOW, -1) || strsame (Command, CONTROL_NET_NOSUSPEND, -1) || (check16 = strsame (Command, CONTROL_NOTE_META, sizeof(CONTROL_NOTE_META)-1)) || strsame (Command, CONTROL_PROXY_ON, -1) || strsame (Command, CONTROL_PROXY_OFF, -1) || strsame (Command, CONTROL_PROXY_PURGE_HOST, -1) || strsame (Command, CONTROL_PROXY_STATISTICS, -1) || /* must be before = */ strsame (Command, CONTROL_REQUEST_RUNDOWN_ALL, -1) || (check16 = strsame (Command, CONTROL_REQUEST_RUNDOWN, sizeof(CONTROL_REQUEST_RUNDOWN)-1)) || strsame (Command, CONTROL_RESTART, -1) || strsame (Command, CONTROL_RESTART_NOW, -1) || strsame (Command, CONTROL_RESTART_QUIET, -1) || (check16 = strsame (Command, CONTROL_NOTE_SERVER, sizeof(CONTROL_NOTE_SERVER)-1)) || (check16 = strsame (Command, CONTROL_SESSION_TICKET_KEY, clen)) || strsame (Command, CONTROL_SSL_CA_LOAD, -1) || strsame (Command, CONTROL_SSL_CERT_LOAD, -1) || strsame (Command, CONTROL_STATUS_NOW, -1) || strsame (Command, CONTROL_STATUS_PURGE, -1) || strsame (Command, CONTROL_STATUS_RESET, -1) || (check16 = strsame (Command, CONTROL_THROTTLE_RELEASE, clen)) || (check16 = strsame (Command, CONTROL_THROTTLE_TERMINATE, clen)) || strsame (Command, CONTROL_THROTTLE_ZERO, -1) || (check16 = strsame (Command, CONTROL_WEBSOCKET_DISCONNECT, clen)) || strsame (Command, CONTROL_ZERO_NOTICED, -1) || strsame (Command, CONTROL_ZERO_PROXY, -1) || strsame (Command, CONTROL_ZERO_STATUS, -1) || strsame (Command, CONTROL_ZERO, -1))) { if (strsame (Command, CONTROL_LOG_FORMAT_AS, strlen(CONTROL_LOG_FORMAT_AS)) || strsame (Command, CONTROL_LOG_OPEN_AS, strlen(CONTROL_LOG_OPEN_AS)) || strsame (Command, CONTROL_LOG_PERIOD_AS, strlen(CONTROL_LOG_PERIOD_AS)) || strsame (Command, CONTROL_LOG_REOPEN_AS, strlen(CONTROL_LOG_REOPEN_AS))) strcpy (String, "!this directive is OBSOLETE"); else strcpy (String, "!directive not understood"); } if (!String[0] && ControlDoAllHttpd) if (strsame (Command, CONTROL_SSL_PKPASSWD, -1)) strcpy (String, "!cannot do this via /ALL"); if (!String[0] && check16 && SysInfo.LockValueBlockSize == LOCK_VALUE_BLOCK_16) { /* size of command constraints (basically free-form directives) */ if ((strsame (Command, CONTROL_NOTE_META, strlen(CONTROL_NOTE_META)) || strsame (Command, CONTROL_NOTE_SERVER, strlen(CONTROL_NOTE_SERVER))) && strlen(Command) > 15) dlm820 = true; else if (plen) dlm820 = true; else dlm820 = false; if (dlm820) strcpy (String, "!requires VMS V8.2 DLM (clusterwide)"); } if (!String[0]) { /* ensure we have 64 bytes for the (largest) lock value block */ memset (Command64, 0, sizeof(Command64)); strncpy (Command64, Command, sizeof(Command64)-1); if (strsame (Command, CONTROL_SESSION_TICKET_KEY, clen)) { /* command-line generation of new ticket key data */ cptr = SesolaSessionTicketNewKey (); /* 48 byte binary key follows the null-terminated string command */ memcpy (Command64 + sizeof(CONTROL_SESSION_TICKET_KEY), cptr, 48); /* this directive can only be applied cluster-wide */ LockIndex = INSTANCE_CLUSTER_DO; } ServerCount = InstanceLockList (LockIndex, ", ", &cptr); status = InstanceNotifyWait (LockIndex, Command64, CONTROL_WAIT_SECONDS); if (VMSok (status)) FaoToBuffer (String, sizeof(String), 0, "\"!AZ\"; !UL instance!%s notified; !AZ", Command64, ServerCount, ServerCount ? cptr : "?"); else if (status == SS$_NOTQUEUED) FaoToBuffer (String, sizeof(String), 0, "!!command (en)queueing did not complete within !UL seconds", CONTROL_WAIT_SECONDS); else if (status == SS$_TIMEOUT) FaoToBuffer (String, sizeof(String), 0, "!!command (en)queueing timed-out after !UL seconds", CONTROL_WAIT_SECONDS); else FaoToBuffer (String, sizeof(String), 0, "!!failed with status %X!8XL", status); if (cptr) VmFree (cptr, FI_LI); } // if (strsame (Command, CONTROL_NET_LIST, sizeof(CONTROL_NET_LIST)-1)) // sleep (1); return (String); } /*****************************************************************************/ /* The EX (exclusive) lock enqueued by the controlling process has been dequeued allowing this AST to be delivered. The control directive is now in the lock value block. Do what it is requesting. This AST is required to allow AdminControl() to use it as part of an executing server. The CONTROL_BUFFER value indicates the server should examine 'ControlBuffer' in the global section shared memory for a this-system-only command. */ ControlHttpdAst (struct lksb *lksbptr) { BOOL WithExtremePrejudice; int clen, status, ConnectNumber = 0, StartupMax; char *cptr, *sptr, *zptr; char NetListTerm [64] = "", RemoteUser [64] = "", RequestUri [64] = "", ScriptName [64] = "", ScriptFileName [64] = "", UserName [64] = ""; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlHttpdAst() !&F !UL !&Z", &ControlHttpdAst, lksbptr, (char*)lksbptr->lksb$b_valblk); /* get the lock value block written by the dequeued EX mode lock */ cptr = ControlCommandPtr = (char*)lksbptr->lksb$b_valblk; clen = strlen(cptr); /* special case - this message is not to be reported */ if (isalnum(*cptr)) ControlMessage (cptr); if (WATCH_CAT && Watch.Category) { if (isalnum(*cptr)) WatchThis (WATCHALL, WATCH_INTERNAL, "HTTPD/DO=\'!AZ\'", cptr); else if (WATCH_CATEGORY (WATCH_INTERNAL)) WatchThis (WATCHALL, WATCH_INTERNAL, "DO \'!AZ\' !64&H", cptr, cptr); } if (strsame (cptr, CONTROL_ADHOC, sizeof(CONTROL_ADHOC)-1)) { while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; ControlAdhoc (cptr); } else if (strsame (cptr, CONTROL_ALIGN_FAULT, sizeof(CONTROL_ALIGN_FAULT)-1)) { while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; HttpdAlignFault (cptr); } else if (strsame (cptr, CONTROL_AUTH_SKELKEY, sizeof(CONTROL_AUTH_SKELKEY)-1)) ControlAuthSkelKey (cptr, false); else if (strsame (cptr, CONTROL_AUTH_LOAD1, clen) || strsame (cptr, CONTROL_AUTH_LOAD2, clen)) AuthConfigInit (); else if (strsame (cptr, CONTROL_AUTH_PURGE, clen)) AuthCachePurge (true); else if (strsame (cptr, CONTROL_CACHE_ON, clen)) CacheEnabled = true; else if (strsame (cptr, CONTROL_CACHE_OFF, clen)) CacheEnabled = false; else if (strsame (cptr, CONTROL_CACHE_PURGE, clen)) CachePurge (false, NULL, NULL); else if (strsame (cptr, CONTROL_DCL_PROCTOR_LOAD, clen)) ConfigControlProctorLoad (); else if (strsame (cptr, CONTROL_DCL_PROCTOR_APPLY, clen)) DclScriptProctor (); else if ((WithExtremePrejudice = strsame (cptr, CONTROL_DCL_DELETE, sizeof(CONTROL_DCL_DELETE)-1)) || strsame (cptr, CONTROL_DCL_PURGE, sizeof(CONTROL_DCL_PURGE)-1)) { while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; if (isdigit(*cptr)) ConnectNumber = atoi(cptr); while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') { cptr++; if (strsame (cptr, "FILE=", 5)) strzcpy (ScriptFileName, cptr+5, sizeof(ScriptFileName)); else if (strsame (cptr, "SCRIPT=", 7)) strzcpy (ScriptName, cptr+7, sizeof(ScriptName)); else if (strsame (cptr, "USER=", 5)) strzcpy (UserName, cptr+5, sizeof(UserName)); else { ErrorNoticed (NULL, 0, "unknown control directive", FI_LI); return; } } DclControlPurgeScriptProcesses (WithExtremePrejudice, UserName, ScriptName, ScriptFileName); } else if (strsame (cptr, CONTROL_DECNET_DISCONNECT, clen)) DECnetControlDisconnect (false); else if (strsame (cptr, CONTROL_DECNET_PURGE, clen)) DECnetControlDisconnect (true); else if (strsame (cptr, CONTROL_EXIT, clen)) { /* stop the server from receiving incoming requests */ NetShutdownServerSocket (); if (NetCurrentProcessing) { /* this will now be handled by RequestEnd() */ ControlExitRequested = true; } else ControlDelay (CONTROL_DELAY_EXIT); } else if (strsame (cptr, CONTROL_EXIT_NOW, clen)) ControlDelay (CONTROL_DELAY_EXIT); else if (strsame (cptr, CONTROL_HTTP2_PURGE_ALL, sizeof(CONTROL_HTTP2_PURGE_ALL)-1)) { /* delay by one second in case it's being initiated over HTTP/2 */ status = sys$setimr (0, &Delta01Sec, Http2NetControl, -9, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } else if (strsame (cptr, CONTROL_HTTP2_PURGE, sizeof(CONTROL_HTTP2_PURGE)-1)) { cptr += sizeof(CONTROL_HTTP2_PURGE)-1; if (*cptr == '=') cptr++; ConnectNumber = atoi(cptr); Http2NetControl (ConnectNumber); } else if (strsame (cptr, CONTROL_INSTANCE_ACTIVE, clen)) NetActive (false); else if (strsame (cptr, CONTROL_INSTANCE_PASSIVE, clen)) NetPassive (); else /* MUST be the final "INSTANCE=" */ if (strsame (cptr, CONTROL_INSTANCE, sizeof(CONTROL_INSTANCE)-1)) { while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; if (strsame (cptr, "max", -1)) StartupMax = 0; else if (strsame (cptr, "cpu", -1)) StartupMax = INSTANCE_PER_CPU; else StartupMax = atoi(cptr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); HttpdGblSecPtr->InstanceStartupMax = StartupMax; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } else if (strsame (cptr, CONTROL_LOG_OPEN, clen)) Logging (NULL, LOGGING_OPEN); else if (strsame (cptr, CONTROL_LOG_REOPEN, clen)) { /* close then open */ if (VMSok (Logging (NULL, LOGGING_CLOSE))) Logging (NULL, LOGGING_OPEN); } else if (strsame (cptr, CONTROL_LOG_CLOSE, clen)) Logging (NULL, LOGGING_CLOSE); else if (strsame (cptr, CONTROL_LOG_FLUSH, clen)) Logging (NULL, LOGGING_FLUSH); else if (strsame (cptr, CONTROL_MAP_LOAD1, clen) || strsame (cptr, CONTROL_MAP_LOAD2, clen)) MapUrl_ControlReload (); else if (strsame (cptr, CONTROL_NET_LIST, sizeof(CONTROL_NET_LIST)-1)) { static char buf [64]; cptr += sizeof(CONTROL_NET_LIST)-1; while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; strcpy (buf, cptr); /* give the /DO=NET=LIST a chance to get back to the prompt */ status = sys$setimr (0, &Delta100mSec, NetListFor, buf, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } else if (strsame (cptr, CONTROL_NET_PURGE_ALL, sizeof(CONTROL_NET_PURGE_ALL)-1)) { /* do not pull rugs from under */ status = sys$setimr (0, &Delta100mSec, NetControl, -9, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } else if (strsame (cptr, CONTROL_NET_PURGE_HTTP1, sizeof(CONTROL_NET_PURGE_HTTP1)-1)) { /* do not pull rugs from under */ status = sys$setimr (0, &Delta100mSec, NetControl, -1, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } else if (strsame (cptr, CONTROL_NET_PURGE_HTTP2, sizeof(CONTROL_NET_PURGE_HTTP2)-1)) { /* do not pull rugs from under */ status = sys$setimr (0, &Delta100mSec, NetControl, -2, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } else if (strsame (cptr, CONTROL_NET_PURGE_URI, sizeof(CONTROL_NET_PURGE_URI)-1)) { cptr += sizeof(CONTROL_NET_PURGE_URI)-1; if (*cptr == '=') cptr++; strzcpy (RequestUri, cptr, sizeof(RequestUri)); NetControl (0, RequestUri); } else if (strsame (cptr, CONTROL_NET_PURGE, sizeof(CONTROL_NET_PURGE)-1)) { cptr += sizeof(CONTROL_NET_PURGE)-1; if (*cptr == '=') cptr++; ConnectNumber = atoi(cptr); NetControl (ConnectNumber, NULL); } else if (strsame (cptr, CONTROL_NET_RESUME, clen) || strsame (cptr, CONTROL_NET_NOSUSPEND, clen)) NetResume (); else if (strsame (cptr, CONTROL_NET_SUSPEND, clen)) NetSuspend (false); else if (strsame (cptr, CONTROL_NET_SUSPEND_NOW, clen)) NetSuspend (true); else if (strsame (cptr, CONTROL_NOTE_META, sizeof(CONTROL_NOTE_META)-1)) MetaConNoteThis (cptr+sizeof(CONTROL_NOTE_META)-1); else if (strsame (cptr, CONTROL_NOTE_SERVER, sizeof(CONTROL_NOTE_SERVER)-1)) HttpdNoteThis (cptr+sizeof(CONTROL_NOTE_SERVER)-1); else if (strsame (cptr, CONTROL_PROXY_ON, clen)) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyServingEnabled = ProxyAccountingPtr->ServingEnabled = true; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } else if (strsame (cptr, CONTROL_PROXY_OFF, clen)) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyServingEnabled = ProxyAccountingPtr->ServingEnabled = false; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } else if (strsame (cptr, CONTROL_PROXY_PURGE_HOST, clen)) TcpIpHostCacheSupervisor ((uint)-1); else /* must be before = */ if (strsame (cptr, CONTROL_REQUEST_RUNDOWN_ALL, clen)) RequestControlRunDown (-1); else if (strsame (cptr, CONTROL_REQUEST_RUNDOWN, sizeof(CONTROL_REQUEST_RUNDOWN)-1)) { cptr += sizeof(CONTROL_REQUEST_RUNDOWN)-1; if (*cptr == '=') cptr++; ConnectNumber = atoi(cptr); if (ConnectNumber) RequestControlRunDown (ConnectNumber); } else if (strsame (cptr, CONTROL_RESTART, clen)) ControlDelay (CONTROL_DELAY_RESTART); else if (strsame (cptr, CONTROL_RESTART_NOW, clen)) ControlDelay (CONTROL_DELAY_RESTART_NOW); else if (strsame (cptr, CONTROL_RESTART_QUIET, clen)) ControlDelay (CONTROL_DELAY_RESTART_QUIET); else if (strsame (cptr, CONTROL_SSL_CA_LOAD, clen)) SesolaControlReloadCA (); else if (strsame (cptr, CONTROL_SSL_CERT_LOAD, clen)) SesolaControlReloadCerts (NULL); else if (strsame (cptr, CONTROL_SESSION_TICKET_KEY, clen)) SesolaSessionTicketUseKey (cptr); else if (strsame (cptr, CONTROL_STATUS_NOW, clen)) InstanceStatusNow (); else if (strsame (cptr, CONTROL_STATUS_PURGE, clen)) InstanceStatusPurge (); else if (strsame (cptr, CONTROL_STATUS_RESET, clen)) InstanceStatusReset (); else if (WithExtremePrejudice = strsame (cptr, CONTROL_THROTTLE_TERMINATE, sizeof(CONTROL_THROTTLE_TERMINATE)-1) || strsame (cptr, CONTROL_THROTTLE_RELEASE, sizeof(CONTROL_THROTTLE_RELEASE)-1)) { while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; if (isdigit(*cptr)) ConnectNumber = atoi(cptr); while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') { cptr++; if (strsame (cptr, "REMOTE=", 7)) strzcpy (RemoteUser, cptr+7, sizeof(RemoteUser)); else if (strsame (cptr, "SCRIPT=", 7)) strzcpy (ScriptName, cptr+7, sizeof(ScriptName)); else if (strsame (cptr, "USER=", 5)) /* backward compatible with REMOTE= */ strzcpy (RemoteUser, cptr+5, sizeof(RemoteUser)); else { ErrorNoticed (NULL, 0, "unknown control directive", FI_LI); return; } } ThrottleControl (WithExtremePrejudice, ConnectNumber, RemoteUser, ScriptName); } else if (strsame (cptr, CONTROL_WEBSOCKET_DISCONNECT, sizeof(CONTROL_WEBSOCKET_DISCONNECT)-1)) { while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') cptr++; if (isdigit(*cptr)) ConnectNumber = atoi(cptr); while (*cptr && *cptr != '=') cptr++; if (*cptr == '=') { cptr++; if (strsame (cptr, "SCRIPT=", 7)) strzcpy (ScriptName, cptr+7, sizeof(ScriptName)); else if (strsame (cptr, "USER=", 5)) strzcpy (UserName, cptr+5, sizeof(UserName)); else { ErrorNoticed (NULL, 0, "unknown control directive", FI_LI); return; } } WebSockControl (ConnectNumber, ScriptName, UserName); } else if (strsame (cptr, CONTROL_THROTTLE_ZERO, clen)) ThrottleZero (); else if (strsame (cptr, CONTROL_ZERO, clen)) ControlZeroAccounting (); else if (strsame (cptr, CONTROL_ZERO_NOTICED, clen)) ControlZeroNoticed (); else if (strsame (cptr, CONTROL_ZERO_PROXY, clen)) ControlZeroProxyAccounting (); else if (strsame (cptr, CONTROL_ZERO_STATUS, clen)) ControlZeroStatus (); else ErrorNoticed (NULL, 0, "unknown control directive", FI_LI); } /*****************************************************************************/ /* Using the supplied PID fill out 'ControlPid', 'ControlProcessName, abnd 'ControlUserName' with the appropriate information. If 'Pid' is zero then it will be the datils of the current process. */ ControlAccount (ulong Pid) { static BOOL NoWorldPriv; static ulong GetJpiControlFlags = JPI$M_IGNORE_TARGET_STATUS; static struct { ushort buf_len; ushort item; uchar *buf_addr; ushort *short_ret_len; } JpiItems [] = { { sizeof(GetJpiControlFlags), JPI$_GETJPI_CONTROL_FLAGS, &GetJpiControlFlags, 0 }, { sizeof(ControlPid), JPI$_PID, &ControlPid, 0 }, { sizeof(ControlNodeName), JPI$_NODENAME, &ControlNodeName, 0 }, { sizeof(ControlProcessName), JPI$_PRCNAM, &ControlProcessName, 0 }, { sizeof(ControlUserName), JPI$_USERNAME, &ControlUserName, 0 }, { 0,0,0,0 } }; int status; char *cptr; IO_SB IOsb; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlAccount() !8XL", Pid); /* use WORLD to allow access to other processes */ status = sys$setprv (1, &WorldMask, 0, 0); if (VMSnok (status) || status == SS$_NOTALLPRIV) { if (!NoWorldPriv) { NoWorldPriv = true; fprintf (stdout, "%%HTTPD-W-CONTROL, installed without WORLD privilege\n"); } } if (!NoWorldPriv && Pid) { status = sys$getjpiw (EfnWait, &Pid, 0, &JpiItems, &IOsb, 0, 0); if (VMSok (status)) status = IOsb.Status; } else status = SS$_NOPRIV; if (VMSok (status)) { ControlNodeName[15] = '\0'; for (cptr = ControlNodeName; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; (cptr = ControlProcessName)[15] = '\0'; cptr--; while (cptr > ControlProcessName && *cptr == ' ') cptr--; *cptr = '\0'; ControlUserName[12] = '\0'; for (cptr = ControlUserName; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; } else { strcpy (ControlNodeName, "?"); strcpy (ControlProcessName, "?"); strcpy (ControlUserName, "?"); } sys$setprv (0, &WorldMask, 0, 0); } /*****************************************************************************/ /* Report a control request/response, to the process log (SYS$OUTPUT) and optionally as OPCOM messages. */ ControlMessage (char *Message) { static ulong LkiValBlkLen, Lki_XVALNOTVALID; static char LkiValBlk [LOCK_VALUE_BLOCK_64]; static VMS_ITEM_LIST3 LkiItems [] = { /* careful, values are dynamically assigned in code below! */ { 0, 0, 0, 0 }, /* reserved for LKI$_[X]VALBLK item */ { 0, 0, 0, 0 }, /* reserved for LKI$_XVALNOTVALID item */ {0,0,0,0} }; int status; char *cptr, *sptr, *zptr; char buf [64]; IO_SB IOsb; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlMessage() !&Z", Message); if (!isalnum(Message[0])) return; if (SysInfo.LockValueBlockSize == LOCK_VALUE_BLOCK_64) { LkiItems[0].buf_len = LOCK_VALUE_BLOCK_64; LkiItems[0].buf_addr = &LkiValBlk; LkiItems[0].item = LKI$_XVALBLK; LkiItems[0].ret_len = &LkiValBlkLen; LkiItems[1].buf_len = sizeof(Lki_XVALNOTVALID); LkiItems[1].buf_addr = &Lki_XVALNOTVALID; LkiItems[1].item = LKI$_XVALNOTVALID; } else { LkiItems[0].buf_len = LOCK_VALUE_BLOCK_16; LkiItems[0].buf_addr = &LkiValBlk; LkiItems[0].item = LKI$_VALBLK; LkiItems[0].ret_len = &LkiValBlkLen; /* in this case this terminates the item list */ LkiItems[1].buf_len = 0; LkiItems[1].buf_addr = 0; LkiItems[1].item = 0; Lki_XVALNOTVALID = 0; } memset (LkiValBlk, 0, sizeof(LkiValBlk)); sys$setprv (1, &SysLckMask, 0, 0); status = sys$getlkiw (EfnWait, &InstanceLockTable[INSTANCE_CLUSTER_NOTIFY].Lksb.lksb$l_lkid, &LkiItems, &IOsb, 0, 0, 0); sys$setprv (0, &SysLckMask, 0, 0); if (VMSok (status)) status = IOsb.Status; if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); if (Lki_XVALNOTVALID) { /* hmmm, change in cluster composition? whatever! go back to 16 bytes */ SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16; ErrorNoticed (NULL, SS$_XVALNOTVALID, ErrorXvalNotValid, FI_LI); } if (LkiValBlkLen >= sizeof(LkiValBlk)) LkiValBlk[sizeof(LkiValBlk)-1] = '\0'; else LkiValBlk[LkiValBlkLen] = '\0'; if (sscanf (LkiValBlk, "%x", &ControlPid) < 1) ControlPid = 0; ControlAccount (ControlPid); zptr = (sptr = buf) + sizeof(buf)-1; if (*(cptr = Message) == '!') cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* mask the sensitive information */ if (strsame (buf, CONTROL_AUTH_SKELKEY, sizeof(CONTROL_AUTH_SKELKEY)-1)) if (*(buf + sizeof(CONTROL_AUTH_SKELKEY)-1) != '0') *(USHORTPTR)(buf + sizeof(CONTROL_AUTH_SKELKEY)-1) = '*\0'; if (Message[0] == '!') cptr = "E"; else cptr = "I"; if (ControlPid) FaoToStdout ("%HTTPD-!AZ-CONTROL, \ !20%D, !8XL !AZ !AZ \"!AZ\", \'!AZ\'\n", cptr, 0, ControlPid, ControlNodeName, ControlUserName, ControlProcessName, buf); else FaoToStdout ("%HTTPD-!AZ-CONTROL, !20%D, \'!AZ\'\n", cptr, 0, buf); if (OpcomMessages & OPCOM_CONTROL) { if (ControlPid) FaoToOpcom ("%HTTPD-!AZ-CONTROL, !8XL !AZ !AZ \"!AZ\", \'!AZ\'", cptr, ControlPid, ControlNodeName, ControlUserName, ControlProcessName, buf); else FaoToOpcom ("%HTTPD-!AZ-CONTROL, \'!AZ\'", cptr, buf); } if (WATCH_CAT && Watch.Category) { WatchThis (WATCHALL, WATCH_INTERNAL, "HTTPD/DO="); WatchDataFormatted ( "%HTTPD-!AZ-CONTROL, !20%D, !8XL !AZ !AZ \"!AZ\", \'!AZ\'\n", cptr, 0, ControlPid, ControlNodeName, ControlUserName, ControlProcessName, buf); } } /*****************************************************************************/ /* AUTH=SKELKEY=_:[:] This parses the above string directly into the global section. It is used in two ways. To parse and validate the elements when coming from the command-line. If not valid a report is provided to and the relevant storage in the global section is reset and an error status returned. The calling code checks the return status and when success and a cluster uses the DLM to distribute to other nodes. When not from the command-line (i.e. is from the lock value block) no report is provided. */ int ControlAuthSkelKey ( char *Command, BOOL FromCli ) { int status, PeriodMinutes; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlAuthSkelKey() !AZ", Command); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); cptr = Command + sizeof(CONTROL_AUTH_SKELKEY)-1; if (!*cptr || *cptr == '0') { PeriodMinutes = 0; status = SS$_NORMAL; } else { status = SS$_ABORT; zptr = (sptr = HttpdGblSecPtr->AuthSkelKeyUserName) + sizeof(HttpdGblSecPtr->AuthSkelKeyUserName)-1; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (*cptr == ':') { cptr++; zptr = (sptr = HttpdGblSecPtr->AuthSkelKeyPassword) + sizeof(HttpdGblSecPtr->AuthSkelKeyPassword)-1; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (FromCli) { if (HttpdGblSecPtr->AuthSkelKeyUserName[0] != '_') FaoToStdout ("%HTTPD-E-DO, _:[:]\n"); else if (strlen(HttpdGblSecPtr->AuthSkelKeyUserName) < 7) FaoToStdout ("%HTTPD-E-DO, username too short\n"); else if (strsame (HttpdGblSecPtr->AuthSkelKeyUserName, "_SKELE", 6)) FaoToStdout ("%HTTPD-E-DO, not a good choice!!\n"); else if (sptr - HttpdGblSecPtr->AuthSkelKeyPassword < 8) FaoToStdout ("%HTTPD-E-DO, password too short\n"); else if (strsame (HttpdGblSecPtr->AuthSkelKeyUserName, HttpdGblSecPtr->AuthSkelKeyPassword, -1) || strsame (HttpdGblSecPtr->AuthSkelKeyUserName+1, HttpdGblSecPtr->AuthSkelKeyPassword, -1)) FaoToStdout ("%HTTPD-E-DO, not a good choice!!\n"); else if (*cptr == ':') { PeriodMinutes = atoi(cptr+1); /* less than a minute or greater than a week */ if (PeriodMinutes < 0 || PeriodMinutes > 10080) FaoToStdout ("%HTTPD-E-DO, invalid period\n"); else status = SS$_NORMAL; } else { PeriodMinutes = 60; status = SS$_NORMAL; } } else if (*cptr == ':') { PeriodMinutes = atoi(cptr+1); status = SS$_NORMAL; } else { PeriodMinutes = 60; status = SS$_NORMAL; } } else if (FromCli) FaoToStdout ("%HTTPD-E-DO, _:[:]\n"); } if (VMSok (status) && PeriodMinutes) { if (PeriodMinutes > 10080) PeriodMinutes = 10080; HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond = HttpdTickSecond + PeriodMinutes * 60; } else { memset (HttpdGblSecPtr->AuthSkelKeyUserName, 0, sizeof(HttpdGblSecPtr->AuthSkelKeyUserName)); memset (HttpdGblSecPtr->AuthSkelKeyPassword, 0, sizeof(HttpdGblSecPtr->AuthSkelKeyPassword)); HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond = 0; } InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); return (status); } /*****************************************************************************/ /* Exits or restarts the server after a short delay (0.5S to 0.9S for one instance, 2.5S to 2.9S for second instance, 4.5S to 4.9S for third instance, up to 14.5S to 14.9S for an eigth instance). This delay is introduced to give a controlling request (e.g. via the Admin Menu) a chance to complete processing (e.g. deliver success message) before the action is taken, to minmise simultaneous actions in the DLM, and an additional delay to stagger multiple instances, preventing mass suicide when RESTART=NOW and reducing the number of instances. */ ControlDelay (int Action) { int number, status; int64 DelayDelta; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlDelay() !UL", Action); if (!(Action & CONTROL_DELAY_DO)) { if (number = InstanceNumber) number--; DelayDelta = -5000000; /* 500mS */ DelayDelta += -20000000 * (number * 2); /* 1000mS * (0..14) */ DelayDelta += -10000 * (HttpdTime7[6] % 5); /* 100mS * (0..4) */ status = sys$setimr (0, &DelayDelta, &ControlDelay, Action | CONTROL_DELAY_DO, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); return; } switch (Action & ~CONTROL_DELAY_DO) { case CONTROL_DELAY_EXIT : ExitStatus = SS$_NORMAL; HttpdExit (&ExitStatus); /* record server event */ GraphActivityEvent (ACTIVITY_DELPRC); sys$delprc (0, 0); case CONTROL_DELAY_RESTART : ControlRestartRequested = true; /* start the server supervisor in case it's not running */ if (!HttpdTicking) HttpdTick (0); /* this will now be handled by InstanceSupervisor() */ return; case CONTROL_DELAY_RESTART_QUIET : ControlRestartQuiet = true; /* start the server supervisor in case it's not running */ if (!HttpdTicking) HttpdTick (0); /* this will now be handled by InstanceSupervisor() */ return; case CONTROL_DELAY_RESTART_NOW : exit (SS$_NORMAL); default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } } /*****************************************************************************/ /* Zero server accounting structure and service counters. These are both process-local and node/instance-global. Each gets reset under differing circumstances. Those accumulating "current" values need to be buffered and then restored. */ ControlZeroAccounting () { int idx, indsize, istcnt, istsize, StartupCount, ZeroedCount; ulong CurrentDclScriptProcess [INSTANCE_MAX+1], CurrentDclScriptCgiPlus [INSTANCE_MAX+1], CurrentDclScriptRTE [INSTANCE_MAX+1], CurrentDECnetCGI [INSTANCE_MAX+1], CurrentDECnetOSU [INSTANCE_MAX+1], CurrentDECnetTasks [INSTANCE_MAX+1], CurrentPersistentHttp1 [INSTANCE_MAX+1], CurrentThrottleProcessing [INSTANCE_MAX+1], CurrentThrottleQueued [INSTANCE_MAX+1], CurrentWebSockets [INSTANCE_MAX+1]; /* total (HTTP/1+2) [0], HTTP/1 [1], HTTP/2 [2] */ ulong CurrentConnected [HTTPN], CurrentProcessing [HTTPN]; ulong CurrentInstanceConnected [HTTPN][INSTANCE_MAX+1], CurrentInstanceProcessing [HTTPN][INSTANCE_MAX+1]; ACCOUNTING_STRUCT *accptr; INSTANCE_NODE_DATA *indptr; INSTANCE_STATUS *istptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlZeroAccounting() !UL !&B", InstanceNodeConfig, InstanceNodeSupervisor); /* these two get done on a per-process basis regardless */ ThrottleZero (); NetServiceZeroAccounting (); /* in a multi-instance config, shared data only by the supervisor */ if (InstanceNodeConfig > 1 && !InstanceNodeSupervisor) return; accptr = AccountingPtr; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); /**********/ /* buffer */ /**********/ StartupCount = accptr->StartupCount; ZeroedCount = accptr->ZeroedCount; CurrentConnected[HTTP12] = accptr->CurrentConnected[HTTP12]; CurrentConnected[HTTP1] = accptr->CurrentConnected[HTTP1]; CurrentConnected[HTTP2] = accptr->CurrentConnected[HTTP2]; CurrentProcessing[HTTP12] = accptr->CurrentProcessing[HTTP12]; CurrentProcessing[HTTP1] = accptr->CurrentProcessing[HTTP1]; CurrentProcessing[HTTP2] = accptr->CurrentProcessing[HTTP2]; /* the zero index is when instances are not enabled */ for (idx = 0; idx <= INSTANCE_MAX; idx++) { CurrentInstanceConnected[HTTP12][idx] = accptr->CurrentInstanceConnected[HTTP12][idx]; CurrentInstanceConnected[HTTP1][idx] = accptr->CurrentInstanceConnected[HTTP1][idx]; CurrentInstanceConnected[HTTP2][idx] = accptr->CurrentInstanceConnected[HTTP2][idx]; CurrentInstanceProcessing[HTTP12][idx] = accptr->CurrentInstanceProcessing[HTTP12][idx]; CurrentInstanceProcessing[HTTP1][idx] = accptr->CurrentInstanceProcessing[HTTP1][idx]; CurrentInstanceProcessing[HTTP2][idx] = accptr->CurrentInstanceProcessing[HTTP2][idx]; CurrentDclScriptProcess[idx] = accptr->CurrentDclScriptProcess[idx]; CurrentDclScriptCgiPlus[idx] = accptr->CurrentDclScriptCgiPlus[idx]; CurrentDclScriptRTE[idx] = accptr->CurrentDclScriptRTE[idx]; CurrentDECnetCGI[idx] = accptr->CurrentDECnetCGI[idx]; CurrentDECnetOSU[idx] = accptr->CurrentDECnetOSU[idx]; CurrentDECnetTasks[idx] = accptr->CurrentDECnetTasks[idx]; CurrentPersistentHttp1[idx] = accptr->CurrentPersistentHttp1[idx]; CurrentThrottleProcessing[idx] = accptr->CurrentThrottleProcessing[idx]; CurrentThrottleQueued[idx] = accptr->CurrentThrottleQueued[idx]; CurrentWebSockets[idx] = accptr->CurrentWebSockets[idx]; } /* save instance status data */ indsize = sizeof(AccountingPtr->InstanceNodeData); indptr = VmGet (indsize); memcpy (indptr, &AccountingPtr->InstanceNodeData, indsize); istcnt = AccountingPtr->InstanceStatusTableCount; istsize = sizeof(AccountingPtr->InstanceStatusTable); istptr = VmGet (istsize); memcpy (istptr, &AccountingPtr->InstanceStatusTable, istsize); /********/ /* zero */ /********/ memset (accptr, 0, sizeof(ACCOUNTING_STRUCT)); memset (ProxyAccountingPtr, 0, sizeof(PROXY_ACCOUNTING_STRUCT)); for (idx = 1; idx <= INSTANCE_MUTEX_COUNT; idx++) HttpdGblSecPtr->MutexCount[idx] = HttpdGblSecPtr->MutexWaitCount[idx] = 0; /***********/ /* restore */ /***********/ accptr->StartupCount = StartupCount; accptr->ZeroedCount = ZeroedCount + 1; accptr->CurrentConnected[HTTP12] = CurrentConnected[HTTP12]; accptr->CurrentConnected[HTTP1] = CurrentConnected[HTTP1]; accptr->CurrentConnected[HTTP2] = CurrentConnected[HTTP2]; accptr->CurrentProcessing[HTTP12] = CurrentProcessing[HTTP12]; accptr->CurrentProcessing[HTTP1] = CurrentProcessing[HTTP1]; accptr->CurrentProcessing[HTTP2] = CurrentProcessing[HTTP2]; /* the zero index is when instances are not enabled */ for (idx = 0; idx <= INSTANCE_MAX; idx++) { accptr->CurrentInstanceConnected[HTTP12][idx] = CurrentInstanceConnected[HTTP12][idx]; accptr->CurrentInstanceConnected[HTTP1][idx] = CurrentInstanceConnected[HTTP1][idx]; accptr->CurrentInstanceConnected[HTTP2][idx] = CurrentInstanceConnected[HTTP2][idx]; accptr->CurrentInstanceProcessing[HTTP12][idx] = CurrentInstanceProcessing[HTTP12][idx]; accptr->CurrentInstanceProcessing[HTTP1][idx] = CurrentInstanceProcessing[HTTP1][idx]; accptr->CurrentInstanceProcessing[HTTP2][idx] = CurrentInstanceProcessing[HTTP2][idx]; accptr->CurrentDclScriptProcess[idx] = CurrentDclScriptProcess[idx]; accptr->CurrentDclScriptCgiPlus[idx] = CurrentDclScriptCgiPlus[idx]; accptr->CurrentDclScriptRTE[idx] = CurrentDclScriptRTE[idx]; accptr->CurrentDECnetCGI[idx] = CurrentDECnetCGI[idx]; accptr->CurrentDECnetOSU[idx] = CurrentDECnetOSU[idx]; accptr->CurrentDECnetTasks[idx] = CurrentDECnetTasks[idx]; accptr->CurrentPersistentHttp1[idx] = CurrentPersistentHttp1[idx]; accptr->CurrentThrottleProcessing[idx] = CurrentThrottleProcessing[idx]; accptr->CurrentThrottleQueued[idx] = CurrentThrottleQueued[idx]; accptr->CurrentWebSockets[idx] = CurrentWebSockets[idx]; } /* "restore" these from the global equivalents */ ProxyAccountingPtr->ServingEnabled = ProxyServingEnabled; /* restore instance status data */ memcpy (&AccountingPtr->InstanceNodeData, indptr, indsize); VmFree (indptr, FI_LI); AccountingPtr->InstanceStatusTableCount = istcnt; memcpy (&AccountingPtr->InstanceStatusTable, istptr, istsize); VmFree (istptr, FI_LI); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /*****************************************************************************/ /* Zero proxy accounting structure. */ ControlZeroProxyAccounting () { int idx, StartupCount, ZeroedCount; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlZeroProxyAccounting() !UL !&B", InstanceNodeConfig, InstanceNodeSupervisor); /* in a multi-instance config, shared data only by the supervisor */ if (InstanceNodeConfig > 1 && !InstanceNodeSupervisor) return; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); memset (ProxyAccountingPtr, 0, sizeof(PROXY_ACCOUNTING_STRUCT)); ProxyAccountingPtr->ServingEnabled = ProxyServingEnabled; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /*****************************************************************************/ /* Zero the errors noticed accounting data. */ ControlZeroNoticed () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlZeroNoticed()"); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->ErrorsNoticedCount = 0; AccountingPtr->ErrorsNoticedTime64 = 0; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /*****************************************************************************/ /* Zero (empty) the HTTPDMON status string. */ ControlZeroStatus () { int idx, StartupCount, ZeroedCount; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlZeroStatus() !UL !&B", InstanceNodeConfig, InstanceNodeSupervisor); /* in a multi-instance config, shared data only by the supervisor */ if (InstanceNodeConfig > 1 && !InstanceNodeSupervisor) return; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); HttpdGblSecPtr->StatusMessage[0] = '\0'; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /*****************************************************************************/ /* Intended for development purpose ad-hoc commands. */ ControlAdhoc (char* ControlString) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlAdhoc() !&Z", ControlString); if (strsame (ControlString, "STARTUP", -1)) { AccountingPtr->StartupCount = 1; AccountingPtr->LastExitStatus = 0; AccountingPtr->LastExitPid = 0; AccountingPtr->LastExitTime64 = 0; } else ErrorNoticed (NULL, 0, "unknown directive", FI_LI); } /*****************************************************************************/