# WASD Hypertext Services - Technical Overview

## 12 - Conditional Configuration

> 12.1 - Conditional Syntax
> 12.2 - Conditional Keywords
> 12.3 - Examples

[next] [previous] [contents] [full-page]

Mapping (HTTPD$MAP) and authorization (HTTPD$AUTH) rules may be conditionally applied depending on request, server or other charactersistics. These include

  server host name, port
  client IP address and host name
  browser-accepted content-types, character sets, languages, encodings
  browser identification string
  scheme ("http:" or "https:", i.e. is it a secure request?)
  HTTP method (GET, POST, etc.)
  request path, query string, cookie data, refering page
  virtual host:port specified in request header
  system information (hardware, Alpha/VAX, node name, VMS version, etc.)
  local time
  random number generation

Conditionals may be nested up to a maximum depth of eight, are not case sensitive and generally match via string comparison, although some tests are performed as boolean operations, by converting the conditional parameter to a number before comparison, and IP address parameters will accept a network mask as well as a string pattern.

###
String Matching

The basis of much conditional decision making is string pattern matching. Both wildcard and regular expression based pattern matching is available (11 - String Matching). Wildcard matching in conditional tests is greedy. Regular expression matching, in common with usage throughout WASD, is differentiated from wildcard patterns using a leading "^" character.

###
12.1 - Conditional Syntax

Conditional expressions and processing flow structures may be used in the following formats. Conditional and rule text may be indented for clarifying structure.

      if (condition) then apply rest of line

      if (condition)
         then apply one
         or more rules
         up until the corresponding ...
      endif

      if (condition)
         then apply one
         or more rules
      else
         apply one or more other rules
         up until the corresponding ...
      endif

      if (condition)
         then apply one
         or more rules
      elif (condition)
         apply one or more other rules
         in a sort or case statement
      else
         a possible default rule or rules
         up until the delimiting
      endif

Logical operators are also supported, in conjunction with precedence ordering parentheses, allowing moderately complex compound expressions to be applied in conditionals.

  ! logical negation
  && logical AND
  || logical OR

There are two more conditional structures that allow previous decisions to be reused. These are unif and the ifif. The first unconditionally includes rules regardless of the current state of execution. The second resumes execution only if the previous if or elif expression was true. The else statement may also be used after an unif to continue only if the previous expression was false. The purpose of these constructs are to allow a single decision statement to include both conditional and unconditional rules.

      if (condition)
         then apply one
         or more rules
      unif
         apply this block of rules
         unconditionally
      ifif
         applied only if the original
         if expression was evaulated as true
      unif
         apply another block of rules
         unconditionally
      else
         and this block of rules
         only if the original if was false
      endif

> CAUTION

> Conditional syntax is checked at rule load time (either server startup or reload). Basic errors such as unknown keywords and unbalanced parentheses or structure statements will be detected and reported to the corresponding Admin Menu report and to the server process log. Unless these reports are checked after modifying rule sets syntax errors may result in unexpected mappings or access. Although the server cannot determine the correct intent of an otherwise syntactically correct conditional, if it encounters an unexpected but detectable condition during processing it aborts the request, supplying an appropriate error message.

###
12.2 - Conditional Keywords

The following keywords provide a match between the corresponding request or other value and a string immediately following the delimiting colon. White space or other reserved characters may not be included unless preceded by a backslash. The actual value being used in the conditional matching may be observed using the mapping item of the WATCH facility (19 - WATCH Facility).

Conditional Keywords

Keyword | Description
| accept: | Browser-accepted content types as listed in the "Accept:" request header field. Same string as provided in CGI variable HTTP_ACCEPT.
| accept-charset: | Browser-accepted character sets as listed in the "Accept-Charset:" request header field. CGI variable HTTP_ACCEPT_CHARSET. | accept-encoding: | Browser-accepted content encoding as listed in the "Accept-Encoding:" request header field. CGI variable HTTP_ACCEPT_ENCODING. | accept-language: | Browser language preferences as listed in the "Accept-Language:" request header field. CGI variable HTTP_ACCEPT_LANGUAGE. | authorization: | The raw authorization string from the request header, if any supplied. This could be simply used to test whether it has been supplied or not. | callout: | Simple boolean value. If a script callout is in progress (see "Scripting Overview, CGI Callouts".) it is true, otherwise false. | client_connect_gt: | An integer representing the current network connections (those currently being processed plus those currently being "kept alive") for the particular client represented by the current request. If greater than this value returns true, otherwise false. See 6.5 - Client Concurrency. | cluster_member: | If the supplied node name is (perhaps currently) a member of the cluster (if any) the server may be executing on. | command_line: | The command line qualifiers and parameters used when the server image was activated. | cookie: | Raw cookie data as the text string provided in "Cookie:" request header field. CGI variable HTTP_COOKIE. | decnet: | Whether DECnet is active on the system and which version is available. This value will be 0 if not active, 4 if PhaseIV or 5 is PhaseV. | document_root: | The DOCUMENT_ROOT CGI variable SET using the map=root=<string> mapping rule. | forwarded: | Proxy/gateway host(s) request forwarded by, as specified in request header field "Forwarded:". CGI variable HTTP_FORWARDED. | host: | The host (and optionally port) specified in request header "Host:" field. This is used by all modern browsers to provide virtual host information to the server. CGI variable HTTP_HOST. | jpi_username: | The account username the server is executing as. | mapped_path: | The path resulting from mapping (phase 2 if script path involved) from which the path-translated is derived. | notepad: | Information (strings) stored using the SET notepad= mapping rule. See Notepad: Keyword. | ods: | Specified as 2 or 5 (Extended File System), or as SRI file name encoding (MultiNet NFS and others) PWK encoding (PATHWORKS 4/5), ADS encoding (Advanced Server / PATHWORKS 6), SMB encoding (Samba - same as ADS). | pass: | A numeric value, 1 or 2, representing the first or second pass (if a script component was parsed) through the path mapping rules. Will be zero at other times. | path-info: | Path specified in the request line. CGI variable PATH_INFO. | path-translated: | VMS translation of path-info. Available after rule mapping (i.e. during authorization rule processing). | query-string: | Query string specified in request line. Same information as provided in CGI variable QUERY_STRING. | rand: | Value from a random number generator. See Rand: Keyword. | redirected: | If a request has been internally redirected (13.4.2 - REDIRECT Rule) this conditional will be non-zero. Can be used as a boolean or with a digit specified. | referer: | URL of refering page as provided in "Referer:" request header field. CGI variable HTTP_REFERER. | regex: | Simple boolean value. If configuration directive [RegEx] is enabled (and hence regular expression string matching, 11 - String Matching) this will be true. | remote-addr: | Client IP address. Same as provided as CGI variable REMOTE_ADDR. As with all IP addresses used for conditional testing this may be wildcard string match or network mask expressed as address/mask-length (see Host Addresses). | remote-host: | Client host name if name resolution enabled, otherwise the IP address (same as remote-addr). CGI variable REMOTE_HOST. | request: | Detect the presence of specific or unknown request fields. See Request: Keyword. | request-method: | HTTP method ("GET", "POST", etc.) specified in the request line. CGI variable REQUEST_METHOD. | request-scheme: | Request protocol as "http:" or "https:". CGI variable REQUEST_SCHEME. | restart: | A numeric value, zero to maximum, representing the number of times path mapping has been SET map=restart. Can be used as a boolean or with a digit specified. | script-name: | After the first pass of rule mapping (script component resolution), or during authorization processing, any script component of the request URI. | server-addr: | The service IP address. CGI variable SERVER_ADDR. This may be wildcard string match or network mask expressed as address/mask-length. | server_connect_gt: | An integer representing the current server network connections (those currently being processed plus those currently being "kept alive"). If greater than this value returns true, otherwise false. | server_process_gt: | An integer representing the current server requests in-progress. If greater than this value returns true, otherwise false. | server-name: | The (possibly virtual) server name. This may or may not exactly match any string provided via the host keyword. CGI variable SERVER_NAME. | server-port: | The (possibly virtual) server port number. CGI variable SERVER_PORT. | server-software: | The server identification string, including the version. For example "HTTPd-WASD/8.0.0 OpenVMS/AXP SSL". CGI variable SERVER_SOFTWARE. | service: | This is the composite server name plus port as server-name:port. | ssl: | Simple boolean value. If request is via Secure Sockets Layer then this will be true. | syi_arch_name: | System information; CPU architecture of the server system, "Alpha", "IA64" or "VAX". | syi_hw_name: | System information; hardware identification string, for example "AlphaStation 400 4/233". | syi_nodename: | System information; the node name, for example "KLAATU". | syi_version: | System information; VMS version string, for example "V7.3". | tcpip: | A string derived from the UCX$IPC_SHR shareable image. It looks something like this "Compaq TCPIP$IPC_SHR V5.1-15 (11-JAN-2001 02:28:33.95)" and comprises the agent (Compaq, MultiNet, TCPware, unknown), the name of the image, the version and finally the link date. | time: | Compare to current system time. See Time: Keyword. | trnlnm: | Translate a logical name. See Trnlnm: Keyword. | user-agent: | Browser identification string as provided in "User-Agent:" request header field. CGI variable HTTP_USER_AGENT. | x-forwarded-for: | Proxied client name or address as provided in "X-Forwarded-For:" request header field. CGI variable HTTP_X_FORWARDED_FOR.

###
Notepad: Keyword

The request notepad is a string storage area that can be used to store and retrieve ad hoc information during path mapping and subsequent authorization processing. The notepad contents can be changed using the SET notepad=<string> or appended to using SET notepad=+<string> (13.4.5 - SET Rule). These contents then can be subsequently detected using the notepad: conditional keyword (or the obsolescent 'NO' mapping conditional) and used to control subsequent mapping or authorization processing.

> NOTE

> Notepad information persists across internal redirection processing (13.4.2 - REDIRECT Rule) and so may be used when the regenerated request is mapped and authorized. To prevent such information from unexpectedly interfering with internally redirected requests a notepad="" can be used to empty the storage area.

###
Rand: Keyword

At the commencement of each pass a new pseudo-random number is generated (and therefore remains constant during that pass). The rand: conditional is intended to allow some sort of distribution to be built into a set of rules, where each pass (request) generates a different one. The random conditional accepts two parameters, a modulas number, which is used to modulas the base number, and a comparison number, which is compared to the modulas result.

Hence the following conditional rules

      if (rand:3:0)
         do this
      elif (rand:3:1)
         do this
      else
         do this
      endif

would pseudo-randomly generate base numbers of 0, 1, 2 and perform the appropriate conditional block. Over a sufficient number of usages this should produce a relatively even distribution of numbers. If the modulas is specified as less than two (i.e. no distribution factor at all) it defaults to 2 (i.e. a distribution of 50%). Hence the following example should be the equivalent of a coin toss.

      if (rand:)
         heads
      else
         tails
      endif

###
Request: Keyword

Looks through each of the lines of the request header for the specified request field and/or value. This may be used to detect the presence of specific or unknown (to the server) request fields. When detecting a specified just field the name can be provided

      if (request:"Keep-Alive:*")

matching any value, or specific values can also be matched for

      if (request:"User-Agent:*Opera*")

Note that all request fields known to the server have a specific associated conditional keyword (i.e. "user-agent:" for the above example). To determine whether any request fields unknown to the server have been supplied use the request: keyword as in the following example.

      if (request:?)
         map * /cgi-bin/unknown_request_notify.com*
      endif

###
Time: Keyword

The time: conditional allows server behaviour to change according to the time of day, week, or even year. It compares the supplied parameter to the current system time in one of three ways.

1. The supplied parameter is in the form "1200-1759", which should be read as "twelve noon to five fifty-nine PM" (i.e. as a time range in minutes, generalized as hhmm-hhmm), where the first is the start time and the second the end time. If the current time is within that range (inclusive) the conditional returns true, otherwise false. If the range doesn't look correct false is always returned.

         if (time:0000-0000)
            it's midnight
         elif (time:0001-1159)
            it's AM
         elif (time:1200-1200)
            it's noon
         else
            it's PM
         endif

2. If the supplied parameter is a single digit it is compared to the VMS day of the week (1-Monday, 2-Tuesday ... 7-Sunday).

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

3. If the supplied string is not in either of the formats described above it is treated as a string match with a VMS comparision time (i.e. yyyy-mm-dd hh-mm-ss.hh).

         if (time:%%%%-05-*)
            it's the month of May
         endif

###
Trnlnm: Keyword

The trnlnm: conditional dynamically translates a logical name and uses the value. One mandatory and up to two optional parameters may be supplied.

      trnlnm:logical-name[;name-table][:string-to-match]

The logical-name must be supplied; without it false is always returned. If just the logical-name is supplied the conditional returns true if the name exists or false if it does not. The default name-table is LNM$FILE_DEV. When the optional name-table is supplied the lookup is confined to that table. If the optional string-to-match is supplied it is matched against the value of the logical and the result returned.

###
Host Addresses

Host names or addresses can be an alpha-numeric string (if DNS lookup is enabled) or dotted-decimal network address, a slash, then a dotted-decimal mask. For example "131.185.250.0/255.255.255.192". This has a 6 bit subnet. It operates by bitwise-ANDing the client host address with the mask, bitwise-ANDing the network address supplied with the mask, then comparing the two results for equality. Using the above example the host 131.185.250.250 would be accepted, but 131.185.250.50 would be rejected. Equivalent notation for this rule would be "131.185.250.0/26".

###
12.3 - Examples

The following provides a collection of examples of conditional mapping and authorization rules illustrating the use of wildcard matching, network mask matching and the various formats in which the rules may be blocked.

1. This first example shows an EXEC mapping rule being applied to a path if the request query string contains the string "example".

         if (query-string:*example*) exec /* /cgi-bin/example/*

2. In this example a block of mapping statements is processed if the virtual service of the request matches that in the conditional, otherwise the block is skipped. Note the indentation to help clarify the structure.

         if (service:the.host.name:80)
            pass /web/* /dka0/the_host_name_web/*
            pass /graphics/* /dka100/graphics/*
            pass * "404 Resource not found."
         endif

3. This example a series of tests allow a form of case processing where the first to match will be processed and terminate the matching process. In this case if a match does not occur rule processing continues after the endif.

         if (service:the.host.name:80)
            pass /web/* /dka0/the_host_name_web/*
         elif (service:next.host.name:80)
            pass /web/* /dka0/next_host_name_web/*
         elif (service:another.host.name:80)
            pass /web/* /dka0/another_host_name_web/*
         endif
         pass /graphics/* /dka100/graphics/*
         pass * "404 Resource not found."

4. In this (somewhat contrived) example a nested test is used to check (virtual) server name and that the request is being handled via Secure Sockets Layer (SSL) for security. If it is not an informative message is supplied. The else and the quotes are not really required but included here for illustration.

         if (server-name:the.host.name)
            if (scheme:"https")
               pass /secure/* /dka0/the_host_name_web/secure/*
            else
               pass * /dka0/the_host_name_web/secure/only-via-SSL.html
            endif
         endif

5. This would be another way to accomplish a similar objective to example 4. This uses a negation operator to exclude access to successive mappings if not requesting via SSL.

         if (server-name:the.host.name)
            if (!SSL:)
               pass * /web/secure/only-via-SSL.html
            endif
            pass /secure/* /web/secure/*
            pass /other/* /web/other/*
            pass /web/* /web/web/*
            pass * "404 Resource not found."
         endif

6. This example shows the use of a compound conditional using the AND and OR operators. It also illustrates the use of a network mask. It will exclude all access to the specified path unless the request is originating from within a specified network (perhaps an intranet) or via SSL.

         if (path:/sensitive/* && !(remote-addr:131.185.250.0/24 || SSL:))
            pass * 404 "Access denied (SSL only)."
         endif

7. This example illustrates restricting authentication to SSL.

         [[*]]
         ["Your VMS password"=VMS]
         if (!request-scheme:https)
            * r+w,#0
         endif

8. Logical name translation may be used to dynamically alter the flow of rule interpretation.

         if (trnlnm:HTTPD_EXAMPLE)
            pass /* /example/*
         else
            pass /* /*
         endif

[next] [previous] [contents] [full-page]