/*****************************************************************************/
#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<comparison-operator><major>.<minor>.<tweak>]]

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(<conditional>) interpret the rest of the line

  if(<conditional>)
     <interpret the block
     up until the corresponding>
  endif

  if(<conditional>)
     <interpret the block
     up until a corresponding>
  else
     <which provides an
     alternative block>
  endif

  if(<conditional>)
     <interpret the block
     up until a corresponding>
  elif(<conditional>)
     <providing a style of
     "case" with an optional>
  else
     <which provides
     a default block>
  endif

  if(<conditional>)
     <interpret the block>
  unif
     <interpret this block
     unconditionally>
  ifif
     <interpret this block
     if the original if was true>
  else
     <interpret this block
     if the original if was false>
  unif
     <interpret this block
     unconditionally>
  else
     <interpret this block
     if the original if was false>
  endif


META DICTIONARY
---------------
The "dict=[<key>[=<value>]]" meta configuration directive allows dictionary
entries to be managed using the same mechansim as the path mapping
"SET dict=[<key>[=<value>]]" directive (in fact path mapping SET uses the
meta-config routine).  Placement of quotes is important, across the entire
"<key>=<value>" string, or they become part of the key and/or value.  A key
value may be set using 

  dict <key-string>=<value-string>
  dict "<key-string>=<value-string>"

(to end of line in the first case and between the quotes in the second) and
removed (deleted) using

  dict <key-string>=

(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) ''<key>' 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:                <key>[=<match>]
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-other-field:>   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)
     <do this>
  elif (rand:3:1)
     <do this>
  else
     <do this>
  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)
     <it's midnight>
  elif (time:0001-1159)
     <it's AM>
  elif (time:1200-1200)
     <it's noon>
  else
     <it's PM>
  endif

  if (time:6 || time:7)
     <it's the weekend>
  else
     <it's the working week>
  endif

  if (time:%%%%-05-*)
     <it's the month May>
  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:<string>" 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:<key>[=<match>]
                  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=<string>)
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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* VMS related header files */
#include <lnmdef.h>
#include <syidef.h>

/* 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 ("<opr[!UL]=(\n", OperatorIndex);
               OperatorIndex--;
               if (*OperatorStack[OperatorIndex] == '!') continue;
            }
            break;
         }

         if (*OperatorStack[OperatorIndex] == '!')
         {
            if (ResultIndex <= 0)
            {
               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<opr[!UL]=!1AZ\n>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<val[!UL]=!&?TRUE\rFALSE\r\n\
<opr[!UL]=!1AZ\n>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<val[!UL]=!&?TRUE\rFALSE\r\n\
<opr[!UL]=!1AZ\n>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 ''<key>' with the contents
of that key.  If the key word is unknown the "''<key>'" 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);
}

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

