/*****************************************************************************/ /* HTTPd.c WASD VMS Hypertext Transfer Protocol daemon. COPYRIGHT NOTICE ---------------- WASD VMS Web Services, Copyright (C) 1996-2018 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=>string> 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|PASSIVE|CONFIG 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 /NOTE= annotate server process log with this string /[NO]ODS5 explicit extended file specification (testing only) /OUTPUT= to file /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 #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" #ifdef __ia64 /*** hmmm, elfdef.h preceding wasd.h results in: } LKIDEF; ......^ %CC-E-NOLINKAGE, In this declaration, "LKIDEF" has no linkage and has a prior declaration in this scope at line number *** in file WASD_ROOT:[SRC.HTTPD]WASD.H;1. at line number 81 in module LKIDEF of text library SYS$COMMON:[SYSLIB]SYS$STARLET_C.TLB;1 (HP C V7.1-011 on OpenVMS IA64 V8.3-1H1) (VSI C V7.4-001 on OpenVMS IA64 V8.4-2L1) ***/ #include #endif /* 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. */ ulong AltPriMask [QUAD2] = { PRV$M_ALTPRI, 0 }, AveJoePrvMask [QUAD2] = { PRV$M_NETMBX | PRV$M_TMPMBX, 0 }, CrePrcMask [QUAD2] = { PRV$M_DETACH | PRV$M_SYSPRV, 0 }, DetachMask [QUAD2] = { PRV$M_DETACH, 0 }, #ifdef __VAX GblSecPrvMask [QUAD2] = { PRV$M_SYSPRV | PRV$M_SYSGBL | PRV$M_PRMGBL | PRV$M_SHMEM, 0 }, #else /* Alpha or ia64 */ GblSecPrvMask [QUAD2] = { PRV$M_SYSPRV | PRV$M_SYSGBL | PRV$M_PRMGBL, 0 }, #endif /* #ifdef VAX */ // GrantIdMask [QUAD2] = { PRV$M_CMKRNL | PRV$M_WORLD, 0 }, GrantIdMask [QUAD2] = { PRV$M_CMKRNL | PRV$M_WORLD | PRV$M_DIAGNOSE, 0 }, // MailboxMask [QUAD2] = { PRV$M_WORLD | PRV$M_SYSPRV, 0 }, MailboxMask [QUAD2] = { PRV$M_WORLD | PRV$M_SYSPRV | PRV$M_OPER, 0 }, PrivAcctMask [QUAD2] = { PRV$M_SETPRV | PRV$M_SYSPRV, 0 }, PswapmMask [QUAD2] = { PRV$M_PSWAPM, 0 }, ResetPrvMask [QUAD2] = { 0xffffffff, 0xffffffff }, SecurityMask [QUAD2] = { 0, 0x40 /* PRV$M_SECURITY */ }, SysLckMask [QUAD2] = { PRV$M_SYSLCK, 0 }, SysNamMask [QUAD2] = { PRV$M_SYSNAM, 0 }, SysPrvMask [QUAD2] = { PRV$M_SYSPRV, 0 }, #if 1 WorldMask [QUAD2] = { PRV$M_WORLD, 0 }; #else WorldMask [QUAD2] = { PRV$M_WORLD | PRV$M_SHARE, 0 }; #endif /* 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, ProcessPriority = 4, ServerPort = 80; ulong HttpdTime64 [QUAD2], HttpdStartTime64 [QUAD2]; ushort HttpdNumTime [7]; char HttpdScriptAsUserName [64], HttpdServerNote [256], NaturalLanguage [64], ProcessIdentName [32], 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, } }; /* Seconds at which the network transfer statistics are updated. Up until v10.4.0 this was set at 17. WASDMON requires per-second updates of network data. Reducing it to 1 should not have a significant impact forr most environments. It it does, well just bump it back up and recompile! */ #define SUPERVISOR_NETWORK_UPDATE 1 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, CliInstanceConfig, CliLoggingDisabled, CliLoggingEnabled, CliMonitorDisabled, CliMonitorEnabled, CliNetworkMode, CliNoNetworkMode, CliInstanceNoCrePrc, CliOdsExtendedEnabled, CliOdsExtendedDisabled, CliSysPlus, CliTests, CliVersion, ControlDoAllHttpd, DclScriptDetachProcess, LoggingEnabled, OdsExtended, ProtocolHttpsAvailable, ProxyServingEnabled; extern int ActivityTotalMinutes, CliServerPort, CliLockValueBlockSize, HttpdGblSecVersion, NetConcurrentMax, InstanceEnvNumber, InstanceNumber, OpcomMessages, OpcomTarget, RequestHistoryMax; extern int ToLowerCase[], ToUpperCase[]; extern char BuildDateTime[], BuildInfo[], CliLogFileName[], CliParameter[], CliProcessIdentName[], CliProxyMaint[], CliScriptAs[], CliServices[], CliUserName[], CommandLine[], ControlBuffer[], ErrorSanityCheck[], HttpdIpPackage[], HttpdSesola[], HttpdVersion[], LoggingFileName[], Services[], SoftwareID[], TcpIpAgentInfo[], TimeGmtString[], WatchFuncCc[]; extern char *CliOutputPtr; extern CONFIG_STRUCT Config; extern LIST_HEAD RequestList; extern INSTANCE_STATUS *InstanceStatusTablePtr; extern META_CONFIG *MetaGlobalConfigPtr; extern META_CONFIG *MetaGlobalServicePtr; extern META_CONFIG *MetaGlobalMsgPtr; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* */ void main () { void *close_enough_to_the_top = 0xDEFEC8ED; int idx, status, GblSecDeleteCount; char *cptr, *sptr, *zptr; $DESCRIPTOR (NaturalLanguageDsc, NaturalLanguage); ODS_STRUCT WasdRootOds; /*********/ /* begin */ /*********/ /* initialize virtual memory management */ VmInit (); /* generate version information */ VersionInfo (); /* 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 (CliOutputPtr) if (!(stdout = freopen (CliOutputPtr, "w", stdout))) exit (vaxc$errno); if (Watch.CliEnabled) WatchCliSettings (false); if (!InstanceEnvNumber) { /* if WASD_ENV defined then use this value in the absence of CLI value */ if (cptr = SysTrnLnm ("WASD_ENV")) InstanceEnvNumber = atoi (cptr); else InstanceEnvNumber = INSTANCE_ENV_NUMBER_DEFAULT; } /* 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 (CliInstanceConfig) 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 (HttpdServerNote[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, "DICTIONARY", 4)) { DictTest (CliParameter+4); exit (SS$_NORMAL); } #if ODS_DIRECT if (strsame (CliParameter, "ODS=DIRECT=", 11) || strsame (CliParameter, "ODS=SEARCH=", 11)) { OdsDirectPerf (CliParameter+4); exit (SS$_NORMAL); } #endif /* ODS_DIRECT */ if (strsame (CliParameter, "MATCH", 5)) { MatchPerf (); exit (SS$_NORMAL); } if (strsame (CliParameter, "MKCERT", 6)) { cptr = SesolaMkCert (); FaoToStdout ("!AZ", cptr); 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 (CliSysPlus) { /**********************/ /* CLI system+ report */ /**********************/ sysPlusReport (NULL); exit (SS$_NORMAL); } if (CliDetach) { /****************************/ /* create a detached server */ /****************************/ exit (HttpdDetachServerProcess ()); } if (CliProxyMaint[0]) { /**********************/ /* proxy maintainance */ /**********************/ exit (ProxyMaintCli ()); } if (CliInstanceConfig) { /*****************************/ /* ensure config values used */ /*****************************/ HttpdGblSecInit (); exit (InstanceUseConfig()); } 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); } /* initialize the resources and locks for multi-instance processing */ InstanceLockInit (); if (HttpdServerNote[0]) { /***************************/ /* annotate the server log */ /***************************/ status = ControlCommand (HttpdServerNote, !ControlBuffer[0]); /* can be used standalone or in conjunction with /DO=.. */ if (!ControlBuffer[0] || VMSnok (status)) exit (status); } if (ControlBuffer[0]) { /*****************************/ /* control the HTTPd process */ /*****************************/ exit (ControlCommand (ControlBuffer, true)); } /***************************/ /* software and image info */ /***************************/ /* output the GNU GENERAL PUBLIC LICENSE message */ FaoToStdout ("%HTTPD-I-SOFTWAREID, !AZ\n!AZ", SoftwareID, CopyRightMessageBrief); HttpdImageInfo (); FaoToStdout ("%HTTPD-I-IMAGE, !AZ\n", HttpdProcess.ImageInfo); if (CliVersion) { FaoToStdout ("-HTTPD-I-BUILD, !AZ, CC (!#AZ/!UL) !AZ\n", BuildDateTime, strchr(__VMS_VERSION, ' ') - __VMS_VERSION, __VMS_VERSION, __DECC_VER, WatchFuncCc); TcpIpSetAgentInfo (); FaoToStdout ("-HTTPD-I-SYSTEM, !AZ !UL CPU!%s !ULMB VMS !AZ\n", SysInfo.HwName, SysInfo.AvailCpuCnt, SysInfo.MemoryMB, SysInfo.Version); FaoToStdout ("-HTTPD-I-TCPIP, !AZ\n", TcpIpAgentInfo); FaoToStdout ("-HTTPD-I-TLS, !AZ!AZ\n", cptr = SesolaVersion(true), *cptr ? "" : "none"); status = SetGlobalSymbol ("HTTPD_VERSION", HTTPD_VERSION); if (VMSnok(status)) exit (status); 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"); } } /****************/ /* HTTPd server */ /****************/ /* failsafe for runaway startups (yup, happened with multi-instances) */ if (SysTrnLnm ("WASD_STARTUP_ABORT")) sys$delprc (0, 0); HttpdServerExecuting = HttpdServerStartup = true; /* initialize ticker */ HttpdTick (0); FaoToStdout ("%HTTPD-I-STARTUP, !20%D\n", &HttpdStartTime64); /* start collection alignment faults as early as possible */ HttpdAlignFault ("START"); cptr = v10orPrev10(CONFIG_WASD_ROOT,-1); OdsStructInit (&WasdRootOds, true); 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 */ if (!CliDemo) HttpdGblSecInit (); /* make any adjustments required after configuration load */ InstanceFinalInit (); /* initialize Secure Sockets Layer, if available and if required */ SesolaInit (); /* initialise (or not) HTTP/2 processing */ Http2Init (); /* 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 (WATCHALL, 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); } /* 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); } /* 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 (); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); /* cancel any startup messages provided for the monitor */ HttpdGblSecPtr->StatusMessage[0] = '\0'; /* Perhaps a little bit too clever ... ALWAYS initialises the zeroeth (instances not enabled) element. If instances are NOT enabled then zeroes all the rest too. If instances ARE enabled then zeroes only the element belonging to it. */ for (idx = 0;;) { AccountingPtr->CurrentInstanceConnected[HTTP12][idx] = AccountingPtr->CurrentInstanceConnected[HTTP1][idx] = AccountingPtr->CurrentInstanceConnected[HTTP2][idx] = AccountingPtr->CurrentInstanceProcessing[HTTP12][idx] = AccountingPtr->CurrentInstanceProcessing[HTTP1][idx] = AccountingPtr->CurrentInstanceProcessing[HTTP2][idx] = 0; AccountingPtr->CurrentDclScriptCgiPlus[idx] = AccountingPtr->CurrentDclScriptProcess[idx] = AccountingPtr->CurrentDclScriptRTE[idx] = AccountingPtr->CurrentDECnetTasks[idx] = AccountingPtr->CurrentDECnetCGI[idx] = AccountingPtr->CurrentDECnetOSU[idx] = AccountingPtr->CurrentPersistentHttp1[idx] = AccountingPtr->CurrentThrottleProcessing[idx] = AccountingPtr->CurrentThrottleQueued[idx] = AccountingPtr->CurrentWebSockets[idx] = 0; if (InstanceNumber) if (idx == InstanceNumber) break; else idx = InstanceNumber; else if (idx++ >= INSTANCE_MAX) break; } NetUpdateConnected (NULL, 0); NetUpdateProcessing (NULL, 0); AccountingPtr->InstanceNodeData[InstanceNumber].StartupCount++; PUT_QUAD_QUAD (&HttpdStartTime64, &AccountingPtr->InstanceNodeData[InstanceNumber].StartTime64); AccountingPtr->StartupCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); 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; } HttpdSysProtProcDmp (); HttpdServerStartup = false; /*****************************/ /* begin to process requests */ /*****************************/ FaoToStdout ("%HTTPD-I-BEGIN, !20%D, !AZ accepting requests\n", 0, HttpdProcess.PrcNam); /* ready to accept requests */ InstanceReady (); /* if WATCHing of the startup was suppressed activate from here-on */ if (Watch.CliEnabled) WatchCliSettings (true); /* queue accepts on all services (as an AST so no interruptions) */ SysDclAst (NetAcceptBegin, 0); /* 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); } /*****************************************************************************/ /* Determine whether a function has been called as an AST or directly in a code path. Three contexts are involved; 1) this function, 2) call to this function, 3) calling function. Hence the two previous contexts. If the third context is from system space then it's (in the context of this WASD usage) an AST. For 64 bit platforms use the LIB$ API. For VAX use a solution cobbled together from fragments out there on the 'net. For test see "/$/HttpdIsAstCall/" in RequestParseExecute(). */ BOOL HttpdIsAstCall () { #ifdef __ALPHA int retval; struct invo_context_blk icb; /*********/ /* begin */ /*********/ lib$get_curr_invo_context (&icb); retval = lib$get_prev_invo_context (&icb); if (retval) retval = lib$get_prev_invo_context (&icb); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdIsAstCall() Alpha !UL !UL !6XL !&B !8XL!8XL", retval, icb.libicb$l_context_length, icb.libicb$v_fflags_bits, icb.libicb$v_ast_frame, (ulong*)icb.libicb$q_program_counter[1], (ulong*)icb.libicb$q_program_counter[0]); if (retval) return ((ulong*)icb.libicb$q_program_counter[1] != 0); else return (false); #endif /* __ALPHA */ #ifdef __ia64 int retval; struct invo_context_blk icb; /*********/ /* begin */ /*********/ lib$i64_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION); lib$i64_get_curr_invo_context (&icb); retval = lib$i64_get_prev_invo_context (&icb); if (retval) retval = lib$i64_get_prev_invo_context (&icb); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdIsAstCall() ia64 !UL !UL !6XL !8XL!8XL", retval, icb.libicb$l_context_length, icb.libicb$v_fflags_bits, ((ulong*)&icb.libicb$ih_pc)[1], ((ulong*)&icb.libicb$ih_pc)[0]); if (retval) return (((ulong*)&icb.libicb$ih_pc)[1] != 0); else return (false); #endif /* __ia64 */ #ifdef __VAX uint *frptr, *pcptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdIsAstCall()"); frptr = (int*)cma$exc_fetch_fp(); if (_PROBER (3, sizeof(uint), frptr) == 1) { /* current call frame */ frptr = (uint*)frptr[3]; pcptr = (uint*)frptr[4]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdIsAstCall() 1 fr:!8XL pc:!8XL", frptr, pcptr); if (_PROBER (3, sizeof(uint), frptr) == 1) { /* call to this function */ frptr = (uint*)frptr[3]; pcptr = (uint*)frptr[4]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdIsAstCall() 2 fr:!8XL pc:!8XL", frptr, pcptr); if (_PROBER (3, sizeof(uint), frptr) == 1) { /* call to calling function */ frptr = (uint*)frptr[3]; pcptr = (uint*)frptr[4]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdIsAstCall() 3 fr:!8XL pc:!8XL", frptr, pcptr); return (((uint)pcptr & 0x8000000) != 0); } } } return (false); #endif /* __VAX */ } /*****************************************************************************/ /* Write the supplied text to the server process . */ void HttpdNoteThis (char* note) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdNoteThis() !AZ", note); if (!note || !note[0]) return; FaoToStdout ("%HTTPD-I-NOTE, !20%D, !AZ\n", 0, note); } /*****************************************************************************/ /* Equivalent of $ DEFINE /EXEC SYS$PROTECTED_PROCDMP SYS$LOGIN: */ void HttpdSysProtProcDmp () { static $DESCRIPTOR (LogNameDsc, "SYS$PROTECTED_PROCDMP"); static $DESCRIPTOR (LogTableDsc, "LNM$PROCESS"); static char SysErrLog [] = "SYS$LOGIN:"; static uchar ExecMode = 1; /* PSL$C_EXEC */ static struct { short int buf_len; short int item; unsigned char *buf_addr; unsigned short *ret_len; } CreLnmItem [2]; int status; /*********/ /* begin */ /*********/ CreLnmItem[0].item = LNM$_STRING; CreLnmItem[0].buf_addr = SysErrLog; CreLnmItem[0].buf_len = sizeof(SysErrLog)-1; if (VMSnok (status = sys$setprv (1, &SysNamMask, 0, 0))) ErrorExitVmsStatus (status, "sys$setprv()", FI_LI); status = sys$crelnm (0, &LogTableDsc, &LogNameDsc, &ExecMode, &CreLnmItem); if (VMSnok(status)) exit (status); if (VMSnok (status = sys$setprv (0, &SysNamMask, 0, 0))) ErrorExitVmsStatus (status, "sys$setprv()", FI_LI); } /*****************************************************************************/ /* Get required system information. */ HttpdSystemInfo () { static $DESCRIPTOR (DECnetDeviceDsc, "_NET:"); static $DESCRIPTOR (NetStartupStatusDsc, "NET$STARTUP_STATUS"); static $DESCRIPTOR (LnmSystemTableDsc, "LNM$SYSTEM_TABLE"); static ushort 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.BootTime64), SYI$_BOOTTIME, &SysInfo.BootTime64, 0 }, { sizeof(SysInfo.ClusterMember), SYI$_CLUSTER_MEMBER, &SysInfo.ClusterMember, 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; ushort DECnetChannel; ulong CsidAdr; char *cptr, *sptr; IO_SB IOsb; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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'; while (sptr > SysInfo.HwName && *(sptr-1) == ' ') sptr--; *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 (WATCHALL, 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 (WATCHALL, 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 (WATCHALL, 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 ushort 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 (WATCHALL, 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 (WATCHALL, 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 ulong Context = -1; static ulong UaiFlags, UaiUic; static ulong UaiPriv [QUAD2]; static struct { ushort buf_len; ushort item; uchar *buf_addr; ushort *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 (WATCHALL, 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 (WATCHALL, 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 ulong 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 ushort Length; int idx, status; uint ProcessPid; ushort slen; char *cptr, *sptr, *zptr; char LogFileName [256], SysCommand [256]; ODS_STRUCT SysCommandOds, SysOutputOds; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdDetachServerProcess()"); sys$gettim (&HttpdTime64); sys$numtim (&HttpdNumTime, &HttpdTime64); 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); } } OdsStructInit (&SysCommandOds, true); 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]); OdsStructInit (&SysOutputOds, true); 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 (ulong *ExitStatusPtr) { static $DESCRIPTOR (ErrorFaoDsc, "%HTTPD-F-EXIT, !AZ, !AZ %X!8XL\r\n-!AZ\0"); static $DESCRIPTOR (DateTimeFaoDsc, "!20%D\0"); static char DateTime [32]; static $DESCRIPTOR (DateTimeDsc, DateTime); int status; ushort Length; ulong 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 (WATCHALL, WATCH_MOD__OTHER, "HttpdExit() !&S", *ExitStatusPtr); /* exit handler executes in non-AST context so disable */ sys$setast (0); ExitStatus = *ExitStatusPtr; /* run down any script processes associated with script processing */ DclExit (); /* ensure any proxy verify records still in use are cleared */ ProxyVerifyInit (); /* Q&D flush SYS$OUTPUT */ fsync (STDOUT_FILENO); 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->LastExitTime64); sys$fao (&DateTimeFaoDsc, NULL, &DateTimeDsc, 0); status = sys$getmsg (ExitStatus, &Length, &MsgStringDsc, 15, 0); if (VMSok (status)) MsgString[Length] = '\0'; if (VMSnok (ExitStatus)) { /**************/ /* error exit */ /**************/ StringDsc.dsc$w_length = sizeof(HttpdGblSecPtr->StatusMessage)-1; StringDsc.dsc$a_pointer = HttpdGblSecPtr->StatusMessage; sys$fao (&ErrorFaoDsc, NULL, &StringDsc, DateTime, HttpdProcess.PrcNam, ExitStatus, MsgString+1); /* record server event */ GraphActivityEvent (ACTIVITY_EXIT_ERROR); /* report to process log */ fprintf (stdout, "%%HTTPD-F-EXIT, %s, %s %%X%08.08X\n", DateTime, HttpdProcess.PrcNam, ExitStatus); if (ExitStatus != SS_W_CONTROLY) { /* add traceback information */ int SanityCheck = 100; #ifdef __ALPHA lib$get_curr_invo_context (&icb); while (lib$get_prev_invo_context (&icb), !icb.libicb$v_bottom_of_stack && SanityCheck--) 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 && SanityCheck--) 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 (); } } if (VMSok (ExitStatus)) { /***************/ /* normal exit */ /***************/ /* record server exit event */ GraphActivityEvent (ACTIVITY_EXIT); fprintf (stdout, "%%HTTPD-I-EXIT, %s, %s %%X%08.08X\n-%s\n", DateTime, HttpdProcess.PrcNam, ExitStatus, MsgString+1); #if WATCH_MOD HttpdStackTrace (FI_LI); RequestDump (); #endif } if (OpcomMessages) { /*********/ /* opcom */ /*********/ struct { ulong TargetType; ulong RequestId; char MsgText [986+1]; } OpcomMsg; $DESCRIPTOR (OpcomDsc, ""); $DESCRIPTOR (OpcomFaoDsc, "Process !AZ reports\r\n" "%HTTPD-!AZ-EXIT, !AZ, %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", DateTime, 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); } InstanceStatusExit (ExitStatus); if (VMSnok (ExitStatus)) fputs ("-HTTPD-F-ADIEU, ...\n", stdout); fflush (stdout); /* anything that needs to be explicitly done during instance shutdown */ InstanceExit (); /* reenable user mode ASTs */ sys$setast (1); } /*****************************************************************************/ /* Trace of call stack for debug purposes. Include call in code to use :-) */ void HttpdStackTrace ( char* module, int line ) { static char buf [32]; static $DESCRIPTOR (TimeBufDsc, buf); static $DESCRIPTOR (TimeFaoDsc, "!8%T\0"); int status; #ifndef __VAX struct invo_context_blk icb; #endif /*********/ /* begin */ /*********/ sys$fao (&TimeFaoDsc, 0, &TimeBufDsc, 0); fprintf (stdout, "TRACE+++++++++++ %s:%d %s\n", module, line, buf); fflush (stdout); #ifdef __ALPHA lib$get_curr_invo_context (&icb); while (lib$get_prev_invo_context (&icb), !icb.libicb$v_bottom_of_stack) fprintf (stdout, "%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, "%08.08X%08.08X\n", ((ULONGPTR)&icb.libicb$ih_pc)[1], ((ULONGPTR)&icb.libicb$ih_pc)[0]); #endif #ifdef __VAX fprintf (stdout, "***UNAVAILABLE*** for VAX\n"); #endif fprintf (stdout, "+++++++++++TRACE\n"); fflush (stdout); } /*****************************************************************************/ /* 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 uchar *GetBufferPtr, *ReportBufferPtr; static struct FaultDataStruct ItemData; static struct FaultAccumStruct *ItemArrayPtr; int idx, status; ulong pc; ulong GetBufferLength = 0; uchar *afrptr, *zfrptr; char *cptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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++) ((ushort*)(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 (WATCHALL, 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; ulong pc; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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) { #ifndef __VAX static char BeginTableFao [] = "

\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; uint SecondsElapsed; struct FaultDataStruct *ItemDataPtr; struct FaultAccumStruct *ItemArrayPtr = NULL; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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 AdminEnd (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 (ulong pc) { #ifndef __VAX static BOOL LoadMap = true; static char *CodePtr; static char PcData [64]; static ODS_STRUCT MapFileOds; int status; ulong end, start; char *cptr, *sptr, *zptr; char ModuleName [32]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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 ulong Mask = LIB$M_CLI_CTRLY, OldMask; static ushort TTChannel = 0; static IO_SB IOsb; int status; $DESCRIPTOR (TTDsc, "TT:"); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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); } /*****************************************************************************/ /* Server exit if more than expected privileges. */ void HttpdCheckPriv ( char *SourceModuleName, int SourceLineNumber ) { ulong *pmptr; char Buffer [128]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdCheckPriv()"); if (!(pmptr = HttpdExpectedPriv())) return; HttpdStackTrace (FI_LI); FaoToBuffer (Buffer, sizeof(Buffer), 0, "privilege sanity check: <63-32>!8XL<31-00>!8XL", pmptr[0], pmptr[0]); ErrorExitVmsStatus (SS$_BUGCHECK, Buffer, SourceModuleName, SourceLineNumber); } /*****************************************************************************/ /* 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 ensure everthing's off that's supposed to be off. */ ulong* HttpdExpectedPriv () { static ulong CurrPriv [2]; int status; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdExpectedPriv()"); if (VMSnok (status = sys$setprv (1, &AveJoePrvMask, 0, &CurrPriv))) ErrorExitVmsStatus (status, "sys$setprv()", FI_LI); if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchDataFormatted ("!&X !&X\n", CurrPriv[1], CurrPriv[0]); /* if only NETMBX and TMPMBX enabled then ok */ if (!(CurrPriv[0] & ~AveJoePrvMask[0] || CurrPriv[1] & ~AveJoePrvMask[1])) return (NULL); #if OPERATE_WITH_SYSPRV /* if operating with SYSPRV and it's enabled then ok */ if (OperateWithSysPrv && (CurrPriv[0] & ~AveJoePrvMask[0] == SysPrvMask[0])) return (NULL); #endif return (CurrPriv); } /*****************************************************************************/ /* 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 ulong OneSecondDelta [QUAD2] = { -10000000, -1 }; static ulong OneMinuteDelta [QUAD2] = { -600000000, -1 }; static ushort PrevDay = -1, PrevHour = -1, PrevMinute = -1; int status; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdTick() !&X", reqidt); sys$gettim (&HttpdTime64); sys$numtim (&HttpdNumTime, &HttpdTime64); HttpdTickSecond = decc$fix_time (&HttpdTime64); if (QUAD_ZERO (&HttpdStartTime64)) { /* initialise */ sys$gettim (&HttpdStartTime64); if (VMSnok (status = TimeSetGMT ())) ErrorExitVmsStatus (status, "TimeSetGMT()", FI_LI); } if (PrevDay != HttpdNumTime[2]) { PrevDay = HttpdNumTime[2]; lib$day_of_week (&HttpdTime64, &HttpdDayOfWeek); } if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "TICK !UL !%D", HttpdTickSecond, &HttpdTime64); if (Watch.RequestPtr) WatchWrite (NULL, 0); if (HttpdServerStartup) HttpdTicking = true; else { InstanceStatusUpdate (NULL); /* (if active) accumulate alignment fault data */ if (HttpdAlignFaultReport) HttpdAlignFault (NULL); HttpdTicking = false; if (HttpdSupervisor ()) HttpdTicking = true; if (NetAcceptSupervisor ()) HttpdTicking = true; if (Http2Supervisor ()) HttpdTicking = true; if (InstanceSupervisor ()) HttpdTicking = true; if (ProxyNetConnectSupervisor (-1)) HttpdTicking = true; if (DclSupervisor (-1)) HttpdTicking = true; if (DECnetSupervisor (-1)) HttpdTicking = true; if (PrevMinute != HttpdNumTime[4]) { /****************/ /* every minute */ /****************/ if (PrevMinute >= 0) { /* after the first minute */ VmCheckPgFlLimit (); sysPlusRmi ((REQUEST_STRUCT*)-1); sysPlusNet ((REQUEST_STRUCT*)-1); #if !WATCH_MOD if (SysInfo.ClusterMember) #endif { sysPlusScs ((REQUEST_STRUCT*)-1); sysPlusMscp ((REQUEST_STRUCT*)-1); } #if WATCH_MOD NetTestSupervisor (); #endif ProxyMaintSupervisor (); ThrottleMonitorReset (); TcpIpHostCacheSupervisor (HttpdTickSecond); Logging (NULL, LOGGING_FLUSH); } PrevMinute = HttpdNumTime[4]; if (PrevHour != HttpdNumTime[3]) { /**************/ /* every hour */ /**************/ if (PrevHour >= 0) { /* after the first hour */ Logging (NULL, LOGGING_TIMESTAMP); Logging (NULL, LOGGING_SHUT); if (VMSnok (status = TimeSetGMT ())) ErrorExitVmsStatus (status, "TimeSetGMT()", FI_LI); } PrevHour = HttpdNumTime[3]; } } } if (HttpdTicking) { if (reqidt != &OneSecondDelta) sys$cantim (&OneSecondDelta, 0); status = sys$setimr (0, &OneSecondDelta, &HttpdTick, &OneSecondDelta, 0); } else { if (reqidt != &OneMinuteDelta) sys$cantim (&OneMinuteDelta, 0); status = sys$setimr (0, &OneMinuteDelta, &HttpdTick, &OneMinuteDelta, 0); } if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$setimr", FI_LI); } /*****************************************************************************/ /* 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 -1 then the timer expires at the next supervision (i.e. immediately). If otherwise 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 ulong InputSeconds, PersistentSeconds, NoProgressPeriod, OutputSeconds; int idx, status, TimerSeconds; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdTimerSet() !UL !SL", 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++; } /* once the request has been terminated (and aborted) that's it! */ if (rqptr->rqTmr.TimeoutCount) return; 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.TerminateSecond = rqptr->rqTmr.TimeoutType = rqptr->rqTmr.ThrottleSecond = 0; if (DurationSeconds) TimerSeconds = DurationSeconds; else TimerSeconds = InputSeconds; if (WATCHING (rqptr, WATCH_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER input !SL seconds", TimerSeconds); rqptr->rqTmr.InputSecond = HttpdTickSecond + TimerSeconds + 1; break; case TIMER_OUTPUT : rqptr->rqTmr.InputSecond = rqptr->rqTmr.PersistentSecond = rqptr->rqTmr.TerminateSecond = rqptr->rqTmr.TimeoutType = rqptr->rqTmr.ThrottleSecond = 0; /* establish the baseline no-progress indicators (lsb) */ rqptr->rqTmr.NoProgressBytesRx = rqptr->NetIoPtr->BytesRawRx[0]; rqptr->rqTmr.NoProgressBytesTx = rqptr->NetIoPtr->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_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER output !SL/!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.TerminateSecond = rqptr->rqTmr.TimeoutType = rqptr->rqTmr.ThrottleSecond = 0; /* establish the baseline no-progress indicators (lsb) */ rqptr->rqTmr.NoProgressBytesRx = rqptr->NetIoPtr->BytesRawRx[0]; rqptr->rqTmr.NoProgressBytesTx = rqptr->NetIoPtr->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_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER no-progress !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.TerminateSecond = rqptr->rqTmr.TimeoutType = rqptr->rqTmr.ThrottleSecond = 0; if (DurationSeconds) TimerSeconds = DurationSeconds; else TimerSeconds = PersistentSeconds; if (WATCHING (rqptr, WATCH_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER persistent !SL 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 = rqptr->rqTmr.TerminateSecond = rqptr->rqTmr.TimeoutType = 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_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER throttle !SL seconds", TimerSeconds); rqptr->rqTmr.ThrottleSecond = HttpdTickSecond + TimerSeconds + 1; break; case TIMER_TERMINATE : rqptr->rqTmr.InputSecond = rqptr->rqTmr.PersistentSecond = rqptr->rqTmr.NoProgressBytesRx = rqptr->rqTmr.NoProgressBytesTx = rqptr->rqTmr.NoProgressSecond = rqptr->rqTmr.NoProgressPeriod = rqptr->rqTmr.OutputSecond = rqptr->rqTmr.ThrottleSecond = rqptr->rqTmr.TimeoutType = 0; if (WATCHING (rqptr, WATCH_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER terminate"); if (DurationSeconds) TimerSeconds = DurationSeconds; else TimerSeconds = 0; rqptr->rqTmr.TerminateSecond = HttpdTickSecond + TimerSeconds + 1; break; default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } /* add/move to new list */ HttpdSupervisorList (rqptr, TimerSeconds); rqptr->rqTmr.TimeoutCount = 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, retval, ChunkSeconds, DeltaSecondCount, TimerSecond, TimeoutType; uint cat; LIST_ENTRY *leptr; NETIO_STRUCT *ioptr; PROXY_TASK *ptkptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ /** if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) { WatchThis (WATCHALL, 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 == NULL) return (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; ioptr = rqeptr->NetIoPtr; if (QUAD_ZERO(ioptr->BytesTallyRx) && QUAD_ZERO(ioptr->BytesTallyTx)) continue; /* update activity first, it uses the bytes accounted-for value */ if (ActivityTotalMinutes) GraphActivityUpdate (rqeptr, false); /* update using the running tally accumulators */ ADD_QUAD_QUAD (ioptr->BytesTallyRx, AccountingPtr->BytesRawRx); ADD_QUAD_QUAD (ioptr->BytesTallyTx, AccountingPtr->BytesRawTx); ADD_QUAD_QUAD (ioptr->BlocksTallyTx, AccountingPtr->BlocksRawTx); ADD_QUAD_QUAD (ioptr->BlocksTallyRx, AccountingPtr->BlocksRawRx); /* reset the running tally accumulators */ PUT_ZERO_QUAD (ioptr->BytesTallyRx); PUT_ZERO_QUAD (ioptr->BytesTallyTx); PUT_ZERO_QUAD (ioptr->BlocksTallyRx); PUT_ZERO_QUAD (ioptr->BlocksTallyTx); if (ptkptr = (PROXY_TASK*)rqeptr->ProxyTaskPtr) { if ((ioptr = ptkptr->NetIoPtr) == NULL) continue; if (QUAD_ZERO(ioptr->BytesTallyRx) && QUAD_ZERO(ioptr->BytesTallyTx)) continue; /* update proxy using the running tally accumulators */ ADD_QUAD_QUAD (ioptr->BytesTallyRx, ProxyAccountingPtr->BytesRawRx); ADD_QUAD_QUAD (ioptr->BytesTallyTx, ProxyAccountingPtr->BytesRawTx); ADD_QUAD_QUAD (ioptr->BlocksTallyTx, ProxyAccountingPtr->BlocksRawTx); ADD_QUAD_QUAD (ioptr->BlocksTallyRx, ProxyAccountingPtr->BlocksRawRx); /* reset the running tally accumulators */ PUT_ZERO_QUAD (ioptr->BytesTallyRx); PUT_ZERO_QUAD (ioptr->BytesTallyTx); PUT_ZERO_QUAD (ioptr->BlocksTallyRx); PUT_ZERO_QUAD (ioptr->BlocksTallyTx); } } 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; leptr = leptr->NextPtr; /** if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, 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.TimeoutCount); **/ if (rqeptr->rqTmr.TimeoutCount) { rqeptr->rqTmr.TimeoutCount++; if (rqeptr->rqTmr.TimeoutCount == TIMEOUT_ABORT_SECONDS) NetAbortSocket (rqeptr); continue; } /* ordered in the most common occurance of timings */ if (rqeptr->rqTmr.OutputSecond) { /*********************/ /* output/noprogress */ /*********************/ if (rqeptr->rqTmr.NoProgressBytesTx != rqeptr->NetIoPtr->BytesRawTx[0] || rqeptr->rqTmr.NoProgressBytesRx != rqeptr->NetIoPtr->BytesRawRx[0]) { rqeptr->rqTmr.NoProgressSecond = HttpdTickSecond + rqeptr->rqTmr.NoProgressPeriod + 1; rqeptr->rqTmr.NoProgressBytesRx = rqeptr->NetIoPtr->BytesRawRx[0]; rqeptr->rqTmr.NoProgressBytesTx = rqeptr->NetIoPtr->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; TimeoutType = TIMEOUT_OUTPUT; } else { if (rqeptr->rqTmr.NoProgressSecond > DeltaSecondCount) continue; TimerSecond = rqeptr->rqTmr.NoProgressSecond; TimeoutType = TIMEOUT_NOPROGRESS; } if (TimerSecond > HttpdTickSecond) { /* move to new list */ HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond); continue; } rqeptr->rqTmr.TimeoutType = TimeoutType; /* fall through to shut down */ } else if (TimerSecond = rqeptr->rqTmr.InputSecond) { /*********/ /* input */ /*********/ if (TimerSecond > DeltaSecondCount) continue; if (TimerSecond > HttpdTickSecond) { /* move to new list */ HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond); continue; } rqeptr->rqTmr.TimeoutType = TIMEOUT_INPUT; /* fall through to shut down */ } else if (TimerSecond = rqeptr->rqTmr.PersistentSecond) { /*************************/ /* persistent connection */ /*************************/ if (TimerSecond > DeltaSecondCount) continue; if (TimerSecond > HttpdTickSecond) { /* move to new list */ HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond); continue; } rqeptr->rqTmr.TimeoutType = TIMEOUT_PERSISTENT; /* fall through to shut down */ } else if (TimerSecond = rqeptr->rqTmr.ThrottleSecond) { /************/ /* throttle */ /************/ if (TimerSecond > DeltaSecondCount) continue; if (TimerSecond > HttpdTickSecond) { /* move to new list */ HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond); continue; } if (WATCHING (rqeptr, WATCH_INTERNAL)) WatchThis (WATCHITM(rqeptr), WATCH_INTERNAL, "TIMER throttle EXPIRED"); /* does not fall through to shut down! */ continue; } else if (TimerSecond = rqeptr->rqTmr.TerminateSecond) { /*************/ /* terminate */ /*************/ if (TimerSecond > DeltaSecondCount) continue; if (TimerSecond > HttpdTickSecond) { /* move to new list */ HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond); continue; } rqeptr->rqTmr.TimeoutType = TIMEOUT_TERMINATE; /* fall through to shut down */ } /*************************/ /* shut down the request */ /*************************/ if ((WATCH_ITM(rqeptr) && WATCH_CATEGORY((cat = WATCH_CONNECT)) || WATCH_CATEGORY((cat = WATCH_INTERNAL)))) WatchThis (WATCHITM(rqeptr), cat, "TIMER !AZ", HttpdTimeoutType (rqeptr->rqTmr.TimeoutType)); if (Watch.RequestPtr == rqeptr) { WatchEnd (); continue; } rqeptr->rqTmr.TimeoutCount++; if (rqeptr->rqTmr.TimeoutType == TIMEOUT_INPUT) rqeptr->rqResponse.HttpStatus = 408; else if (Watch.RequestPtr != rqeptr) rqeptr->rqResponse.HttpStatus = 500; RequestAbort (rqeptr); } } return (true); } /*****************************************************************************/ /* Return a pointer to a string. */ char* HttpdTimeoutType (int type) { /*********/ /* begin */ /*********/ switch (type) { case TIMEOUT_NONE : return (""); case TIMEOUT_INPUT : return ("(t/o:input) "); case TIMEOUT_THROTTLE : return ("(t/o:throttle) "); case TIMEOUT_OUTPUT : return ("(t/o:output) "); case TIMEOUT_NOPROGRESS : return ("(t/o:noprogress) "); case TIMEOUT_PERSISTENT : return ("(t/o:persistent) "); case TIMEOUT_TERMINATE : return ("(t/o:terminate) "); default : return ("(t/o:unknown)"); } } /*****************************************************************************/ /* 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 (WATCHALL, WATCH_MOD__OTHER, "HttpdSupervisorList() !SL", 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, LIST_ENTRY_TYPE_TIMER); 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, LIST_ENTRY_TYPE_TIMER); rqptr->rqTmr.ListEntry.DataPtr = (void*)rqptr; rqptr->rqTmr.ListIndex = 1; } /*****************************************************************************/ /* Provide a report on the server supervisor (do tell!) */ HttpdSupervisorReport (REQUEST_STRUCT *rqptr) { static char BeginPage [] = "

\n\ \n\
\n\ \n\ \ \ \ \ \ \n\ \n"; static char ListFao [] = "\ \ \ \ \ \n"; static char EndOfPageFao [] = "\ \ \ \n\
ListSecondsCountScan
!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\ \n"; int idx, status, ChunkRange, ChunkSeconds, Count, CountTotal; ulong *vecptr; ulong FaoVector [32]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, 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); AdminEnd (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 ulong ProtectionMask = 0xff00; /* it is recommended to map into any virtual address in the region (P0) */ static ulong InAddr [2] = { 0x200, 0x200 }; int attempt, status, GblSecPages, PageCount; short ShortLength; ulong RetAddr [2]; char GblSecName [32]; $DESCRIPTOR (GblSecNameDsc, GblSecName); HTTPD_GBLSEC *gsptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, 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 (WATCHALL, WATCH_MOD__OTHER, "sys$crmpsc() !&S begin:!UL end:!UL bytes:!UL", status, RetAddr[0], RetAddr[1], RetAddr[1] - RetAddr[0]); 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)); if (CliGblSecNoPerm) { GblSectionCount++; GblPageCount += PageCount; } else { GblSectionPermCount++; GblPagePermCount += PageCount; } return (status); } /*****************************************************************************/ /* The above funtion *creates* and maps. This just maps. */ int HttpdGblSecMap () { /* system global section, map into first available virtual address */ static int MapFlags = SEC$M_WRT | SEC$M_SYSGBL | SEC$M_EXPREG; /* it is recommended to map into any virtual address in the region (P0) */ static unsigned long InAddr [2] = { 0x200, 0x200 }; static char HttpdGblSecName [32]; static $DESCRIPTOR (HttpdGblSecNameDsc, HttpdGblSecName); static $DESCRIPTOR (HttpdGblSecNameFaoDsc, GBLSEC_NAME_FAO); int status, ByteSize, PageSize; unsigned short ShortLength; unsigned long RetAddr [2]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdGblSecMap()"); AccountingPtr = HttpdGblSecPtr = NULL; sys$fao (&HttpdGblSecNameFaoDsc, &ShortLength, &HttpdGblSecNameDsc, HTTPD_NAME, HTTPD_GBLSEC_VERSION_NUMBER, InstanceEnvNumber, "HTTPD"); HttpdGblSecNameDsc.dsc$w_length = ShortLength; status = sys$mgblsc (&InAddr, &RetAddr, 0, MapFlags, &HttpdGblSecNameDsc, 0, 0); if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "sys$mgblsc() !AZ %X!8XL begin:!UL end:!UL bytes:!UL", HttpdGblSecName, status, RetAddr[0], RetAddr[1], RetAddr[1] - RetAddr[0]); if (VMSnok (status)) return (status); ByteSize = (RetAddr[1]+1) - RetAddr[0]; PageSize = (RetAddr[1]+1) - RetAddr[0] >> 9; HttpdGblSecPtr = (HTTPD_GBLSEC*)RetAddr[0]; if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER || HttpdGblSecPtr->GblSecLength != sizeof(HTTPD_GBLSEC)) return (SS$_GBLSEC_MISMATCH); AccountingPtr = &HttpdGblSecPtr->Accounting; return (SS$_NORMAL); } /*****************************************************************************/ /* Populate |HttpdProcess.ImageInfo| with a string that identifies the actual image in use. This can then be used in log entries, process output, etc. IA64 ELF image analysis plagiarised from: // File: elf_imginfo.c // Author: Hartmut Becker // Creation Date: 24-Sep-2004 */ void HttpdImageInfo () { static int ImagNameLength; static char ImagName [256]; static VMS_ITEM_LIST3 JpiItems [] = { { sizeof(ImagName), JPI$_IMAGNAME, &ImagName, &ImagNameLength }, { 0,0,0,0 } }; #ifdef __ia64 static const char MagicPlus [] = { EHDR$K_ELFMAG0, EHDR$K_ELFMAG1, EHDR$K_ELFMAG2, EHDR$K_ELFMAG3, EHDR$K_ELFCLASS64, EHDR$K_ELFDATA2LSB }; #endif int status; ushort Length; char *cptr, *ImageDatePtr, *ImageIdentPtr, *ImageNamePtr; char ErrorInfo [256], ImageRecord [512]; ulong Genesis [2]; FILE *ImageFile; IO_SB IOsb; #ifdef __ia64 int cnt; ulong ImageDate [2]; char *secptr = NULL; ELF64_EHDR ElfHeader; ELF64_SHDR *shptr = NULL; ELF64_NHDR *nhptr; #endif /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "HttpdImageInfo()"); memset (ErrorInfo, 0, sizeof(ErrorInfo)); /* get the image name */ 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.ImageInfo[ImagNameLength] = '\0'; /* HttpdProcessInfo() uses this to obtain image name then space is reused */ ImageFile = fopen (ImagName, "r", "shr=get", "dna=WASD_EXE:.EXE"); if (!ImageFile) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } #ifndef __ia64 if (fread (&ImageRecord, sizeof(ImageRecord), 1, ImageFile) != 1) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } #endif #ifdef __ALPHA ImageNamePtr = ImageRecord + 200; ImageIdentPtr = ImageRecord + 240; ImageDatePtr = ImageRecord + 192; #endif #ifdef __ia64 ImageNamePtr = ImageIdentPtr = "?"; ImageDatePtr = 0; if (fread (&ElfHeader, sizeof(ElfHeader), 1, ImageFile) != 1) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } if (strncmp ((char*)&ElfHeader.ehdr$t_e_ident, MagicPlus, sizeof(MagicPlus)-1)) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "ELF file format? (!UL)", __LINE__); goto ImageIdentFailed; } shptr = (ELF64_SHDR*) malloc (sizeof *shptr *ElfHeader.ehdr$w_e_shnum); if (fseek (ImageFile, ElfHeader.ehdr$q_e_shoff, SEEK_SET)) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } if (fread (shptr, sizeof *shptr*ElfHeader.ehdr$w_e_shnum, 1, ImageFile) != 1) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } for (cnt = 1; cnt < ElfHeader.ehdr$w_e_shnum; cnt++) { if (shptr[cnt].shdr$l_sh_type == SHDR$K_SHT_NOTE) { secptr = malloc (shptr[cnt].shdr$q_sh_size); if (fseek (ImageFile, shptr[cnt].shdr$q_sh_offset, SEEK_SET)) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } if (fread (secptr, shptr[cnt].shdr$q_sh_size, 1, ImageFile) != 1) { FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length, "%X!8XL(!UL)", vaxc$errno, __LINE__); goto ImageIdentFailed; } for (nhptr = (ELF64_NHDR*)secptr; (char*)nhptr-secptr < shptr[cnt].shdr$q_sh_size; nhptr = (ELF64_NHDR*)((char*)nhptr+sizeof(ELF64_NHDR)+ ((nhptr->nhdr$q_nh_namesz+7)&~7)+ ((nhptr->nhdr$q_nh_descsz+7)&~7))) { if (nhptr->nhdr$q_nh_descsz > 0) { switch (nhptr->nhdr$q_nh_type) { case NHDR$K_NT_VMS_LINKTIME: ImageDatePtr = (ULONGPTR) ((char*)nhptr+sizeof(ELF64_NHDR) + ((nhptr->nhdr$q_nh_namesz+7)&~7)); memcpy (ImageDate, ImageDatePtr, 8); ImageDatePtr = ImageDate; break ; case NHDR$K_NT_VMS_IMGNAM: cptr = (char*)nhptr+sizeof(ELF64_NHDR) + ((nhptr->nhdr$q_nh_namesz+7)&~7); ImageNamePtr = malloc (strlen(cptr)+1); strcpy (ImageNamePtr, cptr); break ; case NHDR$K_NT_VMS_IMGID: cptr = (char*)nhptr+sizeof(ELF64_NHDR) + ((nhptr->nhdr$q_nh_namesz+7)&~7); ImageIdentPtr = malloc (strlen(cptr)+1); strcpy (ImageIdentPtr, cptr); break ; case NHDR$K_NT_VMS_GSTNAM: break ; case NHDR$K_NT_VMS_IMGBID: break ; case NHDR$K_NT_VMS_LINKID: break ; default: break ; } } } free (secptr); } } free (shptr); #endif #ifdef __VAX Length = *(USHORTPTR)ImageRecord; ImageNamePtr = ImageRecord + Length - 80; ImageIdentPtr = ImageRecord + Length - 40; ImageDatePtr = ImageRecord + Length - 24; #endif /* goto target */ ImageIdentFailed: fclose (ImageFile); if (ErrorInfo[0]) { #ifdef __ia64 /* uses null-terminated strings */ ImageNamePtr = ImageIdentPtr = ErrorInfo + 1; #else /* uses counted strings */ ImageNamePtr = ImageIdentPtr = ErrorInfo; #endif memset (Genesis, 0, sizeof(Genesis)); ImageDatePtr = Genesis; } #ifdef __ia64 /* ever'thin's null-terminated in this brave new world? */ FaoToBuffer (HttpdProcess.ImageInfo, sizeof(HttpdProcess.ImageInfo), NULL, "!AZ !AZ !%D !AZ", ImageNamePtr, ImageIdentPtr, ImageDatePtr, ImagName); #else FaoToBuffer (HttpdProcess.ImageInfo, sizeof(HttpdProcess.ImageInfo), NULL, "!AC !AC !%D !AZ", ImageNamePtr, ImageIdentPtr, ImageDatePtr, ImagName); #endif } /*****************************************************************************/