/*****************************************************************************/
/*
                                 Auth.c


    THE GNU GENERAL PUBLIC LICENSE APPLIES DOUBLY TO ANYTHING TO DO WITH
                    AUTHENTICATION AND AUTHORIZATION!

    This package is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; version 2 of the License, or any later
    version.

>   This package is distributed in the hope that it will be useful,
>   but WITHOUT ANY WARRANTY; without even the implied warranty of
>   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>   GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


OVERVIEW
--------

This is "path"-based authorization/authentication.  That is, access is
controlled according to the path specified in the request.  A path can have
capabilities, or access controls, allowing any combination of DELETE, GET,
HEAD, POST and PUT method capabilities.  The world can have capabilities
against a path. An authenticated user also can have capabilities.  The request
capability is the logical AND of the path and world/user capabilities, meaning
the request gets the minimum of the path and user's access.  There are no
access-control files in any data directory.  It is all under the control of the
HTTPd server manager.

AUTHORIZATION IS ALWAYS PERFORMED AGAINST THE REQUEST'S PATHINFO STRING (the
string parsed from the original request path), NOT AGAINST ANY RE-MAPPED PATH.
THERE SHOULD BE NO AMBIGUITY ABOUT WHAT IS BEING AUTHORIZED!

When a request contains a controlled path the authorization for access to that
path is made against a password database for the authentication "realm" the
path belongs to.  A realm (or group) name is a 1 to 31 character string
describing the collection of one or more paths for purposes of authenticating
against a common password file.  A realm/group name should contain only alpha-
numeric, underscore and hyphen characters.  See below for a description of the
difference between realms and groups.  REITERATION: THE REALM NAME IS WHAT
THE USER-SUPPLIED PASSWORD IS CHECKED AGAINST.

The following realm names are reserved:

  o  "ACME"         authenticated by th VMS ACME service using SYS$ACM()
                    the realm name indicate the Domain of Interpretation (DOI)
                    used by the ACME service (non-VAX, VMS V7.3 and later)

  o  "EXTERNAL"     scripts authenticating their own challenge/response,
                    realm, group, user names and plain-text password are
                    available to the script for it's own authorization

  o  "NONE"         any request, is not authenticated,
                    all authorization details are left empty 

  o  "PROMISCUOUS"  only ever mapped when /PROMISCUOUS in use

  o  "RFC1413"      'authentication' via the "identification protocol"
                    described in RFC1413

  o  "SKELKEY"      only applies if skeleton-key authentication is active
                    if not active a 403 (forbidden) is returned

  o  "VMS"          SYSUAF authentication,
                    via the SYSUAF, or restricted to accounts with identifiers
                    prime/secondary days & network/remote hours will restrict

  o  "WORLD"        any request, is not authenticated,
                    both the realm and username are set to "WORLD"

  o  "X509"         X509 client certificate available when requesting
                    via SSL (see SESOLA.C for detail)

Note that VMS can have one or more synonym realm names which can be used to
disguise the fact SYSUAF authentication is in use.  Just specify the synonym
name followed immediately by "=VMS", as in the following example: "TESTING=VMS"

A plain-text description can also be associated with a realm name, providing
a more informative message to the browser user during the username/password
dialog.  This must be specified as a double-quote enclosed string preceding the
realm name.  Here are a couple of examples:

  ["the server system's SYSUAF"=VMS]
  ["just a contrived example"=VMS]

Also see the topics "SYSUAF-authentication by WASD Identifier" and
"SYSUAF-authentication by non-WASD Identifier" below.

A path specification begins at the root and usually continues down to an
asterisk indicating a match against any path beginning with that string.  Paths
may overlap.  That is a parent directory may have subdirectory trees that
differ in access controls.  The same path specification may not belong to more
than one realm at a time :^)

The [IncludeFile] directive takes a VMS file name as a parameter.  It then
attempts to read and insert any directives contained in that file into the
current point in the authorization configuration.  Files may be nested one deep
(i.e. a main configuration file allowed to include other files, but the other
file not allowed in include yet more).


USER "LOGOUT" FROM AUTHENTICATION
---------------------------------

A user may cancel authentication for any path by submitting a request using
that path and a query string of "?httpd=logout" (or "?httpd=cancel").

An authentication username/password dialog will be presented by the browser. 
The user should clear both username and password fields and submit.  The same
dialog will be represented at which time it should be cancelled resulting in an
authentication failure report.  The browser should have recognised this and
removed the path's cached authentication information.

More usefully, if using a revalidation period via [AuthRevalidateUserMinutes]
or 'SET auth=revalidate=' (perhaps set to something like 23:59:00, or one day),
when the logout query string is supplied the server resets the last access
(way, way back into the past) forcing any future access to require
revalidation.  A successful logout message is then generated, circumventing the
need for the username/password dialog carry-on described earlier.

Also when using a revalidation period a redirection URL may be appended to the
logout query string.  It then redirects to the supplied URL, again
circumventing the need for the username/password dialog carry-on.  It is
important that the redirection is returned to the browser and not handled
internally by WASD.  Normal WASD redirection functionality applies.

  ?httpd=logout&goto=///                     redirects to the local home page
  ?httpd=logout&goto=///help/logout.html     to a specific local page
  ?httpd=logout&goto=http://the.host.name/   to a specific remote server


BREAK-IN EVASION AND FAILURE REPORTS
------------------------------------
The configuration directives

  [AuthFailureLimit]           equivalent to  LGI_BRK_LIM
  [AuthFailurePeriodSeconds]                  LGI_BRK_TMO
  [AuthFailureTimeoutSeconds]                 LGI_HID_TIM

provide a similar break-in detection and evasion as with VMS.  A single
authentication failure marks the particular username in the particular realm as
suspect.  Repeated failures up to [AuthFailureLimit] attempts within the
[AuthFailurePeriodSeconds] period puts it into break-in evasion mode after
which the period [AuthFailureTimeoutSeconds] must expire before further
attempts have authentication performed and so have any chance to succeed. 
(This is a change in behaviour to versions earlier than 8.3.)  If any of the
above three parameters are not specified they default to the corresponding
SYSGEN parameter.

Authentication failures are reported to the process log.
Four messages can be generated.  

  1.  %HTTPD-W-AUTHFAIL, if the request is not authenticated due to there
      being no such user or a password mismatch.  Isolated instances of this
      are only of moderate interest.  Consecutive instances may indicate a
      user thrashing about for the correct password, but they usually give up
      before a dozen attempts.

  2.  %HTTPD-I-AUTHFAILOK (informational), occurs if there has been at least
      one failed attempt to authenticate before a successful attempt.

  3.  %HTTPD-W-AUTHFAILIM, is of greater concern and occurs if the total
      number of failed attempts exceeds the configuration limit (such repeated
      attempts will always continue to fail for if this limit is reached
      successive attempts are automatically failed).

  4.  %HTTPD-I-AUTHFAILEXP, where the failure limit reaches the configuration
      limit (of 3 above) each failure represents one minute of a period before
      expiry of the evasion occurs.  A sucessful authentication after expiry
      of this period results in one of these messages.


CONFIGURATION
-------------

A collection of paths is controlled by a "realm" authentication source, against
which the request username/password is verified, and optionally one or two
"group" sources, from which the username's capabilities are determined (what
HTTP methods the username can apply against the path). 

Authentication sources:

  o  system SYSUAF account
  o  ACME authentication (includes SYSUAF)
  o  HTA database (WASD-specific, non-plain-text file)
  o  simple, plain-text list of names and unencrypted passwords
  o  agent (CGIplus script)
  o  RFC1413 identification protocol
  o  X.509 client certificate (SSL only)

Authorization sources:

  o  account possession of specified VMS rights identifiers
     (only for SYSUAF authenticated requests)
  o  HTA database
  o  host or group of hosts
  o  simple list
  o  agent (CGIplus script)

A single file contains authorization configuration directives.
The realm directive comprises the authentication source and zero, one or two
grouping sources, in the following format:

  [authentication-source;full-access-group;read-only-group]

Optional method or access keywords (e.g. GET, POST, R+W) may be appended to
specify default access for any paths belonging to the realm.  Multiple "realm"
directives for the same realm name may be included, each with its own trailing
paths.  These multiple declarations just accumulate.

All paths specified after a "realm" directive, up until the next "realm"
directive will belong to that realm and use the specified authentication
source. Paths are specified with a leading slash (i.e. from the root) down to
either the last character or a wildcard asterisk.

HTTP method keywords, or the generic "READ", "R", "READ+WRITE", "R+W", "WRITE"
and "W", control access to the path (in combination with user access methods).
The method keywords are "CONNECT", "DELETE", "GET" (implying "HEAD"), "HEAD"
"PUT" and "POST". These keywords are optionally supplied following a path.  If
none are supplied the realm defaults are applied.  The generic keyword "none"
sets all access permissions off, including realm defaults.  It is an error not
to supply either. Multiple method keywords may be separated by commas or
spaces.  Method keywords are applied in two groups.  First the group keywords. 
Second, an optional set for controlling world access.  These are delimited from
the path keywords by a semi-colon.

As part of the path access control a list of comma-separated elements may
optionally be supplied.  These elements may be numeric or alphabetic IP host
addresses (with or without asterisk wildcarding allowing wildcard matching of
domains), authenticated username(s) or the request scheme (protocol, "http:" or
"https:").  If supplied this list restricts access to those matching.  For
example:

   *.wasd.dsto.gov.au       would forbid any host outside the WASD subdomain
   131.185.250.*            similarly, but specifying using numeric notation
   *.wasd.dsto.gov.au,~daniel
   https:,*.131.185.250.*   limited to a subnet requesting via SSL service

The keyword "localhost", or used as "#localhost", refers to the system the
server is executing on.  In this way various functions can be restricted to
browsers executing only on that same system.

The keyword "nocache" prevents caching of authentication information, forcing
each request to be revalidated (this adds significant processing overhead).

The keyword "profile" enables the SYSUAF security profile for that path (see
/PROFILE below).

The keyword "scriptas" results in DCL and DECnet scripts being executed using a
SYSUAF authenticated username (providing the /PERSONA qualifier in is place).

The keyword "final" concludes authorization rule processing as if there was no
matching rule encountered and so acts to prevent further processing at any
point (or even single '*' matching all paths, perhaps for a specific virtual
server).

A network mask may be provided in lieu of a host string.  The mask is a
dotted-decimal network address, a slash, then a dotted-decimal mask.  For
example "131.185.250.0/255.255.255.192".  This has a 6 bit subnet.  It operates
by bitwise-ANDing the client host address with the mask, bitwise-ANDing the
network address supplied with the mask, then comparing the two results for
equality.  Using the above example the host 131.185.250.250 would be accepted,
but 131.185.250.50 would be rejected.  A VLSM (variable-length subnet mask)
style can also be used, where "131.185.250.0/26" would mask the same 6 bit
subnet as above.

Realm names, paths, IP addresses, usernames and access method keywords are all
case insensistive.

Virtual server syntax is the same as for path mapping 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.

Comments may be included by prefixing the line with a "#" characters.  Blank
lines are ignored.  Directives may span multiple lines by ensuring the last
character on any line to be continued is a backslash ("\").

Misconfigured paths, that might otherwise be left without access control, are
reported during server startup, loaded into the configuration and marked
"fail"ed, and will always deny access.  This does not however guarantee that
access control will always work the way intended!!

An example configuration file:

   |[WASD;CONFIDENTIAL]
   |/web/confidential/* get
   |/web/confidential/submissions/* https:,get,post;get
   |/web/authors/* get
   |
   |# realm-context access default
   |[WASD] get,head
   |/web/pub/*
   |/web/pub/submit/* ;get
   |
   |# realm plus full-access and read-only groups
   |[VMS;FULL=LIST;READ=LIST]
   |/web/project/jin/* r+w
   |
   |[WORLD]
   |/web/anyone-can-post-here/* post
   |/web/example/alpha/* *.wasd.dsto.gov.au,read
   |/web/example/numeric/* \
   |   131.185.250.*,read
   |
   |[EXTERNAL]
   |/cgi-bin/footypix*


HTA DATABASES
-------------

WASD authentication/authorization databases (which have the extension .$HTA
accounting for their generic name) are binary-content content files with fixed
512 byte records.  They may only be administered via the server administration
menu.  They provide for encrypted storage of passwords and can also be used to
store groups of usernames for determining group membership.  All HTA databases
must be located in the directory specified by the logical HT_AUTH:

HTA databases are the default for realm and group names.

For example: [VMS;ADMIN;USER]


SIMPLE LISTS
------------

Plain-text files may also be used to store collections of usernames.  This is
primarily intended as a simple yet effective means of collecting together names
for full-access and read-only access group membership.  The file should contain
one username at the beginning of each line.  Blank and comment lines are
ignored.  White-space delimited text following the username is ignored (with
the exception noted below).  The format for each line is
"username other text if required ...".  Simple lists have the extension .$HTL
and must be stored in the directory specified by the logical name HT_AUTH:  The
free-form 'other text if required' is considered the authenticated user
details and supplied to scripts as 'AUTH_USER' CGI variable. 

Although it is better to use another, more secure authentication database,
these lists may have have plain-text "passwords" associated with the usernames. 
As these are unencrypted they should not be used for any security-sensitive
purpose, but may suffice for ad hoc or other simple uses.  The format for
specifying a password is "username=password other text if required ...".

Simple lists are indicated by appending "=LIST" to the realm or group name. 

For example: [VMS;ADMIN=LIST;USER=LIST]

A simple list by default is located in the HT_AUTH: directory.  It is possible
to specify an alternate location using an authorization rule containing
'param=/DIRECTORY=DEVICE:[DIRECTORY]'.  In this way the authorization list can
be devolved to someone other than the site administrator.
***** CAUTION: DO NOT PLACE THESE IN WEB-SPACE! *****


HOST OR GROUP OF HOSTS
----------------------

A group name (write and read-only grouping - not authentication realms) can be
specified as a host or group of hosts via a network mask.  This will serve to
control access via the IP address of the client system.  Note that as IP
addresses can be spoofed (impersonated) this is not a guaranteed authorization
control and should be deployed with that in mind.

Groups of hosts may be useful with such authentication schemes as RFC1413 and
X.509 certification.  Paths within a realm and host group specifications are
all controlled by the host specification, unlike with the access restriction
host/network mask which applies on a per-path basis.

For example: [RFC1413;131.185.250.*;]
             [RFC1413;131.185.250.0/24;]
             [RFC1413;131.185.0.0/255.255.0.0]


VMS IDENTIFIERS
---------------

The qualifier /SYSUAF=ID directs the server to allow SYSUAF authentication only
when the VMS account possesses a specified identifier.  NO IDENTIFIER, NO
ACCESS, NO MATTER HOW MANY CORRECT PASSWORDS SUPPLIED.  An excellent
combination is /SYSUAF=(ID,SSL)!

VMS identifiers may be used to control who may authenticate using the SYSUAF,
who is a member of a full-access group, and who is a member of a read-only
group.  These may be any existing or web-specific VMS identifier name possessed
by a given account.  Standard [realm;group-r+w;group-r] syntax is employed,
with a trailing "=ID..." indicating it is an identifier.

For example: [VMS;JIN_PROJECT=ID;JIN_LIBRARIAN=ID]

For a realm specification without group membership requirements (i.e.
[realm;;]) full-access is granted to the username (this is of course adjusted
according to the path specified access level).  When used for determining group
membership, holding the identifier specified in the full-access group (i.e.
[realm;GROUP-R+W;group-r]) provides full read and write access.  If the
full-access identifier is not held by the user but the read-only one is (i.e.
[realm;group-r+w;GROUP-R]) the username receives read access.

NOTE: VMS Identifier identifiers may only be used for specifying group
membership for realms authenticated using the SYSUAF.  Specifying an identifier
group for any other authentication source results in an authorization
configuration error.

Identifier names may contain 1 to 31 characters.

The following examples should help clarify this description (note that the
identifier names are completely arbitrary).

  [JIN_PROJECT=ID]
  # If an account holds the JIN_PROJECT identifier that username and
  # password may be used for authentication for this realm's paths.
  # Note that this account gets read and write access against the username,
  # subject to the path's access specification (also read+write in this case).
  /web/project/jin/* r+w

  [JIN_PROJECT=ID;JIN_LIBRARIAN=ID;JIN_USER=ID]
  # If an account is a holder of the JIN_PROJECT identifier then the account
  # password may be used for server authentication for this realm's paths.
  # If the account also holds the JIN_LIBRARIAN will it be allowed write
  # access, if JIN_USER then read access, to the realm's paths.
  /web/project/jin/library/* r+w

  [JIN_PROJECT=ID;JIN_CODE=ID]
  # Only if the account possesses both the JIN_PROJECT and JIN_CODE
  # identifiers will the username get read+write access to realm's paths.

  [LOCAL=VMS;JIN_PROJECT=ID]
  # If an account holds the WASD_VMS_R or the WASD_VMS_RE then the username
  # can authenticate against the SYSUAF.  However, only if the account also
  # holds the JIN_PROJECT identifier can they access this realm's paths.
  # Read and/or write depends on the "WASD_VMS_..." identifier held and
  # the path access specification (read-only in this case).
  /web/project/jin/* r

  [JIN_PROJECT=ID;FELIX]
  # If an account is a holder of the JIN_PROJECT identifier then account
  # password may be used for server authentication for this realm's paths.
  # Only if the account also has the WASD_VMS__FELIX identifier will it be
  # allowed to access the realm's paths.
  /web/project/jin/* r+w


SPECIAL-PURPOSE IDENTIFIERS
---------------------------

Three special-purpose identifiers allow supplementary capabilities.  These do
not have to be used or exist, even though if the server is configured to use
rights identifiers, at startup, it will warn of their absence.

WASD_HTTPS_ONLY ........ the account may SYSUAF authenticate only via a
                         secure (SSL) connection
WASD_NIL_ACCESS ........ allows nil-access account (with restrictions from
                         any/all sources and/or days/hours) to authenticate
WASD_PASSWORD_CHANGE ... allows the account to change it's SYSUAF primary
                         password via the server
WASD_PROXY_ACCESS ...... allows a mapping from non-SYSUAF username to SYSUAF
                         username as if authenticated via the SYSUAF


ACME SERVICE
------------
On non-VAX platforms running VMS V7.3 or later the Authentication and
Credentials Management Extensions (ACME) subsystem provides authentication and
persona-based credential services.  Applications use these services to enforce
authentication policies defined by ACME agents running in the context of the
ACME_SERVER process.  The ACME server currently (V7.3-2) supports a VMS
(SYSUAF) and a NT Lan Manager domain agent.  Third-party and local agents re
also possible.

The Domain Of Interpretation (DOI) is specified as the realm name.  If "VMS" is
used, or a string beginning "VMS-" or "VMS_", the ACME service authentications
from the SYSUAF.  This provides all of the facilities and restrictions
available when using the "VMS" realm.  If another string is used as the realm
name it needs to be a DOI (agent) available for the site's particular ACME
service.

  ["ACME Coyote"=VMS=ACME;JIN_PROJECT=id]
  /a/path/* r+w,https:

The above example authenticates the path against the SYSUAF using the ACME
service.  Access to the path is further restricted to users holding the
JIN_PROJECT rights identifier.

The configuration directive [AuthSysUafUseACME] instructs the WASD server to
use the ACME service transparently for SYSUAF (VMS) and rights identifier
authentication.  See discussion in AUTHACME.C module.

  ["Hypothetical Agent"=HYPOAUTH=ACME]
  /a/path/* read,https:

The example above authenticates the path against the hypothetical ACME agent
accessable via the DOI of "HYPOAUTH".


AUTHENTICATION AGENT SCRIPTS
----------------------------

An authentication agent script is a CGIplus script that can be activated during
the authorization process to perform the actual authentication/authorization
and return results to the server for access or denial.  Agents may be used with
realm specifications (for authentication) or with group specifications (for
authorization via group membership).

They are provided so that sites may provide a customized authentication
mechanism (perhaps in addition to the WASD standard ones) or for authentication
via some sort of potentially highly-latent source such as LDAP.  It allows
these authorization mechanisms to be relatively easily built, using a CGI-like
environment, and without the rigors of building in an AST-driven environment. 
See implementation comments in AUTHAGENT.C and example(s) provided in the
[SRC.CGIPLUS] directory.

An agent specification used for authentication might look something like:

  ["a remote database"=REMOTE=agent]
  /path/authorized/by/remote/database/*

The following script would need to be mapped and present:

  /cgiauth-bin/remote.exe

A parameter (any string, enclose in double or single quotes if necessary) may
be passed to the agent.  This parameter appears in the CGI variable
"WWW_AUTH_AGENT" and overrides both "REALM" and "GROUP" authorization passed by
default.  If a specific parameter is passed the agent should take on the role
of fully authenticating and authorizing the request, returning a response as if
authorizing a realm. The parameter should be placed in the restriction list
text as illustrated here.

  ["a remote database"=REMOTE=agent]
  /path/authorized/by/remote/database/* param="HT_ROOT:[LOCAL]LIST.TXT"

Generally an agent can use the username/password data automatically generated
by the server using 401/WWW-Authorize: response and Authorization: request
header transaction.  However some authentication/authorization schemes may not
require the username/password data, obtaining this data outside of the
client/server transaction (examples internally implemented by this server are
X.509 certificates and RFC1413 ident protocol).  To suppress the server's
automatic generation of the username/password browser dialog make the leading
portion of the realm parameter string "/NO401".  The agent will need to ignore
this if other parameters are passed as well.


SYSUAF-AUTHENTICATION BY WASD IDENTIFIER
----------------------------------------

*** THIS FUNCTIONALITY IS DEPRECATED! ***
Use the more generalized form described in "VMS Identifiers" above.

In addition to general identifiers, other "hard-wired" identifiers may be used
to control access to and of VMS accounts.  If a username has been authenticated
using using one of the read or write "hard-wired" identifiers then any group
membership must be determined using the "hard-wired" WASD_VMS__<group>
identifier.  Only a realm and one grouping is allowed.

  o  WASD_VMS_R .......... account has read access
  o  WASD_VMS_RW ......... account has read and write access
  o  WASD_VMS__<group> ... account must possess identifier to access
  o  WASD_VMS_HTTPS ...... account can only SYSUAF authenticate via SSL
  o  WASD_VMS_PWD ........ account can change it's SYSUAF password


SYSUAF-AUTHENTICATION AND VMS SECURITY PROFILE
----------------------------------------------

The ability to be able to authenticate using the system's SYSUAF is controlled
by the server /SYSUAF qualifier.  By default it is disabled.  (This qualifier
was introduced in v4.4 as a result of creeping paranoia :^)

As of v4.4 it has become possible to control access to files and directories
based on the security profile of a SYSUAF-authenticated remote user. This
feature is controlled using the server /PROFILE qualifier.  By default it is
disabled.  The /PROFILE=BYRULE variant only applies this profile to rules that
contain the "profile" keyword.

A security-profile is created using sys$create_user_profile() and stored in
the authentication cache. This cached profile is the most efficient method of
implementing this as it obviously removes the need of creating a user profile
each time a resource is checked. If this profile exists in the cache it is
attached to each request structure authenticated via the cache.

When this profile is attached to a request all accesses to files and
directories are first assessed against that user profile using
sys$check_access(). If the user can access the resource (regardless of whether
that is because it is WORLD accessable, or because it is owned by, or
otherwise accessable to the user) SYSPRV is always enabled before accessing
the file/directory. This ensures that the authenticated user is always given
access to the resource (provided the SYSTEM permissions allow it!)

Of course, this functionality only provides access for the server, IT DOES NOT
PROPAGATE TO ANY SCRIPT ACCESS. If scripts must have a similar ability they
should implement their own sys$check_access() (which is not too difficult)
based on the WWW_AUTH_REALM which would be "VMS" indicating
SYSUAF-authentication, and the authenticated name in WWW_REMOTE_USER.

Note: the sys$assume_persona(), et.al., which might seem a more thorough
approach to this functionality, was not possible due to the "independently"
executing script processes associated with the server that might be adversely
affected by such an abrupt change in identity!


PROXY MAPPING NON-SYSUAF USERNAMES TO SYSUAF USERNAMES
------------------------------------------------------
An authentication realm can have it's usernames mapped into VMS usernames and
the VMS username used as if it had been authenticated from the SYSUAF.  This is
a TYPE OF PROXY access.  CAUTION - this is an extremely powerful mechanism and
as a consequence requires enabling on the command-line at server startup using
the /SYSUAF=PROXY qualifier and keyword.  If identifiers are used to control
SYSUAF authentication (i.e. /SYSUAF=ID) then any account mapped by proxy access
must hold the WASD_PROXY_ACCESS identifier described above (and server startup
would be something like "/SYSUAF=(ID,PROXY)").

For each realm a different collection of mappings can be applied.  Proxy
entries are strings containing no white space on lines begining with
[AuthProxy].  There are three basic variations, each with an optional host or
network mask component.

  [AuthProxy] remote[@host|@network/mask]=SYSUAF
  [AuthProxy] *[@host|@network/mask]=SYSUAF
  [AuthProxy] *=*[@host|@network/mask]

The 'SYSUAF' is the VMS username being mapped to.  The 'remote' is the remote
username (CGI variable WWW_REMOTE_USER).  The first variation maps a matching
remote username (and optional host/network) onto the specific SYSUAF username. 
The second maps all remote usernames (and optional host/network) to the one
SYSUAF username (useful as a final mapping).  The third maps all remote
usernames (optionally on the remote host/network) into the same SYSUAF
username (again useful as a final mapping if there is a one-to-one equivalence
between the systems).

Proxy mappings are processed sequentially from first to last until a matching
rule is encountered.  If none is found authorization is denied.  Match-all and
default mappings can be specified.  The following is an example.

  [RFC1413]
  [AuthProxy] bloggs@131.185.250.1=fred
  [AuthProxy] doe@131.185.250.1=john
  [AuthProxy] system=-
  [AuthProxy] *@131.185.252.0/24=*
  [AuthProxy] *=GUEST

In this example the username 'bloggs' on system '131.185.250.1' can access as
if the request had been authenticated via the SYSUAF using the username and
password of 'FRED', although of course no SYSUAF username or password needs to
be supplied.  The same applies to the second mapping, 'doe' on the remote
system to 'JOHN' on the VMS system.  The third mapping disallows a 'system'
account ever being mapped to it's VMS equivalent.  The fourth, wildcard
mapping, maps all accounts on all systems in '131.185.250.0' network to the
same VMS username on the server system.  The fifth mapping provides a default
username for all remote usernames (and like this would terminate further
mapping).

Note that multiple, space-separated proxy entries may be placed on a single
line.  In this case they are processed from left to right and first to last. 
This example show a host group being used to confine all the proxy mappings to
the hosts in one particular subnet.

  [RFC1413;131.185.250.0/24]
  [AuthProxy] bloggs=fred doe=john
  [AuthProxy] system=- *=GUEST

Proxy mapping rules apply should be placed after a realm specification and
before any authorization path rules in that realm.  In this way the mappings
will aplly to all rules in that realm.  It is possible to change the mappings
between rules.  Just insert the new mappings before the (first) rule they apply
to.  This cancels any previous mappings and starts a new set.  To cancel all
mappings use [AuthProxy] (with no following mapping detail).  This
is an example.

  [RFC1413]
  [AuthProxy] bloggs@131.185.250.1=fred
  [AuthProxy] doe@131.185.250.1=john
  /fred/and/johns/path/* r+w
  [AuthProxy] *=GUEST
  /other/path/* read

REMEMBER - proxy processing can be observed using the WATCH facility.


PROTECT MAPPING RULE
--------------------
The HTTPD$MAP configuration file PROTECT rule stores an authorization string
matching the corresponding path.  Two strings can be stored, one for the full
path (or script component), the second for any resulting path derived after a
script component has been resolved.  The string has the following format and
components.

  "Realm Description"=<realm>=<type>:<access>:<restriction>

where

  o "Realm Description"  optional browser username/password dialog
  o <realm>              mandatory source of the authentication
  o <type>               mandatory type of authentication source
  o <access[,access]>    mandatory 'r' or 'r+w' access (optional world access)
  o <restriction>        optional restriction list

As can be seen these components parallel those found in HTTPD$AUTH
functionality.  The following examples illustrate such PROTECT rules.

  PROTECT /path/* "Just an Example"=WASD_VMS_RW=id:r+w
  PROTECT /nuther/path/* "Second Example"=LOCAL=hta:read
  PROTECT /third/path/* "Last Example"=THESE=list:read+write:~fred,~ginger



CONTROLLING SERVER WRITE ACCESS
-------------------------------

Write access by the server into VMS directories should be controlled using VMS
ACLs.  World write access should not be given to any server accessed directory.
This is in addition to the path authorization of the server itself of course!
The requirement to have an ACL on the directory prevents inadvertant
mapping/authorization path being able to be written to.  Two different ACLs
control two different grades of access.

1. If the ACL grants CONTROL access to the server account then only
VMS-authenticated usernames with security profiles can potentially write to
the directory, potentially, because a further check is made to assess whether
that VMS account has write access.

This example show a suitable ACL that stays only on the original directory:

  $ SET SECURITY directory.DIR -
    /ACL=(IDENT=HTTP$SERVER,ACCESS=READ+WRITE+EXECUTE+DELETE+CONTROL)

This example shows setting an ACL that will propagate to created
subdirectories:

  $ SET SECURITY directory.DIR -
    /ACL=((IDENT=HTTP$SERVER,OPTIONS=DEFAULT,ACCESS=READ+WRITE+EXECUTE+DELETE+CONTROL), -
          (IDENT=HTTP$SERVER,ACCESS=READ+WRITE+EXECUTE+DELETE+CONTROL))

2. If the ACL grants WRITE access then the directory can be written into by
any authenticated username for the authorized path.

This example show a suitable ACL that stays only on the original directory:

  $ SET SECURITY directory.DIR -
    /ACL=(IDENT=HTTP$SERVER,ACCESS=READ+WRITE+EXECUTE+DELETE)

This example shows setting an ACL that will propagate to created
subdirectories:

  $ SET SECURITY directory.DIR -
    /ACL=((IDENT=HTTP$SERVER,OPTIONS=DEFAULT,ACCESS=READ+WRITE+EXECUTE+DELETE), -
          (IDENT=HTTP$SERVER,ACCESS=READ+WRITE+EXECUTE+DELETE))


IMPLEMENTATION
--------------

A linked-list, binary tree (using the the LIB$..._TREE routines) is used to 
store authentication records.  When a request with an "Authorization:" header 
line encounters a point where authentication is required this binary tree is 
checked for an existing record.  If one does not exist a new one is entered 
into the binary tree, with an empty password field.  The password in the 
authorization line is checked against the password in the record.  If it 
matches the request is authenticated.

If a binary tree record is not found the SYSUAF or REALM-based password is
checked.  If the hashed password matches, the plain password is copied into the
record and used for future authentications.  If not, the authentication fails.

Once a user name is authenticated from the password database, for a limited
period (currently 60 minutes), further requests are authenticated directly
from the binary tree.  This improves performance and reduces file system
references for authentication.  After that period expires the password and any
group access database is again explicitlty checked.  The internal, binary tree
database can be explicitly listed, flushed or re-loaded as required.

A request's authorization flags (->AuthRequestCan) governs what can and can't be
done using authorization.  These are arrived at by bit-wise ANDing of flags
from two sources ...

   o  'AuthGroupCan' flags associated with the authorization realm and path
   o  'AuthUserCan' flags associated with the authenticated user

... this ensuring that the minimum of actions permitted by either path or user
capabilities is reflected in the request capabilities.


SKELETON-KEY AUTHENTICATION
---------------------------

Provides a 'special' username and password that is authenticated from data
placed into the global common (i.e. in memory).  The username and password
expire (become non-effective) after one hour by default or after an interval
specified at setup.

It's a method for allowing ad hoc authenticated access to the server, primarily
intended for non-configured access to the Administration Menu.  All the site
admin needs to do is deposit the skeleton key username and password and
hey-presto, the admin can access the Admin Menu (see AUTHCONFIG.C for special
skeleton-key processing).  A skeleton-key authenticated request IS subject
to all other authorization processing (i.e. access restrictions, etc.), and can
be controlled using the likes of '~_*', etc.

The site administrator uses the command line directive

  $ HTTPD /DO=AUTH=SKELKEY=_<username>:<password>[:<period>]

to set the username/password, and optionally the period in minutes.  This
authentication credential can be cancelled at any time using

  $ HTTPD /DO=AUTH=SKELKEY=0

The username must begin with an underscore and be a minimum of 6 characters.
The password is delimited by a colon and must be at least 8 characters.  The
optional period in minutes can be from 1 to 10080 (one week).  If not supplied
it defaults to 60 (one hour).  After the period expires the skeleton key no
longer works until reset.

The (with skeleton-key) authentication process goes like this.

1) Is a skeleton-key set?  If not continue on with authentication as usual.

2) If set then check the request username leading character for an underscore. 
If not then continue on with normal authentication. Skeleton-key usernames must
always begin with an underscore so as to reduce the chances of clashing with a
legitimate username. 

3) If it begins with an underscore then match the request and skeleton-key
usernames.  If they do not match then continue with normal authentication.

4) If the usernames match then compare the request and skeleton-key passwords. 
If they match then it's authenticated.  If they don't then it becomes an
authentication failure.


VERSION HISTORY
---------------
24-SEP-2004  MGD  revalidation periods and '?httpd=logout&goto=...'
22-JUL-2004  MGD  bugfix; TcpIpNetMask() result in AuthRestrictList()
18-MAR-2004  MGD  ACME authentication
26-AUG-2003  MGD  service directory located authorization databases
27-JUL-2003  MGD  massage remote username to comply with VMS requirements,
                  suppress digest auth challenge except for HTA and external
15-MAY-2003  MGD  rework break-in detection and processing
                  (configuration defaults to LGI sysgen parameters and now
                  operates in the same way as described for general VMS)
03-MAY-2003  MGD  /SYSUAF=(VMS,ID) allows both VMS and ID authorization
                  (rules with =VMS and =ID can be concurrently deployed)
26-MAR-2003  MGD  refine rule failure handling and reporting,
                  SKELKEY authorization realm
15-MAR-2003  MGD  script as SYSUAF username via rule 'scriptas'
30-JAN-2003  MGD  authentication profile can be requested via rule 'profile'
07-DEC-2002  MGD  skeleton key authentication,
                  bugfix; -I-FAILOK messages
16-NOV-2002  MGD  implement path SET auth=all (path must be subject to
                  authorization or be fobidden)
10-AUG-2002  MGD  bugfix; always revalidate X509 and RFC1413
                  (for path authorization after script)
22-JAN-2002  MGD  allow /NO401 parameter to suppress server generated
                  challenge to allow external agent to response (e.g. PHP)
20-OCT-2001  MGD  instance support requires locking around global
                  authorization cache access
04-AUG-2001  MGD  support module WATCHing
20-APR-2001  MGD  more efficiently support RFC1413 and X509 authentication
05-APR-2001  MGD  bugfix; restriction list network mask processing
18-MAR-2001  MGD  bugfix; BETA2 cached VMS user profile
08-MAR-2001  MGD  bugfix (again); propagate cache NOCACHE!
22-FEB-2001  MGD  add AuthWatchCheck()
                  write and read-only groupings as a host or network mask
                  /NO401 agent processing (no username/password required),
                  bugfix; restriction network mask, refine nocache
                  bugfix; final status at write group/no read group check
15-FEB-2001  MGD  bugfix; AuthGenerateHashPassword() force upper-case
13-FEB-2001  MGD  authentication via "identification protocol" RFC1413
24-JAN-2001  MGD  bugfix; memory leak with user details
12-DEC-2000  MGD  X509 client certificate authorization
03-DEC-2000  MGD  bugfix; final forbidden requires 403
01-SEP-2000  MGD  generalize authorization to take a path parameter
11-JUN-2000  MGD  add network-mask to authorization restriction list,
                  allow agent "302 location" redirection response
06-MAY-2000  MGD  proxy authorization
08-APR-2000  MGD  bugfix; update cache NoCache flag after authentication 
28-MAR-2000  MGD  AuthRestrictAny() check only if cache entry is authenticated
06-JAN-2000  MGD  bugfix; user restriction list pass (broken in 6.1)
24-DEC-1999  MGD  break-in evasion period with expiry
20-NOV-1999  MGD  add nil-access identifier to bypass hour restrictions
28-AUG-1999  MGD  AUTH.C split into more manageable modules,
                  asynchronous "agent" authentication,
                  usernames and passwords stored case-sensitive,
                  authenticated user details
05-AUG-1999  MGD  authentication cancellation via "?httpd=logout",
                  check UAI_NETWORK/REMOTE_ACCESS_x for access restrictions,
                  identifier enabled access allows CAPTIVE and RESTRICTED,
                  bugfix; check for "SSL only" after authorization cache hit,
                  bugfix; revalidate user minutes update
20-FEB-1999  MGD  extend authentication/authorization use of VMS identifiers,
                  refine authorization with [realm;group-r+w;group-r],
                  ALL paths with problems of any sort load, then always FAIL!
19-DEC-1998  MGD  refine authorization WATCH information
07-NOV-1998  MGD  WATCH facility
17-OCT-1998  MGD  [realm-name=VMS] synonym hiding the fact it's SYSUAF,
                  virtual services via "[[virtual-host:virtual-port]]"
29-AUG-1998  MGD  move authorization path processing into AuthPathLine(),
                  report authentication failures in process log,
                  change in behaviour: after cache minutes expires request
                  revalidation by the user via browser dialog
16-JUL-1998  MGD  /SYSUAF=ID, authentication with possession of an identifier,
                  AUTH_DENIED_BY_OTHER indicates non-authentication forbidden
11-MAR-1998  MGD  added local redirection kludge ('^'),
                  configurable SYSUAF authentication of privileged accounts,
                  bugfix; alpha-numeric hosts in access-restriction lists
                  rejected as "unknown HTTP method"
08-FEB-1998  MGD  provide SSL-only for SYSUAF and authorization in general,
                  provide SSL-only for authorized paths (via "https:" or
                  "http:" in path access restriction list),
                  removed full method descriptions from user auth report
17-AUG-1997  MGD  message database,
                  SYSUAF-authenticated users security-profile
16-JUL-1997  MGD  fixed design flaw with WORLD realm and access restriction
01-FEB-1997  MGD  HTTPd version 4
01-JUL-1996  MGD  path/realm-based authorization/authentication
15-MAR-1996  MGD  bugfix; some ErrorGeneral() not passing the request pointer
01-DEC-1995  MGD  HTTPd version 3
01-APR-1995  MGD  initial development for local (SYSUAF) authentication
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <libdef.h>
#include <ssdef.h>
#include <stsdef.h>

#include <uaidef.h>
/* not defined in VAX C 3.2 <uaidef.h> */
#define UAI$M_RESTRICTED 0x8
#define UAI$C_PURDY_S 3

/* application related header files */
#include "wasd.h"

#define WASD_MODULE "AUTH"

#if WATCH_MOD
#define FI_NOLI WASD_MODULE, __LINE__
#else
/* in production let's keep the exact line to ourselves! */
#define FI_NOLI WASD_MODULE, 0
#endif

/* NO reset after 401 HTTP status, authorization challenge needs the realm */
#define AUTH_RESET_REQUEST { \
   rqptr->RemoteUser[0] = \
      rqptr->RemoteUserPassword[0] = '\0'; \
   rqptr->rqAuth.GroupReadPtr = \
      rqptr->rqAuth.GroupRestrictListPtr = \
      rqptr->rqAuth.GroupWritePtr = \
      rqptr->rqAuth.RealmPtr = \
      rqptr->rqAuth.RealmDescrPtr = \
      rqptr->rqAuth.PathParameterPtr = \
      rqptr->rqAuth.WorldRestrictListPtr = ""; \
   rqptr->rqAuth.GroupCan = \
      rqptr->rqAuth.PathParameterLength = \
      rqptr->rqAuth.Scheme =  \
      rqptr->rqAuth.SourceGroupWrite = \
      rqptr->rqAuth.SourceGroupRead = \
      rqptr->rqAuth.SourceRealm = \
      rqptr->rqAuth.WorldCan = 0; }

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

/* explicit storage so that the address can be used for comparison */
char  AuthAgentParamGroup [] = "GROUP",
      AuthAgentParamRealm [] = "REALM";

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

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

extern BOOL  AuthorizationEnabled,
             AuthConfigACME,
             AuthPolicyAuthorizedOnly,
             AuthPolicySslOnly,
             AuthPolicySysUafProxy,
             AuthPolicySysUafSslOnly,
             AuthPromiscuous,
             AuthProtectRule,
             AuthSysUafEnabled,
             AuthSysUafPromiscuous,
             AuthVmsUserProfileEnabled;

extern BOOL  InstanceMutexHeld[];

extern int  AuthFailureLimit,            /* LGI_BRK_LIM */
            AuthFailurePeriodSeconds,    /* LGI_BRK_TMO */
            AuthFailureTimeoutSeconds,   /* LGI_HID_TIM */
            HttpdDayOfWeek,
            HttpdTickSecond,
            OpcomMessages,
            ServerPort;

extern unsigned long  AuthHttpsOnlyVmsIdentifier,
                      AuthWasdPwdVmsIdentifier,
                      AuthWasdHttpsVmsIdentifier,
                      AuthWasdReadVmsIdentifier,
                      AuthWasdWriteVmsIdentifier;

extern char  *AuthPromiscuousPwdPtr;

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

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This function is pivotal to the HTTPd's authorization and authentication.  All
requests should call this function before proceding.

The results of the authorization must always be determined from
'rqptr->rqAuth.FinalStatus'.  A success status indicates the request may
proceed, any other status (usually errors, including AUTH_DENIED_... pseudo
statuses) indicate not authorized, and the request should be ended. 
Authorization challenges, access denied and error reports, etc., generated
during authorization will be delivered as the request is run down.

This function CAN complete asynchronously and calling code should be prepared
for this.  It can, and often does, complete synchronously and THEREFORE DOES
NOT ALWAYS DELIVER the AST.  If an authorization needs to be completed
asynchronously the 'rqptr->rqAuth.FinalStatus' is set to AUTH_PENDING and
the calling routine after detecting this should just 'return;'.  Upon
completion of the authorization the AST function will be called, when the
results can be ascertained from 'rqptr->rqAuth.FinalStatus' in the normal
manner.  If completing synchronously the calling routine should immediately
call the AST function directly.
*/ 

Authorize
(
REQUEST_STRUCT *rqptr,
char *PathBeingAuthorized,
int PathBeingAuthorizedLength,
char *ProtectRulePtr,
int ProtectRuleLength,
REQUEST_AST AuthorizeAstFunction
)
/*
{
int  status;

Debug = 1;
status = Authorize_ (rqptr);
Debug = 0;
return status;
}

Authorize_ 
*/

{
   BOOL  WatchThisOne;
   int  status,
        VerifyPeer;
   unsigned short  Length;
   char  *cptr, *sptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "Authorize() !&Z !&Z",
                 PathBeingAuthorized, ProtectRulePtr);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThisOne = true;
   else
      WatchThisOne = false;

   if (WATCH_CAT && WatchThisOne)
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "PATH !AZ", PathBeingAuthorized[0] ?
                             PathBeingAuthorized : "(none)");

   /* buffer the completion AST address in case we need it later */
   rqptr->rqAuth.AstFunctionBuffer = AuthorizeAstFunction;
   rqptr->rqAuth.AstFunction = NULL;

   /* always start with this status denying access, allow by changing */
   rqptr->rqAuth.FinalStatus = STS$K_ERROR;

   if (!AuthPromiscuous &&
       ((!AuthorizationEnabled &&
         !AuthPolicyAuthorizedOnly) ||
        (!Config.cfAuth.BasicEnabled &&
         !Config.cfAuth.DigestEnabled)))
   {
      /********************************************/
      /* no authorization is mapped, use defaults */
      /********************************************/

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "DISABLED");

      AUTH_RESET_REQUEST
      rqptr->rqAuth.GroupCan =
         rqptr->rqAuth.RequestCan =
         rqptr->rqAuth.UserCan =
         rqptr->rqAuth.WorldCan = AUTH_READONLY_ACCESS;
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      return;
   }

   /****************************/
   /* authorization is enabled */
   /****************************/

   if (AuthPolicySslOnly && rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
   {
      /***********************************************/
      /* policy ... authorization only when "https:" */
      /***********************************************/

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "POLICY https: (SSL) ONLY");

      rqptr->rqAuth.FinalStatus = STS$K_ERROR;
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
      return;
   }

   /**************/
   /* check path */
   /**************/

   status = AuthConfigSearch (rqptr, PathBeingAuthorized);

   if (VMSnok (status))
   {
      if (status == SS$_ABORT)
      {
         /* hmmm, severe configuration error */
         rqptr->rqAuth.FinalStatus = status;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
      }
      status = AuthorizeGuaranteeAccess (rqptr, PathBeingAuthorized);
  }

  if (VMSnok (status))
  {
      /* not a controlled path */
      if (ProtectRulePtr)
      {
         /****************/
         /* mapping rule */
         /****************/

         sptr = AuthConfigParseProtectRule (rqptr,
                                            ProtectRulePtr,
                                            ProtectRuleLength);
         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                      "PROTECT \'!AZ\'!&? ERROR: \r\r!-!AZ",
                      ProtectRulePtr, sptr);
         if (sptr)
         {
            rqptr->rqAuth.FinalStatus = STS$K_ERROR;
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
            return;
         }
         status = SS$_NORMAL;
      }
  }

  if (VMSnok (status))
  {
      /*************************/
      /* not a controlled path */
      /*************************/

      if (AuthPolicyAuthorizedOnly)
      {
         /******************************************/
         /* policy: no authorization ... no access */
         /******************************************/

         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "POLICY authorized paths ONLY");

         rqptr->rqAuth.FinalStatus = STS$K_ERROR;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         return;
      }

      if (rqptr->rqPathSet.AuthorizeAll)
      {
         /****************************************/
         /* path: no authorization ... no access */
         /****************************************/

         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "PATH set authorized ALL");

         rqptr->rqAuth.FinalStatus = STS$K_ERROR;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         return;
      }

      /**********************/
      /* use default access */
      /**********************/

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "NONE applies");

      /*
         If there is already a source realm set then this has been from
         authorization applied to the script portion of a script request.
         Do not reset this as in the absence of authorization on the
         "file" portion it defines the level of access for that request.
      */
      if (!rqptr->rqAuth.SourceRealm)
      {
         AUTH_RESET_REQUEST
         rqptr->rqAuth.GroupCan =
            rqptr->rqAuth.RequestCan =
            rqptr->rqAuth.UserCan =
            rqptr->rqAuth.WorldCan = AUTH_READONLY_ACCESS;
      }

      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      return;
   }

   /*********************************/
   /* controlled path, authorize it */
   /*********************************/

   if (PathBeingAuthorizedLength <= 0)
       PathBeingAuthorizedLength = strlen(PathBeingAuthorized);
   rqptr->rqAuth.PathBeingAuthorizedPtr =
      VmGetHeap (rqptr, PathBeingAuthorizedLength+1);
   /* including the terminating null */
   memcpy (rqptr->rqAuth.PathBeingAuthorizedPtr,
           PathBeingAuthorized,
           PathBeingAuthorizedLength+1);
   rqptr->rqAuth.PathBeingAuthorizedLength = PathBeingAuthorizedLength;

   rqptr->rqAuth.RevalidateTimeout = rqptr->rqPathSet.AuthRevalidateTimeout;
   if (!rqptr->rqAuth.RevalidateTimeout)
      rqptr->rqAuth.RevalidateTimeout = Config.cfAuth.RevalidateUserMinutes;

   if (WATCH_CAT && WatchThisOne)
   {
      BOOL ShowRealmDescription = rqptr->rqAuth.RealmDescrPtr[0] &&
                                  rqptr->rqAuth.RealmDescrPtr !=
                                  rqptr->rqAuth.RealmPtr; 

      WatchThis (rqptr, FI_LI, WATCH_AUTH,
"!&?REALM-PROBLEM \r\r!&?PATH-PROBLEM \r\r\
[!AZ!AZ!AZ!AZ!AZ;!AZ!AZ;!AZ!AZ] !AZ !AZ ; \
!AZ !AZ!&? PROFILE\r\r!&? NOCACHE\r\r",
         rqptr->rqAuth.RealmProblem, rqptr->rqAuth.PathProblem,
         ShowRealmDescription ? "\"" : "",
         ShowRealmDescription ? (char*)rqptr->rqAuth.RealmDescrPtr : "",
         ShowRealmDescription ? "\"=" : "",
         rqptr->rqAuth.RealmPtr[0] ? (char*)rqptr->rqAuth.RealmPtr : "-",
         AuthSourceString (rqptr->rqAuth.RealmPtr, rqptr->rqAuth.SourceRealm),
         rqptr->rqAuth.GroupWritePtr[0] ? (char*)rqptr->rqAuth.GroupWritePtr : "-",
         AuthSourceString (rqptr->rqAuth.GroupWritePtr,
                           rqptr->rqAuth.SourceGroupWrite),
         rqptr->rqAuth.GroupReadPtr[0] ? (char*)rqptr->rqAuth.GroupReadPtr : "-",
         AuthSourceString (rqptr->rqAuth.GroupReadPtr,
                           rqptr->rqAuth.SourceGroupRead),
         rqptr->rqAuth.GroupRestrictListPtr[0] ?
            (char*)rqptr->rqAuth.GroupRestrictListPtr : "-",
         AuthCanString (rqptr->rqAuth.GroupCan, AUTH_CAN_FORMAT_SHORT),
         rqptr->rqAuth.WorldRestrictListPtr[0] ?
            (char*)rqptr->rqAuth.WorldRestrictListPtr : "-",
         AuthCanString (rqptr->rqAuth.WorldCan, AUTH_CAN_FORMAT_SHORT),
         rqptr->rqAuth.VmsUserProfile,
         rqptr->rqAuth.NoCache);
      if (rqptr->rqAuth.PathParameterPtr &&
          rqptr->rqAuth.PathParameterPtr[0])
         WatchData (rqptr->rqAuth.PathParameterPtr,
                    rqptr->rqAuth.PathParameterLength);
   }

   if (rqptr->rqAuth.RealmProblem ||
       rqptr->rqAuth.PathProblem)
   {
      /*************************************************/
      /* problem with the configuration, default fail! */
      /*************************************************/

      status = AuthorizeGuaranteeAccess (rqptr, PathBeingAuthorized);
      if (VMSnok (status))
      {
         rqptr->rqAuth.FinalStatus = STS$K_ERROR;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         return;
      }
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_NONE)
   {
      /******************/
      /* the NONE realm */
      /******************/

      /*
         The purpose of the "NONE" realm is to set the '->AuthRealmSource'
         so that the server knows it's passed through authorization, without
         actually requiring any authorization, etc.  Supports the use of
         'AuthPolicyAuthorizedOnly' for reducing the possibility of
         inadvertant resource access.
      */

      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      AUTH_RESET_REQUEST
      return;
   }

   if (rqptr->rqHeader.Method & rqptr->rqAuth.WorldCan)
   {
      /**********************/
      /* world capabilities */
      /**********************/

      if (rqptr->rqAuth.WorldRestrictListPtr[0])
      {
         /***********************************************/
         /* check access against world restriction list */
         /***********************************************/

         status = AuthRestrictList (rqptr, rqptr->rqAuth.WorldRestrictListPtr);
         if (VMSnok (status))
         {
            rqptr->rqAuth.FinalStatus = status;
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
            return;
         }
      }

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
            "ACCESS world:!AZ",
            AuthCanString (rqptr->rqAuth.WorldCan, AUTH_CAN_FORMAT_SHORT));

      rqptr->rqAuth.RequestCan = rqptr->rqAuth.WorldCan;
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      return;
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_EXTERNAL)
   {
      /**********************************/
      /* externally (script) authorized */
      /**********************************/

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
            "ACCESS external:!AZ",
            AuthCanString (rqptr->rqAuth.GroupCan, AUTH_CAN_FORMAT_SHORT));

      status = AuthParseAuthorization (rqptr);

      if (!rqptr->rqAuth.PathParameterLength ||
          !strsame (rqptr->rqAuth.PathParameterPtr, "/NO401", 6))
      {
         if (VMSnok (status))
         {
            /* error from parse of authorization field */
            rqptr->rqAuth.FinalStatus = status;
            AuthorizeResponse (rqptr);
            return;
         }
      }

      rqptr->rqAuth.RequestCan = rqptr->rqAuth.GroupCan;
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      return;
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID)
   {
      if (!AuthSysUafEnabled ||
          (AuthPolicySysUafSslOnly &&
           rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS))
      {
         /**********************/
         /* SYSUAF not allowed */
         /**********************/

         /* SYSUAF-authentication is disabled, or disabled for non-"https:" */ 

         if (WATCH_CAT && WatchThisOne)
         {
            if (AuthPolicySysUafSslOnly)
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "SYSUAF SSL only");
            else
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "SYSUAF disabled");
         }

         rqptr->rqAuth.FinalStatus = STS$K_ERROR;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         return;
      }
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_PROMISCUOUS &&
       !AuthPromiscuous)
   {
      /***************************/
      /* promiscuous not allowed */
      /***************************/

      rqptr->rqAuth.FinalStatus = STS$K_ERROR;
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
      return;
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_SKELKEY &&
       !HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
   {
      /***************************/
      /* skeleton key not active */
      /***************************/

      rqptr->rqAuth.FinalStatus = STS$K_ERROR;
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
      return;
   }

   if (rqptr->rqAuth.GroupRestrictListPtr[0])
   {
      /*****************************************/
      /* check access against restriction list */
      /*****************************************/

      status = AuthRestrictList (rqptr, rqptr->rqAuth.GroupRestrictListPtr);
      /* denied by username is reported as authentication, not access, fail */
      if (status == AUTH_DENIED_BY_FAIL ||
          status == AUTH_DENIED_BY_HOSTNAME ||
          status == AUTH_DENIED_BY_PROTOCOL)
      {
         rqptr->rqAuth.FinalStatus = status;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         return;
      }
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WORLD)
   {
      /********************************************/
      /* the WORLD realm (i.e. no authentication) */
      /********************************************/

      strcpy (rqptr->RemoteUser, AUTH_REALM_WORLD);
      rqptr->rqAuth.RequestCan = rqptr->rqAuth.UserCan = rqptr->rqAuth.GroupCan;
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      return;
   }

   rqptr->rqAuth.ResolvedRemoteUser = false;

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_RFC1413)
   {
      /***************************/
      /* identification protocol */
      /***************************/

      rqptr->rqAuth.Scheme = AUTH_SCHEME_RFC1413;
      strcpy (rqptr->rqAuth.Type, "RFC1413");
      rqptr->rqAuth.ResolvedRemoteUser = true;
      /* it will *always* be handled asynchronously! */
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      AuthIdentBegin (rqptr, &AuthorizeRealm);
      if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return;
   }
   else
   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509)
   {
      /********************/
      /* X509 certificate */
      /********************/

      if (rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "FAIL X509 not possible unless SSL");
         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_PROTOCOL;
         rqptr->rqResponse.HttpStatus = 403;
         if (!rqptr->rqResponse.ErrorReportPtr)
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         return;
      }

      if (tolower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' &&
          (strsame (rqptr->rqHeader.QueryStringPtr, "httpd=cancel", 12) ||
           strsame (rqptr->rqHeader.QueryStringPtr, "httpd=logout", 12)))
         VerifyPeer = SESOLA_VERIFY_PEER_NONE;
      else
         VerifyPeer = SESOLA_VERIFY_PEER_AUTH;

      rqptr->rqAuth.Scheme = AUTH_SCHEME_X509;
      strcpy (rqptr->rqAuth.Type, "X509");
      rqptr->rqAuth.ResolvedRemoteUser = true;
      /* it *may* be handled asynchronously! */
      status = SesolaClientCert (rqptr, VerifyPeer, &AuthorizeRealm);
      if (VMSnok (status)) return;
      if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return;
   }
   else
   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_AGENT &&
       rqptr->rqAuth.PathParameterPtr &&
       strsame (rqptr->rqAuth.PathParameterPtr, "/NO401", 6))
   {
      /************************************************/
      /* agent/NO401 does not use a username/password */
      /************************************************/

      rqptr->rqAuth.Scheme = AUTH_SCHEME_NO401;
      strcpy (rqptr->rqAuth.Type, "AGENT/NO401");
      /* cannot be cached, the agent must be invoked each time */
      rqptr->rqAuth.NoCache = true;

      /* conjure up a unique identifier (username) for this request */
      strcpy (rqptr->RemoteUser, "(AGENT/NO401)");
      rqptr->RemoteUserLength = 13;

      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
   }
   else
   {
      /*****************************/
      /* "Authorize:" header field */
      /*****************************/

      /* parse "Authorization:" request header field */
      rqptr->rqAuth.FinalStatus = AuthParseAuthorization (rqptr);
   }

   AuthorizeRealm (rqptr);
}

/*****************************************************************************/
/*
Look for an authentication record with the realm and username in the linked-
list, binary tree.  If one doesn't exists create a new one.  Check the supplied
password against the one in the record.  If it matches then authentication has
succeeded.  If not, the check the supplied password against the database.  If
it matches then copy the supplied password into the authentication record and
the authentication succeeds.  If it doesn't match then the authentication
fails.  Return AUTH_DENIED_BY_LOGIN to indicate authentication failure,
SS$_NORMAL indicating authentication success, or any other error status
indicating any other error.
*/ 

AuthorizeRealm (REQUEST_STRUCT *rqptr)

{
   BOOL  HttpdLogout,
         HasLoginCookie;
   int  status;
   unsigned long  Seconds,
                  Remainder;
   unsigned long  ResultTime [2];
   char  *cptr, *sptr;
   AUTH_CREC  *acrptr,
              *acsrptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeRealm() !&F !&S",
                 &AuthorizeRealm, rqptr->rqAuth.FinalStatus);

   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      AuthorizeResponse (rqptr);
      return;
   }

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "AUTHENTICATE user:!AZ realm:!AZ!AZ!AZ",
                 rqptr->RemoteUser, rqptr->rqAuth.RealmPtr,
                 AuthSourceString (rqptr->rqAuth.RealmPtr,
                                   rqptr->rqAuth.SourceRealm),
                 rqptr->rqAuth.NoCache ? " NOCACHE!" : "");

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID)
   {
      /* massage remote-username to comply with SYSUAF/VMS requirements */
      for (cptr = sptr = rqptr->RemoteUser; *cptr; cptr++)
         if (isalnum(*cptr) || *cptr == '_' || *cptr == '$')
            *sptr++ = toupper(*cptr);
      *sptr = '\0';
      rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;
   }

   if (tolower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' &&
       (strsame (rqptr->rqHeader.QueryStringPtr, "httpd=cancel", 12) ||
        strsame (rqptr->rqHeader.QueryStringPtr, "httpd=logout", 12)))
      HttpdLogout = true;
   else
      HttpdLogout = false;

   /* create record of request authorization data */
   AuthCacheRequestRecord (rqptr, &acsrptr);
   rqptr->rqAuth.CacheSearchRecordPtr = acsrptr;

   /* need to lock it to access the authorization cache extensively */
   if (!InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   /* login cookies are held in the global authorization cache */
   HasLoginCookie = false;
   if (rqptr->rqAuth.RevalidateTimeout && Config.cfAuth.RevalidateLoginCookie)
      if (rqptr->rqHeader.CookiePtr)
         if (HasLoginCookie = AuthCacheCheckLoginCookie (rqptr))
            AuthCacheResetLoginCookie (rqptr);

   /*********************/
   /* lookup the record */
   /*********************/

   status = AuthCacheFindRecord (acsrptr, &acrptr);

   if (VMSok (status))
   {
      /*******************/
      /* existing record */
      /*******************/

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"CACHE [!AZ!AZ;!AZ!AZ;!AZ!AZ] \
user:!AZ proxy:!AZ failures:!UL accesses:!UL seconds-ago:!UL!AZ",
            acrptr->Realm[0] ? (char*)acrptr->Realm : "-",
            AuthSourceString (acrptr->Realm, acrptr->SourceRealm),
            acrptr->GroupWrite[0] ? (char*)acrptr->GroupWrite : "-",
            AuthSourceString (acrptr->GroupWrite, acrptr->SourceGroupWrite),
            acrptr->GroupRead[0] ? (char*)acrptr->GroupRead : "-",
            AuthSourceString (acrptr->GroupRead, acrptr->SourceGroupRead),
            acrptr->UserName,
            acrptr->ProxyUserName[0] ? acrptr->ProxyUserName : "-",
            acrptr->FailureCount, acrptr->AccessCount,
            acrptr->LastAccessSecondsAgo, acrptr->NoCache ? " NOCACHE!" : "");
      }
   }

   if (VMSok (status) && !acrptr->NoCache)
   {
      if (VMSnok (status = AuthRestrictAny (rqptr, acrptr)))
      {
         /****************/
         /* restrictions */
         /****************/

         rqptr->rqAuth.FinalStatus = status;
         AuthorizeFinal (rqptr);
         return;
      }

      if (acrptr->FailureCount >= AuthFailureLimit)
      {
         /***********************************/
         /* LGI_BRK_LIM equivalent exceeded */
         /***********************************/

         if (acrptr->LastAccessSecondsAgo > AuthFailureTimeoutSeconds)
         {
            /**********************************/
            /* LGI_HID_TIM equivalent expired */
            /**********************************/

            WriteFaoStdout (
"%!AZ-I-AUTHFAILEXP, !20%D, !UL seconds, !UL !AZ.\'!AZ\'@!AZ\n \\!AZ !AZ\\\n",
               Utility, 0, acrptr->LastAccessSecondsAgo, acrptr->FailureCount,
               rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr,
               rqptr->rqClient.Lookup.HostName,
               rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

            if (OpcomMessages & OPCOM_AUTHORIZATION)
               WriteFaoOpcom (
"%!AZ-I-AUTHFAILEXP, !UL seconds, !UL !AZ.\'!AZ\'@!AZ\r\n \\!AZ !AZ\\",
                  Utility, acrptr->LastAccessSecondsAgo, acrptr->FailureCount,
                  rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr,
                  rqptr->rqClient.Lookup.HostName,
                  rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                  "EXPIRY break-in evasion after !UL seconds and !UL failures",
                   acrptr->LastAccessSecondsAgo, acrptr->FailureCount);

            acrptr->FailureCount = 0;
         }
         else
         {
            /*******************************************/
            /* within LGI_HID_TIM fail unconditionally */
            /*******************************************/

            acrptr->FailureCount++;
            acrptr->Password[0] = acrptr->DigestResponse[0] = '\0';

            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                  "FAIL break-in evasion !UL failures exceeds limit of !UL",
                  acrptr->FailureCount, AuthFailureLimit);

            rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN; 
            AuthorizeFinal (rqptr);
            return;
         }
      }
      if (acrptr->FailureCount)
      {
         /* at least one failure, but less than maximum */
         acrptr->Password[0] = acrptr->DigestResponse[0] = '\0';
         if (acrptr->LastAccessSecondsAgo > AuthFailurePeriodSeconds)
         {
            /* LGI_BRK_TMO equivalent period expired */
            acrptr->FailureCount = 0;
         }
         /* drop through to recheck authentication */
      }

      if (rqptr->rqAuth.RevalidateTimeout)
      {
         /********************/
         /* revalidate user? */
         /********************/

         /*
            User validation only lasts for a limited period (< 24 hours).
            Check how long ago was the last authorization attempt, if that is
            exceeded force the user to reenter via browser dialog.
         */

         if (acrptr->LastAccessMinutesAgo >= rqptr->rqAuth.RevalidateTimeout)
         {
            if (HasLoginCookie)
            {
               if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
                  WatchThis (rqptr, FI_LI, WATCH_AUTH,
                             "REVALIDATE login cookie (!UL/!UL)",
                             acrptr->LastAccessMinutesAgo,
                             rqptr->rqAuth.RevalidateTimeout);
            }
            else
            {
               if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
                  WatchThis (rqptr, FI_LI, WATCH_AUTH,
                             "FAIL !UL exceeds user revalidate timeout !UL",
                             acrptr->LastAccessMinutesAgo,
                             rqptr->rqAuth.RevalidateTimeout);

               acrptr->Password[0] = acrptr->DigestResponse[0] = '\0';
               rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN; 
               AuthorizeFinal (rqptr);
               return;
            }
         }
      }

      /*********************/
      /* revalidate cache? */
      /*********************/

      if (acrptr->LastAccessMinutesAgo > rqptr->rqAuth.RevalidateTimeout)
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "FAIL !UL exceeds cache revalidate timeout !UL",
                       acrptr->LastAccessMinutesAgo,
                       rqptr->rqAuth.RevalidateTimeout);

         acrptr->Password[0] = acrptr->DigestResponse[0] = '\0';
         /* drop through to recheck authentication */
      }

      /************************/
      /* check authentication */
      /************************/

      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_BASIC ||
          rqptr->rqAuth.Scheme == AUTH_SCHEME_DIGEST)
      {
         if (HttpdLogout)
            rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGOUT;
         else
         if (rqptr->rqAuth.Scheme == AUTH_SCHEME_BASIC)
         {
            if (acrptr->Password[0] &&
                !strcmp (rqptr->RemoteUserPassword, acrptr->Password))
            {
               rqptr->rqAuth.FinalStatus = SS$_NORMAL;
               acrptr->AccessCount++;
            }
            else
               rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;
         }
         else
         if (rqptr->rqAuth.Scheme == AUTH_SCHEME_DIGEST)
         {
            if (acrptr->DigestResponse[0] &&
                !strcmp (acrptr->DigestResponse,
                         rqptr->rqAuth.DigestResponsePtr))
            {
               rqptr->rqAuth.FinalStatus = SS$_NORMAL;
               acrptr->AccessCount++;
            }
            else
               rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;
         }
         else
            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

         if (VMSok (rqptr->rqAuth.FinalStatus))
         {
            /*****************/
            /* authenticated */
            /*****************/

            /* propagate authorization data from cache to request */
            rqptr->rqAuth.CaseLess = acrptr->CaseLess;
            if (!rqptr->rqAuth.NoCache) rqptr->rqAuth.NoCache = acrptr->NoCache;
            rqptr->rqAuth.SysUafCanChangePwd = acrptr->SysUafCanChangePwd;
            rqptr->rqAuth.SysUafAuthenticated = acrptr->SysUafAuthenticated;
            rqptr->rqAuth.SysUafNilAccess = acrptr->SysUafNilAccess;
            rqptr->rqAuth.SourceRealm = acrptr->SourceRealm;
            rqptr->rqAuth.SourceGroupRead = acrptr->SourceGroupRead;
            rqptr->rqAuth.SourceGroupWrite = acrptr->SourceGroupWrite;
            rqptr->rqAuth.UserCan = acrptr->AuthUserCan;

            if (rqptr->rqAuth.UserDetailsLength = acrptr->UserDetailsLength)
            {
               rqptr->rqAuth.UserDetailsPtr =
                  VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1);
               strcpy (rqptr->rqAuth.UserDetailsPtr, acrptr->UserDetails);
            }
            else
               rqptr->rqAuth.UserDetailsPtr = NULL;

            if (rqptr->rqAuth.VmsUserProfileLength =
                acrptr->VmsUserProfileLength)
            {
               rqptr->rqAuth.VmsUserProfilePtr =
                      VmGetHeap (rqptr, acrptr->VmsUserProfileLength);
               memcpy (rqptr->rqAuth.VmsUserProfilePtr,
                       acrptr->VmsUserProfilePtr,
                       acrptr->VmsUserProfileLength);
            }
            else
               rqptr->rqAuth.VmsUserProfilePtr = NULL;

            AuthorizeFinal (rqptr);
            return;
         }
      }
      else
      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_NO401)
      {
         acrptr->AccessCount++;
         rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         AuthorizeFinal (rqptr);
         return;
      }
      else
      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_RFC1413)
      {
         if (rqptr->rqAuth.ResolvedRemoteUser)
            acrptr->DataBaseCount++;
         else
            acrptr->AccessCount++;
         rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         AuthorizeFinal (rqptr);
         return;
      }
      else
      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_X509)
      {
         if (rqptr->rqAuth.ResolvedRemoteUser)
            acrptr->DataBaseCount++;
         else
            acrptr->AccessCount++;
         rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         AuthorizeFinal (rqptr);
         return;
      }
      else
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      /*********************/
      /* not authenticated */
      /*********************/
   }
   else
   if (status == SS$_ITEMNOTFOUND)
   {
      /*****************************/
      /* record not found, add one */
      /*****************************/

      status = AuthCacheAddRecord (acsrptr, &acrptr);
      /* if revalidation timeout applicable ensure there is always one 401 */
      if (rqptr->rqAuth.RevalidateTimeout)
         acrptr->Password[0] = rqptr->RemoteUserPassword[0] = '\0';
   }

   /* last modification to authorization cache entries in this function */
   if (InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

   if (VMSnok (status))
   {
      /* some other error, report it */
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_USER);
      ErrorVmsStatus (rqptr, status, FI_LI);
      rqptr->rqAuth.FinalStatus = status;
      return;
   }

   /**********************************************/
   /* get authentication from appropriate source */
   /**********************************************/

   acrptr->DataBaseCount++;

   if (HttpdLogout)
   {
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGOUT;
      AuthorizeFinal (rqptr);
      return;
   }

   if (AuthPromiscuous)
   {
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;

      if (AuthPromiscuousPwdPtr)
         if (!strsame (rqptr->RemoteUserPassword, AuthPromiscuousPwdPtr, -1))
            rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;

      /* allow the user to do any/everything */
      if (VMSok (rqptr->rqAuth.FinalStatus))
         rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;

      AuthorizeRealmCheck (rqptr);
      return;
   }

   if (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
   {
      if (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond < HttpdTickSecond)
      {
         /* skeleton key details have expired */
         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

         memset (HttpdGblSecPtr->AuthSkelKeyUserName, 0,
                 sizeof(HttpdGblSecPtr->AuthSkelKeyUserName));
         memset (HttpdGblSecPtr->AuthSkelKeyPassword, 0,
                 sizeof(HttpdGblSecPtr->AuthSkelKeyPassword));
         HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond = 0;

         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH, "SKELETON-KEY has expired");

         WriteFaoStdout ("%!AZ-I-AUTHSKEL, !20%D, skeleton-key has expired",
                         Utility, 0);

         if (OpcomMessages & OPCOM_AUTHORIZATION)
            WriteFaoOpcom ("%!AZ-I-AUTHSKEL, skeleton-key has expired",
                           Utility);
      }

      if (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
      {
         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

         if (rqptr->RemoteUser[0] == '_' &&
             strsame (rqptr->RemoteUser,
                      HttpdGblSecPtr->AuthSkelKeyUserName, -1))
         {
            if (strsame (rqptr->RemoteUserPassword,
                         HttpdGblSecPtr->AuthSkelKeyPassword, -1)) 
            {
               rqptr->rqAuth.FinalStatus = SS$_NORMAL;
               rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
               rqptr->rqAuth.NoCache = true;
               rqptr->rqAuth.SkelKeyAuthenticated = true;
               AccountingPtr->AuthSkelKeyCount++;

               if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
                  WatchThis (rqptr, FI_LI, WATCH_AUTH,
                             "SKELETON-KEY authenticated (!UL minute!%s left)",
                             (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond -
                           HttpdTickSecond) / 60);
            }
            else
               rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;

            InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

            AuthorizeRealmCheck (rqptr);
            return;
         }

         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      }
   }

   switch (rqptr->rqAuth.SourceRealm)
   {
      case AUTH_SOURCE_AGENT :

         /* asynchronous check via agent */
         InstanceGblSecIncrLong (&AccountingPtr->AuthAgentCount);

         if (!rqptr->rqAuth.PathParameterPtr[0])
         {
            rqptr->rqAuth.PathParameterPtr = AuthAgentParamRealm;
            rqptr->rqAuth.PathParameterLength = sizeof(AuthAgentParamRealm)-1;
         }

         /* after calling this function all will be asynchronous! */
         AuthAgentBegin (rqptr, rqptr->rqAuth.RealmPtr, &AuthorizeRealmCheck);

         /* ASYNCHRONOUS ... so now we return */
         return;

      case AUTH_SOURCE_ACME :

         /* ACME authentication needs an unencoded password */
         rqptr->rqAuth.ChallengeScheme = AUTH_SCHEME_BASIC;

         if (AuthConfigACME)
         {
            InstanceGblSecIncrLong (&AccountingPtr->AuthAcmeCount);
            /* after calling this function all will be ASYNCHRONOUS! */
            AuthAcmeVerifyUser (rqptr);
            return;
         }

         /* ACME authentication is not enabled! */
         rqptr->rqAuth.FinalStatus = STS$K_ERROR;
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);

         break;

      case AUTH_SOURCE_VMS :
      case AUTH_SOURCE_ID :
      case AUTH_SOURCE_WASD_ID :

         /* SYSUAF authentication needs an unencoded password */
         rqptr->rqAuth.ChallengeScheme = AUTH_SCHEME_BASIC;

         if (!AuthSysUafEnabled)
         {
            /* SYSUAF authentication is not enabled! */
            rqptr->rqAuth.FinalStatus = STS$K_ERROR;
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         }
         else
         if (Config.cfAuth.SysUafUseACME)
         {
            /* config SYSUAF authenticate using ACME */
            if (AuthConfigACME)
            {
               InstanceGblSecIncrLong (&AccountingPtr->AuthVmsCount);
               rqptr->rqAuth.SysUafAuthenticated = true;

               /* after calling this function all will be ASYNCHRONOUS! */
               AuthAcmeVerifyUser (rqptr);
               return;
            }

            /* ACME authentication is not enabled! */
            rqptr->rqAuth.FinalStatus = STS$K_ERROR;
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         }
         else
         {
            /* config SYSUAF authenticate using WASD routines */
            InstanceGblSecIncrLong (&AccountingPtr->AuthVmsCount);
            rqptr->rqAuth.SysUafAuthenticated = true;

            if (VMSok (status = AuthVmsGetUai (rqptr, rqptr->RemoteUser)))
            {
               /* check against SYSUAF */
               if (VMSok (status = AuthVmsVerifyUser (rqptr)))
               {
                  if (VMSok (status = AuthVmsVerifyPassword (rqptr)))
                  {
                     /* authenticated ... user can do anything (path allows!) */
                     rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
                  }
               }
            }
            rqptr->rqAuth.FinalStatus = status;
         }

         break;

      case AUTH_SOURCE_HTA :
      case AUTH_SOURCE_DIR_HTA :

         /* check against per-service HTA database */
         InstanceGblSecIncrLong (&AccountingPtr->AuthHtDatabaseCount);

         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_HTA)
            rqptr->rqAuth.DirectoryPtr = HTA_DIRECTORY;
         else
            rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory;

         rqptr->rqAuth.FinalStatus = 
            AuthReadHtDatabase (rqptr, rqptr->rqAuth.RealmPtr, true);

         rqptr->rqAuth.DirectoryPtr = NULL;
         break;

      case AUTH_SOURCE_HOST :

         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      case AUTH_SOURCE_LIST :
      case AUTH_SOURCE_DIR_LIST :

         /* check against per-service simple list */
         InstanceGblSecIncrLong (&AccountingPtr->AuthSimpleListCount);

         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_LIST)
            rqptr->rqAuth.DirectoryPtr = HTA_DIRECTORY;
         else
            rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory;

         rqptr->rqAuth.FinalStatus =
            AuthReadSimpleList (rqptr, rqptr->rqAuth.RealmPtr, true);

         rqptr->rqAuth.DirectoryPtr = NULL;

         /* if the list has the user name and corrrect password */
         if (VMSok (rqptr->rqAuth.FinalStatus))
            rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;

         break;

      case AUTH_SOURCE_RFC1413 :

         InstanceGblSecIncrLong (&AccountingPtr->AuthOtherCount);
         InstanceGblSecIncrLong (&AccountingPtr->AuthRFC1413Count);
         break;

      case AUTH_SOURCE_X509 :

         InstanceGblSecIncrLong (&AccountingPtr->AuthOtherCount);
         InstanceGblSecIncrLong (&AccountingPtr->AuthX509Count);
         break;

      case AUTH_SOURCE_PROMISCUOUS :

         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      case AUTH_SOURCE_SKELKEY :

         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;
         break;

      default :

         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   AuthorizeRealmCheck (rqptr);
}

/*****************************************************************************/
/*
Called directly or as an AST.  Check the authorization status and if an error
call AuthorizeFinal() directly.  If not an error check for restrictions to this
correctly authenticated access and call AuthorizeFinal() is any found.  If no
restrictions check if there is a read/write group and if there is call
AuthorizeGroupWrite() to check against this, if none then call
AuthorizeFinal().
*/ 

AuthorizeRealmCheck (REQUEST_STRUCT *rqptr)

{
   int  status;
   AUTH_CREC  *acrptr;
   
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeRealmCheck() !&F !&S",
                 &AuthorizeRealmCheck, rqptr->rqAuth.FinalStatus);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      AuthWatchCheck (rqptr, "realm", FI_LI);

   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      /* failure/error from realm authentication */
      AuthorizeFinal (rqptr);
      return;
   }

   if (!InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   status = AuthCacheFindRecord (rqptr->rqAuth.CacheSearchRecordPtr, &acrptr);
   if (VMSnok (status))
   {
      /* cache record has been reused (probably) in the meantime! */
      ErrorNoticed (status, "AuthCacheFindRecord()", FI_LI);
      rqptr->rqAuth.FinalStatus = status;
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE);
      rqptr->rqResponse.ErrorOtherTextPtr = "(increase [AuthCacheEntriesMax])";
      ErrorVmsStatus (rqptr, status, FI_LI);
      AuthorizeResponse (rqptr);
      return;
   }

   /* update the cache record with these flags */
   acrptr->CaseLess = rqptr->rqAuth.CaseLess;
   acrptr->HttpsOnly = rqptr->rqAuth.HttpsOnly;
   acrptr->NoCache = rqptr->rqAuth.NoCache;
   acrptr->SysUafAuthenticated = rqptr->rqAuth.SysUafAuthenticated;
   acrptr->SysUafCanChangePwd = rqptr->rqAuth.SysUafCanChangePwd;
   acrptr->SysUafNilAccess = rqptr->rqAuth.SysUafNilAccess;

   /* check for any non-path restrictions (e.g. "https:"-only, hours, etc.) */
   rqptr->rqAuth.FinalStatus = AuthRestrictAny (rqptr, acrptr);
   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      /* yep, some restriction on user, end of assessment */
      AuthorizeFinal (rqptr);
      return;
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID)
   {
      /* access is controlled by the (deprecated) WASD identifiers */
      rqptr->rqAuth.FinalStatus = AuthorizeWasdId (rqptr);
      AuthorizeFinal (rqptr);
      return;
   }

   if (rqptr->rqAuth.SourceGroupWrite)
   {
      /* there is at least one group following the realm */
      if (InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
         InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);
      AuthorizeGroupWrite (rqptr);
      return;
   }

   /* otherwise we've reached the end of the assessment - successfully */
   AuthorizeFinal (rqptr);
}

/*****************************************************************************/
/*
Deprecated WASD VMS identifiers control access.  Make appropriate assessment.
*/ 

int AuthorizeWasdId (REQUEST_STRUCT *rqptr)

{
   int  status;
   
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeWasdId()");

   status = AuthVmsHoldsIdentifier (rqptr, AUTH_WASD_WRITE_VMS_ID,
                                    AuthWasdWriteVmsIdentifier);
   /* if it has the full-access identifier */
   if (VMSok (status))
      rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
   else
   if (status == 0)
   {
      /* did not hold the full-access identifier */
      status = AuthVmsHoldsIdentifier (rqptr, AUTH_WASD_READ_VMS_ID,
                                       AuthWasdReadVmsIdentifier);
      /* if it has the read-only identifier */
      if (VMSok (status))
         rqptr->rqAuth.UserCan = AUTH_READONLY_ACCESS;
      else
      if (status == 0)
      {
         /* one or other of these MUST have allowed authentication! */
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      }
   }

   if (rqptr->rqAuth.SourceGroupWrite)
   {
      /*
         Group membership specified.  Check for possession of
         a "WASD_VMS__groupname" identifier.
      */

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "CHECK group !AZ!AZ!AZ",
                    AUTH_WASD__GROUP_VMS_ID,
                    rqptr->rqAuth.GroupWritePtr,
                    AuthSourceString (rqptr->rqAuth.GroupWritePtr,
                                      rqptr->rqAuth.SourceGroupWrite));

      status = AuthVmsHoldsWasdGroupIdent (rqptr, rqptr->rqAuth.GroupWritePtr);

      if (VMSnok (status))
      {
         /* doesn't hold the group identifier, no access */
         rqptr->rqAuth.UserCan = 0;
         status = AUTH_DENIED_BY_GROUP;
      }
   }

   return (status);
}

/*****************************************************************************/
/*
Check for full-access (read+write) group membership.
*/ 

AuthorizeGroupWrite (REQUEST_STRUCT *rqptr)

{
   int  status;
   
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeGroupWrite()");

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "CHECK r+w group !AZ!AZ",
                 rqptr->rqAuth.GroupWritePtr,
                 AuthSourceString (rqptr->rqAuth.GroupWritePtr,
                                   rqptr->rqAuth.SourceGroupWrite));

   switch (rqptr->rqAuth.SourceGroupWrite)
   {
      case AUTH_SOURCE_AGENT :

         InstanceGblSecIncrLong (&AccountingPtr->AuthAgentCount);

         if (!rqptr->rqAuth.PathParameterPtr[0] ||
             rqptr->rqAuth.PathParameterPtr == AuthAgentParamRealm)
         {
            rqptr->rqAuth.PathParameterPtr = AuthAgentParamGroup;
            rqptr->rqAuth.PathParameterLength = sizeof(AuthAgentParamGroup)-1;
         }

         /* after calling this function all will be asynchronous! */
         AuthAgentBegin (rqptr, rqptr->rqAuth.GroupWritePtr,
                         &AuthorizeGroupWriteCheck);

         /* ASYNCHRONOUS ... so now we return */
         return;

      case AUTH_SOURCE_HTA :
      case AUTH_SOURCE_DIR_HTA :

         if (rqptr->rqAuth.SourceGroupWrite == AUTH_SOURCE_HTA)
            rqptr->rqAuth.DirectoryPtr = HTA_DIRECTORY;
         else
            rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory;

         rqptr->rqAuth.FinalStatus =
            AuthReadHtDatabase (rqptr, rqptr->rqAuth.GroupWritePtr, false);

         rqptr->rqAuth.DirectoryPtr = NULL;
         break;

      case AUTH_SOURCE_HOST:

         rqptr->rqAuth.FinalStatus =
            AuthClientHostGroup (rqptr, rqptr->rqAuth.GroupWritePtr);
         break;

      case AUTH_SOURCE_ID :

         rqptr->rqAuth.FinalStatus =
            AuthVmsHoldsIdentifier (rqptr, rqptr->rqAuth.GroupWritePtr,
                                    rqptr->rqAuth.GroupWriteVmsIdentifier);
         break;

      case AUTH_SOURCE_LIST :
      case AUTH_SOURCE_DIR_LIST :

         if (rqptr->rqAuth.SourceGroupWrite == AUTH_SOURCE_LIST)
            rqptr->rqAuth.DirectoryPtr = HTA_DIRECTORY;
         else
            rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory;

         rqptr->rqAuth.FinalStatus =
            AuthReadSimpleList (rqptr, rqptr->rqAuth.GroupWritePtr, true);

         rqptr->rqAuth.DirectoryPtr = NULL;
         break;

      case AUTH_SOURCE_RFC1413 :

         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;
         break;

      case AUTH_SOURCE_X509 :

         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;
         break;

      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   AuthorizeGroupWriteCheck (rqptr);
}

/*****************************************************************************/
/*
Called directly or as an AST.  Check the outcome of the the full-access
(read+write) group membership assessment.
*/ 

AuthorizeGroupWriteCheck (REQUEST_STRUCT *rqptr)

{
   int  status;
   
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthorizeGroupWriteCheck() !&F !&S",
                 &AuthorizeGroupWriteCheck, rqptr->rqAuth.FinalStatus);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      AuthWatchCheck (rqptr, "r+w group", FI_LI);

   if (VMSok (rqptr->rqAuth.FinalStatus))
   {
      /* found in the full-access group and therefore has access */
      rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
      AuthorizeFinal (rqptr);
      return;
   }

   if (rqptr->rqAuth.SourceGroupRead)
   {
      /* there is a "read" group following the "write" group */
      AuthorizeGroupRead (rqptr);
      return;
   }

   /* not found in full-access group, no read-only group ... no access */
   rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;
   AuthorizeFinal (rqptr);
}

/*****************************************************************************/
/*
Check for read-only access (second of two) group membership.
*/ 

AuthorizeGroupRead (REQUEST_STRUCT *rqptr)

{
   int  status;
   
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeGroupRead()");

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "CHECK r-only group !AZ!AZ",
                 rqptr->rqAuth.GroupReadPtr,
                 AuthSourceString (rqptr->rqAuth.GroupReadPtr,
                                   rqptr->rqAuth.SourceGroupRead));

   /* reset this group name so the read-only group name is obvious to CGI.C */
   rqptr->rqAuth.GroupWritePtr = "";

   switch (rqptr->rqAuth.SourceGroupRead)
   {
      case AUTH_SOURCE_AGENT :

         InstanceGblSecIncrLong (&AccountingPtr->AuthAgentCount);

         if (!rqptr->rqAuth.PathParameterPtr[0] ||
             rqptr->rqAuth.PathParameterPtr == AuthAgentParamRealm)
         {
            rqptr->rqAuth.PathParameterPtr = AuthAgentParamGroup;
            rqptr->rqAuth.PathParameterLength = sizeof(AuthAgentParamGroup)-1;
         }

         /* after calling this function all will be asynchronous! */
         AuthAgentBegin (rqptr, rqptr->rqAuth.GroupReadPtr,
                         &AuthorizeGroupReadCheck);

         /* ASYNCHRONOUS ... so now we return */
         return;

      case AUTH_SOURCE_HTA :
      case AUTH_SOURCE_DIR_HTA :

         if (rqptr->rqAuth.SourceGroupRead == AUTH_SOURCE_HTA)
            rqptr->rqAuth.DirectoryPtr = HTA_DIRECTORY;
         else
            rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory;

         rqptr->rqAuth.FinalStatus =
            AuthReadHtDatabase (rqptr, rqptr->rqAuth.GroupReadPtr, false);

         rqptr->rqAuth.DirectoryPtr = NULL;
         break;

      case AUTH_SOURCE_HOST:

         rqptr->rqAuth.FinalStatus =
            AuthClientHostGroup (rqptr, rqptr->rqAuth.GroupReadPtr);
         break;

      case AUTH_SOURCE_ID :

         rqptr->rqAuth.FinalStatus =
            AuthVmsHoldsIdentifier (rqptr, rqptr->rqAuth.GroupReadPtr,
                                           rqptr->rqAuth.GroupReadVmsIdentifier);
         break;

      case AUTH_SOURCE_LIST :
      case AUTH_SOURCE_DIR_LIST :

         if (rqptr->rqAuth.SourceGroupRead == AUTH_SOURCE_LIST)
            rqptr->rqAuth.DirectoryPtr = HTA_DIRECTORY;
         else
            rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory;

         rqptr->rqAuth.FinalStatus =
            AuthReadSimpleList (rqptr, rqptr->rqAuth.GroupReadPtr, true);

         rqptr->rqAuth.DirectoryPtr = NULL;
         break;

      case AUTH_SOURCE_RFC1413 :

         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;
         break;

      case AUTH_SOURCE_X509 :

         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;
         break;

      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }
 
   AuthorizeGroupReadCheck (rqptr);
}

/*****************************************************************************/
/*
Called directly or as an AST.  Check the outcome of the the read-only group
membership assessment.
*/ 

AuthorizeGroupReadCheck (REQUEST_STRUCT *rqptr)

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthorizeGroupReadCheck() !&F !&S",
                 &AuthorizeGroupReadCheck, rqptr->rqAuth.FinalStatus);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      AuthWatchCheck (rqptr, "r-only group", FI_LI);

   /* if found in the read-only access group */
   if (VMSok (rqptr->rqAuth.FinalStatus))
      rqptr->rqAuth.UserCan = AUTH_READONLY_ACCESS;
   else
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;

   AuthorizeFinal (rqptr);
}

/*****************************************************************************/
/*
Called directly or as an AST.  Finish the authentication by updating the
authentication cache record with the results of the processing.  Then call
AuthorizeResponse() to set access or generate an appropriate HTTP error
response.
*/ 

AuthorizeFinal (REQUEST_STRUCT *rqptr)

{
   int  status;
   AUTH_CREC  *acrptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeFinal() !&F !&S",
                 &AuthorizeFinal, rqptr->rqAuth.FinalStatus);

   /* may still be locked by some calling function */
   if (!InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   status = AuthCacheFindRecord (rqptr->rqAuth.CacheSearchRecordPtr, &acrptr);
   if (VMSnok (status))
   {
      /* cache record has been reused (probably) in the meantime! */
      ErrorNoticed (status, "AuthCacheFindRecord()", FI_LI);
      rqptr->rqAuth.FinalStatus = status;
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE);
      rqptr->rqResponse.ErrorOtherTextPtr = "(increase [AuthCacheEntriesMax])";
      ErrorVmsStatus (rqptr, status, FI_LI);
      AuthorizeResponse (rqptr);
      return;
   }

   if (VMSok (rqptr->rqAuth.FinalStatus))
   {
      if (rqptr->rqAuth.ProxyStringLength)
      {
         /*****************/
         /* map any proxy */
         /*****************/

         rqptr->rqAuth.FinalStatus = AuthConfigProxyMap (rqptr, acrptr); 
      }
      else
      if (AuthVmsUserProfileEnabled && rqptr->rqAuth.SysUafAuthenticated)
      {
         /******************/
         /* create profile */
         /******************/

         rqptr->rqAuth.FinalStatus = AuthVmsCreateUserProfile (rqptr);
      }
   }

   if (VMSok (rqptr->rqAuth.FinalStatus))
   {
      /*****************/
      /* authenticated */
      /*****************/

      acrptr->AuthUserCan = rqptr->rqAuth.UserCan;

      /* add (any) user detail and VMS profile data */
      if (!acrptr->DetailsUpdated)
         AuthCacheAddRecordDetails (rqptr, acrptr);

      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_BASIC)
      {
         strcpy (acrptr->Password, rqptr->RemoteUserPassword);
         acrptr->BasicCount++;
      }
      else
      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_DIGEST)
      {
         strzcpy (acrptr->DigestResponse,
                  rqptr->rqAuth.DigestResponsePtr,
                  sizeof(acrptr->DigestResponse));
         acrptr->DigestCount++;
      }

      if (acrptr->FailureCount)
      {
         /* just let the log know the access finally succeeded */
         WriteFaoStdout (
"%!AZ-I-AUTHFAILOK, !20%D, !UL !AZ.\'!AZ\'@!AZ\n \\!AZ !AZ\\\n",
            Utility, 0, acrptr->FailureCount,
            rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
            rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         if (OpcomMessages & OPCOM_AUTHORIZATION)
            WriteFaoOpcom (
"%!AZ-I-AUTHFAILOK, !UL !AZ.\'!AZ\'@!AZ\r\n \\!AZ !AZ\\",
               Utility, acrptr->FailureCount, rqptr->RemoteUser,
               rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
               rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         acrptr->FailureCount = 0;
      }
   }
   else
   {
      /*********************/
      /* not authenticated */
      /*********************/

      rqptr->rqAuth.UserCan = 0;
      acrptr->FailureCount++;

      if (rqptr->rqAuth.FinalStatus == AUTH_DENIED_BY_LOGOUT)
      {
         if (rqptr->rqAuth.RevalidateTimeout)
         {
            /* make the last access back in the year dot */
            acrptr->LastAccessTickSecond = 0;
         }

         WriteFaoStdout (
"%!AZ-I-AUTHLOGOUT, !20%D, !UL !AZ.\'!AZ\'@!AZ\n \\!AZ !AZ\\\n",
            Utility, 0, acrptr->FailureCount,
            rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
            rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         if (OpcomMessages & OPCOM_AUTHORIZATION)
            WriteFaoOpcom (
"%!AZ-I-AUTHLOGOUT, !UL !AZ.\'!AZ\'@!AZ\r\n \\!AZ !AZ\\",
               Utility, acrptr->FailureCount, rqptr->RemoteUser,
               rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
               rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);
      }
      else
      if (acrptr->FailureCount >= AuthFailureLimit)
      {
         WriteFaoStdout (
"%!AZ-W-AUTHFAILIM, !20%D, !UL !AZ.\'!AZ\'@!AZ\n \\!AZ !AZ\\\n",
            Utility, 0, acrptr->FailureCount,
            rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
            rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         if (OpcomMessages & OPCOM_AUTHORIZATION)
            WriteFaoOpcom (
"%!AZ-W-AUTHFAILIM, !UL !AZ.\'!AZ\'@!AZ\r\n \\!AZ !AZ\\",
               Utility, acrptr->FailureCount, rqptr->RemoteUser,
               rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
               rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);
      }
      else
      {
         WriteFaoStdout (
"%!AZ-W-AUTHFAIL, !20%D, !UL !AZ.\'!AZ\'@!AZ\n \\!AZ !AZ\\\n",
            Utility, 0, acrptr->FailureCount,
            rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
            rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         if (OpcomMessages & OPCOM_AUTHORIZATION)
            WriteFaoOpcom (
"%!AZ-W-AUTHFAIL, !UL !AZ.\'!AZ\'@!AZ\r\n \\!AZ !AZ\\",
               Utility, acrptr->FailureCount, rqptr->RemoteUser,
               rqptr->rqAuth.RealmDescrPtr, rqptr->rqClient.Lookup.HostName,
               rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);
      }
   }

   AuthorizeResponse (rqptr);
}

/*****************************************************************************/
/*
If authenticated then set the request's level of access and resume processing
(either just return or declare an AST).  The calling routine must check the
'rqptr->rqAuth.orizeStatus' to determine whether access was granted or not.  A
normal status indicates yes, an error status no.  If not then generate an HTTP
response appropriate to the reason for the denial and then resume processing. 
*/ 

AuthorizeResponse (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthorizeResponse() !&S",
                 rqptr->rqAuth.FinalStatus);

   /* last chance to unlock authorization (in case it wasn't done earlier) */
   if (InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

   /* we no longer want this hanging around our CGI variables */
   rqptr->rqAuth.PathParameterPtr = "";
   rqptr->rqAuth.PathParameterLength = 0;

   if (rqptr->rqAuth.FinalStatus == AUTH_DENIED_BY_LOGIN)
   {
      /**************************/
      /* authentication failure */
      /**************************/

      InstanceGblSecIncrLong (&AccountingPtr->AuthNotAuthenticatedCount);

      if (!rqptr->rqAuth.ChallengeScheme)
      {
         /* ensure at least a BASIC challenge is generated if promiscuous */
         if (Config.cfAuth.BasicEnabled || AuthPromiscuous)
            rqptr->rqAuth.ChallengeScheme |= AUTH_SCHEME_BASIC;
         if (Config.cfAuth.DigestEnabled)
            rqptr->rqAuth.ChallengeScheme |= AUTH_SCHEME_DIGEST;
      }

      if (rqptr->rqAuth.ChallengeScheme & AUTH_SCHEME_DIGEST &&
          rqptr->rqAuth.Scheme != AUTH_SOURCE_HTA &&
          rqptr->rqAuth.Scheme != AUTH_SOURCE_EXTERNAL)
         rqptr->rqAuth.ChallengeScheme &= ~AUTH_SCHEME_DIGEST;

      if (!rqptr->rqAuth.ChallengeScheme)
         rqptr->rqResponse.HttpStatus = 403;
      else
      if (rqptr->rqAuth.Scheme == AUTH_SCHEME_NO401)
         rqptr->rqResponse.HttpStatus = 403;
      else
      if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509)
         rqptr->rqResponse.HttpStatus = 403;
      else
      if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_RFC1413)
         rqptr->rqResponse.HttpStatus = 403;
      else
      /* generate a "WWW-Authorize:" challenge */
      if (rqptr->ServicePtr->ProxyAuthRequired)
         rqptr->rqResponse.HttpStatus = 407;
      else
         rqptr->rqResponse.HttpStatus = 401;
      if (rqptr->rqAuth.ReasonLength)
         ErrorGeneral (rqptr, rqptr->rqAuth.ReasonPtr, FI_NOLI);
      else
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_NOLI);
      if (rqptr->rqAuth.AstFunction)
         SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
      return;
   }

   if (rqptr->rqAuth.FinalStatus == AUTH_DENIED_BY_LOGOUT)
   {
      /******************************/
      /* authentication user cancel */
      /******************************/

      InstanceGblSecIncrLong (&AccountingPtr->AuthNotAuthenticatedCount);

      if (rqptr->rqAuth.RevalidateTimeout)
      {
         if (strsame (rqptr->rqHeader.QueryStringPtr+12, "&goto=", 6))
         {
            /* redirect to the specified URL */
            rqptr->rqResponse.LocationPtr = rqptr->rqHeader.QueryStringPtr+12+6;
         }
         else
         {
            /* supply a success message (ensure it has sufficient detail) */
            rqptr->rqPathSet.ReportBasic = false;
            rqptr->rqPathSet.ReportDetailed = true;
/** TODO new logout message
            ReportSuccess (rqptr, MsgFor(rqptr,MSG_AUTH_LOGOUT));
**/
ReportSuccess (rqptr, "Authentication cancelled. The browser and/or window may now be closed.");
         }
      }
      else
      {
         /* otherwise generate an HTTP response */
         if (rqptr->rqAuth.Scheme == AUTH_SCHEME_NO401)
            rqptr->rqResponse.HttpStatus = 403;
         else
         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509)
            rqptr->rqResponse.HttpStatus = 403;
         else
         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_RFC1413)
            rqptr->rqResponse.HttpStatus = 403;
         else
         /* generate a "WWW-Authorize:" challenge */
         if (rqptr->ServicePtr->ProxyAuthRequired)
            rqptr->rqResponse.HttpStatus = 407;
         else
            rqptr->rqResponse.HttpStatus = 401;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_NO_LOGOUT), FI_NOLI);
      }

      if (rqptr->rqAuth.AstFunction)
         SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
      return;
   }

   if (rqptr->rqAuth.FinalStatus == AUTH_DENIED_BY_REDIRECT)
   {
      /***************/
      /* redirection */
      /***************/

      /* either by agent of SYSUAF password expiry */
      InstanceGblSecIncrLong (&AccountingPtr->AuthNotAuthenticatedCount);
      if (rqptr->rqAuth.AstFunction)
         SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
      return;
   }

   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      /***************/
      /* other error */
      /***************/

      /* if a non-specific error occured then always deny access */
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
      if (rqptr->rqAuth.AstFunction)
         SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
      return;
   }

   if (rqptr->rqAuth.GroupRestrictListPtr[0])
   {
      /*****************************************/
      /* check access against restriction list */
      /*****************************************/

      status = AuthRestrictList (rqptr, rqptr->rqAuth.GroupRestrictListPtr);
      if (VMSnok (status))
      {
         rqptr->rqAuth.FinalStatus = status;
         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WORLD ||
             status == AUTH_DENIED_BY_FAIL ||
             status == AUTH_DENIED_BY_HOSTNAME ||
             status == AUTH_DENIED_BY_PROTOCOL)
            rqptr->rqResponse.HttpStatus = 403;
         else
         if (rqptr->rqAuth.Scheme == AUTH_SCHEME_NO401)
            rqptr->rqResponse.HttpStatus = 403;
         else
         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509)
            rqptr->rqResponse.HttpStatus = 403;
         else
         if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_RFC1413)
            rqptr->rqResponse.HttpStatus = 403;
         else
         if (rqptr->ServicePtr->ProxyAuthRequired)
            rqptr->rqResponse.HttpStatus = 407;
         else
            rqptr->rqResponse.HttpStatus = 401;
         if (rqptr->rqAuth.ReasonLength)
            ErrorGeneral (rqptr, rqptr->rqAuth.ReasonPtr, FI_NOLI);
         else
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);
         if (rqptr->rqAuth.AstFunction)
            SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
         return;
      }
   }

   /**************************************/
   /* set and check request capabilities */
   /**************************************/

   /* bit-wise AND of capabilities ensures minimum apply */
   rqptr->rqAuth.RequestCan = rqptr->rqAuth.GroupCan & rqptr->rqAuth.UserCan;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
   {
      if (rqptr->rqAuth.UserDetailsLength)
         WatchData (rqptr->rqAuth.UserDetailsPtr,
                    rqptr->rqAuth.UserDetailsLength);

      WatchThis (rqptr, FI_LI, WATCH_AUTH,
         "CAN group:!AZ user:!AZ request:!AZ profile:!UL",
         AuthCanString (rqptr->rqAuth.GroupCan, AUTH_CAN_FORMAT_SHORT),
         AuthCanString (rqptr->rqAuth.UserCan, AUTH_CAN_FORMAT_SHORT),
         AuthCanString (rqptr->rqAuth.RequestCan, AUTH_CAN_FORMAT_SHORT),
         rqptr->rqAuth.VmsUserProfileLength);
   }

   if (rqptr->rqHeader.Method & rqptr->rqAuth.RequestCan)
   {
      /*************************/
      /* authorized (finally!) */
      /*************************/

      InstanceGblSecIncrLong (&AccountingPtr->AuthAuthorizedCount);
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      if (rqptr->rqAuth.AstFunction)
         SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
      return;
   }

   /*****************/
   /* 403 forbidden */
   /*****************/

   InstanceGblSecIncrLong (&AccountingPtr->AuthNotAuthorizedCount);
   rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_OTHER;
   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "Forbidden to !AZ <TT>!AZ</TT>",
                 rqptr->rqHeader.MethodName,
                 rqptr->rqAuth.PathBeingAuthorizedPtr,
                 FI_LI);

   if (rqptr->rqAuth.AstFunction)
      SysDclAst (rqptr->rqAuth.AstFunction, rqptr);
}

/*****************************************************************************/
/*
Parse the "Authorization:" HTTP request header line.  Call appropriate function
to decode authorization field.  Check information is complete.
*/ 

int AuthParseAuthorization (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthParseAuthorization() !&Z",
                 rqptr->rqAuth.RequestAuthorizationPtr);

   if (!(cptr = rqptr->rqAuth.RequestAuthorizationPtr))
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "FAIL no request \"!AZ\"",
                    rqptr->ServicePtr->ProxyAuthRequired ?
                       "Proxy-Authorization:" : "Authorization:");

      if (rqptr->rqAuth.RevalidateTimeout &&
          Config.cfAuth.RevalidateLoginCookie)
         AuthCacheSetLoginCookie (rqptr);

      return (AUTH_DENIED_BY_LOGIN);
   }

   /***********************************/
   /* get the HTTP authorization line */
   /***********************************/

   zptr = (sptr = rqptr->rqAuth.Type) + sizeof(rqptr->rqAuth.Type);
   while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = toupper(*cptr++);
   if (sptr >= zptr)
   {
      if (rqptr->ServicePtr->ProxyAuthRequired)
         rqptr->rqResponse.HttpStatus = 407;
      else
         rqptr->rqResponse.HttpStatus = 401;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_SCHEME), FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';

   if (strsame (rqptr->rqAuth.Type, "BASIC", -1))
   {
      InstanceGblSecIncrLong (&AccountingPtr->AuthBasicCount);

      if (VMSnok (status = BasicAuthorization (rqptr)))
         return (status);

      if (rqptr->RemoteUser[0] && rqptr->RemoteUserPassword[0])
         return (SS$_NORMAL);

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "FAIL authentication incomplete");
      return (AUTH_DENIED_BY_LOGIN);
   }
   else
   if (strsame (rqptr->rqAuth.Type, "DIGEST", -1))
   {
      InstanceGblSecIncrLong (&AccountingPtr->AuthDigestCount);

      if (VMSnok (status = DigestAuthorization (rqptr)))
         return (status);

      if (rqptr->RemoteUser[0] && rqptr->RemoteUserPassword[0])
         return (SS$_NORMAL);

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "FAIL authentication incomplete");
      return (AUTH_DENIED_BY_LOGIN);
   }

   if (rqptr->ServicePtr->ProxyAuthRequired)
      rqptr->rqResponse.HttpStatus = 407;
   else
      rqptr->rqResponse.HttpStatus = 401;
   ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_SCHEME), FI_LI);
   return (STS$K_ERROR);
}

/*****************************************************************************/
/*
Check for any access restrictions.  SSL only.  SYSUAF restrictions on hours.
*/ 

int AuthRestrictAny
(
REQUEST_STRUCT *rqptr,
AUTH_CREC *acrptr
)
{
   int  status;
   unsigned long  DayOfWeekBit,
                  HourOfDayBit;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthRestrictAny()");

   /* can only use this authentication with "https:" (SSL)? */
   if (acrptr->HttpsOnly &&
       rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL restricted to SSL");
      return (AUTH_DENIED_BY_PROTOCOL);
   }

   /* limited access? */
   if (acrptr->SysUafAuthenticated &&
       !acrptr->SysUafNilAccess)
   {
      DayOfWeekBit = 1 << (HttpdDayOfWeek - 1);
      HourOfDayBit = 1 << rqptr->rqTime.VmsVector[3];

      if (acrptr->UaiPrimeDays & DayOfWeekBit)
      {
         /* secondary day */
         if (HourOfDayBit & acrptr->UaiNetworkAccessS ||
             HourOfDayBit & acrptr->UaiRemoteAccessS)
         {
            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "FAIL restricted SYSUAF secondary hours");
            return (AUTH_DENIED_BY_HOUR);
         }
      }
      else
      {
         /* prime day */
         if (HourOfDayBit & acrptr->UaiNetworkAccessP ||
             HourOfDayBit & acrptr->UaiRemoteAccessP)
         {
            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "FAIL restricted SYSUAF primary hours");
            return (AUTH_DENIED_BY_HOUR);
         }
      }
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
The list is a plain-text string of comma-separated elements, the presence of
which restricts the path to that element.  The element can be an IP host
address (numeric or alphabetic, with or without asterisk wildcard) or an
authenticated username.  IP addresses are recognised by either a leading digit
(for numeric host addresses), a leading asterisk (where hosts are wildcarded
across a domain), a leading "#" which forces an element to be compared to a
host name or address, "http:", "https:", or a leading "~" which indicates a
username.
*/ 

int AuthRestrictList
(
REQUEST_STRUCT *rqptr,
char *RestrictList
)
{
   BOOL  CheckedHost,
         CheckedProtocol,
         CheckedUser,
         FoundHost,
         FoundProtocol,
         FoundUser,
         RestrictedByFail,
         RestrictedOnHost,
         RestrictedOnProtocol,
         RestrictedOnUser;
   int  status;
   char  *lptr, *cptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthRestrictList() !&Z", RestrictList);

   RestrictedByFail = false;
   RestrictedOnHost = RestrictedOnProtocol = RestrictedOnUser = true;
   FoundHost = FoundProtocol = FoundUser = false;
   lptr = RestrictList;
   while (*lptr)
   {
      CheckedUser = CheckedHost = false;
      while (*lptr && *lptr == ',') lptr++;
      if (isdigit(*lptr) || (lptr[0] == '#' && isdigit(lptr[1])))
      {
         /* numeric IP address */
         if (*lptr == '#') lptr++;
         CheckedHost = FoundHost = true;

         /* look ahead for a net-mask (e.g. "131.185.250.23/255.255.255.192") */
         for (cptr = lptr; *cptr && (isdigit(*cptr) || *cptr == '.'); cptr++);
         if (*cptr == '/')
         {
            /* yup, found a '/' so it has a trailing mask */
            status = TcpIpNetMask (rqptr, WATCH_AUTH, &lptr,
                                   &rqptr->rqClient.IpAddress);
            if (status == SS$_NORMAL)
               RestrictedOnHost = false;
            else
            if (status != SS$_UNREACHABLE)
            {
               /* there was a problem with the net-mask, automatic failure */
               RestrictedByFail = true;
               break;
            }
            continue;
         }
         /* just a numeric host string, drop through to comparison routine */
         cptr = rqptr->rqClient.IpAddressString;
      }
      else
      if (*lptr == '~')
      {
         /* authenticated user name */
         lptr++;
         cptr = rqptr->RemoteUser;
         CheckedUser = FoundUser = true;
         /* drop through to the comparison routine */
      }
      else
      if (tolower(*lptr) == 'h' && strsame (lptr, "https:", 6))
      {
         CheckedProtocol = FoundProtocol = true;
         if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
            RestrictedOnProtocol = false;
         while (*lptr && *lptr != ',') lptr++;
         continue;
      }
      else
      if (tolower(*lptr) == 'h' && strsame (lptr, "http:", 5))
      {
         CheckedProtocol = FoundProtocol = true;
         if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
            RestrictedOnProtocol = false;
         while (*lptr && *lptr != ',') lptr++;
         continue;
      }
      else
      if (tolower(*lptr) == 'l' &&
          (strsame (lptr, "localhost", -1) ||
           strsame (lptr, "localhost,", 10)))
      {
         /* check reserved-word, server against client host's IP address */
         CheckedHost = FoundHost = true;
         if (strsame (rqptr->ServicePtr->ServerIpAddressString,
                      rqptr->rqClient.IpAddressString, -1))
            RestrictedOnHost = false;
         while (*lptr && *lptr != ',') lptr++;
         continue;
      }
      else
      {
         /* by default, alpha-numeric IP host name */
         if (*lptr == '#') lptr++;
         cptr = rqptr->rqClient.Lookup.HostName;
         CheckedHost = FoundHost = true;
         /* drop through to the comparison routine */
      }

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "!&Z !&Z", cptr, lptr);

      while (*cptr && *lptr && *lptr != ',')
      {
         if (*lptr == '*')
         {
            while (*lptr == '*') lptr++;
            while (*cptr && tolower(*cptr) != tolower(*lptr)) cptr++;
         }
         while (tolower(*cptr) == tolower(*lptr) && *cptr && *lptr)
         {
            lptr++;
            cptr++;
         }
         if (*lptr != '*') break;
      }
      if (!*cptr && (!*lptr || *lptr == ','))
      {
         if (CheckedUser) RestrictedOnUser = false;
         if (CheckedHost) RestrictedOnHost = false;
      }
      while (*lptr && *lptr != ',') lptr++;
   }

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
   {
      char  Scratch [32];

      *(cptr = Scratch) = '\0';
      if (FoundProtocol && RestrictedOnProtocol)
      {
         strcpy (cptr, "PROTOCOL");
         cptr += 8;
      }
      if (FoundHost && RestrictedOnHost)
      {
         if (Scratch[0]) *cptr++ = '+';
         strcpy (cptr, "HOST");
         cptr += 4;
      }
      if (FoundUser && RestrictedOnUser)
      {
         if (Scratch[0]) *cptr++ = '+';
         strcpy (cptr, "USER");
      }
      if (RestrictedByFail)
      {
         if (Scratch[0]) *cptr++ = '+';
         strcpy (cptr, "NETMASK-PROBLEM");
      }
      if (Scratch[0])
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "RESTRICT \"!AZ\" on:!AZ", RestrictList, Scratch);
   }

   if (RestrictedByFail) return (AUTH_DENIED_BY_FAIL);
   if (FoundProtocol && RestrictedOnProtocol) return (AUTH_DENIED_BY_PROTOCOL);
   if (FoundHost && RestrictedOnHost) return (AUTH_DENIED_BY_HOSTNAME);
   if (FoundUser && RestrictedOnUser) return (AUTH_DENIED_BY_USERNAME);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "RESTRICT \"!AZ\" none applies", RestrictList);
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Passed a string like "131.185.250.1",  "131.185.250.*",
"131.185.250.0/255.255.255.0" or "131.185.250.0/24" determine whether the
request client host belongs in the group of hosts.
*/ 

int AuthClientHostGroup
(
REQUEST_STRUCT *rqptr,
char *HostGroup
)
{
   int  status;
   char  *cptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthClientHostGroup() !&Z", HostGroup);

   /* check if it looks like a wildcard host or network mask specification */
   for (cptr = HostGroup; *cptr && *cptr != '/'; cptr++);

   if (!*cptr)
   {
      /* wildcard string compare */
      if (StringMatch (rqptr, rqptr->rqClient.Lookup.HostName, HostGroup))
         return (AUTH_DENIED_BY_GROUP);
      else
         return (SS$_NORMAL);
   }

   status = TcpIpNetMask (rqptr, WATCH_AUTH, &HostGroup,
                          &rqptr->rqClient.IpAddress);
   if (VMSok(status)) return (SS$_NORMAL);
   if (status == SS$_UNREACHABLE) return (AUTH_DENIED_BY_GROUP);
   /* a problem with the net-mask */
   return (AUTH_DENIED_BY_FAIL);
}

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

AuthorizeGuaranteeAccess
(
REQUEST_STRUCT *rqptr,
char *PathBeingAuthorized
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_AUTH)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthorizeGuaranteeAccess() !&Z", PathBeingAuthorized);

   if (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
   {
      /**************************************/
      /* skeleton-key authentication active */
      /**************************************/

      /* guarantee access to the administration menu */
      if (!strsame (PathBeingAuthorized, HTTPD_ADMIN, sizeof(HTTPD_ADMIN)-1) &&
          !strsame (PathBeingAuthorized, HTTPD_LOCAL, sizeof(HTTPD_LOCAL)-1))
         return (STS$K_ERROR);

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
          WatchThis (rqptr, FI_LI, WATCH_AUTH,
                     "SKELKEY guarantees access to !AZ", PathBeingAuthorized);

      rqptr->rqAuth.RealmPtr =
         rqptr->rqAuth.RealmDescrPtr = AUTH_REALM_SKELKEY;
      rqptr->rqAuth.RealmLength = strlen(AUTH_REALM_SKELKEY);
      rqptr->rqAuth.SourceRealm = AUTH_SOURCE_SKELKEY;
      rqptr->rqAuth.GroupCan = AUTH_READWRITE_ACCESS;
   }
   else
   if (AuthPromiscuous)
   {
      /********************/
      /* promiscuous mode */
      /********************/

      /* guarantee access to the administration menu */
      if (!strsame (PathBeingAuthorized, HTTPD_ADMIN, sizeof(HTTPD_ADMIN)-1) &&
          !strsame (PathBeingAuthorized, HTTPD_LOCAL, sizeof(HTTPD_LOCAL)-1))
         return (STS$K_ERROR);

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
          WatchThis (rqptr, FI_LI, WATCH_AUTH,
                     "PROMISCUOUS guarantees access to !AZ",
                     PathBeingAuthorized);

      rqptr->rqAuth.RealmPtr =
         rqptr->rqAuth.RealmDescrPtr = AUTH_REALM_PROMISCUOUS;
      rqptr->rqAuth.RealmLength = strlen(AUTH_REALM_PROMISCUOUS);
      rqptr->rqAuth.SourceRealm = AUTH_SOURCE_PROMISCUOUS;
      rqptr->rqAuth.GroupCan = AUTH_READWRITE_ACCESS;
   }
   else
      return (STS$K_ERROR);

   rqptr->rqAuth.PathParameterPtr =
      rqptr->rqAuth.GroupReadPtr =
      rqptr->rqAuth.GroupRestrictListPtr =
      rqptr->rqAuth.GroupWritePtr =
      rqptr->rqAuth.ProxyStringPtr =
      rqptr->rqAuth.WorldRestrictListPtr = "";

   rqptr->rqAuth.PathParameterLength =
      rqptr->rqAuth.GroupReadLength =
      rqptr->rqAuth.GroupWriteLength =
      rqptr->rqAuth.GroupWriteVmsIdentifier =
      rqptr->rqAuth.NoCache =
      rqptr->rqAuth.ProxyStringLength =
      rqptr->rqAuth.RealmVmsIdentifier =
      rqptr->rqAuth.SourceGroupRead =
      rqptr->rqAuth.SourceGroupWrite =
      rqptr->rqAuth.VmsUserProfile =
      rqptr->rqAuth.VmsUserScriptAs =
      rqptr->rqAuth.WorldCan = 0;

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Place the VMS-hashed password into the pointed to quadword.  This CANNOT be
used to hash the UAF password as it does not use the UAF entry salt, etc. 
Force both username and password to upper-case before hashing.
*/ 

AuthGenerateHashPassword
(
char *UserName,
char *Password,
unsigned long *HashedPwdPtr
)
{
   static char  PasswordUpperCase [AUTH_MAX_PASSWORD_LENGTH+1],
                UserNameUpperCase [AUTH_MAX_USERNAME_LENGTH+1];
   static $DESCRIPTOR (PasswordDsc, PasswordUpperCase);
   static $DESCRIPTOR (UserNameDsc, UserNameUpperCase);

   int  status;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthGenerateHashPassword() !&Z !#*",
                 UserName, strlen(Password));

   zptr = (sptr = UserNameUpperCase) + sizeof(UserNameUpperCase)-1;
   for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   UserNameDsc.dsc$w_length = sptr - UserNameUpperCase;

   zptr = (sptr = PasswordUpperCase) + sizeof(PasswordUpperCase)-1;
   for (cptr = Password; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   PasswordDsc.dsc$w_length = sptr - PasswordUpperCase;

   status = sys$hash_password (&PasswordDsc, UAI$C_PURDY_S, 0,
                               &UserNameDsc, HashedPwdPtr);

   return (status);
}

/****************************************************************************/
/*
Place the MD5 Digest upper and lower case passwords into to pointed to
two 16-byte arrays.  This is the 128 bit, binary digest, NOT the hexadecimal
version.  See note on upper and lower case versions in program comments.
*/ 

AuthGenerateDigestPassword
(
char *DatabaseName,
char *UserName,
char *Password,
unsigned char *A1DigestLoCasePtr,
unsigned char *A1DigestUpCasePtr
)
{
   int  status;
   char  *cptr, *sptr, *zptr;
   char PasswordUpCase [AUTH_MAX_PASSWORD_LENGTH+1],
        PasswordLoCase [AUTH_MAX_PASSWORD_LENGTH+1],
        UserNameUpCase [AUTH_MAX_PASSWORD_LENGTH+1],
        UserNameLoCase [AUTH_MAX_PASSWORD_LENGTH+1];

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthGenerateDigestPassword() !&Z !&Z !#*",
                 DatabaseName, UserName, strlen(Password));

   zptr = (sptr = PasswordUpCase) + sizeof(PasswordUpCase)-1;
   for (cptr = Password; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   zptr = (sptr = PasswordLoCase) + sizeof(PasswordLoCase)-1;
   for (cptr = Password; *cptr && sptr < zptr; *sptr++ = tolower(*cptr++));
   *sptr = '\0';
   zptr = (sptr = UserNameUpCase) + sizeof(UserNameUpCase)-1;
   for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   zptr = (sptr = UserNameLoCase) + sizeof(UserNameLoCase)-1;
   for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = tolower(*cptr++));
   *sptr = '\0';
   if (VMSok (status =
       DigestA1 (UserNameUpCase, DatabaseName, PasswordUpCase,
                 A1DigestUpCasePtr)))
       status = DigestA1 (UserNameLoCase, DatabaseName, PasswordLoCase,
                          A1DigestLoCasePtr);

   return (status);
}

/*****************************************************************************/
/*
Set string text according to capability vector in the in-memory authorization
information.  These may be different to the bits in the on-disk database,
reported by HTAdminCanString().
*/

char* AuthCanString
(
unsigned long CanFlags,
int Format
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthCanString() !UL", Format);

   if ((CanFlags & HTTP_METHOD_DELETE ||
        CanFlags & HTTP_METHOD_POST ||
        CanFlags & HTTP_METHOD_PUT) &&
       CanFlags & HTTP_METHOD_GET)
   {
      if (Format == AUTH_CAN_FORMAT_HTML)
         return ("read <B>+ write</B>");
      else
      if (Format == AUTH_CAN_FORMAT_LONG)
         return ("READ+WRITE");
      else
         return ("R+W");
   }
   else
   if ((CanFlags & HTTP_METHOD_DELETE ||
        CanFlags & HTTP_METHOD_POST ||
        CanFlags & HTTP_METHOD_PUT))
   {
      if (Format == AUTH_CAN_FORMAT_HTML)
         return ("write-only");
      else
      if (Format == AUTH_CAN_FORMAT_LONG)
         return ("WRITE");
      else
         return ("W");
   }
   else
   if (CanFlags & HTTP_METHOD_GET)
   {
      if (Format == AUTH_CAN_FORMAT_HTML)
         return ("read-only");
      else
      if (Format == AUTH_CAN_FORMAT_LONG)
         return ("READ");
      else
         return ("R");
   }
   else
   {
      if (Format == AUTH_CAN_FORMAT_HTML)
         return ("<I>none!</I>");
      else
      if (Format == AUTH_CAN_FORMAT_LONG)
         return ("");
      else
         return ("-");
   }
   return ("*ERROR*");
}

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

char* AuthSourceString
(
char *NamePtr,
unsigned long Value
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthSourceString() !&Z !UL", NamePtr, Value);

   switch (Value)
   {
      case AUTH_SOURCE_ACME         : return ("=ACME");
      case AUTH_SOURCE_AGENT        : return ("=agent");
      case AUTH_SOURCE_EXTERNAL     : return (""); 
      case AUTH_SOURCE_HTA          : return ("=hta");
      case AUTH_SOURCE_DIR_HTA      : return ("=@hta");
      case AUTH_SOURCE_HOST         : return ("=host");
      case AUTH_SOURCE_ID           : return ("=id");
      case AUTH_SOURCE_LIST         : return ("=list");
      case AUTH_SOURCE_DIR_LIST     : return ("=@list");
      case AUTH_SOURCE_NONE         : return ("");
      case AUTH_SOURCE_PROMISCUOUS  : return ("");
      case AUTH_SOURCE_SKELKEY      : return ("");
      case AUTH_SOURCE_VMS :
         if (NamePtr && strsame (NamePtr, AUTH_REALM_VMS, -1)) return ("");
         return ("=vms");
      case AUTH_SOURCE_WASD_ID      : return ("=wasd_id");
      case AUTH_SOURCE_WORLD        : return ("");
      case AUTH_SOURCE_X509         : return ("");
      case AUTH_SOURCE_RFC1413      : return ("");
      default :
         if (NamePtr && NamePtr[0]) return ("=?");
         return ("");
   }
}

/*****************************************************************************/
/*
Used when category WATCHing.
*/ 

AuthWatchCheck
(
REQUEST_STRUCT *rqptr,
char *Description,
char *SourceModuleName,
int SourceLineNumber
)
{
#if WATCH_CAT

   char  *cptr;
   char  Buffer [256];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthWatchCheck()");

   switch (rqptr->rqAuth.FinalStatus)
   {
      case AUTH_DENIED_BY_FAIL : cptr = "DENIED_BY_FAIL"; break;
      case AUTH_DENIED_BY_GROUP : cptr = "DENIED_BY_GROUP"; break;
      case AUTH_DENIED_BY_HOSTNAME : cptr = "DENIED_BY_HOSTNAME"; break;
      case AUTH_DENIED_BY_HOUR : cptr = "DENIED_BY_HOUR"; break;
      case AUTH_DENIED_BY_LOGIN : cptr = "DENIED_BY_LOGIN"; break;
      case AUTH_DENIED_BY_LOGOUT : cptr = "DENIED_BY_LOGOUT"; break;
      case AUTH_DENIED_BY_NOCACHE : cptr = "DENIED_BY_NOCACHE"; break;
      case AUTH_DENIED_BY_OTHER : cptr = "DENIED_BY_OTHER"; break;
      case AUTH_DENIED_BY_PROTOCOL : cptr = "DENIED_BY_PROTOCOL"; break;
      case AUTH_DENIED_BY_USERNAME : cptr = "DENIED_BY_USERNAME"; break;
      case AUTH_DENIED_BY_REDIRECT : cptr = "DENIED_BY_REDIRECT"; break;
      case AUTH_PENDING : cptr = "AUTH_PENDING"; break;
      default : WriteFao (cptr = Buffer, sizeof(Buffer), NULL,
                          "%!&M", rqptr->rqAuth.FinalStatus);
   }

   WatchThis (rqptr, SourceModuleName, SourceLineNumber, WATCH_AUTH,
              "CHECK !AZ !&S !AZ",
              Description, rqptr->rqAuth.FinalStatus, cptr);

#endif /* WATCH_CAT */
}

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

