/*****************************************************************************/ /* 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 3 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 realm, group, user names and plain-text password are available to the script for it's own authorization processing (a little like OPAQUE but server generates the challenge) o "NONE" any request, is not authenticated, all authorization details are left empty o "OPAQUE" a script generating it's own challenge/response and does all it's own "Authorization:" field processing (a little like EXTERNAL but server does nothing) 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 "TOKEN" a short-lived token issued in one authorisation space is used to authorise access in another o "VMS" SYSUAF authentication, via the SYSUAF, or restricted to accounts with identifiers prime/secondary days & 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 [AuthFailurePeriod] LGI_BRK_TMO [AuthFailureTimeout] 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 token 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. ["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". TOKEN ----- Reflect the authorisation applied in one environment to another using a short-lived token supplied as a cookie. Originally devised to allow controlled access to very large datasets without the overhead of SSL in the transmission but with the access credentials supplied in the privacy of an SSL connection. The cookie contains NO CREDENTIAL data at all and the agent manages an internal database (in global memory) of these so it can determine whether any supplied token is valid and when that token has expired. By default (and commonly) token authorisation occurs in non-SSL space (http:) and the credential authorisation in SSL space (https:) although it is possible to have the two differentiated by port only. See description in AUTHTOKEN.C 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 is to add "+OPAQUE" to the 'agent', as in the following example. ["a remote database"=REMOTE=agent+opaque] /path/authorized/by/remote/database/* An alternative is to 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__ 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__ ... 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 apply 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 WASD_CONFIG_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"==:: where o "Realm Description" optional browser username/password dialog o mandatory source of the authentication o mandatory type of authentication source o mandatory 'r' or 'r+w' access (optional world access) o 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=_:[:] 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. EMPTY LOGIN PROMPTS ------------------- Consecutive browser authentication dialogs can occur if a cache entry exists for the realm/username with a user revalidation timer expired but in the interim the user has closed the browser and so submits the initial request with no request header authorization field at all. This is bounced straight back and when valid credentials are entered and accessed the cached entry indicates there need to be revalidation resulting in an immediate browser reprompt for the same resource ... mighty irritating! Access to required authorisations if missing are counted (using rqptr->AuthRevalidateCount) and if registered the revalidation prompt is not induced. (This takes the place of the obsoleted pre-v10.2.1 "revalidate login cookie".) VERSION HISTORY --------------- 07-JAN-2019 MGD bugfix; AuthCompleted() and AuthNotComplete() to address AST delivery following request end and rundown 17-MAY-2017 MGD Authorize() move AuthorizeGuaranteeAccess() up-front to ensure access to guaranteed paths not only with failure 07-NOV-2013 MGD AuthCacheNeedsReval() so multiple cache entries for the same credentials do not trigger multiple revalidations 25-MAY-2013 MGD [AuthRevalidateLoginCookie] obsolete (in favour of ...) rqptr->AuthRevalidateCount to track empty authentication prompts preceding potential redundant revalidation prompt 05-MAY-2013 MGD read-only group can be specified as "*" for everyone else bugfix; AuthClientHostGroup() wildcard match result reversed 09-SEP-2012 MGD TOKEN authentication 28-AUG-2012 MGD bugfix; AuthorizeResponse() digest scheme bugfix; AuthParseAuthorization() digest no user password bugfix; disable digest depending on source realm 01-JUL-2012 MGD bugfix; (at least improve) caching of group write/read 28-NOV-2009 MGD AuthorizeResponse() allow agent reason for 403 10-OCT-2009 MGD AuthRestrictAny() single set of restricted days/hours retrieved by AuthVmsGetUai() as optionally specified by [AuthSYSUAFlogonType] and/or 'param="logon=.."' (default is still NETWORK) 24-NOV-2007 MGD add AGENT/OPAQUE force ACME on VMS V7.3 and later bugfix; agent mappings nocache 15-MAR-2007 MGD bugfix; agent mappings using VMS-USER: not being cached 24-NOV-2005 MGD OPAQUE realm to allow a script to completely generate it's own authentication challenge and process the authorization 31-JUL-2005 MGD refine data provided with authorization failure logging 11-JUN-2005 MGD bugfix; prevent expired SYSUAF password from being cached 05-JAN-2005 MGD bugfix; AuthorizeRealm() check for login cookie before revalidating new cache record credentials (jpp@esme.fr) 16-OCT-2004 MGD bugfix; AuthReadSimpleList() group member password check 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_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include #include /* not defined VAX C 3.2 (probably unnecessary now 11-MAY-2005) */ #ifndef UAI$C_PURDY_S #define UAI$C_PURDY_S 3 #endif /* 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.GroupReadStatus = \ rqptr->rqAuth.GroupWriteStatus = \ 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, AuthConfigSysUafUseACME, 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 int ToLowerCase[], ToUpperCase[]; extern unsigned long AuthHttpsOnlyVmsIdentifier, AuthWasdPwdVmsIdentifier, AuthWasdHttpsVmsIdentifier, AuthWasdReadVmsIdentifier, AuthWasdWriteVmsIdentifier; extern char *AuthConfigHtaDirectory, *AuthPromiscuousPwdPtr; extern char ErrorSanityCheck[], ServerHostPort[], SoftwareID[]; 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. */ void 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "Authorize() !&Z !&Z", PathBeingAuthorized, ProtectRulePtr); if (WATCHING (rqptr, WATCH_AUTH)) WatchThisOne = true; else WatchThisOne = false; if (WATCH_CAT && WatchThisOne) WatchThis (WATCHITM(rqptr), 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 (WATCHITM(rqptr), 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 (WATCHITM(rqptr), 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 = AuthorizeGuaranteeAccess (rqptr, PathBeingAuthorized); if (VMSnok (status)) status = AuthConfigSearch (rqptr, PathBeingAuthorized); 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); } if (VMSnok (status)) { /* not a controlled path */ if (ProtectRulePtr) { /****************/ /* mapping rule */ /****************/ sptr = AuthConfigParseProtectRule (rqptr, ProtectRulePtr, ProtectRuleLength); if (WATCH_CAT && WatchThisOne) WatchThis (WATCHITM(rqptr), 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 (WATCHITM(rqptr), 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 (WATCHITM(rqptr), 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 (WATCHITM(rqptr), 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 && rqptr->rqAuth.RealmDescrPtr[0] && rqptr->rqAuth.RealmDescrPtr != rqptr->rqAuth.RealmPtr; BOOL ShowRealmParam = rqptr->rqAuth.RealmParamPtr && rqptr->rqAuth.RealmParamPtr[0]; WatchThis (WATCHITM(rqptr), WATCH_AUTH, "!&?REALM-PROBLEM \r\r!&?PATH-PROBLEM \r\r\ [!AZ!AZ!AZ!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), ShowRealmParam ? "+\"" : "", ShowRealmParam ? (char*)rqptr->rqAuth.RealmParamPtr : "", ShowRealmParam ? "\"" : "", 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! */ /*************************************************/ 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 (WATCHITM(rqptr), 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 (WATCHITM(rqptr), WATCH_AUTH, "ACCESS external:!AZ", AuthCanString (rqptr->rqAuth.GroupCan, AUTH_CAN_FORMAT_SHORT)); status = AuthParseAuthorization (rqptr); if (!rqptr->rqAuth.PathParameterPtr || !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_OPAQUE) { /************************************/ /* opaquely (don't care) authorized */ /************************************/ if (WATCH_CAT && WatchThisOne) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "ACCESS opaque:N/A"); 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 (WATCHITM(rqptr), WATCH_AUTH, "SYSUAF SSL only"); else WatchThis (WATCHITM(rqptr), 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_TOKEN) { /*********/ /* token */ /*********/ rqptr->rqAuth.Scheme = AUTH_SCHEME_TOKEN; strcpy (rqptr->rqAuth.Type, "TOKEN"); /* cannot be cached, the scheme must be invoked each time */ rqptr->rqAuth.NoCache = true; /* conjure up a unique identifier (username) for this request */ strcpy (rqptr->RemoteUser, "(TOKEN)"); rqptr->RemoteUserLength = 7; rqptr->rqAuth.FinalStatus = SS$_NORMAL; } else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509) { /********************/ /* X509 certificate */ /********************/ if (rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), 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 (rqptr->rqHeader.QueryStringLength && TOLO(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_OPAQUE) { /***********************************************/ /* opaque agent handles any required challenge */ /***********************************************/ /* 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+OPAQUE)"); rqptr->RemoteUserLength = 14; rqptr->rqAuth.FinalStatus = SS$_NORMAL; } 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. */ void AuthorizeRealm (REQUEST_STRUCT *rqptr) { BOOL HttpdLogout; int status; unsigned long Seconds, Remainder; unsigned long ResultTime [2]; char *cptr, *sptr; AUTH_CREC *acrptr, *acsrptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthorizeRealm() !&F !&S", &AuthorizeRealm, rqptr->rqAuth.FinalStatus); if (VMSnok (rqptr->rqAuth.FinalStatus)) { AuthorizeResponse (rqptr); return; } if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), 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++ = TOUP(*cptr); *sptr = '\0'; rqptr->RemoteUserLength = sptr - rqptr->RemoteUser; } if (rqptr->rqHeader.QueryStringLength && TOLO(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); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "AuthRevalidateCount !UL", rqptr->AuthRevalidateCount); /*********************/ /* lookup the record */ /*********************/ status = AuthCacheFindRecord (acsrptr, &acrptr); if (VMSok (status)) { /*******************/ /* existing record */ /*******************/ if (WATCHING (rqptr, WATCH_AUTH)) { WatchThis (WATCHITM(rqptr), WATCH_AUTH, "CACHE [!AZ!AZ;!AZ!AZ;!AZ!AZ] \ user:!AZ remote:!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->RemoteUser[0] ? acrptr->RemoteUser : "-", acrptr->ProxyUserName[0] ? acrptr->ProxyUserName : "-", acrptr->FailureCount, acrptr->AccessCount, acrptr->LastAccessSecondsAgo, acrptr->NoCache ? " NOCACHE!" : ""); } } if (VMSok (status) && !rqptr->rqAuth.NoCache && !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 */ /**********************************/ FaoToStdout ( "%HTTPD-I-AUTHFAILEXP, !20%D, !UL seconds, !UL failure!%s\n\ -AUTHFAILEXP-I-SERVICE, !AZ//!AZ\n\ -AUTHFAILEXP-I-CLIENT, !AZ\n\ -AUTHFAILEXP-I-USERNAME, \"!AZ\" in \"!AZ\"\n\ -AUTHFAILEXP-I-URI, !AZ !AZ\n", 0, acrptr->LastAccessSecondsAgo, acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ( "%HTTPD-I-AUTHFAILEXP, !UL seconds, !UL failure!%s\r\n\ -AUTHFAILEXP-I-SERVICE, !AZ//!AZ\r\n\ -AUTHFAILEXP-I-CLIENT, !AZ\r\n\ -AUTHFAILEXP-I-USERNAME, \"!AZ\" in \"!AZ\"\r\n\ -AUTHFAILEXP-I-URI, !AZ !AZ", acrptr->LastAccessSecondsAgo, acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "EXPIRY break-in evasion after !UL seconds and !UL failure!%s", acrptr->LastAccessSecondsAgo, acrptr->FailureCount); acrptr->FailureCount = 0; } else { /*******************************************/ /* within LGI_HID_TIM fail unconditionally */ /*******************************************/ acrptr->FailureCount++; acrptr->Password[0] = acrptr->DigestResponse[0] = '\0'; acrptr->GroupReadStatus = acrptr->GroupWriteStatus = 0; if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL break-in evasion !UL failure!%s 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'; acrptr->GroupReadStatus = acrptr->GroupWriteStatus = 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 (AuthCacheNeedsReval (rqptr, acrptr)) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL !UL exceeds user revalidate timeout !UL", acrptr->LastAccessMinutesAgo, rqptr->rqAuth.RevalidateTimeout); acrptr->Password[0] = acrptr->DigestResponse[0] = '\0'; acrptr->GroupReadStatus = acrptr->GroupWriteStatus = 0; rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN; AuthorizeFinal (rqptr); return; } } } /*********************/ /* revalidate cache? */ /*********************/ if (acrptr->LastAccessMinutesAgo >= rqptr->rqAuth.RevalidateTimeout) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL !UL exceeds cache revalidate timeout !UL", acrptr->LastAccessMinutesAgo, rqptr->rqAuth.RevalidateTimeout); acrptr->Password[0] = acrptr->DigestResponse[0] = '\0'; acrptr->GroupReadStatus = acrptr->GroupWriteStatus = 0; /* drop through to recheck authentication */ } rqptr->AuthRevalidateCount = 0; /************************/ /* 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.SysUafLogonType = acrptr->SysUafLogonType; rqptr->rqAuth.SourceRealm = acrptr->SourceRealm; rqptr->rqAuth.SourceGroupRead = acrptr->SourceGroupRead; rqptr->rqAuth.GroupReadStatus = acrptr->GroupReadStatus; rqptr->rqAuth.SourceGroupWrite = acrptr->SourceGroupWrite; rqptr->rqAuth.GroupWriteStatus = acrptr->GroupWriteStatus; rqptr->rqAuth.UserCan = acrptr->AuthUserCan; if (rqptr->rqAuth.RemoteUserLength = acrptr->AuthRemoteUserLength) { /* if an agent has substituted a VMS-USER: username */ strcpy (rqptr->rqAuth.RemoteUser, acrptr->AuthRemoteUser); rqptr->RemoteUserLength = acrptr->RemoteUserLength; strcpy (rqptr->RemoteUser, acrptr->RemoteUser); } if (rqptr->rqAuth.UserDetailsLength = acrptr->UserDetailsLength) { rqptr->rqAuth.UserDetailsPtr = VmGetHeap (rqptr, acrptr->UserDetailsLength); memcpy (rqptr->rqAuth.UserDetailsPtr, acrptr->UserDetails, acrptr->UserDetailsLength); } 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; if (rqptr->rqAuth.SourceGroupWrite) { /* there is at least one group following the realm */ AuthorizeGroupWrite (rqptr); return; } AuthorizeFinal (rqptr); return; } } else if (rqptr->rqAuth.Scheme == AUTH_SCHEME_NO401) { acrptr->AccessCount++; if (rqptr->rqAuth.SourceGroupWrite) { /* there is at least one group following the realm */ rqptr->rqAuth.GroupReadStatus = acrptr->GroupReadStatus; rqptr->rqAuth.GroupWriteStatus = acrptr->GroupWriteStatus; AuthorizeGroupWrite (rqptr); return; } 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++; if (rqptr->rqAuth.SourceGroupWrite) { /* there is at least one group following the realm */ rqptr->rqAuth.GroupReadStatus = acrptr->GroupReadStatus; rqptr->rqAuth.GroupWriteStatus = acrptr->GroupWriteStatus; AuthorizeGroupWrite (rqptr); return; } rqptr->rqAuth.FinalStatus = SS$_NORMAL; AuthorizeFinal (rqptr); return; } else if (rqptr->rqAuth.Scheme == AUTH_SCHEME_TOKEN) { if (rqptr->rqAuth.ResolvedRemoteUser) acrptr->DataBaseCount++; else acrptr->AccessCount++; if (rqptr->rqAuth.SourceGroupWrite) { /* there is at least one group following the realm */ rqptr->rqAuth.GroupReadStatus = acrptr->GroupReadStatus; rqptr->rqAuth.GroupWriteStatus = acrptr->GroupWriteStatus; AuthorizeGroupWrite (rqptr); return; } 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++; if (rqptr->rqAuth.SourceGroupWrite) { /* there is at least one group following the realm */ rqptr->rqAuth.GroupReadStatus = acrptr->GroupReadStatus; rqptr->rqAuth.GroupWriteStatus = acrptr->GroupWriteStatus; AuthorizeGroupWrite (rqptr); return; } 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) { if (AuthCacheNeedsReval (rqptr, acrptr)) { acrptr->Password[0] = rqptr->RemoteUserPassword[0] = '\0'; acrptr->GroupReadStatus = acrptr->GroupWriteStatus = 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_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "SKELETON-KEY has expired"); FaoToStdout ("%HTTPD-I-AUTHSKEL, !20%D, skeleton-key has expired\n", 0); if (OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ("%HTTPD-I-AUTHSKEL, skeleton-key has expired"); } 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_AUTH)) WatchThis (WATCHITM(rqptr), 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 : case AUTH_SOURCE_AGENT_OPAQUE : /* 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 (AuthConfigSysUafUseACME) { /* ACME routines are the default on VMS V7.3 and later */ InstanceGblSecIncrLong (&AccountingPtr->AuthVmsCount); rqptr->rqAuth.SysUafAuthenticated = true; /* after calling this function all will be ASYNCHRONOUS! */ AuthAcmeVerifyUser (rqptr); return; } 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 = AuthConfigHtaDirectory; 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 = AuthConfigHtaDirectory; 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_TOKEN : InstanceGblSecIncrLong (&AccountingPtr->AuthOtherCount); InstanceGblSecIncrLong (&AccountingPtr->AuthTokenCount); AuthTokenProcess (rqptr); 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(). */ void AuthorizeRealmCheck (REQUEST_STRUCT *rqptr) { int status; AUTH_CREC *acrptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthorizeRealmCheck() !&F !&S", &AuthorizeRealmCheck, rqptr->rqAuth.FinalStatus); if (WATCHING (rqptr, 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 (rqptr, status, NULL, 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->SysUafLogonType = rqptr->rqAuth.SysUafLogonType; 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), 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_AUTH)) WatchThis (WATCHITM(rqptr), 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. */ void AuthorizeGroupWrite (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthorizeGroupWrite() !8XL", rqptr->rqAuth.GroupWriteStatus); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "CHECK r+w group !AZ!AZ", rqptr->rqAuth.GroupWritePtr, AuthSourceString (rqptr->rqAuth.GroupWritePtr, rqptr->rqAuth.SourceGroupWrite)); if (rqptr->rqAuth.GroupWriteStatus) { /* status already established (i.e. from authorisation cache) */ AuthorizeGroupWriteCheck (rqptr); return; } switch (rqptr->rqAuth.SourceGroupWrite) { case AUTH_SOURCE_AGENT : case AUTH_SOURCE_AGENT_OPAQUE : 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 = AuthConfigHtaDirectory; else rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory; rqptr->rqAuth.GroupWriteStatus = AuthReadHtDatabase (rqptr, rqptr->rqAuth.GroupWritePtr, false); rqptr->rqAuth.DirectoryPtr = NULL; break; case AUTH_SOURCE_HOST: rqptr->rqAuth.GroupWriteStatus = AuthClientHostGroup (rqptr, rqptr->rqAuth.GroupWritePtr); break; case AUTH_SOURCE_ID : rqptr->rqAuth.GroupWriteStatus = 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 = AuthConfigHtaDirectory; else rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory; rqptr->rqAuth.GroupWriteStatus = AuthReadSimpleList (rqptr, rqptr->rqAuth.GroupWritePtr, false); rqptr->rqAuth.DirectoryPtr = NULL; break; case AUTH_SOURCE_RFC1413 : rqptr->rqAuth.GroupWriteStatus = AUTH_DENIED_BY_GROUP; break; case AUTH_SOURCE_TOKEN : rqptr->rqAuth.GroupWriteStatus = AUTH_DENIED_BY_GROUP; break; case AUTH_SOURCE_X509 : rqptr->rqAuth.GroupWriteStatus = 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. */ void AuthorizeGroupWriteCheck (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthorizeGroupWriteCheck() !&F !&S", &AuthorizeGroupWriteCheck, rqptr->rqAuth.GroupWriteStatus); if (WATCHING (rqptr, WATCH_AUTH)) AuthWatchCheck (rqptr, "r+w group", FI_LI); rqptr->rqAuth.FinalStatus = rqptr->rqAuth.GroupWriteStatus; 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. */ void AuthorizeGroupRead (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthorizeGroupRead() !8XL", rqptr->rqAuth.GroupReadStatus); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "CHECK r-only group !AZ!AZ", rqptr->rqAuth.GroupReadPtr, AuthSourceString (rqptr->rqAuth.GroupReadPtr, rqptr->rqAuth.SourceGroupRead)); if (rqptr->rqAuth.GroupReadStatus) { /* status already established (i.e. from authorisation cache) */ AuthorizeGroupReadCheck (rqptr); return; } /* 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 : case AUTH_SOURCE_AGENT_OPAQUE : 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 = AuthConfigHtaDirectory; else rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory; rqptr->rqAuth.GroupReadStatus = AuthReadHtDatabase (rqptr, rqptr->rqAuth.GroupReadPtr, false); rqptr->rqAuth.DirectoryPtr = NULL; break; case AUTH_SOURCE_HOST: rqptr->rqAuth.GroupReadStatus = AuthClientHostGroup (rqptr, rqptr->rqAuth.GroupReadPtr); break; case AUTH_SOURCE_ID : rqptr->rqAuth.GroupReadStatus = 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 = AuthConfigHtaDirectory; else rqptr->rqAuth.DirectoryPtr = rqptr->ConfigDirectory; rqptr->rqAuth.GroupReadStatus = AuthReadSimpleList (rqptr, rqptr->rqAuth.GroupReadPtr, false); rqptr->rqAuth.DirectoryPtr = NULL; break; case AUTH_SOURCE_RFC1413 : rqptr->rqAuth.GroupReadStatus = AUTH_DENIED_BY_GROUP; break; case AUTH_SOURCE_THEREST : rqptr->rqAuth.GroupReadStatus = SS$_NORMAL; break; case AUTH_SOURCE_TOKEN : rqptr->rqAuth.GroupReadStatus = AUTH_DENIED_BY_GROUP; break; case AUTH_SOURCE_X509 : rqptr->rqAuth.GroupReadStatus = 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. */ void AuthorizeGroupReadCheck (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthorizeGroupReadCheck() !&F !&S", &AuthorizeGroupReadCheck, rqptr->rqAuth.GroupReadStatus); rqptr->rqAuth.FinalStatus = rqptr->rqAuth.GroupReadStatus; if (WATCHING (rqptr, 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. */ void AuthorizeFinal (REQUEST_STRUCT *rqptr) { int status; AUTH_CREC *acrptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), 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 (rqptr, status, NULL, 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 */ /*****************/ if (rqptr->rqAuth.Scheme == AUTH_SCHEME_BASIC) { if (rqptr->rqAuth.SysUafPwdExpired) acrptr->Password[0] = '\0'; else 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++; } acrptr->AuthUserCan = rqptr->rqAuth.UserCan; acrptr->GroupReadStatus = rqptr->rqAuth.GroupReadStatus; acrptr->GroupWriteStatus = rqptr->rqAuth.GroupWriteStatus; /* add (any) user detail and VMS profile data */ if (!acrptr->DetailsUpdated) AuthCacheAddRecordDetails (rqptr, acrptr); if (acrptr->AuthRemoteUserLength = rqptr->rqAuth.RemoteUserLength) { /* if an agent has substituted a VMS-USER: username */ strcpy (acrptr->AuthRemoteUser, rqptr->rqAuth.RemoteUser); acrptr->RemoteUserLength = rqptr->RemoteUserLength; strcpy (acrptr->RemoteUser, rqptr->RemoteUser); } if (acrptr->FailureCount) { /* just let the log know the access finally succeeded */ FaoToStdout ( "%HTTPD-I-AUTHFAILOK, !20%D, !UL failure!%s\n\ -AUTHFAILOK-I-SERVICE, !AZ//!AZ\n\ -AUTHFAILOK-I-CLIENT, !AZ\n\ -AUTHFAILOK-I-USERNAME, \"!AZ\" in \"!AZ\"\n\ -AUTHFAILOK-I-URI, !AZ !AZ\n", 0, acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ( "%HTTPD-I-AUTHFAILOK, !UL failure!%s\r\n\ -AUTHFAILOK-I-SERVICE, !AZ//!AZ\r\n\ -AUTHFAILOK-I-CLIENT, !AZ\r\n\ -AUTHFAILOK-I-USERNAME, \"!AZ\" in \"!AZ\"\r\n\ -AUTHFAILOK-I-URI, !AZ !AZ", acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); acrptr->FailureCount = 0; } } else { /*********************/ /* not authenticated */ /*********************/ acrptr->FailureCount++; rqptr->rqAuth.UserCan = acrptr->GroupReadStatus = acrptr->GroupWriteStatus = 0; if (rqptr->rqAuth.FinalStatus == AUTH_DENIED_BY_LOGOUT) { if (rqptr->rqAuth.RevalidateTimeout) { /* make the last access back in the year dot */ acrptr->LastAccessTickSecond = 0; } FaoToStdout ( "%HTTPD-I-AUTHLOGOUT, !20%D, !UL failure!%s\n\ -AUTHLOGOUT-I-SERVICE, !AZ//!AZ\n\ -AUTHLOGOUT-I-CLIENT, !AZ\n\ -AUTHLOGOUT-I-USERNAME, \"!AZ\" in \"!AZ\"\n\ -AUTHLOGOUT-I-URI, !AZ !AZ\n", 0, acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ( "%HTTPD-I-AUTHLOGOUT, !UL failure!%s\r\n\ -AUTHLOGOUT-I-SERVICE, !AZ//!AZ\r\n\ -AUTHLOGOUT-I-CLIENT, !AZ\r\n\ -AUTHLOGOUT-I-USERNAME, \"!AZ\" in \"!AZ\"\r\n\ -AUTHLOGOUT-I-URI, !AZ !AZ", acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); } else if (acrptr->FailureCount >= AuthFailureLimit) { FaoToStdout ( "%HTTPD-W-AUTHFAILIM, !20%D, !UL failure!%s\n\ -AUTHFAILIM-I-SERVICE, !AZ//!AZ\n\ -AUTHFAILIM-I-CLIENT, !AZ\n\ -AUTHFAILIM-I-USERNAME, \"!AZ\" in \"!AZ\"\n\ -AUTHFAILIM-I-URI, !AZ !AZ\n", 0, acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ( "%HTTPD-W-AUTHFAILIM, !UL failure!%s\r\n\ -AUTHFAILIM-I-SERVICE, !AZ//!AZ\r\n\ -AUTHFAILIM-I-CLIENT, !AZ\r\n\ -AUTHFAILIM-I-USERNAME, \"!AZ\" in \"!AZ\"\r\n\ -AUTHFAILIM-I-URI, !AZ !AZ", acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); } else { FaoToStdout ( "%HTTPD-W-AUTHFAIL, !20%D, !UL failure!%s\n\ -AUTHFAIL-I-SERVICE, !AZ//!AZ\n\ -AUTHFAIL-I-CLIENT, !AZ\n\ -AUTHFAIL-I-USERNAME, \"!AZ\" in \"!AZ\"\n\ -AUTHFAIL-I-URI, !AZ !AZ\n", 0, acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ( "%HTTPD-W-AUTHFAIL, !UL failure!%s\r\n\ -AUTHFAIL-I-SERVICE, !AZ//!AZ\r\n\ -AUTHFAIL-I-CLIENT, !AZ\r\n\ -AUTHFAIL-I-USERNAME, \"!AZ\" in \"!AZ\"\r\n\ -AUTHFAIL-I-URI, !AZ !AZ", acrptr->FailureCount, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, ClientHostString(rqptr), rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr, 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. */ void AuthorizeResponse (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), 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.SourceRealm != AUTH_SOURCE_HTA && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_EXTERNAL && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_OPAQUE) 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_RFC1413) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_TOKEN) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509) 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 (rqptr->rqHeader.QueryStringLength > 12 && strsame (rqptr->rqHeader.QueryStringPtr+12, "&goto=", 6)) ResponseLocation (rqptr, rqptr->rqHeader.QueryStringPtr+12+6, -1); else { /* supply a success message (ensure it has sufficient detail) */ rqptr->rqPathSet.ReportType = ERROR_REPORT_DETAILED; ReportSuccess (rqptr, MsgFor(rqptr,MSG_AUTH_LOGOUT)); } } else { /* otherwise generate an HTTP response */ if (rqptr->rqAuth.Scheme == AUTH_SCHEME_NO401) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_RFC1413) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_TOKEN) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509) 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.SourceRealm != AUTH_SOURCE_TOKEN) { /***************************/ /* NOT token authorisation */ /***************************/ if (rqptr->rqAuth.RealmParamPtr && rqptr->rqAuth.RealmParamPtr[0]) if (strsame (rqptr->rqAuth.RealmParamPtr, "TOKEN=", 6)) AuthTokenGenerate (rqptr); } if (rqptr->rqAuth.FinalStatus == AUTH_DENIED_BY_REDIRECT) { /***************/ /* redirection */ /***************/ /* either by agent or token or 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; 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; } 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_RFC1413) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_TOKEN) rqptr->rqResponse.HttpStatus = 403; else if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509) 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_AUTH)) { if (rqptr->rqAuth.UserDetailsLength) WatchData (rqptr->rqAuth.UserDetailsPtr, rqptr->rqAuth.UserDetailsLength); WatchThis (WATCHITM(rqptr), WATCH_AUTH, "CAN group:!AZ(!4XL) user:!AZ(!4XL) request:!AZ(!4XL) profile:!UL", AuthCanString (rqptr->rqAuth.GroupCan, AUTH_CAN_FORMAT_SHORT), rqptr->rqAuth.GroupCan, AuthCanString (rqptr->rqAuth.UserCan, AUTH_CAN_FORMAT_SHORT), rqptr->rqAuth.UserCan, AuthCanString (rqptr->rqAuth.RequestCan, AUTH_CAN_FORMAT_SHORT), rqptr->rqAuth.RequestCan, 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; if (rqptr->rqAuth.ReasonLength) ErrorGeneral (rqptr, rqptr->rqAuth.ReasonPtr, FI_NOLI); else ErrorGeneral (rqptr, "Forbidden to !AZ !AZ", rqptr->rqHeader.MethodName, rqptr->rqAuth.PathBeingAuthorizedPtr, FI_NOLI); 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthParseAuthorization() !&Z", rqptr->rqAuth.RequestAuthorizationPtr); if (!(cptr = rqptr->rqAuth.RequestAuthorizationPtr)) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL no request \"!AZ\"", rqptr->ServicePtr->ProxyAuthRequired ? "Proxy-Authorization:" : "Authorization:"); rqptr->AuthRevalidateCount++; 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++ = TOUP(*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_AUTH)) WatchThis (WATCHITM(rqptr), 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]) return (SS$_NORMAL); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthRestrictAny()"); /* can only use this authentication with "https:" (SSL)? */ if (acrptr->HttpsOnly && rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), 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.BeginNumTime[3]; if (acrptr->UaiAccessP == 0x00ffffff && acrptr->UaiAccessS == 0x00ffffff) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL not authorized to login from this source (!AZ)", AuthSysUafLogonType(acrptr->SysUafLogonType)); return (AUTH_DENIED_BY_HOUR); } else if (acrptr->UaiPrimeDays & DayOfWeekBit) { /* secondary day */ if (HourOfDayBit & acrptr->UaiAccessS) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL restricted SYSUAF secondary hours (!AZ)", AuthSysUafLogonType(acrptr->SysUafLogonType)); return (AUTH_DENIED_BY_HOUR); } } else { /* prime day */ if (HourOfDayBit & acrptr->UaiAccessP) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL restricted SYSUAF primary hours (!AZ)", AuthSysUafLogonType(acrptr->SysUafLogonType)); return (AUTH_DENIED_BY_HOUR); } } } return (SS$_NORMAL); } /****************************************************************************/ /* */ char* AuthSysUafLogonType (int LogonType) { /*********/ /* begin */ /*********/ switch (LogonType) { case 0 : return ("default"); case AUTH_LOGON_TYPE_NETWORK : return ("NETWORK"); case AUTH_LOGON_TYPE_BATCH : return ("BATCH"); case AUTH_LOGON_TYPE_LOCAL : return ("LOCAL"); case AUTH_LOGON_TYPE_DIALUP : return ("DIALUP"); case AUTH_LOGON_TYPE_REMOTE : return ("REMOTE"); default : return ("*ERROR*"); } } /*****************************************************************************/ /* 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), 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->ClientPtr->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->ClientPtr->IpAddressString; } else if (*lptr == '~') { /* authenticated user name */ lptr++; cptr = rqptr->RemoteUser; CheckedUser = FoundUser = true; /* drop through to the comparison routine */ } else if (TOLO(*lptr) == 'h' && strsame (lptr, "https:", 6)) { CheckedProtocol = FoundProtocol = true; if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS) RestrictedOnProtocol = false; while (*lptr && *lptr != ',') lptr++; continue; } else if (TOLO(*lptr) == 'h' && strsame (lptr, "http:", 5)) { CheckedProtocol = FoundProtocol = true; if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) RestrictedOnProtocol = false; while (*lptr && *lptr != ',') lptr++; continue; } else if (TOLO(*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->ClientPtr->IpAddressString, -1)) RestrictedOnHost = false; while (*lptr && *lptr != ',') lptr++; continue; } else { /* by default, alpha-numeric IP host name */ if (*lptr == '#') lptr++; cptr = rqptr->ClientPtr->Lookup.HostName; CheckedHost = FoundHost = true; /* drop through to the comparison routine */ } if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "!&Z !&Z", cptr, lptr); while (*cptr && *lptr && *lptr != ',') { if (*lptr == '*') { while (*lptr == '*') lptr++; while (*cptr && TOLO(*cptr) != TOLO(*lptr)) cptr++; } while (TOLO(*cptr) == TOLO(*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_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 (WATCHITM(rqptr), 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_AUTH)) WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthClientHostGroup() !&Z", HostGroup); /* backward-compatibility with kludge suggested on info-WASD */ if (HostGroup[0] == '$') return (SS$_NORMAL); /* 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->ClientPtr->Lookup.HostName, HostGroup)) return (SS$_NORMAL); else return (AUTH_DENIED_BY_GROUP); } status = TcpIpNetMask (rqptr, WATCH_AUTH, &HostGroup, &rqptr->ClientPtr->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 (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), 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, WASD_ROOT_LOCAL, sizeof(WASD_ROOT_LOCAL)-1) && !strsame (PathBeingAuthorized, HT_ROOT_LOCAL, sizeof(HT_ROOT_LOCAL)-1)) return (STS$K_ERROR); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), 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, WASD_ROOT_LOCAL, sizeof(WASD_ROOT_LOCAL)-1) && !strsame (PathBeingAuthorized, HT_ROOT_LOCAL, sizeof(HT_ROOT_LOCAL)-1)) return (STS$K_ERROR); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), 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 (WATCHALL, WATCH_MOD_AUTH, "AuthGenerateHashPassword() !&Z !#*", UserName, strlen(Password)); zptr = (sptr = UserNameUpperCase) + sizeof(UserNameUpperCase)-1; for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; UserNameDsc.dsc$w_length = sptr - UserNameUpperCase; zptr = (sptr = PasswordUpperCase) + sizeof(PasswordUpperCase)-1; for (cptr = Password; *cptr && sptr < zptr; *sptr++ = TOUP(*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 (WATCHALL, WATCH_MOD_AUTH, "AuthGenerateDigestPassword() !&Z !&Z !UL", DatabaseName, UserName, strlen(Password)); zptr = (sptr = PasswordUpCase) + sizeof(PasswordUpCase)-1; for (cptr = Password; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; zptr = (sptr = PasswordLoCase) + sizeof(PasswordLoCase)-1; for (cptr = Password; *cptr && sptr < zptr; *sptr++ = TOLO(*cptr++)); *sptr = '\0'; zptr = (sptr = UserNameUpCase) + sizeof(UserNameUpCase)-1; for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; zptr = (sptr = UserNameLoCase) + sizeof(UserNameLoCase)-1; for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = TOLO(*cptr++)); *sptr = '\0'; if (VMSok (status = DigestA1 (UserNameUpCase, DatabaseName, PasswordUpCase, A1DigestUpCasePtr))) status = DigestA1 (UserNameLoCase, DatabaseName, PasswordLoCase, A1DigestLoCasePtr); if (WATCH_MODULE(WATCH_MOD_AUTH)) { WatchDataDump (A1DigestUpCasePtr, 16); WatchDataDump (A1DigestLoCasePtr, 16); } 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 (WATCHALL, WATCH_MOD_AUTH, "AuthCanString() !8XL !UL", CanFlags, 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 + write"); 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 ("none!"); 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 (WATCHALL, 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_AGENT_OPAQUE : return ("=agent+opaque"); case AUTH_SOURCE_EXTERNAL : return (""); case AUTH_SOURCE_OPAQUE : 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 (""); case AUTH_SOURCE_TOKEN : return ("=token"); case AUTH_SOURCE_THEREST : return (""); /* read group only */ default : if (NamePtr && NamePtr[0]) return ("=?"); return (""); } } /*****************************************************************************/ /* Used when category WATCHing. */ void AuthWatchCheck ( REQUEST_STRUCT *rqptr, char *Description, char *SourceModuleName, int SourceLineNumber ) { #if WATCH_CAT char *cptr; char Buffer [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), 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 : FaoToBuffer (cptr = Buffer, sizeof(Buffer), NULL, "%!&M", rqptr->rqAuth.FinalStatus); } WatchThis (WATCH_ITM(rqptr), SourceModuleName, SourceLineNumber, WATCH_AUTH, "CHECK !AZ !&S !AZ", Description, rqptr->rqAuth.FinalStatus, cptr); #endif /* WATCH_CAT */ } /*****************************************************************************/