/*****************************************************************************/
#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 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 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.

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)
authorization:       'Authorization:'   (AUTHORIZATION)
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.)
cookie:              'Cookie:'  (HTTP_COOKIE)
decnet:              0/4/5 is none/PhaseIV/V
demo:                boolean, true if started /DEMO, false otherwise
document-root:       'Document-Root:' (DOCUMENT_ROOT)
forwarded:           'Forwarded:', proxies/gateways  (HTTP_FORWARDED)
host:                'Host:"  (HTTP_HOST)
jpi_username:        $GETJPI user name  (server process username)
mapped-path:         remainder after script name parsing, or after 'map' rule
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)
query-string:        request query string  (QUERY_STRING)
rand:                random number generator  (see note below)
redirected:          usually 0, can be 1..4 (count internally redirected)
referer:             'Referer:'  (HTTP_REFERER)
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)
request-scheme:      request scheme  (REQUEST_SCHEME)
restart:             number of times rule processing restarted (0 is default)
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-software:     server identification string  (SERVER_SOFTWARE)
service:             essentially server-name plus server-port as one string
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)
user-agent:          'User-Agent:'  (USER_AGENT)
x-forwarded-for:     "X-Forwarded-For:" proxies/gateways (HTTP_X_FORWARDED_FOR)
[[service]]          virtual service (or [[scheme://service:port]])


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 HTTPD$MAP

  if (client_connect_gt:10) set * throttle=10

  if (client_connect_gt:10) \
     pass * "503 Your client is exceeding it's 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


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
---------------
02-OCT-2004  MGD  bugfix; MetaconClientConcurrent() if IP address not the same!
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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#undef __VMS_VER
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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;

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

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  CliDemo;

extern int  EfnWait,
            HttpdDayOfWeek,
            InstanceNodeCurrent,
            OpcomMessages;

extern unsigned short  HttpdNumTime[];

extern unsigned long  HttpdBinTime[],
                      SysPrvMask[];

extern char  CommandLine[],
             ErrorSanityCheck[],
             SoftwareId[],
             TcpIpAgentInfo[],
             Utility[];

extern LIST_HEAD  RequestList;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
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
)
{
#if WATCH_MOD
   /* for testing purposes only */
   BOOL  TestToken = false;
#endif

   BOOL  ok,
         WatchThisOne;
   int  status,
        ByteCount,
        FlowControlLevel,
        MetaFileLevel,
        ParseNumber,
        TotalLineCount;
   int  MetaFileType [METACON_FILE_DEPTH_MAX+1];
   char  *cptr, *sptr, *tptr, *zptr,
         *BackslashMeans,
         *InlinePtr;
   char  ConfigDirectory [METACON_CONFIG_DIR_LENGTH+1],
         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 (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConLoad() !&Z !&A !&B !&B",
                 FileName, CallBackFunction,
                 ContinueLines, ReportVirtualService);

   if (ContinueLines)
      BackslashMeans = '\\';
   else
      BackslashMeans = '\n';

   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);

   /* use SYSPRV to allow access to possibly protected files */
   sys$setprv (1, &SysPrvMask, 0, 0);

   MetaFileLevel = TotalLineCount = 0;
   ConfigDirectory[0] = MetaFileName[0] = '\0';
   odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[MetaFileLevel];

   status = OdsLoadTextFile (odsptr, FileName);
   if (VMSnok (status))
   {
      sys$setprv (0, &SysPrvMask, 0, 0);
      MetaConReport (mcptr, METACON_REPORT_ERROR,
                     "Error opening !AZ, !&m", FileName, 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 (!strlen(cptr)) continue;
      /* if a comment line */
      if (*cptr == '#' || *cptr == '!') continue;
      ByteCount += sizeof(METACON_LINE) + strlen(cptr)+1;

      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];
      }
   }

   /* terminating empty (zero-length) line */
   ByteCount += sizeof(METACON_LINE);

   /************/
   /* populate */
   /************/

   /* dispose of the previously allocated structure */
   MetaConUnload (&mcptr, NULL);

   mcptr = (META_CONFIG*)VmGet(ByteCount);
   *MetaConPtrPtr = mcptr;

   if (WATCH_MOD && WatchThisOne)
      WatchThis (NULL, FI_LI, WATCH_MOD_METACON, "!&X", mcptr);

   mcptr->ThisSize = ByteCount;
   mcptr->LineCount = TotalLineCount;
   mcptr->ContentPtr = mcptr->ParsePtr = mclptr = &mcptr->Lines;

   /* 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.LoadBinTime);
   strcpy (mcptr->LoadReport.FileName, odsptr->ResFileName);
   memcpy (&mcptr->LoadReport.FileBinTime, &odsptr->XabDat.xab$q_rdt, 8);

   MetaFileLevel = FlowControlLevel = ParseNumber = 0;
   ConfigDirectory[0] = MetaFileName[0] = '\0';
   MetaFileType[MetaFileLevel] = METACON_TYPE_FILE;
   odsptr = mcptr->CurrentOdsPtr = &ConfigFileOds[MetaFileLevel];

   status = OdsLoadTextFile (odsptr, FileName);
   if (VMSnok (status))
   {
      sys$setprv (0, &SysPrvMask, 0, 0);
      MetaConReport (mcptr, METACON_REPORT_ERROR,
                     "Error opening !AZ, !&m", FileName, status);
      return (SS$_ABORT);
   }

   /* 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;
      }

      /* point to the string storage area of that "line" */
      sptr = mclptr->TextPtr = (char*)&mclptr->Storage;
      /* skip over any text leading white-space */
      while (*cptr && ISLWS(*cptr)) cptr++;
      /* if a blank line */
      if (!strlen(cptr)) continue;
      /* if a comment line */
      if (*cptr == '#' || *cptr == '!') continue;
      /* copy the remainder of the line */
      for (zptr = cptr; *zptr; *sptr++ = *zptr++);
      *sptr++ = '\0';

      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 start of the next "line" storage area */
      NextLinePtr = (METACON_LINE*)sptr;

      mclptr->Number = ++ParseNumber;

      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 (NULL, mclptr, WatchThisOne);
         if (*InlinePtr || *(unsigned long*)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 (NULL, mclptr, WatchThisOne);
         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 (NULL, mclptr, WatchThisOne);
         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 (*(short*)cptr == '[[')
      {
         /*******************/
         /* virtual-service */
         /*******************/

         mclptr->Token = METACON_TOKEN_SERVICE;
         mclptr->FlowControlLevel = FlowControlLevel = 0;
         /* eliminate the leading "[[" and trailing "]]" */
         sptr = mclptr->TextPtr;
         cptr = mclptr->TextPtr + 2;
         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 (strsame (cptr, "http://", 7))
               cptr += 7;
            else
            if (strsame (cptr, "https://", 8))
               cptr += 8;
            if (*cptr != '*:*\0' && !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 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 = &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
      {
         /*********************/
         /* just another line */
         /*********************/

         mclptr->Token = METACON_TOKEN_TEXT;
         mclptr->FlowControlLevel = FlowControlLevel;
         mclptr->MetaFileLevel = MetaFileLevel;
         mclptr->MetaFileType = MetaFileType[MetaFileLevel];
      }

      /****************/
      /* 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);

#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 (NULL, FI_LI, 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 (NULL, FI_LI, 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);
   }
   mcptr->ParsePtr = mcptr->ParseNextPtr = mcptr->ContentPtr;
} 

/*****************************************************************************/
/*
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 (NULL, FI_LI, 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) 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  NotThisVirtualService,
         UnresolvedFlowControl;
   int  LineSize,
        LineToken;
   char  *cptr;
   METACON_LINE  *mclptr;

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

   if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON))
      WatchThis (rqptr, FI_LI, WATCH_MOD_METACON,
                 "MetaConParse() !&X !&X", mcptr, LinePtrPtr);

   NotThisVirtualService = false;

   for (;;)
   {
      mclptr = mcptr->ParsePtr = mcptr->ParseNextPtr;

      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_SERVICE)
      {
         if (WatchThisOne)
            WatchDataFormatted ("!3ZL [[!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 2");

         /* 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 (cptr[4] == ':' && strsame (cptr, "http://", 7))
         {
            if (rqptr->ServicePtr->RequestScheme != SCHEME_HTTP)
            {
               NotThisVirtualService = true;
               continue;
            }
            cptr += 7;
         }
         else
         if (cptr[5] == ':' && strsame (cptr, "https://", 8))
         {
            if (rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
            {
               NotThisVirtualService = true;
               continue;
            }
            cptr += 8;
         }

         /* if for all virtual services */
         if (*(unsigned long*)cptr == '*:*\0') continue;

         /* if conditional matches this request's virtual service */
         NotThisVirtualService =
            !StringMatch (rqptr, rqptr->ServicePtr->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!3ZL !AZ\n",
               mclptr->ConfigProblem ? "?" : "",
               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 ("!3ZL !AZ\n",
                     mclptr->Number, mclptr->TextPtr);
            return (mclptr->TextPtr);
         }

         if (WatchThisOne)
            WatchDataFormatted ("!3ZL !AZ\n",
               mclptr->Number, mclptr->TextPtr);
         /* return if just checking */
         if (!rqptr) return ("\0");
         continue;
      }

      if (WatchThisOne)
         WatchDataFormatted ("!3ZL !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 (rqptr, mclptr, WatchThisOne);
            if (*cptr || *(unsigned long*)cptr == '\0\0\0\1')
            {
               /* expression with inline string (true or false) */
               mcptr->ParseIndex--;
               return (cptr);
            }
            /* set processing-now if expression is true */
            if (*(unsigned long*)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 (rqptr, mclptr, WatchThisOne);
               if (*cptr)
               {
                  /* an inline directive following the expression */
                  return ("\0Extraneous text follows \'elif()\'");
               }
               /* set processing-now if sentinal string is true */
               if (*(unsigned long*)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 (rqptr, mclptr, WatchThisOne);
         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
(
REQUEST_STRUCT *rqptr,
METACON_LINE *mclptr,
int WatchThisOne
)                                               
{
#define TOUP toupper

   static char  ReturnScratch [128];

   BOOL  Result;
   BOOL  ResultStack [METACON_STACK_MAX];
   int  idx, modulas, number,
        CurrentNumber,
        OperatorIndex,
        RandomNumber,
        ResultIndex;
   char  ch1, ch2;
   char  *cptr, *sptr, *zptr;
   char  *OperatorStack [METACON_STACK_MAX];
   char  Scratch [1024];

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

   if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_METACON, "MetaConEvaluate()");
      WatchDataFormatted ("!&Z\n", mclptr->TextPtr);
   }

   RandomNumber = rand();

   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 */
      *(unsigned long*)Scratch = '\0\0\0\0';
      mclptr->BufferPtr = Scratch;
      mclptr->SizeOfBuffer = sizeof(Scratch);
      mclptr->RegexPregCount = 0;
      Result = false;

      if (ch1 == ')')
         cptr++;
      else
      if (TOUP(cptr[0]) == 'A')
      {
         if (cptr[6] == ':')
         {
            /***********/
            /* Accept: */
            /***********/
                               
            cptr += 7;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (rqptr->rqHeader.AcceptPtr)
            {
               Result = MetaConConditionalList (rqptr, Scratch,
                                                mclptr->RegexPregPtr,
                                                rqptr->rqHeader.AcceptPtr);
               if (WatchThisOne)
                  WatchDataFormatted ("!&B accept:!AZ \'!AZ\'\n",
                     Result, Scratch, rqptr->rqHeader.AcceptPtr);
            }
         }
         else
         if (TOUP(cptr[7]) == 'L' && cptr[15] == ':')
         {
            /**********************/
            /* "Accept-Language:" */
            /**********************/

            cptr += 16;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (rqptr->rqHeader.AcceptLangPtr)
            {
               Result = MetaConConditionalList (rqptr, Scratch,
                                                mclptr->RegexPregPtr,
                                                rqptr->rqHeader.AcceptLangPtr);
               if (WatchThisOne)
                  WatchDataFormatted ("!&B accept-language:!AZ \'!AZ\'\n",
                     Result, Scratch, rqptr->rqHeader.AcceptLangPtr);
            }
         }
         else
         if (TOUP(cptr[7]) == 'C' && cptr[14] == ':')
         {
            /*********************/
            /* "Accept-Charset:" */
            /*********************/

            cptr += 15;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (rqptr->rqHeader.AcceptCharsetPtr)
            {
               Result = MapUrl_ConditionalList (rqptr, Scratch,
                           mclptr->RegexPregPtr,
                           rqptr->rqHeader.AcceptCharsetPtr);
               if (WatchThisOne)
                  WatchDataFormatted ("!&B accept-charset:!AZ \'!AZ\'\n",
                     Result, Scratch, rqptr->rqHeader.AcceptCharsetPtr);
            }
         }
         else
         if (TOUP(cptr[7]) == 'E' && cptr[15] == ':')
         {
            /**********************/
            /* "Accept-Encoding:" */
            /**********************/

            cptr += 16;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (rqptr->rqHeader.AcceptEncodingPtr)
            {
               Result = MapUrl_ConditionalList (rqptr, Scratch,
                           mclptr->RegexPregPtr,
                           rqptr->rqHeader.AcceptEncodingPtr);
               if (WatchThisOne)
                  WatchDataFormatted ("!&B accept-encoding:!AZ \'!AZ\'\n",
                     Result, Scratch, rqptr->rqHeader.AcceptEncodingPtr);
            }
         }
         else
         if (cptr[13] == ':')
         {
            /******************/
            /* Authorization: */
            /******************/

            cptr += 14;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.AuthorizationPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B authorization:!AZ \'!AZ\'\n",
                  Result, Scratch, rqptr->rqHeader.AuthorizationPtr);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'C')
      {
         if (TOUP(cptr[3]) == 'L' && cptr[7] == ':')
         {
            /************/
            /* callout: */
            /************/

            cptr += 8;
            if (!rqptr) goto JustChecking;
            Result = rqptr->rqCgi.CalloutInProgress;
            if (WatchThisOne) WatchDataFormatted ("!&B callout:\n", Result);
         }
         else
         if (TOUP(cptr[7]) == 'C' && cptr[17] == ':')
         {
            /**********************/
            /* client_connect_gt: */
            /**********************/

            cptr += 18;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;

            CurrentNumber = MetaconClientConcurrent (rqptr);
            Result = CurrentNumber > atoi(Scratch);
            if (WatchThisOne)
               WatchDataFormatted ("!&B client_connect_gt:!AZ !UL\n",
                                   Result, Scratch, CurrentNumber);
         }
         else
         if (TOUP(cptr[8]) == 'M' && cptr[14] == ':')
         {
            /*******************/
            /* cluster_member: */
            /*******************/

            cptr += 15;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = MetaConEvaluateClusterMember (rqptr, Scratch,
                                                   mclptr->RegexPregPtr,
                                                   WatchThisOne);
         }
         else
         if (TOUP(cptr[8]) == 'L' && cptr[12] == ':')
         {
            /*****************/
            /* command_line: */
            /*****************/

            cptr += 13;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, CommandLine, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B command_line:!AZ \'!AZ\'\n",
                                   Result, Scratch, CommandLine);
         }
         else
         if (cptr[6] == ':')
         {
            /***********/
            /* cookie: */
            /***********/

            cptr += 7;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (rqptr->rqHeader.CookiePtr)
               Result = StringMatchAndRegex (rqptr,
                                             rqptr->rqHeader.CookiePtr,
                                             Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B cookie:!AZ \'!AZ\'\n",
                  Result, Scratch, rqptr->rqHeader.CookiePtr);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'D')
      {
         if (cptr[6] == ':')
         {
            /***********/
            /* decnet: */
            /***********/

            cptr += 7;
            if (!rqptr) goto JustChecking;
            Result = (SysInfo.DECnetVersion == *cptr - '0');
            if (WatchThisOne)
               WatchDataFormatted ("!&B decnet:!UL !UL\n",
                  Result, *cptr - '0', SysInfo.DECnetVersion);
         }
         else
         if (cptr[4] == ':')
         {
            /*********/
            /* demo: */
            /*********/

            cptr += 5;
            if (!rqptr) goto JustChecking;
            Result = CliDemo;
            if (WatchThisOne) WatchDataFormatted ("!&B demo: /DEMO\n", Result);
         }
         else
         if (TOUP(cptr[9]) == 'D' && cptr[13] == ':')
         {
            /******************/
            /* document-root: */
            /******************/

            cptr += 13;
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqPathSet.MapRootPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B document-root:!AZ \'!AZ\'\n",
                  Result, Scratch, rqptr->rqPathSet.MapRootPtr);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'F' && cptr[9] == ':')
      {
         /**************/
         /* forwarded: */
         /**************/

         cptr += 10;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         if (rqptr->rqHeader.ForwardedPtr)
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.ForwardedPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B forwarded:!AZ \'!AZ\'\n",
               Result, Scratch, rqptr->rqHeader.ForwardedPtr);
      }
      else
      if (TOUP(cptr[0]) == 'H' && cptr[4] == ':')
      {
         /*********/
         /* Host: */
         /*********/

         cptr += 5;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         if (rqptr->rqHeader.HostPtr)
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.HostPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B host:!AZ \'!AZ\'\n",
               Result, Scratch, rqptr->rqHeader.HostPtr);
      }
      else
      if (TOUP(cptr[0]) == 'J' && TOUP(cptr[4]) == 'U' && cptr[11] == ':')
      {
         /*******************/
         /* "jpi_username:" */
         /*******************/

         cptr += 12;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         Result = StringMatchAndRegex (rqptr,
                                       HttpdProcess.UserName,
                                       Scratch,
                                       SMATCH_GREEDY_REGEX,
                                       mclptr->RegexPregPtr,
                                       NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B jpi_username:!AZ !AZ\n",
               Result, Scratch, HttpdProcess.UserName);
      }
      else
      if (TOUP(cptr[0]) == 'M' && cptr[11] == ':')
      {
         /****************/
         /* Mapped-Path: */
         /****************/

         /* different to path-info, result after script parsing or 'map' rule */
         cptr += 12;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) 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, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B mapped-path:!AZ \'!AZ\'\n",
               Result, Scratch, sptr);
      }
      else
      if (TOUP(cptr[0]) == 'N' && cptr[7] == ':')
      {
         /************/
         /* notepad: */
         /************/

         cptr += 8;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         if (rqptr->NotePadPtr)
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->NotePadPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B notepad:!AZ \'!AZ\'\n",
               Result, Scratch, rqptr->NotePadPtr);
      }
      else
      if (TOUP(cptr[0]) == 'O' && cptr[3] == ':')
      {
         /********/
         /* ODS: */
         /********/

         cptr += 4;
         if (!rqptr) goto JustChecking;
         if (*cptr == '2')
            Result = rqptr->PathOds == MAPURL_PATH_ODS_2;
         else
         if (*cptr == '5')
            Result = rqptr->PathOds == MAPURL_PATH_ODS_5;
         else
         if (toupper(*cptr) == 'A')
            Result = rqptr->PathOds == MAPURL_PATH_ODS_ADS;
         else
         if (toupper(*cptr) == 'P')
            Result = rqptr->PathOds == MAPURL_PATH_ODS_PWK;
         else
         if (toupper(*cptr) == 'S' && toupper(*(cptr+1)) == 'M')
            Result = rqptr->PathOds == MAPURL_PATH_ODS_SMB;
         else
         if (toupper(*cptr) == 'S' && toupper(*(cptr+1)) == 'R')
            Result = rqptr->PathOds == MAPURL_PATH_ODS_SRI;
         else
            Result = false;

         if (WatchThisOne)
            WatchDataFormatted ("!&B ods:!&C !&?2\r5\r\n",
               Result, *cptr, !rqptr->PathOdsExtended);
      }
      else
      if (TOUP(cptr[0]) == 'Q' && cptr[12] == ':')
      {
         /*****************/
         /* Query-String: */
         /*****************/

         cptr += 13;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         Result = StringMatchAndRegex (rqptr,
                                       rqptr->rqHeader.QueryStringPtr,
                                       Scratch,
                                       SMATCH_GREEDY_REGEX,
                                       mclptr->RegexPregPtr,
                                       NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B query-string:!AZ \'!AZ\'\n",
               Result, Scratch, rqptr->rqHeader.QueryStringPtr);
      }
      else
      if (TOUP(cptr[0]) == 'P')
      {
         if (cptr[4] == ':')
         {
            /*********/
            /* pass: */
            /*********/

            cptr += 5;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = (Scratch[0] - '0' == rqptr->MetaConPass);
            if (WatchThisOne)
               WatchDataFormatted ("!&B pass:!AZ \'!UL\'\n",
                  Result, Scratch, rqptr->MetaConPass);
         }
         else
         if (cptr[9] == ':')
         {
            /**************/
            /* Path-Info: */
            /**************/

            cptr += 10;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.PathInfoPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B path-info:!AZ \'!AZ\'\n",
                  Result, Scratch, rqptr->rqHeader.PathInfoPtr);
         }
         else
         if (cptr[15] == ':')
         {
            /********************/
            /* Path-Translated: */
            /********************/

            cptr += 16;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (rqptr->RequestMappedFile[0])
               Result = StringMatchAndRegex (rqptr,
                                             rqptr->RequestMappedFile,
                                             Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
            else
               Result = false;
            if (WatchThisOne)
               WatchDataFormatted ("!&B path-translated:!AZ \'!AZ\'\n",
                  Result, Scratch, rqptr->RequestMappedFile);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'R')
      {
         if (cptr[4] == ':')
         {
            /*********/
            /* rand: */
            /*********/

            cptr += 5;
            if (!rqptr) goto JustChecking;
            /* conversion from ASCII string is relatively expensive */
            if (isdigit(*cptr))
            {
               if (isdigit(cptr[1]))
               {
                  modulas = atoi(cptr);
                  while (*cptr && isdigit(*cptr)) cptr++;
               }
               else
                  modulas = *cptr++ - '0';
            }
            else
               modulas = 0;
            if (*cptr == ':') cptr++;
            if (isdigit(*cptr))
            {
               if (isdigit(cptr[1]))
               {
                  number = atoi(cptr);
                  while (*cptr && isdigit(*cptr)) cptr++;
               }
               else
                  number = *cptr++ - '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 (cptr[10] == ':')
         {
            /***************/
            /* Redirected: */
            /***************/

            cptr += 11;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (Scratch[0])
               /* specific count */
               Result = (Scratch[0] - '0' == rqptr->RedirectCount);
            else
               /* no count specified, treat as a boolean */
               Result = rqptr->RedirectCount;
            if (WatchThisOne)
               WatchDataFormatted ("!&B redirected:!AZ \'!UL\'\n",
                  Result, Scratch, rqptr->RedirectCount);
         }
         else
         if (TOUP(cptr[2]) == 'F' && cptr[7] == ':')
         {
            /************/
            /* Referer: */
            /************/

            cptr += 8;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.RefererPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B referer:!AZ \'!AZ\'\n",
                  Result, Scratch, rqptr->rqHeader.RefererPtr);
         }
         else
         if (cptr[5] == ':')
         {
            /**********/
            /* regex: */
            /**********/

            cptr += 6;
            if (!rqptr) goto JustChecking;
            Result = Config.cfMisc.RegexEnabled;
            if (WatchThisOne) WatchDataFormatted ("!&B regex:\n", Result);
         }
         else
         if (TOUP(cptr[7]) == 'A' && cptr[11] == ':')
         {
            /****************/
            /* Remote-Addr: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (!strchr (Scratch, '/'))
            {
               /* not a network mask */
               sptr = rqptr->rqClient.IpAddressString;
               Result = StringMatchAndRegex (rqptr, sptr, Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
            }
            else
            {
               /* a network mask */
               char *tptr = Scratch;  /* preserve string address using ptr */
               Result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr,
                                             &rqptr->rqClient.IpAddress));
            }
            if (WatchThisOne)
               WatchDataFormatted ("!&B remote-addr:!AZ !AZ\n",
                  Result, Scratch, rqptr->rqClient.IpAddressString);
         }
         else
         if (TOUP(cptr[7]) == 'H' && cptr[11] == ':')
         {
            /****************/
            /* Remote-Host: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (!strchr (Scratch, '/'))
            {
               /* not a network mask */
               if (isdigit(Scratch[0]))
                  sptr = rqptr->rqClient.IpAddressString;
               else
                  sptr = rqptr->rqClient.Lookup.HostName;
               Result = StringMatchAndRegex (rqptr, sptr, Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
            }
            else
            {
               /* a network mask */
               char *tptr = Scratch;  /* preserve string address using ptr */
               Result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr,
                                             &rqptr->rqClient.IpAddress));
            }
            if (WatchThisOne)
               WatchDataFormatted ("!&B remote-host:!AZ !AZ\n",
                  Result, Scratch, isdigit(Scratch[0]) ?
                     rqptr->rqClient.IpAddressString :
                     rqptr->rqClient.Lookup.HostName);
         }
         else
         if (TOUP(cptr[2]) == 'Q' && cptr[7] == ':')
         {
            /************/
            /* request: */
            /************/

            cptr += 8;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (*(unsigned short*)Scratch == '?\0')
            {
               Result = rqptr->rqHeader.UnknownFieldsCount;
               sptr = "";
            }
            else
            for (idx = 0; idx < rqptr->rqHeader.RequestFieldsCount; idx++)
            {
               sptr = rqptr->rqHeader.RequestFieldsPtr[idx];
               Result = StringMatchAndRegex (rqptr, sptr, Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
               if (Result) break;
               sptr = "";
            }
            if (WatchThisOne)
               WatchDataFormatted ("!&B request:!AZ \'!AZ\'\n",
                                   Result, Scratch, sptr);
         }
         else
         if (TOUP(cptr[8]) == 'M' && cptr[14] == ':')
         {
            /*******************/
            /* request-method: */
            /*******************/

            cptr += 15;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.MethodName,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B request-method:!AZ !AZ\n",
                  Result, Scratch, rqptr->rqHeader.MethodName);
         }
         else
         if (TOUP(cptr[8]) == 'S' && cptr[14] == ':')
         {
            /*******************/
            /* Request-Scheme: */
            /*******************/

            cptr += 15;
            if (!rqptr) goto JustChecking;
            if (strsame (cptr, "https", 5))
               Result = rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS;
            else
            if (strsame (cptr, "http", 4))
               Result = rqptr->ServicePtr->RequestScheme == SCHEME_HTTP;
            else
               Result = false;
            if (WatchThisOne)
            {
               for (sptr = cptr; !ISLWS(*sptr) && *sptr != ')'; sptr++);
               WatchDataFormatted ("!&B request-scheme:!#AZ !AZ\n",
                  Result, sptr-cptr, cptr,
                  rqptr->ServicePtr->RequestScheme == SCHEME_HTTP ?
                  "http:" : "https:");
            }
         }
         else
         if (TOUP(cptr[2]) == 'S' && cptr[7] == ':')
         {
            /************/
            /* restart: */
            /************/

            cptr += 8;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (isdigit(Scratch[1]))
               Result = (atoi(Scratch[0]) == rqptr->MetaConRestartCount);
            else
               Result = (Scratch[0] - '0' == rqptr->MetaConRestartCount);
            if (WatchThisOne)
               WatchDataFormatted ("!&B restart:!AZ \'!UL\'\n",
                  Result, Scratch, rqptr->MetaConRestartCount);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'S')
      {
         if (TOUP(cptr[5]) == 'T' && cptr[11] == ':')
         {
            /****************/
            /* Script-Name: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (sptr = rqptr->MetaConScriptPtr)
            {
               /* in second pass of rule mapping (groan) */
               if (*sptr == '+')
               {
                  /* CGIplus script indicator */
                  *sptr = '/';
                  Result = StringMatchAndRegex (rqptr, sptr, Scratch,
                                                SMATCH_GREEDY_REGEX,
                                                mclptr->RegexPregPtr,
                                                NULL);
                  if (WatchThisOne)
                     WatchDataFormatted ("!&B script-name:!AZ !AZ\n",
                        Result, Scratch, sptr);
                  *sptr = '+';
               }
               else
               {
                  /* just a standard script beginning with '/' */
                  Result = StringMatchAndRegex (rqptr, sptr, Scratch,
                                                SMATCH_GREEDY_REGEX,
                                                mclptr->RegexPregPtr,
                                                NULL);
                  if (WatchThisOne)
                     WatchDataFormatted ("!&B script-name:!AZ !AZ\n",
                        Result, Scratch, sptr);
               }
            }
            else
            {
               /* must be past rule mapping */
               Result = StringMatchAndRegex (rqptr,
                                             rqptr->ScriptName,
                                             Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
               if (WatchThisOne)
                  WatchDataFormatted ("!&B script-name:!AZ !AZ\n",
                     Result, Scratch, rqptr->ScriptName);
            }
         }
         else
         if (TOUP(cptr[7]) == 'A' && cptr[11] == ':')
         {
            /****************/
            /* Server-Addr: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            if (!strchr (Scratch, '/'))
            {
               /* not a network mask */
               sptr = rqptr->ServicePtr->ServerIpAddressString;
               Result = StringMatchAndRegex (rqptr, sptr, Scratch,
                                             SMATCH_GREEDY_REGEX,
                                             mclptr->RegexPregPtr,
                                             NULL);
            }
            else
            {
               /* a network mask */
               char *tptr = Scratch;  /* preserve string address using ptr */
               Result = VMSok (TcpIpNetMask (rqptr, WatchThisOne, &tptr,
                                  &rqptr->ServicePtr->ServerIpAddress));
            }
            if (WatchThisOne)
               WatchDataFormatted ("!&B server-addr:!AZ !AZ\n",
                  Result, Scratch, rqptr->ServicePtr->ServerIpAddressString);
         }
         else
         if (TOUP(cptr[7]) == 'C' && cptr[17] == ':')
         {
            /**********************/
            /* server_connect_gt: */
            /**********************/

            cptr += 18;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
            CurrentNumber = AccountingPtr->ConnectCurrent;
            InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
            Result = CurrentNumber > atoi(Scratch);
            if (WatchThisOne)
               WatchDataFormatted ("!&B server_cunnect_gt:!AZ !UL\n",
                                   Result, Scratch, CurrentNumber);
         }
         else
         if (TOUP(cptr[7]) == 'N' && cptr[11] == ':')
         {
            /****************/
            /* Server-Name: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->ServicePtr->ServerHostName,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B server-name:!AZ !AZ\n",
                  Result, Scratch, rqptr->ServicePtr->ServerHostName);
         }
         else
         if (TOUP(cptr[7]) == 'P' && cptr[11] == ':')
         {
            /****************/
            /* Server-Port: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, 
                                          rqptr->ServicePtr->ServerPortString,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B server-port:!AZ !AZ\n",
                  Result, Scratch, rqptr->ServicePtr->ServerPortString);
         }
         else
         if (TOUP(cptr[7]) == 'P' && cptr[17] == ':')
         {
            /**********************/
            /* server_process_gt: */
            /**********************/

            cptr += 18;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
            CurrentNumber = AccountingPtr->ConnectProcessing;
            InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
            Result = CurrentNumber > atoi(Scratch);
            if (WatchThisOne)
               WatchDataFormatted ("!&B server_process_gt:!AZ !UL\n",
                                   Result, Scratch, CurrentNumber);
         }
         else
         if (TOUP(cptr[7]) == 'S' && cptr[15] == ':')
         {
            /********************/
            /* Server-Software: */
            /********************/

            cptr += 16;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, SoftwareId, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B server-software:!AZ \'!AZ\'\n",
                  Result, Scratch, SoftwareId);
         }
         else
         if (cptr[7] == ':')
         {
            /************/
            /* service: */
            /************/

            cptr += 8;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->ServicePtr->ServerHostPort,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B service:!AZ !AZ\n",
                  Result, Scratch, rqptr->ServicePtr->ServerHostPort);
         }
         else
         if (cptr[3] == ':')
         {
            /********/
            /* SSL: */
            /********/

            if (!rqptr) goto JustChecking;
            Result = rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS;
            if (WatchThisOne) WatchDataFormatted ("!&B ssl:\n", Result);
         }
         else
         if (TOUP(cptr[4]) == 'A' && cptr[13] == ':')
         {
            /******************/
            /* syi_arch_name: */
            /******************/

            cptr += 14;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
#ifdef __ALPHA
            Result = strsame (Scratch, "ALPHA", -1) ||
                     strsame (Scratch, "AXP", -1);
            if (WatchThisOne)
               WatchDataFormatted ("!&B syi_arch_name:!AZ Alpha/AXP\n",
                  Result, Scratch);
#endif
#ifdef __ia64
            Result = strsame (Scratch, "IA64", -1);
            if (WatchThisOne)
               WatchDataFormatted ("!&B syi_arch_name:!AZ IA64\n",
                  Result, Scratch);
#endif
#ifdef __VAX
            Result = strsame (Scratch, "VAX", -1);
            if (WatchThisOne)
               WatchDataFormatted ("!&B syi_arch_name:!AZ VAX\n",
                  Result, Scratch);
#endif
         }
         else
         if (TOUP(cptr[4]) == 'H' && cptr[11] == ':')
         {
            /****************/
            /* syi_hw_name: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, SysInfo.HwName, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B syi_hw_name:!AZ \'!AZ\'\n",
                  Result, Scratch, SysInfo.HwName);
         }
         else
         if (TOUP(cptr[0]) == 'S' && TOUP(cptr[4]) == 'N' && cptr[12] == ':')
         {
            /*****************/
            /* syi_nodename: */
            /*****************/

            cptr += 13;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, SysInfo.NodeName, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
           if (WatchThisOne)
               WatchDataFormatted ("!&B syi_nodename:!AZ !AZ\n",
                  Result, Scratch, SysInfo.NodeName);
         }
         else
         if (TOUP(cptr[4]) == 'V' && cptr[11] == ':')
         {
            /****************/
            /* syi_version: */
            /****************/

            cptr += 12;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, SysInfo.Version, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B syi_version:!AZ !AZ\n",
                  Result, Scratch, SysInfo.Version);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'T')
      {
         if (cptr[5] == ':')
         {
            /**********/
            /* tcpip: */
            /**********/

            cptr += 6;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = StringMatchAndRegex (rqptr, TcpIpAgentInfo, Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
            if (WatchThisOne)
               WatchDataFormatted ("!&B tcpip:!AZ \'!AZ\'\n",
                  Result, Scratch, TcpIpAgentInfo);
         }
         else
         if (cptr[4] == ':')
         {
            /*********/
            /* time: */
            /*********/

            cptr += 5;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = MetaConEvaluateTime (rqptr, Scratch,
                                          mclptr->RegexPregPtr,
                                          WatchThisOne);
         }
         else
         if (cptr[6] == ':')
         {
            /***********/
            /* trnlnm: */
            /***********/

            cptr += 7;
            cptr += MetaConBuffer (mclptr, cptr);
            if (!rqptr) goto JustChecking;
            Result = MetaConEvaluateTrnLnm (rqptr, Scratch,
                                            mclptr->RegexPregPtr,
                                            WatchThisOne);
         }
         else
         {
            if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
            return ("\0Unknown conditional");
         }
      }
      else
      if (TOUP(cptr[0]) == 'U' && TOUP(cptr[5]) == 'A' && cptr[10] == ':')
      {
         /***************/
         /* User-Agent: */
         /***************/

         cptr += 11;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         if (rqptr->rqHeader.UserAgentPtr)
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.UserAgentPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B user-agent:!AZ \'!AZ\'\n",
               Result, Scratch, rqptr->rqHeader.UserAgentPtr);
      }
      else
      if (TOUP(cptr[0]) == 'X' && TOUP(cptr[2]) == 'F' && cptr[15] == ':')
      {
         /********************/
         /* X-Forwarded-For: */
         /********************/

         cptr += 15;
         cptr += MetaConBuffer (mclptr, cptr);
         if (!rqptr) goto JustChecking;
         if (rqptr->rqHeader.XForwardedForPtr)
            Result = StringMatchAndRegex (rqptr,
                                          rqptr->rqHeader.XForwardedForPtr,
                                          Scratch,
                                          SMATCH_GREEDY_REGEX,
                                          mclptr->RegexPregPtr,
                                          NULL);
         if (WatchThisOne)
            WatchDataFormatted ("!&B x-forwarded-for:!AZ \'!AZ\'\n",
               Result, Scratch, rqptr->rqHeader.XForwardedForPtr);
      }
      else
      if (isdigit(*cptr))
      {
         /*********************/
         /* numeric "boolean" */
         /*********************/

         Result = atoi(sptr = cptr);
         while (isdigit(*cptr)) cptr++;
         if (WatchThisOne)
            WatchDataFormatted ("!&B !#AZ\n", Result, cptr - sptr, sptr);
      }
      else
      {
         /***********/
         /* unknown */
         /***********/

         if (WatchThisOne) WatchDataFormatted ("UNKNOWN CONDITIONAL\n");
         return ("\0Unknown conditional");
      }
 
      JustChecking:

      if (!Scratch[0] && Scratch[1])
      {
         if (WatchThisOne) WatchDataFormatted ("!AZ\n", Scratch+1);
         memcpy (ReturnScratch, Scratch, sizeof(ReturnScratch));
         return (ReturnScratch);
      }

      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 TOUP
}

/*****************************************************************************/
/*
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
(
REQUEST_STRUCT *rqptr,
char *String,
regex_t *pregptr,
BOOL WatchThisOne
)
{
#  define SYI_NODENAME_SIZE 15
   struct ClusterNodeStruct {
      char  Name [SYI_NODENAME_SIZE+1];
   };
   static unsigned short  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;
   unsigned long  CsidAdr;
   char  *cptr, *sptr, *zptr;
   IO_SB  IOsb;

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

   if (WatchThisOne && WATCH_MODULE(WATCH_MOD_METACON))
      WatchThis (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConEvaluateClusterMember() !&Z", String);

   status = sys$getsyiw (EfnWait, 0, 0, &SyiClusterNodesItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      ErrorNoticed (status, "sys$getsyiw()", FI_LI);
      return (false);
   }

   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 (status, "sys$getsyiw()", FI_LI);
            return (false);
         }
      }
      ClusterNodesCount = idx;
   }

   Result = false;
   for (idx = 0; idx < ClusterNodesCount; idx++)
      if (StringMatchAndRegex (rqptr, ClusterNodesPtr[idx].Name, String,
                               SMATCH_GREEDY_REGEX, pregptr, NULL))
      {
         if (!WatchThisOne) return (true);
         Result = true;
         break;
      }

   if (!WatchThisOne) return (Result);

   WatchDataFormatted ("!&B cluster-member:!AZ ",
                       Result, String, ClusterNodesCount);
   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
(
REQUEST_STRUCT *rqptr, 
char *String,
regex_t *pregptr,
BOOL WatchThisOne
)
{
   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;

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

   if (WATCH_MODULE(WATCH_MOD_METACON))
      WatchThis (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConEvaluateTime() !&Z", String);

   if (isdigit(String[0]) && !String[1])
   {
      /* day of week, 1 == Monday, 2 == Tuesday .. 7 == Sunday */
      DayOfWeek = String[0] - '0';
      Result = (DayOfWeek == HttpdDayOfWeek);
      if (WatchThisOne)
         WatchDataFormatted ("!&B !UL == !UL\n",
            Result, DayOfWeek, HttpdDayOfWeek);
      return (Result);
   }

   if (isdigit(String[0]) && isdigit(String[1]) &&
       isdigit(String[2]) && isdigit(String[3]) && String[4] == '-' &&
       isdigit(String[5]) && isdigit(String[6]) &&
       isdigit(String[7]) && isdigit(String[8])) 
   {
      /* time range */
      Result = true;
      StartMinute = (((String[0]-'0')*10) + (String[1]-'0')) * 60;
      if (StartMinute > 1380) Result = false;
      StartMinute += ((String[2]-'0')*10) + (String[3]-'0');
      if (StartMinute >= 1440) Result = false;
      EndMinute = (((String[5]-'0')*10) + (String[6]-'0')) * 60;
      if (EndMinute > 1380) Result = false;
      EndMinute += ((String[7]-'0')*10) + (String[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 (WatchThisOne)
            WatchDataFormatted ("!&B !UL <= !UL <= !UL\n",
               Result, StartMinute, CurrentMinute, EndMinute);
         return (Result);
      }
      if (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, String,
                                 SMATCH_GREEDY_REGEX, pregptr, NULL);
   if (WatchThisOne)
      WatchDataFormatted ("!&B !AZ \'!AZ\'\n", Result, TimeString, String);
   return (Result);
} 

/*****************************************************************************/
/*
Provide the "trnlnm:" conditional evaluation.
See description in module prologue.
*/

BOOL MetaConEvaluateTrnLnm
(
REQUEST_STRUCT *rqptr,
char *String,
regex_t *pregptr,
BOOL WatchThisOne
)
{
   static long  LnmCaseBlind = LNM$M_CASE_BLIND;
   static unsigned short  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  *cptr;
   char  LnmValue [256];

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

   if (WATCH_MODULE(WATCH_MOD_METACON))
      WatchThis (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConEvaluateTrnLnm() !&Z", String);

   /* syntax is 'trnlnm:name[;table][:value]' */
   for (cptr = String; *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 (!String[0]) return (false);
   NameDsc.dsc$a_pointer = String;
   NameDsc.dsc$w_length = strlen(String);

   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, String, LnmTableDsc.dsc$a_pointer, Length, LnmValue, cptr); 

   if (VMSok (status))
   {
      LnmValue[Length] = '\0';
      if (*cptr)
         Result = StringMatchAndRegex (rqptr, LnmValue, cptr,
                                       SMATCH_GREEDY_REGEX, pregptr, NULL);
      else
         Result = true;
      if (WatchThisOne)
         WatchDataFormatted ("!&B !AZ !AZ \'!AZ\' \'!AZ\'\n",
            Result, NameDsc.dsc$a_pointer, LnmTableDsc.dsc$a_pointer,
            LnmValue, cptr);
      return (Result);
   }

   if (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 (status, "sys$trnlnm()", FI_LI);
   return (false);
} 

/*****************************************************************************/
/*
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 - it's
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.
*/ 

MetaconClientConcurrent (REQUEST_STRUCT *rqptr)

{
   static BOOL  ConcurrentCurrent;
   static int  ClientConcurrent; 

   IPADDRESS  IpAddress;
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (NULL, FI_LI, WATCH_MOD_REQUEST, "MetaconClientConcurrent()");

   if (!rqptr)
   {
      /* reset */
      ClientConcurrent = 0;
      ConcurrentCurrent = false;
      return;
   }

   if (ConcurrentCurrent) return (ClientConcurrent);
   ConcurrentCurrent = true;

   memcpy (&IpAddress, &rqptr->rqClient.IpAddress, sizeof(IpAddress));

   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;
      /* if most probably a persistent connection waiting */
      if (!rqeptr->BytesRx && rqeptr->rqNet.KeepAliveCount)
         continue;
      if (!IPADDRESS_IS_SAME (&IpAddress, &rqeptr->rqClient.IpAddress))
         continue;
      if (rqptr->rqHeader.XForwardedForFieldPtr &&
          rqeptr->rqHeader.XForwardedForFieldPtr)
      {
         /* if the "X-Forwarded-For:" fields are not the same then next */
         if (strcmp (rqptr->rqHeader.XForwardedForFieldPtr,
                     rqeptr->rqHeader.XForwardedForFieldPtr)) continue;
      }
      else
      if (rqptr->rqHeader.ForwardedFieldPtr &&
          rqeptr->rqHeader.ForwardedFieldPtr)
      {
         /* if the "Forwarded:" fields are not the same then next */
         if (strcmp (rqptr->rqHeader.ForwardedFieldPtr,
                     rqeptr->rqHeader.ForwardedFieldPtr)) continue;
      }
      ClientConcurrent++;
   }

   /* if we're only using the one instance then that's that! */
   if (InstanceNodeCurrent == 1) return (ClientConcurrent);
   /* if three or more on this instance it's likely the same on others */
   if (ClientConcurrent > 2) ClientConcurrent *= InstanceNodeCurrent;
   return (ClientConcurrent);
}

/*****************************************************************************/
/*
Copies the conditional parameter string following a conditional directive name
into the specified buffer.  For example, this function would buffer
"*.vsm.com.au" from the conditional "host:*.vsm.com.au".  If the buffer
contents are a regular expression, and there is still available precompile
regex buffers available, then compile the regex.
*/

int MetaConBuffer
(
METACON_LINE *mclptr,
char *String
)
{
   char  ch;
   char  *cptr, *sptr, *zptr;
   regex_t  RegexPreg;

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

   if (WATCH_MODULE(WATCH_MOD_METACON))
      WatchThis (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConBuffer() !&Z", String);

   zptr = (sptr = mclptr->BufferPtr) + mclptr->SizeOfBuffer-1;
   cptr = String;
   if (*cptr == '\"' || *cptr == '\'')
   {
      ch = *cptr++;
      while (*cptr && *cptr != ch)
      {
         /* step over any escape character */
         if (cptr[0] == '\\' && cptr[1]) cptr++;
         if (sptr <= zptr) *sptr++ = *cptr;
         cptr++;
      }
      if (*cptr) cptr++;
   }
   else
   {
      while (*cptr && !ISLWS(*cptr) && *cptr != ')')
      {
         /* step over any escape character */
         if (cptr[0] == '\\' && cptr[1]) cptr++;
         if (sptr <= zptr) *sptr++ = *cptr;
         cptr++;
      }
   }
   if (sptr < zptr)
   {
      *sptr = '\0';
      if (!mclptr->BufferPtr[0]) mclptr->BufferPtr[1] = '\0';
   }
   else
      strcpy (mclptr->BufferPtr, "\0Conditional buffer overflow");

   if (mclptr->RegexCompiled)
   {
      /* during rule processing */
      if (Config.cfMisc.RegexEnabled && 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.RegexEnabled && mclptr->BufferPtr[0] == REGEX_CHAR)
   {
      /* 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->SizeOfBuffer-8);
      }
      else
         mclptr->RegexPregCount++;
      if (mclptr->RegexPregPtr == &RegexPreg)
         regfree (mclptr->RegexPregPtr);
      mclptr->RegexPregPtr = NULL;
   }

   if (WATCH_MODULE(WATCH_MOD_METACON))
      WatchDataFormatted ("!&Z !&B\n",
         mclptr->BufferPtr, mclptr->RegexPregPtr);

   return (cptr - String);
} 

/*****************************************************************************/
/*
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 we're operating at
AST-delivery level and cannot be pre-empted!
*/

BOOL MetaConConditionalList
(
REQUEST_STRUCT  *rqptr,
char *String,
regex_t *pregptr,
char *List
)
{
   char  ch;
   char  *cptr, *lptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_METACON)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_METACON,
                 "MetaConConditionalList() !&Z !&Z", String, List);

   if (!(lptr = List)) return (false);
   if (!String) return (false);
   while (*lptr)
   {
      /* spaces and commas are element separators, step over */
      while (ISLWS(*lptr) || *lptr == ',') lptr++;
      /* if end of list (or empty) then finished */
      if (!*lptr) return (false);
      /* find end of this element, save next character, terminate element */
      for (cptr = lptr; *lptr && !ISLWS(*lptr) && *lptr != ','; lptr++);
      ch = *lptr;
      *lptr = '\0';
      /* look for what we want */
      if (StringMatchAndRegex (rqptr, cptr, String,
                               SMATCH_GREEDY_REGEX, pregptr, NULL))
      {
         /* restore list */
         *lptr = ch;
         return (true);
      }
      /* restore list */
      *lptr = ch;
   }
   /* ran out of list elements, so it can't be a match! */
   return (false);
}

/*****************************************************************************/
/*
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 (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConSetInteger() !&Z", cptr);

   if (isdigit(*cptr)) return (atoi(cptr));
   if (!*cptr) return (0);
   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 (NULL, FI_LI, 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.  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.
*/

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  hr, min, sec;

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

   if (WATCH_MODULE(WATCH_MOD_METACON))
      WatchThis (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConSetSeconds() !&X !AZ !UL",
                 mcptr, cptr, SingleIntegerSeconds);

   hr = min = sec = 0;
   if (isdigit(*cptr))
      sec = atoi(cptr);
   else
   if (strsame (cptr, "NONE", 4))
      return (-2);
   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 (hr < 0 || hr > 99 || min < 0 || min > 59 || sec < 0 || sec > 59)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemPeriodValue);
      return (-1);
   }
   if (Debug) fprintf (stdout, "%d:%d:%d=%d\n", hr,min,sec,hr*3600+min*60+sec);
   return (hr*3600+min*60+sec);
}

/*****************************************************************************/
/*
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;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  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 (NULL, FI_LI, 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, unsigned long);
   va_end (argptr);

   if (mcptr->CurrentOdsPtr)
   {
      /* while source files are being loaded */
      if (mcptr->CurrentOdsPtr->DataLineNumber)
      {
         *vecptr++ = " (line !UL!&@)";
         *vecptr++ = mcptr->CurrentOdsPtr->DataLineNumber;
         if (mcptr->IncludeFile)
         {
            *vecptr++ = " of !AZ";
            *vecptr++ = mcptr->CurrentOdsPtr->ExpFileName;
         }
         else
            *vecptr++ = "";
      }
      else
         *vecptr++ = "";
      if (mcptr->CurrentOdsPtr->DataLinePtr)
      {
         for (cptr = mcptr->CurrentOdsPtr->DataLinePtr;
              *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)";
         *vecptr++ = mcptr->ParsePtr->Number;
      }
      else
         *vecptr++ = "";
      if (mcptr->ParsePtr->TextPtr)
      {
         for (cptr = mcptr->ParsePtr->TextPtr; *cptr && ISLWS(*cptr); cptr++);
         if (*cptr)
         {
            *vecptr++ = "\\!AZ\\\n";
            *vecptr++ = cptr;
         }
         else
            *vecptr++ = "";
      }
      else
         *vecptr++ = "";
   }
   else
   {
      *vecptr++ = "";
      *vecptr++ = "";
      *vecptr++ = "";
   }

   status = WriteFaol (Buffer1, sizeof(Buffer1), NULL,
                       "!&@!&@\n!&@", &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFao()", FI_LI);
      return (status);
   }

   /* put into 'Buffer2' ensuring newlines are padded from the left margin */
   vecptr = FaoVector;
   *vecptr++ = mcptr->LoadReport.ItemCount;
   *vecptr++ = ch;
   status = WriteFaol (Buffer2, sizeof(Buffer2), &Length,
                       "!UL.!&C ", &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFao()", 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 (SS$_BUFFEROVF, "WriteFao()", 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 (NULL, FI_LI, WATCH_MOD_METACON,
                 "MetaConStartupString() !&Z", Facility);

   if (mcptr->LoadReport.ItemCount)
      WriteFaoStdout (
"%!AZ-W-!AZ, !UL informational, !UL warning, !UL error!%s at load\n!AZ",
                      Utility, 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)
      WriteFaoOpcom (
"%!AZ-W-!AZ, !UL informational, !UL warning, !UL error!%s at load",
                     Utility, Facility,
                     mcptr->LoadReport.InformCount,
                     mcptr->LoadReport.WarningCount,
                     mcptr->LoadReport.ErrorCount);
}

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

