/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                MapUrl.c

Functions supporting mapping of URLs to VMS file specifications and VMS 
specifications to URLs, uses the HTTPd rule mapping file.  The number of rules 
it can store is not fixed, but is limited by available memory and the 
practical considerations of processing large, linear rule databases. 

Comment lines may be provided by beginning a line with a '#' character.

Lines may be continued by ensuring the last character on the line is a '\'.

Virtual server syntax is the same as for authentication/authorization rules,
[[virtual-host]] (all ports of host) or [[virtual-host:virtual-port]] (
matching specified port of host) for a specific host and [[*]] to resume rules
for all virtual hosts.


Wildcards
---------
A single '*' wildcard matches intervening text up until the first character
encountered that matches the character immediately following the wildcard.

The rule

  map /one/*/three/* /first/*/third/*

would map the following request path

  /one/two/three/four

into

  /first/two/third/four

but would not match the request path

  /one/two/and-a-half/three/four

Two consecutive ('**') wildcards match intervening text up until the next
string that matches the string following the wildcard.

The rule

  map /one/**/three/* /first/*/third/*

would map the following request paths

  /one/two/three/four
  /one/two/and-a-half/three/four

respectively into

  /first/two/third/four
  /first/two/and-a-half/third/four

Historically (pre v8.1) the template wildcard-matched portions of path strings
could only be substituted in the positions of corresponding wildcards in the
result string.  There can be ocassions where there is not a one-to-one
relationship between these wildcard sections in the source (template) and
target (result) strings.  It is sometimes useful to be able to drop one or more
strings from the template and/or change the order of substitution.

The use of 'positional wildcards' (for want of an even marginally better term)
will allow this.  When a wildcard is used in the result string it may
optionally be followed by a single apostrophe character (') and a single digit
character '1'..'9'.  The digit is used to indicate which of the template
wildcards is intended (first to last).

The following request path

  /one/two/three/four/five.six

would be processed by the rule

  pass /one/*/three/*/* /*'2/one/*'1/*'3

into the mapped path

  /four/one/two/five.six

Using this notation all template wildcards must be specified 'positionally' :^)
This rule is not legal.

  pass /one/*/three/* /two/*'2/four/*

Positional wildcards ONLY APPLY to PASS, MAP, REDIRECT and USER rules.
They cannot be used with EXEC, SCRIPT and UXEC rules.


Extended File Specification
---------------------------
Extended file specifications (required for ODS-5) are supported.  A rule is
used to flag such paths for ODS-5 tailored processing.  Things that are not
done to ODS-5 paths; case-changes and character substitution.  Things that are
done to ODS-5 paths; escaping to and from the extended file specification
canonical escape syntax (i.e. '^').

Paths located on ODS-5 volumes need to have the ODS-5 rule SET against them.

  set /extended/* ODS-5

It is possible to explicitly mark a path as ODS-2 also, although as this is the
default it is usually unnecessary.  One situation where this might be useful is
where all paths are ODS-5 except a small minority, in which case a rule such as
the following might be used.

  set /* ODS-5
  set /old-path/* ODS-2

Paths derived using the USER rule are specially processed.  The SYSUAF user
home directory's structure is determined using a sys$getdvi() to determined
it's ACPTYPE.  Hence no explicit rule is required for these.


Mapping Rules
-------------
[[virtual-host:optional-virtual-port]]
EXEC       template  result           [setting(s)] [conditional(s)]
EXEC       (runtime)template  result  [setting(s)] [conditional(s)]
EXEC+      template  result           [setting(s)] [conditional(s)]
FAIL       template                                [conditional(s)]
MAP        template  result           [setting(s)] [conditional(s)]
PASS       template                                [conditional(s)]
PASS       template  result           [setting(s)] [conditional(s)]
PROTECT    template  access-control   [setting(s)] [conditional(s)]
REDIRECT   template  result           [setting(s)] [conditional(s)]
SCRIPT     template  result           [setting(s)] [conditional(s)]
SCRIPT     (runtime)template  result  [setting(s)] [conditional(s)]
SCRIPT+    template  result           [setting(s)] [conditional(s)]
SET        template  setting(s)                    [conditional(s)]
USER       template  result           [setting(s)] [conditional(s)]
UXEC       template  result           [setting(s)] [conditional(s)]
UXEC       (runtime)template  result  [setting(s)] [conditional(s)]
UXEC+      template  result           [setting(s)] [conditional(s)]

(Somewhat awkwardly the square brackets both denote optional fields and to
literally delimit a conditional.  Please do not confuse the two!)

Note that as of V8.0 path settings (anything that can be set with a SET rule)
can also follow the result string.  If the rule applies and any conditional is
matched then the settings are applied to the path BEFORE the mapping is done.

PASS rules can map to a result comprising an HTTP status code (e.g. 403) with a
following textual message (including white-space).  Just delimit the result
string with double or single quotes, or within curly braces.  For example:

Similarly, PASS rules can map to a result comprising an 200 HTTP status code
with a following dollar symbol and CLI command string which is passed to the
script processor.

PASS /private/stuff/* "403 Can't go in there!" [!ho:my.host.name]
PASS /private/stuff/* '403 "/private/stuff/" is off-limits!' [!ho:my.host.name]

EXEC rules can be used to map both directories and file types.  The former only
requires a single, trailing wildcard (example 1).  The latter requires two
wildcards (examples 2 and 3).  The  irst to locate the script, the second to
map any path.  This format can be used to globally map a file type, or confine
that mapping to specified part of a tree.

EXEC /cgi-bin/*
EXEC *.cgi* *.cgi*
EXEC /specified/path/*.cgi* /specified/path/*.cgi*

PROTECT rules can be used require authorization for for the matching path.  It
complements the facility available via the WASD_CONFIG_AUTH configuration file,
providing a simpler (and to certain extent less capble) authorization schema. 
See the relevant section in the description to AUTH.C module.


General Run-Time Mapping
------------------------
This is distinct from "Run-Time (RTE) Environment" scripting discussed below. 
It also uses a similar rule construct and historically came much after RTE
mappings.  Hence the '!' kludge mentioned shortly.  The historical way to map
script file types (other than .EXE and .COM which are handled implicitly) has
been to add [DclScriptRunTime] entries to WASD_CONFIG_GLOBAL.  The (very small)
disadvantage to using this is the need to completely restart the server to
apply changes.  Design-wise the better place for these sorts of mappings (file
types to run-time engine) is here in WASD_CONFIG_MAP (it also allows /DO=MAP to
change these).  From v8.2 we can use an RTE-like construct to specify what
non-RTE engine should be activated to process the specified script.  It can be
an executable, a DCL procedure or even verb.  Here's how we might activate Perl
using these rules:

  exec /cgi-bin/*.pl* (!perl)/cgi-bin/*.pl*
  exec /cgi-bin/*.pl* (!$perl_root:[000000]perl.exe)/cgi-bin/*.pl*
  exec /cgi-bin/*.pl* (!@cgi-bin:[000000]perl.com)/cgi-bin/*.pl*
  script /this_one* (!$perl_root:[000000]perl.exe)/cgi-bin/this_one.pl*

The first uses a CLI verb, the second activates a specified executable and the
last executes a DCL procedure (which in this presumably sets up some
environment before activating Perl).

Note the leading '!'.  This is a bit of a kludge but not too onerous.


Mapping Logical-Name Content
----------------------------
Generally logical names only provide concealed devices into the file-system
for "root"ing some part of the web-space.  That is the value of the logical
name is only used in RMS (file-system) parsing, etc.

There is a single exception.  When template and result patterns contain no
wildcard(s) a one-to-one mapping if performed.  For example

  pass /this_is_an_example /dka0/example/this.txt

and the file content at

  DKA0:[EXAMPLE]THIS.TXT

is returned.

With a one-to-one mapping the result string can represent a logical name and
the translation of that name used as the mapping element.  For a PASS rule the
logical name value is used as the VMS result of the mapping (and therefore a
VMS file specification).  For a REDIRECT rule it should be a URL undertood by
redirection.  Here are examples:

  $ DEFINE /SYSTEM /EXEC EXAMPLE_OF DKA0:[EXAMPLE]ONE.TXT
  $ DEFINE /SYSTEM /EXEC EXAMPLE_GO "/wasd_root/runtime/httpd/"

  pass /of EXAMPLE_OF
  redirect /go EXAMPLE_GO

Using this capability, the target of a rule may be changed simply by modifying
the translated value of a logical name.

Note that the mapping rule logical name translation is case-sensitive, and so
common use of logical names (all upper-case) means the rule must be all upper
case.  With all lower or mixed case the rule must specify exact case, as in
this example:

  $ DEFINE /SYSTEM /EXEC "EXAMPLE_go_two" "/wasd_root/src/httpd/"

  redirect /go_2 EXAMPLE_go_two


Run-Time Environment (RTE) Mapping
----------------------------------
Each persistent run-time environment must have it's own mapping environment. 
This may, or may not, correspond to physically distinct directory areas.  It is
the mapping rules that identify run-time environments.  A parenthesized
interprester specification leads the result component of the rule.  This is
first extracted and then the EXEC rule behaves as normal.  Hence the following 
example shows two such environments each with it's own interpreter.

  exec /plbin/* (cgi-bin:[000000]perlrte.exe)/ht_root/perl_local/*
  exec /pybin/* (cgi-bin:[000000]pyrte.exe)/ht_root/python_local/*
  script /this_one* (cgi-bin:[000000]oneeng.exe)/cgi-bin/this.one*

The "/plbin/*" identifies request paths beginning with this string as a
hypothetical, persistent Perl run-time interpreter, with the Perl source
scripts available to it from HT_ROOT:[PERL_LOCAL].  Similarly with a
hypothetical Java Environment.

If a request with the following URI is given to the server

  http://the.host.name/plbin/plsearch/web/doc/

the following components will be derived

  run-time interpreter ... CGI-BIN:[000000]PERLRT.EXE
  Perl source script ..... HT_ROOT:[PERL_LOCAL]PLSEARCH.PL
  request path-info ...... WEB:[DOC]


Mapping User Directories
------------------------
The USER rule effectively PASSes the user's SYSUAF default device and directory
as the first wildcard substitution in the rule.  Accounts that are disusered,
captive or have expired passwords are never mapped, they are always "denied
access by default".  In the same way privileged accounts (those possessing
SYSPRV) cannot be mapped this way (see below for a workaround).

An example; if the user name DANIEL has the default device USER$DISK: and
default directory [DANIEL] the following request path

  /~daniel/

would be mapped to the result and VMS file specification

  /user$disk/daniel/www/
  USER$DISK:[DANIEL.WWW]

using the following rule.

  USER  /~*/*  /*/www/

Note the "/www" subdirectory component.  It is recommended that user
directories always be mapped to a subdirectory of the physcial area.  This
effectively "sandboxes" Web access to that subdirectory hierarchy, allowing the
user privacy elsewhere in the home area.

To accomodate request user paths that do not incorporate a trailing delimiter
after the username the following redirect may be used to cause the browser to
re-request with a more appropriate path.

  REDIRECT  /~*  ///*/www/

WASD also "reverse maps" VMS specifications into paths and so requires
additional rules to provides these mappings.  (Reverse mapping is required
during directory listings and error reporting.)  In the above case the
following rules would actually be required (and in the stated order).

  USER  /~*/*  /*/www/
  PASS  /~*/*  /user$disk/*/www/*

Where user home directories are spread over multiple devices (physical or
concealed logical) a reverse-mapping rule would be required for each. Consider
the following situation, where user directories are distributed across these
devices (concealed logicals)

  USER$GROUP1:
  USER$GROUP2:
  USER$GROUP2:
  USER$OTHER:

This would require the following mapping rules (in the stated order).

  USER  /~*/*  /*/www/
  PASS  /~*/*  /user$group1/*/www/*
  PASS  /~*/*  /user$group2/*/www/*
  PASS  /~*/*  /user$group3/*/www/*
  PASS  /~*/*  /user$other/*/www/*

Of course vanilla mapping rules may be used to provide for special cases.  For
instance, if there is requirement for a particular, privileged account to have
a user mapping that could be provided as in the following (rather exagerated)
example.

  PASS  /~system/*  /sys$common/sysmgr/www/*
  USER  /~*/*  /*/www/
  PASS  /~*/*  /user$disk/*/www/*


Path SETings
------------
As of v12... see MAPSET.C


[CONFIGFILE]
------------
The non-global [IncludeFile] equivalent [ConfigFile] designed for local,
per-service configuration management can include a subset of the full set of
mapping rules and settings.  The following are ALLOWED:

  FAIL
  MAP
  PASS
  PROTECT
  REDIRECT

  SET [NO]ACCEPT= 
  SET CHARSET= 
  SET [NO]EXPIRED
  SET [NO]HTML=
  SET INDEX=
  SET [NO]MAP=ONCE
  SET QUERY-STRING=
  SET RESPONSE=HEADER=

Those that cannot be used in [ConfigFile] are reported as errors during
configuration load.


MAP=ROOT Mapping
----------------
This path SETing allows for the more straight-forward mapping of virtual
service paths into a virtual-service-specific directory structure.  It ensures
that for all applicable rules the specified string is prepended to the mapped
path before it is converted into a file-system specification.  This remove the
need to do it with each virtual-service-specific rule.  It needs to be redone
or undone (e.g. set to empty) for each virtual service or when reverting to
general rules.  For example

  [[a.virtual.host]]
  set * map=root=/dka0/a/
  # 'a' has no access to any scripts at all
  pass /* /*
  fail *
  [[b.virtual.host]]
  set * map=root=/dka0/b/
  # 'b' are allowed to execute their own scripts
  exec /cgi-bin/* /cgi-bin/*
  pass /* /*
  fail *
  [[c.virtual.host]]
  # 'c' is allowed to execute the general server scripts
  exec /cgi-bin/* /cgi-bin/*
  set * map=root=/dka0/c/
  pass /* /*
  fail *
  [[*]]
  set * map=root=
  exec /cgi-bin/* /cgi-bin/*
  pass /* /web/*
  fail *


PERSONA-based User Scripting
----------------------------
For use with VMS V6.2 and greater (or 6.0 and 6.1 if compiled with the
PERSONA_MACRO build option), with the /PERSONA qualifier active.
The following rule set will allow user account based scripting.

  SET   /~*/www/cgi-bin/*  script=as=~
  UXEC  /~*/cgi-bin/*  /*/www/cgi-bin/*
  USER  /~*/*  /*/www/*
  REDIRECT  /~*  /~*/
  PASS  /~*/*  /dka0/users/*/*


DECnet-based User Scripting
---------------------------
If DECnet/OSU scripting is enabled then the following rule will activate a
script in the specified user's directory (all other things being equal).
Always map to some unique and specific subdirectory of the home directory.

exec /~*/cgi-bin/* /0""::/where/ever/*/cgi-bin/*

It's a little like the user-mapping rules:

pass /~* /where/ever/*


Mapping Conditionals
--------------------

 ***************
 * OBSOLESCENT *
 ***************

 ALTHOUGH MAPPING RULE CONDITIONALS DESCRIBED BELOW WILL CONTINUE
 TO BE SUPPORTED IN THE FORESEEABLE FUTURE THE PREFERED POST-7.2
 MECHANISM FOR CONDITIONAL PROCESSING OF RULES IS PROVIDED BY THE
 META-CONFIG CONDITIONAL STRUCTURES AND DIRECTIVES.

Conditionals are new to v4.4 and allow mapping rules to be applied
conditionally! That is, the conditionals contain strings that are matched to
specific elements of the request and only if matched are the corresponding
rules applied. The conditionals may contain the '*' and '%' wildcards, and
optionally be negated by an '!' at the start of the conditional string.

Conditionals must follow the rule and are delimited by '[' and ']'. Multiple,
space-separated conditions may be included within one '[...]'. This behaves as
a logical OR (i.e. the condition is true if only one is matched). Multiple
'[...]' conditionals may be included against a rule. These act as a logical
AND (i.e. all must have at least one condition matched). The result of an
entire conditional may be optionally negated by prefixing the '[' with a '!'.

If a conditional is not met the line is completely ignored.  Conditional rules
are not used for 'backward' translation (i.e. from VMS to URL path) so another
rule must exist somewhere to do these mappings.  Conditional rules are only
used by the HTTPd server, they are completely ignored by any scripts using the
MapUrl.c module.

[!]ac:accept               'Accept:'
[!]al:accept-language      'Accept-Language:'
[!]as:accept-charset       'Accept-Charset:'
[!]ca:                     boolean, callout in progress?
[!]ck:cookie               'Cookie:'
[!]dr:string               document root (set map=root=)
[!]ex:                     boolean, extended file specification path
[!]fo:host-name/address    'Forwarded:', proxies/gateways
[!]ho:host-name/address    client's of course!
[!]hm:host network mask    client mask, see immediately below
[!]me:http-method          GET, POST, ?, etc.
[!]mp:string               derived mapped path (after script or map rule)
[!]no:string               notepad keywords/tokens
[!]pa:digit                1 if first pass, 2 if second pass through rules
[!]pi:path-info            request path information
[!]qs:query-string         request query string
[!]rc:[digit]              internally redirected count, 0..4, or boolean
[!]rf:refering-URL         'Referer:'
[!]rq:string               request field (e.g. "Keep-Alive: 300")
[!]ru:string               request URI - non-decoded path string
[!]sc:scheme               request scheme 'http:', 'http', 'https:' or 'https'
[!]sn:server-name          server host name
[!]sp:server-port          server port
[!]st:script-name          if during second pass compare to script name
[!]ua:user-agent           'User-Agent:'
[!]vs:host-name/address    virtual server, "Host:", destination
[!]xf:client               "X-Forwarded-For:" proxied cleint details

The host-mask ('HM') directive is a dotted-decimal network address, a slash,
then a dotted-decimal mask.  For example "[HM: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.


VERSION HISTORY
---------------
22-AUG-2022  MGD  MapUrl_ReportNow() format massaging via MetaConCounter()
27-MAY-2021  MGD  carve out path SETing functions to MAPSET.C module
14-APR-2021  MGD  pass /whatever "200 $ CLI command" to RequestMappedToStatus()
20-MAR-2021  MGD  SET response=var=asis
28-FEB-2021  MGD  SET proxy=rework=<string>
16-OCT-2020  MGD  SET webdav=all and webdav=auth
26-SEP-2020  MGD  bugfix; ODS=ADS missing from reporting
12-FEB-2020  MGD  SET response=[no]csp=<string> ("content-security-policy:")
                  SET response=[no]cspro=<string> ("..policy-report-only:")
                  refactor MapUrl_ExplainPathSet()
20-JAN-2020  MGD  quieten string overflow reports
30-JAN-2019  MGD  SET response=200=203 for request tracking and log analysis
15-AUG-2018  MGD  bugfix; for DECnet tasks when StringSliceValue() modified
                    (25-MAY-2017 allow quote-delim inside space-delimited)
13-AUG-2018  MGD  SET response=var=crlf
                  SET response=var=lf
                  SET response=var=none
06-APR-2018  MGD  MapUrl__Map() allow one-to-one logical name content
05-APR-2018  MGD  bugfix; longstanding MapUrl__Map() mutiple template wildcards
                    when reverse mapping (search for the version log date)
25-JAN-2018  MGD  SET dir=title=default
                  SET dir=title=owner
                  SET dir=title=remote
                  SET dir=title=<integer>
                  SET dir=title=this=<string>
15-JUN-2017  MGD  MapUrl_GuaranteeAccess() mapping as well as authorisation
25-MAY-2017  MGD  bugfix; MapUrl_ExplainPathSet() response=header=add=..
07-MAR-2017  MGD  SET proxy=header=<name>[=<string>]
26-OCT-2015  MGD  SET dict=<key>[=<value>]
                  SET http2=streams=max=<integer>
                  SET http2=protocol=1.1
                  SET http2=write=[low|normal|high]
                  SET http2=send=goaway[=<integer>]
                  SET http2=send=ping
                  SET http2=send=reset[=<integer>]
                  SET sslcgi=apache_mod_ssl_client
                  SET sslcgi=apache_mod_ssl_extens
17-APR-2015  MGD  #define MAP_BUFFER_SIZE for fundamental mapping buffers
04-APR-2015  MGD  bugfix; MapUrl_ExplainPathSet() ->ResponseChunked
24-MAR-2015  MGD  SET response=sts=<value> (Strict-Transport-Security: header)
21-NOV-2014  MGD  MapUrl_SetClientAddress() and MapUrl_ResetClientAddress()
                    call WatchFilterClientService() if client address changed
06-NOV-2014  MGD  SET client=[forwarded|if=forwarded|literal=|reset|
                              if=xforwardedfor|xforwardedfor]
06-SEP-2014  MGD  SET dir=font=[inherit|monospace(D)]
                  SET dir=style=TABLE (new default)
                  SET ods=name=8bit, ods=name=utf8, ods=name=default
                  SET webdav=[no]hidden
                  SET webdav=meta=dir=<string>
28-SEP-2013  MGD  SET cors=<detail>
27-SEP-2013  MGD  SET dir=versions=<integer>|*
17-AUG-2013  MGD  put=rfm=[STM|STMCD|UDF] added to FIX512,STMLF
                  bugfix; MapUrl__Map() MAPURL_REPORT_MATCH_RULENOCOND
25-JUN-2013  MGD  SET dir=delimit=<which>
                  SET dir=[no]ilink
                  SET dir=style=sort (plus the dir=style=<which>2)
                  SET dir=sort=<char>[+|-]
                  SET dir=target=<string>
                  SET dir=these=<wildcard1>[,<wildcard2>]
30-MAY-2013  MGD  bugfix; MapUrl__Map() reverse mapping wildcard copy
15-SEP-2011  MGD  SET map=uri
                  bugfix; map=noonce
06-MAR-2011  MGD  SET cache=[no]cookie
26-FEB-2011  MGD  bugfix; set rule 'CacheSetting' boolean with any CACHE=..
02-JAN-2011  MGD  SET regex=<keyword>
07-SEP-2010  MGD  SET proxy=tunnel=request=<string>
18-JUL-2010  MGD  Uni Malaga pushes report items from !3ZL to !4ZL :-)
01-JUL-2010  MGD  bugfix; MapUrl_ControlReload()
19-JUN-2010  MGD  put=max=* represents no (effective) limit
27-FEB-2010  MGD  SET proxy=chain=cred=<string>
09-FEB-2010  MGD  SET script=lifetime=<hh:mm:ss>
                  SET response=HTTP=original
23-JAN-2010  MGD  SET service=<string>
                  SET websocket=<keyword>
                  SET notimeout (short-hand for timeout=none,none,none)
11-JAN-2010  JPP  MapUrl__Map() increase some buffer sizes
05-SEP-2009  MGD  allow "pass:-1" to indicate reverse-mapping
23-AUG-2009  MGD  SET css=<style-sheet-URL>
                  bugfix; MapUrl__Map() to URL use request ODS not path ODS
21-JUN-2009  MGD  SET put=max=<kbytes>, put=rfm=[FIX512|STMLF]
08-OCT-2008  MGD  bugfix; MapUrl__Map() proxy 'fall-thru'
19-APR-2008  MGD  SET script=agent=as=<account>
23-MAY-2007  MGD  refine loading and mapping of path SETings,
                  MapUrl_Map()/__Map() now have a REQUEST_PATHSET parameter
                  (to better decouple file-system mapping and path SETing)
11-MAY-2007  MGD  bugfix; MapUrl_Map() auth agent modifying path SETings
01-MAY-2007  MGD  SET access=[no]access, access=[no]profile,
                      access=[no]read, access=[no]write,
                      webdav=[no]access, webdav=[no]profile,
                      webdav=[no]read, webdav=[no]write,
                      webdav=[no]lock, webdav=[no]prop,
                      webdav=lock=timeout=
22-APR-2006  MGD  SET proxy=[no]affinity
26-JAN-2006  MGD  increase MapUrl__Map() WildBuffer[] storage to 4096
26-OCT-2005  MGD  bugfix; MapUrl__Map() SCRIPT result copy not checking
                  for null resulting in occasional overflow error status
10-SEP-2005  JPP  SET proxy=reverse=[no]auth
01-JUN-2005  MGD  SET script=symbol=[no]truncate
21-MAY-2005  MGD  SET throttle=<integer>/<integer> per-user throttle
08-JAN-2005  MGD  SET script=control=<...>
16-DEC-2004  MGD  SET response=[no]chunked
04-NOV-2004  MGD  SET script=syntax=[no]unix
16-OCT-2004  MGD  SET response=gzip=<...>,
                  SET script=body=[no]decode,
                  allow a throttle of zero to be set,
                  throttle=none to reset previously mapped throttle,
                  unbundle some functionality into MAPCON.C, MAPODS.C and
                  MAPUSER.C modules (been wanting to do this for ages)
24-SEP-2004  MGD  bugfix; auth=revalidate= is minutes not seconds
31-AUG-2004  MGD  SET report=tunnel
12-AUG-2004  MGD  bugfix; parse timeout=n,n,n allowing to specify "none"
20-JUL-2004  MGD  SET timeout=keepalive= superceded by timeout=persistent=
20-APR-2004  MGD  actual on-disk structure for each PASS result (ODS-2 or
                  ODS-5) is applied to a path unless otherwise SET with ODS=,
                  don't bother translating a PASS unless it looks file-system
                  (consumes a few less cycles for things like proxy)
08-MAR-2004  MGD  MapUrl_Map() increase wildcard buffer space
26-FEB-2004  MGD  SET script=default=<directory>
13-JAN-2004  MGD  review string SETings for empty string behaviour
10-JAN-2004  MGD  SET ssi=exec=<string>
08-JAN-2004  MGD  SET response=header=[no]add[="<string>"]
16-DEC-2003  MGD  mapping now URL-encodes a redirect wildcard path portions
20-NOV-2003  MGD  SET proxy=reverse=location, proxy=reverse=verify
28-SEP-2003  MGD  when do a callout path mapping use a 'throw-away' path
                  SETing structure to prevent modification of request itself,
                  SET path=set=[no]ignore, 
                      path=set=[no]request 
                  prevent ODS-2 directory paths from containing single periods
01-SEP-2003  MGD  SET map=root=<string> (groan %^p)
                  'DR' conditional (parallels metacon "document-root:")
07-JUL-2003  MGD  SET script=command=<string>,
                  SET cache=[no]cgi, cache=expires=.., cache=[no]file,
                      cache=[no]net, cache=maxkbytes=.., cache=[no]nph,
                      cache=[no]script, cache=[no]ssi,
                      response=header=[append|full|none]
                  bugfix; do not allow SET mapping during a callout
27-JUN-2003  MGD  bugfix; request Html.. memory allocation (jpp@esme.fr)
24-MAY-2003  MGD  SET cache=[no]permanent, cache=max=<integer>
09-MAY-2003  MGD  regular expression support,
                  SET notepad=[+]<string> (associated 'NO' conditional),
                  SET map=restart,
                  SET proxy=unknown
                  'RQ' conditional (parallels metacon "request:")
22-APR-2003  MGD  improve SET rule error reporting (if no rule or no template)
02-APR-2003  MGD  SET proxy=xforwardedfor=[disabled|enabled|address|unknown]
                  SET proxy=forwarded=[disabled|by|for|address]
                  added 'XF' conditional, removed 'UD' (meaningless)
26-MAR-2003  MGD  SET alert=nnn (were 'nnn' can be 500, 599, 403, etc.)
08-MAR-2003  MGD  SET html=[bodytag|header|headertag|footer|footertag]=[..],
                  dir=style[=default|original|anchor|htdir]
15-FEB-2002  MGD  SET [no]search=none,
                  script=params=+(name=value) concatenates to any existing
17-JAN-2003  MGD  general (non-RTE) run-time allowed with (!..) syntax,
                  both run-time specifications allowed with SCRIPT rule
                  SET cgiplusin=[none|cr|lf|crlf],
                  SET cgiplusin=eof,
                  SET script=query=none,
                  SET script=path=find
10-JAN-2003  MGD  SET script=query=relaxed
16-NOV-2002  MGD  SET auth=all (all access must be authorized or fail)
06-NOV-2002  MGD  added 'MP', 'RC' and 'ST' conditionals
12-OCT-2002  MGD  refine metacon reporting,
                  refine mapping rule processing to ensure that paths with
                  forbidden syntax (e.g. multiple '/') generate RMS bad syntax
05-OCT-2002  MGD  'positional' wildcards,
                  SET query-string=,
                  provide 'rqptr->MetaConPass', 
                  confine script=as= to first pass (script resolution)
                  added 'PA' conditional
24-SEP-2002  MGD  added 'PI' and 'RU' conditionals
16-SEP-2002  MGD  MapOdsElementsToVms() excise parent directory syntax,
                  SET path [no]map=ellipsis (off by default),
                  SET report=4nn=nnn for mapping HTTP status
14-SEP-2002  MGD  only use MapUrl_VmsUserName() path ODS if not already set,
                  SET dir=charset= directory listing charset mapping rule
10-SEP-2002  MGD  add en/decoding for Advanced Server (Samba) file names
15-AUG-2002  MGD  SET alert=map, alert=auth, alert=end variants for ALERT,
                  SET ods=2, ods=5, now ods=pwk and ods=sri for below,
                  rework ..UrlToVms() and ..VmsToUrl() to allow for mapping
                  to and from SRI encoding (NFS) and PATHWORKS encoding
                  (and VMS to URL functions no longer explicitly URL-encode)
07-AUG-2002  MGD  bugfix; 'script' and 'exec' MetaConParseReset() state
04-AUG-2002  MGD  allow for 'pass /* 400' (i.e. no trailing message),
                  bugfix; template/result wildcard checking for scripting rules
31-MAY-2001  MGD  SET dir=access, dir=noaccess, dir=access=selective,
                  dir=impliedwildcard, dir=noimpliedwildcard,
                  dir=wildcard, dir=nowildcard
13-MAY-2002  MGD  SET auth=SYSUAF=pwdexpURL=
07-APR-2002  MGD  exchange DEVICE:[DIRECTORY]FILE.TYPE for the hopefully
                  more informative NO:[REVERSE.MAPPING.FOR.THIS]FILE.PATH
14-MAR-2002  MGD  bugfix; throttle report (jf.pieronne@laposte.net)
14-JAN-2002  MGD  added '**' and '*!' wildcard match and discard to template,
                  eliminate device:[.directory] (multiple consecutive '/'),
                  bugfix; arrghhh - wildcard substitution in MapUrl__Map()
18-NOV-2001  MGD  path SETings may now trail any rule with a result string,
                  and are applied to the matched path before mapping occurs,
                  SET http=accept-charset=, accept=lang
28-OCT-2001  MGD  bugfix; DECnet user script mapping
13-OCT-2001  MGD  ensure a conditional is not mistaken for a missing template,
                  script=params=(name=value[,name="value1 value2"]),
                  proxy=bind=IP-address, proxy=chain=host:port, alert,
                  auth=revalidate=hh:mm:ss,
                  bugfix; wildcard substitution in MapUrl__Map()
25-AUG-2001  MGD  meta-config (even workin' on my birthday, sigh)
04-AUG-2001  MGD  support module WATCHing
15-MAY-2001  MGD  bugfix; remove 'RU' conditional (mapping before auth!)
10-MAY-2001  MGD  modify throttle parameter meanings,
                  set rule SCRIPT=CPU= for limiting script process CPU
13-APR-2001  MGD  add queue length to THROTTLE=n[,n,n,hh:mm:ss,hh:mm:ss]
                  add SCRIPT=BIT-BUCKET=hh:mm:ss,
                  bugfix; conditions for NetThisVirtualService() call
05-APR-2001  MGD  bugfix; 'HM' network mask processing
28-MAR-2001  MGD  EXEC of file type
13-MAR-2001  MGD  set rules THROTTLE=n[,n,n,n] & TIMEOUT=n,n,n
                  "hh:mm:ss" for providing a more versatile period
28-FEB-2001  MGD  OdsLoadTextFile(), OdsParseTextFile(), [IncludeFile]
22-DEC-2000  MGD  use of backslash to escape non-wildcard reserved characters
                  in rule and conditional strings (i.e. \] for ])
19-NOV-2000  MGD  bugfix; mapping '/'
10-NOV-2000  MGD  bugfix; MapOdsUrlToVms() final ';'
01-OCT-2000  MGD  set rule SCRIPT=AS=,
                  rework mapping code somewhat,
                  MapUrl_VmsUserNameCache() on reset free entries
31-AUG-2000  MGD  bugfix; MapOdsUrlToVms() escaped version semi-colon
24-JUN-2000  MGD  add network-mask to conditionals,
                  persistent run-time environment in EXEC rule (...),
                  change 'HH' to 'VS' (virtual service) backward compatible
06-APR-2000  MGD  set rules SSLCGI= and MAP=
04-MAR-2000  MGD  use FaolToNet(), et.al.
02-JAN-2000  MGD  config file opened via ODS module,
                  detection of a path's on-disk-structure (ODS-2 or ODS-5),
                  modify URL->VMS and VMS->URL mapping for extended syntax,
                  avoid confusion; redirect indicators changed from '^' and
                  '@' for local and remote, to '<' and '>' respectively,
                  bugfix; trailing wildcard in set rule comparison
24-OCT-1999  MGD  groan; ensure storage does not overflow!!
                  (plus copious quantities of self-flagellation),
                  SYSUAF-based user directory mapping via a "USER" rule,
                  CGIprefix, [no]profile and [no]authonce SET rules,
                  happy seventh wedding anniversary
12-SEP-1999  MGD  virtual services modifications
10-MAY-1999  MGD  conditional mapping on HTTP cookie and referer,
                  increase the size of scratch storage,
                  mapping endless-loop detection
16-JAN-1999  MGD  allow for proxy service mappings by removing requirement
                  for template and result paths to be absolute ("/...") paths
                  (every time I have to deal with this module I groan)
10-JAN-1999  MGD  bugfix; pass rules not returning mapped path
17-OCT-1998  MGD  SET mapping rule,
                  virtual services via "[[virtual-host:virtual-port]]",
                  "qs:" query string conditional,
                  invalid RMS file name/character substitution configurable
12-AUG-1998  MGD  MapOdsVmsToUrl() now only optionally absorbs MFD
19-JUL-1998  MGD  'E'xec DECnet-based user scripts
10-MAR-1998  MGD  allow for local redirects (using '^' detect character)
07-FEB-1998  MGD  added "sc:" scheme/protocol conditional for SSL support
20-DEC-1997  MGD  added DECnet support in _UrlToVms(),
                  result paths can now begin with an '*'
25-OCT-1997  MGD  added "as:", "fo:", and "hh:" conditionals,
                  bugfix; MsgFor() required request pointer to find langauge
15-SEP-1997  MGD  MsgFor() was not returning a leading-null message (does now),
                  HTTP status code rules (e.g. "pass /no/* 403 forbidden"),
                  removed two consecutive slashes inhibiting rule mapping
07-SEP-1997  MGD  added "sn:" and "sp:" conditionals
09-AUG-1997  MGD  v4.3, message database, mapping "conditionals",
                  more cart-wheels, hand-stands and contortions (see 01-OCT-96)
05-JUN-1997  MGD  v4.2, CGIplus "exec+" and "script+" rules
01-FEB-1997  MGD  HTTPd version 4, major changes for form-based config
01-OCT-1996  MGD  not proud of how I shoe-horned the reporting features in,
                  good example of slop-down or object-disoriented programming,
                  :^( will rework the whole thing one day, sigh!
                  bugfix; requiring use of 'TheFirstRuleWillBeTheNextRule'
06-APR-1996  MGD  modified conversion of non-VMS characters in MapOdsUrlToVms()
15-FEB-1995  MGD  bugfix; redirect rule
01-DEC-1995  MGD  v3.0
24-MAR-1995  MGD  bugfix; end-of-string sometimes not detected when matching
20-DEC-1994  MGD  revised for multi-threaded HTTP daemon
20-JUN-1994  MGD  single-threaded daemon
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#undef __VMS_VER
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <devdef.h>
#include <dvidef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <uaidef.h>

#include "wasd.h"

#define WASD_MODULE "MAPURL"

/* fundamental buffer size */
#ifdef __ALPHA
#define MAP_BUFFER_SIZE (8192 * 2)
#else
/* IA64 and whatever else may follow */
#define MAP_BUFFER_SIZE (8192 * 4)
#endif

/**********/
/* macros */
/**********/

#define MAPURL_RMS_SUBSTITUTION_DEFAULT '$'

#define WATCH_MODULE_DETAIL (WATCH_MODULE(WATCH_MOD_MAPURL) && \
                             WATCH_MODULE(WATCH_MOD__DETAIL))

/******************/
/* global storage */
/******************/

BOOL  MapUrlExtensionMethod,
      MapUrlPathOds5;

MAPPING_META  MappingMeta;
MAPPING_META  *MappingMetaPtr;

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

extern BOOL  AuthPromiscuous,
             AuthProtectRule,
             AuthPolicySysUafRelaxed,
             CliOdsExtendedDisabled,
             CliOdsExtendedEnabled,
             OdsExtended;

extern int  EfnWait,
            ServerPort,
            ServerHostNameLength;

extern unsigned long  SysPrvMask[];

extern char  *FaoUrlEncodeTable[];

extern char  ErrorSanityCheck[],
             ErrorWatchSysFao[],
             ServerHostName[],
             ServerHostPort[],
             SoftwareID[];

extern LIST_HEAD  MapUrlUserNameCacheList;
extern int  MapUrlUserNameCacheEntries;

extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern META_CONFIG  *MetaGlobalMappingPtr;
extern MSG_STRUCT  Msgs;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This overly-long function (sorry) is not AST reentrant, but when executed 
within the context of the HTTPd, is executed either without the possibility of 
AST interruption, or during AST processing and so provides atomic 
functionality.  Pointers to any strings returned must be used immediately 
(before leaving AST context), or copied into storage maintained by the calling 
routine. 

Maps from URL format to VMS file specification, and REVERSE-MAPS from VMS file 
specification to URL format.  Maps scripts.

Always returns a pointer to char.  An error message is detected by being  
returned as a pointer to a string beginning with a null-character!  The 
message begins from character one. 

Call with 'PathPtr' pointing at a string containing the URL path and 'VmsPtr' 
pointing to the storage to contain the mapped VMS equivalent.

Call with 'VmsPtr' pointing to a VMS file specification (dev:[dir]file.ext) 
and 'PathPtr' pointing to an empty string, used as storage to contain the 
REVERSE-MAPPED file specification.

Call with 'PathPtr' pointing at a string containing the URL path, 'VmsPtr' 
pointing to storage to contain the mapped VMS equivalent, 'ScriptPtr' pointing 
to storage for the URL format script path and 'ScriptVmsPtr' pointing to 
storage to contain the VMS specification of the script procedure/image.

The MapUrl_Map() just wraps MapUrl__Map() so that it's function can be WATCHed.

See alter-egos defined in MapUrl.h
*/ 

char* MapUrl_Map
(
char *PathPtr,
int SizeOfPathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
char *ScriptPtr,
int SizeOfScriptPtr,
char *ScriptVmsPtr,
int SizeOfScriptVmsPtr,
char *RunTimePtr,
int SizeOfRunTimePtr,
int *PathOdsPtr,
REQUEST_STRUCT *rqptr,
REQUEST_PATHSET *PathSetPtr
)
{
   BOOL  VmsToPath;
   char  *cptr, *sptr;
   char  OdsNote [32];

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

   if (!rqptr || !WATCHPNT(rqptr))
   {
      cptr = MapUrl__Map (PathPtr, SizeOfPathPtr,
                          VmsPtr, SizeOfVmsPtr,
                          ScriptPtr, SizeOfScriptPtr,
                          ScriptVmsPtr, SizeOfScriptVmsPtr,
                          RunTimePtr, SizeOfRunTimePtr,
                          PathOdsPtr, rqptr, PathSetPtr);
      if (rqptr)
      {
         rqptr->MetaConPass = 0;
         rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
      }
      return (cptr);
   }

   if (!WATCH_CATEGORY(WATCH_MAPPING))
   {
      cptr = MapUrl__Map (PathPtr, SizeOfPathPtr,
                          VmsPtr, SizeOfVmsPtr,
                          ScriptPtr, SizeOfScriptPtr,
                          ScriptVmsPtr, SizeOfScriptVmsPtr,
                          RunTimePtr, SizeOfRunTimePtr,
                          PathOdsPtr, rqptr, PathSetPtr);
      if (rqptr)
      {
         rqptr->MetaConPass = 0;
         rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
      }
      return (cptr);
   }

#if WATCH_CAT

   VmsToPath = false;
   if (PathPtr && PathPtr[0])
      WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "PATH !AZ", PathPtr); 
   else
   if (VmsPtr && VmsPtr[0])
   {
      VmsToPath = true;
      WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "VMS !AZ", VmsPtr); 
   }

   cptr = MapUrl__Map (PathPtr, SizeOfPathPtr,
                       VmsPtr, SizeOfVmsPtr, 
                       ScriptPtr, SizeOfScriptPtr,
                       ScriptVmsPtr, SizeOfScriptVmsPtr,
                       RunTimePtr, SizeOfRunTimePtr,
                       PathOdsPtr, rqptr, PathSetPtr);

   if (VmsToPath)
      WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "RESULT !AZ", cptr);
   else
   {
      if (!PathPtr) PathPtr = "";
      if (!VmsPtr) VmsPtr = "";
      if (!ScriptPtr) ScriptPtr = "";
      if (!ScriptVmsPtr) ScriptVmsPtr = "";
      if (!RunTimePtr) RunTimePtr = "";

      WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "RESULT");
      if (!cptr[0] && cptr[1])
         WatchDataFormatted ("Error: !AZ\n", cptr+1);
      else
      if (cptr[0] == '\1')
         WatchDataFormatted ("Redirect: !AZ\n", cptr+1);
      else
      {
         switch (PathOdsPtr ? *PathOdsPtr : rqptr->PathOds)
         {
            case MAPURL_PATH_ODS_2 : sptr = " (ODS-2)"; break;
            case MAPURL_PATH_ODS_5 : sptr = " (ODS-5)"; break;
            case MAPURL_PATH_ODS_ADS : sptr = " (ADS)"; break;
            case MAPURL_PATH_ODS_PWK : sptr = " (PWK)"; break;
            case MAPURL_PATH_ODS_SMB : sptr = " (SMB)"; break;
            case MAPURL_PATH_ODS_SRI : sptr = " (SRI)"; break;
            default : sptr = " (ods-2)";
         }
         WatchDataFormatted ("\
     Mapped: !AZ\n\
 Translated: !AZ!AZ\n\
     Script: !AZ\n\
Script-File: !AZ\n\
   Run-Time: !AZ\n",
           cptr, VmsPtr, VmsPtr[0] ? sptr : "",
           ScriptPtr, ScriptVmsPtr, RunTimePtr);
      }
   }

   if (rqptr)
   {
      rqptr->MetaConPass = 0;
      rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
   }
   return (cptr);

#endif /* WATCH_CAT */
}

char* MapUrl__Map
(
char *PathPtr,
int SizeOfPathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
char *ScriptPtr,
int SizeOfScriptPtr,
char *ScriptVmsPtr,
int SizeOfScriptVmsPtr,
char *RunTimePtr,
int SizeOfRunTimePtr,
int *PathOdsPtr,
REQUEST_STRUCT *rqptr,
REQUEST_PATHSET *PathSetPtr
)
{
   static char  DerivedPathBuffer [MAP_BUFFER_SIZE+128],
                PathBuffer [MAP_BUFFER_SIZE+128],
                VmsBuffer [MAP_BUFFER_SIZE+128];

   static char  CommentFao [] = "!AZ<span class=\"incomment\">!AZ</span>\n";

   static char  NutherFileFao [] = "!AZ<span class=\"include\">!AZ</span>\n";

   BOOL  EvanescentZeroed,
         ExecFileType,
         LoadingRules,
         MapEllipsis,
         MapPathToVms,
         StatusCodeMapping,
         SubstituteForUserName;
   int  idx, widx, status,
        InConfigLevel,
        MapRootLength,
        MetaConPass,
        MetaConRestartCount,
        PathOds,
        SetPathIgnore,
        TotalLength,
        WatchThisMatch,
        WatchThisOne,
        WildStringCount;
   unsigned short  Length;
   char  ch,
         RmsSubChar;
   char  *cptr, *eptr, *pptr, *rptr, *sptr, *tptr, *uptr, *zptr,
         *MapRootPtr;
   char  *WildString [REGEX_PMATCH_MAX];
   char  Scratch [MAP_BUFFER_SIZE+128],
         UserDefault [256],
         VmsUserMappedBuffer [256],
         WildBuffer [(MAP_BUFFER_SIZE*4)+128];
   MAP_RULE_META  *mrptr;
   MAP_SET_META  *mrpsptr;
   METACON_LINE  *mclptr;
   REQUEST_PATHSET  EvanescentPathSet;
   REQUEST_PATHSET  *rqpsptr;
   regex_t  *pregptr;
   regmatch_t  pmatch [REGEX_PMATCH_MAX];

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_Map() !UL !&Z !UL !&Z !UL !&Z !UL",
                 SizeOfPathPtr, PathPtr, SizeOfVmsPtr, VmsPtr,
                 SizeOfScriptPtr, ScriptPtr, SizeOfScriptVmsPtr);

   /* if just loading the rules then free any previous set */
   if (LoadingRules = (!PathPtr && !VmsPtr && !ScriptPtr))
      MetaConUnload (&MetaGlobalMappingPtr, &MapUrl_ConfigUnloadLineData);

   if (!MetaGlobalMappingPtr)
   {
      /*********************/
      /* read mapping file */
      /*********************/

      MapUrl_ConfigLoad (&MetaGlobalMappingPtr);
   }

   /* if just (re)loading rules then return now */
   if (LoadingRules) return ("\0\0");

   /*****************************************/
   /* map from the URL or VMS to VMS or URL */
   /*****************************************/

   if (!PathPtr)
   {
      SET2(PathBuffer,'\0\0');
      PathPtr = PathBuffer + 1;
      SizeOfPathPtr = sizeof(PathBuffer);
   }
   if (!VmsPtr)
   {
      *(VmsPtr = VmsBuffer) = '\0';
      SizeOfVmsPtr = sizeof(VmsBuffer);
   }
   /* making 'ScriptPtr' non-empty means "exec" and "script" are ignored */
   if (!ScriptPtr) ScriptPtr = "?";

   if (PathPtr[0])
   {
      /* if the URL is not an empty string then force the conversion to VMS */
      MapPathToVms = true;
      *VmsPtr = '\0';
      MetaConPass = 1;
   }
   else
   {
      /* URL was an empty string, convert from VMS to URL */
      MapPathToVms = false;
      /* generate a URL-style version of the VMS specification */
      MapOdsVmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, true, rqptr->PathOds);
      MetaConPass = -1;
   }

   /***********************/
   /* loop thru the rules */
   /***********************/

   if (!MetaGlobalMappingPtr)
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_NO_RULES)-1);

   WatchThisOne = WatchThisMatch = 0;
   if (WATCHPNT(rqptr))
   {
      if (WATCH_CATEGORY(WATCH_MAPPING)) WatchThisOne = WATCH_MAPPING;
      if (WATCH_CATEGORY(WATCH_MATCH)) WatchThisMatch = WATCH_MATCH;
   }

   InConfigLevel = MapRootLength = PathOds = 0;
   RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
   EvanescentZeroed = MapEllipsis = SetPathIgnore = false;
   MapRootPtr = NULL;
   MetaConRestartCount = 0;

   if (rqptr)
   {
      rqptr->MetaConPass = MetaConPass;
      rqptr->MetaConRestartCount = MetaConRestartCount;
      rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
   }

   if (!PathSetPtr)
   {
      PathSetPtr = &EvanescentPathSet;
      memset (&EvanescentPathSet, 0, sizeof(EvanescentPathSet));
      EvanescentZeroed = true;
   }

   rqpsptr = PathSetPtr;

   mclptr = NULL;
   MetaConParseReset (MetaGlobalMappingPtr, true);
   for (;;)
   {
      mclptr = MetaGlobalMappingPtr->ParsePtr =
         MetaGlobalMappingPtr->ParseNextPtr;

      if (WATCH_MODULE_DETAIL)
         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) break;

      mclptr->WatchThisOne = WatchThisOne;

      if (mclptr->Token == METACON_TOKEN_COMMENT)
      {
         if (WatchThisOne)
         {
            if (SAME4(mclptr->TextPtr, '!#+#'))
            {
               /* from InConfigLoad() add a level */
               WatchDataFormatted ("!AZ!AZ\n",
                                   MetaConCounter (mclptr, InConfigLevel),
                                   mclptr->TextPtr + 5);
               InConfigLevel++;
            }
            else
            if (SAME4(mclptr->TextPtr, '!#-#'))
            {
               /* from InConfigLoad() remove a level */
               if (InConfigLevel) InConfigLevel--;
            }
            else
               WatchDataFormatted ("!AZ!AZ\n",
                                   MetaConCounter (mclptr, InConfigLevel),
                                   mclptr->TextPtr);
         }
         MetaGlobalMappingPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
         continue;
      }

      if (mclptr->Token == METACON_TOKEN_TEXT)
      {
         /* get required data, adjust the parse context to the next "line" */
         cptr = mclptr->TextPtr;
         mrptr = mclptr->LineDataPtr;
         MetaGlobalMappingPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
      }
      else
      if (mclptr->Token != METACON_TOKEN_DIRECTORY)
      {
         /* not a simple text line, have meta-config parse this one for us */
         cptr = MetaConParse (rqptr, MetaGlobalMappingPtr, &mclptr,
                              WatchThisOne);
         /* if the end of rules */
         if (!cptr) break;

         /* if error string */
         if (!*cptr && *(cptr+1)) return (cptr);
         /* if inline rule and expression was false */
         if (SAME4 (cptr, '\0\0\0\1')) continue;
         mrptr = mclptr->LineDataPtr;
      }

      if (mclptr->Token == METACON_TOKEN_DIRECTORY)
      {
         /* get required data, adjust the parse context to the next "line" */
         cptr = mclptr->TextPtr + sizeof("[ConfigDirectory]");
         while (*cptr && ISLWS(*cptr)) cptr++;
         zptr = (sptr = rqptr->ConfigDirectory) +
                sizeof(rqptr->ConfigDirectory)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         rqptr->ConfigDirectoryLength = sptr - rqptr->ConfigDirectory;
         MetaGlobalMappingPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
         continue;
      }

      /* there was a problem in initializing the rule */
      if (!mrptr)
      {
         if (WatchThisOne) WatchDataFormatted ("?!AZ\n", cptr);
         continue;
      }

      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!&X !UL !&Z !&Z !&Z\n", 
            mrptr, mrptr->RuleType, mrptr->TemplatePtr,
            mrptr->ResultPtr, mrptr->ConditionalPtr);

      /* hmmm, endless loop!! */
      if (MetaConPass > 2)
         return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);

      if (rqptr->rqPathSet.MapUri)
      {
         /* substitute the request URI (raw path plus query) for rewriting */
         rqptr->rqPathSet.MapUri = false;
         zptr = (pptr = PathPtr = PathBuffer+1) + sizeof(PathBuffer)-1;
         for (cptr = rqptr->rqHeader.RequestUriPtr;
              *cptr && pptr < zptr;
              *pptr++ = *cptr++);
         if (pptr >= zptr)
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
         *pptr = '\0';
      }

      /* if 'ScriptPtr' is not to be used then ignore "exec" and "script" */
      if (ScriptPtr[0] &&
          (mrptr->RuleType == MAPURL_RULE_EXEC ||
           mrptr->RuleType == MAPURL_RULE_SCRIPT ||
           mrptr->RuleType == MAPURL_RULE_UXEC))
      {
         if (WatchThisOne)
            MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                              PathPtr, PathOds,
                              MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
         continue;
      }

      if (cptr = MapUrl_GuaranteeAccess (rqptr, PathPtr))
         return (cptr);

      /*****************************************************************/
      /* compare the URL with the template/result in the mapping entry */
      /*****************************************************************/

      if (mrptr->RuleType == MAPURL_RULE_SET ||
          mrptr->RuleType == MAPURL_RULE_PROTECT)
      {
         /*********************/
         /* SET/PROTECT rules */
         /*********************/

         /* cheap check obvious non-/match before using expensive functions */
         tptr = mrptr->TemplatePtr;
         pregptr = &mrptr->RegexPregTemplate;
         pptr = PathPtr;
         if (Config.cfMisc.RegexSyntax && *tptr == REGEX_CHAR) tptr++;
         /* while matching vanilla characters */
         while ((isalnum(*tptr) || *tptr == '/' ||
                 *tptr == '-' || *tptr == '_') &&
                to_lower(*pptr) == to_lower(*tptr))
         {
            pptr++;
            tptr++;
         }
         /* if non-matching vanilla character */
         if (isalnum(*tptr) || *tptr == '/' || *tptr == '-' || *tptr == '_')
         {
            if (WatchThisMatch)
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO !&Z", tptr);
            if (WatchThisOne)
               MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                                 PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
            continue;
         }

         /* if a trailing wildcard then it's a match already! */
         if (SAME2(tptr,'*\0'))
         {
            if (WatchThisMatch)
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES");
         }
         else
         {
            /* expensive comparison of path and template */
            if (!StringMatchAndRegex (rqptr, PathPtr, mrptr->TemplatePtr,
                                      SMATCH_STRING_REGEX, pregptr, NULL))
            {
               /* not matched then straight to next rule */
               if (!WatchThisOne) continue;
               MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                                 PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
               continue;
            }
         }

         if (WATCH_MODULE(WATCH_MOD_MAPURL))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "MATCHED");
      }
      else
      {
         /***************/
         /* other rules */
         /***************/

         /* when mapping VMS to URL then ignore all BUT "pass" & "set" rules */
         if (!MapPathToVms && mrptr->RuleType != MAPURL_RULE_PASS) continue;

         /* if reverse-mapping VMS to URL use the result if available */
         if (!MapPathToVms && mrptr->ResultPtr[0])
         {
            if (MapRootPtr)
            {
               /* if a mapping root is set then eliminate this first */
               cptr = MapRootPtr;
               pptr = PathPtr;
               while (*cptr && *pptr && to_lower(*cptr) == to_lower(*pptr))
               {
                  cptr++;
                  pptr++;
               }
               if (*cptr || !*pptr)
               {
                  /* the path and root do not match */
                  if (!WatchThisMatch) continue;
                  WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO (root) !&Z", cptr);
                  continue;
                }
            }
            cptr = tptr = mrptr->ResultPtr;
            pregptr = NULL;
         }
         else
         {
            cptr = tptr = mrptr->TemplatePtr;
            pregptr = &mrptr->RegexPregTemplate;
         }

         /* cheap check obvious non-/match before using expensive functions */
         if (Config.cfMisc.RegexSyntax && *tptr == REGEX_CHAR) tptr++;
         pptr = PathPtr;
         /* while matching vanilla characters */
         while ((isalnum(*tptr) || *tptr == '/' ||
                 *tptr == '-' || *tptr == '_') &&
                to_lower(*pptr) == to_lower(*tptr))
         {
            pptr++;
            tptr++;
         }
         /* if non-matching vanilla character */
         if (isalnum(*tptr) || *tptr == '/' || *tptr == '-' || *tptr == '_')
         {
            if (WatchThisMatch)
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO !&Z", tptr);
            if (WatchThisOne)
               MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                                 PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
            continue;
         }

         /* if a trailing wildcard then it's a match already! */
         if (SAME2(tptr,'*\0'))
         {
            if (WatchThisMatch)
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES");
            /* simulate the setting of the 'pmatch' data */
            pmatch[0].rm_so = 0;
            pmatch[1].rm_so = pptr - PathPtr;
            while (*pptr) pptr++;
            pmatch[0].rm_eo = pmatch[1].rm_eo = pptr - PathPtr;
            for (idx = 2; idx < REGEX_PMATCH_MAX; idx++)
                pmatch[idx].rm_so = pmatch[idx].rm_eo = -1;
         }
         else
         {
            /* expensive comparison of path and template */
            if (!StringMatchAndRegex (rqptr, PathPtr, cptr,
                                      SMATCH_STRING_REGEX, pregptr, &pmatch))
            {
               /* not matched then straight to next rule */
               if (!WatchThisOne) continue;
               MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                                 PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
               continue;
            }
         }

         /************/
         /* matched! */
         /************/

         /* copy using 'regmatch_t' data, strings into 'WildBuffer' */
         zptr = (sptr = WildBuffer) + sizeof(WildBuffer);
         for (idx = WildStringCount = 0; idx < REGEX_PMATCH_MAX; idx++)
         {
            WildString[idx] = "";
            if (pmatch[idx].rm_so == -1 || pmatch[idx].rm_eo == -1) continue;
            WildString[WildStringCount++] = sptr;
            pptr = PathPtr + pmatch[idx].rm_so;
            cptr = PathPtr + pmatch[idx].rm_eo;
            while (pptr < cptr && sptr < zptr) *sptr++ = *pptr++;
            if (sptr >= zptr) break;
            *sptr++ = '\0';
         }
         if (sptr >= zptr) 
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
         /* the actual number of wildcard/group matched strings */
         if (WildStringCount) WildStringCount--;

         if (WATCH_MODULE(WATCH_MOD_MAPURL))
         {
            WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "MATCHED");
            StringWatchPmatch (PathPtr, &pmatch);
            WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "WILDSTRING");
            for (idx = 1; idx <= WildStringCount; idx++)
               WatchDataFormatted ("!UL. !&Z\n", idx, WildString[idx]);
         }
      }

      if (mrptr->ConditionalPtr[0])
      {
         /***************/
         /* conditional */
         /***************/

         /* when not mapping using a request then ignore conditionals */
         if (rqptr)
         {
            /* if condition not met then continue */
            if (!MapConString ((REQUEST_STRUCT*)rqptr, mrptr, rqpsptr))
            {
               if (WatchThisOne)
                  MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                                    PathPtr, PathOds,
                                    MAPURL_REPORT_MATCH_RULE, SetPathIgnore);
               continue;
            }

            /* report rule and conditional matched */
            if (WatchThisOne)
               MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                                 PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_RULECOND, SetPathIgnore);
         }

         if (WATCH_MODULE_DETAIL)
            WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "CONDITIONAL MATCHED");
      }
      else
      {
         /* report rule, there was no conditional */
         if (WatchThisOne)
            MapUrl_WatchRule (rqptr, mrptr, mclptr, InConfigLevel,
                              PathPtr, PathOds,
                              MAPURL_REPORT_MATCH_RULENOCOND, SetPathIgnore);
      }

      if (mrptr->PathSet)
      {
         /****************/
         /* path SETings */
         /****************/

         mrpsptr = &mrptr->mpPathSet;

         if (mrpsptr->MapSetIgnore)
         {
            /* takes effect immediately */
            SetPathIgnore = true;
            continue;
         }
         else
         if (mrpsptr->MapSetNoIgnore)
            SetPathIgnore = false;
 
         /* ignoring path SETings (except for the above of course ;^) */
         if (SetPathIgnore) continue;

         /***********************/
         /* being ignored? - no */
         /***********************/

         if (mrpsptr->MapRestart)
         {
            MetaConRestartCount++;
            if (WatchThisOne)
               WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                          "RESTART !UL", MetaConRestartCount); 
            if (MetaConRestartCount > MAPURL_RESTART_MAX)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            if (rqptr) rqptr->MetaConRestartCount = MetaConRestartCount;
            MetaConParseReset (MetaGlobalMappingPtr, true);
            continue;
         }

         /* some SETings must be applied in this function! */

         if (!(rqptr->AgentRequestPtr ||
               rqptr->rqCgi.CalloutInProgress))
         {
            if (mrpsptr->MapSetRequest)
               rqpsptr = &PathSetPtr;
            else
            if (mrpsptr->MapSetNoRequest)
            {
               rqpsptr = &EvanescentPathSet;
               if (!EvanescentZeroed)
               {
                  memset (&EvanescentPathSet, 0, sizeof(EvanescentPathSet));
                  EvanescentZeroed = true;
               }
            }
         }

         if (mrpsptr->NoMapEllipsis)
            rqpsptr->MapEllipsis = MapEllipsis = false;
         else
         if (mrpsptr->MapEllipsis)
            rqpsptr->MapEllipsis = MapEllipsis = true;

         if (mrpsptr->MapRootPtr)
         {
            /* empty string resets */
            if (mrpsptr->MapRootLength)
            {
               MapRootPtr = mrpsptr->MapRootPtr;
               MapRootLength = mrpsptr->MapRootLength;
               rqpsptr->MapRootPtr =
                  VmGetHeap (rqptr, mrpsptr->MapRootLength+1);
               memcpy (rqpsptr->MapRootPtr,
                       mrpsptr->MapRootPtr,
                       mrpsptr->MapRootLength+1);
            }
            else
            {
               MapRootPtr = rqpsptr->MapRootPtr = NULL;
               MapRootLength = 0;
            }
         }

         if (mrpsptr->RmsSubChar)
            RmsSubChar = mrpsptr->RmsSubChar;

         if (rqptr->AgentRequestPtr)
         {
            /* bypass evanecsent structure by SETing directly into request! */
            if (MetaConPass == 1 &&
                mrpsptr->ScriptAgentAsPtr)
            {
               /* empty string resets */
               if (mrpsptr->ScriptAgentAsLength)
               {
                  rqptr->rqPathSet.ScriptAgentAsPtr =
                     VmGetHeap (rqptr, mrpsptr->ScriptAgentAsLength+1);
                  memcpy (rqptr->rqPathSet.ScriptAgentAsPtr,
                          mrpsptr->ScriptAgentAsPtr,
                          mrpsptr->ScriptAgentAsLength+1);
               }
               else
                  rqptr->rqPathSet.ScriptAgentAsPtr = NULL;
            }
         }

         if (MetaConPass == 1 &&
             mrpsptr->ScriptAsPtr)
         {
            /* empty string resets */
            if (mrpsptr->ScriptAsLength)
            {
               rqpsptr->ScriptAsPtr =
                  VmGetHeap (rqptr, mrpsptr->ScriptAsLength+1);
               memcpy (rqpsptr->ScriptAsPtr,
                       mrpsptr->ScriptAsPtr,
                       mrpsptr->ScriptAsLength+1);
            }
            else
               rqpsptr->ScriptAsPtr = NULL;
         }

         /* others can be applied in an independent function */

         MapSetPath (rqptr, rqpsptr, mrpsptr);

         /* when mapping VMS to URL then ignore all BUT "pass" rules */
         if (!MapPathToVms && mrptr->RuleType != MAPURL_RULE_PASS) continue;

         /* must be immediately detected and actioned by the calling routine */
         if (rqpsptr->Http2ToHttp11) return ("\0\0");
         
         if (rqpsptr->ClientAddress &&
             rqpsptr == &rqptr->rqPathSet)
         {
            if (rqpsptr->ClientAddress == MAPURL_CLIENT_RESET)
               MapUrl_ResetClientAddress (rqptr);
            else
            if (cptr = MapUrl_SetClientAddress (rqptr))
               return (cptr);
         }
      }

      /***************/
      /* end SETings */
      /***************/

      if (rqpsptr->PathOds)
      {
         /* path file-system has been set, perhaps to turn off ODS-5! */
         if (rqpsptr->PathOds == MAPURL_PATH_ODS_0)
            PathOds = rqptr->PathOds = rqpsptr->PathOds = 0;
         else
            PathOds = rqptr->PathOds = rqpsptr->PathOds; 
      }
      else
      {
         /* set from the $GETDVI volume file-system */
         PathOds = rqptr->PathOds = mrptr->ResultPathOds;
      }
      if (PathOdsPtr) *PathOdsPtr = PathOds;

      if (MapPathToVms)
      {
         /***************************/
         /* mapping from URL to VMS */
         /***************************/

         switch (mrptr->RuleType)
         {
         case MAPURL_RULE_EXEC :
         case MAPURL_RULE_UXEC :

            /*******************/
            /* EXEC/UXEC rule */
            /*******************/

            if (!WildStringCount)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);

            /* first get the script component from the request */
            tptr = mrptr->TemplatePtr;
            zptr = (sptr = ScriptPtr) + SizeOfScriptPtr;
            if (mrptr->RuleType == MAPURL_RULE_UXEC)
            {
               if (!rqpsptr->ScriptAsPtr ||
                   rqpsptr->ScriptAsPtr[0] != '~')
               {
                  /* hmmm, not enabled with a mapping rule!! */
                  return (MsgFor(rqptr,MSG_GENERAL_DISABLED)-1);
               }
               /* first insert the /~username component */
               while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
               if (*tptr) tptr++;
               cptr = WildString[1];
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[2];
            }
            else
            if (SAME2(tptr,'/~'))
            {
               /* mapping a DECnet username based script */
               while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
               while (*tptr == '*') tptr++;
               cptr = WildString[1];
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[2];
            }
            else
            {
               /* mapping a vanilla exec rule */
               cptr = WildString[1];
            }

            while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
            while (*tptr && SAME2(tptr,'**')) tptr++;
            if (SAME2(tptr,'*.'))
            {
               /* EXECing a file type here */
               ExecFileType = true;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               /* now get the script file type (e.g. ".cgi") */
               tptr++;
               while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
            }
            else
            {
               /* EXECing a directory here */
               ExecFileType = false;
               while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            }

            if (sptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *sptr = '\0';

            if (mrptr->RuleType == MAPURL_RULE_UXEC)
            {
               /* "append" the username to the circumflex */
               rqpsptr->ScriptAsPtr = sptr =
                   VmGetHeap (rqptr, strlen(WildString[1])+2);
               *sptr++ = '~';
               for (cptr = WildString[1]; *cptr; *sptr++ = to_upper(*cptr++));
               *sptr = '\0';
            }

            /* now get what it's mapped into (the more complex bit ;^) */
            rptr = mrptr->ResultPtr;

            if (*rptr == '(')
            {
               /* run-time, e.g. exec /plbin/@ (ht_exe:pl.exe)/plbin/@ */
               rptr++;
               zptr = (sptr = RunTimePtr) + SizeOfRunTimePtr;
               while (*rptr && *rptr != ')' && sptr < zptr) *sptr++ = *rptr++;
               if (sptr >= zptr)
                  return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
               *sptr = '\0';
               if (*rptr) rptr++;
            }

            zptr = (sptr = Scratch) + sizeof(Scratch);

            if (mrptr->RuleType == MAPURL_RULE_UXEC)
            {
               /* begin with the mapped user instead of the /~username */
               if (rqpsptr->PathOds)
                  uptr = MapUserName (rqptr, WildString[1], NULL);
               else
               {
                  uptr = MapUserName (rqptr, WildString[1], &PathOds);
                  rqptr->PathOds = PathOds;
                  if (PathOdsPtr) *PathOdsPtr = PathOds;
               }
               if (!uptr[0]) return (uptr);
               /* copy in the now-mapped username */
               while (*uptr && sptr < zptr) *sptr++ = *uptr++;
               /* skip over the equivalent in the result (i.e. "/~*") */
               while (*rptr && *rptr != '*') rptr++;
               if (*rptr) rptr++;
               /* skip the first wildcarded string (username) */
               cptr = WildString[widx=2];
            }
            else
            if (SAME2(mrptr->TemplatePtr,'/~'))
            {
               /*
                  e.g. exec /~@/cgi-bin/@ /0""::/web/user/@/cgi-bin/@
                  i.e. the user part must be prepended to the script part.
               */
               while (*rptr && *rptr != '*' && sptr < zptr)
               {
                  if (*rptr != '\"' || !SAME4(rptr,'\"\"::'))
                  {
                     *sptr++ = *rptr++;
                     continue;
                  }
                  /* mapping a DECnet username based script */
                  *sptr++ = *rptr++;
                  cptr = WildString[1];
                  while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               }
               while (*rptr == '*') rptr++;
               cptr = WildString[1];
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[widx=2];
            }
            else
            {
               /* vanilla script exec */
               if (MapRootPtr)
               {
                  if (WATCHING (rqptr, WATCH_MAPPING))
                     WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                                "ROOT !AZ", MapRootPtr);
                  for (cptr = MapRootPtr;
                       *cptr && sptr < zptr;
                       *sptr++ = *cptr++);
               }
               cptr = WildString[widx=1];
            }

            /* now the leading script-location part of the result */
            while (*rptr && *rptr != '*' && sptr < zptr) *sptr++ = *rptr++;
            while (*rptr == '*') rptr++;

            if (WATCH_MODULE_DETAIL)
               WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "!&Z", cptr);
            if (ExecFileType)
            {
               /* EXECing a file type here, script name (full buffer) */
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[++widx];
               /* now get the script file type (e.g. ".cgi") */
               while (*rptr && *rptr != '*' && sptr < zptr) *sptr++ = *rptr++;
            }
            else
            {
               /* EXECing a directory here, script name */
               while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            }

            if (sptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *sptr = '\0';

            /* generate the VMS script name */
            MapOdsUrlToVms (Scratch, ScriptVmsPtr, SizeOfScriptVmsPtr,
                            RmsSubChar, MapEllipsis, PathOds);

            /* indicate CGIplus script via leading '+' instead of '/' */
            if (mrptr->IsCgiPlusScript && (!RunTimePtr || !RunTimePtr[0]))
               ScriptPtr[0] = '+';

            /* get all including and after the first slash as the new path */
            zptr = (sptr = DerivedPathBuffer) + sizeof(DerivedPathBuffer);
            rqptr->MetaConMappedPtr = sptr;
            /* continue copying from the wildcard string */
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *sptr++ = '\0';
            TotalLength = sptr - DerivedPathBuffer;

            if (WATCH_MODULE_DETAIL)
               WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "E/UXEC !&Z !&Z !&Z",
                          ScriptPtr, ScriptVmsPtr, DerivedPathBuffer);

            if (!rqpsptr->MapEmpty && !DerivedPathBuffer[0])
               return ("\0\0");

            if (rqpsptr->MapOnce)
            {
               /* convert the URL-style to a VMS-style specification */
               MapOdsUrlToVms (DerivedPathBuffer, VmsPtr, SizeOfVmsPtr,
                               RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                             "E/UXEC !&Z !&Z", DerivedPathBuffer, VmsPtr);
               return (DerivedPathBuffer);
            }

            /* restarting at first rule map the path derived from the script */
            MetaConPass++;
            if (rqptr)
            {
               rqptr->MetaConPass = MetaConPass;
               rqptr->MetaConScriptPtr = ScriptPtr;
            }
            *VmsPtr = '\0';
            PathPtr = DerivedPathBuffer;
            RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
            MapEllipsis = rqpsptr->MapEllipsis = false;
            MetaConParseReset (MetaGlobalMappingPtr, true);
            continue;

         case MAPURL_RULE_FAIL :

            /*************/
            /* FAIL rule */
            /*************/

            if (WATCH_MODULE_DETAIL)
               WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "FAIL !&Z", PathPtr);
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_RULE)-1);

         case MAPURL_RULE_MAP :

            /************/
            /* MAP rule */
            /************/

            /* place this in a buffer so not to overwrite original path */
            zptr = (pptr = PathPtr = PathBuffer+1) + sizeof(PathBuffer)-1;
            rqptr->MetaConMappedPtr = pptr;
            widx = 1;
            /* scan through the result string */
            rptr = mrptr->ResultPtr;
            while (*rptr)
            {
               while (*rptr && *rptr != '*' && pptr < zptr) *pptr++ = *rptr++;
               if (!*rptr || pptr >= zptr) break;
               /* a wildcard asterisk, substitute from original path */
               while (*rptr == '*') rptr++;
               if (*rptr == '\'')
               {
                  /* specific wildcard component, e.g. "*'1" */
                  rptr++;
                  if (isdigit(*rptr))
                  {
                     idx = *rptr++ - '0';
                     cptr = WildString[idx];
                  }
                  else
                     cptr = "";
               }
               else
               if (widx < REGEX_PMATCH_MAX)
                  cptr = WildString[widx++];
               else
                  cptr = "";
               while (*cptr && pptr < zptr) *pptr++ = *cptr++;
            }
            if (pptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *pptr = '\0';

            /* continue with the substituted URL mapping */
            if (WATCH_MODULE_DETAIL)
               WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL, "MAP !&Z", PathPtr);
            continue;

         case MAPURL_RULE_PASS :
         case MAPURL_RULE_REDIRECT :
         case MAPURL_RULE_USER :

            /****************************/
            /* PASS/REDIRECT/USER rules */
            /****************************/

            cptr = WildString[widx=1];
            rptr = mrptr->ResultPtr; 
            zptr = (pptr = PathBuffer+1) + sizeof(PathBuffer)-1;

            /* if there is no result to map just pass back the original path */
            if (!*rptr) 
            {
               if (mrptr->RuleType == MAPURL_RULE_PASS && MapRootPtr)
               {
                  if (WATCHING (rqptr, WATCH_MAPPING))
                     WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                                "ROOT !AZ", MapRootPtr);
                  for (cptr = MapRootPtr;
                       *cptr && pptr < zptr;
                       *pptr++ = *cptr++);
                  for (cptr = PathPtr; *cptr && pptr < zptr; *pptr++ = *cptr++);
                  if (pptr >= zptr)
                     return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
                  *pptr = '\0';
                  pptr = PathBuffer;
               }
               else
                  pptr = PathPtr;

               if (*pptr != '/')
               {
                  /* doesn't look like file-system, don't bother translating */
                  return (PathPtr);
               }

               /* convert the URL-style to a VMS-style specification */
               MapOdsUrlToVms (pptr, VmsPtr, SizeOfVmsPtr,
                               RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                              "PASS !&Z !&Z", PathPtr, VmsPtr);
               return (PathPtr);
            }

            /* if redirect rule leave room for the redirection indicator */
            if (mrptr->RuleType == MAPURL_RULE_REDIRECT) pptr++;

            if (mrptr->RuleType == MAPURL_RULE_USER)
            {
               /* begin with the mapped user instead of the /~username */
               if (rqpsptr->PathOds)
                  uptr = MapUserName (rqptr, WildString[1], NULL);
               else
               {
                  uptr = MapUserName (rqptr, WildString[1], &PathOds);
                  rqptr->PathOds = PathOds;
                  if (PathOdsPtr) *PathOdsPtr = PathOds;
               }
               if (!uptr[0]) return (uptr);
               /* copy in the-now mapped username */
               while (*uptr && pptr < zptr) *pptr++ = *uptr++;
               /* skip over the equivalent in the result (i.e. "/~*") */
               while (*rptr && *rptr != '*') rptr++;
               if (*rptr) rptr++;
               /* skip first wildcard string (username) */
               cptr = WildString[widx=2];
            }

            if (mrptr->RuleType == MAPURL_RULE_PASS ||
                mrptr->RuleType == MAPURL_RULE_REDIRECT)
            {
               if (!WildStringCount)
               {
                  if (cptr = SysTrnLnm (mrptr->ResultPtr))
                  {
                     /****************************/
                     /* use logical name content */
                     /****************************/

                     if (WATCHING (rqptr, WATCH_MAPPING))
                        WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                                   "TRLNM !AZ \"!AZ\"", mrptr->ResultPtr, cptr);

                     if (mrptr->RuleType == MAPURL_RULE_PASS)
                     {
                        zptr = (sptr = VmsPtr) + SizeOfVmsPtr;
                        while (*cptr && sptr < zptr) *sptr++ = *cptr++;
                        if (sptr >= zptr)
                           return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
                        *sptr = '\0';

                        zptr = (sptr = PathBuffer+1) + sizeof(PathBuffer)-1;
                        cptr = mrptr->ResultPtr;
                     }
                     else
                     {
                        zptr = (sptr = PathBuffer+1) + sizeof(PathBuffer)-1;
                        /* the redirection indicator */
                        *sptr++ = '\1';
                     }

                     while (*cptr && sptr < zptr) *sptr++ = *cptr++;
                     if (sptr >= zptr)
                        return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
                     *sptr = '\0';
                     return (PathBuffer+1);
                  }
               }
            }

            if (mrptr->RuleType == MAPURL_RULE_PASS)
            {
               /* scan through the result string */
               if (rptr[0] == '\"' || rptr[0] == '\'')
                  if (isdigit(rptr[1]) && isdigit(rptr[2]) &&
                      isdigit(rptr[3]) && (!rptr[4] || rptr[4] == ' '))
                     StatusCodeMapping = true;
                  else
                     StatusCodeMapping = false;
               else
               if (isdigit(rptr[0]) && isdigit(rptr[1]) &&
                   isdigit(rptr[2]) && (!rptr[3] || rptr[3] == ' '))
                  StatusCodeMapping = true;
               else
                  StatusCodeMapping = false;

               if (StatusCodeMapping)
                  while (*rptr && pptr < zptr) *pptr++ = *rptr++;
            }
            else
               StatusCodeMapping = false;

            if (!StatusCodeMapping)
            {                                  
               if (mrptr->RuleType == MAPURL_RULE_PASS && MapRootPtr)
               {
                  if (WATCHING (rqptr, WATCH_MAPPING))
                     WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                                "ROOT !AZ", MapRootPtr);
                  for (cptr = MapRootPtr;
                       *cptr && pptr < zptr;
                       *pptr++ = *cptr++);
               }
               /* map wildcard asterisks */
               while (*rptr)
               {
                  while (*rptr && *rptr != '*' && pptr < zptr)
                     *pptr++ = *rptr++;
                  if (!*rptr) break;
                  /* wildcard asterisk, substitute from the wildcard buffer */
                  while (*rptr == '*') rptr++;
                  if (*rptr == '\'')
                  {
                     /* specific wildcard component, e.g. "*'1" */
                     rptr++;
                     if (isdigit(*rptr))
                     {
                        idx = *rptr++ - '0';
                        cptr = WildString[idx];
                     }
                     else
                        cptr = "";
                  }
                  else
                  if (widx < REGEX_PMATCH_MAX)
                     cptr = WildString[widx++];
                  else
                     cptr = "";
                  if (mrptr->RuleType == MAPURL_RULE_REDIRECT)
                  {
                     /* URL-encode wildcard portions from the path */
                     while (*cptr && pptr < zptr)
                     {
                        for (eptr = FaoUrlEncodeTable[*(unsigned char*)cptr];
                             *eptr && pptr < zptr;
                             *pptr++ = *eptr++);
                        cptr++;
                     }
                  }
                  else
                     while (*cptr && pptr < zptr) *pptr++ = *cptr++;
                  if (pptr >= zptr) break;
               }
            }
            if (pptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *pptr = '\0';

            if (mrptr->RuleType == MAPURL_RULE_PASS ||
                mrptr->RuleType == MAPURL_RULE_USER)
            {
               /* if status code mapping return leading null char */
               if (StatusCodeMapping)
               {
                  PathBuffer[0] = '\0';
                  return (PathBuffer);
               }

               if (PathBuffer[1] != '/')
               {
                  /* doesn't look like file-system, don't bother translating */
                  return (PathBuffer+1);
               }

               /* convert the URL-style to a VMS-style specification */
               MapOdsUrlToVms (PathBuffer+1, VmsPtr, SizeOfVmsPtr,
                               RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                             "PASS !&Z !&Z", PathPtr, VmsPtr);
               return (PathBuffer+1);
            }
            else
            {
               if (WATCH_MODULE_DETAIL)
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                             "REDIRECT !&Z !&Z", PathPtr, VmsPtr);
               /* indicate it's a redirect */
               PathBuffer[1] = '\1';
               return (PathBuffer+1);
            }

         case MAPURL_RULE_SCRIPT :

            /***************/
            /* SCRIPT rule */
            /***************/

            tptr = mrptr->TemplatePtr;
            zptr = (pptr = ScriptPtr) + SizeOfScriptPtr;
            while (*tptr && *tptr != '*' && pptr < zptr) *pptr++ = *tptr++;
            if (pptr > ScriptPtr && pptr[-1] == '/') pptr--;
            if (pptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *pptr = '\0';

            rptr = mrptr->ResultPtr;

            if (*rptr == '(')
            {
               /* run-time, e.g. exec /plbin/@ (ht_exe:pl.exe)/plbin/@ */
               rptr++;
               zptr = (sptr = RunTimePtr) + SizeOfRunTimePtr;
               while (*rptr && *rptr != ')' && sptr < zptr) *sptr++ = *rptr++;
               if (sptr >= zptr)
                  return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
               *sptr = '\0';
               if (*rptr) rptr++;
            }

            zptr = (sptr = Scratch) + sizeof(Scratch);
            if (MapRootPtr)
            {
               if (WATCHING (rqptr, WATCH_MAPPING))
                  WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                             "ROOT !AZ", MapRootPtr);
               for (cptr = MapRootPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
            }
            while (*rptr &&
                   *rptr != '*' &&
                   !SAME2(rptr,'/*') &&
                   sptr < zptr) *sptr++ = *rptr++;
            if (sptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *sptr = '\0';

            /* convert the URL-format script to VMS specification */
            MapOdsUrlToVms (Scratch, ScriptVmsPtr, SizeOfScriptVmsPtr,
                            RmsSubChar, MapEllipsis, PathOds);

            /* indicate CGIplus script via leading '+' instead of '/' */
            if (mrptr->IsCgiPlusScript && (!RunTimePtr || !RunTimePtr[0]))
               ScriptPtr[0] = '+';

            /* get wildcard matched second section of path as new path */
            zptr = (sptr = DerivedPathBuffer) + sizeof(DerivedPathBuffer);
            rqptr->MetaConMappedPtr = sptr;
            if (rqpsptr->MapOnce && MapRootPtr)
            {
               if (WATCHING (rqptr, WATCH_MAPPING))
                  WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                             "ROOT !AZ", MapRootPtr);
               for (cptr = MapRootPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
            }
            while (*rptr && *rptr != '*' && sptr < zptr) *sptr++ = *rptr++;
            cptr = WildString[1];
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '\0';
            if (sptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            TotalLength = sptr - DerivedPathBuffer;

            if (WATCH_MODULE_DETAIL)
               WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                          "SCRIPT !&Z !&Z !&Z",
                          ScriptPtr, ScriptVmsPtr, DerivedPathBuffer);

            if (!rqpsptr->MapEmpty && !DerivedPathBuffer[0])
               return ("\0\0");

            if (rqpsptr->MapOnce)
            {
               /* convert the URL-style to a VMS-style specification */
               MapOdsUrlToVms (DerivedPathBuffer, VmsPtr, SizeOfVmsPtr,
                               RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                             "SCRIPT !&Z !&Z", DerivedPathBuffer, VmsPtr);
               return (DerivedPathBuffer);
            }

            /* restarting at first rule map the path derived from the script */
            MetaConPass++;
            if (rqptr)
            {
               rqptr->MetaConPass = MetaConPass;
               rqptr->MetaConScriptPtr = ScriptPtr;
            }
            *VmsPtr = '\0';
            PathPtr = DerivedPathBuffer;
            RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
            MapEllipsis = rqpsptr->MapEllipsis = false;
            MetaConParseReset (MetaGlobalMappingPtr, true);
            continue;

         case MAPURL_RULE_PROTECT :

            /****************/
            /* PROTECT rule */
            /****************/

            if (MetaConPass == 1)
            {
               /* pass one protects the full path or script component */
               if (mrptr->ResultLength)
               {
                  rqptr->rqAuth.Protect1Ptr =
                     VmGetHeap (rqptr, mrptr->ResultLength+1);
                  rqptr->rqAuth.Protect1Length = mrptr->ResultLength;
                  memcpy (rqptr->rqAuth.Protect1Ptr,
                          mrptr->ResultPtr,
                          mrptr->ResultLength+1);
               }
               else
               {
                  /* empty result string cancels any previous authorization */
                  rqptr->rqAuth.Protect1Ptr = NULL;
                  rqptr->rqAuth.Protect1Length = 0;
               }
            }
            else
            {
               /* pass two protects any path following the script component */
               if (mrptr->ResultLength)
               {
                  rqptr->rqAuth.Protect2Ptr =
                     VmGetHeap (rqptr, mrptr->ResultLength+1);
                  rqptr->rqAuth.Protect2Length = mrptr->ResultLength;
                  memcpy (rqptr->rqAuth.Protect2Ptr,
                          mrptr->ResultPtr,
                          mrptr->ResultLength+1);
               }
               else
               {
                  /* empty result string cancels any previous authorization */
                  rqptr->rqAuth.Protect1Ptr = NULL;
                  rqptr->rqAuth.Protect1Length = 0;
               }
            }

            continue;

         case MAPURL_RULE_SET :

            /************/
            /* SET rule */
            /************/

            /* path SETings have already been applied */
            continue;
         }
      }
      else
      {
         /***************************/
         /* mapping from VMS to URL */
         /***************************/

         /* REVERSE maps a VMS "result" to a "template" :^) */

         cptr = WildString[idx = 1];
         if (MapRootPtr)
         {
            if (WATCHING (rqptr, WATCH_MAPPING))
               WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                          "ROOT !AZ", MapRootPtr);
            while (MapRootLength-- && *cptr) cptr++;
         }
         zptr = (pptr = PathPtr) + SizeOfPathPtr;
         tptr = mrptr->TemplatePtr;
         /* scan through the template string */
         while (*tptr)
         {
            while (*tptr && *tptr != '*' && pptr < zptr) *pptr++ = *tptr++;
            if (!*tptr) break;
            /* a wildcard asterisk, substitute from result path */
            while (*tptr == '*') tptr++;
            if (*cptr) while (*cptr && pptr < zptr) *pptr++ = *cptr++;
            /* three lines fix 05-APR-2018 longstanding bug */
            if (!*tptr) break;
            if (idx < REGEX_PMATCH_MAX) idx++;
            cptr = WildString[idx];
         }
         if (pptr >= zptr)
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
         *pptr = '\0';

         return (PathPtr);
      }
   }

   /***********************/
   /* a mapping not found */
   /***********************/

   if (WatchThisOne)
      WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "END of mapping rules"); 

   if (MapPathToVms)
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_DEFAULT)-1);
   else
      return (MAPURL_NO_REVERSE_PATH);
}

/*****************************************************************************/
/*
Load mapping rules into meta-config structure.
*/ 
 
int MapUrl_ConfigLoad (META_CONFIG **MetaConPtrPtr)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL, "MapUrl_ConfigLoad()");

   status = MetaConLoad (MetaConPtrPtr, CONFIG_MAP_FILE_NAME,
                         &MapUrl_ConfigLoadCallBack, true, true);
   if (*MetaConPtrPtr == MetaGlobalMappingPtr)
   {
      /* server startup/reload */
      MetaConStartupReport (MetaGlobalMappingPtr, "MAP");
      if (VMSnok (status)) exit (status);
   }
   return (status);
}

/*****************************************************************************/
/*
Called by MetaConUnload() to free resources allocated during mapping rule
configuration.
*/ 
 
MapUrl_ConfigUnload (META_CONFIG *mcptr)

{
   int  status;
   MAPPING_META  *mmptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL, "MapUrl_ConfigUnload()");

   if (mcptr->MappingMetaPtr)
   {
      if (mcptr->MappingMetaPtr == MappingMetaPtr)
      {
         memset (MappingMetaPtr, 0, sizeof(MAPPING_META));
         MappingMetaPtr = NULL;
      }
      else
         VmFree (mcptr->MappingMetaPtr, FI_LI);
      mcptr->MappingMetaPtr = NULL;
   }
}

/*****************************************************************************/
/*
Called by MetaConUnload() callback for each line's associated data, basically
to check for a regular expression structure and free it if present, then just
dispose of the line data itself.
*/ 
 
MapUrl_ConfigUnloadLineData (void *LineDataPtr)

{
   int  status;
   MAP_RULE_META  *mrptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapUrl_ConfigUnloadLineData()");

   mrptr = (MAP_RULE_META*)LineDataPtr;
   if (mrptr->RegexPregTemplate.buffer) regfree (&mrptr->RegexPregTemplate);
   VmFree (mrptr, FI_LI);
}

/*****************************************************************************/
/*
For each non-meta-config directive line read by MetaConLoad() this function is
called to parse the line text's contents and to configure the private data
structure associated with each rule.
*/ 

BOOL MapUrl_ConfigLoadCallBack (META_CONFIG *mcptr)

{
   static char  Ods5Disabled [] = "ODS-5 processing CLI disabled",
                Ods5Enabled [] = "ODS-5 processing enabled",
                Ods5NotSupported [] = "ODS-5 not supported",
                ProblemAcceptLang [] =
                   "Set path \'accept=lang\' parameter problem",
                ProblemConditional [] = "Conditional problem",
                ProblemConfused [] = "Cannot understand this one at all",
                ProblemHostNameLookup [] = "Cannot resolve host name !AZ, !&m",
                ProblemNameValue [] = "Set path \'name=value\' pair problem",
                ProblemNotAllowed [] = "Rule cannot be used in [ConfigFile]",
                ProblemOverflow [] = "Storage overflow",
                ProblemProtect [] = "Protect path problem",
                ProblemProxyHeader [] = "Proxy header exceeded maximum",
                ProblemRegex [] = "Regex: !AZ",
                ProblemResultPath [] = "Problem with \'result\' path",
                ProblemResultRequired [] = "Requires a \'result\'",
                ProblemResultStatus [] = "Rule \'result\' cannot be a status",
                ProblemRmsSubstitution [] = "RMS substitution character",
                ProblemSetPath [] = "Set path problem",
                ProblemSpecifiedWildcardCannot [] =
                   "Cannot use \'specified\' wildcards with rule",
                ProblemSpecifiedWildcardMix [] =
                   "Mix of \'specified\' and non-\'specified\' wildcards",
                ProblemSpecifiedWildcardRange [] =
                   "Value of \'specified\' wildcard out-of-range",
                ProblemTemplateRequired [] = "Requires a \'template\'",
                ProblemThrottleValues [] =
                   "Successive dependent throttle values must be larger",
                ProblemWildcardMapping [] = "Wildcard mapping problem",
                ProblemTooManyWildcard [] = "Too many wildcards";

   BOOL  AllowedInConfigFile,
         ConfigProblem,
         IsCgiPlusScript,
         PathSet,
         VirtualServerUnknown;
   int  length, status,
        ConfigFileIndex,
        ResultPathOds,
        RuleType,
        TemplateWildcardCount,
        ResultWildcardCount,
        ResultSpecifiedWildcardCount;
   unsigned short  Length;
   char  *cptr, *sptr, *zptr,
         *ConditionalPtr,
         *ResultPtr,
         *RulePtr,
         *SetPathPtr,
         *TemplatePtr;
   char  Name [256],
         DiskDevice [64+1],
         Value [1024];
   MAPPING_META  *mmptr;
   MAP_RULE_META  *mrptr;
   MAP_SET_META  *mrpsptr;
   METACON_LINE  *mclptr;
   regex_t  RegexPreg;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
   {
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapUrl_ConfigLoadCallBack() !&F !&X",
                 &MapUrl_ConfigLoadCallBack, mcptr);
      if (WATCH_MODULE(WATCH_MOD__DETAIL))
      {
         mclptr = mcptr->ParsePtr;
         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);
      }
   }

   /* get a pointer to the current "line" */
   mclptr = mcptr->ParsePtr;

   /* if this is during server startup/reload set the global service pointer */
   if (mcptr == MetaGlobalMappingPtr)
      mmptr = mcptr->MappingMetaPtr = MappingMetaPtr = &MappingMeta;
   else
   /* if a report then conjure one up through quantum mechanics */
   if (!mcptr->MappingMetaPtr)
      mmptr = mcptr->MappingMetaPtr = VmGet (sizeof(MAPPING_META));
   else
      /* not the first time through */
      mmptr = mcptr->MappingMetaPtr;

   if (mclptr->Token == METACON_TOKEN_PRE)
   {
      /******************/
      /* pre-initialize */
      /******************/

      MapUrlPathOds5 = OdsExtended = false;

      return (true);
   }

   if (mclptr->Token == METACON_TOKEN_POST)
   {
      /****************/
      /* post-process */
      /****************/

      /* if global service pointer, during server startup or reload */
      if (mcptr == MetaGlobalMappingPtr)
      {
         /* if no ODS5 paths then EFS can be disabled */
#ifdef ODS_EXTENDED
         if (CliOdsExtendedDisabled)
         {
            OdsExtended = false;
            MetaConReport (mcptr, METACON_REPORT_INFORM, Ods5Disabled);
         }
         else
#endif /* ODS_EXTENDED */
         if (CliOdsExtendedEnabled || MapUrlPathOds5)
         {
#ifdef ODS_EXTENDED
            if (SysInfo.VersionInteger >= 720)
            {
               OdsExtended = true;
               MetaConReport (mcptr, METACON_REPORT_INFORM, Ods5Enabled);
            }
            else
#endif /* ODS_EXTENDED */
            {
               OdsExtended = false;
               MetaConReport (mcptr, METACON_REPORT_INFORM, Ods5NotSupported);
            }
         }
         else
            OdsExtended = false;

         /* reset the username cache */
         MapUserNameCache (NULL, NULL, NULL, NULL);

         /* initialize the throttled paths */
         ThrottleInit ();
      }

      return (true);
   }

   /***********/
   /* process */
   /***********/

   /* if it's not text/inline then mapping's not interested in it */
   if (mclptr->Token != METACON_TOKEN_TEXT && !mclptr->InlineTextPtr)
      return (true);

   if (mclptr->InlineTextPtr)
      cptr = mclptr->InlineTextPtr;
   else
      cptr = mclptr->TextPtr;
   for (sptr = cptr; *sptr; sptr++);

   /* allocate memory for the structure plus the null terminated strings */
   mrptr = (MAP_RULE_META*)VmGet (sizeof(MAP_RULE_META)+(sptr-cptr+1));

   /* copy to storage the configuration string */
   sptr = &mrptr->Storage;
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';

   /* point to the MAP_RULE_META */
   mrpsptr = &mrptr->mpPathSet;

   AllowedInConfigFile = ConfigProblem = IsCgiPlusScript = PathSet = false;

   /********/
   /* rule */
   /********/

   for (cptr = &mrptr->Storage; *cptr && ISLWS(*cptr); cptr++);

   RulePtr = cptr;
   if (StringSliceValue (&cptr) <= 0)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   IsCgiPlusScript = false;
   if (strsame (RulePtr, "EXEC", -1))
      RuleType = MAPURL_RULE_EXEC;
   else
   if (strsame (RulePtr, "EXEC+", -1))
   {
      RuleType = MAPURL_RULE_EXEC;
      IsCgiPlusScript = true;
   }
   else
   if (strsame (RulePtr, "FAIL", -1))
   {
      RuleType = MAPURL_RULE_FAIL;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RulePtr, "MAP", -1))
   {
      RuleType = MAPURL_RULE_MAP;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RulePtr, "PASS", -1))
   {
      RuleType = MAPURL_RULE_PASS;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RulePtr, "REDIRECT", -1))
   {
      RuleType = MAPURL_RULE_REDIRECT;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RulePtr, "SCRIPT", -1))
      RuleType = MAPURL_RULE_SCRIPT;
   else
   if (strsame (RulePtr, "SCRIPT+", -1))
   {
      RuleType = MAPURL_RULE_SCRIPT;
      IsCgiPlusScript = true;
   }
   else
   if (strsame (RulePtr, "SET", -1))
      RuleType = MAPURL_RULE_SET;
   else
   if (strsame (RulePtr, "UXEC", -1) ||
       strsame (RulePtr, "UEXEC", -1))
      RuleType = MAPURL_RULE_UXEC;
   else
   if (strsame (RulePtr, "UXEC+", -1) ||
       strsame (RulePtr, "UEXEC+", -1))
   {
      RuleType = MAPURL_RULE_UXEC;
      IsCgiPlusScript = true;
   }
   else
   if (strsame (RulePtr, "USER", -1))
      RuleType = MAPURL_RULE_USER;
   else
   if (strsame (RulePtr, "PROTECT", -1))
   {
      RuleType = MAPURL_RULE_PROTECT;
      AllowedInConfigFile = true;
   }
   else
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   /* ensure a conditional is not mistaken for a missing template */
   if (!*cptr || *cptr == '[' || SAME2(cptr,'![')) 
   {
      /* there must be a "template" to map from */
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemTemplateRequired);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   /* extract template (the path the rule is applied to) */
   TemplatePtr = cptr;
   if (StringSliceValue (&cptr) <= 0)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemTemplateRequired);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   /* note the position of an empty string in case none of these! */
   ResultPtr = SetPathPtr = ConditionalPtr = RulePtr + strlen(RulePtr);

   if (RuleType != MAPURL_RULE_SET)
   {
      /*********************/
      /* path mapping rule */
      /*********************/

      /* if this doesn't look like a conditional */
      if (*cptr && *cptr != '[' && !SAME2(cptr,'!['))
      {
         ResultPtr = cptr;
         if (StringSliceValue (&cptr) <= 0)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
            VmFree (mrptr, FI_LI);
            return (true);
         }
      }
   }

   /* if it doesn't look like a conditional then assume it's SET directives */
   if (*cptr && *cptr != '[' && !SAME2(cptr,'!['))
   {
      /****************/
      /* path SETings */
      /****************/

      while (*cptr)
      {
         /* break if this looks like a conditional */
         if (*cptr == '[' || SAME2(cptr,'![')) break;

         SetPathPtr = cptr;
         if ((length = StringSliceValue (&cptr)) <= 0)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemSetPath);
            VmFree (mrptr, FI_LI);
            return (true);
         }

         if (!MapSetConfig (SetPathPtr, TemplatePtr,
                            mcptr, mrpsptr,
                            &AllowedInConfigFile))
         {
            /* problem with SETing */
            VmFree (mrptr, FI_LI);
            return (true);
         }

         PathSet = true;
      }
   }

   if (RuleType == MAPURL_RULE_SET && !PathSet)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemSetPath);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   if (RuleType == MAPURL_RULE_PROTECT)
   {
      /****************/
      /* protect rule */
      /****************/

      /*
         PROTECT rules are always added to the mapping database, whether
         correct or not, so that even when incorrect the path is still
         access controlled (and in an incorrect case always denied!)
      */
      sptr = AuthConfigParseProtectRule (NULL, ResultPtr, strlen(ResultPtr));
      if (sptr)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, sptr);
         ConfigProblem = true;
      }
      AuthProtectRule = true;
   }

   /* check for optional mapping conditional(s) */
   if (*cptr == '[' || SAME2(cptr,'!['))
   {
      /***************/
      /* conditional */
      /***************/

      ConditionalPtr = cptr;
      while (*cptr == '[' || SAME2(cptr,'!['))
      {
         while (*cptr == '[' || *cptr == '!') cptr++;
         while (*cptr && *cptr != ']')
         {
            while (ISLWS(*cptr)) cptr++;
            sptr = cptr;
            while (*cptr && *cptr != ':' && !ISLWS(*cptr) && *cptr != ']')
            {
               if (*cptr == '\\') cptr++;
               *cptr = to_upper(*cptr);
               if (*cptr) cptr++;
            }
            /* basic check of conditional rule */
            if (*sptr == '!') sptr++;
            if ((!SAME2(sptr,'AC') &&
                 !SAME2(sptr,'AL') &&
                 !SAME2(sptr,'AS') &&
                 !SAME2(sptr,'CA') &&
                 !SAME2(sptr,'CK') &&
                 !SAME2(sptr,'DR') &&
                 !SAME2(sptr,'EX') &&
                 !SAME2(sptr,'FO') &&
                 !SAME2(sptr,'HH') &&
                 !SAME2(sptr,'HM') &&
                 !SAME2(sptr,'HO') &&
                 !SAME2(sptr,'ME') &&
                 !SAME2(sptr,'MP') &&
                 !SAME2(sptr,'NO') &&
                 !SAME2(sptr,'PA') &&
                 !SAME2(sptr,'PI') &&
                 !SAME2(sptr,'QS') &&
                 !SAME2(sptr,'RC') &&
                 !SAME2(sptr,'RF') &&
                 !SAME2(sptr,'RQ') &&
                 !SAME2(sptr,'RU') &&
                 !SAME2(sptr,'SC') &&
                 !SAME2(sptr,'SN') &&
                 !SAME2(sptr,'SP') &&
                 !SAME2(sptr,'ST') &&
                 !SAME2(sptr,'UA') &&
                 !SAME2(sptr,'VS') &&
                 !SAME2(sptr,'XF')) ||
                sptr[2] != ':')
            {
               /***********************/
               /* conditional problem */
               /***********************/

               MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConditional);
               VmFree (mrptr, FI_LI);
               return (true);
            }

            while (*cptr && !ISLWS(*cptr) && *cptr != ']')
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) cptr++;
            }
            while (ISLWS(*cptr)) cptr++;
         }
         if (*cptr == ']') cptr++;
         while (ISLWS(*cptr)) cptr++;
      }
      /* terminate in the white-space following the conditional(s) */
      if (*cptr) *cptr = '\0';
   }

   /**********************/
   /* allowed rule check */
   /**********************/

   if (mclptr->MetaFileType == METACON_TYPE_CONFIG)
   {
      if (!AllowedInConfigFile)
      {
         MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemNotAllowed);
         VmFree (mrptr, FI_LI);
         return (true);
      }
   }

   /***************************/
   /* rule consistency checks */
   /***************************/

   if (mrpsptr->RmsSubChar &&
       !isalnum(mrpsptr->RmsSubChar) && mrpsptr->RmsSubChar != '$' &&
       mrpsptr->RmsSubChar != '-' && mrpsptr->RmsSubChar != '_')
   {
      MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemRmsSubstitution);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   TemplateWildcardCount = 0;
   cptr = TemplatePtr;
   while (*cptr)
   {
      if (*cptr++ != '*') continue;
      TemplateWildcardCount++;
      /* contiguous count as one! */
      while (*cptr == '*') cptr++;
   }

   if (TemplateWildcardCount > REGEX_PMATCH_MAX)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemTooManyWildcard);
      VmFree (mrptr, FI_LI);
      return (true);
   }

   memset (&RegexPreg, 0, sizeof(RegexPreg));
   if (Config.cfMisc.RegexSyntax && *TemplatePtr == REGEX_CHAR)
   {
      cptr = StringRegexCompile (TemplatePtr+1, &RegexPreg);
      if (cptr)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemRegex, cptr);
         VmFree (mrptr, FI_LI);
         return (true);
      }
   }

   ResultWildcardCount = ResultSpecifiedWildcardCount = 0;
   cptr = ResultPtr;
   while (*cptr)
   {
      if (*cptr++ != '*') continue;
      ResultWildcardCount++;
      /* contiguous count as one! */
      while (*cptr == '*') cptr++;
      if (*cptr != '\'') continue;
      ResultSpecifiedWildcardCount++;
      cptr++;
      if (*cptr < '1' || *cptr > '9')
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        ProblemSpecifiedWildcardRange);
         VmFree (mrptr, FI_LI);
         return (true);
      }
   }

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "!&Z !&Z !&Z !UL !UL !UL",
                 TemplatePtr, ResultPtr, ConditionalPtr,
                 TemplateWildcardCount, ResultWildcardCount,
                 ResultSpecifiedWildcardCount);

   ResultPathOds = 0;

   switch (RuleType)
   {
      case MAPURL_RULE_SCRIPT :

         if (!ResultPtr[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultRequired);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (isdigit(ResultPtr[0]))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultStatus);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (ResultSpecifiedWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemSpecifiedWildcardCannot);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (ResultWildcardCount != TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemWildcardMapping);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         break;

      case MAPURL_RULE_EXEC :
      case MAPURL_RULE_UXEC :

         if (!ResultPtr[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultRequired);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (isdigit(ResultPtr[0]))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultStatus);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (ResultSpecifiedWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemSpecifiedWildcardCannot);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (!ResultWildcardCount ||
             !TemplateWildcardCount ||
             ResultWildcardCount != TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemWildcardMapping);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         break;

      case MAPURL_RULE_FAIL :
         break;

      case MAPURL_RULE_PASS :

         if (ResultSpecifiedWildcardCount &&
             ResultSpecifiedWildcardCount != ResultWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_WARNING,
                           ProblemSpecifiedWildcardMix);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (((ResultPtr[0] == '\"' || ResultPtr[0] == '\'') &&
              !isdigit(ResultPtr[0]) &&
              ResultWildcardCount > TemplateWildcardCount) ||
             ((ResultPtr[0] != '\"' && ResultPtr[0] != '\'') &&
              !isdigit(ResultPtr[0]) &&
              ResultWildcardCount > TemplateWildcardCount))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemWildcardMapping);
            VmFree (mrptr, FI_LI);
            return (true);
         }

#ifdef ODS_EXTENDED

         /* determine the on-disk structure */
         zptr = (sptr = DiskDevice) + sizeof(DiskDevice)-1;
         if (ResultPtr[0])
            cptr = ResultPtr;
         else
            cptr = TemplatePtr;
         if (*cptr == '/') cptr++;
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr < zptr) *sptr++ = ':';
         *sptr = '\0';
         ResultPathOds = OdsVolumeStructure (DiskDevice);

#endif /* ODS_EXTENDED */

         break;

      case MAPURL_RULE_MAP :
      case MAPURL_RULE_REDIRECT :
      case MAPURL_RULE_USER :

         if (!ResultPtr[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultRequired);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (isdigit(ResultPtr[0]))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultStatus);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (ResultSpecifiedWildcardCount &&
             ResultSpecifiedWildcardCount != ResultWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_WARNING,
                           ProblemSpecifiedWildcardMix);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         if (ResultWildcardCount > TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemWildcardMapping);
            VmFree (mrptr, FI_LI);
            return (true);
         }
         break;

      case MAPURL_RULE_PROTECT :
      case MAPURL_RULE_SET :

         break;

      default :
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
         VmFree (mrptr, FI_LI);
         return (true);
   }

   /*****************************/
   /* throttle integrity checks */
   /*****************************/

   if (mrpsptr->ThrottleSet &&
       !mrpsptr->ThrottleNoSet)
   {
      /* throttle=from,to,resume,busy,t/o-resume,t/o-busy */
      if ((mrpsptr->ThrottleTo &&
           mrpsptr->ThrottleTo <= mrpsptr->ThrottleFrom) ||
          (mrpsptr->ThrottleResume && !mrpsptr->ThrottleTo) ||
          (mrpsptr->ThrottleResume &&
           mrpsptr->ThrottleResume <= mrpsptr->ThrottleTo) ||
          (mrpsptr->ThrottleBusy && mrpsptr->ThrottleTo &&
           mrpsptr->ThrottleBusy <= mrpsptr->ThrottleTo) ||
          (mrpsptr->ThrottleBusy && mrpsptr->ThrottleResume &&
           mrpsptr->ThrottleBusy <= mrpsptr->ThrottleResume))
      {
         MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemThrottleValues);
         VmFree (mrptr, FI_LI);
         return (true);
      }
   }

   /************************************/
   /* script=param=(name=value) checks */
   /************************************/

   if (mrpsptr->ScriptParamsPtr)
   {
      /* these will generally end up as DCL symbols for a script */
      cptr = mrpsptr->ScriptParamsPtr;
      for (;;)
      {
         status = StringParseNameValue (&cptr, true,
                                        Name, sizeof(Name),
                                        Value, sizeof(Value));
         if (VMSnok (status)) break;
      }
      if (status != SS$_ENDOFFILE)
      {
         MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemNameValue);
         VmFree (mrptr, FI_LI);
         return (true);
      }
   }

   /*********************************/
   /* add the rule as the line data */
   /*********************************/

   /* set the meta-config data */
   mclptr->LineDataPtr = mrptr;
   mclptr->ConfigProblem = ConfigProblem;

   /* set the rule data */
   mrptr->MetaConNumber = mclptr->Number;
   mrptr->RuleType = RuleType;
   mrptr->TemplatePtr = TemplatePtr;
   mrptr->ResultPtr = ResultPtr;
   mrptr->ResultLength = strlen(ResultPtr);
   mrptr->ConditionalPtr = ConditionalPtr;
   mrptr->PathSet = PathSet;
   mrptr->IsCgiPlusScript = IsCgiPlusScript;

#ifdef ODS_EXTENDED

   if (ResultPathOds)
   {
      /* indicate that this path is located on an ODS-5 volume */
      mrptr->ResultPathOds = ResultPathOds;
      /* indicate that at least one mapped volume is ODS-5 */
      if (ResultPathOds == MAPURL_PATH_ODS_5) MapUrlPathOds5 = true;
   }

#endif /* ODS_EXTENDED */

   if (RegexPreg.buffer)
   {
      /* the template was a regular expression, create a compiled version */
      StringRegexCompile (TemplatePtr+1, &mrptr->RegexPregTemplate);
      /* free the temporary compiled version */
      regfree (&RegexPreg);
   }

   if (mrpsptr->ThrottleSet &&
       !mrpsptr->ThrottleNoSet)
   {
      mrpsptr->ThrottleIndex = mmptr->ThrottleIndex++;
      if (mmptr->ThrottleIndex > mmptr->ThrottleTotal)
         mmptr->ThrottleTotal = mmptr->ThrottleIndex;
   }

   if (mrpsptr->ProxyBindIpAddressLength)
   {
      status = TcpIpStringToAddress (mrpsptr->ProxyBindIpAddressPtr,
                                     &mrpsptr->ProxyBindIpAddress);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Could not get bind address for !AZ",
                        mrpsptr->ProxyBindIpAddressPtr);
   }

   if (mrpsptr->ProxyChainHostPortLength)
   {
      IPADDRESS_ZERO4 (&mrpsptr->ProxyChainIpAddress) 
      status = NetHostNameLookup (mrpsptr->ProxyChainHostPortPtr,
                                  0, NULL, NULL, NULL,
                                  &mrpsptr->ProxyChainIpAddress,
                                  &mrpsptr->ProxyChainPort);
      if (!mrpsptr->ProxyChainPort)
         mrpsptr->ProxyChainPort = DEFAULT_HTTP_PROXY_PORT;
      if (VMSnok (status))
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemHostNameLookup, 
                        mrpsptr->ProxyChainHostPortPtr, status);
         /* make sure the connection can't succeed */
         IPADDRESS_SET_UNUSABLE (&mrpsptr->ProxyChainIpAddress)
         mrpsptr->ProxyChainPort = 0;
      }
   }

   /* note that we've got an extension-method rule */
   if (mrpsptr->MapExtensionMethod) MapUrlExtensionMethod = true;

   if (WATCH_MODULE_DETAIL)
      WatchDataFormatted ("!&X !UL !&Z !&Z (!UL) !&B !&Z\n",
         mrptr, mrptr->RuleType, mrptr->TemplatePtr,
         mrptr->ResultPtr, mrptr->ResultPathOds, mrptr->PathSet,
         mrptr->ConditionalPtr);

   return (true);
}

/*****************************************************************************/
/*
Given the current document's URL path and the specified document's URL path, 
generate a resultant URL path based on whether the specified document's path 
is absolute or relative the the current document's path.  Insufficient space to
contain the result path will not crash the function and the result will be set
to an empty string.
*/ 

int MapUrl_VirtualPath
(
char *CurrentPath,
char *DocumentPath,
char *ResultPath,
int ResultPathSize
)
{
   char  *cptr, *dptr, *sptr, *tptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapUrl_VirtualPath() !&Z !&Z !UL",
                 CurrentPath, DocumentPath, ResultPathSize);

   if (*(dptr = DocumentPath) == '/' || strstr (DocumentPath, "//")) 
   {
      /*****************/
      /* absolute path */
      /*****************/

      strzcpy (ResultPath, dptr, ResultPathSize);
      zptr = (sptr = ResultPath) + ResultPathSize;
      while (*dptr && sptr < zptr) *sptr++ = *dptr++;
      if (sptr >= zptr) return (-1);
      *sptr = '\0';
      return (sptr - ResultPath);
   }

   /* step over any "relative to this directory" syntax ("./") */
   if (dptr[0] == '.' && dptr[1] == '/') dptr += 2;
   if (*dptr != '.')
   {
      /*****************/
      /* inferior path */
      /*****************/

      zptr = (sptr = tptr = ResultPath) + ResultPathSize;
      cptr = CurrentPath;
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '/') tptr = sptr+1;
         *sptr++ = *cptr++;
      }
      sptr = tptr;
      while (*dptr && sptr < zptr) *sptr++ = *dptr++;
      if (sptr >= zptr) return (-1);
      *sptr = '\0';
      return (sptr - ResultPath);
   }

   /*****************/
   /* relative path */
   /*****************/

   zptr = (sptr = tptr = ResultPath) + ResultPathSize;
   cptr = CurrentPath;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '/') tptr = sptr;
      *sptr++ = *cptr++;
   }
   sptr = tptr;
   /* loop, stepping back one level for each "../" in the document path */
   while (dptr[0] == '.' && dptr[1] == '.' && dptr[2] == '/')
   {
      dptr += 3;
      if (sptr > ResultPath) sptr--;
      while (sptr > ResultPath && *sptr != '/') sptr--;
   }
   if (sptr > ResultPath && sptr < zptr)
      sptr++;
   else
   if (sptr < zptr)
      *sptr++ = '/';
   while (*dptr && sptr < zptr) *sptr++ = *dptr++;
   if (sptr >= zptr) return (-1);
   *sptr = '\0';
   return (sptr - ResultPath);
}

/*****************************************************************************/
/*
Modify the client's IP address data using the source indicated by the path
setting.  Used to support a transparent load balancer/accelerator/proxy
upstream from the server while retaining the original client address data. 
Return a NULL pointer to indicate success.  Return an error string if it fails
for some reason (e.g. header is not present).  A fail should abort mapping and
report the error.
*/ 
 
char* MapUrl_SetClientAddress (REQUEST_STRUCT *rqptr)

{
   int  ClientAddress;
   char  *cptr, *hptr, *sptr, *zptr;
   char  strbuf [256];
   CLIENT_STRUCT  *clptr;
   IPADDRESS  IpAddress;
   REQUEST_PATHSET  *rqpsptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_SetClientAddress()");

   /* do not reproduce again if multiple client data mappings */
   if (rqptr->ClientPtr->SetClientAddress)
      clptr = rqptr->ClientPtr;
   else
   {
      /* reproduce the existing client data and then use that */
      clptr = (CLIENT_STRUCT*)VmGetHeap (rqptr, sizeof(CLIENT_STRUCT));
      memcpy (clptr, &rqptr->ClientPtr, sizeof(CLIENT_STRUCT));
      rqptr->ClientResetPtr = rqptr->ClientPtr;
      rqptr->ClientPtr = clptr;
   }

   rqpsptr = &rqptr->rqPathSet;

   ClientAddress = rqpsptr->ClientAddress;
   rqpsptr->ClientAddress = 0;

   if (ClientAddress == clptr->SetClientAddress &&
       ClientAddress != MAPURL_CLIENT_LITERAL)
   {
      if (WATCHING (rqptr, WATCH_MAPPING))
         WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                    "CLIENT already set to this!!");
      return (NULL);
   }

   if (ClientAddress == MAPURL_CLIENT_XFORWARDEDFOR ||
       ClientAddress == MAPURL_CLIENT_IF_XFORWARDEDFOR)
   {
      cptr = rqptr->rqHeader.XForwardedForPtr;
      hptr = "X-Forwarded-For:";
   }
   else
   if (ClientAddress == MAPURL_CLIENT_FORWARDED ||
       ClientAddress == MAPURL_CLIENT_IF_FORWARDED)
   {
      cptr = rqptr->rqHeader.ForwardedPtr;
      hptr = "Forwarded:";
   }
   else
   if (ClientAddress == MAPURL_CLIENT_LITERAL)
   {
      /* really just for ease of testing */
      cptr = rqpsptr->ClientAddressLiteralPtr;
      hptr = "X-Literal:";
   }
   else
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
   }

   if (!cptr)
   {
      if (WATCHING (rqptr, WATCH_MAPPING))
         WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "CLIENT no !AZ", hptr);
      if (ClientAddress == MAPURL_CLIENT_IF_XFORWARDEDFOR ||
          ClientAddress == MAPURL_CLIENT_IF_FORWARDED) return (NULL);
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_RULE)-1);
   }

   /* parse out the leading (IP address) string representing the client */
   zptr = (sptr = strbuf) + sizeof(strbuf)-1;
   while (ISLWS(*cptr)) cptr++;
   if (MATCH4(cptr,"for=")) cptr += 4;
   if (*cptr == '\"')
      for (cptr++; *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++);
   else
      while (*cptr && *cptr != ',' && !ISLWS(*cptr) && sptr < zptr)
         *sptr++ = *cptr++;
   *sptr = '\0';

   if (VMSnok (TcpIpStringToAddress (strbuf, &IpAddress)))
   {
      if (WATCHING (rqptr, WATCH_MAPPING))
         WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                    "CLIENT address error !AZ !AZ", hptr, strbuf);
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_RULE)-1);
   }

   /* when not multiple client data mapping */
   if (!clptr->SetClientAddress)
   {
      /* buffer the TCP connection data (see MapUrl_ResetClientData()) */
      IPADDRESS_COPY (&clptr->UpstreamIpAddress, &clptr->IpAddress);
      strzcpy (clptr->UpstreamIpAddressString, &clptr->IpAddressString,
               sizeof(clptr->UpstreamIpAddressString));
      strzcpy (clptr->UpstreamHostName, clptr->Lookup.HostName,
               sizeof(clptr->UpstreamHostName));
      clptr->UpstreamHostNameLength = clptr->Lookup.HostNameLength;
   }

   /* rewrite the client address data */
   clptr->SetClientAddress = ClientAddress;
   IPADDRESS_COPY (&clptr->IpAddress, &IpAddress);
   /* recreate a "clean" IP address string from the IP address above */
   cptr = TcpIpAddressToString (&IpAddress, 0);
   strzcpy (clptr->IpAddressString, cptr,
            sizeof(clptr->IpAddressString));
   strzcpy (clptr->Lookup.HostName, cptr,
            sizeof(clptr->Lookup.HostName));
   clptr->Lookup.HostNameLength = strlen(cptr);

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "!AZ !&I !AZ !AZ -> !&I !AZ !AZ",
                 hptr, &clptr->UpstreamIpAddress,
                       clptr->UpstreamIpAddressString,
                       clptr->UpstreamHostName,
                       &clptr->IpAddress,
                       &clptr->IpAddressString,
                       clptr->Lookup.HostName);

   if (WATCHING (rqptr, WATCH_MAPPING))
      WatchThis (WATCHITM(rqptr), WATCH_MAPPING, "CLIENT !AZ !AZ", hptr, cptr);

   if (WATCH_NOT_ONE_SHOT(Watch.Category))
      WatchFilterClientService (rqptr);

   return (NULL);
}

/*****************************************************************************/
/*
Undo the client address mapping performed by MapUrl_SetClientAddress() above by
simply pointing back using the saved original.
*/ 
 
void MapUrl_ResetClientAddress (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_ResetClientAddress()");

   rqptr->ClientPtr = rqptr->ClientResetPtr;
}

/*****************************************************************************/
/*
Guarantee access to certain paths under certain conditions.
*/ 

char* MapUrl_GuaranteeAccess 
(
REQUEST_STRUCT *rqptr,
char *PathBeingMapped
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_GuaranteeAccess() !&Z", PathBeingMapped);

   if (AuthPromiscuous ||
       HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
   {
      /* guarantee access to the administration menu */
      if (!strsame (PathBeingMapped, HTTPD_ADMIN,
                    sizeof(HTTPD_ADMIN)-1) &&
          !strsame (PathBeingMapped, WASD_ROOT_LOCAL,
                    sizeof(WASD_ROOT_LOCAL)-1) &&
          !strsame (PathBeingMapped, HT_ROOT_LOCAL,
                    sizeof(HT_ROOT_LOCAL)-1))
         return (NULL);

      if (WATCHING (rqptr, WATCH_MAPPING))
          WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                     "!AZ guarantees access to !AZ",
                     AuthPromiscuous ? "PROMISCUOUS" : "SKELKEY",
                     PathBeingMapped);

      return (PathBeingMapped);
   }

   return (NULL);
}

/*****************************************************************************/
/*
A server administration report on the server's mapping rules. This function
just wraps the reporting function, loading a temporary database if necessary
for reporting from the rule file.
*/

MapUrl_Report
(
REQUEST_STRUCT *rqptr,
BOOL UseServerDatabase,
char *VirtualService
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_Report() !UL !&Z",
                 UseServerDatabase, VirtualService);

   if (UseServerDatabase)
   {
      /* use mapping rules already loaded into the server */
      MapUrl_ReportNow (rqptr, true, MetaGlobalMappingPtr, VirtualService);
   }
   else
   {
      /* load temporary set of rules from mapping file */
      status = MapUrl_ConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         MapUrl_ReportNow (rqptr, false, mcptr, VirtualService);
      MetaConUnload (&mcptr, &MapUrl_ConfigUnloadLineData);
   }

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Return a report on the HTTPd server's mapping rules ... now!
*/

MapUrl_ReportNow
(
REQUEST_STRUCT *rqptr,
BOOL UseServerDatabase,
META_CONFIG *mcptr,
char *VirtualService
)
{
   static char  BeginRules [] =
"<p><h3><u>Mapping Rules</u></h3>\n";

   static char  BeginTable [] =
"<p><table class=\"lftlft\">\n\
<tr><td><pre>";

   static char  CommentFao [] = "!AZ<span class=\"incomment\">!AZ</span>\n";

   static char  NutherFileFao [] = "!AZ<span class=\"include\">!AZ</span>\n";

   static char  RuleFao [] =
"!AZ!&?<strike>\r\r<b>!AZ</b>  !&;AZ!&@!&@!&@!&?</strike>\r\r\n";

   static char  NonRuleFao [] =
"!AZ!&?<strike>\r\r<b>!#AZ</b>  !&;AZ!&?</strike>\r\r\n";

   static char  ProblemFao [] = "!AZ<strike>!&;AZ</strike>\n";

   static char  RuleIncludeFao [] = "!AZ<span class=\"include\">!AZ</span>\n";

   static char  RuleVirtualFao [] =
"!AZ!&?<strike>\r\r<b>[[!&;AZ]]</b>!&?</strike>\r\r\n";

   static char  RuleImplicitVirtualFao [] =
"!AZ<b>[[*:*]]</b>\n";

   static char  VersionFao [] = "!AZ[[!AZ]]\n";

   static char  UserMappingFao [] =
"!AZ\
<b>----</b>\
</pre>\n\
</td></tr>\n\
</table>\n\
<p><h3><u>User Mapping Cache</u></h3>\n\
<p><table class=\"lftlft\">\n\
<tr>\
<th></th>\
<th class=\"sbttl\">Name</th>\
<th class=\"sbttl\">Path</th>\
<th class=\"sbttl\">ODS</th>\
<th class=\"sbttl\">Hits</th>\
<th class=\"sbttl\">Last</th>\
<th class=\"sbttl\">Reuse</th>\
</tr>\n\
<tr style=\"height:1.2em;\"></tr>\n";

   static char  EntryFao [] =
"<tr!AZ>\
<th class=\"targht\">!4ZL</th>\
<td>!AZ&nbsp;&nbsp;</td>\
<td>!AZ&nbsp;&nbsp;</td>\
<td>!AZ&nbsp;&nbsp;</td>\
<td>!UL&nbsp;&nbsp;</td>\
<td><nobr>!20%D&nbsp;&nbsp;</nobr></td>\
<td>!UL</td>\
</tr>\n";

   static char EmptyUserMappingFao [] =
"<tr class=\"hlght\"><th>0000</th><td colspan=\"6\"><i>empty</i></td><tr>\n";

   static char  EndPageFao [] =
"</table>\n\
</div>\n\
</body>\n\
</html>\n";

   BOOL  FinalRule,
         ThisVirtualService;
   int  status,
        EntryCount,
        InConfigLevel;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   unsigned short  Length;
   char  *cptr;
   char  Buffer [MAP_BUFFER_SIZE],
         RmsSubChar [2],
         TimeString [32];
   LIST_ENTRY  *leptr;
   MAP_RULE_META  *mrptr;
   MAP_URL_USER_ENTRY  *ucptr;
   METACON_LINE  *mclptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_ReportNow() !&B !&Z",
                 UseServerDatabase, VirtualService);

   AdminPageTitle (rqptr, "Path Mapping");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalMappingPtr);
   AdminMetaConSource (rqptr, mcptr, MetaGlobalMappingPtr);

   /*****************/
   /* mapping rules */
   /*****************/

   status = FaolToNet (rqptr, BeginRules, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   AdminVirtualServiceForm (rqptr, ADMIN_REPORT_MAPPING,
                            VirtualService, UseServerDatabase);

   status = FaolToNet (rqptr, BeginTable, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   status = SS$_NORMAL;
   EntryCount = InConfigLevel = 0;
   FinalRule = false;
   ThisVirtualService = true;

   MetaConParseReset (mcptr, true);
   while (mclptr = MetaConParseRaw (mcptr))
   {
      if (mclptr->Token == METACON_TOKEN_INCLUDE)
      {
         vecptr = FaoVector;
         *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
         *vecptr++ = mclptr->IncludeFilePtr;
         status = FaolToNet (rqptr, RuleIncludeFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         continue;
      }

      if (mclptr->Token == METACON_TOKEN_COMMENT)
      {
         if (SAME4(mclptr->TextPtr, '!#+#'))
         {
            /* from InConfigLoad() add a level */
            vecptr = FaoVector;
            *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
            *vecptr++ = mclptr->TextPtr + 5;
            status = FaolToNet (rqptr, NutherFileFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
            InConfigLevel++;
         }
         else
         if (SAME4(mclptr->TextPtr, '!#-#'))
         {
            /* from InConfigLoad() remove a level */
            if (InConfigLevel) InConfigLevel--;
         }
         else
         {
            vecptr = FaoVector;
            *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
            *vecptr++ = mclptr->TextPtr;
            status = FaolToNet (rqptr, CommentFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }
         continue;
      }

      if (mclptr->Token == METACON_TOKEN_SERVICE)
      {
         /* if filtering on a virtual service */
         if (VirtualService[0] &&
             !StringMatch (rqptr, VirtualService, mclptr->TextPtr))
         {
            ThisVirtualService = false;
            continue;
         }
         ThisVirtualService = true;
         vecptr = FaoVector;
         *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
         *vecptr++ = mclptr->ConfigProblem || FinalRule;
         *vecptr++ = mclptr->TextPtr;
         *vecptr++ = mclptr->ConfigProblem || FinalRule;
         status = FaolToNet (rqptr, RuleVirtualFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         continue;
      }
      else
      if (mclptr->Number == 1)
      {
         vecptr = FaoVector;
         *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
         status = FaolToNet (rqptr, RuleImplicitVirtualFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }

      if (!ThisVirtualService) continue;
      EntryCount++;

      if (mclptr->Token != METACON_TOKEN_TEXT)
      {
         for (cptr = mclptr->TextPtr; *cptr && !ISLWS(*cptr); cptr++);
         vecptr = FaoVector;
         *vecptr++ = MetaConCounter (mclptr, InConfigLevel);

         if (mclptr->Token == METACON_TOKEN_VERSION)
         {
            *vecptr++ = mclptr->TextPtr;
            status = FaolToNet (rqptr, VersionFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
            continue;
         }

         *vecptr++ = mclptr->ConfigProblem || FinalRule ||
                     (mclptr->InlineTextPtr && !mclptr->LineDataPtr);
         *vecptr++ = cptr - mclptr->TextPtr;
         *vecptr++ = mclptr->TextPtr;
         while (*cptr && ISLWS(*cptr)) cptr++;
         *vecptr++ = cptr;
         *vecptr++ = mclptr->ConfigProblem || FinalRule ||
                     (mclptr->InlineTextPtr && !mclptr->LineDataPtr);
         status = FaolToNet (rqptr, NonRuleFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         continue;
      }

      if (!(mrptr = mclptr->LineDataPtr))
      {
         vecptr = FaoVector;
         *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
         *vecptr++ = mclptr->TextPtr;
         status = FaolToNet (rqptr, ProblemFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         continue;
      }

      if (WATCHMOD (rqptr, WATCH_MODULE_DETAIL))
         WatchDataFormatted ("!&X !UL !&Z !&Z !&Z\n",
            mrptr, mrptr->RuleType, mrptr->TemplatePtr,
            mrptr->ResultPtr, mrptr->ConditionalPtr);

      vecptr = FaoVector;
      *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
      *vecptr++ = mclptr->ConfigProblem || FinalRule;

      if (mrptr->RuleType == MAPURL_RULE_EXEC)
      {
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "exec+";
         else
            *vecptr++ = "exec";
      }
      else
      if (mrptr->RuleType == MAPURL_RULE_FAIL)
         *vecptr++ = "fail";
      else
      if (mrptr->RuleType == MAPURL_RULE_MAP)
         *vecptr++ = "map";
      else
      if (mrptr->RuleType == MAPURL_RULE_PASS)
         *vecptr++ = "pass";
      else
      if (mrptr->RuleType == MAPURL_RULE_PROTECT)
         *vecptr++ = "protect";
      else
      if (mrptr->RuleType == MAPURL_RULE_REDIRECT)
         *vecptr++ = "redirect";
      else
      if (mrptr->RuleType == MAPURL_RULE_SCRIPT)
      {
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "script+";
         else
            *vecptr++ = "script";
      }
      else
      if (mrptr->RuleType == MAPURL_RULE_SET)
         *vecptr++ = "set";
      else
      if (mrptr->RuleType == MAPURL_RULE_UXEC)
         *vecptr++ = "uxec";
      else
      if (mrptr->RuleType == MAPURL_RULE_USER)
         *vecptr++ = "user";
      else
         *vecptr++ = "*ERROR*";

      *vecptr++ = mrptr->TemplatePtr;
      if (isdigit(*mrptr->ResultPtr))
      {
         *vecptr++ = "  &quot;!&;AZ&quot;";
         *vecptr++ = mrptr->ResultPtr;
      }
      else
      if (mrptr->ResultPtr[0])
      {
         *vecptr++ = "  !&;AZ";
         *vecptr++ = mrptr->ResultPtr;
      }
      else
         *vecptr++ = "";

      if (mrptr->PathSet)
      {
         /* just one space, ExplainPathSet() adds it's own leading space */
         *vecptr++ = " !&;AZ";
         *vecptr++ = MapSetExplain (rqptr, mrptr);
      }
      else
         *vecptr++ = "";

      if (mrptr->ConditionalPtr[0])
      {
         *vecptr++ = "  !&;AZ";
         *vecptr++ = mrptr->ConditionalPtr;
      }
      else
         *vecptr++ = "";

      *vecptr++ = mclptr->ConfigProblem || FinalRule;

      status = FaolToNet (rqptr, RuleFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

      /* stop reporting a specific virtual service if terminating rule */
      if (!VirtualService[0]) continue;
      if ((mrptr->RuleType == MAPURL_RULE_FAIL ||
           mrptr->RuleType == MAPURL_RULE_PASS) &&
           SAME2(mrptr->TemplatePtr,'*\0'))
         FinalRule = true;
   }

   vecptr = FaoVector;
   if (EntryCount)
      *vecptr++ = "";
   else
      *vecptr++ = "<i>(none)</i>\n";
   *vecptr++ = MapUrlUserNameCacheEntries;

   status = FaolToNet (rqptr, UserMappingFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   /*****************/
   /* user mappings */
   /*****************/

   EntryCount = 0;

   /* process the cache entry list from most to least recent */
   for (leptr = MapUrlUserNameCacheList.HeadPtr;
        leptr;
        leptr = leptr->NextPtr)
   {
      ucptr = (MAP_URL_USER_ENTRY*)leptr;

      /* if empty name, must have been reset, no point in going any further */
      if (!ucptr->UserName[0]) break;

      vecptr = FaoVector;

      EntryCount++;
      if (EntryCount % 2)
         *vecptr++ = " class=\"hlght\"";
      else
         *vecptr++ = " class=\"hlght\"";
      *vecptr++ = EntryCount;
      *vecptr++ = ucptr->UserName;
      *vecptr++ = ucptr->UserPath;

      if (ucptr->PathOds == MAPURL_PATH_ODS_2)
         *vecptr++ = "ODS-2";
      else
      if (ucptr->PathOds == MAPURL_PATH_ODS_5)
         *vecptr++ = "ODS-5";
      else
         *vecptr++ = "ods-2";

      *vecptr++ = ucptr->HitCount;
      *vecptr++ = &ucptr->LastTime64;
      *vecptr++ = ucptr->ReuseCount;

      status = FaolToNet (rqptr, EntryFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   if (!MapUrlUserNameCacheList.HeadPtr)
   {
      status = FaolToNet (rqptr, EmptyUserMappingFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   status = FaolToNet (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_MAPPING_RULES;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);
}

/*****************************************************************************/
/*
Report an individual rule during processing of a mapping rule check.
*/

MapUrl_WatchRule
(
REQUEST_STRUCT *rqptr,
MAP_RULE_META *mrptr,
METACON_LINE *mclptr,
int InConfigLevel,
char *PathPtr,
int PathOds,
int Match,
BOOL SetPathIgnore
)
{
   static char  RuleFao [] = "!AZ!AZ!AZ  !AZ  !8AZ  !AZ  !&@!&@!&@\n";

   BOOL WatchMatch = false;
   int  status;
   unsigned short  Length;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  Buffer [8192],
         RmsSubChar [2];

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUrl_WatchRule() !&Z !UL !UL", PathPtr, PathOds, Match);

   vecptr = FaoVector;
   *vecptr++ = MetaConCounter (mclptr, InConfigLevel);
   *vecptr++ = PathPtr;

   if (PathOds == MAPURL_PATH_ODS_0)
      *vecptr++ = "  NOODS";
   else
   if (PathOds == MAPURL_PATH_ODS_2)
      *vecptr++ = "  ODS=2";
   else
   if (PathOds == MAPURL_PATH_ODS_5)
      *vecptr++ = "  ODS=5";
   else
   if (PathOds == MAPURL_PATH_ODS_ADS)
      *vecptr++ = "  ODS=ADS";
   else
   if (PathOds == MAPURL_PATH_ODS_PWK)
      *vecptr++ = "  ODS=PWK";
   else
   if (PathOds == MAPURL_PATH_ODS_SMB)
      *vecptr++ = "  ODS=SMB";
   else
   if (PathOds == MAPURL_PATH_ODS_SRI)
      *vecptr++ = "  ODS=SRI";
   else
      *vecptr++ = "";
 
   if (Match == MAPURL_REPORT_MATCH_NOT)
      *vecptr++ = "..";
   else
   if (Match == MAPURL_REPORT_MATCH_RULE)
   {
      *vecptr++ = "YN";
      WatchMatch = true;
   }
   else
   if (Match == MAPURL_REPORT_MATCH_RULECOND)
   {
      *vecptr++ = "YY";
      WatchMatch = true;
   }
   else
   if (Match ==  MAPURL_REPORT_MATCH_RULENOCOND)
   {
      *vecptr++ = "Y-";
      WatchMatch = true;
   }
   else
      *vecptr++ = "??";

   switch (mrptr->RuleType)
   {
      case MAPURL_RULE_EXEC :
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "EXEC+";
         else
            *vecptr++ = "EXEC";
         break;
      case MAPURL_RULE_FAIL :
         *vecptr++ = "FAIL";
         break;
      case MAPURL_RULE_MAP :
         *vecptr++ = "MAP";
         break;
      case MAPURL_RULE_PASS :     
         *vecptr++ = "PASS";
         break;
      case MAPURL_RULE_PROTECT :
         *vecptr++ = "PROTECT";
         break;
      case MAPURL_RULE_REDIRECT :
         *vecptr++ = "REDIRECT";
         break;
      case MAPURL_RULE_SCRIPT :
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "SCRIPT+";
         else
            *vecptr++ = "SCRIPT";
         break;
      case MAPURL_RULE_SET :
         if (SetPathIgnore)
            *vecptr++ = "(SET)";
         else
            *vecptr++ = "SET";
         break;
      case MAPURL_RULE_USER :
         *vecptr++ = "USER";
         break;
      case MAPURL_RULE_UXEC :
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "UXEC+";
         else
            *vecptr++ = "UXEC";
         break;
      default :
         *vecptr++ = "*ERROR*";
   }

   *vecptr++ = mrptr->TemplatePtr;

   if (isdigit(*mrptr->ResultPtr))
   {
      *vecptr++ = "\"!AZ\"";
      *vecptr++ = mrptr->ResultPtr;
   }
   else
   {
      *vecptr++ = "!AZ";
      *vecptr++ = mrptr->ResultPtr;
   }

   if (mrptr->PathSet)
   {
      *vecptr++ = " !AZ";
      *vecptr++ = MapSetExplain (rqptr, mrptr);
   }
   else
      *vecptr++ = "";

   if (mrptr->ConditionalPtr[0])
   {
      *vecptr++ = " !AZ";
      *vecptr++ = mrptr->ConditionalPtr;
   }
   else
      *vecptr++ = "";

   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length, RuleFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   if (WATCHING (rqptr, WATCH_MAPPING))
      WatchData (Buffer, Length); 
}

/*****************************************************************************/
/*
Supplied with an integer in 'ThrottleIndex' representing the element number of
the 'ThrottleArray' path, scan through all the paths in the rule list
noting all SET THROTTLE= paths until the count equals that of the integer
supplied.  This is the rule representing that index.  Return a pointer to the
rule, or NULL.
*/ 
 
MAP_RULE_META* MapUrl_ThrottleRule (int ThrottleIndex)

{
   int  RuleCount;
   MAP_RULE_META  *mrptr;
   MAP_SET_META  *mrpsptr;
   METACON_LINE  *mclptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapUrl_ThrottleRule() !UL", ThrottleIndex);

   RuleCount = 0;
   MetaConParseReset (MetaGlobalMappingPtr, true);
   while (mclptr = MetaConParseRaw (MetaGlobalMappingPtr))
   {
      if (!(mrptr = mclptr->LineDataPtr)) continue;
      mrpsptr = &mrptr->mpPathSet;
      if (!mrpsptr->ThrottleSet) continue;
      if (ThrottleIndex != RuleCount++) continue;
      return (mrptr);
   }
   return (NULL);
}

/*****************************************************************************/
/*
Reload the mapping rules.
*/

MapUrl_ControlReload ()

{
   /*********/
   /* begin */
   /*********/

   if ((WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL, "MapUrl_ControlReload()");

   MetaConUnload (&MetaGlobalMappingPtr, &MapUrl_ConfigUnloadLineData);

   MapUrl_ConfigLoad (&MetaGlobalMappingPtr);

   /* purge DCL module script task list and script name cache */
   DclLoadedMappingRules();
}

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

