/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* MetaCon.c "MetaCon"fig attempts to provide an overall structure and consistent set of functions for WASD configuration. The META_CONFIG file structure is designed for "high-level" parsing by MetaConParse(). This function implements the meta-config configuration directives described below. Calling a function for each "line" of a file is moderately more expensive than parsing them inline so for efficiency and speed configuration facilities (such as mapping and authorization) also explicitly parse this structure. If it detects a line with a token that is not _TEXT only then does it call the MetaConParse() to perform the high-level parse. For configuration requiring "rule" interpretation (such as mapping and authorization) it creates an internal representation of the original file. META FILES ---------- There are two 'types' of configuration file. The ones belonging to the primary configuration and administered by the site administrator. And those belonging to virtual service administrators. [IncludeFile] include a configuration file [ConfigDirectory] virtual service administered directory [ConfigFile] virtual service administered configuration file The [IncludeFile] directive allows a primary configuration to be built up of one of more files included from elsewhere. These directives must have an absolute file specification and must exists at server startup or other configuration load. They can contain all configuration directives available to the site administratator. Those belonging to a virtual service administrator are included using the [ConfigFile] directive. The should but do not need to exists at configuration load. The directives used in these files may have limitations depending on the configuration context. The file specification can be absolute r relative to anything currently specified by [ConfigDirectory]. META VERSION ------------ Configurations can have sections conditionally processed based on the WASD version. Where a version does not match the intervening directives are not processed at all. This means that unknown conditionals, etc., are not reported and do not result in directive processing aborts. All WASD configuration files support the version conditional. The syntax is: [[wasd..]] Only the major version number is required. The minor and tweak numbers if omitted default to zero. Comparison operators are as follows: [[wasd==11.0]] equal to [[wasd<=11.1]] less than or equal to [[wasd<11.2]] less than [[wasd>=11.2.1]] greater than or equal to [[wasd>11.3.2]] greater than [[wasd!=11.4.0]] not equal to [[wasd!<11.5]] not less than [[wasd!>11]] not greater than [[wasd==*]] all versions Any version restricted block can and should be ended using [[wasd==*]]. META DIRECTIVES --------------- A number of directives allow the conditional interpretation of the following or intervening lines (these are similar in concept to the pre-8.0 mapping rule conditionals). The decision statements can be nested up to eight levels. if() if parenthesis expression is true elif() else if parenthesis expression is true else else last if() or elif() expression (or ifif) was false unif unconditional anywhere inside if()..endif block ifif if last if() or elif() expression was true endif end of conditional block as with the following examples ... if() interpret the rest of the line if() endif if() else endif if() elif() else endif if() unif ifif else unif else endif META DICTIONARY --------------- The "dict=[[=]]" meta configuration directive allows dictionary entries to be managed using the same mechansim as the path mapping "SET dict=[[=]]" directive (in fact path mapping SET uses the meta-config routine). Placement of quotes is important, across the entire "=" string, or they become part of the key and/or value. A key value may be set using dict = dict "=" (to end of line in the first case and between the quotes in the second) and removed (deleted) using dict = (i.e. an empty value). A dictionary entry can be set to an empty value by making the value a single (escaped) backslash (i.e. "\\"). Bit of a kludge but one is needed for either setting empty or removing, so choose your poison. All meta configuration entries can be removed using dict= Of course dictionary entries can be tested with the "dict:" conditional. Values from dictionary entries may be substituted using (the somewhat familiar) ''' syntax when setting dictionary entries (DICT and SET DICT=..) These are some examples. DICT "first=''user-agent'" SET dict="first=''user-agent'" DICT "X-response-header=''host'" SET dict="X-response-header=''host'" DICT one=two SET dict=one=two DICT three=\"fo\ ur\" SET dict=three=\"fo\ ur\" META CONDITIONALS ----------------- Conditionals often use data that can also be found as CGI variables, although some have no such analogue. Conditional expressions expecting strings may have those string enclosed in balanced double or single quotation marks. Reserved characters may be escaped using the backslash character. The following lists conditionals with a brief explanation of their meaning. [[wasd*n.n.n]] server version conditional [[service]] virtual service (or [[scheme://service:port]]) [[?]] or [[?:port]] for an unknown virtual service accept: 'Accept:' (HTTP_ACCEPT) accept-charset: 'Accept-Charset:' (HTTP_ACCEPT_CHARSET) accept-encoding: 'Accept-Encoding:' (HTTP_ACCEPT_ENCODING) accept-language: 'Accept-Language:' (HTTP_ACCEPT_LANGUAGE) alpn: TLS application level protocol negotiation callout: boolean, true if during a callout, false otherwise client_connect_gt: boolean, client greater than number of concurrent requests cluster_member: is the specified node a cluster member command_line: server startup command line (qualifiers, etc.) decnet: 0/4/5 is none/PhaseIV/V demo: boolean, true if started /DEMO, false otherwise dict: [=] directory: if this directory exists (request path if empty) document-root: 'Document-Root:' (DOCUMENT_ROOT) file: if this file (or directory) exists (request path if empty) http2: true if underlying protocol is HTTP/2 instance: test cluster member has a WASD instance (see note below) jpi_username: $GETJPI user name (server process username) mapped-path: remainder after script name parsing, or after 'map' rule multihome: an IP address if client used IP address different to IP address of the current service (i.e. no service match) note: admin mapping notes (via /DO=NOTE= or Server Admin menu) notepad: per-request keywords added/modified during mapping NOTEPAD PERSISTS ACROSS INTERNALLY REDIRECTED REQUESTS! ods: on-disk-structure (2 or 5) pass: usually 0, can be 1 or 2 only in second pass of mapping path-info: request path (PATH_INFO) path-translated: VMS-style mapped-path (available after mapping) proctor: if this is a proctored script activation query-string: request query string (QUERY_STRING) rand: random number generator (see note below) redirected: usually 0, can be 1..4 (count internally redirected) regex: boolean, true if regular expressions enabled remote-addr: client address (REMOTE_ADDR) remote-host: client host name (REMOTE_HOST) request: request fields (e.g. "Keep-Alive: 300") request-method: GET, POST, etc. (REQUEST_METHOD) "?" for HTTP extension method (i.e. not GET, PUT, etc.) request_peek: request peek buffer (443 tunnel processing) request-protocol: server/request HTTP protocol ("2", "1.1", "1.0", "0.9") request-scheme: request scheme (REQUEST_SCHEME) request-uri: undecoded request line (e.g. /path?query=%20string) restart: number of times rule processing restarted (0 is default) robin: round-robin between specified node names (see note below) script-name: if mapped (or during mapping if pass 2) server-addr: server address (SERVER_ADDR) server_connect_gt: boolean, if current server connections greater than server-name: server host name (SERVER_NAME) server-port: server port (SERVER_PORT) server_process_gt: boolean, if server processing greater than server-protocol: server/request HTTP protocol ("1.1", "1.0", "0.9") server-software: server identification string (SERVER_SOFTWARE) service: essentially server-name plus server-port as one string "?" or "?:port" for an unknown virtual service ssl: boolean, if Secure Sockets Layer (https:) syi_arch_name: $GETSYI arch_name ("Alpha", "IA64" or "VAX") syi_hw_name: $GETSYI hw_name syi_nodename: $GETSYI nodename syi_version: $GETSYI version (e.g. "V7.3") tcpip: identification string generated from UCX$IPC_SHR time: system time (see note below) trnlnm: $TRNLNM logical name (see note below) upstream-addr: upstream proxy/accelerator address (UPSTREAM_ADDR) webdav: boolean, request using a WebDAV-specific method webdav:MSagent true if a Microsoft WebDAV agent websocket: boolean, request is a WebSocket (ws: or wss:) x509:[keyword[=...]] X509 client certificate any request header name can be specified (see below) Request Header Fields ~~~~~~~~~~~~~~~~~~~~~ Any request header field name, known or unknown to the server, can be specified as a conditional. This provided in the list immediately above may be processed in some non-string comparison mode (e.g. the network masks available against remote-addr:'). This following list provides the current recognised header fields (may not be exhaustive). Accept: Accept-Charset: Accept-Encoding: Accept-Language: Authorization: Cache-Control: Connection: Content-Length: Content-Type: Cookie: ETag: Expect: Forwarded: Host: If-Match: If-None-Match: If-Modified-Since: If-Unmodified-Since: If-Range: Keep-Alive: Max-Forwards: Origin: Pragma: Proxy-Authorization: Range: Referer: Trailer: Transfer-Encoding: Upgrade: User-Agent: WebSocket-Protocol: X-Forwarded-For: And any other field name that can be sent. If the field is found in the request header the comparison string is used against the value and the result returned based on that. If the field name cannot be found false is always returned. Host Addresses ~~~~~~~~~~~~~~ The host names or addresses can be a dotted-decimal network address, a slash, then a dotted-decimal mask. For example "131.185.250.0/255.255.255.192". This has a 6 bit subnet. It operates by bitwise-ANDing the client host address with the mask, bitwise-ANDing the network address supplied with the mask, then comparing the two results for equality. Using the above example the host 131.185.250.250 would be accepted, but 131.185.250.50 would be rejected. Equivalent notation for this rule would be "131.185.250.0/26" Logical Name Translations ~~~~~~~~~~~~~~~~~~~~~~~~~ The 'trnlnm' conditional dynamically translates a logical name and uses that. One mandatory and up to two optional parameters may be supplied. trnlnm:logical-name[;name-table][:string-to-match] The 'logical-name' must be supplied, without it false is always returned. If just the 'logical-name' is supplied the conditional returns true if the name exists or false if it does not. The default 'name-table' is LNM$FILE_DEV. When the optional 'name-table' is supplied the lookup is confined to that table. If the optional 'string-to-match' is supplied it is matched against the value of the logical and the result returned. Random Numbers ~~~~~~~~~~~~~~ This number is generated once for each pass through a set of rules, and therefore remains constant during that pass. The 'rand' conditional is intended to allow some sort of distribution to be built into a set of rules, where each pass (request) generates a different one. The random conditional accepts two parameters, a 'modulas' number, which is used to modulas the base number, and a comparison number, which is compared to the modulas result. Hence the following conditional rules if (rand:3:0) elif (rand:3:1) else endif would pseudo-randomly generate base numbers of 0, 1, 2 and perform the appropriate conditional block. Over a sufficient number of usages this should produce a relatively even distribution of numbers. If the modulas is specified as less than two (i.e. no distribution factor at all) it defaults to 2 (i.e. a distribution of 50% - the equivalent of a coin toss!) Client and Server Concurrency ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The client_connect_gt:, server_connect_gt: and server_process_gt: conditionals attempt to allow some measurement of the number of requests a particular client currently has being processed (on the local instance), and the total requests the server (all instances) is currently processing. Using these decision critera subsequent request mapping or processing can be undertaken. It is not intended to provide fine-grained control over activities, rather just to prevent a single client (or clients) hogging a sizeable proportion of the resources. For example. If the number of request from one particulat client looks like it has got out of control (at the client end) then it becomes possible to queue (throttle) or reject further requests. In WASD_CONFIG_MAP if (client_connect_gt:10) set * throttle=10 if (client_connect_gt:10) \ pass * "503 Your client is exceeding its concurrency limit!" While not completely foolproof it does offer some measure of control over client concurrency and server behaviour under specified loads. System Time ~~~~~~~~~~~ The time: conditional allows server behaviour to change according to the time of day (or even year), comparing the supplied parameter to the current system time in one of three forms. 1) A twenty-four hour clock range as 'hhmm-hhmm', for example '1200-1759', which should be read as "twelve noon to five fifty-nine PM" (i.e. as a time range in minutes), where the first is the start time and the second the end time of a range. If the current time is within that range (inclusive) the conditional returns true, otherwise false. If the range doesn't look correct false is always returned. 2) A single digit, '1'..'7', representing the day of the week, where 1 is Monday, 2 is Tuesday .. 7 is Sunday. Not exactly the historical way of numbering weekdays (ask any student of Judaism) but it is what lib$day_of_week() returns :^) 3) The final way (if neither of the above) is as a string match with a VMS comparision time (i.e. 'yyyy-mmm-dd hh-mm-ss.hh'). Some examples: if (time:0000-0000) elif (time:0001-1159) elif (time:1200-1200) else endif if (time:6 || time:7) else endif if (time:%%%%-05-*) endif Instance ~~~~~~~~ The instance: directive allows testing for a particular cluster member having a WASD instance currently running. This can allow requests to be redirected or reverse-proxied to a particular system with the knowlege that it should be processed (of course there is a small window of uncertainty as events such as system shutdown and startup occur asynchronously). The behaviour of the conditional block is entirely determinate based on which node names have a WASD instance and the order of evaluation. Compare this to a similar construct using the robin: directive, as described below. This conditional is deployed in two phases. In the first, it contains a comma-separated list of node names (that are expected to have instances of WASD instantiated). In the second, containing a single node name, allowing the selected node to be tested. For example. if (instance:NODE1,NODE2,NODE3) if (instance:NODE1) redirect /* http://node1.domain.name/*? if (instance:NODE2) redirect /* http://node2.domain.name/*? if (instance:NODE3) redirect /* http://node3.domain.name/*? pass * "500 Some sort of logic error!!" endif pass * "503 No instance currently available!" If none of the node names specified in the first phase is currently running a WASD instance the rule returns false, otherwise true. If true the above example has conditional block processed with each of the node names successively tested. If NODE1 has a WASD instance executing it returns true and the associated redirect is performed. The same for NODE2 and NODE3. At least one of these would be expected to test true otherwise the outer conditional established during phase one would have been expected to return false. Round-Robin ~~~~~~~~~~~ The robin: conditional allows rules to be applied sequentially against specified members of a cluster that currently have instances of WASD running. This is obviously intended to allow a form of load sharing and/or with redundancy (not balancing, as no evaluation of the selected target's current workload is performed, see below). As with the instance: directive above, there is, of course, a small window of potential uncertainty as events such as system shutdown and startup occur asynchronously and may impact availability between the phase one test and ultimate request distribution. This conditional is again used in two phases. The first, containing a comma-separated list of node names (that are expected to have instances of WASD instantiated). The second, containing a single node name, allowing the selected node (from phase one) to have a rule applied. For example. if (robin:VAX1,ALPHA1,ALPHA2,IA64A) if (robin:VAX1) redirect /* http://vax1.domain.name/*? if (robin:ALPHA1) redirect /* http://alpha1.domain.name/*? if (robin:ALPHA2) redirect /* http://alpha2.domain.name/*? if (robin:IA64A) redirect /* http://ia64a.domain.name/*? pass * "500 Some sort of logic error!!" endif pass * "503 No round-robin node currently available!" In this case round-robining will be made through four node names. Of course these do not have to represent all the systems in the cluster currently available or having WASD instantiated. The first time the 'robin:' rule containing multiple names is called VAX1 will be selected. The second time ALPHA1, the third ALPHA2, and the fourth IA64A. With the fifth call VAX1 is returned to, the sixth ALPHA1, etc. In addition, the selected nodename is verified to have a instance of WASD currently running (using the DLM and WASD's instance awareness). If it does not, round-robining is applied again until one is found (if none is available the phase one conditional returns false). THIS IS MOST SIGNIFICANT as it ensures that the selected node should be able to respond to a redirected or (reverse-)proxied requested. This is the selection set-up phase. Then there is the selection application phase. Inside the set-up conditional other conditionals APPLY the selection made in the first phase (through simple nodename string comparison). The rule, in the above example a redirect, is applied if that was the node selected. During selection set-up unequal weighting can be applied to the round-robin algorithm by including particular node names more than once. if (robin:VAX1,ALPHA,VAX2,ALPHA) In the above example, the node ALPHA will be selected twice as often as either of VAX1 and VAX2 (and because of the ordering interleaved with the VAX selections). EXAMPLES -------- if( host:10.64.1.0/24 ) pass /* /area-1/* elif( host:10.64.2.0/24 ) pass /* /area-2/* else pass /* /default/* endif VERSION HISTORY --------------- 15-MAR-2020 MGD add "alpn:" TLS application level protocol negotiation add "proctor:" explicitly documents proctored clause bugfix; MetaConConditionalList() buggered 04-AUG-2018 MGD bugfix; MetaConEvaluate() "webdav:MSagent" 19-JUL-2016 MGD bugfix; MetaConEvaluate() request:, path-translated: connect_gt: regressions 04-JUL-2016 MGD bugfix; MetaConEvaluate() request-scheme: regression (ARGH!) 22-MAY-2016 MGD add [[wasd*n.n.n]] server version conditional bugfix; MetaConEvaluate() request-scheme: regression 19-DEC-2015 MGD add "dict:[=] add "X509:[=keyword[=...]" significant rework of conditional processing 26-OCT-2015 MGD add "request-protocol" specifically to support HTTP/2 03-OCT-2015 MGD add "http2:" to test if HTTP/2 underlying protocol 16-APR-2015 JPP when "remote-addr:" begins '?' translate host to IP address 12-NOV-2014 MGD add "upstream-addr:" for "SET client=" detection 11-FEB-2012 MGD MetaConLoad() compress non-signficant white-space MetConSameField() more efficient if not inline 14-MAR-2011 MGD MetaConLoad() ensure metacon "lines" are quadword aligned 05-SEP-2010 MGD add "request_peek:" supporting [ServiceShareTLS] tunneling lines beginning "!#" are now configured allowing WATCHable commentary to be inserted into configuration files 08-JUN-2010 MGD bugfix; MetaConEvaluate() when JustChecking: HTTP header fields (e.g. "cookie:") 29-MAY-2010 MGD add "file:" and "directory:" to probe file-system 21-JAN-2010 MGD add "websocket:" for WebSocket-specific requests add "Origin:", "Upgrade:", "WebSocket-Protocol:" headers 13-OCT-2009 MGD allow for []-delimited IPv6 address (as service names) 05-SEP-2009 MGD allow "pass:-1" to indicate reverse-mapping 28-MAY-2008 MGD add "request-uri:" 23-APR-2007 MGD add "webdav:" for WebDAV-specific requests 15-SEP-2005 MGD add "server-protocol:" (lamentable oversight) 10-JUL-2005 MGD [[?]] and service:? to match unknown virtual service 13-JUN-2005 MGD bugfix; MetaConLoad() allocate structure before non-filename return! (revealed by Alex Daniels with no HTTPD$SERVICE) 26-MAY-2005 MGD add "note:" to allow testing of admin mapping notes 01-MAY-2005 MGD add "instance:" to allow testing of cluster WASD instances, add "robin:" to allow 'round-robin'ing cluster instances, bugfix; MetaConSameField(cptr,"syi_arch_name:") 20-APR-2005 MGD add "multihome:" to allow detection of multihomed IP addresses with mismatched services 04-MAR-2005 MGD allow config files to be a logical search list (initially to support multiple language HTTPD$MSG files) 02-OCT-2004 MGD bugfix; MetaconClientConcurrent() if IP address not the same! 12-AUG-2004 MGD MetaConShowSeconds() 28-JUL-2004 MGD 'request-method:?' now tests for an HTTP extension method, any recognised request header field can now be used as a conditional directive (e.g. "if-none-match:"), made directive matching precise (MetaConSameField()) 26-JAN-2004 MGD add server_process_gt:, change to client_connect_gt: and server_connect_gt: to better reflect functionality 29-DEC-2003 MGD add client_current_gt: and server_current_gt: 04-OCT-2003 MGD [ConfigDirectory] and [ConfigFile], add "document-root:" (set map=root=) 28-SEP-2003 MGD add "callout:" in progress? 09-MAY-2003 MGD regular expression support, add "notepad:", "regex:", "request:", "restart:" 22-APR-2003 MGD bugfix; MetaConParse() decrement index (back) when not currently executing an if()inline directive 02-APR-2003 MGD add "x-forwarded-for:" 28-JAN-2003 MGD allow [[service]] to include the [[scheme://service]] 06-NOV-2002 MGD add "mapped-path:" (can be different to path-info) add "path-translated:" (for use in authorization rules) add "script-name:" (if mapped or in second pass) add "redirected:[digit]" (can be used as a boolean) 12-OCT-2002 MGD refine reporting 05-OCT-2002 MGD add "pass:1", "pass:2" (for mapping) and "demo:" 24-SEP-2002 MGD bugfix; expressions with inline statements 21-SEP-2002 MGD bugfix; MetConLoad() return RMS status 24-AUG-2002 MGD add "ods:pwk" and "ods:sri" to ods: conditional 16-APR-2002  MGD add "unif" and "ifif" conditional statements and a swag of new conditional directives 06-APR-2002 MGD bugfix; MetaConParse() return double-null empty strings to avoid having them mistaken for error strings (VAX) 11-AUG-2001 MGD initial */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #undef __VMS_VER #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "METACON" /******************/ /* global storage */ /******************/ /* mainline configuration plus depth of four [IncludeFile] or [ConfigFile] */ #define METACON_FILE_DEPTH_MAX 4 #define METACON_STACK_MAX 16 META_CONFIG *MetaGlobalAuthPtr, *MetaGlobalConfigPtr, *MetaGlobalMappingPtr, *MetaGlobalMsgPtr, *MetaGlobalServicePtr; int MetaConInstanceListCount; char *MetaConInstanceListPtr = ""; BOOL MetaConNoteValid; char MetaConNote [LOCK_VALUE_BLOCK_64]; /********************/ /* external storage */ /********************/ extern BOOL CliDemo; extern int EfnWait, HttpdDayOfWeek, HttpdServerExecuting, HttpdServerStartup, InstanceNodeCurrent, InstanceNodeJoiningCount, InstanceNumber, NetCurrentProcessing, OpcomMessages; ulong HttpdVersionNumber; extern int ToLowerCase[], ToUpperCase[]; extern ushort HttpdNumTime[]; extern ulong HttpdTime64[], SysPrvMask[]; extern char CommandLine[], ErrorSanityCheck[], SoftwareId[], TcpIpAgentInfo[]; extern LIST_HEAD RequestList; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_GBLSEC *HttpdGblSecPtr; extern HTTPD_PROCESS HttpdProcess; extern MSG_STRUCT Msgs; extern SYS_INFO SysInfo; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Load the contents of the specific file into a METACON configuration file structure. Allow the file to [IncludeFile]. Two passes are made through the primary and any included files. The first is used to determine how much space needs to be allocated for METACON structure. The second populates it once allocated. */ int MetaConLoad ( META_CONFIG **MetaConPtrPtr, char *FileName, CALL_BACK CallBackFunction, BOOL ContinueLines, BOOL ReportVirtualService ) { static ulong LnmIndex; static $DESCRIPTOR (LogNameDsc, ""); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); static ulong LnmAttributes; static VMS_ITEM_LIST3 LnmItems [] = { { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 }, { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 }, { 0, LNM$_STRING, 0, 0 }, { 0,0,0,0 } }; #if WATCH_MOD /* for testing purposes only */ BOOL TestToken = false; #endif BOOL ok, NotThisVersion, WatchThisOne; int status, ByteCount, BytesAligned, FlowControlLevel, MetaFileLevel, ParseNumber, TotalLineCount; int MetaFileType [METACON_FILE_DEPTH_MAX+1]; ushort LogValueLength; ulong LnmCount; char ch; char *cptr, *sptr, *tptr, *zptr, *BackslashMeans, *InlinePtr; char ConfigDirectory [METACON_CONFIG_DIR_LENGTH+1], LogValue [256], MetaFileName [ODS_MAX_FILE_NAME_LENGTH+1]; ODS_STRUCT ConfigFileOds [METACON_FILE_DEPTH_MAX+1]; ODS_STRUCT *odsptr; META_CONFIG *mcptr; METACON_LINE *mclptr, *NextLinePtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThisOne = true; else WatchThisOne = false; if (WATCH_MOD && WatchThisOne) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConLoad() !&Z !&A !&B !&B", FileName, CallBackFunction, ContinueLines, ReportVirtualService); ByteCount = sizeof(META_CONFIG); /* allocate a structure that will be used only for problem reports */ mcptr = (META_CONFIG*)VmGet(ByteCount); *MetaConPtrPtr = mcptr; /* if there is no file to load and we just want the structure */ if (!FileName) return (SS$_NORMAL); if (ContinueLines) BackslashMeans = '\\'; else BackslashMeans = '\n'; /* use SYSPRV to allow access to possibly protected files */ sys$setprv (1, &SysPrvMask, 0, 0); LogNameDsc.dsc$w_length = strlen(FileName); LogNameDsc.dsc$a_pointer = FileName; LnmItems[2].buf_len = sizeof(LogValue)-1; LnmItems[2].buf_addr = LogValue; LnmItems[2].ret_len = &LogValueLength; for (LnmIndex = 0; LnmIndex <= 127; LnmIndex++) { status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (VMSnok (status)) ErrorExitVmsStatus (status, FileName, FI_LI); if (!(LnmAttributes & LNM$M_EXISTS)) break; LogValue[LogValueLength] = '\0'; if (WATCH_MODULE(WATCH_MOD_MSG)) WatchThis (WATCHALL, WATCH_MOD_MSG, "!UL !AZ", LnmIndex, LogValue); } LnmCount = LnmIndex; /********************/ /* preliminary pass */ /********************/ mcptr->LnmCount = LnmCount; MetaFileLevel = TotalLineCount = 0; ConfigDirectory[0] = MetaFileName[0] = '\0'; odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[MetaFileLevel]; for (LnmIndex = 0; LnmIndex <= 127; LnmIndex++) { status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (VMSnok (status)) ErrorExitVmsStatus (status, FileName, FI_LI); if (!(LnmAttributes & LNM$M_EXISTS)) break; if (LnmIndex+1 > LnmCount) ErrorExitVmsStatus (SS$_BUGCHECK, FileName, FI_LI); LogValue[LogValueLength] = '\0'; mcptr->LnmIndex = LnmIndex; if (WATCH_MOD && WatchThisOne) WatchThis (WATCHALL, WATCH_MOD_METACON, "!UL !&Z", LnmIndex, LogValue); status = OdsLoadTextFile (odsptr, LogValue); if (VMSnok (status)) { sys$setprv (0, &SysPrvMask, 0, 0); MetaConReport (mcptr, METACON_REPORT_ERROR, "Error opening !AZ, !&m", LogValue, status); return (status); } for (;;) { if (!(cptr = OdsParseTextFile (odsptr, BackslashMeans))) { /* end of source file */ OdsFreeTextFile (odsptr); mcptr->CurrentOdsPtr = NULL; /* if original (not an included) file then break */ if (!MetaFileLevel) break; /* nest out of an included file */ odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[--MetaFileLevel]; continue; } TotalLineCount++; /* skip over leading white-space */ while (*cptr && ISLWS(*cptr)) cptr++; /* if a blank line */ if (!*cptr) continue; /* if a comment line and not comment-include line */ if (*cptr == '#' || *cptr == '!') if (!SAME2(cptr,'!#')) continue; /* compress white-space */ sptr = tptr = cptr; while (ch = *tptr) { if (ch == '\"' || ch == '\'') { *sptr++ = *tptr++; while (*tptr && *tptr != ch) { /* step over any escape character */ if (*tptr == '\\' && *(tptr+1)) *sptr++ = *tptr++; *sptr++ = *tptr++; } continue; } *sptr++ = *tptr++; if (ISLWS(ch)) while (ISLWS(*tptr)) tptr++; } *sptr++ = '\0'; ByteCount += sizeof(METACON_LINE) + (sptr - cptr); /* ensure it allows for the quadword alignment */ if (ByteCount % 8) ByteCount += 8 - (ByteCount % 8); if (WATCH_MOD && WatchThisOne) WatchDataFormatted ("!UL {!UL}!-!#AZ\n", ByteCount, strlen(cptr), cptr); if (strsame (cptr, "[ConfigDirectory]", 17)) { /********************/ /* config directory */ /********************/ cptr += 17; /* skip over intervening white-space */ while (*cptr && ISLWS(*cptr)) cptr++; if (!MetaFileLevel) { /* can be an empty string, which resets the directory */ zptr = (sptr = ConfigDirectory) + sizeof(ConfigDirectory)-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } continue; } else if (strsame (cptr, "[ConfigFile]", 12)) { /* config file statement */ cptr += 12; /* skip over intervening white-space */ while (*cptr && ISLWS(*cptr)) cptr++; if (MetaFileLevel > METACON_FILE_DEPTH_MAX) continue; zptr = (sptr = MetaFileName) + sizeof(MetaFileName)-1; /* if the file name is not absolute then include any directory */ for (tptr = cptr; *tptr && *tptr != ':'; tptr++); if (!*tptr) for (tptr = ConfigDirectory; *tptr && sptr < zptr; *sptr++ = *tptr++); while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[++MetaFileLevel]; status = OdsLoadTextFile (odsptr, MetaFileName); if (VMSnok (status)) odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[--MetaFileLevel]; } else if (strsame (cptr, "[IncludeFile]", 13)) { /* include file statement */ cptr += 13; /* skip over intervening white-space */ while (*cptr && ISLWS(*cptr)) cptr++; if (MetaFileLevel > METACON_FILE_DEPTH_MAX) continue; zptr = (sptr = MetaFileName) + sizeof(MetaFileName)-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[++MetaFileLevel]; status = OdsLoadTextFile (odsptr, MetaFileName); if (VMSnok (status)) { odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[--MetaFileLevel]; if (OpcomMessages) FaoToOpcom ( "%HTTPD-E-INCLUDE, failed to include !AZ (!&m) by !AZ", MetaFileName, status, odsptr->ExpFileName); } } } } /* terminating empty (zero-length) line */ ByteCount += sizeof(METACON_LINE); /* dispose of the previously allocated structure */ MetaConUnload (&mcptr, NULL); /*****************/ /* populate pass */ /*****************/ mcptr = (META_CONFIG*)VmGet(ByteCount); *MetaConPtrPtr = mcptr; if (WATCH_MOD && WatchThisOne) WatchThis (WATCHALL, WATCH_MOD_METACON, "!&X", mcptr); NotThisVersion = false; mcptr->ThisSize = ByteCount; mcptr->LineCount = TotalLineCount; mcptr->ContentPtr = mcptr->ParsePtr = mclptr = &mcptr->Lines; mcptr->LnmCount = LnmCount; if (mcptr->LnmCount > 1) mcptr->IncludeFile = true; /* due to some peculiarity with ANSI aliasing rules (BADANSIALIAS)! */ mcptr->LoadReport.ErrorCount = 0; mcptr->LoadReport.InformCount = 0; mcptr->LoadReport.ItemCount = 0; mcptr->LoadReport.WarningCount = 0; sys$gettim (&mcptr->LoadReport.LoadTime64); MetaFileLevel = FlowControlLevel = ParseNumber = 0; ConfigDirectory[0] = MetaFileName[0] = '\0'; MetaFileType[MetaFileLevel] = METACON_TYPE_FILE; odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[MetaFileLevel]; for (LnmIndex = 0; LnmIndex <= 127; LnmIndex++) { status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (VMSnok (status)) ErrorExitVmsStatus (status, FileName, FI_LI); if (!(LnmAttributes & LNM$M_EXISTS)) break; if (LnmIndex+1 > LnmCount) ErrorExitVmsStatus (SS$_BUGCHECK, FileName, FI_LI); LogValue[LogValueLength] = '\0'; mcptr->LnmIndex = LnmIndex; status = OdsLoadTextFile (odsptr, LogValue); if (VMSnok (status)) { sys$setprv (0, &SysPrvMask, 0, 0); MetaConReport (mcptr, METACON_REPORT_ERROR, "Error opening !AZ, !&m", FileName, status); return (SS$_ABORT); } strcpy (mcptr->LoadReport.FileName, odsptr->ResFileName); PUT_QUAD_QUAD (&odsptr->XabDat.xab$q_rdt, mcptr->LoadReport.FileTime64); /* initial callback to allow pre-configuration initialization */ mcptr->ParsePtr = mclptr; mclptr->Token = METACON_TOKEN_PRE; if (CallBackFunction) ok = (*CallBackFunction)(mcptr); else ok = true; while (ok) { /*************/ /* next line */ /*************/ if (!(cptr = OdsParseTextFile (odsptr, BackslashMeans))) { /* end of source file */ OdsFreeTextFile (odsptr); mcptr->CurrentOdsPtr = NULL; /* if original (not an included) file then break */ if (!MetaFileLevel) break; /* nest out of an included file */ odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[--MetaFileLevel]; continue; } /* skip over any text leading white-space */ while (*cptr && ISLWS(*cptr)) cptr++; /* if a blank line */ if (!*cptr) continue; /* if a comment line and not comment-include line */ if (*cptr == '#' || *cptr == '!') if (!SAME2(cptr,'!#')) continue; /* point to the string storage area of that "line" */ sptr = mclptr->TextPtr = (char*)&mclptr->Storage; /* copy remainder of the line compressing white-space */ tptr = cptr; while (ch = *tptr) { if (ch == '\"' || ch == '\'') { *sptr++ = *tptr++; while (*tptr && *tptr != ch) { /* step over any escape character */ if (*tptr == '\\') *sptr++ = *tptr++; if (*tptr) *sptr++ = *tptr++; } if (*tptr) *sptr++ = *tptr++; } else *sptr++ = *tptr++; if (ISLWS(ch)) while (ISLWS(*tptr)) tptr++; } *sptr++ = '\0'; /* quadword alignment!! */ if ((ulong)sptr % 8) sptr += 8 - ((ulong)sptr % 8); InlinePtr = ""; mclptr->Size = sptr - (char*)mclptr; /* quick sanity check might be opportune at this point */ if (sptr > (char*)mcptr + ByteCount) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* point at the next "line" - already quadword aligned of course! */ NextLinePtr = (METACON_LINE*)sptr; mclptr->Number = ++ParseNumber; if (strsame (cptr, "[[wasd", 6) && (cptr[6] == '!' || cptr[6] == '=' || cptr[6] == '<' || cptr[6] == '>')) { /****************/ /* wasd-version */ /****************/ mclptr->Token = METACON_TOKEN_VERSION; mclptr->FlowControlLevel = FlowControlLevel = 0; /* eliminate the leading "[[" and trailing "]]" */ sptr = mclptr->TextPtr; for (cptr = mclptr->TextPtr + 2; *cptr && *cptr != ']'; *sptr++ = *cptr++); *sptr = '\0'; /* e.g. "[[wasd==11.0.1]]", "[[wasd<11.1]]", "[[wasd!=11.2]]" */ cptr = mclptr->TextPtr + 4; *(USHORTPTR)mclptr->VersionCompare = 0; if (*cptr == '!' || *cptr == '=' || *cptr == '<' || *cptr == '>') mclptr->VersionCompare[0] = *cptr++; if (*cptr == '=' || *cptr == '<' || *cptr == '>') mclptr->VersionCompare[1] = *cptr++; if (*cptr == '*') { /* only valid uses of '*' are [[wasd=*]] and [[wasd==*]] */ if (mclptr->VersionCompare[0] != '=') *(USHORTPTR)mclptr->VersionCompare = 0; else mclptr->VersionNumber = -1; } else { mclptr->VersionNumber = atoi(cptr) * 10000; while (isdigit(*cptr)) cptr++; if (*cptr == '.') cptr++; mclptr->VersionNumber += atoi(cptr) * 100; while (isdigit(*cptr)) cptr++; if (*cptr == '.') cptr++; mclptr->VersionNumber += atoi(cptr); } switch (*(USHORTPTR)mclptr->VersionCompare) { case '=\0' : case '==' : if (mclptr->VersionNumber == -1) NotThisVersion = false; else NotThisVersion = HttpdVersionNumber != mclptr->VersionNumber; break; case '!=' : NotThisVersion = HttpdVersionNumber == mclptr->VersionNumber; break; case '<\0' : NotThisVersion = HttpdVersionNumber >= mclptr->VersionNumber; break; case '!>' : case '<=' : NotThisVersion = HttpdVersionNumber > mclptr->VersionNumber; break; case '>\0' : NotThisVersion = HttpdVersionNumber <= mclptr->VersionNumber; break; case '!<' : case '>=' : NotThisVersion = HttpdVersionNumber < mclptr->VersionNumber; break; default : NotThisVersion = true; MetaConReport (mcptr, METACON_REPORT_ERROR, "Version conditional not understood"); mclptr->ConfigProblem = true; } if (mclptr->VersionNumber != -1 && mclptr->VersionNumber < 110000) { NotThisVersion = true; MetaConReport (mcptr, METACON_REPORT_ERROR, "Version conditional less than v11.0"); mclptr->ConfigProblem = true; continue; } } if (strsame (cptr, "if(", 3) || strsame (cptr, "if ", 3)) { /******/ /* if */ /******/ mclptr->Token = METACON_TOKEN_IF; mclptr->FlowControlLevel = FlowControlLevel++; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; /* perform a basic syntax check */ InlinePtr = MetaConEvaluate (mclptr); if (*InlinePtr || SAME4 (InlinePtr, '\0\0\0\1')) { /* inline directive (true or false) */ mclptr->FlowControlLevel = --FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; } else if (!*InlinePtr && *(InlinePtr+1)) MetaConReport (mcptr, METACON_REPORT_ERROR, "!AZ", InlinePtr+1); } else if (strsame (cptr, "unif", 4)) { /********/ /* unif */ /********/ mclptr->Token = METACON_TOKEN_UNIF; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; while (*cptr && !ISLWS(*cptr)) cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr) InlinePtr = cptr; } else if (strsame (cptr, "ifif", 4)) { /********/ /* ifif */ /********/ mclptr->Token = METACON_TOKEN_IFIF; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; while (*cptr && !ISLWS(*cptr)) cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr) InlinePtr = cptr; } else if (strsame (cptr, "elif(", 5) || strsame (cptr, "elif ", 5)) { /********/ /* elif */ /********/ mclptr->Token = METACON_TOKEN_ELIF; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; /* perform a basic syntax check */ InlinePtr = MetaConEvaluate (mclptr); if (!*InlinePtr && *(InlinePtr+1)) MetaConReport (mcptr, METACON_REPORT_ERROR, "!AZ", InlinePtr+1); } else #if WATCH_MOD if (strsame (cptr, "test(", 5) || strsame (cptr, "test ", 5)) { /********/ /* test */ /********/ mclptr->Token = METACON_TOKEN_TEST; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; TestToken = true; /* perform a a basic syntax check */ InlinePtr = MetaConEvaluate (mclptr); if (!*InlinePtr && *(InlinePtr+1)) MetaConReport (mcptr, METACON_REPORT_ERROR, "!AZ", InlinePtr+1); } else #endif /* WATCH_MOD */ if (strsame (cptr, "else", 4)) { /********/ /* else */ /********/ mclptr->Token = METACON_TOKEN_ELSE; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; while (*cptr && !ISLWS(*cptr)) cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr) InlinePtr = cptr; } else if (strsame (cptr, "endif", 5)) { /*********/ /* endif */ /*********/ mclptr->Token = METACON_TOKEN_ENDIF; if (FlowControlLevel > 0) FlowControlLevel--; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; } else if (SAME2(cptr,'[[')) { /*******************/ /* virtual-service */ /*******************/ mclptr->Token = METACON_TOKEN_SERVICE; mclptr->FlowControlLevel = FlowControlLevel = 0; /* eliminate the leading "[[" and trailing "]]" */ sptr = mclptr->TextPtr; cptr = mclptr->TextPtr + 2; if (*cptr == '[' || strsame (cptr, "http://[", 8) || strsame (cptr, "https://[", 9)) { /* IPv6 address */ while (*cptr && *cptr != ']') *sptr++ = *cptr++; if (SAME2(cptr,']]') && !SAME2(cptr+1,']]')) { MetaConReport (mcptr, METACON_REPORT_ERROR, "IPv6 address format problem"); mclptr->ConfigProblem = true; } else if (*cptr) *sptr++ = *cptr++; } else while (*cptr && *cptr != ':' && *cptr != ']') *sptr++ = *cptr++; if (*cptr == ':') while (*cptr && *cptr != ']') *sptr++ = *cptr++; else /* no port component, add a wildcard */ for (cptr = ":*"; *cptr; *sptr++ = *cptr++); *sptr++ = '\0'; if (ReportVirtualService) { cptr = mclptr->TextPtr; if (SAME4 (cptr, 'http')) { if (SAME3 (cptr+4, '://')) cptr += 7; else if (SAME4 (cptr+4, 's://')) cptr += 8; } if (!SAME4(cptr,'*:*\0') && /* do not report virtual services for command-line checks */ (HttpdServerExecuting || HttpdServerStartup) && !ServiceIsConfigured (cptr)) { MetaConReport (mcptr, METACON_REPORT_ERROR, "Virtual service not configured"); mclptr->ConfigProblem = true; } } } else if (strsame (cptr, "[ConfigDirectory]", 17)) { /********************/ /* config directory */ /********************/ cptr += 17; /* skip over intervening white-space */ while (*cptr && ISLWS(*cptr)) cptr++; if (MetaFileType[MetaFileLevel] == METACON_TYPE_CONFIG) { /* limit the use of [ConfigDirectory] to site admin */ MetaConReport (mcptr, METACON_REPORT_ERROR, "Cannot [ConfigDirectory] inside [ConfigFile]"); mclptr->ConfigProblem = true; } else { /* can be an empty string, which resets the directory */ zptr = (sptr = ConfigDirectory) + sizeof(ConfigDirectory)-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } mclptr->Token = METACON_TOKEN_DIRECTORY; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; } else if (strsame (cptr, "[ConfigFile]", 12)) { /***************/ /* config file */ /***************/ if (MetaFileLevel > METACON_FILE_DEPTH_MAX) { sys$setprv (0, &SysPrvMask, 0, 0); MetaConReport (mcptr, METACON_REPORT_ERROR, "Exceeded file depth"); mclptr->ConfigProblem = true; } else { cptr += 12; /* skip over intervening white-space */ while (*cptr && ISLWS(*cptr)) cptr++; zptr = (sptr = MetaFileName) + sizeof(MetaFileName)-1; /* if file name is not absolute then include any directory */ for (tptr = cptr; *tptr && *tptr != ':'; tptr++); if (!*tptr) for (tptr = ConfigDirectory; *tptr && sptr < zptr; *sptr++ = *tptr++); while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; odsptr = &ConfigFileOds[++MetaFileLevel]; status = OdsLoadTextFile (odsptr, MetaFileName); if (VMSok (status)) { /* successfully loaded this file */ mcptr->CurrentOdsPtr = odsptr; /* config functions can tell what type of file it is from */ MetaFileType[MetaFileLevel] = METACON_TYPE_CONFIG; } else { MetaConReport (mcptr, METACON_REPORT_ERROR, "Error including config file, !&m", status); mclptr->ConfigProblem = true; odsptr = &ConfigFileOds[--MetaFileLevel]; } } mclptr->Token = METACON_TOKEN_CONFIG; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel-1; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; mcptr->IncludeFile = true; } else if (strsame (cptr, "[IncludeFile]", 13)) { /****************/ /* include file */ /****************/ cptr += 13; /* skip over intervening white-space */ while (*cptr && ISLWS(*cptr)) cptr++; if (MetaFileType[MetaFileLevel] == METACON_TYPE_CONFIG) { /* limit the use of [IncludeFile] to site admin */ MetaConReport (mcptr, METACON_REPORT_ERROR, "Cannot [IncludeFile] inside [ConfigFile]"); mclptr->ConfigProblem = true; } else if (MetaFileLevel > METACON_FILE_DEPTH_MAX) { /* fatal error */ sys$setprv (0, &SysPrvMask, 0, 0); MetaConReport (mcptr, METACON_REPORT_ERROR, "Exceeded file depth"); return (SS$_ABORT); } else { zptr = (sptr = MetaFileName) + sizeof(MetaFileName)-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; odsptr = &ConfigFileOds[++MetaFileLevel]; status = OdsLoadTextFile (odsptr, MetaFileName); if (VMSnok (status)) { /* fatal error */ MetaConReport (mcptr, METACON_REPORT_ERROR, "Error including file, !&m", status); sys$setprv (0, &SysPrvMask, 0, 0); return (SS$_ABORT); } mcptr->CurrentOdsPtr = odsptr; /* config functions can tell what type of file it is from */ MetaFileType[MetaFileLevel] = METACON_TYPE_FILE; } mclptr->Token = METACON_TOKEN_INCLUDE; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel-1; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; mcptr->IncludeFile = true; } else if (strsame (cptr, "dict", 4)) { /********/ /* dict */ /********/ mclptr->Token = METACON_TOKEN_DICT; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; } else if (SAME2(cptr,'!#')) { /********************/ /* included comment */ /********************/ mclptr->Token = METACON_TOKEN_COMMENT; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel-1; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; } else if (mclptr->Token != METACON_TOKEN_VERSION) { /*********************/ /* just another line */ /*********************/ mclptr->Token = METACON_TOKEN_TEXT; mclptr->FlowControlLevel = FlowControlLevel; mclptr->MetaFileLevel = MetaFileLevel; mclptr->MetaFileType = MetaFileType[MetaFileLevel]; } if (NotThisVersion && mclptr->Token != METACON_TOKEN_VERSION) continue; /****************/ /* post-process */ /****************/ mclptr->Length = sptr - (char*)&mclptr->Storage; mclptr->InlineTextPtr = InlinePtr && InlinePtr[0] ? InlinePtr : NULL; mclptr->LineDataPtr = NULL; if (WATCH_MOD && WatchThisOne) WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z !&Z\n", mclptr, mclptr->Size, mclptr->Token, mclptr->Number, mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr, mclptr->InlineTextPtr); /* point to the current "line" (for callback purposes) */ mcptr->ParsePtr = mclptr; if (CallBackFunction) if (!(ok = (*CallBackFunction)(mcptr))) break; /* point at the start of the next "line" storage area */ mclptr = NextLinePtr; } /* terminating empty (zero-length) line */ mclptr->Size = 0; /* final callback to allow post-configuration processing */ mclptr->Token = METACON_TOKEN_POST; mcptr->ParsePtr = mclptr; if (CallBackFunction) ok = (*CallBackFunction)(mcptr); } /* disable SYSPRV after accessing the required files */ sys$setprv (0, &SysPrvMask, 0, 0); /* replace the last file name with the search list logical name */ if (mcptr->LnmCount > 1) strcpy (mcptr->LoadReport.FileName, FileName); #if WATCH_MOD if (TestToken) { fprintf (stdout, "%s", mcptr->LoadReport.TextPtr); exit (SS$_NORMAL); } #endif /* WATCH_MOD */ MetaConParseReset (mcptr, true); while (cptr = MetaConParse (NULL, mcptr, NULL, false)) if (!cptr[0] && cptr[1]) MetaConReport (mcptr, METACON_REPORT_ERROR, cptr+1); if (ok) return (SS$_NORMAL); return (SS$_ABORT); } /*****************************************************************************/ /* Free the memory associated with a meta-config structure. If there is any related data associated with a "line" then either free it or if a callback function was supplied then call that to do the job. */ MetaConUnload ( META_CONFIG **MetaConPtrPtr, CALL_BACK CallBackFunction ) { META_CONFIG *mcptr; METACON_LINE *mclptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConUnload() !&A", CallBackFunction); if (!(mcptr = *MetaConPtrPtr)) return; for (mclptr = mcptr->ContentPtr; mclptr && mclptr->Size; mclptr = (METACON_LINE*)((char*)mclptr + mclptr->Size)) { if (mclptr->LineDataPtr) { if (CallBackFunction) (*CallBackFunction)(mclptr->LineDataPtr); else VmFree (mclptr->LineDataPtr, FI_LI); } /* free any precompiled regular expression structures */ for (mclptr->RegexPregCount = 0; mclptr->RegexPregCount < METACON_REGEX_PREG_MAX; mclptr->RegexPregCount++) if (mclptr->RegexPreg[mclptr->RegexPregCount].buffer) regfree (&mclptr->RegexPreg[mclptr->RegexPregCount]); } if (mcptr->LoadReport.TextPtr) VmFree (mcptr->LoadReport.TextPtr, FI_LI); if (mcptr->AuthMetaPtr) AuthConfigUnload (mcptr); if (mcptr->ConfigMetaPtr) ConfigUnload (mcptr); if (mcptr->MappingMetaPtr) MapUrl_ConfigUnload (mcptr); if (mcptr->MsgMetaPtr) MsgConfigUnload (mcptr); if (mcptr->ServiceMetaPtr) ServiceConfigUnload (mcptr); VmFree (mcptr, FI_LI); *MetaConPtrPtr = NULL; } /*****************************************************************************/ /* Restart the parse processing by reseting some pointers and optionally flow-control information. */ MetaConParseReset ( META_CONFIG *mcptr, BOOL StateReset ) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConParseReset() !&X !&B", mcptr, StateReset); if (StateReset) { mcptr->ParseIndex = 0; mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_DEFAULT; mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; MetaconClientConcurrent (NULL, NULL, 0); } mcptr->ParsePtr = mcptr->ParseNextPtr = mcptr->ContentPtr; MetaConNoteValid = false; } /*****************************************************************************/ /* Successively called to parse raw "lines" (i.e. the METACON_LINE structure) from the configuration. When exhausted return NULL. MetaConParseReset() needs to be called prior to beginning the parse. */ METACON_LINE* MetaConParseRaw (META_CONFIG *mcptr) { METACON_LINE *mclptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConParseRaw() !&X", mcptr); mclptr = mcptr->ParsePtr = mcptr->ParseNextPtr; if (WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z\n", mclptr, mclptr->Size, mclptr->Token, mclptr->Number, mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr); /* if terminating empty "line" */ if (mclptr->Size == 0) return (NULL); /* adjust the parse context to the next "line" */ mcptr->ParseNextPtr = (METACON_LINE*)((char*)mclptr + mclptr->Size); return (mclptr); } /*****************************************************************************/ /* Successively called to parse "lines" from the configuration. The function automatically processes the conditional directives and returns to the caller successive lines that the caller can process. When the configuration "lines" are exhausted it returns NULL. MetaConParseReset() needs to be called prior to beginning the parse. If 'rqptr' parameter is NULL then the function is just being used for checking. Note that 'empty strings' are returned as "\0" (i.e. two successive null characters) because error strings are returned with a leading null character followed by the error message characters! */ char* MetaConParse ( REQUEST_STRUCT *rqptr, META_CONFIG *mcptr, METACON_LINE **LinePtrPtr, BOOL WatchThisOne ) { BOOL NotThisVersion, NotThisVirtualService, UnresolvedFlowControl; int LineSize, LineToken; ulong number; char *cptr; METACON_LINE *mclptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConParse() !&X !&X", mcptr, LinePtrPtr); NotThisVersion = NotThisVirtualService = false; if (rqptr) svptr = rqptr->ServicePtr; for (;;) { mclptr = mcptr->ParsePtr = mcptr->ParseNextPtr; /* used by the parse functions */ mclptr->RequestPtr = rqptr; if (rqptr == NULL) mclptr->WatchThisOne = WatchThisOne = NULL; else mclptr->WatchThisOne = WatchThisOne; if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ( "flow[!UL]=!UL state[!UL]=!UL\n\ !&X !UL !UL !UL !UL !&X !&Z !&Z\n", mcptr->ParseIndex, mcptr->ParseFlow[mcptr->ParseIndex], mcptr->ParseIndex, mcptr->ParseState[mcptr->ParseIndex], mclptr, mclptr->Size, mclptr->Token, mclptr->Number, mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr, mclptr->InlineTextPtr); /* if terminating empty "line" */ if (!(LineSize = mclptr->Size)) { if (LinePtrPtr) *LinePtrPtr = NULL; if (!mcptr->ParseIndex) return (NULL); mcptr->ParseIndex = 0; return ("\0Unresolved flow-control 1"); } /* adjust the parse context to the next "line" */ mcptr->ParseNextPtr = (METACON_LINE*)((char*)mclptr + LineSize); if (LinePtrPtr) *LinePtrPtr = mclptr; LineToken = mclptr->Token; if (LineToken == METACON_TOKEN_VERSION) { if (WatchThisOne) WatchDataFormatted ("!4ZL [[!AZ]]\n", mclptr->Number, mclptr->TextPtr); /* "[[wasd*n.n.n]]" resets all meta-config flow-control */ UnresolvedFlowControl = mcptr->ParseIndex; mcptr->ParseIndex = 0; mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_DEFAULT; mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; if (UnresolvedFlowControl) return ("\0Unresolved flow-control 2"); /* if just doing a syntax check */ if (!rqptr) continue; /* e.g. "[[wasd==11.0.1]]", "[[wasd<11.1]]", "[[wasd!=11.2]]" */ switch (*(USHORTPTR)mclptr->VersionCompare) { case '=\0' : case '==' : if (mclptr->VersionNumber == -1) NotThisVersion = false; else NotThisVersion = HttpdVersionNumber != mclptr->VersionNumber; break; case '!=' : NotThisVersion = HttpdVersionNumber == mclptr->VersionNumber; break; case '<\0' : NotThisVersion = HttpdVersionNumber >= mclptr->VersionNumber; break; case '!>' : case '<=' : NotThisVersion = HttpdVersionNumber > mclptr->VersionNumber; break; case '>\0' : NotThisVersion = HttpdVersionNumber <= mclptr->VersionNumber; break; case '!<' : case '>=' : NotThisVersion = HttpdVersionNumber < mclptr->VersionNumber; break; default : NotThisVersion = true; } continue; } if (NotThisVersion) continue; if (LineToken == METACON_TOKEN_SERVICE) { if (WatchThisOne) WatchDataFormatted ("!4ZL [[!AZ]]\n", mclptr->Number, mclptr->TextPtr); /* "[[service]]" resets all meta-config flow-control */ UnresolvedFlowControl = mcptr->ParseIndex; mcptr->ParseIndex = 0; mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_DEFAULT; mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; if (UnresolvedFlowControl) return ("\0Unresolved flow-control 3"); /* start off with it being of interest and deny as necessary */ NotThisVirtualService = false; /* if just doing a syntax check */ if (!rqptr) continue; cptr = mclptr->TextPtr; if (SAME4 (cptr, 'http')) { if (SAME3 (cptr+4, '://')) { if (svptr->RequestScheme != SCHEME_HTTP) { NotThisVirtualService = true; continue; } cptr += 7; } else if (SAME4 (cptr+4, 's://')) { if (svptr->RequestScheme != SCHEME_HTTPS) { NotThisVirtualService = true; continue; } cptr += 8; } } /* if for all virtual services */ if (SAME4(cptr,'*:*\0')) continue; /* if for an unknown virtual service */ if (*cptr == '?') { if (!rqptr->UnknownVirtualService) { NotThisVirtualService = true; continue; } /* check port specificity */ while (*cptr && *cptr != ':') cptr++; if (*cptr) cptr++; if (!*cptr || *cptr == '?' || SAME2(cptr,'*\0')) continue; NotThisVirtualService = !StringMatch (rqptr, svptr->ServerPortString, cptr); continue; } /* if conditional matches this request's virtual service */ NotThisVirtualService = !StringMatch (rqptr, svptr->ServerHostPort, cptr); continue; } /* if it wasn't a service spec and not interested in the current one */ if (NotThisVirtualService) continue; if (LineToken == METACON_TOKEN_CONFIG || LineToken == METACON_TOKEN_INCLUDE) { if (WatchThisOne) WatchDataFormatted ("!AZ!4ZL !AZ\n", mclptr->ConfigProblem ? "?" : "", mclptr->Number, mclptr->TextPtr); continue; } if (LineToken == METACON_TOKEN_DICT) { if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_NOW) { if (WatchThisOne) WatchDataFormatted ("!4ZL !AZ\n", mclptr->Number, mclptr->TextPtr); /* if not just checking */ if (rqptr != NULL) { for (cptr = mclptr->TextPtr + 4; *cptr && ISLWS(*cptr); cptr++); MetaConBuffer (mclptr, NULL, 0); MetaConBuffer (mclptr, &cptr, 0); MetaConDictionary (rqptr, mclptr->BufferPtr); if (WatchThisOne && WATCH_CATEGORY(WATCH_INTERNAL)) DictWatch (rqptr->rqDictPtr, DICT_TYPE_CONFIG, "*"); } } continue; } if (LineToken == METACON_TOKEN_COMMENT) { if (WatchThisOne) WatchDataFormatted ("!4ZL !AZ\n", mclptr->Number, mclptr->TextPtr); continue; } if (LineToken == METACON_TOKEN_TEXT || LineToken == METACON_TOKEN_DIRECTORY) { if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_NOW) { if (WatchThisOne) if (LineToken == METACON_TOKEN_DIRECTORY) WatchDataFormatted ("!4ZL !AZ\n", mclptr->Number, mclptr->TextPtr); return (mclptr->TextPtr); } if (WatchThisOne) WatchDataFormatted ("!4ZL !AZ\n", mclptr->Number, mclptr->TextPtr); /* return if just checking */ if (!rqptr) return ("\0"); continue; } if (WatchThisOne) WatchDataFormatted ("!4ZL !AZ\n", mclptr->Number, mclptr->TextPtr); if (LineToken == METACON_TOKEN_IF) { if (mcptr->ParseIndex >= METACON_MAX_FLOW_CONTROL-1) { if (WatchThisOne) WatchDataFormatted ("TOO MANY CONDITIONS\n"); return ("\0Too many conditions"); } mcptr->ParseIndex++; mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_IF; mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_BEFORE; if (mcptr->ParseState[mcptr->ParseIndex-1] == METACON_STATE_NOW) { cptr = MetaConEvaluate (mclptr); if (*cptr || SAME4(cptr,'\0\0\0\1')) { /* expression with inline string (true or false) */ mcptr->ParseIndex--; return (cptr); } /* set processing-now if expression is true */ if (SAME4(cptr,'\0\0\1\0')) mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; else /* return if a problem report */ if (!*cptr && *(cptr+1)) { /* decrement the parse index if it was an inline directive */ if (mclptr->InlineTextPtr) { mcptr->ParseFlow[mcptr->ParseIndex] = mcptr->ParseState[mcptr->ParseIndex] = 0; mcptr->ParseIndex--; } return (cptr); } /* return if just checking */ if (!rqptr) return ("\0"); } else { /* decrement the parse index if it was an inline directive */ if (mclptr->InlineTextPtr) { mcptr->ParseFlow[mcptr->ParseIndex] = mcptr->ParseState[mcptr->ParseIndex] = 0; mcptr->ParseIndex--; } } continue; } if (LineToken == METACON_TOKEN_ELIF) { if (mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_IF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_IFIF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_UNIF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_ELIF) { if (WatchThisOne) WatchDataFormatted ("OUT-OF-PLACE \'ELIF()\'\n"); return ("\0Out-of-place \'elif()\'"); } if (mcptr->ParseFlow[mcptr->ParseIndex] == METACON_FLOW_UNIF) { /* 'unif' unconditional execution must be popped */ mcptr->ParseFlow[mcptr->ParseIndex] = mcptr->ParseState[mcptr->ParseIndex] = 0; mcptr->ParseIndex--; } if (mcptr->ParseState[mcptr->ParseIndex-1] == METACON_STATE_NOW) { mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_ELIF; if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_NOW) mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_AFTER; else if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_BEFORE) { cptr = MetaConEvaluate (mclptr); if (*cptr) { /* an inline directive following the expression */ return ("\0Extraneous text follows \'elif()\'"); } /* set processing-now if sentinal string is true */ if (SAME4(cptr,'\0\0\1\0')) mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; else /* return if a problem report */ if (!*cptr && *(cptr+1)) return (cptr); /* return if just checking */ if (!rqptr) return ("\0"); } } continue; } if (LineToken == METACON_TOKEN_ELSE) { if (mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_IF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_ELIF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_UNIF) { if (WatchThisOne) WatchDataFormatted ("OUT-OF-PLACE \'ELSE\'\n"); return ("\0Out-of-place \'else\'"); } if (mcptr->ParseFlow[mcptr->ParseIndex] == METACON_FLOW_UNIF) { /* 'unif' unconditional execution must be popped */ mcptr->ParseFlow[mcptr->ParseIndex] = mcptr->ParseState[mcptr->ParseIndex] = 0; mcptr->ParseIndex--; } if (mcptr->ParseState[mcptr->ParseIndex-1] == METACON_STATE_NOW) { mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_ELSE; if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_NOW) mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_AFTER; else if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_BEFORE) { cptr = mclptr->TextPtr; while (*cptr && !ISLWS(*cptr)) cptr++; while (*cptr && ISLWS(*cptr)) cptr++; /* return if directive following the conditional */ if (*cptr) return (cptr); mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; /* return if just checking */ if (!rqptr) return ("\0"); } } continue; } if (LineToken == METACON_TOKEN_IFIF) { if (mcptr->ParseFlow[mcptr->ParseIndex] == METACON_FLOW_ELSE) { /* 'ifif' after 'else' executes only if the 'else' wasn't */ if (mcptr->ParseState[mcptr->ParseIndex-1] == METACON_STATE_NOW) { mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_IFIF; if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_NOW) mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_AFTER; else if (mcptr->ParseState[mcptr->ParseIndex] == METACON_STATE_AFTER) { /* i.e. after the original 'if' or 'elif' executed */ cptr = mclptr->TextPtr; while (*cptr && !ISLWS(*cptr)) cptr++; while (*cptr && ISLWS(*cptr)) cptr++; /* return if directive following the conditional */ if (*cptr) return (cptr); mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; /* return if just checking */ if (!rqptr) return ("\0"); } } continue; } if (mcptr->ParseFlow[mcptr->ParseIndex] == METACON_FLOW_UNIF) { /* 'unif' unconditional execution must be popped */ mcptr->ParseFlow[mcptr->ParseIndex] = mcptr->ParseState[mcptr->ParseIndex] = 0; mcptr->ParseIndex--; /* return if just checking */ if (!rqptr) return ("\0"); continue; } /* 'ifif' after anything other than 'else' or 'unif' */ if (WatchThisOne) WatchDataFormatted ("OUT-OF-PLACE \'IFIF\'\n"); return ("\0Out-of-place \'ifif\'"); } if (LineToken == METACON_TOKEN_UNIF) { /* unconditional execution */ if (mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_IF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_ELIF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_IFIF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_ELSE) { if (WatchThisOne) WatchDataFormatted ("OUT-OF-PLACE \'UNIF\'\n"); return ("\0Out-of-place \'unif\'"); } if (mcptr->ParseIndex >= METACON_MAX_FLOW_CONTROL-1) { if (WatchThisOne) WatchDataFormatted ("TOO MANY CONDITIONS\n"); return ("\0Too many conditions"); } mcptr->ParseIndex++; mcptr->ParseFlow[mcptr->ParseIndex] = METACON_FLOW_UNIF; mcptr->ParseState[mcptr->ParseIndex] = METACON_STATE_NOW; /* return if just checking */ if (!rqptr) return ("\0"); continue; } if (LineToken == METACON_TOKEN_ENDIF) { if (mcptr->ParseIndex == 0 || (mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_IF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_ELIF && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_ELSE && mcptr->ParseFlow[mcptr->ParseIndex] != METACON_FLOW_UNIF)) { if (WatchThisOne) WatchDataFormatted ("OUT-OF-PLACE \'ENDIF\'\n"); return ("\0Out-of-place \'endif\'"); } mcptr->ParseFlow[mcptr->ParseIndex] = mcptr->ParseState[mcptr->ParseIndex] = 0; mcptr->ParseIndex--; /* return if just checking */ if (!rqptr) return ("\0"); continue; } #if WATCH_MOD if (LineToken == METACON_TOKEN_TEST) { /* used for testing of MetaConEvaluate() */ cptr = MetaConEvaluate (mclptr); continue; } #endif /* WATCH_MOD */ ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } /*****************************************************************************/ /* Evaluate the conditional expression pointed to by 'LinePtr'. If 'rqptr' parameter is NULL then the function is just being used to check the syntax of the conditional and/or return and error or in-line text. */ char* MetaConEvaluate (METACON_LINE *mclptr) { #define IFRQ(datum) (rqptr != NULL ? (void*)rqptr->datum : (void*)NULL) static ulong PrevTime640, RandomNumber; BOOL result, WatchThisOne; BOOL ResultStack [METACON_STACK_MAX]; int idx, klen, modulas, number, CurrentNumber, FileNameLength, OperatorIndex, ResultIndex; char ch1, ch2; char *cptr, *fptr, *sptr, *zptr, *DirectivePtr; char key [256]; char FileName [ODS_MAX_FILE_NAME_LENGTH+1]; char *OperatorStack [METACON_STACK_MAX]; char IpAddrString [128]; DICT_ENTRY_STRUCT *denptr; REQUEST_STRUCT *rqptr; SERVICE_STRUCT *svptr; TCPIP_HOST_LOOKUP HostLookup; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; WatchThisOne = mclptr->WatchThisOne; if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConEvaluate()"); WatchDataFormatted ("!&Z\n", mclptr->TextPtr); } if (rqptr) svptr = rqptr->ServicePtr; /* (re)seed the random number every second or so */ if (PrevTime640 != HttpdTime64[0]) RandomNumber += (PrevTime640 = HttpdTime64[0]); /* cheap (no subroutine call) MTH$RANDOM() */ RandomNumber = RandomNumber * 69069 + 1; OperatorIndex = ResultIndex = 0; OperatorStack[OperatorIndex] = ""; ResultStack[ResultIndex] = false; cptr = mclptr->TextPtr; while (isalpha(*cptr)) cptr++; while (*cptr) { while (ISLWS(*cptr)) cptr++; if (!(ch1 = *cptr)) break; if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ("!&Z\n", cptr); if (ch1 == '!' || ch1 == '&' || ch1 == '|' || ch1 == '(') { if (OperatorIndex++ > METACON_STACK_MAX) { if (WatchThisOne) WatchDataFormatted ("TOO MANY \'(..)\'\n"); return ("\0Too many \'(..)\'"); } OperatorStack[OperatorIndex] = cptr++; if (*cptr == '&' || *cptr == '|') cptr++; if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted (">opr[!UL]=!1AZ\n", OperatorIndex, OperatorStack[OperatorIndex]); continue; } /* if the end of the conditional expression has been reached */ if (OperatorIndex <= 0 && ch1 != ')') break; /* initialize */ MetaConBuffer (mclptr, NULL, 0); mclptr->RegexPregCount = 0; result = false; /* buffer the keyword, pushing it to lower-case */ DirectivePtr = cptr; zptr = (sptr = key) + sizeof(key)-1; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = TOLO(*cptr++); *sptr = '\0'; klen = sptr - key; cptr = DirectivePtr; if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "!UL !&Z", klen, key); if (sptr >= zptr) { if (WatchThisOne) WatchDataFormatted ("Unfinished keyword\n"); return ("\0Unfinished keyword"); } if (ch1 == ')') cptr++; else if (key[0] == 'a') { /*******/ /* a.. */ /*******/ /* also match the trailing null character (in all these cases) */ if (klen == 6 && MATCH7(key, "accept")) result = MetaConConditionalList (mclptr, &cptr, klen, IFRQ(rqHeader.AcceptPtr)); else if (klen == 15 && MATCH16(key, "accept-language")) result = MetaConConditionalList (mclptr, &cptr, klen, IFRQ(rqHeader.AcceptLangPtr)); else if (klen == 14 && MATCH15(key,"accept-charset")) result = MetaConConditionalList (mclptr, &cptr, klen, IFRQ(rqHeader.AcceptCharsetPtr)); else if (klen == 15 && MATCH16(key,"accept-encoding")) result = MetaConConditionalList (mclptr, &cptr, klen, IFRQ(rqHeader.AcceptEncodingPtr)); else if (klen == 4 && MATCH5(key,"alpn")) result = MetaConSimpleMatch (mclptr, &cptr, klen, SesolaRequestALPN(rqptr)); } else if (key[0] == 'c') { /*******/ /* c.. */ /*******/ if (klen == 7 && MATCH8(key,"callout")) result = MetaConSimpleBoolean (mclptr, &cptr, klen, IFRQ(rqCgi.CalloutInProgress)); else if (klen == 17 && MATCH18(key,"client_connect_gt")) result = MetaconClientConcurrent (mclptr, &cptr, klen); else if (klen == 14 && MATCH15(key,"cluster_member")) result = MetaConEvaluateClusterMember (mclptr, &cptr, klen); else if (klen == 12 && MATCH13(key,"command_line")) result = MetaConSimpleMatch (mclptr, &cptr, klen, CommandLine); } else if (key[0] == 'd') { /*******/ /* d.. */ /*******/ if (klen == 6 && MATCH7(key,"decnet")) result = MetaConSimpleBoolean (mclptr, &cptr, klen, (SysInfo.DECnetVersion == *cptr - '0')); else if (klen == 4 && MATCH5(key,"demo")) result = MetaConSimpleBoolean (mclptr, &cptr, klen, CliDemo); else if (klen == 4 && MATCH5(key,"dict")) { int keylen; char *aptr, *keyptr; char DictType [2]; DICT_ENTRY_STRUCT *denptr; aptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; for (keyptr = aptr; *aptr && *aptr != '='; aptr++); keylen = aptr - keyptr; if (*aptr) aptr++; /* if key first char is not an alpha-numeric use it as the type */ if (isalnum (*keyptr)) DictType[0] = DICT_TYPE_CONFIG[0]; else { DictType[0] = *keyptr++; keylen--; } DictType[1] = '\0'; /* at this stage |aptr| points to the test expression */ denptr = DictLookup (rqptr->rqDictPtr, DictType, keyptr, keylen); if (denptr == NULL) result = false; else if (!*aptr) result = true; else result = StringMatchAndRegex (rqptr, DICT_GET_VALUE(denptr), aptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B dict:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, denptr == NULL ? "(null)" : (char*)DICT_GET_VALUE(denptr)); } else if (klen == 9 && MATCH10(key,"directory:")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; FileName[0] = '\0'; if (!mclptr->BufferPtr[0] && rqptr->rqHeader.PathInfoPtr) strzcpy (mclptr->BufferPtr, rqptr->rqHeader.PathInfoPtr, mclptr->BufferSize); for (sptr = mclptr->BufferPtr; *sptr && !SAME2(sptr,':['); sptr++); if (*sptr) strzcpy (FileName, mclptr->BufferPtr, sizeof(FileName)); else MapOdsUrlToVms (mclptr->BufferPtr, FileName, sizeof(FileName), 0, FALSE, rqptr->PathOds); for (sptr = FileName; *sptr; sptr++); if (sptr > FileName && *(sptr-1) == ']') { OdsNameOfDirectoryFile (FileName, 0, FileName, &FileNameLength); if (mclptr->BufferPtr[0] && FileName[0]) result = VMSok (OdsFileExists (NULL, FileName)); else result = false; } else result = false; if (WatchThisOne) WatchDataFormatted ("!&B directory:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, FileName); } else if (klen == 12 && MATCH13(key,"document-root")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqPathSet.MapRootPtr)); } else if (key[0] == 'f' && klen == 4 && MATCH5(key,"file")) { /*******/ /* f.. */ /*******/ MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; FileName[0] = '\0'; if (!mclptr->BufferPtr[0] && rqptr->rqHeader.PathInfoPtr) strzcpy (mclptr->BufferPtr, rqptr->rqHeader.PathInfoPtr, mclptr->BufferSize); for (sptr = mclptr->BufferPtr; *sptr && !SAME2(sptr,':['); sptr++); if (*sptr) strzcpy (FileName, mclptr->BufferPtr, sizeof(FileName)); else MapOdsUrlToVms (mclptr->BufferPtr, FileName, sizeof(FileName), 0, FALSE, rqptr->PathOds); for (sptr = FileName; *sptr; sptr++); if (sptr > FileName && *(sptr-1) == ']') OdsNameOfDirectoryFile (FileName, 0, FileName, &FileNameLength); if (mclptr->BufferPtr[0] && FileName[0]) result = VMSok (OdsFileExists (NULL, FileName)); else result = false; if (WatchThisOne) WatchDataFormatted ("!&B file:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, FileName); } else if (key[0] == 'h') { /*******/ /* h.. */ /*******/ if (klen == 4 && MATCH5(key,"host")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqHeader.HostPtr)); else if (klen == 5 && MATCH6(key,"http2")) result = MetaConSimpleBoolean (mclptr, &cptr, klen, (IFRQ(Http2Stream.Http2Ptr) != NULL)); } else if (key[0] == 'i' && klen == 8 && MATCH9(key,"instance")) { /*******/ /* i.. */ /*******/ result = MetaConEvaluateInstance (mclptr, &cptr, klen); } else if (key[0] == 'j' && klen == 12 && MATCH13(key,"jpi_username")) { /*******/ /* j.. */ /*******/ result = MetaConSimpleMatch (mclptr, &cptr, klen, HttpdProcess.UserName); } else if (key[0] == 'm') { /*******/ /* m.. */ /*******/ if (klen == 11 && MATCH12(key,"mapped-path")) { /* different to path-info, result after script or 'map' rule */ MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (rqptr->MetaConMappedPtr) /* only valid during rule mapping (what a kludge!) */ sptr = rqptr->MetaConMappedPtr; else sptr = rqptr->MappedPathPtr; if (sptr) result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B mapped-path:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, sptr); } else if (klen == 9 && MATCH10(key,"multihome")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (!rqptr->ClientPtr->MultiHomeIpAddressString[0]) { /* it is not a mismatched multi-homed service */ result = false; } else if (!strchr (mclptr->BufferPtr, '/')) { /* not a network mask */ sptr = rqptr->ClientPtr->MultiHomeIpAddressString; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } else { /* a network mask */ char *tptr = mclptr->BufferPtr; /* preserve string address */ result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr, &rqptr->ClientPtr->MultiHomeIpAddress)); } if (WatchThisOne) WatchDataFormatted ("!&B server_multihome:!AZ !AZ\n", result, mclptr->BufferPtr, rqptr->ClientPtr->MultiHomeIpAddressString); } } else if (key[0] == 'n') { /*******/ /* n.. */ /*******/ if (klen == 4 && MATCH5(key,"note")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (!MetaConNoteValid) { /* only need to get the value once per parse reset */ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); strzcpy (MetaConNote, HttpdGblSecPtr->MetaConNote, sizeof(MetaConNote)); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); MetaConNoteValid = true; } /* this semi-boolean just tests if the note has content */ if (!mclptr->BufferPtr[0] && MetaConNote[0]) result = true; else if (mclptr->BufferPtr[0]) result = StringMatchAndRegex (rqptr, MetaConNote, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B note:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, MetaConNote); } else if (klen == 7 && MATCH8(key,"notepad")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(NotePadPtr)); } else if (key[0] == 'o' && klen == 3 && MATCH4(key,"ods")) { /*******/ /* o.. */ /*******/ sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (*sptr == '2') result = rqptr->PathOds == MAPURL_PATH_ODS_2; else if (*sptr == '5') result = rqptr->PathOds == MAPURL_PATH_ODS_5; else if (TOUP(*sptr) == 'A') result = rqptr->PathOds == MAPURL_PATH_ODS_ADS; else if (TOUP(*sptr) == 'P') result = rqptr->PathOds == MAPURL_PATH_ODS_PWK; else if (TOUP(*sptr) == 'S' && TOUP(*(sptr+1)) == 'M') result = rqptr->PathOds == MAPURL_PATH_ODS_SMB; else if (TOUP(*sptr) == 'S' && TOUP(*(sptr+1)) == 'R') result = rqptr->PathOds == MAPURL_PATH_ODS_SRI; else result = false; if (WatchThisOne) WatchDataFormatted ("!&B ods:!&C !&?2\r5\r\n", result, *sptr, !rqptr->PathOdsExtended); } else if (key[0] == 'p') { /*******/ /* p.. */ /*******/ if (klen == 4 && MATCH5(key,"pass")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (mclptr->BufferPtr[0] == '-') result = (rqptr->MetaConPass < 0); else result = (mclptr->BufferPtr[0] - '0' == rqptr->MetaConPass); if (WatchThisOne) WatchDataFormatted ("!&B pass:!AZ \'!SL\'\n", result, mclptr->BufferPtr, rqptr->MetaConPass); } else if (klen == 9 && MATCH10(key,"path-info")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqHeader.PathInfoPtr)); else if (klen == 15 && MATCH16(key,"path-translated")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(RequestMappedFile)); else if (klen == 7 && MATCH8(key,"proctor")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr) result = !rqptr->rqHeader.MethodName[0]; } } else if (key[0] == 'q' && klen == 12 && MATCH13(key,"query-string")) { /*******/ /* q.. */ /*******/ result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqHeader.QueryStringPtr)); } else if (key[0] == 'r') { /*******/ /* r.. */ /*******/ if (klen == 4 && MATCH5(key,"rand")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; /* conversion from ASCII string is relatively expensive */ if (isdigit(*sptr)) { if (isdigit(sptr[1])) { modulas = atoi(sptr); while (*sptr && isdigit(*sptr)) sptr++; } else modulas = *sptr++ - '0'; } else modulas = 0; if (*sptr == ':') sptr++; if (isdigit(*sptr)) { if (isdigit(sptr[1])) { number = atoi(sptr); while (*sptr && isdigit(*sptr)) sptr++; } else number = *sptr++ - '0'; } else number = 0; if (modulas < 2) modulas = 2; result = RandomNumber % modulas == number; if (WatchThisOne) WatchDataFormatted ("!&B rand:!UL:!UL !UL\n", result, modulas, number, RandomNumber); } else if (klen == 10 && MATCH11(key,"redirected")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (sptr[0]) /* specific count */ result = (sptr[0] - '0' == rqptr->RedirectCount); else /* no count specified, treat as a boolean */ result = rqptr->RedirectCount; if (WatchThisOne) WatchDataFormatted ("!&B redirected:!AZ \'!UL\'\n", result, sptr, rqptr->RedirectCount); } else if (klen == 7 && MATCH8(key,"referer")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqHeader.RefererPtr)); else if (klen == 5 && MATCH6(key,"regex")) result = MetaConSimpleBoolean (mclptr, &cptr, klen, Config.cfMisc.RegexSyntax); else if (klen == 11 && MATCH12(key,"remote-addr")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (strchr (mclptr->BufferPtr, '/')) { /* a network mask */ char *tptr = mclptr->BufferPtr; /* preserve string address */ sptr = &rqptr->ClientPtr->IpAddressString; result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr, &rqptr->ClientPtr->IpAddress)); } else if (mclptr->BufferPtr[0] == '?') { /* attempt to resolve a host name and use its IP address */ if (WatchThisOne) WatchDataFormatted ("LOOKUP !AZ\n", mclptr->BufferPtr+1); memset (&HostLookup, 0, sizeof(HostLookup)); TcpIpNameToAddress (&HostLookup, mclptr->BufferPtr+1, 1, NULL, 0); result = IPADDRESS_IS_SAME (&rqptr->ClientPtr->IpAddress, &HostLookup.IpAddress); if (WatchThisOne) FaoToBuffer (sptr = IpAddrString, sizeof(IpAddrString), 0, "!&I", &HostLookup.IpAddress); } else { sptr = &rqptr->ClientPtr->IpAddressString; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } if (WatchThisOne) WatchDataFormatted ("!&B remote-addr:!AZ !AZ\n", result, mclptr->BufferPtr, sptr); } else if (klen == 11 && MATCH12(key,"remote-host")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (strchr (mclptr->BufferPtr, '/')) { /* a network mask */ char *tptr = mclptr->BufferPtr; /* preserve string address */ sptr = &rqptr->ClientPtr->IpAddressString; result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr, &rqptr->ClientPtr->IpAddress)); } else if (isdigit(mclptr->BufferPtr[0]) || strchr(mclptr->BufferPtr,':')) { sptr = &rqptr->ClientPtr->IpAddressString; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } else { sptr = rqptr->ClientPtr->Lookup.HostName; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } if (WatchThisOne) WatchDataFormatted ("!&B remote-host:!AZ !AZ\n", result, mclptr->BufferPtr, sptr); } else if (klen == 7 && MATCH8(key,"request")) { char *aptr; sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; for (aptr = sptr; *aptr && *aptr != ':'; aptr++); if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST, sptr, aptr - sptr)) { result = StringMatchAndRegex (rqptr, sptr = DICT_GET_VALUE(denptr), *aptr == ':' ? aptr+1 : aptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } if (WatchThisOne) WatchDataFormatted ("!&B request:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, sptr); } else if (klen == 14 && MATCH15(key,"request-method")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (sptr[0] == '?') result = (rqptr->rqHeader.Method == HTTP_METHOD_EXTENSION); else result = StringMatchAndRegex (rqptr, rqptr->rqHeader.MethodName, sptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B request-method:!AZ !AZ\n", result, sptr, rqptr->rqHeader.MethodName); } else if (klen == 16 && MATCH17(key,"request-protocol")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (MATCH5 (sptr, "HTTP/") || MATCH5 (sptr, "http/")) sptr += 5; if (HTTP2_REQUEST(rqptr)) result = MATCH2 (sptr, "2"); else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) result = MATCH4 (sptr, "1.1"); else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) result = MATCH4 (sptr, "1.0"); else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9) result = MATCH4 (sptr, "0.9"); if (WatchThisOne) WatchDataFormatted ("!&B request-protocol:!AZ \'!AZ\'\n", result, mclptr->BufferPtr, SoftwareId); } else if (klen == 14 && MATCH15(key,"request-scheme")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (MATCH6(mclptr->BufferPtr,"https") || MATCH7(mclptr->BufferPtr,"https:")) result = svptr->RequestScheme == SCHEME_HTTPS; else if (MATCH5(mclptr->BufferPtr,"http") || MATCH6(mclptr->BufferPtr,"http:")) result = svptr->RequestScheme == SCHEME_HTTP; else result = false; } else if (klen == 11 && MATCH12(key,"request-uri")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqHeader.RequestUriPtr)); else if (klen == 7 && MATCH8(key,"restart")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (isdigit(sptr[1])) result = (atoi(mclptr->BufferPtr[0]) == rqptr->MetaConRestartCount); else result = (mclptr->BufferPtr[0] - '0' == rqptr->MetaConRestartCount); if (WatchThisOne) WatchDataFormatted ("!&B restart:!AZ \'!UL\'\n", result, sptr, rqptr->MetaConRestartCount); } else if (klen == 5 && MATCH6(key,"robin")) result = MetaConEvaluateRoundRobin (mclptr, &cptr, klen); } else if (key[0] == 's') { /*******/ /* s.. */ /*******/ if (klen == 11 && MATCH12(key,"script-name")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (sptr = rqptr->MetaConScriptPtr) { /* in second pass of rule mapping (groan) */ if (*sptr == '+') { /* CGIplus script indicator */ *sptr = '/'; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B script-name:!AZ !AZ\n", result, mclptr->BufferPtr, sptr); *sptr = '+'; } else { /* just a standard script beginning with '/' */ result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B script-name:!AZ !AZ\n", result, mclptr->BufferPtr, sptr); } } else { /* must be past rule mapping */ result = StringMatchAndRegex (rqptr, rqptr->ScriptName, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B script-name:!AZ !AZ\n", result, mclptr->BufferPtr, rqptr->ScriptName); } } else if (klen == 11 && MATCH12(key,"server-addr")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (!strchr (mclptr->BufferPtr, '/')) { /* not a network mask */ sptr = svptr->ServerIpAddressString; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } else { /* a network mask */ char *tptr = mclptr->BufferPtr; /* preserve string address */ result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr, &svptr->ServerIpAddress)); } if (WatchThisOne) WatchDataFormatted ("!&B server-addr:!AZ !AZ\n", result, mclptr->BufferPtr, svptr->ServerIpAddressString); } else if (klen == 17 && MATCH18(key,"server_connect_gt")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); CurrentNumber = 0; for (idx = 0; idx <= InstanceNodeCurrent; idx++) CurrentNumber += AccountingPtr->CurrentInstanceConnected[HTTP1][idx]; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); result = CurrentNumber > atoi(sptr); if (WatchThisOne) WatchDataFormatted ("!&B server_connect_gt:!AZ !UL\n", result, sptr, CurrentNumber); } else if (klen == 11 && MATCH12(key,"server-name")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(ServicePtr->ServerHostName)); else if (klen == 11 && MATCH12(key,"server-port")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(ServicePtr->ServerPortString)); else if (klen == 17 && MATCH18(key,"server_process_gt")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); CurrentNumber = NetCurrentProcessing; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); result = CurrentNumber > atoi(sptr); if (WatchThisOne) WatchDataFormatted ("!&B server_process_gt:!AZ !UL\n", result, sptr, CurrentNumber); } else if (klen == 15 && MATCH16(key,"server-protocol")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (MATCH5 (sptr, "HTTP/")) sptr += 5; if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) result = MATCH4 (sptr, "1.1"); else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) result = MATCH4 (sptr, "1.0"); else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9) result = MATCH4 (sptr, "0.9"); if (WatchThisOne) WatchDataFormatted ("!&B server-protocol:!AZ \'!AZ\'\n", result, sptr, SoftwareId); } else if (klen == 15 && MATCH16(key,"server-software")) result = MetaConSimpleMatch (mclptr, &cptr, klen, SoftwareId); else if (klen == 7 && MATCH8(key,"service")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (*sptr == '?') { if (rqptr->UnknownVirtualService) { while (*sptr && *sptr != ':') sptr++; if (*sptr) sptr++; if (!*sptr || *sptr == '?' || SAME2(sptr,'*\0')) result = true; else result = StringMatch (rqptr, svptr->ServerPortString, sptr); } } else result = StringMatchAndRegex (rqptr, svptr->ServerHostPort, sptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B service:!AZ !AZ\n", result, mclptr->BufferPtr, svptr->ServerHostPort); } else if (klen == 3 && MATCH4(key,"ssl")) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; result = svptr->RequestScheme == SCHEME_HTTPS; if (WatchThisOne) WatchDataFormatted ("!&B ssl:\n", result); } else if (klen == 13 && MATCH14(key,"syi_arch_name")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; #ifdef __ALPHA result = strsame (mclptr->BufferPtr, "ALPHA", -1) || strsame (mclptr->BufferPtr, "AXP", -1); if (WatchThisOne) WatchDataFormatted ("!&B syi_arch_name:!AZ Alpha/AXP\n", result, mclptr->BufferPtr); #endif #ifdef __ia64 result = strsame (mclptr->BufferPtr, "IA64", -1); if (WatchThisOne) WatchDataFormatted ("!&B syi_arch_name:!AZ IA64\n", result, mclptr->BufferPtr); #endif #ifdef __VAX result = strsame (mclptr->BufferPtr, "VAX", -1); if (WatchThisOne) WatchDataFormatted ("!&B syi_arch_name:!AZ VAX\n", result, mclptr->BufferPtr); #endif } else if (klen == 11 && MATCH12(key,"syi_hw_name")) result = MetaConSimpleMatch (mclptr, &cptr, klen, SysInfo.HwName); else if (klen == 12 && MATCH13(key,"syi_nodename")) result = MetaConSimpleMatch (mclptr, &cptr, klen, SysInfo.NodeName); else if (klen == 11 && MATCH12(key,"syi_version")) result = MetaConSimpleMatch (mclptr, &cptr, klen, SysInfo.Version); } else if (key[0] == 't') { /*******/ /* t.. */ /*******/ if (klen == 5 && MATCH6(key,"tcpip")) result = MetaConSimpleMatch (mclptr, &cptr, klen, TcpIpAgentInfo); else if (klen == 4 && MATCH5(key,"time")) result = MetaConEvaluateTime (mclptr, &cptr, klen); else if (klen == 6 && MATCH7(key,"trnlnm")) result = MetaConEvaluateTrnLnm (mclptr, &cptr, klen); } else if (key[0] == 'u') { /*******/ /* u.. */ /*******/ if (klen == 13 && MATCH14(key,"upstream-addr")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (!strchr (mclptr->BufferPtr, '/')) { /* not a network mask */ sptr = rqptr->ClientPtr->UpstreamIpAddressString; result = StringMatchAndRegex (rqptr, sptr, mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); } else { /* a network mask */ char *tptr = mclptr->BufferPtr; /* preserve string address */ result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr, &rqptr->ClientPtr->UpstreamIpAddress)); } if (WatchThisOne) WatchDataFormatted ("!&B upstream-addr:!AZ !AZ\n", result, mclptr->BufferPtr, rqptr->ClientPtr->UpstreamIpAddressString); } else if (klen == 10 && MATCH11(key,"user-agent")) result = MetaConSimpleMatch (mclptr, &cptr, klen, IFRQ(rqHeader.UserAgentPtr)); } else if (key[0] == 'w') { /*******/ /* w.. */ /*******/ if (klen == 6 && MATCH7(key,"webdav")) { MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (mclptr->BufferPtr[0]) { if (strsame (mclptr->BufferPtr, "MSagent", 7)) result = DavWebMicrosoftDetect (rqptr); } else result = rqptr->WebDavRequest; if (WatchThisOne) WatchDataFormatted ("!&B WebDAV:!AZ\n", result, mclptr->BufferPtr); } else if (klen == 9 && MATCH10(key,"websocket")) result = MetaConSimpleBoolean (mclptr, &cptr, klen, IFRQ(rqHeader.UpgradeWebSocket)); } else if (key[0] == 'x' && klen == 4 && MATCH5(key,"x509")) { /*******/ /* x.. */ /*******/ MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; if (!(result = SesolaClientCertMetaCon (rqptr, mclptr, WatchThisOne))) if (rqptr->X509ClientCertMeta) return ("\0X509 client certificate required"); if (WatchThisOne) WatchDataFormatted ("!&B X509:!AZ\n", result, mclptr->BufferPtr); } else if (isdigit(*cptr)) { /*******/ /* 9.. */ /*******/ /* numeric "boolean" */ result = atoi(sptr = cptr); while (isdigit(*cptr)) cptr++; if (WatchThisOne) WatchDataFormatted ("!&B !#AZ\n", result, cptr - sptr, sptr); } if (rqptr != NULL && cptr == DirectivePtr) { /************************/ /* no directive matched */ /************************/ /* see if it corresponds to a request header field */ if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST, key, klen)) { sptr = MetaConBuffer (mclptr, &cptr, klen); if (rqptr == NULL) goto JustChecking; result = StringMatchAndRegex (rqptr, DICT_GET_VALUE(denptr), mclptr->BufferPtr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (WatchThisOne) WatchDataFormatted ("!&B !AZ \'!AZ\'\n", result, DICT_GET_KEY(denptr), sptr, cptr); } else if (WatchThisOne) if (MetaConCommonRequestHeader (key)) WatchDataFormatted ("REQUEST FIELD !AZ NOT FOUND\n", key); else WatchDataFormatted ("(REQUEST FIELD?) !AZ NOT FOUND\n", key); } if (!mclptr->BufferPtr[0] && mclptr->BufferPtr[1]) { /********************/ /* problem reported */ /********************/ if (WatchThisOne) WatchDataFormatted ("!AZ\n", mclptr->BufferPtr+1); return (mclptr->BufferPtr); } JustChecking: /* can you feel Edsger looking over your shoulder? */ if (*cptr == '\"' || *cptr == '\'') { ch2 = *cptr++; while (*cptr && *cptr != ch2) { /* step over any escape character */ if (*cptr == '\\') cptr++; if (*cptr) cptr++; } if (*cptr) cptr++; } else { while (*cptr && !ISLWS(*cptr) && *cptr != ')') { /* step over any escape character */ if (*cptr == '\\') cptr++; if (*cptr) cptr++; } } if (ch1 != ')') { if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted (">val[!UL]=!&?TRUE\rFALSE\r\n", ResultIndex+1, result); if (ResultIndex++ > METACON_STACK_MAX) { if (WatchThisOne) WatchDataFormatted ("TOO MANY \'(..)\'\n"); return ("\0Too many \'(..)\'"); } ResultStack[ResultIndex] = result; } for (;;) { if (OperatorIndex <= 0) { if (WatchThisOne) WatchDataFormatted ("UNBALANCED \'(..)\'\n"); return ("\0Unbalanced \'(..)\'"); } if (*OperatorStack[OperatorIndex] == '(') { if (ch1 == ')') { if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ("val[!UL]=!&?TRUE\rFALSE\r\n", ResultIndex, ResultStack[ResultIndex], OperatorIndex, OperatorStack[OperatorIndex], ResultIndex, !ResultStack[ResultIndex]); ResultStack[ResultIndex] = !ResultStack[ResultIndex]; OperatorIndex--; continue; } if (*OperatorStack[OperatorIndex] == '&') { if (ResultIndex <= 1) { if (WatchThisOne) WatchDataFormatted ("INSUFFICIENT CONDITIONALS FOR \'&&\'\n"); return ("\0Insufficient conditionals for \'&&\'"); } if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ( "val[!UL]=!&?TRUE\rFALSE\r\n", ResultIndex, ResultStack[ResultIndex], ResultIndex-1, ResultStack[ResultIndex-1], OperatorIndex, OperatorStack[OperatorIndex], ResultIndex-1, ResultStack[ResultIndex-1] && ResultStack[ResultIndex]); ResultStack[ResultIndex-1] = ResultStack[ResultIndex-1] && ResultStack[ResultIndex]; ResultIndex--; OperatorIndex--; continue; } if (*OperatorStack[OperatorIndex] == '|') { if (ResultIndex <= 1) { if (WatchThisOne) WatchDataFormatted ("INSUFFICIENT CONDITIONALS FOR \'||\'\n"); return ("\0Insufficient conditionals for \'||\'"); } if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ( "val[!UL]=!&?TRUE\rFALSE\r\n", ResultIndex, ResultStack[ResultIndex], ResultIndex-1, ResultStack[ResultIndex-1], OperatorIndex, OperatorStack[OperatorIndex], ResultIndex-1, ResultStack[ResultIndex-1] || ResultStack[ResultIndex]); ResultStack[ResultIndex-1] = ResultStack[ResultIndex-1] || ResultStack[ResultIndex]; ResultIndex--; OperatorIndex--; continue; } if (WatchThisOne) WatchDataFormatted ("SANITY CHECK\n"); return ("\0Sanity check"); } } if (!rqptr) mclptr->RegexCompiled = true; if (OperatorIndex) { if (WatchThisOne) WatchDataFormatted ("UNBALANCED \'(..)\'\n"); return ("\0Unbalanced \'(..)\'"); } if (ResultIndex != 1) { if (WatchThisOne) WatchDataFormatted ("MISSING \'&&\', \'||\', \'!\' OR CONDITIONAL\n"); return ("\0Missing \'&&\', \'||\', \'!\' or conditional"); } if (ResultStack[ResultIndex]) { if (WatchThisOne) WatchDataFormatted ("TRUE\n"); /* a sentinal string indicating block-statement true */ if (!*cptr) return ("\0\0\1\0"); /* return an in-line directive (also indicates true) */ return (cptr); } if (WatchThisOne) WatchDataFormatted ("FALSE\n"); if (!*cptr) { /* a sentinal string indicating block-statement false */ return ("\0\0\0\0"); } /* a sentinal string indicating inline false */ if (rqptr) return ("\0\0\0\1"); /* just doing a rule check, return any inline text */ return (cptr); #undef IFRQ } /*****************************************************************************/ /* Copies the parameter string following a directive name into the specified buffer. For example, this function would buffer "*.vsm.com.au" from the conditional "host:*.vsm.com.au". These strings can be enclosed by single or double quotes and contain backslash-escaped characters. Escaped chacaters are unescaped by this function. If the parameter |klen| is zero then it's a DICT keyword and can be processed to end-of-string. For conditionals, an unquoted string is terminated by white-space, closing parenthesis, or end-of-string. If the buffer contents are a regular expression, and there is still available precompile regex buffers available, then compile the regex. Don't try to mix up substitution syntax (''..') inside single quoted strings!! */ char* MetaConBuffer ( METACON_LINE *mclptr, char **text, int klen ) { static int BufferSize; static char *BufferPtr; char *cptr, *sptr, *tptr, *zptr; regex_t RegexPreg; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConBuffer() !&Z", text ? *text : "(null)"); zptr = (sptr = BufferPtr) + BufferSize; if (text != NULL) if (klen) tptr = *text + klen + 1; /* key length plus the colon */ else tptr = *text; for (;;) { if (sptr >= zptr) { BufferSize = BufferSize ? BufferSize + BufferSize : 512; BufferPtr = VmRealloc (BufferPtr, BufferSize, FI_LI); zptr = (sptr = BufferPtr) + BufferSize; } mclptr->BufferPtr = BufferPtr; mclptr->BufferSize = BufferSize; if (text == NULL) { /* intialise the buffer */ mclptr->BufferLength = 0; SET4(mclptr->BufferPtr,'\0\0\0\0'); return (NULL); } cptr = tptr; while (*cptr && NOTEOL(*cptr)) { if (mclptr->Token != METACON_TOKEN_DICT) if (*cptr == ')' || ISLWS(*cptr) || ISEOL(*cptr)) break; if (*cptr == '\"') { cptr++; while (*cptr && *cptr != '\"' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } if (*cptr) cptr++; } else /* if two leading quotes (i.e. substitution) */ if (*cptr == '\'' && *(cptr+1) == '\'') { if (sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != '\'' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } if (*cptr) *sptr++ = *cptr++; } else /* a single leading quote (i.e. not substitution) */ if (*cptr == '\'') { cptr++; while (*cptr && *cptr != '\'' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } if (*cptr) cptr++; } else { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } } /* if the buffer overflowed then expand and do it all again */ if (sptr >= zptr) continue; *sptr = '\0'; mclptr->BufferLength = sptr - mclptr->BufferPtr; break; } if (mclptr->RegexCompiled) { /* this is during rule processing */ if (Config.cfMisc.RegexSyntax && mclptr->BufferPtr[0] == REGEX_CHAR) { if (mclptr->RegexPregCount < METACON_REGEX_PREG_MAX) mclptr->RegexPregPtr = &mclptr->RegexPreg[mclptr->RegexPregCount]; else mclptr->RegexPregPtr = NULL; mclptr->RegexPregCount++; } else mclptr->RegexPregPtr = NULL; } else if (Config.cfMisc.RegexSyntax && mclptr->BufferPtr[0] == REGEX_CHAR) { /* this is during rule load */ if (mclptr->RegexPregCount < METACON_REGEX_PREG_MAX) mclptr->RegexPregPtr = &mclptr->RegexPreg[mclptr->RegexPregCount]; else mclptr->RegexPregPtr = &RegexPreg; sptr = StringRegexCompile (mclptr->BufferPtr+1, mclptr->RegexPregPtr); if (sptr) { memcpy (mclptr->BufferPtr, "\0Regex: ", 8); strzcpy (mclptr->BufferPtr+8, sptr, mclptr->BufferSize-8); } else mclptr->RegexPregCount++; if (mclptr->RegexPregPtr == &RegexPreg) regfree (mclptr->RegexPregPtr); mclptr->RegexPregPtr = NULL; } if (WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ("regex:!&B !&Z\n", mclptr->RegexPregPtr, mclptr->BufferPtr); *text = cptr; return (BufferPtr); } /*****************************************************************************/ /* Return the result in |result| (after parsing any parameter and discarding). */ BOOL MetaConSimpleBoolean ( METACON_LINE *mclptr, char **text, int klen, BOOL result ) { char *bptr, *key; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (rqptr == NULL || (WATCHMOD (rqptr, WATCH_MOD_METACON))) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConSimpleBoolean()"); key = *text; bptr = MetaConBuffer (mclptr, text, klen); if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !#AZ:!AZ\n", result, klen, key, bptr); return (result); } /*****************************************************************************/ /* Parse the directive parameter and return the result of a string match with the supplied |string|. */ BOOL MetaConSimpleMatch ( METACON_LINE *mclptr, char **text, int klen, char *string ) { BOOL result; char *bptr, *key; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConSimpleMatch()"); key = *text; bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); if (string) result = StringMatchAndRegex (rqptr, string, bptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); else result = false; if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !#AZ:!AZ \'!AZ\'\n", result, klen, key, bptr, string ? string : "(null)"); return (result); } /*****************************************************************************/ /* Provide the "cluster-member:" conditional evaluation. Regenerates the list of cluster members only when the cluster member count changes. I guess it's not impossible that one can leave and another join disabling this approach but that's just tough, I'm not adding the overhead of a complete round of sys$getsyiw()s to each usage. */ BOOL MetaConEvaluateClusterMember ( METACON_LINE *mclptr, char **text, int klen ) { # define SYI_NODENAME_SIZE 15 struct ClusterNodeStruct { char Name [SYI_NODENAME_SIZE+1]; }; static ushort ClusterNodesCount, Length, SyiClusterNodes; static struct ClusterNodeStruct *ClusterNodesPtr; static char SyiNodeName [16]; static VMS_ITEM_LIST3 SyiClusterNodesItem [] = { { sizeof(SyiClusterNodes), SYI$_CLUSTER_NODES, &SyiClusterNodes, 0 }, { 0,0,0,0 } }; static VMS_ITEM_LIST3 SyiNodeNameItem [] = { { SYI_NODENAME_SIZE, SYI$_NODENAME, NULL, &Length }, { 0,0,0,0 } }; BOOL Result; int idx, status; ulong CsidAdr; char *bptr, *cptr, *sptr, *zptr; IO_SB IOsb; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (rqptr == NULL || (WATCHMOD (rqptr, WATCH_MOD_METACON))) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConEvaluateClusterMember()"); bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); status = sys$getsyiw (EfnWait, 0, 0, &SyiClusterNodesItem, &IOsb, 0, 0); if (VMSok (status)) status = IOsb.Status; if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (false); } #ifdef WATCH_MOD if (!SyiClusterNodes) if (cptr = SysTrnLnm ("WASD_CLUSTER_NODES")) SyiClusterNodes = atoi(cptr); #endif if (SyiClusterNodes != ClusterNodesCount) { if (ClusterNodesPtr) VmFree (ClusterNodesPtr, FI_LI); ClusterNodesPtr = VmGet (sizeof(struct ClusterNodeStruct) * SyiClusterNodes); ClusterNodesCount = idx = 0; CsidAdr = -1; for (idx = 0; idx < SyiClusterNodes; idx++) { SyiNodeNameItem[0].buf_addr = ClusterNodesPtr[idx].Name; status = sys$getsyiw (EfnWait, &CsidAdr, 0, &SyiNodeNameItem, &IOsb, 0, 0); if (VMSok (status)) status = IOsb.Status; if (status == SS$_NOMORENODE) break; if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (false); } } ClusterNodesCount = idx; } Result = false; for (idx = 0; idx < ClusterNodesCount; idx++) { if (StringMatchAndRegex (rqptr, ClusterNodesPtr[idx].Name, bptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL)) { if (!mclptr->WatchThisOne) return (true); Result = true; break; } } if (!mclptr->WatchThisOne) return (Result); WatchDataFormatted ("!&B cluster-member:!AZ", Result, bptr); for (idx = 0; idx < ClusterNodesCount; idx++) WatchDataFormatted (" !AZ", ClusterNodesPtr[idx].Name); WatchDataFormatted ("\n"); return (Result); } /*****************************************************************************/ /* Provide the "time:" conditional evaluation. See description in module prologue. */ BOOL MetaConEvaluateTime ( METACON_LINE *mclptr, char **text, int klen ) { static char TimeString [24]; static $DESCRIPTOR (TimeStringDsc, TimeString); static $DESCRIPTOR (TimeFaoDsc, "!4UL-!2ZL-!2ZL !2ZL:!2ZL:!2ZL.!2ZL\0"); BOOL Result; int CurrentMinute, DayOfWeek, EndMinute, StartMinute; char *bptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (rqptr == NULL || (WATCHMOD (rqptr, WATCH_MOD_METACON))) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConEvaluateTime()"); bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); if (isdigit(bptr[0]) && !bptr[1]) { /* day of week, 1 == Monday, 2 == Tuesday .. 7 == Sunday */ DayOfWeek = bptr[0] - '0'; Result = (DayOfWeek == HttpdDayOfWeek); if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !UL == !UL\n", Result, DayOfWeek, HttpdDayOfWeek); return (Result); } if (isdigit(bptr[0]) && isdigit(bptr[1]) && isdigit(bptr[2]) && isdigit(bptr[3]) && bptr[4] == '-' && isdigit(bptr[5]) && isdigit(bptr[6]) && isdigit(bptr[7]) && isdigit(bptr[8])) { /* time range */ Result = true; StartMinute = (((bptr[0]-'0')*10) + (bptr[1]-'0')) * 60; if (StartMinute > 1380) Result = false; StartMinute += ((bptr[2]-'0')*10) + (bptr[3]-'0'); if (StartMinute >= 1440) Result = false; EndMinute = (((bptr[5]-'0')*10) + (bptr[6]-'0')) * 60; if (EndMinute > 1380) Result = false; EndMinute += ((bptr[7]-'0')*10) + (bptr[8]-'0'); if (EndMinute >= 1440) Result = false; if (Result) { CurrentMinute = HttpdNumTime[3] * 60 + HttpdNumTime[4]; if (StartMinute <= CurrentMinute && CurrentMinute <= EndMinute) Result = true; else Result = false; if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !UL <= !UL <= !UL\n", Result, StartMinute, CurrentMinute, EndMinute); return (Result); } if (mclptr->WatchThisOne) WatchDataFormatted ("FALSE times?\n"); return (Result); } /* comparison time string */ sys$fao (&TimeFaoDsc, NULL, &TimeStringDsc, HttpdNumTime[0], HttpdNumTime[1], HttpdNumTime[2], HttpdNumTime[3], HttpdNumTime[4], HttpdNumTime[5], HttpdNumTime[6]); Result = StringMatchAndRegex (rqptr, TimeString, bptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !AZ \'!AZ\'\n", Result, TimeString, bptr); return (Result); } /*****************************************************************************/ /* Provide the "trnlnm:" conditional evaluation. See description in module prologue. */ BOOL MetaConEvaluateTrnLnm ( METACON_LINE *mclptr, char **text, int klen ) { static long LnmCaseBlind = LNM$M_CASE_BLIND; static ushort Length; static $DESCRIPTOR (NameDsc, ""); static $DESCRIPTOR (LnmTableDsc, ""); static VMS_ITEM_LIST3 LnmItem [] = { { 255, LNM$_STRING, NULL, &Length }, { 0,0,0,0 } }; BOOL Result; int status; char *bptr, *cptr; char LnmValue [256]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (rqptr == NULL || (WATCHMOD (rqptr, WATCH_MOD_METACON))) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConEvaluateTrnLnm()"); bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); /* syntax is 'trnlnm:name[;table][:value]' */ for (cptr = bptr; *cptr && *cptr != ';' && *cptr != ':'; cptr++); if (*cptr == ';') { /* a logical name table has been specified */ *cptr++ = '\0'; LnmTableDsc.dsc$a_pointer = cptr; while (*cptr && *cptr != ':') cptr++; LnmTableDsc.dsc$w_length = cptr - LnmTableDsc.dsc$a_pointer; if (*cptr == ':') *cptr++ = '\0'; } else { if (*cptr == ':') *cptr++ = '\0'; LnmTableDsc.dsc$a_pointer = "LNM$FILE_DEV"; LnmTableDsc.dsc$w_length = 12; } if (!bptr[0]) return (false); NameDsc.dsc$a_pointer = bptr; NameDsc.dsc$w_length = strlen(bptr); LnmItem[0].buf_addr = LnmValue; status = sys$trnlnm (&LnmCaseBlind, &LnmTableDsc, &NameDsc, 0, &LnmItem); if (WATCH_MODULE(WATCH_MOD_METACON)) WatchDataFormatted ("!&S !&Z !&Z {!UL}!-!#AZ !&Z\n", status, bptr, LnmTableDsc.dsc$a_pointer, Length, LnmValue, cptr); if (VMSok (status)) { LnmValue[Length] = '\0'; if (*cptr) Result = StringMatchAndRegex (rqptr, LnmValue, cptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); else Result = true; if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !AZ !AZ \'!AZ\' \'!AZ\'\n", Result, NameDsc.dsc$a_pointer, LnmTableDsc.dsc$a_pointer, LnmValue, cptr); return (Result); } if (mclptr->WatchThisOne) WatchDataFormatted ("FALSE !AZ !AZ %!&M\n", NameDsc.dsc$a_pointer, LnmTableDsc.dsc$a_pointer, status); if (status == SS$_NOLOGNAM || status == SS$_IVLOGTAB) return (false); ErrorNoticed (rqptr, status, NULL, FI_LI); return (false); } /*****************************************************************************/ /* This function is called with 'String' containing either a comma-separated list of node names (that are expected to have instances of WASD instantiated) or a single node name. The first case is used to set up the round-robin selection (i.e. select the next node name to apply the second case to), while that second case is used to apply a particular rule against. See description of the functioning of the (round-)robin: rule in the module description. */ MetaConEvaluateRoundRobin ( METACON_LINE *mclptr, char **text, int klen ) { static char NodeName [32]; BOOL Result; int cnt, NodeCount, SearchCount; char *bptr, *cptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (rqptr == NULL || (WATCHMOD (rqptr, WATCH_MOD_METACON))) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConEvaluateRoundRobin() !UL", mclptr->RoundRobinCount+1); bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); NodeCount = 1; for (cptr = bptr; *cptr; cptr++) if (*cptr == ',') NodeCount++; /* if it's a comma-separated list */ if (NodeCount > 1) { /* then it's setting-up the round-robin conditional */ MetaConInstanceList (); /* search for each of the nodes of the conditional in the list */ for (SearchCount = NodeCount; SearchCount; SearchCount--) { /* modulas the round-robin count by the node count */ cnt = mclptr->RoundRobinCount++ % NodeCount; /* find a particular node name in the cluster instance list */ for (cptr = bptr; *cptr && cnt; cptr++) if (*cptr == ',') cnt--; if (*cptr == ',') cptr++; zptr = (sptr = NodeName) + sizeof(NodeName); while (*cptr && *cptr != ',' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* check if the above name is found in the current instance list */ sptr = MetaConInstanceListPtr; while (*sptr) { cptr = NodeName; while (*cptr && *sptr && *sptr != ',' && TOLO(*cptr) == TOLO(*sptr)) { cptr++; sptr++; } if (!*cptr && (!*sptr || *sptr == ',')) break; while (*sptr && *sptr != ',') sptr++; if (*sptr) sptr++; } /* if it was found in the instance list */ if (!*cptr && (!*sptr || *sptr == ',')) break; } if (SearchCount) Result = true; else { Result = false; NodeName[0] = '?'; NodeName[1] = '\0'; } if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !AZ from !AZ in instances !AZ\n", Result, NodeName, bptr, MetaConInstanceListPtr); } else { /* it's applying the round-robin conditional */ Result = strsame (NodeName, bptr, -1); if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !AZ in instances !AZ\n", Result, NodeName, bptr); } return (Result); } /*****************************************************************************/ /* This function is called with 'String' containing either a comma-separated list of node names (that are expected to have instances of WASD instantiated) or a single node name. The first case is used to set up the round-robin selection (i.e. select the next node name to apply the second case to), while that second case is used to apply a particular rule against. See description of the functioning of the instance: rule in the module description. */ MetaConEvaluateInstance ( METACON_LINE *mclptr, char **text, int klen ) { BOOL Result; int NodeCount; char *aptr, *bptr, *cptr, *sptr, *zptr; char NodeName [32]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (mclptr->WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConEvaluateInstance()"); bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); NodeCount = 1; for (cptr = bptr; *cptr; cptr++) if (*cptr == ',') NodeCount++; /* if it's a comma-separated list */ if (NodeCount > 1) { /* then it's setting-up the instance conditional */ MetaConInstanceList (); /* search for each of the nodes of the conditional in the list */ aptr = bptr; while (*aptr) { zptr = (sptr = NodeName) + sizeof(NodeName); while (*aptr && *aptr != ',' && sptr < zptr) *sptr++ = *aptr++; *sptr = '\0'; while (*aptr == ',') aptr++; /* check if the above name is found in the current instance list */ sptr = MetaConInstanceListPtr; while (*sptr) { cptr = NodeName; while (*cptr && *sptr && *sptr != ',' && TOLO(*cptr) == TOLO(*sptr)) { cptr++; sptr++; } if (!*cptr && (!*sptr || *sptr == ',')) break; while (*sptr && *sptr != ',') sptr++; if (*sptr) sptr++; } if (!*cptr && (!*sptr || *sptr == ',')) break; } /* if at least one of the specified nodenames is in the instance list */ if (!*cptr && (!*sptr || *sptr == ',')) Result = true; else Result = false; if (mclptr->WatchThisOne) WatchDataFormatted ("!&B at least one of !AZ in instances !AZ\n", Result, bptr, MetaConInstanceListPtr); } else { /* find the specified node name in the cluster instance list */ sptr = MetaConInstanceListPtr; while (*sptr) { cptr = bptr; while (*cptr && *sptr && *sptr != ',' && TOLO(*cptr) == TOLO(*sptr)) { cptr++; sptr++; } if (!*cptr && (!*sptr || *sptr == ',')) break; while (*sptr && *sptr != ',') sptr++; if (*sptr) sptr++; } /* if it was found in the instance list */ if (!*cptr && (!*sptr || *sptr == ',')) Result = true; else Result = false; if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !AZ in instances !AZ\n", Result, bptr, MetaConInstanceListPtr); } return (Result); } /*****************************************************************************/ /* Generate a global pointer to a comma-separated list of the cluster node names currently executing instances of WASD. If a node has joined the instance group (i.e. potentially more nodes) or the number of members of that group has changed (i.e. potenitally fewer nodes) then regenerate the list. */ MetaConInstanceList () { static int NodeJoiningCount; int InstanceCount; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConInstanceList() !UL !AZ", MetaConInstanceListCount, MetaConInstanceListPtr); if (InstanceNodeJoiningCount == NodeJoiningCount) InstanceCount = InstanceLockList (INSTANCE_CLUSTER, NULL, NULL); else { /* cluster instance composition may have changed */ NodeJoiningCount = InstanceNodeJoiningCount; InstanceCount = -1; } /* if no changes just continue using the current pointer */ if (InstanceCount == MetaConInstanceListCount) return; /* changed since last time, regenerate the list */ if (MetaConInstanceListPtr) free (MetaConInstanceListPtr); MetaConInstanceListCount = InstanceLockList (INSTANCE_CLUSTER, ",", &MetaConInstanceListPtr); /* turn "NODE1::WASD:80,NODE2::WASD:80" into "NODE1,NODE2" */ cptr = sptr = MetaConInstanceListPtr; while (*cptr) { if (*cptr != ':') { *sptr++ = *cptr++; continue; } while (*cptr && *cptr != ',') cptr++; } *sptr = '\0'; if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "!UL !AZ", MetaConInstanceListCount, MetaConInstanceListPtr); } /*****************************************************************************/ /* Try to establish how many other requests the client represented by 'rqptr' has being processed by the server. It does this by scanning the list of requests, initially looking for requests with the same client IP address. When it finds one it then checks if both the candidate and list requests are being handled by a proxy server (or reasonable indication thereof). If it is it compares the proxy data (if any, if not then too bad for the proxy) and it's the same then this is counted as a concurrent request. Request structures in a keep-alive state (i.e. not actually currently having a request processed are not counted. Note that it will always find at least one matching request in the list - its own, and so the concurrent value returned will always be at least one! This mechanism breaks down a little with multiple instances so we're going to try a guesstimate. This facility is not intended for fine-grained control anyway, just to stop one client hogging a sizable proportion of the resources. This function needs to be called with 'rqptr' NULL to reset the client with each metacon rule parse reset. */ BOOL MetaconClientConcurrent ( METACON_LINE *mclptr, char **text, int klen ) { static BOOL ConcurrentCurrent; static int ClientConcurrent; BOOL result; char *bptr; IPADDRESS IpAddress; LIST_ENTRY *leptr; REQUEST_STRUCT *rqptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (mclptr == NULL) { /* reset */ ConcurrentCurrent = false; ClientConcurrent = 0; return (false); } rqptr = mclptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "MetaconClientConcurrent()"); bptr = MetaConBuffer (mclptr, text, klen); if (rqptr == NULL) return (false); if (ConcurrentCurrent) { result = (ClientConcurrent > atoi(mclptr->BufferPtr)); if (mclptr->WatchThisOne) WatchDataFormatted ("!&B client_connect_gt:!AZ !UL\n", result, bptr, ClientConcurrent); return (result); } ConcurrentCurrent = true; memcpy (&IpAddress, &rqptr->ClientPtr->IpAddress, sizeof(IpAddress)); for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; /* if most probably a persistent connection waiting */ if (QUAD_ZERO(rqeptr->BytesRx) && rqeptr->rqNet.PersistentCount) continue; if (!IPADDRESS_IS_SAME (&IpAddress, &rqeptr->ClientPtr->IpAddress)) continue; if (rqptr->rqHeader.XForwardedForPtr && rqeptr->rqHeader.XForwardedForPtr) { /* if the "X-Forwarded-For:" fields are not the same then next */ if (strcmp (rqptr->rqHeader.XForwardedForPtr, rqeptr->rqHeader.XForwardedForPtr)) continue; } else if (rqptr->rqHeader.ForwardedPtr && rqeptr->rqHeader.ForwardedPtr) { /* if the "Forwarded:" fields are not the same then next */ if (strcmp (rqptr->rqHeader.ForwardedPtr, rqeptr->rqHeader.ForwardedPtr)) continue; } ClientConcurrent++; } if (InstanceNodeCurrent > 1) { /* if three or more on this instance it's likely the same on others */ if (ClientConcurrent > 2) ClientConcurrent *= InstanceNodeCurrent; } result = (ClientConcurrent > atoi(mclptr->BufferPtr)); if (mclptr->WatchThisOne) WatchDataFormatted ("!&B client_connect_gt:!AZ !UL\n", result, bptr, ClientConcurrent); return (result); } /*****************************************************************************/ /* Compare single "??:" conditional string to each of a comma-separated list in the form "item" or "item1, item2, item3", etc. String may contain '*' and '%' wildcards. We can munge the list string like this because it's in a scratch buffer. */ BOOL MetaConConditionalList ( METACON_LINE *mclptr, char **text, int klen, char *string ) { BOOL result; char ch; char *bptr, *cptr, *key, *sptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mclptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConConditionalList() !&Z !&Z", *text, string); if (string == NULL) return (false); result = false; key = *text; bptr = MetaConBuffer (mclptr, text, klen); if (*(cptr = string)) { while (*cptr) { /* spaces and commas are element separators, step over */ while (ISLWS(*cptr) || *cptr == ',') cptr++; /* if end of list (or empty) then finished */ if (!*cptr) break; /* find end of this element, save next character, terminate element */ for (sptr = cptr; *sptr && !ISLWS(*cptr) && *sptr != ','; sptr++); ch = *sptr; *sptr = '\0'; /* look for what we want */ result = StringMatchAndRegex (rqptr, bptr, cptr, SMATCH_GREEDY_REGEX, mclptr->RegexPregPtr, NULL); if (result) break; if (*(cptr = sptr) = ch) cptr++; } } if (mclptr->WatchThisOne) WatchDataFormatted ("!&B !#AZ:!AZ \'!AZ\'\n", result, klen, key, bptr, string); return (result); } /*****************************************************************************/ /* Process a meta-config key in the request dictionary. Strings have already been processed to remove enclosing quotes and to escape characters. Prefix the key with an exclamation point to remove an entry. Used by both the DICT meta directive and the mapping SET DICT=.. directive. */ void MetaConDictionary ( REQUEST_STRUCT *rqptr, char *string ) { BOOL remove; int klen, vlen; char *cptr, *kptr, *type, *vptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConDictionary() !&Z", string); /* use substitution only when indicated */ for (cptr = string; *cptr; cptr++) if (MATCH2(cptr, "\'\'")) break; if (*cptr) string = MetaConSubstitute (rqptr, string); for (kptr = cptr = string; *cptr && *cptr != '='; cptr++); klen = cptr - kptr; if (*cptr) cptr++; for (vptr = cptr; *cptr; cptr++); vlen = cptr - vptr; if (remove = (kptr[0] == '!')) { /* remove entry */ kptr++; klen--; } if (remove && !klen && !vlen) { /* remove all config (DICT and SET DICT..) entries */ DictIterate (rqptr->rqDictPtr, NULL); while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_CONFIG)) DictRemoveEntry (denptr); } else { if (isalnum(kptr[0])) type = DICT_TYPE_CONFIG; else { /* control exactly what other types of entry can be managed */ if (kptr[0] == DICT_TYPE_CONFIG[0]) type = DICT_TYPE_CONFIG; else if (kptr[0] == DICT_TYPE_INTERNAL[0]) type = DICT_TYPE_INTERNAL; else if (kptr[0] == DICT_TYPE_REQUEST[0]) type = DICT_TYPE_REQUEST; else if (kptr[0] == DICT_TYPE_RESPONSE[0]) type = DICT_TYPE_RESPONSE; else { if (WATCHING (rqptr, WATCH_INTERNAL)) WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "DICT key \\!#AZ\\ type \\!&C\\ not permitted", klen, kptr, kptr[0]); return; } kptr++; klen--; } if (remove) DictRemove (rqptr->rqDictPtr, type, kptr, klen); else DictInsert (rqptr->rqDictPtr, type, kptr, klen, vptr, vlen); } } /*****************************************************************************/ /* Maintaining its own dynamic buffer, parse the supplied string looking for ''..' substitution escape sequence and replace the ''' with the contents of that key. If the key word is unknown the "'''" just becomes a literal. */ char* MetaConSubstitute ( REQUEST_STRUCT *rqptr, char *string ) { static int BufferSize; static char *BufferPtr; char ch; char *aptr, *cptr, *sptr, *tptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConSubstitute() !AZ", string); zptr = (sptr = BufferPtr) + BufferSize; for (;;) { if (sptr >= zptr) { BufferSize = BufferSize ? BufferSize + BufferSize : 512; BufferPtr = VmRealloc (BufferPtr, BufferSize, FI_LI); zptr = (sptr = BufferPtr) + BufferSize; } zptr = (sptr = BufferPtr) + BufferSize; for (cptr = string; *cptr && sptr < zptr;) { if (MATCH2(cptr, "\'\'")) { tptr = cptr; for (aptr = (cptr += 2); *cptr && *cptr != '\''; cptr++); if (*cptr == '\'') { /* search for the keyword and copy in if found */ if (aptr = MetaConDatum (rqptr, aptr, cptr - aptr)) { while (*aptr && sptr < zptr) *sptr++ = *aptr++; cptr++; } else { /* key not found - copy as-is */ while (*tptr && tptr <= cptr && sptr < zptr) *sptr++ = *tptr++; cptr++; } } else /* key not finalised with a single quote - copy as-is */ while (*tptr && sptr < zptr) *sptr++ = *tptr++; } else *sptr++ = *cptr++; } /* if the buffer overflowed then expand and do it all again */ if (sptr >= zptr) continue; *sptr = '\0'; break; } return (BufferPtr); } /*****************************************************************************/ /* Look up the substitution key and return a pointer to the value string. Return NULL if the key word is unknown. If the key begins with an alpha-numeric it is looked for in the meta entries, then the request field entries, then the internal entries. If it begins with a non-alpha-numeric then it is understood to be an entry type character and the look-up is specific. */ void* MetaConDatum ( REQUEST_STRUCT *rqptr, char *key, int klen ) { char *aptr, *cptr, *sptr, *zptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_METACON)) WatchThis (WATCHITM(rqptr), WATCH_MOD_METACON, "MetaConDatum()"); if (!isalnum(key[0])) { /* leading character must be an entry type character (e.g. '$', '~') */ denptr = DictLookup (rqptr->rqDictPtr, key, key+1, klen-1); if (denptr != NULL) return ((void*)DICT_GET_VALUE(denptr)); return (NULL); } /* configuration set meta data */ denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_CONFIG, key, klen); if (denptr != NULL) return ((void*)DICT_GET_VALUE(denptr)); /* request header fields */ denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST, key, klen); if (denptr != NULL) return ((void*)DICT_GET_VALUE(denptr)); /* internal dictionary data */ denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, key, klen); if (denptr != NULL) return ((void*)DICT_GET_VALUE(denptr)); return (NULL); } /*****************************************************************************/ /* Manage the notepad dictionary entry. Notepad is somewhat obsolescent with the arrival of dictionary meta-config (v11.0) but obviously kept for backward compatibility. */ void MetaConNotePad ( REQUEST_STRUCT *rqptr, char *string, BOOL WatchThisOne ) { int length; char *bptr, *cptr, *sptr, *zptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConNotePad() !&Z", string); if (string == NULL || !string[0]) { /* reset the notepad */ if (rqptr->NotePadPtr != NULL) DictRemove (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "notepad", 7); rqptr->NotePadPtr = NULL; rqptr->NotePadDictEntry = NULL; return; } for (cptr = string; *cptr; cptr++); length = cptr - string; if ((denptr = rqptr->NotePadDictEntry) == NULL) { /* two times whatever asked for with a minimum of 256 bytes */ if (length < 128) length = 128; /* create a dictionary entry reserving space */ denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "notepad", 7, NULL, length * 2); rqptr->NotePadDictEntry = denptr; rqptr->NotePadPtr = DICT_GET_VALUE(denptr); } else if (DICT_GET_VALUE_LEN(denptr) + length > DICT_GET_BYTES(denptr)) { /* the difference times two */ denptr = DictExpandEntry (denptr, (length - DICT_GET_BYTES(denptr)) * 2); rqptr->NotePadDictEntry = denptr; rqptr->NotePadPtr = DICT_GET_VALUE(denptr); } bptr = DICT_GET_VALUE(denptr); zptr = (sptr = bptr) + DICT_GET_BYTES(denptr); if (string[0] == '+') { string++; sptr += DICT_GET_VALUE_LEN(denptr); } for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; DictValueLength (denptr, sptr - bptr); } /*****************************************************************************/ /* Return true or false. */ BOOL MetaConCommonRequestHeader (char *key) { static char *CommonRequestHeaders [] = { "accept", "accept-charset", "accept-encoding", "accept-language", "accept-datetime", "authorization", "cache-control", "connection", "cookie", "cookie-length", "content-md5", "content-type", "date", "dnt", "expect", "from", "host", "if-match", "if-modifid-since", "if-none-match", "if-range", "if-unmodifid-since", "max-forwards", "origin", "pragma", "proxy-authorization", "range", "referer", "te", "user-agent", "upgrade", "via", "warning", NULL }; int idx; char *cptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConCommonRequestHeader() !&Z", key); for (idx = 0; cptr = CommonRequestHeaders[idx]; idx++) if (MATCH3(cptr,key) && strsame (cptr, key, -1)) return (true); return (false); } /*****************************************************************************/ /* Put a string into the "note:" rule buffer. Called from ControlHttpdAst(). */ MetaConNoteThis (char *NotePtr) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConNoteThis() !&Z", NotePtr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); strzcpy (HttpdGblSecPtr->MetaConNote, NotePtr, sizeof(HttpdGblSecPtr->MetaConNote)); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /*****************************************************************************/ /* Return SERVICE_ specific values equivalent to the string pointed to by 'pptr'. */ BOOL MetaConSetBoolean ( META_CONFIG *mcptr, char *cptr ) { static char ProblemBoolean [] = "Confusing boolean value"; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConSetBoolean() !&Z", cptr); if (strsame (cptr, "YES", 3) || strsame (cptr, "ON", 2) || strsame (cptr, "ENABLED", 7)) return (true); if (!cptr[0] || strsame (cptr, "NO", 2) || strsame (cptr, "OFF", 3) || strsame (cptr, "DISABLED", 8)) return (false); MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemBoolean); return (false); } /*****************************************************************************/ /* Return an integer equivalent to the numeric string pointed to by 'cptr'. */ int MetaConSetInteger ( META_CONFIG *mcptr, char *cptr ) { static char ProblemInteger [] = "Confusing integer value"; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConSetInteger() !&Z", cptr); if (isdigit(*cptr)) return (atoi(cptr)); if (*cptr == '-' && isdigit(*(cptr+1))) return (-atoi(cptr+1)); MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemInteger); return (-1); } /*****************************************************************************/ /* Copy a simple string configuration parameter trimming leading and trailing white-space. */ MetaConSetString ( META_CONFIG *mcptr, char *cptr, char *String, int SizeOfString ) { static char ProblemStringOverflow [] = "String value overflow"; char *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConSetString() !&Z", cptr); zptr = (sptr = String) + SizeOfString; while (*cptr && ISLWS(*cptr)) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { *String = '\0'; MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemStringOverflow); return; } *sptr = '\0'; if (sptr > String) { sptr--; while (*sptr && ISLWS(*sptr) && sptr > String) sptr--; *++sptr = '\0'; } } /*****************************************************************************/ /* Return an integer equivalent to the HH:MM:SS string. Examples; "0:35" is 35 seconds, "02:20" is 140 seconds, "1:0:0" is 3600 seconds. A day can be prefixed to the hh:mm:ss as follows; "2-00:00:00". The 'SingleIntegerSeconds' represents what a single, unqualified-by-colon integer represents in seconds (most commonly 60, i.e. one minute). This is for backward compatibility with previous configuration files that used single integers and fixed the unit they represented. If the 'seconds' are "NONE" then return -1. */ int MetaConSetSeconds ( META_CONFIG *mcptr, char *cptr, int SingleIntegerSeconds ) { static char ProblemPeriodFormat [] = "Time period format problem", ProblemPeriodValue [] = "One or more components out-of-range"; int day, hr, min, sec; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConSetSeconds() !&X !AZ !UL", mcptr, cptr, SingleIntegerSeconds); if (strsame (cptr, "NONE", -1)) return (-1); day = hr = min = sec = 0; if (isdigit(*cptr)) sec = atoi(cptr); else if (strsame (cptr, "none", 4)) return (31536000+1); /* seconds in one year */ else if (strsame (cptr, "infinite", 8)) return (31536000+1); /* seconds in one year */ else { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemPeriodFormat); /* with a period problem make it as short as practicable */ return (1); } while (*cptr && isdigit(*cptr)) cptr++; if (*cptr == '-') { day = sec; cptr++; if (isdigit(*cptr)) sec = atoi(cptr); else { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemPeriodFormat); return (-1); } while (*cptr && isdigit(*cptr)) cptr++; } if (*cptr == ':') { cptr++; min = sec; if (isdigit(*cptr)) sec = atoi(cptr); else { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemPeriodFormat); return (-1); } while (*cptr && isdigit(*cptr)) cptr++; if (*cptr == ':') { cptr++; hr = min; min = sec; if (isdigit(*cptr)) sec = atoi(cptr); else { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemPeriodFormat); return (-1); } } } else { /* single integer, what's it worth (i.e. a second, minute, hour?) */ sec *= SingleIntegerSeconds; hr = sec / 3600; sec = sec % 3600; min = sec / 60; sec = sec % 60; } if (day < 0 || day > 36500 || hr < 0 || hr > 99 || min < 0 || min > 59 || sec < 0 || sec > 59) { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemPeriodValue); return (-1); } return (day*86400+hr*3600+min*60+sec); } /*****************************************************************************/ /* Return a heap allocated string containing a string representing the HH:MM:SS represented by the specified number of seconds. */ char* MetaConShowSeconds ( REQUEST_STRUCT *rqptr, int Seconds ) { static $DESCRIPTOR (StrBufDsc, ""); static $DESCRIPTOR (HourMinSecFaoDsc, "!2ZL:!2ZL:!2ZL\0"); static $DESCRIPTOR (DayHourMinSecFaoDsc, "!UL-!2ZL:!2ZL:!2ZL\0"); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConShowSeconds() !UL", Seconds); if (Seconds < 0) return ("NONE"); StrBufDsc.dsc$w_length = 32; if (rqptr) StrBufDsc.dsc$a_pointer = VmGetHeap (rqptr, StrBufDsc.dsc$w_length); else /* this might leak a bit! */ StrBufDsc.dsc$a_pointer = VmGet (StrBufDsc.dsc$w_length); if (Seconds > 31536000) /* seconds in one year */ strcpy (StrBufDsc.dsc$a_pointer, "NONE"); else if (Seconds / 86400) /* seconds in one day */ sys$fao (&DayHourMinSecFaoDsc, 0, &StrBufDsc, Seconds / 86400, (Seconds % 86400) / 3600, (Seconds % 3600) / 60, (Seconds % 3600) % 60); else sys$fao (&HourMinSecFaoDsc, 0, &StrBufDsc, Seconds / 3600, (Seconds % 3600) / 60, (Seconds % 3600) % 60); return (StrBufDsc.dsc$a_pointer); } /*****************************************************************************/ /* Provide information about the load (often problem reports) by building up a text string over successive calls. 'FormatString' may contain WASD-FAO directives and supply appropriate parameters. It is incorporated into a meta-message :^) which includes additional detail about the source file line number and file name (if an [IncludeFile] has been encountered). */ int MetaConReport ( META_CONFIG *mcptr, int ItemSeverity, char *FormatString, ... ) { int argcnt, cnt, status; ushort Length; ulong *vecptr; ulong FaoVector [32+8]; char ch; char *cptr, *sptr, *zptr; char Buffer1 [1024], Buffer2 [1024]; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConReport() !&X !UL !UL !&Z", mcptr, ItemSeverity, argcnt, FormatString); /* may be some occasions where we might want to call this regardless */ if (!mcptr) return (SS$_BADPARAM); if (argcnt > 32) return (SS$_OVRMAXARG); switch (ItemSeverity) { case METACON_REPORT_INFORM : mcptr->LoadReport.InformCount++; ch = 'i'; break; case METACON_REPORT_WARNING : mcptr->LoadReport.WarningCount++; ch = 'w'; break; case METACON_REPORT_ERROR : mcptr->LoadReport.ErrorCount++; ch = 'e'; break; default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } mcptr->LoadReport.ItemCount++; vecptr = FaoVector; *vecptr++ = FormatString; /* now add the variable length arguments to the vector */ va_start (argptr, FormatString); for (argcnt -= 3; argcnt; argcnt--) *vecptr++ = va_arg (argptr, ulong); va_end (argptr); if (mcptr->CurrentOdsPtr) { /* while source files are being loaded */ if (mcptr->CurrentOdsPtr->DataLineNumber) { *vecptr++ = " (line !UL!&@)"; if (mcptr->ReportLineNumber) *vecptr++ = mcptr->ReportLineNumber; else *vecptr++ = mcptr->CurrentOdsPtr->DataLineNumber; if (mcptr->IncludeFile) { *vecptr++ = " of !AZ"; *vecptr++ = mcptr->CurrentOdsPtr->ExpFileName; } else *vecptr++ = ""; } else *vecptr++ = ""; if (mcptr->CurrentOdsPtr->DataLinePtr) { if (mcptr->ReportLinePtr) cptr = mcptr->ReportLinePtr; else cptr = mcptr->CurrentOdsPtr->DataLinePtr; while (*cptr && ISLWS(*cptr)) cptr++; *vecptr++ = "\\!AZ\\\n"; *vecptr++ = cptr; } else *vecptr++ = ""; } else if (mcptr->ParsePtr) { /* during the basic check parse */ if (mcptr->ParsePtr->Number) { *vecptr++ = " (directive !UL)"; if (mcptr->ReportLineNumber) *vecptr++ = mcptr->ReportLineNumber; else *vecptr++ = mcptr->ParsePtr->Number; } else *vecptr++ = ""; if (mcptr->ParsePtr->TextPtr) { if (mcptr->ReportLinePtr) cptr = mcptr->ReportLinePtr; else cptr = mcptr->ParsePtr->TextPtr; while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr) { *vecptr++ = "\\!AZ\\\n"; *vecptr++ = cptr; } else *vecptr++ = ""; } else *vecptr++ = ""; } else { if (mcptr->ReportLineNumber) { *vecptr++ = " (line !UL)"; *vecptr++ = mcptr->ReportLineNumber; } else *vecptr++ = ""; if (mcptr->ReportLinePtr) { cptr = mcptr->ReportLinePtr; while (*cptr && ISLWS(*cptr)) cptr++; *vecptr++ = "\\!AZ\\\n"; *vecptr++ = cptr; } else *vecptr++ = ""; } status = FaolToBuffer (Buffer1, sizeof(Buffer1), NULL, "!&@!&@\n!&@", &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) { ErrorNoticed (NULL, status, NULL, FI_LI); return (status); } /* put into 'Buffer2' ensuring newlines are padded from the left margin */ vecptr = FaoVector; *vecptr++ = mcptr->LoadReport.ItemCount; *vecptr++ = ch; status = FaolToBuffer (Buffer2, sizeof(Buffer2), &Length, "!UL.!&C ", &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) { ErrorNoticed (NULL, status, NULL, FI_LI); return (status); } zptr = (sptr = Buffer2) + sizeof(Buffer2); while (*sptr) sptr++; for (cptr = Buffer1; *cptr && sptr < zptr; *sptr++ = *cptr++) { if (cptr[0] != '\n' || !cptr[1]) continue; *sptr++ = *cptr++; for (cnt = Length; cnt && sptr < zptr; cnt--) *sptr++ = ' '; } if (sptr >= zptr) { ErrorNoticed (NULL, SS$_BUFFEROVF, NULL, FI_LI); return (SS$_BUFFEROVF); } *sptr = '\0'; Length = sptr - Buffer2; /* append it to the report text */ mcptr->LoadReport.TextPtr = VmRealloc (mcptr->LoadReport.TextPtr, mcptr->LoadReport.TextLength+Length+1, FI_LI); memcpy (mcptr->LoadReport.TextPtr+mcptr->LoadReport.TextLength, Buffer2, Length); mcptr->LoadReport.TextLength += Length; return (SS$_NORMAL); } /*****************************************************************************/ /* Copy a simple string configuration parameter trimming leading and trailing white-space. */ MetaConStartupReport ( META_CONFIG *mcptr, char *Facility ) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_METACON)) WatchThis (WATCHALL, WATCH_MOD_METACON, "MetaConStartupString() !&Z", Facility); if (mcptr->LoadReport.ItemCount) FaoToStdout ( "%HTTPD-W-!AZ, !UL informational, !UL warning, !UL error!%s at load\n!AZ", Facility, mcptr->LoadReport.InformCount, mcptr->LoadReport.WarningCount, mcptr->LoadReport.ErrorCount, mcptr->LoadReport.TextPtr); /* only alert via OPCOM if there were severe error(s) */ if (OpcomMessages && mcptr->LoadReport.ErrorCount) FaoToOpcom ( "%HTTPD-W-!AZ, !UL informational, !UL warning, !UL error!%s at load", Facility, mcptr->LoadReport.InformCount, mcptr->LoadReport.WarningCount, mcptr->LoadReport.ErrorCount); } /*****************************************************************************/