/*****************************************************************************/
/*
                                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  ACCEPT=LOAD   (re)load accept connection list\n"
#if ALIGN_FAULT
"  o  ALIGN=        START, STOP, ZERO with [<buf-size>,<items>,<mask>]\n"
#endif /* ALIGN_FAULT */
"  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=__<name>[=<vms-name>]:<psswd>[:<minutes>] temporary access\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=<pattern>\n\
     DCL=DELETE=SCRIPT=<pattern>\n\
     DCL=DELETE=USER=<username>\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=<pattern>\n\
     DCL=PURGE=SCRIPT=<pattern>\n\
     DCL=PURGE=USER=<username>\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=<integer>       this particular HTTP/2 connection number\n\
     HTTP2=PURGE=ALL     purge all HTTP/2 connections\n\
  o  INSTANCE=MAX|CPU|<integer>  explicitly set the startup instance value\n\
     INSTANCE=ACTIVE|PASSIVE     move between active and passive 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=<integer> 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=<pattern>     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=<string>       provide an admin note to the metacon rules\n\
  o  NOTICE=INVALID=<integer>    invalid request noticed lines reported\n\
  o  OPCOM=<string>      provide a message via OPCOM\n\
  o  PROXY=ON                proxy processing enabled\n\
     PROXY=OFF               proxy processing disabled\n\
  o  REJECT=LOAD             (re)load reject connection list\n\
  o  REJECT=PURGE[=<integer>]    purge $STATUS IP addresses\n\
  o  REQUEST=RUNDOWN=<integer>   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=<pattern>\n\
     THROTTLE=RELEASE=USER=<username>\n\
     THROTTLE=TERMINATE      terminate all queued requests \n\
     THROTTLE=TERMINATE=SCRIPT=<pattern>\n\
     THROTTLE=TERMINATE=USER=<username>\n\
     THROTTLE=ZERO           zero throttle statistics\n\
  o  WATCH=END               end the current WATCH report\n\
     WATCH=FLUSH             flush the current WATCH report file\n\
  o  WEBSOCKET=DISCONNECT    all WebSocket connections\n\
     WEBSOCKET=DISCONNECT=<integer>         this WebSocket connection number\n\
     WEBSOCKET=DISCONNECT=SCRIPT=<pattern>  matching this script name\n\
     WEBSOCKET=DISCONNECT=USER=<pattern>    matching this scripting user name\n\
  o  ZERO          zero all accounting\n\
     ZERO=HTTP2    zero HTTP/2 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=<string>          *INTERNAL* CLI /NOTE=<string> to process log
  o  TICKET=KEY\0<48-bytes>  *INTERNAL* propagate session ticket key\n\

  o  LOG=OPEN=<name>         *OBSOLETE* open log file using the specified name
  o  LOG=REOPEN=<name>       *OBSOLETE* log reopened using specified name
  o  LOG=FORMAT=<string>     *OBSOLETE* set the log format
  o  LOG=PERIOD=<string>     *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=<username>
  HTTPD/DO=DCL=DELETE=USER=<username>

This variant acts only on script processes with script paths matching the
specified pattern.

  HTTPD/DO=DCL=PURGE=SCRIPT=<pattern>
  HTTPD/DO=DCL=DELETE=SCRIPT=<pattern>

This variant acts only on script processes with script VMS file specifications
matching the specified pattern.

  HTTPD/DO=DCL=PURGE=FILE=<pattern>
  HTTPD/DO=DCL=DELETE=FILE=<pattern>

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=<username>
  HTTPD/DO=THROTTLE=TERMINATE=USER=<username>

This variant acts only on script processes with script paths matching the
specified pattern.

  HTTPD/DO=THROTTLE=RELEASE=SCRIPT=<pattern>
  HTTPD/DO=THROTTLE=TERMINATE=SCRIPT=<pattern>

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
---------------
07-SEP-2024  MGD  extend skeleton-key functionality
04-APR-2024  MGD  /DO=ZERO=HTTP2
04-FEB-2023  MGD  bugfix; strzcpy(ControlBuffer,"AUTH=SKELKEY=0",ControlBufferSize);
10-OCT-2022  MGD  /DO=NOTICE=INVALID=<integer>
10-JUN-2022  MGD  InConfig..()
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=<integer> 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[=<host:port>] 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[=<host:port>] 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=<string> 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[=<integer>]
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=<string> 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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <lckdef.h>
#include <lkidef.h>
#include <prvdef.h>
#include <psldef.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* 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],
      ControlString [256],
      ControlUserName [13];
      
int  ControlStringSize = sizeof(ControlString),
     ControlBufferSize = sizeof(ControlBuffer);

/********************/
/* external storage */
/********************/

extern BOOL  CacheEnabled,
             HttpdServerExecuting,
             HttpdTicking,
             InstanceNodeSupervisor,
             OperateWithSysPrv,
             ProxyServingEnabled;

extern const int64  Delta01Sec,
                    Delta100mSec;

extern int  CliServerPort,
            ConfigNoticeInvalid,
            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 CONFIG_STRUCT  Config;
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 (!Command[0] || strsame (Command, CONTROL_HELP, -1))
   {
      ControlDoHelp (NULL);
      return (SS$_NORMAL);
   }

   if (strsame (Command, CONTROL_CONFIG_CHECK, -1))
   {
      status = InConfigInit (NULL);
      if (VMSnok (status) && status != SS$_NOLOGNAM) return (status);
      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);
      InConfigClose ();
      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 = InConfigInit (NULL);
      if (VMSnok (status) && status != SS$_NOLOGNAM) return (status);
      status = AuthConfigLoad (&mcptr);
      ControlCheck (mcptr, "auth", status);
      InConfigClose ();
      return (SS$_NORMAL);
   }
   if (strsame (Command, CONTROL_GLOBAL_CHECK, -1))
   {
      status = InConfigInit (NULL);
      if (VMSnok (status) && status != SS$_NOLOGNAM) return (status);
      status = ConfigLoad (&mcptr);
      ControlCheck (mcptr, "global", status);
      InConfigClose ();
      return (SS$_NORMAL);
   }
   if (strsame (Command, CONTROL_MAP_CHECK, -1))
   {
      status = InConfigInit (NULL);
      if (VMSnok (status) && status != SS$_NOLOGNAM) return (status);
      status = MapUrl_ConfigLoad (&mcptr);
      ControlCheck (mcptr, "map", status);
      InConfigClose ();
      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 = InConfigInit (NULL);
      if (VMSnok (status) && status != SS$_NOLOGNAM) return (status);
      status = ServiceConfigLoad (&mcptr);
      ControlCheck (mcptr, "service", status);
      InConfigClose ();
      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))) return (SS$_NORMAL);
   }

   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
)
{
   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);

   ControlString[0] = '\0';

   if (strsame (Command, CONTROL_NET_LIST, sizeof(CONTROL_NET_LIST)-1))
   {
      /* append the current terminal to the command */
      strzcpy (CommandRedux, Command, sizeof(CommandRedux));
      strzcat (CommandRedux, "=", sizeof(CommandRedux));
      strzcat (CommandRedux, getenv("SYS$OUTPUT"), sizeof(CommandRedux));
      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_ACCEPT_LOAD, 6) ||
         strsame (Command, CONTROL_ADHOC, sizeof(CONTROL_ADHOC)-1) ||
#if ALIGN_FAULT
         strsame (Command, CONTROL_ALIGN_FAULT,
                  sizeof(CONTROL_ALIGN_FAULT)-1) ||
#endif /* ALIGN_FAULT */
         strsame (Command, CONTROL_ACCEPT_REJECT, -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_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)) ||
         (check16 = strsame (Command, CONTROL_NOTE_SERVER,
                             sizeof(CONTROL_NOTE_SERVER)-1)) ||
         strsame (Command, CONTROL_NOTICE_INVALID,
                  sizeof(CONTROL_NOTICE_INVALID)-1) ||
         (check16 = strsame (Command, CONTROL_OPCOM,
                             sizeof(CONTROL_OPCOM)-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) ||
         (check16 = strsame (Command, CONTROL_REJECT_PURGE,
                             sizeof(CONTROL_REJECT_PURGE)-1)) ||
         strsame (Command, CONTROL_REJECT_LOAD, 6) ||
         /* must be before =<integer> */
         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_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) ||
         strsame (Command, CONTROL_WATCH_END, -1) ||
         strsame (Command, CONTROL_WATCH_FLUSH, -1) ||
         (check16 = strsame (Command, CONTROL_WEBSOCKET_DISCONNECT, clen)) ||
         strsame (Command, CONTROL_ZERO_HTTP2, -1) ||
         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)))
         strzcpy (ControlString, "!this directive is OBSOLETE",
                  sizeof(ControlString));
      else
         strzcpy (ControlString, "!directive not understood",
                  sizeof(ControlString));
   }

   if (!ControlString[0] && ControlDoAllHttpd)
      if (strsame (Command, CONTROL_SSL_PKPASSWD, -1))
         strzcpy (ControlString, "!cannot do this via /ALL",
                  sizeof(ControlString));

   if (!ControlString[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)) ||
           strsame (Command, CONTROL_OPCOM,
                    strlen(CONTROL_OPCOM))) &&
          strlen(Command) > 15)
         dlm820 = true;
      else
      if (plen)
         dlm820 = true;
      else
         dlm820 = false;

      if (dlm820)
         strzcpy (ControlString, "!requires VMS V8.2 DLM (clusterwide)",
                  sizeof(ControlString));
   }

   if (!ControlString[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 (ControlString, sizeof(ControlString), 0,
                      "\"!AZ\"; !UL instance!%s notified; !AZ",
                      Command64, ServerCount, ServerCount ? cptr : "?");
      else
      if (status == SS$_NOTQUEUED)
         FaoToBuffer (ControlString, sizeof(ControlString), 0,
            "!!command (en)queueing did not complete within !UL seconds",
                      CONTROL_WAIT_SECONDS);
      else
      if (status == SS$_TIMEOUT)
         FaoToBuffer (ControlString, sizeof(ControlString), 0,
            "!!command (en)queueing timed-out after !UL seconds",
                      CONTROL_WAIT_SECONDS);
      else
         FaoToBuffer (ControlString, sizeof(ControlString), 0,
                      "!!failed with status %X!8XL", status);

      if (cptr) VmFree (cptr, FI_LI);
   }

   return (ControlString);
}

/*****************************************************************************/
/*
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,
        PurgeMinutes = 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_ACCEPT_LOAD, 6))
   {
      if (sptr = NetRejectInit (CONTROL_ACCEPT_LOAD))
         FaoToStdout ("%HTTPD-W-NET, !20%D, !AZ\n", 0, sptr);
   } 
   else
   if (strsame (cptr, CONTROL_ACCEPT_REJECT, -1))
   {
      /* any buffered rejection notes flushed */
      NetRejectConnect (NULL, NULL);
      /* secial case from Server Admin menu */
      if (sptr = NetRejectInit (CONTROL_REJECT_LOAD))
         FaoToStdout ("%HTTPD-W-NET, !20%D, !AZ\n", 0, sptr);
      if (sptr = NetRejectInit (CONTROL_ACCEPT_LOAD))
         FaoToStdout ("%HTTPD-W-NET, !20%D, !AZ\n", 0, sptr);
   } 
   else
   if (strsame (cptr, CONTROL_ADHOC, sizeof(CONTROL_ADHOC)-1))
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr == '=') cptr++;
      ControlAdhoc (cptr);
   }
   else
#if ALIGN_FAULT
   if (strsame (cptr, CONTROL_ALIGN_FAULT, sizeof(CONTROL_ALIGN_FAULT)-1))
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr == '=') cptr++;
      HttpdAlignFault (cptr);
   }
   else
#endif /* ALIGN_FAULT */
   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))
   {
      if (InConfigStartup (true))
      {
         InConfigReload ();
         InConfigKeepAuth ();
         AuthConfigInit ();
         InConfigUnload ();
         InConfigStartup (false);
      }
      else
         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_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))
   {
      if (InConfigStartup (true))
      {
         InConfigReload ();
         InConfigKeepMap ();
         MapUrl_ControlReload ();
         InConfigUnload ();
         InConfigStartup (false);
      }
      else
         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++;
      strzcpy (buf, cptr, sizeof(buf));
      /* 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_ACCEPT_LOAD, -1))
   {
      if (sptr = NetRejectInit (CONTROL_ACCEPT_LOAD))
         FaoToStdout ("%HTTPD-W-NET, !20%D, !AZ\n", 0, sptr);
   } 
   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_NOTICE_INVALID, sizeof(CONTROL_NOTICE_INVALID)-1))
   {
      cptr += sizeof(CONTROL_NOTICE_INVALID)-1;
      if (*cptr == '=') cptr++;
      ConfigNoticeInvalid = atoi(cptr);
   } 
   else
   if (strsame (cptr, CONTROL_OPCOM, sizeof(CONTROL_OPCOM)-1))
      HttpdToOpcom (cptr+sizeof(CONTROL_OPCOM)-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
   if (strsame (cptr, CONTROL_REJECT_PURGE, sizeof(CONTROL_REJECT_PURGE)-1))
   {
      /* any buffered rejection notes flushed */
      NetRejectConnect (NULL, NULL);
      cptr += sizeof(CONTROL_REJECT_PURGE)-1;
      if (*cptr == '=') cptr++;
      /* default is to purge all entries */
      PurgeMinutes = atoi(cptr);
      NetRejectPurgeStatus (PurgeMinutes);
   }
   else
   if (strsame (cptr, CONTROL_REJECT_LOAD, 6))
   {
      /* any buffered rejection notes flushed */
      NetRejectConnect (NULL, NULL);
      if (sptr = NetRejectInit (CONTROL_REJECT_LOAD))
         FaoToStdout ("%HTTPD-W-NET, !20%D, !AZ\n", 0, sptr);
   } 
   else
   /* must be before =<integer> */
   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=<name> */
            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_WATCH_END, clen))
      WatchEnd ();
   else
   if (strsame (cptr, CONTROL_WATCH_FLUSH, clen)) {
      if (Watch.LogFileOut) fsync (fileno(Watch.LogFileOut));
   }
   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_HTTP2, clen))
      Http2ZeroAccounting ();
   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
   {
      strzcpy (ControlNodeName, "?", sizeof(ControlNodeName));
      strzcpy (ControlProcessName, "?", sizeof(ControlProcessName));
      strzcpy (ControlUserName, "?", sizeof(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=__<name>[=<vmsname>]:<psswd>[:<hours>]

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 <stdout> 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 <stdout> report is provided.
*/

int ControlAuthSkelKey
(
char *Command,
BOOL FromCli
)
{
   int  status, PeriodMinutes;
   char  *cptr, *sptr, *zptr;
   char  SkelKeyPassword [sizeof(HttpdGblSecPtr->AuthSkelKeyPassword)],
         SkelKeyUserName [sizeof(HttpdGblSecPtr->AuthSkelKeyUserName)],
         SkelKeyVmsName [sizeof(HttpdGblSecPtr->AuthSkelKeyVmsName)];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "ControlAuthSkelKey() !AZ",
                 Command);

   cptr = Command + sizeof(CONTROL_AUTH_SKELKEY)-1;
   if (!*cptr || *cptr == '0')
   {
      if (FromCli)
      {
         if (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
            FaoToStdout ("%HTTPD-W-SKELKEY, cancelled\n");
         else
            FaoToStdout ("%HTTPD-E-SKELKEY, not current\n");
      }
      AuthSkelKeyReset (false);
      return (SS$_NORMAL);
   }

   status = SS$_NORMAL;
   PeriodMinutes = 60;

   if (*(ushort*)cptr != '__')
   {
      FaoToStdout ("%HTTPD-E-SKELKEY, username must begin with two underscores\n");
      return (SS$_ABORT);
   }

   if (*cptr)
   {
      zptr = (sptr = SkelKeyUserName) + sizeof(SkelKeyUserName)-1;
      while (*cptr && *cptr != ':' && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (*cptr == '=')
      {
         cptr++;
         zptr = (sptr = SkelKeyVmsName) + sizeof(SkelKeyVmsName)-1;
         while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
      }
      if (*cptr == ':')
      {
         cptr++;
         zptr = (sptr = SkelKeyPassword) + sizeof(SkelKeyPassword)-1;
         while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
      }
      if (*cptr == ':')
         PeriodMinutes = atoi(cptr+1);

      if (FromCli)
      {
         if (strlen(SkelKeyUserName) < 8)
         {
            FaoToStdout ("%HTTPD-E-SKELKEY, username too short\n");
            status = SS$_ABORT;
         }
         if (strlen(SkelKeyPassword) < 8)
         {
            FaoToStdout ("%HTTPD-E-SKELKEY, password too short\n");
            status = SS$_ABORT;
         }
         if (PeriodMinutes < 0)
            PeriodMinutes = 60;
         else
         if (PeriodMinutes > 10080)  /* one week */
            PeriodMinutes = 10080;
         if (VMSnok (status))
            FaoToStdout ("%HTTPD-E-DO, __<username>[=<vms-username>]:<password>[:<minutes>]\n");
      }
   }

   if (VMSok (status))
   {
      /***************************/
      /* update the skeleton key */
      /***************************/

      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

      HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond = HttpdTickSecond +
                                                   (PeriodMinutes * 60);
      strzcpy (HttpdGblSecPtr->AuthSkelKeyUserName,
               SkelKeyUserName,
               sizeof(HttpdGblSecPtr->AuthSkelKeyUserName));
      strzcpy (HttpdGblSecPtr->AuthSkelKeyPassword,
               SkelKeyPassword,
               sizeof(HttpdGblSecPtr->AuthSkelKeyPassword));
      strzcpy (HttpdGblSecPtr->AuthSkelKeyVmsName,
               SkelKeyVmsName,
               sizeof(HttpdGblSecPtr->AuthSkelKeyVmsName));

      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);
}

/*****************************************************************************/

