/*****************************************************************************/ /* HTTPd.c WASD VMS Hypertext Transfer Protocol daemon. COPYRIGHT NOTICE ---------------- WASD VMS Web Services, Copyright (C) 1996-2011 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 /ALL[=] /do= this to all server processes on node/cluster /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[=] /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 /ENV= do to all servers in this WASD environment /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=|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 (0-15) /[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 /VALBLK[=16|64] overrides value block size determination /VERSION simply display the server version /[NO]ZERO accounting zeroed on startup (default is non-zeroed) VERSION HISTORY --------------- See VERSION.H */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #ifndef __VAX #include #endif #include #include #include #include #include #include #include #ifndef __VAX #include #endif #include #include #include #include #include #include #include #include #include #include /* application-related header files */ #include "wasd.h" #include "sha1.h" #include "base64.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 }, SecurityMask [2] = { 0, 0x40 /* PRV$M_SECURITY */ }, SysLckMask [2] = { PRV$M_SYSLCK, 0 }, SysPrvMask [2] = { PRV$M_SYSPRV, 0 }, WorldMask [2] = { PRV$M_WORLD, 0 }; /* 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 HttpdAlignFaultReport, HttpdNetworkMode, HttpdServerExecuting, HttpdServerStartup, HttpdTicking, MonitorEnabled, NaturalLanguageEnglish, NoSwapOut = true, OperateWithSysPrv; int EfnWait, EfnNoWait, ExitStatus, GblSectionCount, GblSectionPermCount, GblPageCount, GblPagePermCount, HttpdAlignFaultCount, HttpdDayOfWeek, HttpdTickSecond, HttpdTickPrevSecond, ProcessPriority = 4, ServerPort = 80; unsigned long HttpdBinTime [2], HttpdStartBinTime [2]; unsigned short HttpdNumTime [7]; char HttpdScriptAsUserName [64], NaturalLanguage [64], ProcessIdentName [32], ServerPortString [8]; char WasdCss [] = WASD_CSS; /* 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, } }; /* seconds at which the network transfer statistics are updated */ #define SUPERVISOR_NETWORK_UPDATE 17 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, ProxyServingEnabled, WatchCliNoStartup; extern int ActivityTotalMinutes, CliServerPort, CliLockValueBlockSize, HttpdGblSecVersion, NetConcurrentMax, InstanceEnvNumber, OpcomMessages, OpcomTarget, RequestHistoryMax; extern int ToLowerCase[], ToUpperCase[]; extern char BuildInfo[], CliLogFileName[], CliParameter[], CliProcessIdentName[], CliProxyMaint[], CliScriptAs[], CliServices[], CliUserName[], CommandLine[], ControlBuffer[], ErrorSanityCheck[], HttpdIpPackage[], HttpdSesola[], HttpdVersion[], LoggingFileName[], Services[], 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; /*****************************************************************************/ /* */ int main () { int status, GblSecDeleteCount; char *cptr, *sptr, *zptr; $DESCRIPTOR (NaturalLanguageDsc, NaturalLanguage); ODS_STRUCT WasdRootOds; /*********/ /* begin */ /*********/ /* generate version information */ VersionInfo (); /* initialize general-use virtual memory management */ VmGeneralInit (); /* 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 ("WASD_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 { if (strsame (CliParameter, "BASE64", 6)) { base64_self_test (1); exit (SS$_NORMAL); } if (strsame (CliParameter, "MATCH", 5)) { MatchPerf (); exit (SS$_NORMAL); } if (strsame (CliParameter, "SHA1", 4)) { SHA1selfTest (); exit (SS$_NORMAL); } if (strsame (CliParameter, "TCPIP=", 6)) { IPADDRESS IpAddress; status = TcpIpStringToAddress (CliParameter+6, &IpAddress); exit (status); } if (strsame (CliParameter, "WEBDAV=DLM=", 11)) { status = DavWebDlmTest (CliParameter+11); exit (status); } exit (SS$_ABORT); } #else FaoToStdout ("%HTTPD-W-TESTS, NOT a compiled option\n"); #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; InstanceEnvNumber = 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 ()); } 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++; FaoToStdout ("%HTTPD-!AZ-GBLSEC, deleted !UL global sections\n", GblSecDeleteCount ? "I" : "W", GblSecDeleteCount); exit (SS$_NORMAL); } /* output the GNU GENERAL PUBLIC LICENSE message */ FaoToStdout ("%HTTPD-I-SOFTWAREID, !AZ\n!AZ", SoftwareID, CopyRightMessageBrief); if (CliVersion) { status = SetGlobalSymbol ("HTTPD_VERSION", HTTPD_VERSION); if (VMSnok(status)) exit (status); exit (SS$_NORMAL); } /****************/ /* HTTPd server */ /****************/ /* initialize the server timestamps */ HttpdTick (-1); HttpdServerExecuting = HttpdServerStartup = true; FaoToStdout ("%HTTPD-I-STARTUP, !20%D\n", &HttpdStartBinTime); /* start collection alignment faults as early as possible */ HttpdAlignFault ("START"); cptr = v10orPrev10(CONFIG_WASD_ROOT,-1); OdsParse (&WasdRootOds, cptr, strlen(cptr), NULL, 0, NAM$M_NOCONCEAL | NAM$M_SYNCHK, 0, 0); sptr = strstr (WasdRootOds.ExpFileName, ".][000000]"); if (sptr) SET2(sptr,']\0'); FaoToStdout ("%HTTPD-I-!#AZ, !AZ\n", strchr(cptr,':') - cptr, cptr, WasdRootOds.ExpFileName); FaoToStdout ("%HTTPD-I-ENVIRONMENT, !UL\n", InstanceEnvNumber); FaoToStdout ("%HTTPD-I-SYSTEM, !AZ VMS !AZ\n", SysInfo.HwName, SysInfo.Version); if (HttpdProcess.Grp <= SysInfo.MaxSysGroup) FaoToStdout ( "%HTTPD-W-SYSPRV, operating with implicit SYSPRV (UIC group !UL)\n", HttpdProcess.Grp); /* get the TCP/IP agent information */ TcpIpSetAgentInfo (); FaoToStdout ("%HTTPD-I-TCPIP, !AZ\n", 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 = "?"; } FaoToStdout ("%HTTPD-I-MODE, !AZ\n", 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); FaoToStdout ("%HTTPD-I-GMT, !AZ\n", 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; FaoToStdout ("%HTTPD-I-SYSPRV, operating with SYSPRV\n"); } else if (OperateWithSysPrv) { OperateWithSysPrv = false; FaoToStdout ("%HTTPD-W-SYSPRV, is NOT a compiled option\n"); } if (ProcessPriority > 15) ProcessPriority = 15; 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); /* initialize the GZIP module and ZLIB sharable image */ GzipInit (); /* 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, "%HTTPD-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, 0, HttpdProcess.PrcNam); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /* note the process name has to be set before OPCOM can be used */ if (OpcomMessages) FaoToOpcom ("%HTTPD-I-STARTUP, !AZ", 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 = PROCESS_RIGHTS_ID_PREFIX; *cptr; *sptr++ = *cptr++); for (cptr = HttpdProcess.PrcNam; *cptr && sptr < zptr; cptr++) if (isalnum(*cptr)) *sptr++ = TOUP(*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 (); /* WebDAV initialisation */ DavWebInit (); /* authentication/authorization configuration */ AuthConfigInit (); /* Now that authentication (potentially using X509) has been configured the multi-instance SSL session cache (if required) can be initialized and appropriately adjust the record size upwards to accomodate sessions potentially including client certificate details. */ SesolaCacheInit (); /* 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 request virtual memory management */ VmRequestInit (); /* initialize DCL processing */ DclInit (); /* initialize file cache */ CacheInit (true); /* 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; FaoToStdout ("%HTTPD-I-LANGUAGE, natural language is !AZ\n", NaturalLanguage); } } else { if (status == LIB$_ENGLUSED) { NaturalLanguageEnglish = true; strcpy (NaturalLanguage, "ENGLISH"); FaoToStdout ( "%HTTPD-I-LANGUAGE, natural language has defaulted to !AZ\n", 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))) ErrorNoticed (NULL, status, NULL, FI_LI); ServiceFudgeServerHostPort (); if (CliDemo) { /*************/ /* demo mode */ /*************/ FaoToStdout ( "%HTTPD-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"); /* 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'; FaoToStdout ("%HTTPD-I-BEGIN, !20%D, accepting requests\n", 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 (); /* do not use a mutex here! an AST delivery could break the logic!! */ AccountingPtr->SpuriousWakeCount++; } 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, CsidHwModel; static char CsidNodeName [16], CsidVersion [9], DECnetScratch [32]; 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 } }; static VMS_ITEM_LIST3 SyiCsidItem [] = { { sizeof(CsidNodeName)-1, SYI$_NODENAME, &CsidNodeName, 0 }, { sizeof(CsidHwModel), SYI$_HW_MODEL, &CsidHwModel, 0 }, { sizeof(CsidVersion)-1, SYI$_VERSION, &CsidVersion, 0 }, { 0,0,0,0 } }; int status, CsidVersionInteger; unsigned short DECnetChannel; unsigned long CsidAdr; char *cptr, *sptr; 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, NULL, FI_LI); if (cptr = getenv("WASD_VMS_VERSION")) strncpy (SysInfo.Version, cptr, sizeof(SysInfo.Version)); SysInfo.HwName[SysInfo.HwNameLength] = '\0'; for (cptr = sptr = SysInfo.HwName; *cptr; *sptr++ = *cptr++) while (SAME2(cptr,' ')) cptr++; *sptr = '\0'; SysInfo.NodeName[SysInfo.NodeNameLength] = '\0'; SysInfo.Version[sizeof(SysInfo.Version)-1] = '\0'; for (cptr = SysInfo.Version; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; if (isalpha(SysInfo.Version[0]) && 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 { FaoToStdout ( "%HTTPD-E-VMS, cannot understand VMS version string \"!AZ\"\n\ -VMS-I-KLUDGE, continue by $ DEFINE /SYSTEM WASD_VMS_VERSION \"Vn.n\"\n", SysInfo.Version); 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); /* 16 bytes for VAX and pre-V8.2 Alpha and Itanium, otherwise 64 bytes */ SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16; #ifndef __VAX if (SysInfo.VersionInteger >= 820) SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_64; /* search the cluster for anything not 64 bit value block compliant */ CsidAdr = -1; for (;;) { memset (&CsidNodeName, 0, sizeof(CsidNodeName)); memset (&CsidVersion, 0, sizeof(CsidVersion)); /* get cluster system information */ status = sys$getsyiw (EfnWait, &CsidAdr, 0, &SyiCsidItem, &IOsb, 0, 0); if (VMSok (status)) status = IOsb.Status; if (status == SS$_NOMORENODE) break; if (status == SS$_UNREACHABLE) { ErrorNoticed (NULL, status, "unexpected cluster response", FI_LI); SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16; continue; } if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); for (cptr = CsidVersion; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; if (isalpha(CsidVersion[0]) && isdigit(CsidVersion[1]) && CsidVersion[2] == '.' && isdigit(CsidVersion[3])) { /* e.g. "V7.3" */ CsidVersionInteger = ((CsidVersion[1]-48) * 100) + ((CsidVersion[3]-48) * 10); /* if something like "V7.3-2" */ if (CsidVersion[4] == '-') CsidVersionInteger += CsidVersion[5]-48; } else ErrorNoticed (NULL, SS$_BUGCHECK, "VMS version string", FI_LI); if (CsidHwModel < 1024 || CsidVersionInteger < 820) SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!&Z !&Z !UL !UL", CsidNodeName, CsidVersion, CsidVersionInteger, CsidHwModel); } /* veto! (well sort-of, still SS$_XVALNOTVALID falls-back if necessary) */ if (CliLockValueBlockSize == -1) { /* set to the default value for this platform */ CliLockValueBlockSize == 0; if (SysInfo.VersionInteger >= 820) SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_64; else SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16; } else if (CliLockValueBlockSize) { /* set to the command-line specified value */ SysInfo.LockValueBlockSize = CliLockValueBlockSize; } if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "lksb$b_valblk[!UL]", SysInfo.LockValueBlockSize); #endif /* __VAX */ } /*****************************************************************************/ /* 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, &HttpdProcess.PrcNamLength }, { 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, NULL, FI_LI); HttpdProcess.UserName[sizeof(HttpdProcess.UserName)-1] = '\0'; for (cptr = HttpdProcess.UserName; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; HttpdProcess.PrcNam[HttpdProcess.PrcNamLength] = '\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, NULL, FI_LI); if (!SAME2(LogValue,0x001b)) { HttpdProcess.SysInputFile = false; strcpy (HttpdProcess.SysInput, "PPF?"); } else { ScratchFab = cc$rms_fab; ScratchFab.fab$w_ifi = *(USHORTPTR)(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, NULL, FI_LI); if (!SAME2(LogValue,0x001b)) { HttpdProcess.SysOutputFile = false; strcpy (HttpdProcess.SysOutput, "PPF?"); } else { ScratchFab = cc$rms_fab; ScratchFab.fab$w_ifi = *(USHORTPTR)(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= 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)) { FaoToStdout ("%HTTPD-W-SCRIPTING, as server account !AZ\n", 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; FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled\n-!&M\n", uptr, status); return (status); } /* the HTTP$NOBODY account doesn't exist, undo preemptive disable */ HttpdScriptAsUserName[0] = '\0'; FaoToStdout ("%HTTPD-W-SCRIPTING, as server account !AZ\n", HttpdProcess.UserName); return (SS$_NORMAL); } if (VMSnok (status)) { FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled\n-!&M\n", uptr, status); return (status); } if (UaiFlags & UAI$M_DISACNT) { FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled - DISUSERED\n", uptr); return (SS$_INVUSER); } if ((UaiUic & 0xffff0000) >> 16 <= SysInfo.MaxSysGroup) { FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled - SYSTEM GROUP\n", uptr); return (SS$_INVUSER); } if (UaiPriv[0] & ~AveJoePrvMask[0] || UaiPriv[1] & ~AveJoePrvMask[1]) { /* something other than NETMBX and TMPMBX authorized */ FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled - PRIVILEGED\n", uptr); return (SS$_INVUSER); } /* overwrite the preemptive disable */ strzcpy (HttpdScriptAsUserName, uptr, sizeof(HttpdScriptAsUserName)); if (strsame (HttpdScriptAsUserName, HttpdProcess.UserName, -1)) FaoToStdout ("%HTTPD-W-SCRIPTING, as server account !AZ\n", HttpdProcess.UserName); else FaoToStdout ("%HTTPD-I-SCRIPTING, as !AZ\n", 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. To support startup with a WASD-specific logical name table (v10.0) it has become necessary to parse the command and output files to obtain the unconcealed devices. */ HttpdDetachServerProcess () { static char *StartupProcedures[] = { "WASD_STARTUP:STARTUP_SERVER.COM", "WASD_ROOT:[STARTUP]STARTUP_SERVER.COM", "WASD_ROOT:[LOCAL]STARTUP_SERVER.COM", "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 char PrcNam [16]; static $DESCRIPTOR (PrcNamDsc, PrcNam); static $DESCRIPTOR (SysCommandDsc, ""); static $DESCRIPTOR (SysOutputDsc, ""); static unsigned short Length; int idx, status; unsigned int ProcessPid; unsigned short slen; char *cptr, *sptr, *zptr; char LogFileName [256], SysCommand [256]; ODS_STRUCT SysCommandOds, SysOutputOds; /*********/ /* 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); } } OdsParse (&SysCommandOds, cptr, strlen(cptr), NULL, 0, NAM$M_NOCONCEAL | NAM$M_SYNCHK, 0, 0); SysCommandDsc.dsc$a_pointer = SysCommandOds.ExpFileName; SysCommandDsc.dsc$w_length = SysCommandOds.ExpFileNameLength; FaoToBuffer (LogFileName, sizeof(LogFileName), &Length, "!AZ!AZ_!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL.LOG", v10orPrev10(CONFIG_SERVER_LOGS,-1), SysInfo.NodeName, HttpdNumTime[0], HttpdNumTime[1], HttpdNumTime[2], HttpdNumTime[3], HttpdNumTime[4], HttpdNumTime[5], HttpdNumTime[6]); OdsParse (&SysOutputOds, LogFileName, Length, NULL, 0, NAM$M_NOCONCEAL | NAM$M_SYNCHK, 0, 0); SysOutputDsc.dsc$a_pointer = SysOutputOds.ExpFileName; SysOutputDsc.dsc$w_length = SysOutputOds.ExpFileNameLength; if (InstanceEnvNumber > 1) { /* the starting HTTPd process will look for and parse this number */ FaoToBuffer (PrcNam, sizeof(PrcNam), &Length, "!ULWASD:starting", InstanceEnvNumber); PrcNam[PrcNamDsc.dsc$w_length = Length] = '\0'; } else { PrcNamDsc.dsc$a_pointer = "WASD:starting"; PrcNamDsc.dsc$w_length = 13; } if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchDataFormatted ("$CREPRC !UL !&Z !&Z !&Z !AZ !AZ !&B\n", ProcessPriority, LoginOutDsc.dsc$a_pointer, SysCommandDsc.dsc$a_pointer, SysOutputDsc.dsc$a_pointer, CliUserName, PrcNamDsc.dsc$a_pointer, 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, &PrcNamDsc, 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, &PrcNamDsc, 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); FaoToStdout ( "%HTTPD-S-PROC_ID, identification of created process is !8XL\n", ProcessPid); return (SS$_NORMAL); } /*****************************************************************************/ /* Exit the HTTP server, via this declared exit handler. Use vanilla output in case other routines are implicated. */ HttpdExit (unsigned long *ExitStatusPtr) { static $DESCRIPTOR (FaoDsc, "%HTTPD-F-EXIT, !20%D, !AZ %X!8XL\r\n-!AZ\0"); int status; unsigned short Length; unsigned long ExitStatus; char MsgString [256] = "?"; $DESCRIPTOR (MsgStringDsc, MsgString); $DESCRIPTOR (StringDsc, ""); #ifndef __VAX struct invo_context_blk icb; #endif /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdExit() !&S", *ExitStatusPtr); ExitStatus = *ExitStatusPtr; /* run down any script processes associated with script processing */ DclExit (); /* ensure any proxy verify records still in use are cleared */ ProxyVerifyInit (); fflush (stdout); if (LoggingEnabled) { /* provide a server exit entry */ Logging (NULL, LOGGING_END); } /* don't worry about locking the section with this last gasp */ AccountingPtr->LastExitStatus = ExitStatus; AccountingPtr->LastExitPid = HttpdProcess.Pid; sys$gettim (&AccountingPtr->LastExitBinTime); if (VMSnok (ExitStatus)) { /**************/ /* error exit */ /**************/ StringDsc.dsc$w_length = sizeof(HttpdGblSecPtr->StatusMessage)-1; StringDsc.dsc$a_pointer = HttpdGblSecPtr->StatusMessage; status = sys$getmsg (ExitStatus, &Length, &MsgStringDsc, 15, 0); if (VMSok (status)) StringDsc.dsc$a_pointer[Length] = '\0'; /* don't worry about locking the section with this last gasp */ sys$fao (&FaoDsc, NULL, &StringDsc, 0, HttpdProcess.PrcNam, ExitStatus, MsgString+1); /* record server event */ GraphActivityEvent (ACTIVITY_EXIT_ERROR); /* report to process log */ fprintf (stdout, "%%HTTPD-F-EXIT, %s %%X%08.08X\n", HttpdProcess.PrcNam, ExitStatus); if (ExitStatus != SS_W_CONTROLY) { /* add traceback information */ #ifdef __ALPHA lib$get_curr_invo_context (&icb); while (lib$get_prev_invo_context (&icb), !icb.libicb$v_bottom_of_stack) fprintf (stdout, "-HTTPD-F-TRACE, %08.08X%08.08X\n", icb.libicb$q_program_counter[1], icb.libicb$q_program_counter[0]); #endif #ifdef __ia64 lib$i64_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION); lib$i64_get_curr_invo_context (&icb); while (lib$i64_get_prev_invo_context (&icb), !icb.libicb$v_bottom_of_stack) fprintf (stdout, "-HTTPD-F-TRACE, %08.08X%08.08X\n", ((ULONGPTR)&icb.libicb$ih_pc)[1], ((ULONGPTR)&icb.libicb$ih_pc)[0]); #endif /* list current and history list requests */ RequestDump (); } } StringDsc.dsc$w_length = sizeof(MsgString)-1; StringDsc.dsc$a_pointer = MsgString; status = sys$getmsg (ExitStatus, &Length, &MsgStringDsc, 15, 0); if (VMSok (status)) MsgString[Length] = '\0'; if (VMSok (ExitStatus)) { /***************/ /* normal exit */ /***************/ /* record server exit event */ GraphActivityEvent (ACTIVITY_EXIT); fprintf (stdout, "%%HTTPD-I-EXIT, %s %%X%08.08X\n-%s\n", HttpdProcess.PrcNam, ExitStatus, MsgString+1); } if (OpcomMessages) { /*********/ /* opcom */ /*********/ struct { unsigned long TargetType; unsigned long RequestId; char MsgText [986+1]; } OpcomMsg; $DESCRIPTOR (OpcomDsc, ""); $DESCRIPTOR (OpcomFaoDsc, "Process !AZ reports\r\n" "%HTTPD-!AZ-EXIT, %X!8XL\r\n-!AZ"); OpcomDsc.dsc$a_pointer = &OpcomMsg.MsgText; OpcomDsc.dsc$w_length = sizeof(OpcomMsg.MsgText)-1; status = sys$fao (&OpcomFaoDsc, &Length, &OpcomDsc, HttpdProcess.PrcNam, VMSnok(ExitStatus) ? "F" : "I", ExitStatus, MsgString+1); /* errors noticed uses vanilla output (for the same reasons) */ if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI); OpcomMsg.TargetType = OPC$_RQ_RQST + ((OpcomTarget & 0xffffff) << 8); OpcomMsg.RequestId = 0; OpcomDsc.dsc$a_pointer = &OpcomMsg; OpcomDsc.dsc$w_length = Length + 8; /* an error exit is ALWAYS reported via OPCOM */ if (VMSnok (ExitStatus)) status = sys$sndopr (&OpcomDsc, 0); else /* others will be reported is enabled */ if (OpcomMessages & OPCOM_HTTPD) status = sys$sndopr (&OpcomDsc, 0); else status = SS$_NORMAL; /* errors noticed uses vanilla output (for the same reasons) */ if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI); } if (VMSnok (ExitStatus)) fputs ("-HTTPD-F-ADIEU, ...\n", stdout); fflush (stdout); /* anything that needs to be explicitly done during instance shutdown */ InstanceExit (); } /*****************************************************************************/ /* Collect alignment fault statistics. Intended to be used as a tool for the WASD developer to monitor alignment faults (expensive on Alpha and VERY expensive on Itanium) and track down (and perhaps remedy) the code sections generating those faults. Data is collected into an array of PC addresses masked to aggregate those addresses into groups (conserving the number of actual entries). The PCs thus generated allow the linker map and then associated compiler lists to be used to "zero-in" on sections of code generating alignments faults. Collection and reporting is enabled by default but can be modified using the command-line /DO=ALIGN=START, /DO=ALIGN=STOP and /DO=ALIGN=ZERO. The control string also allows three data collection parameters to be set (which also starts collection) /DO=ALIGN=[,[,]]. When WASD is correctly coded there should be no alignment faults (or close to none perhaps due to those lurking in uncommonly traversed code paths) so it might be reassuring that an alignment fault report indicating none is really working! The /DO=ALIGN=FAULT= will generate alignment faults whenever the data collection function is called (usually at least once per second). These will be reported in the HTTPD module (of course). Use /DO=ALIGN=FAULT=0 to disabled fault generation. */ void* HttpdAlignFault (char *ControlString) { #ifndef __VAX #define ALIGN_BUFFER_SIZE 64 /* kByte */ #define ALIGN_PCS_MAX 128 static int GenerateFault, GetBufferSize, ItemArraySize, ItemCount, ItemMax, ItemOverflow, PCmask, ReportBufferSize, ReportOverflow, StartTickSecond; static unsigned char *GetBufferPtr, *ReportBufferPtr; static struct FaultDataStruct ItemData; static struct FaultAccumStruct *ItemArrayPtr; int idx, status; unsigned long pc; unsigned long GetBufferLength = 0; unsigned char *afrptr, *zfrptr; char *cptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdAlignFault() !&Z", ControlString); if (!ControlString) { /**********************/ /* get and accumulate */ /**********************/ if (!HttpdAlignFaultReport) return (NULL); /* generate reassurement faults for quiet reports */ for (idx = 0; idx < GenerateFault; idx++) ((unsigned short*)(GetBufferPtr+1))[idx]++; status = sys$get_align_fault_data (GetBufferPtr, GetBufferSize, &GetBufferLength); if (VMSnok (status)) ErrorNoticed (NULL, status, "sys$get_align_fault_data()", FI_LI); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL !UL", ReportBufferSize, GetBufferSize, GetBufferLength); /* (educated) guess as to whether the storage was exhausted */ if (GetBufferLength >= GetBufferSize - AFR$K_USER_LENGTH) ReportOverflow++; zfrptr = (afrptr = GetBufferPtr) + GetBufferLength; while (afrptr < zfrptr) { HttpdAlignFaultCount++; pc = ((struct afrdef*)afrptr)->afr$l_fault_pc_l; pc = pc & PCmask; for (idx = 0; idx < ItemCount; idx++) if (ItemArrayPtr[idx].pc == pc) break; if (idx < ItemCount) ItemArrayPtr[idx].cnt++; else if (ItemCount < ItemMax) { ItemArrayPtr[ItemCount].pc = pc; ItemArrayPtr[ItemCount].cnt = 1; ItemCount++; } else ItemOverflow++; afrptr += AFR$C_USER_LENGTH; } /* populate the data structure to be returned */ ItemData.ItemArray = ItemArrayPtr; ItemData.ItemCount = ItemCount; ItemData.ItemMax = ItemMax; ItemData.ItemOverflow = ItemOverflow; ItemData.GetBufferSize = GetBufferSize; ItemData.PCmask = PCmask; ItemData.ReportBufferSize = ReportBufferSize; ItemData.ReportOverflow = ReportOverflow; ItemData.StartTickSecond = StartTickSecond; return (&ItemData); } cptr = ControlString; if (strsame (cptr, "START", -1) || isdigit(*cptr)) { /********************/ /* start collecting */ /********************/ if (HttpdAlignFaultReport) return (NULL); GenerateFault = HttpdAlignFaultCount = ItemCount = ItemMax = ItemOverflow = ReportOverflow = 0; StartTickSecond = HttpdTickSecond; if (isdigit(*cptr)) ReportBufferSize = atoi(cptr); if (ReportBufferSize < ALIGN_BUFFER_SIZE) ReportBufferSize = ALIGN_BUFFER_SIZE; ReportBufferSize *= 1024; while (*cptr && isdigit(*cptr)) cptr++; while (*cptr && !isdigit(*cptr)) cptr++; ItemMax = atoi(cptr); if (ItemMax < ALIGN_PCS_MAX) ItemMax = ALIGN_PCS_MAX; /* two hex digits (256 addresses, 8 bits) of mask can be specified */ while (*cptr && isdigit(*cptr)) cptr++; while (*cptr && !isxdigit(*cptr)) cptr++; if (*cptr) { PCmask = strtol(cptr,NULL,16); PCmask = (PCmask & 0xff) | 0xffffff00; } else PCmask = 0xfffffff0; ReportBufferPtr = VmGet (ReportBufferSize); ItemArraySize = sizeof(struct FaultAccumStruct) * ItemMax; ItemArrayPtr = (struct FaultAccumStruct*) VmGet (ItemArraySize); GetBufferSize = ReportBufferSize; GetBufferPtr = VmGet (GetBufferSize); status = sys$start_align_fault_report (AFR$C_BUFFERED, ReportBufferPtr, ReportBufferSize); if (VMSok (status)) { HttpdAlignFaultReport = true; FaoToStdout ("%HTTPD-I-ALIGN, start collecting \ alignment faults (!ULkB,!UL,0x!8XL)\n", ReportBufferSize/1024, ItemMax, PCmask); } else { VmFree (GetBufferPtr, FI_LI); VmFree (ItemArrayPtr, FI_LI); VmFree (ReportBufferPtr, FI_LI); GetBufferSize = HttpdAlignFaultCount = ItemArraySize = ReportBufferSize = 0; GetBufferPtr = ItemArrayPtr = ReportBufferPtr = NULL; ErrorNoticed (NULL, status, "sys$start_align_fault_report()", FI_LI); } } else if (strsame (cptr, "STOP", -1)) { /*******************/ /* stop collecting */ /*******************/ if (!HttpdAlignFaultReport) return (NULL); status = sys$stop_align_fault_report (); if (VMSok (status)) { HttpdAlignFaultReport = false; VmFree (GetBufferPtr, FI_LI); VmFree (ItemArrayPtr, FI_LI); VmFree (ReportBufferPtr, FI_LI); GetBufferSize = HttpdAlignFaultCount = ItemArraySize = ReportBufferSize = 0; GetBufferPtr = ItemArrayPtr = ReportBufferPtr = NULL; FaoToStdout ("%HTTPD-I-ALIGN, stop collecting alignment faults\n"); } else ErrorNoticed (NULL, status, "sys$stop_align_fault_report()", FI_LI); } else if (strsame (cptr, "ZERO", -1)) { /*******************/ /* zero statistics */ /*******************/ if (!HttpdAlignFaultReport) return (NULL); HttpdAlignFaultCount = ItemCount = ItemOverflow = ReportOverflow = 0; StartTickSecond = HttpdTickSecond; memset (ItemArrayPtr, 0, ItemArraySize); } else if (strsame (cptr, "FAULT=", 6)) { /*******************/ /* generate faults */ /*******************/ GenerateFault = atoi(cptr+6); } #endif return (NULL); } /*****************************************************************************/ /* Elementary bubble sort with PC ascending. Just makes the alignment fault PC listing a little more intuitive. */ HttpdAlignSort ( struct FaultAccumStruct *ItemArrayPtr, int ItemCount ) { #ifndef __VAX int cnt, idx1, idx2; unsigned long pc; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdAlignSort()"); for (idx1 = 0; idx1 < ItemCount; idx1++) { for (idx2 = idx1+1; idx2 < ItemCount; idx2++) { if (ItemArrayPtr[idx1].pc < ItemArrayPtr[idx2].pc) continue; pc = ItemArrayPtr[idx1].pc; cnt = ItemArrayPtr[idx1].cnt; ItemArrayPtr[idx1].pc = ItemArrayPtr[idx2].pc; ItemArrayPtr[idx1].cnt = ItemArrayPtr[idx2].cnt; ItemArrayPtr[idx2].pc = pc; ItemArrayPtr[idx2].cnt = cnt; } } #endif } /*****************************************************************************/ /* Report on the statistics gathered by HttpAlignFault(). If logical name WASD_ALIGN_MAP points to a linker map the also provides the code module containing the PC and its offset. */ HttpdAlignReport ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { #ifndef __VAX static char BeginTableFao [] = "

\n\ \n\
\n\ \n\ \n"; static char HeadFao [] = "!AZ\ "; static char ItemFao [] = "!AZ"; static char ItemTotalFao [] = "\n\ \n"; static char EndTableFao [] = "
PCCount!UL.!AZ!UL
Total:!UL
Per-Second:!UL
\n\
\n"; static char EndPageFao [] = "

PC mask:  0x!8XL\n\
PCs:  !UL max  (overflow:!UL)\n\
Buffer:  !ULkB  (overflow:!UL)\n\ \n\ \n"; static struct FaultDataStruct ItemData; int cnt, idx1, idx2, ColStep, ItemCount, NumbCols; unsigned int SecondsElapsed; struct FaultDataStruct *ItemDataPtr; struct FaultAccumStruct *ItemArrayPtr = NULL; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdAlignReport()"); ItemDataPtr = HttpdAlignFault (NULL); AdminPageTitle (rqptr, "Alignment Fault Report"); if (ItemDataPtr) { ItemArrayPtr = ItemDataPtr->ItemArray; ItemCount = ItemDataPtr->ItemCount; HttpdAlignSort (ItemArrayPtr, ItemCount); if (ItemCount < 15) NumbCols = 1; else if (ItemCount < 80) NumbCols = 3; else if (ItemCount < 160) NumbCols = 4; else NumbCols = 5; ColStep = ItemCount / NumbCols; if (ItemCount % NumbCols) ColStep++; FaoToNet (rqptr, BeginTableFao); if (HttpdAlignFaultCount) { FaoToNet (rqptr, ""); for (cnt = 0; cnt < NumbCols; cnt++) FaoToNet (rqptr, HeadFao, cnt ? "" : ""); FaoToNet (rqptr, "\n"); } for (idx1 = 0; idx1 * NumbCols < ItemCount; idx1++) { FaoToNet (rqptr, ""); for (cnt = 0; cnt < NumbCols; cnt++) { idx2 = idx1 + (ColStep * cnt); if (idx2 >= ItemCount) break; FaoToNet (rqptr, ItemFao, cnt ? "" : "", idx2+1, HttpdAlignModule(ItemArrayPtr[idx2].pc), ItemArrayPtr[idx2].cnt); } FaoToNet (rqptr, "\n"); } SecondsElapsed = HttpdTickSecond - ItemDataPtr->StartTickSecond; if (SecondsElapsed) FaoToNet (rqptr, ItemTotalFao, HttpdAlignFaultCount, HttpdAlignFaultCount / SecondsElapsed); FaoToNet (rqptr, EndTableFao); /* reset the module function */ HttpdAlignModule (0); } else ItemDataPtr = &ItemData; FaoToNet (rqptr, EndPageFao, ItemDataPtr->PCmask, ItemDataPtr->ItemMax, ItemDataPtr->ItemOverflow, ItemDataPtr->ReportBufferSize/1024, ItemDataPtr->ReportOverflow); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); #endif SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/ /* Return a pointer to a static buffer with either the PC parameter or if the file WASD_ALIGN_MAP (logical name) exists the PC with the module associated with that PC and the offset into that module. Must be called with PC zero to free allocated resources. */ char* HttpdAlignModule (unsigned long pc) { #ifndef __VAX static BOOL LoadMap = true; static char *CodePtr; static char PcData [64]; static ODS_STRUCT MapFileOds; int status; unsigned long end, start; char *cptr, *sptr, *zptr; char ModuleName [32]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpdAlignModule() !8XL", pc); if (!pc) { if (MapFileOds.DataLinePtr) VmFree (MapFileOds.DataLinePtr, FI_LI); memset (&MapFileOds, 0, sizeof(ODS_STRUCT)); CodePtr = NULL; LoadMap = true; return (NULL); } FaoToBuffer (PcData, sizeof(PcData), 0, "!8XL", pc); if (LoadMap) { LoadMap = false; status = OdsLoadTextFile (&MapFileOds, ProtocolHttpsAvailable ? "WASD_ROOT:[SRC.HTTPD]HTTPD_SSL.MAP" : "WASD_ROOT:[SRC.HTTPD]HTTPD.MAP"); if (VMSnok(status)) status = OdsLoadTextFile (&MapFileOds, "WASD_ALIGN_MAP"); if (VMSnok (status)) return (PcData); if (CodePtr = strstr (MapFileOds.DataPtr, "$CODE$")) if (cptr = strstr (CodePtr, "$BSS$")) *cptr = '\0'; } if (!CodePtr) return (PcData); start = end = 0; ModuleName[0] = '\0'; cptr = CodePtr; while (*cptr) { start = end = 0; while (*cptr && *cptr != '\n') cptr++; if (!*cptr) break; cptr++; if (!isspace(*cptr) && !strncmp (cptr, "$CODE$", 6)) continue; while (*cptr && isspace(*cptr) && *cptr != '\n') cptr++; if (*cptr == '\n') continue; ModuleName[0] = '\0'; zptr = (sptr = ModuleName) + sizeof(ModuleName)-1; while (*cptr && !isspace(*cptr) && *cptr != '\n' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; while (*cptr && !isspace(*cptr) && *cptr != '\n') cptr++; while (*cptr && isspace(*cptr) && *cptr != '\n') cptr++; if (!isxdigit(*cptr)) continue; start = strtol(cptr,NULL,16); while (*cptr && !isspace(*cptr) && *cptr != '\n') cptr++; while (*cptr && isspace(*cptr) && *cptr != '\n') cptr++; if (!isxdigit(*cptr)) continue; end = strtol(cptr,NULL,16); if (pc >= start && pc <= end) break; } if (start && end) FaoToBuffer (PcData, sizeof(PcData), 0, "!8XL
!AZ+!8XL", pc, ModuleName, pc-start); return (PcData); #endif } /*****************************************************************************/ /* */ 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() !&B", 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, NULL, 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!! */ FaoToBuffer (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 = -1, PrevHour = -1; 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 active) accumulate alignment fault data */ if (HttpdAlignFaultReport) HttpdAlignFault (NULL); if (reqidt) HttpdTicking = false; else HttpdTicking = true; if (HttpdSupervisor ()) HttpdTicking = true; if (InstanceSupervisor ()) HttpdTicking = true; if (NetAcceptSupervisor ()) HttpdTicking = true; if (ProxyNetConnectSupervisor (-1)) HttpdTicking = true; if (DclSupervisor (-1)) HttpdTicking = true; if (DECnetSupervisor (-1)) HttpdTicking = true; if (HttpdTickSecond > MinuteTickSecond) { if (HttpdTickSecond > HourTickSecond) { /* every hour recheck the GMT time offset */ if (VMSnok (status = TimeSetGMT ())) ErrorExitVmsStatus (status, "TimeSetGMT()", FI_LI); /* 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 (HttpdTickSecond); Logging (NULL, LOGGING_FLUSH); } MinuteTickSecond = HttpdTickSecond + 59; } if (PrevHour != HttpdNumTime[3]) { /* every hour, (approximately) on the hour */ if (PrevHour != -1) { /* just shut the access log file(s) */ Logging (NULL, LOGGING_SHUT); } PrevHour = HttpdNumTime[3]; } 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, NULL, 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, PersistentSeconds, 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.Persistent) PersistentSeconds = Config.cfTimeout.Persistent; else PersistentSeconds = DEFAULT_TIMEOUT_PERSISTENT_SECONDS; /* ensure small periods are at least that duration */ if (PersistentSeconds && PersistentSeconds < 10) PersistentSeconds++; } switch (Function) { case TIMER_INPUT : rqptr->rqTmr.PersistentSecond = 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_OUTPUT : rqptr->rqTmr.InputSecond = rqptr->rqTmr.PersistentSecond = rqptr->rqTmr.ThrottleSecond = 0; /* establish the baseline no-progress indicators (lsb) */ rqptr->rqTmr.NoProgressBytesRx = rqptr->BytesRawRx[0]; rqptr->rqTmr.NoProgressBytesTx = rqptr->BytesRawTx[0]; 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.PersistentSecond = rqptr->rqTmr.ThrottleSecond = 0; /* establish the baseline no-progress indicators (lsb) */ rqptr->rqTmr.NoProgressBytesRx = rqptr->BytesRawRx[0]; rqptr->rqTmr.NoProgressBytesTx = rqptr->BytesRawTx[0]; 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_PERSISTENT : rqptr->rqTmr.InputSecond = rqptr->rqTmr.OutputSecond = rqptr->rqTmr.NoProgressBytesRx = rqptr->rqTmr.NoProgressBytesTx = rqptr->rqTmr.NoProgressSecond = rqptr->rqTmr.NoProgressPeriod = rqptr->rqTmr.ThrottleSecond = 0; if (DurationSeconds) TimerSeconds = DurationSeconds; else TimerSeconds = PersistentSeconds; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_TIMER)) WatchThis (rqptr, FI_LI, WATCH_TIMER, "PERSISTENT !UL seconds", TimerSeconds); rqptr->rqTmr.PersistentSecond = HttpdTickSecond + TimerSeconds + 1; break; case TIMER_THROTTLE : rqptr->rqTmr.InputSecond = rqptr->rqTmr.PersistentSecond = 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 () { BOOL PersistentTimeout; int idx, status, ChunkSeconds, DeltaSecondCount, TimerSecond; unsigned long BytesRawTx [2], BytesRawRx [2]; 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); PersistentTimeout = false; /************************************/ /* update selected network counters */ /************************************/ if (!(HttpdTickSecond % SUPERVISOR_NETWORK_UPDATE)) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (ActivityTotalMinutes) InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; if (QUAD_ZERO(rqeptr->BytesRawRx) && QUAD_ZERO(rqeptr->BytesRawTx)) continue; /* update activity first, it uses the bytes accounted-for value */ if (ActivityTotalMinutes) GraphActivityUpdate (rqeptr, false); PUT_QUAD_QUAD (rqeptr->BytesRawRx, BytesRawRx); /* bytes accounted tracks what's already been accounted-for */ SUB_QUAD_QUAD (rqeptr->BytesAccountedForRx, BytesRawRx); /* accumulate the difference */ ADD_QUAD_QUAD (BytesRawRx, AccountingPtr->BytesRawRx); ADD_QUAD_QUAD (BytesRawRx, AccountingPtr->BytesRawTotal); /* update what's already been accounted-for */ ADD_QUAD_QUAD (BytesRawRx, rqeptr->BytesAccountedForRx); PUT_QUAD_QUAD (rqeptr->BytesRawTx, BytesRawTx); SUB_QUAD_QUAD (rqeptr->BytesAccountedForTx, BytesRawTx); /* accumulate the difference */ ADD_QUAD_QUAD (BytesRawTx, AccountingPtr->BytesRawTx); ADD_QUAD_QUAD (BytesRawTx, AccountingPtr->BytesRawTotal); /* update what's already been accounted-for */ ADD_QUAD_QUAD (BytesRawTx, rqeptr->BytesAccountedForTx); } if (ActivityTotalMinutes) InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /***************************/ /* 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; /* IMMEDIATELY get a pointer to the next in the list */ leptr = leptr->NextPtr; /** 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.PersistentSecond, rqeptr->rqTmr.TerminatedCount); **/ /* ordered in the most common occurance of timings */ if (rqeptr->rqTmr.OutputSecond) { /*********************/ /* output/noprogress */ /*********************/ if (rqeptr->rqTmr.NoProgressBytesTx != rqeptr->BytesRawTx[0] || rqeptr->rqTmr.NoProgressBytesRx != rqeptr->BytesRawRx[0]) { rqeptr->rqTmr.NoProgressSecond = HttpdTickSecond + rqeptr->rqTmr.NoProgressPeriod + 1; rqeptr->rqTmr.NoProgressBytesRx = rqeptr->BytesRawRx[0]; rqeptr->rqTmr.NoProgressBytesTx = rqeptr->BytesRawTx[0]; } /* 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.PersistentSecond) { /*************************/ /* persistent connection */ /*************************/ if (TimerSecond > DeltaSecondCount) continue; if (TimerSecond > HttpdTickSecond) { /* move to new list */ HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond); continue; } TimerTypePtr = "PERSISTENT"; PersistentTimeout = true; /* 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)) { if (rqeptr->WatchItem && WATCH_CATEGORY(WATCH_TIMER)) WatchThis (rqeptr, FI_LI, WATCH_TIMER, "!AZ expired", TimerTypePtr); if (rqeptr->rqNet.PeekInProgress) { rqeptr->rqNet.PeekInProgress = false; sys$cancel (rqeptr->rqClient.Channel); continue; } if (rqeptr == Watch.RequestPtr) WatchEnd (rqeptr); if (rqeptr->DclTaskPtr) DclTaskRunDown (rqeptr->DclTaskPtr); if (rqeptr->DECnetTaskPtr) DECnetEnd (rqeptr); if (rqeptr->ProxyTaskPtr) { if (rqeptr->ProxyTaskPtr->ProxyChannel) ProxyNetCloseSocket (rqeptr->ProxyTaskPtr); } if (rqeptr->rqClient.Channel) { /* give an SSL connection a chance to be elegantly shut down */ if (PersistentTimeout && rqeptr->rqNet.SesolaPtr && rqeptr->rqTmr.TerminatedCount == 1) sys$cancel (rqeptr->rqClient.Channel); else 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 (rqptr, 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 [] = "

\n\ \n\
\n\ \n\ \ \ \ \ \ \n\ \n"; static char ListFao [] = "\ \ \ \ \ \n"; static char EndOfPageFao [] = "\n\ \ \ \ \ \n\
List  Seconds  Count  Scan  
!UL  !&@  !UL  !AZ  
Total  !UL  
\n\

\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \
Supervisor:  !UL
Year:  !UL
Month:  !UL
Day:  !UL
Hour:  !UL
Minute:  !UL
Second:  !UL
\n\

\n\ \n\ \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()"); 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 - infinite"; *vecptr++ = ChunkSeconds; } else { *vecptr++ = "!UL - !UL"; *vecptr++ = ChunkSeconds; *vecptr++ = ChunkRange-1; } *vecptr++ = Count; if (idx == 1 || (HttpdTickSecond && !(HttpdTickSecond % ChunkSeconds))) *vecptr++ = "YES"; else *vecptr++ = "no"; status = FaolToNet (rqptr, ListFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, 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 = FaolToNet (rqptr, EndOfPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); 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 [] = "%HTTPD-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()"); FaoToBuffer (GblSecName, sizeof(GblSecName), &ShortLength, GBLSEC_NAME_FAO, HTTPD_NAME, HTTPD_GBLSEC_VERSION_NUMBER, InstanceEnvNumber, "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]; FaoToBuffer (String, sizeof(String), NULL, "1 global section, !UL global pages", GblSecPages); ErrorExitVmsStatus (status, String, FI_LI); } if (status == SS$_CREATED) FaoToStdout (ReportGblSecPages, "created", PageCount); else FaoToStdout (ReportGblSecPages, "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 these global values may left hanging */ AccountingPtr->ConnectCurrent = AccountingPtr->ConnectCurrentPersistent = AccountingPtr->ConnectProcessing = AccountingPtr->WebSocketCurrent = 0; InstanceGblSecIncrLong (&AccountingPtr->StartupCount); if (CliGblSecNoPerm) { GblSectionCount++; GblPageCount += PageCount; } else { GblSectionPermCount++; GblPagePermCount += PageCount; } return (status); } /*****************************************************************************/