/*****************************************************************************/
/*
                                  HTTPd.c

WASD VMS Hypertext Transfer Protocol daemon.


COPYRIGHT NOTICE
----------------
WASD VMS Hypertext Services, Copyright (C) 1996-2003 Mark G.Daniel.
(prior to 01-JUL-1997, "HFRD VMS Hypertext Services")

This package is free software; you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the Free Software 
Foundation; version 2 of the License, or any later version. 

This package is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
details. 

You should have received a copy of the GNU General Public License along with 
this package; if not, write to the Free Software Foundation, Inc., 675 Mass 
Ave, Cambridge, MA 02139, USA. 


PRIVILEGES REQUIRED
-------------------
These should be INSTALLed against the image.

ALTPRI   Allows the server account to raise it's prioity above 4 if enabled by
         the /PRIORITY= qualifier.

CMKRNL   Required for single use of $GRANTID system service in DCL.C module.
         This rights identifier is used to mark detached WASD script processes.

DETACH   (IMPERSONATE) Allows the server to impersonate specific accounts
         for scripting and startup purposes.

PRMGBL   Create the permanent global section used by the HTTPDMON utility.

PRMMBX   Used by the DCL scripting module to create permanent mailboxes

PSWAPM   Allows the server process to prevent itself from being swapped out if 
         enabled by the /[NO]SWAP qualifier.

SHMEM    Allow shared section memory between multiple processors
         (VAX only ,for access to the global section used by HTTPDMON).

SYSGBL   Create the system global section used by the HTTPDMON utility.

SYSLCK   Allows SYSTEM locks to be enqueued for sending commands to all
         server processes on a node/cluster (via /DO=/ALL or Admin Menu).

SYSPRV   Used for various purposes, including creating sockets within the
         privileged port range (1-1023, which includes port 80 of course),
         accessing configuration files (which can be protected from world
         access), enable AUTHORIZED write access to the file system, checking
         authentication details, amongst others.

SYSNAM   Is actually not required with version 8.n and later.

WORLD    Allows some functions to obtain information about and affect 
         processes (e.g. scripts) that do not belong to the server process.


QUALIFIERS  (implemented in the CLI.c module)
----------
/ACCEPT=                comma separated list of accepted hosts/domains
/AUTHORIZE=[SSL|ALL]    authorization may only be used with "https:",
                        all paths must be authorized
/CGI_PREFIX=            prefix to CGI-script variable (symbol) names
/CLUSTER[=integer]      /do= this to all server processes on node/cluster
/DBUG                   turn on all "if (Debug)" statements (if compiled DBUG)
/DEMO                   demonstration mode 
/DETACH=                DCL procedure to be run as /USER=
/DO=                    command to be passed to server
/FILBUF=                number of bytes in record buffer
/FORMAT=                log record format
/GBLSEC=DELETE          delete the permanent global section (with /INSTANCE=)
/IDENT=                 VMS rights identifier name used for detached processes
/INSTANCES=integer|CPU  maximum number of per-node servers
/NOINSTANCES            suppresses the server's desire to $CREPRC new instances
/[NO]LOG[=]             logging enabled/disabled, optional log file name
/[NO]MONITOR            monitor enabled/disabled
/[NO]NETWORK            explicitly enables/disables global network mode
/NETBUF=                number of bytes in network read buffer
/[NO]ODS5               explicit extended file specification (testing only)
/OUTBUF=                number of bytes in output buffer
/PERIOD=                log file period
/PERSONA[=ident]        enable PERSONA services for DCL scripting,
                        optional identifier name controlling persona access
/PERSONA[=AUTHORIZED]   enable persona only if request subject to authorization
/PERSONA[=RELAXED]      optional keyword to allow privileged accounts to script
/PERSONA[=RELAXED=AUTHORIZED] optional keyword to allow privileged accounts
                              to script if request subject to authorization
/PORT=                  server IP port number (overriden by [service] config)
/PRIORITY=              process priority
/[NO]PROFILE            allow/disallow SYSUAF-authenticated access control
/PROFILE=NORULE         SYSUAF profile withput auth rule (pre-8.1 behaviour)
/[NO]PROMISCUOUS[=pwd]  authenticates any user name for path authorization
                        (optional password required for authorization) 
/REJECT=                comma separated list of rejected hosts/domains
/SCRIPT=AS=             detached scripting using this persona
                        (keyword SUBPROCESS forces subprocess scripting)
/SERVICES=              list of [host-name:]port providing HTTP service
/SOFTWAREID=            overrides the server's software ID string
/SYSPRV                 normally operate with SYSPRV enabled (CAUTION!)
/[NO]SSL=               enables Secure Sockets Layer, sets protocol parameters
/[NO]SYSUAF[=ID,PROXY,RELAXED,SSL]
                        allow/disallow SYSUAF authentication, or
                        SYSUAF authentication is allowed by identifier,
                        proxy SYSUAF authentication is allowed,
                        SYSUAF authentication allowed with any current account,
                        SYSUAF authentication is only allowed with "https:"
/[NO]SWAP               allow/disallow process to be swapped out
/USER=                  set the username (account) the server executes under
/VERSION                simply display the server version
/[NO]ZERO               accounting zeroed on startup (default is non-zeroed)


VERSION HISTORY
---------------
See VERSION.H
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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 <devdef.h>
#include <iodef.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <libclidef.h>
#include <lnmdef.h>
#include <prvdef.h>
#include <prcdef.h>
#include <psldef.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>
#include <uaidef.h>

/* application-related header files */
#include "wasd.h"
#include "copyright.h"

/* a control-y turned into a warning */
#define SS_W_CONTROLY (SS$_CONTROLY & 0xfffffffe)

#define WASD_MODULE "HTTPD"

/******************/
/* global storage */
/******************/

/*
   These are the required privileges of the executing HTTP server.
   The server ACCOUNT should only have TMPMBX and NETMBX (just for
   extra security, policy ... keep privileged accounts to a minimum).
   Script processes are created only with the process's current privileges,
   which are always maintained at TMPMBX and NETMBX.  If additional
   privileges are required for any particular purpose (e.g. binding to
   a privileged IP port) then they are enabled, the action performed,
   and then they are disabled again immediately.
*/
unsigned long  AltPriMask [2] = { PRV$M_ALTPRI, 0 },
               AveJoePrvMask [2] = { PRV$M_NETMBX | PRV$M_TMPMBX, 0 },
               CrePrcMask [2] = { PRV$M_DETACH | PRV$M_SYSPRV, 0 },
               DetachMask [2] = { PRV$M_DETACH, 0 },
#ifdef __VAX
               GblSecPrvMask [2] = { PRV$M_SYSPRV | PRV$M_SYSGBL |
                                     PRV$M_PRMGBL | PRV$M_SHMEM, 0 },
#else  /* Alpha or ia64 */
               GblSecPrvMask [2] = { PRV$M_SYSPRV | PRV$M_SYSGBL |
                                     PRV$M_PRMGBL, 0 },
#endif /* #ifdef VAX */
               GrantIdMask [2] = { PRV$M_CMKRNL | PRV$M_WORLD, 0 },
               MailboxMask [2] = { PRV$M_WORLD | PRV$M_SYSPRV, 0 },
               PrivAcctMask [2] = { PRV$M_SETPRV | PRV$M_SYSPRV, 0 },
               PswapmMask [2] = { PRV$M_PSWAPM, 0 },
               ResetPrvMask [2] = { 0xffffffff, 0xffffffff },
               SysLckMask [2] = { PRV$M_SYSLCK, 0 },
               SysPrvMask [2] = { PRV$M_SYSPRV, 0 },
               WorldMask [2] = { PRV$M_WORLD, 0 };

char  Utility [] = "HTTPD";

/*
   Decided to be able to eliminate the debug statements from production
   executables completely.  This will eliminate some largely unused code
   from the images reducing the overall file size, but more importantly
   will eliminate the test and branch execution overhead each time a debug
   statement is encountered.  Do this by conditionally turning the integer
   Debug storage into a constant false value ... compiler optimization of
   an impossible-to-execute section of code does the rest!  Very little
   other code needs to be explicitly conditionally compiled.
*/
#ifdef DBUG
BOOL  Debug;
#else
#define Debug 0
#endif

BOOL  HttpdNetworkMode,
      HttpdServerExecuting,
      HttpdServerStartup,
      HttpdTicking,
      MonitorEnabled,
      NaturalLanguageEnglish,
      NoSwapOut = true,
      OperateWithSysPrv;

int  EfnWait,
     EfnNoWait,
     ExitStatus,
     FileBufferSize = DEFAULT_FILE_BUFFER_SIZE,
     GblSectionCount,
     GblSectionPermCount,
     GblPageCount,
     GblPagePermCount,
     HttpdDayOfWeek,
     HttpdTickSecond,
     HttpdTickPrevSecond,
     ProcessPriority = 4,
     ServerPort = 80;

unsigned long  HttpdBinTime [2],
               HttpdStartBinTime [2];

unsigned short  HttpdNumTime [7];

char  HttpdScriptAsUserName [64],
      NaturalLanguage [64],
      ProcessIdentName [32] = "WASD_",
      ServerPortString [8];

/* zero index is used to indicate request is not in a list */
#define SUPERVISOR_LIST_MAX 10
SUPERVISOR_LIST  SupervisorListArray [SUPERVISOR_LIST_MAX+1] =
/* these numbers are staggered so they are not all scanned at any one time */
   { { {0,0,0}, 0, },
     { {0,0,0}, 1, },
     { {0,0,0}, 15, },
     { {0,0,0}, 50, },
     { {0,0,0}, 110, },
     { {0,0,0}, 290, },
     { {0,0,0}, 590, },
     { {0,0,0}, 1100, },
     { {0,0,0}, 3500, },
     { {0,0,0}, 7100, },
     { {0,0,0}, 999999999, } };

struct AnExitHandler  ExitHandler;

ACCOUNTING_STRUCT  *AccountingPtr;
BOOL  AccountingZeroOnStartup;

SYS_INFO  SysInfo;
HTTPD_PROCESS  HttpdProcess;

/* default storage in case the real global section cannot be/is not in use */
HTTPD_GBLSEC  HttpdGblSecDefault;
HTTPD_GBLSEC  *HttpdGblSecPtr;
int  HttpdGblSecPages;

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

extern BOOL  AuthorizationEnabled,
             AuthPromiscuous,
             CliDetach,
             CliDemo,
             CliGblSecDelete,
             CliGblSecNoPerm,
             CliLoggingDisabled,
             CliLoggingEnabled,
             CliMonitorDisabled,
             CliMonitorEnabled,
             CliNetworkMode,
             CliNoNetworkMode,
             CliInstanceNoCrePrc,
             CliOdsExtendedEnabled,
             CliOdsExtendedDisabled,
             CliTests,
             CliVersion,
             ControlDoAllHttpd,
             DclScriptDetachProcess,
             LoggingEnabled,
             OdsExtended,
             ProtocolHttpsAvailable,
             ProtocolHttpsConfigured,
             ProxyServingEnabled,
             WatchCliNoStartup;

extern int  CliServerPort,
            HttpdGblSecVersion,
            InstanceConcurrentMax,
            InstanceGroupNumber,
            OpcomMessages,
            RequestHistoryMax;

extern char  BuildInfo[],
             CliLogFileName[],
             CliParameter[],
             CliProcessIdentName[],
             CliProxyMaint[],
             CliScriptAs[],
             CliServices[],
             CliUserName[],
             CommandLine[],
             ControlBuffer[],
             ErrorSanityCheck[],
             HttpdIpPackage[],
             HttpdSesola[],
             HttpdVersion[],
             LoggingFileName[],
             Services[],
             ServerHostPort[],
             SoftwareID[],
             TcpIpAgentInfo[],
             TimeGmtString[];

extern CONFIG_STRUCT  Config;
extern LIST_HEAD  RequestList;
extern META_CONFIG  *MetaGlobalConfigPtr;
extern META_CONFIG  *MetaGlobalServicePtr;
extern META_CONFIG  *MetaGlobalMsgPtr;
extern PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;
extern WATCH_STRUCT  Watch,
                     WatchCli;

/* for initial debugging only */
extern unsigned long  VmCacheVmZoneId,
                      VmGeneralVmZoneId,
                      VmRequestVmZoneId;

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

int main ()

{
   int  status,
        GblSecDeleteCount;
   char  *cptr, *sptr, *zptr;
   $DESCRIPTOR (NaturalLanguageDsc, NaturalLanguage);

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

   /* initialize general-use virtual memory management */
   VmGeneralInit ();
   /** if (Debug) VmDebug (VmGeneralVmZoneId); **/

   /* no global section (yet) gotta have the storage somewhere! */
   HttpdGblSecPtr = &HttpdGblSecDefault;
   HttpdGblSecPages = sizeof(HTTPD_GBLSEC) / 512;
   if (HttpdGblSecPages & 0x1ff) HttpdGblSecPages++;
   AccountingPtr = &HttpdGblSecPtr->Accounting;
   ProxyAccountingPtr = &HttpdGblSecPtr->ProxyAccounting;

   if (VMSnok (status = ParseCommandLine ()))
      exit (status);

#ifdef DBUG
   if (!Debug)
      if ((char*)getenv ("HTTPD$DBUG"))
         Debug = true;
#endif

   /* if (any) WATCHing of the startup was NOT suppressed activate now */
   if (!WatchCliNoStartup)
   {
      WatchCliSettings ();
      memcpy (&Watch, &WatchCli, sizeof(WATCH_STRUCT));
   }

   /* get required server system information */
   HttpdSystemInfo ();

   /* get required server process information */
   HttpdProcessInfo ();

   if (!HttpdProcess.PrivilegedAccount)
   {
      /*********************************************************/
      /* things we're not allowed to do without account SYSPRV */
      /*********************************************************/

      if (AuthPromiscuous)      exit (SS$_NOSYSPRV);
      if (CliDemo)              exit (SS$_NOSYSPRV);
      if (CliDetach)            exit (SS$_NOSYSPRV);
      if (CliGblSecDelete)      exit (SS$_NOSYSPRV);
      if (CliParameter[0])      exit (SS$_NOSYSPRV);
      if (CliServices[0])       exit (SS$_NOSYSPRV);
      if (CliUserName[0])       exit (SS$_NOSYSPRV);
      if (ControlBuffer[0])     exit (SS$_NOSYSPRV);
   }

   if (CliTests)
   {
      /*********/
      /* tests */
      /*********/

#if WATCH_MOD
{
   IPADDRESS  IpAddress;
   status = TcpIpStringToAddress (CliParameter, &IpAddress);
   exit (status);
}
#else
      WriteFaoStdout ("%!AZ-W-TESTS, NOT a compiled option\n", Utility);
#endif /* WATCH_MOD */
      exit (SS$_NORMAL); 
   }

   if (CliDemo)
   {
      /*************/
      /* demo mode */
      /*************/

      /* with 'instances' demonstration mode was becoming a little complex */
      CliLoggingEnabled = false;
      AuthPromiscuous = CliInstanceNoCrePrc =
         CliLoggingDisabled = CliGblSecNoPerm = true;
      InstanceGroupNumber = DEMO_INSTANCE_GROUP_NUMBER;
      if (!CliServices[0])
      {
         strcpy (CliServices, "http://*:7080");
         if (ProtocolHttpsAvailable) strcat (CliServices, ",https://*:7443");
      }
   }

   /* initialize the resources and locks for multi-instance processing */
   InstanceLockInit ();

   if (ControlBuffer[0])
   {
      /*****************************/
      /* control the HTTPd process */
      /*****************************/

      exit (ControlCommand (ControlBuffer));
   }

   if (CliDetach)
   {
      /****************************/
      /* create a detached server */
      /****************************/

      exit (HttpdDetachServerProcess (NULL));
   }
   
   if (CliProxyMaint[0])
   {
      /**********************/
      /* proxy maintainance */
      /**********************/

      exit (ProxyMaintCli ());
   }

   if (CliGblSecDelete)
   {
      /**************************/
      /* delete global sections */
      /**************************/

      /* permanent ones, that is */
      GblSecDeleteCount = 0;
      status = HttpdGblSecInit ();
      if (VMSok (status)) GblSecDeleteCount++;
      status = GraphActivityGblSecInit ();
      if (VMSok (status)) GblSecDeleteCount++;
      WriteFaoStdout ("%!AZ-!AZ-GBLSEC, deleted !UL global sections\n",
                      Utility, GblSecDeleteCount ? "I" : "W",
                      GblSecDeleteCount);
      exit (SS$_NORMAL); 
   }

   /* output some application version information */
   VersionInfo ();

   /* output the GNU GENERAL PUBLIC LICENSE message */
   WriteFaoStdout ("%!AZ-I-SOFTWAREID, !AZ\n!AZ",
                   Utility, SoftwareID, CopyRightMessageBrief);

   if (CliVersion) exit (SS$_NORMAL); 

   /****************/
   /* HTTPd server */
   /****************/

   /* initialize the server timestamps */
   HttpdTick (-1);

   HttpdServerExecuting = HttpdServerStartup = true;

   WriteFaoStdout ("%!AZ-I-STARTUP, !20%D\n", Utility, &HttpdStartBinTime);

   WriteFaoStdout ("%!AZ-I-SYSTEM, !AZ VMS !AZ\n",
                   Utility, SysInfo.HwName, SysInfo.Version);

   if (HttpdProcess.Grp <= SysInfo.MaxSysGroup)
      WriteFaoStdout (
"%!AZ-W-SYSPRV, operating with implicit SYSPRV (UIC group !UL)\n",
         Utility, HttpdProcess.Grp);

   /* get the TCP/IP agent information */
   TcpIpSetAgentInfo ();
   WriteFaoStdout ("%!AZ-I-TCPIP, !AZ\n", Utility, TcpIpAgentInfo);

   switch (HttpdProcess.Mode)
   {
      case JPI$K_INTERACTIVE : HttpdProcess.ModeName = "INTERACTIVE"; break;
      case JPI$K_NETWORK     : HttpdProcess.ModeName = "NETWORK"; break;
      case JPI$K_OTHER       : HttpdProcess.ModeName = "OTHER"; break;
      case JPI$K_BATCH       : HttpdProcess.ModeName = "BATCH"; break;
      default : HttpdProcess.ModeName = "?";
   }
   WriteFaoStdout ("%!AZ-I-MODE, !AZ\n", Utility, HttpdProcess.ModeName);
   if (HttpdProcess.Mode == JPI$K_NETWORK) HttpdNetworkMode = true;
   /* the server process' detached scripts can still have mode overridden */
   if (CliNetworkMode) HttpdNetworkMode = true;
   if (CliNoNetworkMode) HttpdNetworkMode = false;

   /* set the flag and report indicating whether ODS-5 is supported */
   OdsSetExtended ();
   /* (testing only) */
   if (CliOdsExtendedEnabled) OdsExtended = true;
   if (CliOdsExtendedDisabled) OdsExtended = false;

   /* ensure the GMT time/logical is available */
   if (VMSnok (status = TimeSetGMT ()))
      ErrorExitVmsStatus (status, "TimeSetGMT()", FI_LI);
   WriteFaoStdout ("%!AZ-I-GMT, !AZ\n", Utility, TimeGmtString);

   /* set up and declare the exit handler */
   ExitHandler.HandlerAddress = &HttpdExit;
   ExitHandler.ArgCount = 1;
   ExitHandler.ExitStatusPtr = &ExitStatus;
   if (VMSnok (status = sys$dclexh (&ExitHandler)))
      ErrorExitVmsStatus (status, "sys$dclexh()", FI_LI);
   HttpdOnControlY (false);

   /* join per-node and per-cluster instances (the earlier the better) */
   InstanceServerInit ();

   /* make sure the process' privileges are those of a mere mortal */
   if (VMSnok (status = sys$setprv (0, &ResetPrvMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
   if (VMSnok (status = sys$setprv (1, &AveJoePrvMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

   if (OperateWithSysPrv && OPERATE_WITH_SYSPRV)
   {
      /* looks like we're been asked to use Superman's Xray vision! */
      if (VMSnok (status = sys$setprv (1, &SysPrvMask, 0, 0)))
         ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

      /* OK, now reset the SYSPRV bit so it isn't manipulated */
      CrePrcMask[0] &= ~PRV$M_SYSPRV;
      GblSecPrvMask[0] &= ~PRV$M_SYSPRV;
      MailboxMask[0] &= ~PRV$M_SYSPRV;
      SysPrvMask[0] &= ~PRV$M_SYSPRV;

      WriteFaoStdout ("%!AZ-I-SYSPRV, operating with SYSPRV\n", Utility);
   }
   else
   if (OperateWithSysPrv)
   {
      OperateWithSysPrv = false;
      WriteFaoStdout ("%!AZ-W-SYSPRV, is NOT a compiled option\n", Utility);
   }

   /* set (a sensible) process priority */
   if (ProcessPriority > 6) ProcessPriority = 6;
   if (VMSnok (status = sys$setprv (1, &AltPriMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
   if (VMSnok (status = sys$setpri (0, 0, ProcessPriority, 0, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setpri()", FI_LI);
   if (VMSnok (status = sys$setprv (0, &AltPriMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

   /* anywhere near before starting logging! */
   NetGetServerHostName ();

   /* read the server configuration file */
   ConfigLoad (&MetaGlobalConfigPtr);

   /* a blast from the past */
   if (CliServerPort)
      ServerPort = CliServerPort;
   else
   if (Config.cfServer.DefaultPort)
      ServerPort = Config.cfServer.DefaultPort;
   if (ServerPort < 1 || ServerPort > 65535)
      ErrorExitVmsStatus (0, "IP port", FI_LI);
   sprintf (ServerPortString, "%d", ServerPort);

   /* set/reset global section used by CLI control and the HTTPMON utility */
   HttpdGblSecInit ();

   /* make any adjustments required after configuration load */
   InstanceFinalInit ();

   /* initialize Secure Sockets Layer, if available and if required */
   SesolaInit ();

   /* get the configured services */
   ServiceConfigLoad (&MetaGlobalServicePtr);

   /* set this instance's process name */
   InstanceProcessName ();

   {
      /* don't use WASD functions to write into the locked areas */
      $DESCRIPTOR (FaoDsc, "%!AZ-I-STARTUP, !20%D, !AZ\0");
      $DESCRIPTOR (StringDsc, ""); 

      StringDsc.dsc$w_length = sizeof(HttpdGblSecPtr->StatusMessage)-1;
      StringDsc.dsc$a_pointer = HttpdGblSecPtr->StatusMessage;
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      sys$fao (&FaoDsc, NULL, &StringDsc, Utility, 0, HttpdProcess.PrcNam);
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }

   /* note the process name has to be set before OPCOM can be used */
   if (OpcomMessages)
      WriteFaoOpcom ("%!AZ-I-STARTUP, !AZ", Utility, SoftwareID);

   /* update (reset) the request information */
   if (MonitorEnabled) RequestGblSecUpdate (NULL);

   /* now that we have the accounting data (in the global section) */
   if (AccountingZeroOnStartup) ControlZeroAccounting ();

   /* set up a rights identifier name (currently for detached scripting) */
   zptr = (sptr = ProcessIdentName) + sizeof(ProcessIdentName)-1;
   if (CliProcessIdentName[0])
      for (cptr = CliProcessIdentName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   else
   {
      /* server-set rights identifiers begin with the following */
      for (cptr = "WASD_"; *cptr; *sptr++ = *cptr++);
      for (cptr = HttpdProcess.PrcNam; *cptr && sptr < zptr; cptr++)
         if (isalnum(*cptr)) *sptr++ = toupper(*cptr); else *sptr++ = '_';
   }
   *sptr = '\0';
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!&Z", ProcessIdentName);

   if (CliMonitorDisabled)
      MonitorEnabled = false;
   else
   if (Config.cfMisc.MonitorEnabled || CliMonitorEnabled)
      MonitorEnabled = true;
   else
      MonitorEnabled = false;

   /* initialize message database, check contents of some of those messages */
   MsgConfigLoad (&MetaGlobalMsgPtr);
   ErrorCheckReportFormats ();

   /* authentication/authorization configuration */
   AuthConfigInit ();

   /* load the rule mapping database */
   MapUrl_Load ();

   /* initialize proxy processing */
   ProxyInit ();

   /* if required initialize the TCP/IP host name/address cache */
   if (Config.cfMisc.DnsLookupClient || ProxyServingEnabled) TcpIpHostCacheInit ();

   /* check for and if necessary enable the default scripting account */
   HttpdScriptAs ();

   /* initialize DCL processing */
   DclInit ();

   /* initialize request virtual memory management */
   VmRequestInit (InstanceConcurrentMax); 
   /** if (Debug) VmDebug (VmRequestVmZoneId); **/

   /* initialize file cache  */
   CacheInit (true);
   /** if (Debug) VmDebug (VmCacheVmZoneId); **/
   /** if (Debug) VmDebug (0); **/

   /* initialize activity statistics, record server event */
   GraphActivityGblSecInit ();
   GraphActivityEvent (ACTIVITY_STARTUP);

   /* initialize the language environment */
   if (VMSok (status = lib$get_users_language (&NaturalLanguageDsc)))
   {
      for (cptr = NaturalLanguage; *cptr && !ISLWS(*cptr); cptr++);
      *cptr = '\0';
      if (strsame (NaturalLanguage, "ENGLISH", -1))
         NaturalLanguageEnglish = true;
      else
      {
         NaturalLanguageEnglish = false;
         fprintf (stdout, "%%%s-I-LANGUAGE, natural language is %s\n",
                  Utility, NaturalLanguage);
      }
   }
   else
   {
      if (status == LIB$_ENGLUSED)
      {
         NaturalLanguageEnglish = true;
         strcpy (NaturalLanguage, "ENGLISH");
         fprintf (stdout,
            "%%%s-W-LANGUAGE, natural language has defaulted to %s\n",
            Utility, NaturalLanguage);
      }
      else
         ErrorExitVmsStatus (status, "lib$get_users_language()", FI_LI);
   }

   /* set up for the HTTPD to be controlled via DLM */
   ControlInit ();

   /* initialize request history mechanism (ensure it's reasonable!) */
   RequestHistoryMax = Config.cfMisc.RequestHistory;
   if (RequestHistoryMax > 999) RequestHistoryMax = 0;

   /* disable process swapping */
   if (NoSwapOut)
   {
      if (VMSnok (status = sys$setprv (1, &PswapmMask, 0, 0)))
         ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
      if (VMSnok (status = sys$setswm (1)))
         ErrorExitVmsStatus (status, "sys$setswm()", FI_LI);
      if (VMSnok (status = sys$setprv (0, &PswapmMask, 0, 0)))
         ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
   }

   /* disable AST's until we've created the listening sockets */
   sys$setast (0);

   /* need SYSPRV to bind to a well-known port */
   sys$setprv (1, &SysPrvMask, 0, 0);

   /* create network services */
   NetCreateService ();

   sys$setprv (0, &SysPrvMask, 0, 0);

   /* get the available BYTLM quota after all server sockets created */
   HttpdProcess.BytLmAvailable = GetJpiBytLm();

   /* initialize logging after service creation (in case of per-service logs) */
   if (VMSnok (status = Logging (NULL, LOGGING_BEGIN)))
      ErrorExitVmsStatus (status, "Logging()", FI_LI);

   if (CliDemo)
   {
      /*************/
      /* demo mode */
      /*************/

      WriteFaoStdout (
"%!AZ-I-DEMO, demonstration mode\n\
1.i subprocess scripting\n\
2.i promiscuous authentication\n\
3.i directory access control files ignored\n\
4.i [DirAccess] enabled\n\
5.i [DirMetaInfo] enabled\n\
6.i [DirWildcard] enabled\n\
7.i [Logging] disabled\n\
8.i [ReportBasicOnly] disabled\n\
9.i [ReportMetaInfo] enabled\n",
         Utility);

      /*
         Subprocess scripting is forced in DCL.C
         Logging is disabled and promiscuous authentication enabled
         in the previous if(CliDemo) section.
      */
      Config.cfReport.BasicOnly = false;
      Config.cfReport.MetaInfoEnabled = true;
      Config.cfDir.Access = true;
      Config.cfDir.AccessSelective = false;
      Config.cfDir.MetaInfoEnabled = true;
      Config.cfDir.WildcardEnabled = true;
   }

   /*****************************/
   /* begin to process requests */
   /*****************************/

   HttpdServerStartup = false;

   /* cancel any startup messages provided for the monitor */
   HttpdGblSecPtr->StatusMessage[0] = '\0';

   WriteFaoStdout ("%!AZ-I-BEGIN, !20%D, accepting requests\n", Utility, 0);

   /* ready to accept requests */
   InstanceReady ();

   /* kick off ticking to initiate any supervisory activities */
   if (!HttpdTicking) HttpdTick (0);

   /* if WATCHing of the startup was suppressed activate from here-on */
   if (WatchCliNoStartup)
   {
      WatchCliSettings ();
      memcpy (&Watch, &WatchCli, sizeof(WATCH_STRUCT));
   }

   /* reenable user-mode ASTs to allow use of our services */
   sys$setast (1);

   /*
      Just set and wait!
      Well that's the way it should be anyway.  BUT ...
      Whenever $GRANTID() is used in DclSysCommandAst() this $HIBER()
      (very) occasionally returns!!  Apparently $GRANTID works by queuing
      a kernel mode AST to the target process with setimr, wake, and cantimr
      involved.  This leaves windows for possible spurious wakes.
      Just keep track of these for interest' sake!
   */
   for (;;)
   {
      sys$hiber ();
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      AccountingPtr->SpuriousWakeCount++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }
   exit (SS$_BUGCHECK);
}

/*****************************************************************************/
/*
Get required system information.
*/

HttpdSystemInfo ()

{
   static $DESCRIPTOR (DECnetDeviceDsc, "_NET:");
   static $DESCRIPTOR (NetStartupStatusDsc, "NET$STARTUP_STATUS");
   static $DESCRIPTOR (LnmSystemTableDsc, "LNM$SYSTEM_TABLE");

   static unsigned short  Length;
   static char  DECnetScratch [32];
   unsigned short  DECnetChannel;
   static VMS_ITEM_LIST3
   DECnetLnmItem [] =
   {
      { sizeof(DECnetScratch), LNM$_STRING, NULL, 0 },
      { 0,0,0,0 }
   };

   static VMS_ITEM_LIST3
   SyiItem [] =
   {
     { sizeof(SysInfo.AvailCpuCnt), SYI$_AVAILCPU_CNT,
       &SysInfo.AvailCpuCnt, 0 },
     { sizeof(SysInfo.HwName)-1, SYI$_HW_NAME,
       &SysInfo.HwName, &SysInfo.HwNameLength },
     { sizeof(SysInfo.MaxSysGroup), SYI$_MAXSYSGROUP,
       &SysInfo.MaxSysGroup, 0 },
     { sizeof(SysInfo.MemSize)-1, SYI$_MEMSIZE, &SysInfo.MemSize, 0 },
     { sizeof(SysInfo.PageSize)-1, SYI$_PAGE_SIZE, &SysInfo.PageSize, 0 },
     { sizeof(SysInfo.NodeName)-1, SYI$_NODENAME,
       &SysInfo.NodeName, &SysInfo.NodeNameLength },
     { sizeof(SysInfo.Version)-1, SYI$_VERSION, &SysInfo.Version, 0 },
     { sizeof(SysInfo.BootBinTime), SYI$_BOOTTIME, &SysInfo.BootBinTime, 0 },
     { 0,0,0,0 }
   };

   int  status;
   char  *cptr;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdSystemInfo()");

   /* get system information */
   status = sys$getsyiw (EfnWait, 0, 0, &SyiItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$getsyiw()", FI_LI);

   if (cptr = getenv("WASD_VMS_VERSION"))
      strncpy (SysInfo.Version, cptr, sizeof(SysInfo.Version));

   SysInfo.HwName[SysInfo.HwNameLength] = '\0';
   SysInfo.NodeName[SysInfo.NodeNameLength] = '\0';
   SysInfo.Version[sizeof(SysInfo.Version)-1] = '\0';
   for (cptr = SysInfo.Version; *cptr && *cptr != ' '; cptr++);
   *cptr = '\0';
   if (SysInfo.Version[0] == 'V' &&
       isdigit(SysInfo.Version[1]) &&
       SysInfo.Version[2] == '.' &&
       isdigit(SysInfo.Version[3]))
   {
      /* e.g. "V7.3" */
      SysInfo.VersionInteger = ((SysInfo.Version[1]-48) * 100) +
                               ((SysInfo.Version[3]-48) * 10);
      /* if something like "V7.3-2" */
      if (SysInfo.Version[4] == '-')
         SysInfo.VersionInteger += SysInfo.Version[5]-48;
   }
   else
   {
      WriteFaoStdout (
"%!AZ-E-VMS, cannot understand VMS version string \"!AZ\"\n\
-!AZ-I-KLUDGE, continue by using WASD_VMS_VERSION environment variable\n",
                      Utility, SysInfo.Version, Utility);
      exit (SS$_BUGCHECK);
   }

   /* the number of VAX pages supported in an architecture page */
   SysInfo.PageFactor = SysInfo.PageSize / 512;
#ifndef __VAX
   if (SysInfo.PageFactor >= 16)
      /* q&d - try and cater for systems with up to 64GB memory */
      SysInfo.MemoryMB = SysInfo.MemSize * (SysInfo.PageFactor / 16) / 128;
   else
#endif
      SysInfo.MemoryMB = SysInfo.MemSize * SysInfo.PageFactor / 2048;

   /* versions earlier than 7.0 do not support EFN$C_ENF */
   if (SysInfo.VersionInteger >= 700)
      EfnWait = EfnNoWait = EFN$C_ENF;
   else
   {
      /* and require a unique event flag number */
      if (VMSnok (status = lib$get_ef (&EfnWait)))
         ErrorExitVmsStatus (status, "lib$get_ef()", FI_LI);
      if (VMSnok (status = lib$get_ef (&EfnNoWait)))
         ErrorExitVmsStatus (status, "lib$get_ef()", FI_LI);
   }

   /* establish whether DECnet is running and guess it's version */
   status = sys$assign (&DECnetDeviceDsc, &DECnetChannel, 0, 0);
   if (VMSok (status))
   {
      sys$dassgn (DECnetChannel);
      status = sys$trnlnm (0, &LnmSystemTableDsc, &NetStartupStatusDsc,
                           0, &DECnetLnmItem);
      if (VMSok (status))
         SysInfo.DECnetVersion = 5;
      else
         SysInfo.DECnetVersion = 4;
   }
   else
      SysInfo.DECnetVersion = 0;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
"!&Z !&Z !&Z ver:!UL MB:!UL cpu:!UL DECnet:!UL EfnWait:!UL EfnNoWait:!UL",
                 SysInfo.NodeName, SysInfo.HwName, SysInfo.Version,
                 SysInfo.VersionInteger, SysInfo.MemoryMB,
                 SysInfo.AvailCpuCnt, SysInfo.DECnetVersion,
                 EfnWait, EfnNoWait);
}

/*****************************************************************************/
/*
Gets required current process' information/characteristics.  Translates the
server's SYS$OUTPUT process logical name, $DISPLAYs it to get the file name (if
any), checks it's a file, sets the name in storage.
*/

HttpdProcessInfo ()

{
   static unsigned short  Length;
   static char  LogValue [64];
   static $DESCRIPTOR (LnmProcessDsc, "LNM$PROCESS");
   static $DESCRIPTOR (SysOutputDsc, "SYS$OUTPUT");
   static $DESCRIPTOR (SysInputDsc, "SYS$INPUT");

   static VMS_ITEM_LIST3
   JpiItems [] =
   {
     { sizeof(HttpdProcess.Pid), JPI$_PID, &HttpdProcess.Pid, 0 },
     { sizeof(HttpdProcess.Uic), JPI$_UIC, &HttpdProcess.Uic, 0 },
     { sizeof(HttpdProcess.Mode), JPI$_MODE, &HttpdProcess.Mode, 0 },
     { sizeof(HttpdProcess.AuthPriv), JPI$_AUTHPRIV,
       &HttpdProcess.AuthPriv, 0 },
     { sizeof(HttpdProcess.UserName)-1, JPI$_USERNAME,
       &HttpdProcess.UserName, 0 },
     { sizeof(HttpdProcess.PrcNam)-1, JPI$_PRCNAM,
       &HttpdProcess.PrcNam, &Length },
     { sizeof(HttpdProcess.AstLm), JPI$_ASTLM, &HttpdProcess.AstLm, 0 },
     { sizeof(HttpdProcess.BioLm), JPI$_BIOLM, &HttpdProcess.BioLm, 0 },
     { sizeof(HttpdProcess.BytLm), JPI$_BYTLM, &HttpdProcess.BytLm, 0 },
     { sizeof(HttpdProcess.DioLm), JPI$_DIOLM, &HttpdProcess.DioLm, 0 },
     { sizeof(HttpdProcess.EnqLm), JPI$_ENQLM, &HttpdProcess.EnqLm, 0 },
     { sizeof(HttpdProcess.FilLm), JPI$_FILLM, &HttpdProcess.FilLm, 0 },
     { sizeof(HttpdProcess.Grp), JPI$_GRP, &HttpdProcess.Grp, 0 },
     { sizeof(HttpdProcess.PgFlQuo), JPI$_PGFLQUOTA,
       &HttpdProcess.PgFlQuo, 0 },
     { sizeof(HttpdProcess.PrcLm), JPI$_PRCLM, &HttpdProcess.PrcLm, 0 },
     { sizeof(HttpdProcess.TqLm), JPI$_TQLM, &HttpdProcess.TqLm, 0 },
     { 0,0,0,0 }
   },
   LnmItems [] =
   {
      { sizeof(LogValue)-1, LNM$_STRING, LogValue, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr;
   IO_SB  IOsb;
   struct FAB  ScratchFab;
   struct NAM  ScratchNam;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdProcessInfo()");

   /* get some details of the account */
   status = sys$getjpiw (EfnWait, 0, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$getjpiw()", FI_LI);

   HttpdProcess.UserName[sizeof(HttpdProcess.UserName)-1] = '\0';
   for (cptr = HttpdProcess.UserName; *cptr && *cptr != ' '; cptr++);
   *cptr = '\0';
   HttpdProcess.PrcNam[Length] = '\0';

   if (HttpdProcess.AuthPriv[0] & PrivAcctMask[0])
      HttpdProcess.PrivilegedAccount = true;
   else
      HttpdProcess.PrivilegedAccount = false;

   status = sys$trnlnm (0, &LnmProcessDsc, &SysInputDsc, 0, &LnmItems);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$trnlnm()", FI_LI);
   if (*(unsigned short*)LogValue != 0x001b)
   {
      HttpdProcess.SysInputFile = false;
      strcpy (HttpdProcess.SysInput, "PPF?");
   }
   else
   {
      ScratchFab = cc$rms_fab;
      ScratchFab.fab$w_ifi = *(unsigned short*)(LogValue+2) | FAB$M_PPF_IND;
      ScratchFab.fab$l_nam = &ScratchNam;
      ScratchNam = cc$rms_nam;
      ScratchNam.nam$l_rsa = HttpdProcess.SysInput;
      ScratchNam.nam$b_rss = sizeof(HttpdProcess.SysInput)-1;

      status = sys$display (&ScratchFab, 0, 0);
      if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$display()", FI_LI);

      if (ScratchFab.fab$l_dev & DEV$M_TRM)
      {
         HttpdProcess.SysInputFile = false;
         strcpy (HttpdProcess.SysInput, "TERMINAL");
      }
      else
      {
         HttpdProcess.SysInputFile = true;
         HttpdProcess.SysInput[ScratchNam.nam$b_rsl] = '\0';
      }
   }

   status = sys$trnlnm (0, &LnmProcessDsc, &SysOutputDsc, 0, &LnmItems);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$trnlnm()", FI_LI);
   if (*(unsigned short*)LogValue != 0x001b)
   {
      HttpdProcess.SysOutputFile = false;
      strcpy (HttpdProcess.SysOutput, "PPF?");
   }
   else
   {
      ScratchFab = cc$rms_fab;
      ScratchFab.fab$w_ifi = *(unsigned short*)(LogValue+2) | FAB$M_PPF_IND;
      ScratchFab.fab$l_nam = &ScratchNam;
      ScratchNam = cc$rms_nam;
      ScratchNam.nam$l_rsa = HttpdProcess.SysOutput;
      ScratchNam.nam$b_rss = sizeof(HttpdProcess.SysOutput)-1;

      status = sys$display (&ScratchFab, 0, 0);
      if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$display()", FI_LI);

      if (ScratchFab.fab$l_dev & DEV$M_TRM)
      {
         HttpdProcess.SysOutputFile = false;
         strcpy (HttpdProcess.SysOutput, "TERMINAL");
      }
      else
      {
         HttpdProcess.SysOutputFile = true;
         HttpdProcess.SysOutput[ScratchNam.nam$b_rsl] = '\0';
      }
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
"Mode:!UL Uic:!8XL UserName:!&Z PrcNam:!&Z ProcessId:!8XL Grp:!UL \
priv:<63-32>!8XL<31-00>!8XL !&B in:!&Z !&B out:!&Z !&B",
          HttpdProcess.Mode, HttpdProcess.Uic,
          HttpdProcess.UserName, HttpdProcess.PrcNam,
          HttpdProcess.Pid, HttpdProcess.Grp, HttpdProcess.AuthPriv[1],
          HttpdProcess.AuthPriv[0], HttpdProcess.PrivilegedAccount,
          HttpdProcess.SysInput, HttpdProcess.SysInputFile,
          HttpdProcess.SysOutput, HttpdProcess.SysOutputFile);
}

/****************************************************************************/
/*
Check if the /SCRIPT=AS=<username> has been specified, or if not the default
HTTP$NOBODY account, exists and is allowed to be used (not privileged or a
member of the SYSTEM group).  If it is then set the global storage
'HttpdScriptAsUserName' to that username, if not the set it to an invalid user
name to prevent default scripting.  The /SCRIPT=AS=SUBPROCESS keyword forces
subprocess scripting from the command line.
*/ 

int HttpdScriptAs ()

{
   static unsigned long  Context = -1;
   static unsigned long  UaiFlags,
                         UaiUic;
   static unsigned long  UaiPriv [2];

   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char   *buf_addr;
      unsigned short  *short_ret_len;
   }
   UaiItems [] =
   {
      { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 },
      { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 },
      { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 },
      {0,0,0,0}
   };

   int  status;
   char  *uptr;
   static $DESCRIPTOR (UserNameDsc, "");

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdScriptAs()\n");

   /* try any supplied /SCRIPT=AS=, or default scripting account */
   if (CliScriptAs[0])
   {
      if (strsame (CliScriptAs, "SUBPROCESS", -1))
      {
         WriteFaoStdout ("%!AZ-W-SCRIPTING, as server account !AZ\n",
                         Utility, HttpdProcess.UserName);
         return (SS$_NORMAL);
      }
      UserNameDsc.dsc$a_pointer = uptr = CliScriptAs;
   }
   else
      UserNameDsc.dsc$a_pointer = uptr = "HTTP$NOBODY";
   UserNameDsc.dsc$w_length = strlen(uptr);

   /*  preemptively disable default scripting with an illegal username */
   HttpdScriptAsUserName[0] = '!';
   strzcpy (HttpdScriptAsUserName+1, uptr, sizeof(HttpdScriptAsUserName)-1);

   /* turn on SYSPRV to allow access to SYSUAF records */
   sys$setprv (1, &SysPrvMask, 0, 0);
   status = sys$getuai (0, &Context, &UserNameDsc, &UaiItems, 0, 0, 0);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
"sys$getuai() !&S uic:!8XL flags:!8XL priv:<63-32>!8XL<31-00>!8XL",
                 status, UaiUic, UaiFlags, UaiPriv[1], UaiPriv[0]);

   if (status == RMS$_RNF)
   {
      if (uptr == CliScriptAs)
      {
         /* command-line specified account MUST exist! */
         status = SS$_INVUSER;
         WriteFaoStdout ("%!AZ-E-SCRIPTING, as !AZ disabled\n-!&M\n",
                         Utility, uptr, status);
         return (status);
      }

      /* the HTTP$NOBODY account doesn't exist, undo preemptive disable */
      HttpdScriptAsUserName[0] = '\0';
      WriteFaoStdout ("%!AZ-W-SCRIPTING, as server account !AZ\n",
                      Utility, HttpdProcess.UserName);
      return (SS$_NORMAL);
   }

   if (VMSnok (status))
   {
      WriteFaoStdout ("%!AZ-E-SCRIPTING, as !AZ disabled\n-!&M\n",
                      Utility, uptr, status);
      return (status);
   }

   if (UaiFlags & UAI$M_DISACNT)
   {
      WriteFaoStdout ("%!AZ-E-SCRIPTING, as !AZ disabled - DISUSERED\n",
                      Utility, uptr);
      return (SS$_INVUSER);
   }

   if ((UaiUic & 0xffff0000) >> 16 <= SysInfo.MaxSysGroup)
   {
      WriteFaoStdout ("%!AZ-E-SCRIPTING, as !AZ disabled - SYSTEM GROUP\n",
                      Utility, uptr);
      return (SS$_INVUSER);
   }

   if (UaiPriv[0] & ~AveJoePrvMask[0] ||
       UaiPriv[1] & ~AveJoePrvMask[1])
   {
      /* something other than NETMBX and TMPMBX authorized */
      WriteFaoStdout ("%!AZ-E-SCRIPTING, as !AZ disabled - PRIVILEGED\n",
                      Utility, uptr);
      return (SS$_INVUSER); 
   }

   /* overwrite the preemptive disable */
   strzcpy (HttpdScriptAsUserName, uptr, sizeof(HttpdScriptAsUserName));
   if (strsame (HttpdScriptAsUserName, HttpdProcess.UserName, -1))
      WriteFaoStdout ("%!AZ-W-SCRIPTING, as server account !AZ\n",
                      Utility, HttpdProcess.UserName);
   else
      WriteFaoStdout ("%!AZ-I-SCRIPTING, as !AZ\n",
                      Utility, HttpdScriptAsUserName);
   return (SS$_NORMAL); 
}

/*****************************************************************************/
/*
Create a detached server process.  If the /USER= qualifier was used the process
will  have changed it's persona and the process created here will be created
under that user name.  If 'CliParameter' is not supplied it looks for the
startup procedure from 'StartupProcedures' in succession.  If it can't find one
it exits with an error status.
*/

HttpdDetachServerProcess ()

{
   static char  *StartupProcedures[] =
   {
      "HT_STARTUP:STARTUP_SERVER.COM",
      "HT_ROOT:[STARTUP]STARTUP_SERVER.COM",
      "HT_ROOT:[LOCAL]STARTUP_SERVER.COM",
      NULL
   };

   static struct
   {
      short  flag;
      char  data [1+UAF$S_USERNAME + 1+UAF$S_USERNAME +
                  1+UAF$S_PASSWORD + 1+UAF$S_ACCOUNT];
   } LgiData;
   static $DESCRIPTOR (LgiDataDsc,"");

   static unsigned long  CrePrcFlags = PRC$M_DETACH;
   static $DESCRIPTOR (LoginOutDsc, "SYS$SYSTEM:LOGINOUT.EXE");
   static $DESCRIPTOR (SysCommandDsc, "");
   static $DESCRIPTOR (SysOutputDsc, "");
   static unsigned short  Length;

   int  idx, status;
   unsigned int  ProcessPid;
   char  *cptr, *sptr, *zptr;
   char  SysCommand [256],
         LogFileName [256];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdDetachServerProcess()");

   sys$gettim (&HttpdBinTime);
   sys$numtim (&HttpdNumTime, &HttpdBinTime);

   if (CliParameter[0])
      cptr = CliParameter;
   else
   {
      /* look for one of several possible startup procedures */
      for (idx = 0; cptr = StartupProcedures[idx]; idx++)
      {
         if (VMSok (status = OdsFileExists (NULL, cptr))) break;
         if (status != RMS$_DEV && status != RMS$_DNF && status != RMS$_FNF)
            break;
      }
      if (VMSnok (status))
      {
         if (!cptr) cptr = "STARTUP_SERVER.COM";
         ErrorExitVmsStatus (status, cptr, FI_LI);
      }
   }
   SysCommandDsc.dsc$a_pointer = cptr;
   SysCommandDsc.dsc$w_length = strlen(cptr);

   WriteFao (LogFileName, sizeof(LogFileName), &Length,
             "HT_SERVER_LOGS:!AZ_!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL.LOG",
             SysInfo.NodeName,
             HttpdNumTime[0], HttpdNumTime[1], HttpdNumTime[2],
             HttpdNumTime[3], HttpdNumTime[4], HttpdNumTime[5],
             HttpdNumTime[6]);

   SysOutputDsc.dsc$a_pointer = LogFileName;
   SysOutputDsc.dsc$w_length = Length;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchDataFormatted ("$CREPRC !UL !&Z !&Z !&Z !AZ !&B\n",
                          ProcessPriority,
                          LoginOutDsc.dsc$a_pointer,
                          SysCommandDsc.dsc$a_pointer,
                          SysOutputDsc.dsc$a_pointer,
                          CliUserName, HttpdNetworkMode);

   if (CliNetworkMode ||
       HttpdNetworkMode)
   {
      /*********************************/
      /* create "network" mode process */
      /*********************************/
      
      if (CliUserName[0])
         cptr = CliUserName;
      else
         cptr = HttpdProcess.UserName;
      memset (&LgiData, 0, sizeof(LgiData));
      LgiData.flag = LGI$M_NET_PROXY;
      idx = 0;
      LgiData.data[idx] = strlen(cptr);
      memcpy (&LgiData.data[idx+1], cptr, LgiData.data[idx]);
      idx += LgiData.data[idx] + 1;
      LgiData.data[idx++] = 0;
      LgiData.data[idx++] = 0;
      LgiData.data[idx++] = 0;
      LgiDataDsc.dsc$a_pointer = &LgiData;
      LgiDataDsc.dsc$w_length = sizeof(LgiData.flag) + idx;
      CrePrcFlags = PRC$M_DETACH | PRC$M_NETWRK | PRC$M_NOPASSWORD;
      
      status = sys$creprc (&ProcessPid,
                           &LoginOutDsc,
                           /* composite SYS$INPUT and log file name */
                           &SysCommandDsc,
                           /* proxy login data structure */
                           &LgiDataDsc,
                           /* SYS$NET, which must be redirected to */
                           &SysOutputDsc,
                           0, 0, 0, ProcessPriority, 0, 0,
                           CrePrcFlags,
                           0, 0);
   }
   else
   {
      /***********************/
      /* create "other" mode */
      /***********************/

      if (CliUserName[0])
      {
         /* needs to be done explicitly in case PERSONA_MACRO is in use */
         PersonaInit ();
         if (VMSnok (status = PersonaAssume (CliUserName)))
            exit (status);
      }

      status = sys$creprc (&ProcessPid,
                           &LoginOutDsc,
                           &SysCommandDsc,
                           &SysOutputDsc,
                           0, 0, 0, 0, ProcessPriority, 0, 0,
                           CrePrcFlags,
                           0, 0);

      /* needs to be done explicitly in case PERSONA_MACRO is in use */
      if (CliUserName[0]) PersonaAssume (NULL);
   }

   if (VMSnok (status))
      ErrorExitVmsStatus (status, "sys$creprc()", FI_LI);

   WriteFaoStdout (
"%!AZ-S-PROC_ID, identification of created process is !8XL\n",
      Utility, ProcessPid);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Exit the HTTP server, via this declared exit handler.
*/

HttpdExit (unsigned long *ExitStatusPtr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "HttpdExit() !&S", *ExitStatusPtr);

   /* run down any script processes associated with script processing */
   DclExit ();

   /* ensure any proxy verify records still in use are cleared */
   ProxyVerifyInit ();

   if (LoggingEnabled)
   {
      /* provide a server exit entry */
      Logging (NULL, LOGGING_END);
   }

   /* don't worry about locking the section with this last gasp */
   AccountingPtr->LastExitStatus = *ExitStatusPtr;

   if (VMSnok (*ExitStatusPtr))
   {
      /* don't use WASD functions to write into the locked areas */
      int  status;
      unsigned short  Length;
      char  MsgString [256] = "?";
      $DESCRIPTOR (FaoDsc, "%!AZ-F-EXIT, !20%D, !AZ %X!8XL\r\n-!AZ\0");
      $DESCRIPTOR (MsgStringDsc, MsgString); 
      $DESCRIPTOR (StringDsc, ""); 

      StringDsc.dsc$w_length = sizeof(HttpdGblSecPtr->StatusMessage)-1;
      StringDsc.dsc$a_pointer = HttpdGblSecPtr->StatusMessage;

      status = sys$getmsg (*ExitStatusPtr, &Length, &MsgStringDsc, 15, 0);
      if (VMSok (status)) MsgString[Length] = '\0';

      /* don't worry about locking the section with this last gasp */
      sys$fao (&FaoDsc, NULL, &StringDsc,
               Utility, 0, HttpdProcess.PrcNam, *ExitStatusPtr, MsgString+1);

      /* record server event */
      GraphActivityEvent (ACTIVITY_EXIT_ERROR);

      /* report to process log */
      WriteFaoStdout ("%!AZ-F-EXIT, !AZ !&S\n",
                      Utility, HttpdProcess.PrcNam, *ExitStatusPtr);

      /* list current and history list requests */
      if (*ExitStatusPtr != SS_W_CONTROLY) RequestDump ();
   }
   else
   {
      /* record server exit event */
      GraphActivityEvent (ACTIVITY_EXIT);

      WriteFaoStdout ("%!AZ-I-EXIT, !AZ !&S\n-!-!&M\n",
                      Utility, HttpdProcess.PrcNam, *ExitStatusPtr);
   }

   if (OpcomMessages)
   {
      /* an error exit is always reported via OPCOM */
      if (VMSnok (*ExitStatusPtr))
         WriteFaoOpcom ("%!AZ-F-EXIT, !&S\r\n-!-!&M", Utility, *ExitStatusPtr);
      else
      if (OpcomMessages & OPCOM_HTTPD)
         WriteFaoOpcom ("%!AZ-I-EXIT, !&S\r\n-!-!&M", Utility, *ExitStatusPtr);
   }

   /* anything that needs to be explicitly done during instance shutdown */
   InstanceExit ();
}

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

int HttpdOnControlY (BOOL ControlY)

{
   static BOOL  Disabled = false;
   static unsigned long  Mask = LIB$M_CLI_CTRLY,
                         OldMask;
   static unsigned short  TTChannel = 0;
   static IO_SB  IOsb;

   int  status;
   $DESCRIPTOR (TTDsc, "TT:");

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "HttpdOnControlY() !UL", ControlY);

   if (ControlY) exit (SS_W_CONTROLY);

   if (!TTChannel)
      if (VMSnok (status = sys$assign (&TTDsc, &TTChannel, 0, 0, 0)))
         return (status);

   status = sys$qiow (EfnWait, TTChannel, IO$_SETMODE | IO$M_CTRLYAST, &IOsb,
                      0, 0, &HttpdOnControlY, true, PSL$C_USER, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) return (status);

   status = sys$qiow (EfnWait, TTChannel, IO$_SETMODE | IO$M_CTRLCAST, &IOsb,
                      0, 0, &HttpdOnControlY, true, PSL$C_USER, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) return (status);

   if (!Disabled)
   {
      Disabled = true;
      return (lib$disable_ctrl (&Mask, &OldMask));
   }
   else
      return (status);
}

/*****************************************************************************/
/*
As the code gets more complex it's becoming increasingly possible a coding or
design error will leave privileges turned on somewhere. To help detect any such
problem occasionally (when there are no more connections to process) ensure
everthing's off that's supposed to be off.
*/

HttpdCheckPriv
(
char *SourceModuleName,
int SourceLineNumber
)
{
   static long  Pid = 0;
   static unsigned long  JpiCurPriv [2];
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char   *buf_addr;
      unsigned short  *short_ret_len;
   }
      JpiItems [] =
   {
      { sizeof(JpiCurPriv), JPI$_CURPRIV, &JpiCurPriv, 0 },
      {0,0,0,0}
   };

   int  status;
   char  Buffer [128];
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdCheckPriv()");

   status = sys$getjpiw (EfnWait, &Pid, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$getjpiw()", FI_LI);

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&X !&X\n", JpiCurPriv[1], JpiCurPriv[0]);

   /* if only NETMBX and TMPMBX enabled then ok */
   if (!(JpiCurPriv[0] & ~AveJoePrvMask[0] ||
         JpiCurPriv[1] & ~AveJoePrvMask[1])) return;

#if OPERATE_WITH_SYSPRV
   /* if operating with SYSPRV and it's enabled then ok */
   if (OperateWithSysPrv &&
       (JpiCurPriv[0] & ~AveJoePrvMask[0] == SysPrvMask[0])) return;
#endif

   /* hmmmm, shouldn't have extended privileges!! */
   WriteFao (Buffer, sizeof(Buffer), 0,
             "privilege sanity check: <63-32>!8XL<31-00>!8XL",
             JpiCurPriv[1], JpiCurPriv[0]);
   ErrorExitVmsStatus (SS$_BUGCHECK, Buffer,
                       SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
This function calls itself (via a timer AST) every second or every sixty
seconds.  It updates the 'HttpdTickSecond' global storage by using system time
as a baseline (which works around any latency or cluster transition issues,
etc.)  This counts upwards across year boundaries.  It then calls the various
"activity supervisors" within the server.  If any of these has requests or
tasks still to supervise, or other periodic activities to perform,  they do it
and return a true which indicates the per-second ticker should continue to
tick.  Part of the duty of this function is to update the global storage
'HttpdTickSecond' which indicates the timeline progress of the server as well
as is used by various sections to set expiry points (marked by these global
"seconds").  Per-second ticking is initiated by any one of a small number of
code points calling this function with a zero value parameter upon the first
request processed in an otherwise quiescent server ('HttpdTicking' is false).
*/

HttpdTick (long reqidt)

{
   static unsigned long  OneSecondDelta [2] =  { -10000000, -1 };
   static unsigned long  OneMinuteDelta [2] = { -600000000, -1 };
   static unsigned long  HourTickSecond = 0,
                         MinuteTickSecond = 0;
   static unsigned short  PrevDay;

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdTick() !&X", reqidt);

   /* if already ticking then forget it */
   if (!reqidt && HttpdTicking) return;

   sys$gettim (&HttpdBinTime);
   sys$numtim (&HttpdNumTime, &HttpdBinTime);
   HttpdTickSecond = decc$fix_time (&HttpdBinTime);

   if (PrevDay != HttpdNumTime[2])
   {
      PrevDay = HttpdNumTime[2];
      lib$day_of_week (&HttpdBinTime, &HttpdDayOfWeek);
   }

   /* if just initializing the server timestamps then return */
   if (reqidt == -1)
   {
      sys$gettim (&HttpdStartBinTime);
      return;
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "TICK !UL !%D", HttpdTickSecond, &HttpdBinTime);

   if (reqidt)
      HttpdTicking = false;
   else
      HttpdTicking = true;

   if (HttpdSupervisor ()) HttpdTicking = true;
   if (InstanceSupervisor ()) HttpdTicking = true;
   if (NetAcceptSupervisor ()) HttpdTicking = true;
   if (DclSupervisor (-1)) HttpdTicking = true;
   if (DECnetSupervisor (-1)) HttpdTicking = true;

   if (HttpdTickSecond > MinuteTickSecond)
   {
      if (HttpdTickSecond > HourTickSecond)
      {
         /* every hour (but only after the first hour) */
         if (HourTickSecond)
         {
            VmRequestTune ();
            ThrottleMonitorReset ();
            Logging (NULL, LOGGING_TIMESTAMP);
         }
         HourTickSecond = HttpdTickSecond + 3541;
      }
      else
      {
         /* every minute (but only after the first minute) */
         ProxyMaintSupervisor ();
         ThrottleMonitorReset ();
         TcpIpHostCacheSupervisor ();
         Logging (NULL, LOGGING_FLUSH);
      }
      MinuteTickSecond = HttpdTickSecond + 59;
   }

   if (HttpdTicking)
      status = sys$setimr (0, &OneSecondDelta, &HttpdTick, &OneSecondDelta, 0);
   else
   {
      sys$cantim (&OneMinuteDelta, 0);
      status = sys$setimr (0, &OneMinuteDelta, &HttpdTick, &OneMinuteDelta, 0);
   }
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$setimr()", FI_LI);

   /* now note the last we ticked (which just happens to be this time :^) */
   HttpdTickPrevSecond = HttpdTickSecond;
}

/*****************************************************************************/
/*
Set the timer counters for the request according to the function code. 
HttpdSupervisor() will monitor these counters.  The period is set by adding the
period in seconds to the current second count to derive a value in the furture. 
When this is reached or exceeded the timer has expired.   The 'DurationSeconds'
is an optional parameter.  If set to zero the standard timer values are used. 
If non-zero the value is used.  This allows CGI callouts and scripting control
to set these with specific values.
*/

HttpdTimerSet
(
REQUEST_STRUCT *rqptr,
int Function,
int DurationSeconds
)
{
   static BOOL  Initialize = true;
   static unsigned long  InputSeconds,
                         KeepAliveSeconds,
                         NoProgressPeriod,
                         OutputSeconds;

   int  idx, status,
        TimerSeconds;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "HttpdTimerSet() !UL !UL", Function, DurationSeconds);

   if (Initialize)
   {
      Initialize = false;
      if (Config.cfTimeout.Input)
         InputSeconds = Config.cfTimeout.Input;
      else
         InputSeconds = DEFAULT_TIMEOUT_INPUT_MINUTES * 60;
      if (Config.cfTimeout.Output)
         OutputSeconds = Config.cfTimeout.Output;
      else
         OutputSeconds = DEFAULT_TIMEOUT_OUTPUT_MINUTES * 60;
      if (Config.cfTimeout.NoProgress)
         NoProgressPeriod = Config.cfTimeout.NoProgress;
      else
         NoProgressPeriod = DEFAULT_TIMEOUT_NOPROGRESS_MINUTES * 60;
      if (Config.cfTimeout.KeepAlive)
         KeepAliveSeconds = Config.cfTimeout.KeepAlive;
      else
         KeepAliveSeconds = DEFAULT_TIMEOUT_KEEPALIVE_SECONDS;
      /* ensure small periods are at least that duration */
      if (KeepAliveSeconds && KeepAliveSeconds < 10) KeepAliveSeconds++;
   }

   switch (Function)
   {
      case TIMER_INPUT :

          rqptr->rqTmr.KeepAliveSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.OutputSecond =
            rqptr->rqTmr.ThrottleSecond = 0;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
            TimerSeconds = InputSeconds;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_TIMER))
            WatchThis (rqptr, FI_LI, WATCH_TIMER,
                       "INPUT !UL seconds", TimerSeconds);

         rqptr->rqTmr.InputSecond = HttpdTickSecond + TimerSeconds + 1;

         break;

      case TIMER_KEEPALIVE :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.OutputSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.ThrottleSecond = 0;

         TimerSeconds = KeepAliveSeconds;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_TIMER))
            WatchThis (rqptr, FI_LI, WATCH_TIMER,
                       "KEEP-ALIVE !UL seconds", TimerSeconds);

         rqptr->rqTmr.KeepAliveSecond = HttpdTickSecond + KeepAliveSeconds + 1;

         break;

      case TIMER_OUTPUT :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.KeepAliveSecond =
            rqptr->rqTmr.ThrottleSecond = 0;

         /* establish the baseline no-progress indicators */
         rqptr->rqTmr.NoProgressBytesRx = rqptr->BytesRx;
         rqptr->rqTmr.NoProgressBytesTx = rqptr->BytesTx;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
         if (rqptr->rqPathSet.TimeoutOutput)
            TimerSeconds = rqptr->rqPathSet.TimeoutOutput;
         else
            TimerSeconds = OutputSeconds;

         if (rqptr->rqPathSet.TimeoutNoProgress)
            rqptr->rqTmr.NoProgressPeriod = rqptr->rqPathSet.TimeoutNoProgress;
         else
            rqptr->rqTmr.NoProgressPeriod = NoProgressPeriod;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_TIMER))
            WatchThis (rqptr, FI_LI, WATCH_TIMER,
                       "OUTPUT !UL/!UL seconds",
                       TimerSeconds, rqptr->rqTmr.NoProgressPeriod);

         rqptr->rqTmr.NoProgressSecond =
            HttpdTickSecond + rqptr->rqTmr.NoProgressPeriod + 1;

         rqptr->rqTmr.OutputSecond = HttpdTickSecond + TimerSeconds + 1;

         /* use the smaller of the two values to establish the list */
         if (rqptr->rqTmr.NoProgressPeriod < TimerSeconds)
            TimerSeconds = rqptr->rqTmr.NoProgressPeriod;

         break;

      case TIMER_NOPROGRESS :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.KeepAliveSecond =
            rqptr->rqTmr.ThrottleSecond = 0;

         /* establish the baseline no-progress indicators */
         rqptr->rqTmr.NoProgressBytesRx = rqptr->BytesRx;
         rqptr->rqTmr.NoProgressBytesTx = rqptr->BytesTx;

         if (DurationSeconds)
            rqptr->rqTmr.NoProgressPeriod = DurationSeconds;
         else
         if (rqptr->rqPathSet.TimeoutNoProgress)
            rqptr->rqTmr.NoProgressPeriod = rqptr->rqPathSet.TimeoutNoProgress;
         else
            rqptr->rqTmr.NoProgressPeriod = NoProgressPeriod;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_TIMER))
            WatchThis (rqptr, FI_LI, WATCH_TIMER,
                       "NOPROGRESS !UL seconds",
                       rqptr->rqTmr.NoProgressPeriod);

         rqptr->rqTmr.NoProgressSecond =
            HttpdTickSecond + rqptr->rqTmr.NoProgressPeriod + 1;

         /* no need to adjust the list! */
         return;

      case TIMER_THROTTLE :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.KeepAliveSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.OutputSecond = 0;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
         /* timeout-busy only becomes affective after any timeout-queue */
         if (rqptr->rqPathSet.ThrottleTimeoutQueue)
            TimerSeconds = rqptr->rqPathSet.ThrottleTimeoutQueue;
         else
         if (rqptr->rqPathSet.ThrottleTimeoutBusy)
            TimerSeconds = rqptr->rqPathSet.ThrottleTimeoutBusy;
         else
            TimerSeconds = OutputSeconds;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_TIMER))
            WatchThis (rqptr, FI_LI, WATCH_TIMER,
                       "THROTTLE !UL seconds", TimerSeconds);

         rqptr->rqTmr.ThrottleSecond = HttpdTickSecond + TimerSeconds + 1;

         break;

      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   /* add/move to new list */
   HttpdSupervisorList (rqptr, TimerSeconds);

   rqptr->rqTmr.TerminatedCount = 0;
}

/*****************************************************************************/
/*
Whenever at least one request is being processed this function is called every
second by HttpdTick() to supervise the requests' progress.  An array of lists
is maintained so that the duration of the scan is somewhat limited each second,
peaks occuring only when non-one-second lists need scanning.  Timer duration
counts are set by adding the timer period in seconds to the current second
count to get a second count in the future.  When this is reached  or exceeded
the timer has expired.  Time queue entries are moved from list to list as the
remaining period in seconds changes, ensuring that the entry is checked at
appropriate intervals.  Return true to indicate the HTTPd should continue to
tick.
*/

BOOL HttpdSupervisor ()

{
   int  idx, status,
        ChunkSeconds,
        DeltaSecondCount,
        TimerSecond;
   char  *TimerTypePtr;
   REQUEST_STRUCT  *rqeptr;
   LIST_ENTRY  *leptr;

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

/**
   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
   {
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "SUPERVISOR !UL", HttpdTickSecond);
      WatchDataFormatted ("SECONDS COUNT SCAN\n");
      for (idx = 1; idx <= SUPERVISOR_LIST_MAX; idx++)
      {
         ChunkSeconds = SupervisorListArray[idx].ChunkSeconds;
         WatchDataFormatted ("!7UL !5UL !AZ\n",
            SupervisorListArray[idx].ChunkSeconds,
            LIST_GET_COUNT (&SupervisorListArray[idx].RequestList),
            (idx == 1 || !(HttpdTickSecond % ChunkSeconds)) ? " YES" : "  no");
      }
   }
**/

   if (!RequestList.HeadPtr) return (false);

   /***************************/
   /* scan supervisor list(s) */
   /***************************/

   for (idx = 1; idx <= SUPERVISOR_LIST_MAX; idx++)
   {
      ChunkSeconds = SupervisorListArray[idx].ChunkSeconds;
      if (!(idx == 1 || !(HttpdTickSecond % ChunkSeconds))) continue;
      DeltaSecondCount = HttpdTickSecond + ChunkSeconds;

      /* process the current request list entries */
      leptr = SupervisorListArray[idx].RequestList.HeadPtr;
      while (leptr)
      {
         rqeptr = (REQUEST_STRUCT*)leptr->DataPtr;

/**
         if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
            WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
"!UL <- !UL -> !UL (!UL) in: !UL o:!UL np:!UL ka:!UL term:!UL",
               leptr->PrevPtr, leptr, leptr->NextPtr, rqeptr,
               rqeptr->rqTmr.InputSecond, rqeptr->rqTmr.OutputSecond,
               rqeptr->rqTmr.NoProgressSecond, rqeptr->rqTmr.KeepAliveSecond,
               rqeptr->rqTmr.TerminatedCount);
**/

         /* IMMEDIATELY get a pointer to the next in the list */
         leptr = leptr->NextPtr;

         /* ordered in the most common occurance of timings */
         if (rqeptr->rqTmr.OutputSecond)
         {
            /*********************/
            /* output/noprogress */
            /*********************/

            if (rqeptr->rqTmr.NoProgressBytesTx != rqeptr->BytesTx ||
                rqeptr->rqTmr.NoProgressBytesRx != rqeptr->BytesRx)
            {
               rqeptr->rqTmr.NoProgressSecond =
                  HttpdTickSecond + rqeptr->rqTmr.NoProgressPeriod + 1;
               rqeptr->rqTmr.NoProgressBytesRx = rqeptr->BytesRx;
               rqeptr->rqTmr.NoProgressBytesTx = rqeptr->BytesTx;
            }

            /* use the lesser of output and no-progress counts */
            if (rqeptr->rqTmr.OutputSecond <= rqeptr->rqTmr.NoProgressSecond)
            {
               if (rqeptr->rqTmr.OutputSecond > DeltaSecondCount) continue;
               TimerSecond = rqeptr->rqTmr.OutputSecond;
               TimerTypePtr = "OUTPUT";
            }
            else
            {
               if (rqeptr->rqTmr.NoProgressSecond > DeltaSecondCount) continue;
               TimerSecond = rqeptr->rqTmr.NoProgressSecond;
               TimerTypePtr = "NO-PROGRESS";
            }

            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }
            /* fall through to 'TerminatedCount' */
         }
         else
         if (TimerSecond = rqeptr->rqTmr.InputSecond)
         {
            /*********/
            /* input */
            /*********/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }
            TimerTypePtr = "INPUT";
            /* fall through to 'TerminatedCount' */
         }
         else
         if (TimerSecond = rqeptr->rqTmr.KeepAliveSecond)
         {
            /**************/
            /* keep-alive */
            /**************/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }
            TimerTypePtr = "KEEP-ALIVE";
            /* fall through to 'TerminatedCount' */
         }
         else
         if (TimerSecond = rqeptr->rqTmr.ThrottleSecond)
         {
            /************/
            /* throttle */
            /************/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }

            if (rqeptr->WatchItem && WATCH_CATEGORY(WATCH_TIMER))
               WatchThis (rqeptr, FI_LI, WATCH_TIMER, "THROTTLE expired");

            if (!rqeptr->rqTmr.TerminatedCount++)
               ThrottleTimeout (rqeptr);

            /* does not fall through to 'TerminatedCount'! */
            continue;
         }

         /*************************/
         /* shut down the request */
         /*************************/

         if (!(rqeptr->rqTmr.TerminatedCount++ %
               TERMINATED_COUNT_RETRY_SECONDS))
         {
            if (rqeptr->WatchItem && WATCH_CATEGORY(WATCH_TIMER))
               WatchThis (rqeptr, FI_LI, WATCH_TIMER,
                          "!AZ expired", TimerTypePtr);

            if (rqeptr == Watch.RequestPtr) WatchEnd (rqeptr);
            if (rqeptr->ProxyTaskPtr) ProxyCloseSocket (rqeptr->ProxyTaskPtr);
            if (rqeptr->DclTaskPtr) DclTaskRunDown (rqeptr->DclTaskPtr);
            if (rqeptr->DECnetTaskPtr) DECnetEnd (rqeptr);
            if (rqeptr->rqClient.Channel) NetCloseSocket (rqeptr);
         }
      }
   }

   return (true);
}

/*****************************************************************************/
/*
If necessary remove a request entry from it's appropriate supervisor array
list.  The search for an element within the range of timer currently still
outstanding with the request and add it to the HEAD of that list.  The head is
important because HttpdSupervisor() scans through these lists from head to tail
and if the tail is played with at the same time all hell breaks loose.
*/

HttpdSupervisorList
(
REQUEST_STRUCT *rqptr,
int TimerSeconds
)
{
   int  idx;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "HttpdSupervisorList() !UL", TimerSeconds);

   if (rqptr->rqTmr.ListIndex)
   {
      /* remove from current list */
      ListRemove (&SupervisorListArray[rqptr->rqTmr.ListIndex].RequestList,
                  &rqptr->rqTmr.ListEntry);
      rqptr->rqTmr.ListIndex = 0;
      rqptr->rqTmr.ListEntry.DataPtr = NULL;
   }

   /* just removing from timer list */
   if (TimerSeconds == -1) return;

   /* add to new list */
   for (idx = 1; idx < SUPERVISOR_LIST_MAX; idx++)
   {
      if (TimerSeconds > SupervisorListArray[idx+1].ChunkSeconds)
         continue;
      /* add this to the head of the list (less carpet pulling that way) */
      ListAddHead (&SupervisorListArray[idx].RequestList,
                   &rqptr->rqTmr.ListEntry);
      rqptr->rqTmr.ListEntry.DataPtr = (void*)rqptr;
      rqptr->rqTmr.ListIndex = idx;
      return;
   }

   ErrorNoticed (0, "supervisor list", FI_LI);

   /* ensure it goes somewhere */
   ListAddHead (&SupervisorListArray[1].RequestList, &rqptr->rqTmr.ListEntry);
   rqptr->rqTmr.ListEntry.DataPtr = (void*)rqptr;
   rqptr->rqTmr.ListIndex = 1;
}

/*****************************************************************************/
/*
Provide a report on the server supervisor (do tell!)
*/ 

HttpdSupervisorReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   static char  BeginPage [] =
"<P><TABLE CELLPADDING=8 CELLSPACING=0 BORDER=1>\n\
<TR><TD>\n\
<TABLE CELLPADDING=1 CELLSPACING=0 BORDER=0>\n\
<TR>\
<TH ALIGN=right><U>List</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Seconds</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Count</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Scan</U>&nbsp;&nbsp;</TH>\
</TR>\n\
<TR HEIGHT=5></TR>\n";

   static char  ListFao [] =
"<TR>\
<TH ALIGN=right>!UL&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!&@&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!AZ&nbsp;&nbsp;</TD>\
</TR>\n";

   static char  EndOfPageFao [] =
"<TR HEIGHT=5></TR>\n\
<TR>\
<TH ALIGN=right><U>Total</U>&nbsp;&nbsp;</TH>\
<TD></TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
</TR>\n\
</TABLE>\n\
<P><TABLE CELLPADDING=1 CELLSPACING=0 BORDER=0>\n\
<TR><TH ALIGN=right>Supervisor:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
<TR><TH ALIGN=right>Year:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
<TR><TH ALIGN=right>Month:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
<TR><TH ALIGN=right>Day:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
<TR><TH ALIGN=right>Hour:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
<TR><TH ALIGN=right>Minute:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
<TR><TH ALIGN=right>Second:&nbsp;&nbsp;</TH>\
<TD ALIGN=right>!UL</TD></TR>\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</BODY>\n\
</HTML>\n";

   int  idx, status,
        ChunkRange,
        ChunkSeconds,
        Count,
        CountTotal;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdSupervisorReport()");

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Supervisor Report", BeginPage);

   ChunkRange = CountTotal = 0;
   for (idx = 1; idx < SUPERVISOR_LIST_MAX; idx++)
   {
      ChunkSeconds = SupervisorListArray[idx].ChunkSeconds;
      ChunkRange = SupervisorListArray[idx+1].ChunkSeconds;
      Count = LIST_GET_COUNT (&SupervisorListArray[idx].RequestList);
      CountTotal += Count;

      vecptr = FaoVector;
      *vecptr++ = idx;
      if (idx == 9)
      {
         *vecptr++ = "!UL&nbsp;-&nbsp;<I>infinite</I>";
         *vecptr++ = ChunkSeconds;
      }
      else
      {
         *vecptr++ = "!UL&nbsp;-&nbsp;!UL";
         *vecptr++ = ChunkSeconds;
         *vecptr++ = ChunkRange-1;
      }
      *vecptr++ = Count;
      if (idx == 1 || (HttpdTickSecond && !(HttpdTickSecond % ChunkSeconds)))
         *vecptr++ = "YES";
      else
         *vecptr++ = "no";

      status = NetWriteFaol (rqptr, ListFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   vecptr = FaoVector;
   *vecptr++ = CountTotal;
   *vecptr++ = HttpdTickSecond;
   *vecptr++ = HttpdNumTime[0];
   *vecptr++ = HttpdNumTime[1];
   *vecptr++ = HttpdNumTime[2];
   *vecptr++ = HttpdNumTime[3];
   *vecptr++ = HttpdNumTime[4];
   *vecptr++ = HttpdNumTime[5];

   status = NetWriteFaol (rqptr, EndOfPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Create and/or map (an existing) a permanent global section.  This global
section is used to contain the accounting structure and other data that might
be of interest to a separate utility such a HTTPDMON.  The utility merely maps
the section and continues to read values from the "common" memory.  This used
to be stored in logical names but this is seen as a more elegant and efficient
method.  The section name is based on the string "WASD_HTTPD_" plus the
"official" port number of the server process.  As this is a permanent global
section it can "permanently" store accounting data between server invocations
(in much the same way the old method of using logicals could).  Because it is
permanent though, and global section is created on a per-server basis, anyone
who "plays around" with servers on a whole range of "official" port numbers run
the risk of consuming all system sections and/or global pages.  To allow for
removing no-longer-required global sections this function contains a hack
allowing the server qualifier /GBLSEC=DELETE (plus /INSTANCE= if necessary) to
delete the corresponding global section.
*/ 

int HttpdGblSecInit ()

{
   static char  ReportGblSecPages [] =
"%!AZ-I-GBLSEC, !AZ global section of !UL page(let)s\n";

   /* global, allocate space, system, in page file, permanent, writable */
   static int CreFlags = SEC$M_GBL | SEC$M_EXPREG | SEC$M_SYSGBL |
                         SEC$M_PAGFIL | SEC$M_PERM | SEC$M_WRT;
   static int DelFlags = SEC$M_SYSGBL;
   /* system & owner full access, group and world no access */
   static unsigned long  ProtectionMask = 0xff00;
   /* it is recommended to map into any virtual address in the region (P0) */
   static unsigned long  InAddr [2] = { 0x200, 0x200 };

   int  attempt, status,
        GblSecPages,
        PageCount;
   short  ShortLength;
   unsigned long  RetAddr [2];
   char  GblSecName [32];
   $DESCRIPTOR (GblSecNameDsc, GblSecName);
   HTTPD_GBLSEC  *gsptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdGblSecInit()");

   WriteFao (GblSecName, sizeof(GblSecName), &ShortLength,
             GBLSEC_NAME_FAO, HTTPD_NAME, HTTPD_GBLSEC_VERSION,
             InstanceGroupNumber, "HTTPD");
   GblSecNameDsc.dsc$w_length = ShortLength;

   if (CliGblSecDelete)
   {
      /* delete the specified global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      return (status);
   }

   GblSecPages = sizeof(HTTPD_GBLSEC) / 512;
   if (GblSecPages & 0x1ff) GblSecPages++;

   /* do not create a permanent global section */
   if (CliGblSecNoPerm) CreFlags &= ~SEC$M_PERM;

   for (attempt = 1; attempt <= 2; attempt++)
   {
      /* create and/or map the specified global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$crmpsc (&InAddr, &RetAddr, 0, CreFlags,
                           &GblSecNameDsc, 0, 0, 0, GblSecPages, 0,
                           ProtectionMask, GblSecPages);
      sys$setprv (0, &GblSecPrvMask, 0, 0);

      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                    "sys$crmpsc() !&S begin:!UL end:!UL",
                    status, RetAddr[0], RetAddr[1]);

      PageCount = (RetAddr[1]+1) - RetAddr[0] >> 9;
      HttpdGblSecPtr = gsptr = (HTTPD_GBLSEC*)RetAddr[0];
      if (VMSnok (status) || status == SS$_CREATED) break;

      /* section already exists, break if 'same size' and version! */
      if (PageCount >= GblSecPages &&
          HttpdGblSecPtr->GblSecVersion == HttpdGblSecVersion)
         break;

      /* delete the current global section, have one more attempt */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      status = SS$_IDMISMATCH;
   }

   if (VMSnok (status))
   {
      /* must have this global section! */
      char  String [256];
      WriteFao (String, sizeof(String), NULL,
                "1 global section, !UL global pages", GblSecPages);
      ErrorExitVmsStatus (status, String, FI_LI);
   }

   if (status == SS$_CREATED)
      WriteFaoStdout (ReportGblSecPages, Utility, "created", PageCount);
   else
      WriteFaoStdout (ReportGblSecPages, Utility, "existing", PageCount);

   /* if it has a different structure reset the storage */
   if (gsptr->GblSecLength != sizeof(HTTPD_GBLSEC))
   {
      memset (gsptr, 0, sizeof(HTTPD_GBLSEC));
      ControlZeroAccounting ();
   }

   /* if it was just created or reset due to different structure */
   if (!gsptr->GblSecVersion)
   {
      gsptr->GblSecVersion = HttpdGblSecVersion;
      gsptr->GblSecLength = sizeof(HTTPD_GBLSEC);
   }

   HttpdGblSecPages = PageCount;
   AccountingPtr = &gsptr->Accounting;
   ProxyAccountingPtr = &gsptr->ProxyAccounting;
   gsptr->HttpdProcessId = HttpdProcess.Pid;
   strzcpy (gsptr->HttpdVersion, HttpdVersion, sizeof(gsptr->HttpdVersion));

   /* allow for a server crash where this global value may left hanging */
   AccountingPtr->ConnectCurrent = AccountingPtr->ConnectProcessing = 0;
   /* and for a freshly initialized global section */
   if (!AccountingPtr->ResponseDurationMin)
      AccountingPtr->ResponseDurationMin = 0xffffffff;

   InstanceGblSecIncrLong (&AccountingPtr->StartupCount);

   if (CliGblSecNoPerm)
   {
      GblSectionCount++;
      GblPageCount += PageCount;
   }
   else
   {
      GblSectionPermCount++;
      GblPagePermCount += PageCount;
   }

   return (status);
}

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

