Provided by: dacs_1.4.28b-3ubuntu1_amd64 bug

NAME

       dacs.exprs - DACS expression language

DESCRIPTION

       These files are part of the DACS suite.

       DPL (the DACS programming language) is used in access control rules, its revocation list,
       and in configuration files. This programmability gives DACS maximum run-time
       configurability and flexibility. A DPL expression - or even a small program - may appear
       within predicate, allow, and deny elements of an access control rule, for example.  DPL is
       also accessible using the dacsexpr(1)[1] command, which can be used for writing scripts
       even for non-DACS applications.

       DPL, which is gradually evolving in mostly backward-compatible ways, is similar in many
       ways to Perl[2], PHP[3], Tcl[4] and its expressions look and behave much like C/C++
       expressions. The calling signatures for functions are reminiscent of those of Tcl, with
       literal or string arguments used to select a particular mode of operation or specify
       options. The syntaxes used for strings and variables have been influenced by various Unix
       shells. Our intent is for the language to feel familiar and be easy to use for the typical
       tasks at hand. We have tried not to be gratuitously different.

           Note
           The philosophy guiding the design of the DACS expression language is that its power
           should be limited to basic operations on elementary data types that can be expressed
           simply and evaluated efficiently, along with a collection of utility and higher-level
           functions, targeted for the tasks at hand, that hide complexity. This is why the
           language does not include much in the way of control flow statements - our feeling is
           that complicated expressions are more likely to introduce mistakes, which can easily
           result in access control rules not working as intended.

           While fleshing out the language is not a priority, expression syntax and the set of
           functions are being extended as necessary. An extensibility mechanism is planned that
           would let user-defined functions be loaded at run-time.

           While there are no immediate plans to do so, replacing the DACS expression language
           with a general-purpose extension language may eventually make sense.  Tcl and Perl
           would be leading contenders.

           Tip
           The dacsexpr(1)[1] utility can be useful for learning, testing, and debugging DPL.

   Expression Syntax
       Expression evaluation consists of a lexical analysis stage, in which the expression is
       broken into a sequence of tokens, followed by evaluation of the tokens.

       Expression syntax is checked before an expression is evaluated. Any syntactic or run-time
       evaluation error immediately terminates evaluation of the top-level expression and returns
       a False result.

           Note
           Because files containing expressions are local to the DACS site on which they appear
           (i.e., DACS does not copy them), they need not be portable across sites. This means
           that any DACS jurisdiction is free to customize or extend these expressions at will
           since they do not have to be understood or executed by any other jurisdiction.

       Comments
           Three comment styles are recognized:

           •   The /* ... */ C style comment syntax, which does not nest;

           •   The // syntax of C++, where the remainder of the line following the token is
               ignored; and

           •   The # syntax of shells and many scripting languages, provided the # is either at
               the beginning of a line or appears after whitespace, where the remainder of the
               line following the token is ignored. Note that escaping the # by preceding it with
               a backslash prevents the text that follows from being interpreted as a comment.
               For example, this will result in a syntax error if the backslash is omitted:

                   > ${foo:? \#xxx}
                   " #xxx"

           Here are examples of all three styles:

               /*
                * This is a comment
                */

               // This is another comment

               ${x} = 17;  # And one last comment

           Additionally, when expressions are parsed in the context of an XML document (such as
           in an access control rule), the XML comment syntax can be used (<!-- A comment -->).
           Such comments can span multiple lines.

               <!--
               Comment out this clause for now...
               <Auth id="authx">
               STYLE "expr"
               CONTROL "sufficient"
               </Auth>
               -->

   Basic Data Types
       The following basic data types are supported:

       integer
       int
           , Integers are represented internally as a C/C++ long int. Maximum and minimum values
           are platform dependent. Integers are written in the C-style syntax; for example,
           -1958, 0377 (octal), and 0xABC (hexadecimal, upper or lower case).

       real
       double
           , Reals are represented internally as a C/C++ double. Maximum and minimum values are
           platform dependent. A real constant is an optional sequence of decimal digits
           (possibly signed) followed by a period and 1) at least one digit or 2) an 'e' or 'E'
           followed by at least one digit.

       string
           A string is a sequence of characters enclosed between matching single or double quotes
           (e.g., 'Hello world'). Interpolation of variables occurs within double quotes but not
           single quotes. C-style character escape codes and octal numeric escape codes are
           understood (e.g., "\t", "\010") and either quote character (e.g., 'It\'s here') and
           the backslash character (e.g., "\\") can be quoted. An unrecognized quoted character
           is mapped to that character (e.g., "\x" is "x"). Character strings are limited in
           length by available memory and are represented internally as a null-terminated vector.

               Note
               •   Because a string is null-terminated, it cannot contain a NUL character. Also,
                   functions that deal with strings usually do not expect (most) ASCII control
                   characters to appear in a string. Therefore a string that contains an
                   unprintable character (a character that is not a tab, newline, carriage
                   return, and that does not satisfy isprint(3)[5]) automatically becomes a
                   bstring (see below).

               •   Because DACS configuration files are XML documents, characters special to XML
                   must be properly escaped within them. In particular, an ampersand character
                   must always be written as &amp; and a < character must be written as &lt;. For
                   example, the query string a=1&b=2 might be used as

                       ${Foo::QUERY_STRING} = "a=1&amp;b=2"

           Variable references may occur within a (double-quoted) string; the value of the
           variable reference is interpolated at that point. If ${Foo::bar} is "hello", then the
           value of "${Foo::bar}, world" is "hello, world".

               Note
               The first expression is invalid and must be written as the second:

                   foo"baz"
                   foo."baz"

       binary
       bstring
           , A binary string is a sequence of bytes, limited in length by available memory. Most
           language operators cannot be applied to data of this type without converting it to
           another type (e.g., two bstring values cannot be added using the + operator). A binary
           string is not necessarily portable across systems.

               > "\0\1\2"
               "000102"

       bareword
           This type is a "literal word" much like Perl's barewords. A bareword consists of an
           initial alphabetic character, followed by any number of alphanumerics and underscores.
           The resulting lexical token must have no other interpretation in the language and is
           treated as if it were a quoted string. This syntactic convenience makes these two
           function calls equivalent:

               file(test, "-e", foo)
               file("test", "-e", "foo")

           These two expressions are equivalent and yield "foobaz":

               foo."baz"
               foo.baz

       bool
           The boolean values True and False are either the result of evaluating certain
           expressions or are implicit argument values. This is really a pseudo-type because it
           is represented internally as an integer. In the former case, the integer 1 is the
           canonical "true" value and 0 is considered "false". In the latter case, there are
           several possibilities. If the argument is an integer or real, any non-zero value is
           considered True and 0 is considered False. For the string data type, both the empty
           string (i.e., "") and the string "0" are considered False and anything else is
           considered True. A binary string is equivalent to False if and only if its length is
           zero. An empty list of either variety ("[]" or "{}") is False, while any non-empty
           list or alist is True.

       Automatic type conversion is performed when necessary and possible. In general, a "lower"
       type is promoted to a "higher" type (e.g., an integer is converted to a real when it is
       added to a real) and the result is of the higher type. Arguments to function calls are
       automatically coerced to the required types. A printable binary string (one not containing
       any "troublesome" control characters) can be converted into a string without loss; other
       binary strings are converted into a hexadecimal string representation for assignment or
       display.

       The C/C++ unary cast operation is available for explicit type conversion. Not all
       conversions are supported (e.g., integer to binary and binary to string). These type names
       are case sensitive.

       The language includes the concept of the void type, which cannot be stored in a variable,
       used as an operand, or printed. Some functions are void, print()[6] for example. A value
       can be cast[7] to void.

           Note
           Support for binary data is only partially implemented.

   Variables and Namespaces
       Every variable exists within a namespace. Namespaces exist so that the same variable name
       can exist safely and without ambiguity in different contexts. They also serve to group
       together and name a set of closely related variables, and they make it easy for all
       variables in the set to be assigned a characteristic (such as being read-only). For
       example, CGI parameter values are automatically put in the Args namespace and variables
       automatically created by DACS are put in the DACS namespace. Namespaces address the
       problem of a parameter name that happens to have the same name as a variable created by
       DACS, for example. They also allow intermediate results to be stored in their own
       namespace, also avoiding the problem of clashing variable names.

       Variables are not declared in advance. The value of an uninitialized variable is the empty
       string, which is invalid in a numerical context, but variables should always be
       initialized before being used. Some variables are created automatically by DACS from the
       execution context (e.g., the value of a CGI parameter value, the identity of the client,
       an environment variable), as a side-effect of function evaluation, or by an assignment
       operator.

       The interpreter tries to maintain the natural type of a variable when possible, to avoid
       conversions to and from the string type.

       Variable Syntax
           A variable reference may have either of the following syntaxes:

               ${[namespace::]variable-name[:flags]}
               $[namespace::]variable-name

           For instance, the following refers to the value of a variable called JURISDICTION_NAME
           within the namespace called Conf:

               ${Conf::JURISDICTION_NAME}

           A variable called JURISDICTION_NAME within a different namespace could exist and would
           be completely distinct.

           A namespace must begin with an alphabetic character and can be followed by any number
           of alphabetics, digits, dashes, and underscores. By convention, predefined namespaces
           begin with an upper case letter.

           If the namespace is omitted from a variable reference, a default namespace is implied
           (see below).

           A variable name consists of any number of alphanumeric characters (upper and lower
           case), and characters from this set:

               -_.!~*'()

           Additionally, a "%" character that is followed by two hexadecimal characters (upper
           and lower case) is acceptable.

           If instead of a variable name the character "#" appears, the number of variables in
           the namespace is returned. If the namespace does not exist, 0 is returned. For
           example, the value of this variable reference is the number of variables in the Conf
           namespace:

               ${Conf::#}

           When the syntax with braces is used, a variable name may be followed by a colon and
           then one or more modifier flags that affect the processing of the variable.
           Referencing an invalid variable name or unknown namespace, or using an undefined
           modifier flag[8] is an error. Referencing an undefined variable yields the empty
           string.

           Variable names are case sensitive by default; namespaces are always case sensitive.

           User-defined variables and namespaces are not persistent. They disappear when their
           execution context terminates.

               Tip
               A variable reference may not contain any whitespace except when it appears after a
               ?  or +modifier flag[8].

               Tip
               Because many variable references do not include flags or use punctuation
               characters in the variable name, as a convenience the braces that surround a
               variable reference may be omitted in certain cases. This is only possible if the
               variable name begins with an alphabetic or an underscore, which can be followed by
               alphanumerics and underscores. A namespace may be specified, but flags are not
               permitted, although the special "#" construct is also allowed. The variable name
               ends with the first invalid character. For example, these pairs of variable
               references are equivalent:

                   ${myvar}
                   $myvar

                   ${foo::baz}
                   $foo::baz

               Note that the variable reference ${foo-17} has a valid but different
               interpretation if the braces are omitted.

       Variable Modifier Flags
           A variable reference may include one or more modifier flags that control how the
           reference is to be interpreted.

           The following modifier flags are recognized:

           e
               Exists: The "e" modifier flag is used to test whether the variable exists (has
               been defined). Instead of returning the value of the variable or causing an error,
               the value of the variable reference is the string "1" if the variable is defined,
               the empty string otherwise (equivalent to False).

           i
               Insensitive: When looking up the name of a variable, the default is to use a
               case-sensitive comparison for the variable name. To use a case-insensitive
               comparison instead, an "i" flag is used (e.g., ${FOO::i}). The namespace lookup is
               always case sensitive.

           n
               Non-empty: The "n" modifier flag tests whether the variable exists (has been
               defined) and is not the empty string (i.e., has zero length). Instead of returning
               the value of the variable or causing an error, the value of the variable reference
               is the string "1" if the variable is defined and is not the empty string,
               otherwise it is the empty string (equivalent to False).

           z
               Zero: The opposite of the "n" flag, instead of returning the value of the variable
               or causing an error, the value of the variable reference is the string "1" if the
               variable is undefined or the empty string, otherwise it is the empty string
               (equivalent to False).

           ?
               Default: The "?" modifier flag must appear last if it is used. The flag is
               immediately followed by zero or more characters. Its purpose is to associate a
               default value with the variable reference. If the variable is defined and is not
               empty, then the result of the variable reference is the value of the variable;
               otherwise, the result is the evaluation of the characters that follow the "?"
               flag. If no character follows the "?" flag, the empty string is indicated. The
               default may itself contain variable references, embedded spaces, etc., and is
               evaluated left to right. Any "}" characters appearing in the string before the
               last closing brace must be escaped by being preceded by a backslash.

           +
               Substitute: The "+" modifier flag must appear last if it is used. The flag is
               immediately followed by zero or more characters. Its purpose is to associate a
               substitute value with a variable reference. If the variable is defined and is not
               the empty string, then the result of the variable reference is the evaluation of
               the characters that follow the "+" flag; if the variable is undefined or is the
               empty string, the value of the variable reference is the empty string. If no
               character follows the "+" flag, the empty string is indicated. The substitute may
               itself contain variable references, embedded spaces, etc., and is evaluated left
               to right. Any "}" characters appearing in the string before the last closing brace
               must be escaped by being preceded by a backslash.
           The i can be combined with any other flag, but it must appear first. All other flags
           are mutually exclusive. Repetitions of a flag are ignored. An unrecognized flag raises
           an error condition.

           Consider these examples:

               ${Args::SCALE:?17}
               ${Foo::bar:i?${Bar::baz\}baz}
               "${DACS::QUERY:+?}${DACS::QUERY:?}"

           In the first example, if ${Args::SCALE} is undefined or empty, the value of the
           variable reference is "17" instead of the value of ${Args::SCALE}. In the second
           example, if ${Foo::bar} (case insensitive) is defined, the result is its value,
           otherwise the result is the value of the string "${Bar::baz}baz". In the third
           example, if ${DACS::QUERY} is defined and not empty, the value of the expression will
           be a question mark followed by the value of ${DACS::QUERY}. If ${DACS::QUERY} is
           undefined or empty, the value will be the empty string.

       Reserved Namespaces
           The following namespaces are predefined by DACS and reserved for particular uses. Some
           are read-only, which means that only DACS can create a variable or change the value of
           a variable in the namespace, except in certain contexts.

           Args
               Instantiated from query string arguments and the POST data stream (if the content
               type is application/x-www-form-urlencoded or multipart/form-data). This namespace
               is read-only.

           Argv
               Instantiated by dacsexpr from the command line flags passed to the script. The
               value of ${Argv::0} is the name of the file being processed, with - signifying the
               standard input. The next argument, if any, will be ${Argv::1}, and so on. This
               namespace is read-only.

           Auth
               Used by dacs_authenticate(8)[9] during authentication processing.

           Conf
               Instantiated with configuration directive variables, this namespace is made
               read-only after configuration processing. See dacs.conf(5)[10].

           Cookies
               This namespace is instantiated with HTTP cookies that were submitted with a
               request. For security reasons, those associated with DACS credentials are
               excluded. This is a read-only namespace. If a cookie named foo is sent by a user
               agent, an access control rule can access the cookie value as ${Cookies::foo}.

           DACS
               Instantiated with DACS-specific variables. It is read-only. See dacs_acs(8)[11].

           Env
               For web services, instantiated with the standard Apache environment variables; for
               other programs, instantiated from the execution environment (environ(7)[12]). It
               is read-only.

           ExecEnv
               Used by exec()[13].

           LDAP
               Used by local_ldap_authenticate.

           Temp
               Unless disabled or redefined at build-time, variable references that do not
               include a namespace are associated with this namespace as a convenience. The
               following three expressions are therefore equivalent:

                   ${foo} = 17
                   ${Temp::foo} = 17
                   $foo = 17

               In a future release, this mechanism may be generalized to provide a run-time means
               of selecting the default namespace.

   Lists, Alists, and Arrays
       DPL supports more complicated data structures based on lists and associative lists. These
       types may also be combined and composed so that programmers can create lists of lists, and
       so on.

       Lists
           A list is composed of zero or more basic data types or sub-lists. A list is created
           using the following syntax:

               LIST     -> "[" "]" | "[" LIST-ELS "]"
               LIST-ELS -> EL | EL "," LIST-ELS
               EL       -> BASIC-DATA-TYPE | LIST

           A list can also be created through the list()[14] function.

           Here is a list consisting of four elements:

               [1, "one", 1.000, ["one sublist"]]

           The length()[15] function returns the number of elements in a list.

           A list can be assigned to a variable:

               $mylist = [1, 2, 3, 4, 5, 6]
               $mylist_copy = $mylist

               Note
               These two statements are equivalent:

                   $mylist = ["one", "two"]
                   ${mylist} = ["one", "two"]

               And so are these two:

                   $mylist[0]
                   ${mylist}[0]

               Modifier flags therefore do not apply to list elements, only the list variable.

           A list or element can be appended to another list using the "." ("dot") concatenation
           operator. List elements can be rotated using the ">>" ("shift left") or "<<" ("shift
           right") operators. The compound assignment operator versions of these operators may
           also be used.

               > $mylist=[orange, apple, grape]
               [orange,apple,grape]
               > $mylist . banana
               [orange,apple,grape,banana]
               > $mylist .= [prune,plum]
               [orange,apple,grape,prune,plum]
               > $mylist .= [[lime]]
               [orange,apple,grape,prune,plum,[lime]]
               >$mylist << 1
               [apple,grape,banana,prune,plum,[lime],orange]

           A list element can be referenced using a subscript between zero and one less than the
           number of elements in the list:

               > $mylist = [1, 2, 3, 4, 5, 6]; length($mylist)
               6
               > $mylist[0]
               1

           It is an error to reference a non-existent list element using a subscript. (Note:
           additional syntax may be introduced to provide a way to declare lists and arrays.)

           The values of one or more list elements are selected by a list reference, which
           includes the simple subscript case just described. The value of a list reference is
           either a basic data type or a list.

               LIST-REFERENCE       -> "[" LIST-REFERENCE-ELS "]"
               LIST-REFERENCE-ELS   -> EMPTY | LIST-REFERENCE-EL | LIST-REFERENCE-EL "," LIST-REFERENCE-ELS
               LIST-REFERENCE-EL    -> EXP | LIST-REFERENCE-SLICE
               LIST-REFERENCE-SLICE -> EXP ".." EXP

               LIST-REFERENCE-SEQ   -> LIST-REFERENCE | LIST-REFERENCE LIST-REFERENCE-SEQ

           An EXP must evaluate to a non-negative integer value. The ".." ("dotdot") range
           operator specifies a sequence of subscripts between the value to its left and the
           value to its right, inclusive. The left value must not be greater than the right
           value. If "#" appears to the right of the ".." operator, the number of elements in the
           list variable or the intermediate list computation is implied. A "#" may not appear to
           the left of ".." and may not be used in an expression (e.g., "#-2" is invalid). As in
           a function's argument list, a comma is not treated as the comma operator in this
           context. Note that it is not an error to specify non-existent elements in a slice;
           therefore it is possible for the value of a list reference to be the empty list.

               > $i=1, $mylist[$i]
               2
               > $mylist[1,3,5]
               [2,4,6]
               > $mylist[0..2,4]
               [1,2,3,5]
               > $mylist[2..#]
               [3,4,5,6]
               > $mylist[0..3]
               [1,2,3,4]

           The dotdot operator can also be used to construct an element of a list or alist:

               > $a = [1, 4..8, 10, 12, 13]
               [1,4..8,10,12,13]
               > length($a)
               5
               > $b = [0..2,4]; listref($a, $b)
               [1,4..8,10,13]

           Whether a "[" ... "]" sequence introduces a list constructor or list reference depends
           on the context; if it appears to the right of a list variable, list constructor, a
           function that returns a list, or another list reference, it is treated as a list
           reference.

           List references can be composed as a right-associative operation. For example:

               > $a = [[1,2,3], [4,5,6], [7,8,9]]
               [[1,2,3], [4,5,6], [7,8,9]]
               > $a[1][1]
               5
               > $a[0..1][1..2]
               [[4,5,6]]
               > $a[0..1][1..2][0][2]
               6

               Tip
               Individual characters and sequences of characters of a string-valued expression
               can be selected using strchars()[16], which uses a similar syntax.

               Note
               •   The list constructor and list reference syntax has not yet been integrated
                   with the expression grammar[17].

               •   A list value can also be assigned to a subscripted variable; only a single
                   subscript is allowed, however, and the referenced element must already exist:

                       > $a = [1, 2, 3]
                       [1,2,3]
                       > $a[2] = 17
                       17
                       > $i = 1
                       1
                       > $a[$i] = [10, 11]
                       [10,11]
                       > $a
                       [1,[10,11],17]

       Alists
           DPL's associative list, or "alist", is similar to Perl's hashes. An alist is composed
           of zero or more pairs. The first element of each pair is a case-sensitive key, unique
           within the alist, that is used to index the element. The second element of a pair is
           its value, which may be any data type. The key element of a pair, or all the keys in
           an alist, can be obtained using keysof()[18]. Similarly, valuesof()[19] yields the
           value element or a list of value elements.

           Unlike a regular list, elements within an alist are not ordered. Two alists can only
           be compared for equality (or inequality); they are equal if they contain exactly the
           same pairs.

           An alist has the following syntax:

               ALIST       -> "{" "}" | "{" ALIST-PAIRS "}"
               ALIST-PAIRS -> ALIST-PAIR | ALIST-PAIRS "," ALIST-PAIR
               ALIST-PAIR  -> KEY-EL "," VALUE-EL
               KEY-EL      -> STRING
               VALUE-EL    -> BASIC-DATA-TYPE | LIST | ALIST

           An alist can also be created through the alist()[20] function.

           Here is an alist consisting of four elements:

               {"red", 0, "blue", 2, "green", 5, "black", 7}

           The length()[15] function returns the number of pairs of elements in an alist.

           An alist can be assigned to a variable:

               $myalist = {1, 2, 3, 4, 5, 6}
               $myalist_copy = $myalist

           An alist can be appended to another alist using the "." ("dot") concatenation
           operator. The compound assignment operator version of this operator may also be used.

               > $myalist={sunny, 3}
               {"sunny", 3}
               > $myalist . {rainy, 11}
               {"sunny", 3, "rainy", 11}
               > $myalist .= {"snowy", 13}
               {"sunny", 3, "snowy", 13}

           An alist element or pair is referenced using a string subscript. A sequence of string
           subscripts can be used to select multiple pairs. If the subscript (or subscripts) are
           within brackets, then a successful result will be a basic data type or a list. If the
           subscript (or subscripts) are within braces, then a successful result will always be
           an alist. Note that because an alist subscript is not automatically converted to the
           string type, a numeric subscript is illegal.

               > $myalist = {a, 2, b, 4, c, 6}; length($myalist)
               3
               > $myalist["a"]
               2
               > $myalist{"b"}
               {"b", 4}
               > $myalist{"c", "a"}
               {"c", 6, "a", 2}

           It is an error to reference a non-existent alist element. (Note: additional syntax may
           be introduced to provide a way to declare lists and arrays.)

           Like regular lists, alist references can be composed as a right-associative operation:

               > $myalist = {a, [1, 2], b, [3, 4], c, [5, 6]}; length($myalist)
               3
               > $myalist["a"]
               [1, 2]
               > $myalist{"b"}
               {"b", [3, 4]}
               > $myalist{"b"}[1]
               4

           It is possible to convert an alist to a regular list, or vice versa; see the cast[7]
           operator.

               Note
               There is currently no way to delete an alist pair.

   Expression Grammar
       The following grammar is used to construct an expression (EXP) or sequence (S) of
       expressions.

           Note
           The syntax is very similar to that of the C programming language. It differs with
           respect to data types, variables, compile-time operators, and on some minor aspects of
           grammar.

       A sequence of statements (or simply a sequence) is two or more expressions, with a ";"
       character separating them. The ";" is unnecessary following the last statement in a
       sequence of statements (and is therefore unnecessary if there is only one expression). The
       statements are evaluated in the order in which they appear. The value of a sequence is
       that of the last expression, unless an exit or return function is invoked, in which case
       the value of the sequence is the value returned by the function call. An error condition
       will also terminate evaluation of the sequence and yield a result of False. A sequence
       within curly braces is called a block.

       Figure 1. Expression Grammar

           S    -> E    | E ";" | E ";" S
           E    -> E2   | E2 "," E
           E2   -> E3   | VAR ASSIGN_OP E2 | IF_ELSEIF_ELSE
           E3   -> E4   | E4 "?" E ":" E
           E4   -> E5   | E5 OR E5
           E5   -> E6   | E6 AND E5
           E6   -> E7   | E7 "|" E7
           E7   -> E8   | E8 "^" E8
           E8   -> E9   | E9 "&" E9
           E9   -> E10  | E10 EQ_OP E10
           E10  -> E11  | E11 REL_OP E11
           E11  -> E12  | E12 "." E12
           E12  -> E13  | E13 "<<" E13 | E13 ">>" E13
           E13  -> E14  | E14 "+" E14  | E14 "-" E14
           E14  -> E15  | E15 "*" E15  | E15 "/" E15 | E15 "%" E15
           E15  -> E16  | E16 "^" E14  | E16 "**" E14
           E16  -> E17  | NOT E16 | "~" E16 | "++" VAR | "--" VAR
                        | "+" E | "-" E | "(" type ")"
           E17  -> "(" E ")" | VAR "++" | VAR "--" | FUNCTION_CALL | PRIMARY

           ASSIGN_OP -> "=" | "+=" | "-=" | "*=" | "/=" | "%=" | ">>="
                            | "<<=" | "&=" | "^=" | "|=" | ".="

           PRIMARY -> a number | a string | VAR
           OR      -> "||" | "or"
           AND     -> "&&" | "and"
           NOT     -> "!"  | "not"
           EQ_OP   -> "==" | "!=" | "eq" | "ne"
           REL_OP  -> "<"  | "<=" | ">" | ">=" | "lt" | "le" | "gt" | "ge"
           VAR     -> a variable reference
           FUNCTION_CALL -> FUNCTION_NAME "(" ARG_LIST ")"
           ARG_LIST -> EMPTY | E2 | ARG_LIST "," E2
           EMPTY    ->

       Keywords and function names are case sensitive.

       The production VARASSIGN_OPE in the grammar refers to assignment of the evaluation of E to
       a variable using the given assignment operator (ASSIGN_OP). For example,

           ${a} += 17

       Provided ${a} has been initialized to an integer value, this expression increments it by
       17.

       The production IF_ELSEIF_ELSE represents a familiar if statement with zero or more elseif
       components and an optional else component:

           if (expression) {
             sequence
           }
           [elseif (expression) {
             sequence
           }] ...
           [else {
             sequence
           }]

       Each block is an optional sequence of statements. Braces are mandatory.

           Tip
           An if_elseif_else statement has a value: it is either that of the last statement
           executed in the selected block, or the empty string if no statement is executed. In
           this example, ${a} is set to either 33 or ${b} - 1, depending on whether ${b} is
           greater than eight:

               ${a} = if (${b} > 8) {${b}++; 33;} else {${b} - 1}

           The value of this expression is the string "hello, world":

               "hello, " . (if (0) {b . y . e} else {"world"})

           Note
           As in C, function calls, nested assignment operators, and increment and decrement
           operators cause side effects where the value of a variable is changed during
           expression evaluation. Because exactly when such side effects take place is left
           unspecified, programmers should avoid writing code with these kinds of dependencies on
           evaluation ordering.

   Operators
       The operators that appear in the grammar have the following semantics. They are listed in
       order of increasing precedence (which is very close to ISO C's), with operators in the
       same section having equal precedence. The result of applying an operator is one of the
       supported data types[21], or an error. Parentheses can be applied to subexpressions in the
       usual way.

       Whenever it makes sense, intermediate values are automatically converted to an appropriate
       type by an operator. So, for example, adding an integer and a real will cause the integer
       to automatically be converted to a real, yielding a real value. Adding a string and a
       number will work only if the string can be successfully converted to a number. In
       situations where an integer is required, a real value (including a string that represents
       a valid real number) will be truncated to an integer. For logical comparison operators,
       the operands will both be converted to integers, reals, or strings as necessary. A string
       value that is an illegal number will always be treated as a string.

           Note
            id="note11" xreflabel="dacsexpr prompts".PP In the examples that follow, the '>'
           character at the beginning of an input line is a prompt from dacsexpr(1)[1].

       ,
           This is the C/C++ comma operator. A pair of expressions separated by a comma is
           evaluated left to right, and the type and value of the result are the type and value
           of the right operand.

       =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=, .=
           Assignment is done using a simple or compound assignment operator, each of which has
           right to left associativity. In the case of a compound assignment operator, the left
           hand side is evaluated only once. The type and value of an assignment is that of its
           right hand side. A variable reference is expected on the left side of the operator.
           Modifier flags are not permitted. The variable, which is created if it does not exist.
           The syntax of the variable reference includes the initial "${" and terminating "}"
           character (so it's similar to Perl's syntax).

               > ${foo::bar} = "hello"
               "hello"
               > ${foo::bar} .= ", world"
               "hello, world"
               > ${a} = [1, 2]
               [1,2]
               > ${a} .= [3, 4]
               [1,2,3,4]

       ?:
           This is equivalent to the C/C++ conditional expression, which has right to left
           associativity. If the first expression is True, the result is the value of the second
           expression (the third is not evaluated). If the first expression is False, the result
           is the value of the third expression (the second is not evaluated).

       or, ||
           This is the C/C++ logical OR operator, which yields 1 (True) if either operand is
           True, otherwise it yields 0 (False). Evaluation is from left to right and and stops as
           soon as the truth or falsehood of the result is known. The two tokens are synonymous.

       and, &&
           This is the C/C++ logical AND operator, which yields 1 (True) if both operands are
           True, otherwise it yields 0 (False). Evaluation is from left to right and and stops as
           soon as the truth or falsehood of the result is known. The two tokens are synonymous.

               Note
               When expressions are parsed as XML attribute values, an '&' character must be
               encoded as the five characters '&amp;'.

       |
           This is the C/C++ bitwise inclusive OR operator. Both operands must be integers.

       ^
           This is the C/C++ bitwise exclusive OR operator. Both operands must be integers.

       &
           This is the C/C++ bitwise AND operator. Both operands must be integers.

               Note
               When expressions are parsed as XML attribute values, an '&' character must be
               encoded as the five characters '&amp;'.

       ==, !=, eq, ne, eq:i, ne:i
           These operators compare their arguments and return 1 if the relation is true, 0
           otherwise. If both arguments are lists, corresponding elements of both lists are
           compared, recursively. If both arguments are alists, the number of pairs in both lists
           is compared and, if necessary, pairs in the first list are looked up in the second
           list for matching values (note that the case-insensitive variant applies only to the
           value component of a pair, not the key component). For other valid arguments an
           attempt is first made to coerce both arguments to numbers and do a numeric comparison.
           If that fails, a lexicographic comparison is performed. Operators having a :i modifier
           are like their counterparts without the modifier except they do case-insensitive
           string comparisons.

           If either argument is of type bstring, however, the comparison is done differently
           than explained above. Two bstring arguments are equal if and only if they are
           byte-wise identical. If one argument is a bstring and the other is a string, the
           latter is treated as a bstring of length(string) bytes. The case flag is ignored if at
           least one argument is a bstring.

       <, <=, >, >=, lt, le, lt:i, le:i, gt, ge, gt:i, ge:i
           These operators compare their arguments and return 1 if the relation is true, 0
           otherwise. An attempt is first made to coerce both arguments to numbers and do a
           numeric comparison. If that fails, a lexicographic comparison is performed. Operators
           having a :i modifier are like their counterparts without the modifier except they do a
           case-insensitive comparison.

               Note
               When expressions are parsed as XML attribute values, the '<' character must be
               encoded as the four characters '&lt;'; the same applies to the "greater than"
               symbol.
           If either argument is of type bstring, however, the comparison is done differently
           than explained above. If two bstring arguments are compared, the shorter bstring is
           "less than" the other argument and they are equal if and only if they are byte-wise
           identical. If one argument is a bstring and the other is a string, the latter is
           treated as a bstring of length(string) bytes. The case flag is ignored if at least one
           argument is a bstring.

       .
           The "dot" operator (not in ISO C) concatenates its right operand to its left operand.
           If both arguments are of type bstring, the result is also of type bstring. If the left
           operand is a list and the right operand is a basic data type, the right operand is
           appended to the list. If the left operand is a list and the right operand is also a
           list, the elements of the right operand are appended to the left operand. A list may
           not appear as the right operand if the left operand is not a list. In all other cases,
           both arguments are coerced to string (an error occurs if this cannot be done) before
           the left operand is appended to the right.

               > "hello" . ", world"
               "hello, world"
               > "hello" . (16 + 1)
               "hello17"
               > 17 . (16 + 1)
               "1717"
               > [1, 2, 3] . 4
               [1,2,3,4]
               > [1, 2, 3] . [4, 5, 6]
               [1,2,3,4,5,6]
               > [1, 2, 3] . [[4]]
               [1,2,3,[4]]

               Note
               A period will be recognized as a decimal point in a real number context rather
               than as the dot operator, so the input:

                   4.5

               will be scanned as a number whereas, for example, the input:

                   "4".5

               will evaluate to the string "45".

       <<, >>
           These are the C/C++ bitwise left shift and right shift operators, respectively. The
           first operand may be an integer or a list, the second operand must be an integer. When
           shifting an integer, these operators are implemented using the corresponding C/C++
           operators. In the case of right shifting, the behaviour with respect to arithmetic vs.
           logical shifts will be platform dependent.

       +, -
           These are the (binary) addition and subtraction operators, respectively. Both
           arguments are coerced to numbers. An error occurs if this cannot be done. Also, unary
           + and - operators may precede an arithmetic-valued expression.

       *, /, %
           These are the multiplication, division, and remainder operators, respectively. Both
           arguments are coerced to numbers. An error occurs if this cannot be done, such as
           attempting to divide by zero. For the remainder operator, both operands must be
           integers.

       **
           This is the exponentiation operator (not in ISO C). Both arguments are coerced to
           numbers (either both integers or both reals). An error occurs if this cannot be done,
           such as attempting to raise to a negative power.

               > 2**10
               1024

       +, -, not, !, ~, ++VAR, --VAR, (type)
           The + and - operators are the (unary) arithmetic plus and minus operators,
           respectively. These may precede an arithmetic-valued expression. Both arguments are
           coerced to numbers. An error occurs if this cannot be done.

           The logical NOT operator (not, or equivalently, !) yields a result of zero when
           applied to a non-zero numeric value and non-zero when applied to an operand of zero.
           The result of applying this operator to a non-empty string is zero and it is non-zero
           when applied to an empty string string. These two tokens are synonymous.

           The ~ operator is the one's complement (bitwise not) unary operator.

           The ++VAR and --VAR operators are the prefix increment and decrement operators,
           respectively. These operators are followed by a variable reference. The variable must
           have an integer value.

               > ${foo} = 17, ++${foo}
               18

           An explicit type conversion can be forced by using a cast. The syntax for this type
           coercion is:

               (type) expression

           The type must be a recognized data type name: integer or int (for an integer), real or
           double (for a real), bool (for a boolean value as a long integer), string (for a
           character string), bstring or binary (for a binary string), list, alist, or void.

           A list can be cast to an alist, provided it has no elements or an even number of
           elements and if no key would appear more than once in the alist. A namespace can be
           cast to an alist; the operand specifies the namespace, either as a literal or a
           string. An alist can be cast to an list; the ordering of the pairs in the resulting
           list is unspecified. A void type can only be cast to void, which is a no-op. Here are
           some examples:

               > (int) 3.4
               3
               > (int) "3.6"
               3
               > (bool) 17
               1
               > (bool) ""
               0
               > (string) (4 * 3)
               "12"
               > ${x} = "17"; (int) ((real) ${x} + (bool) 1965)
               18
               > (bstring) "abc"
               "abc"
               > (bstring) 4.4
               "4.400000"
               > (bstring) "\0\1\2"
               ""
               > bstring("\0\1\2",3) . bstring("\3\4", 3)
               "0001020304"
               > (void) ($b=$x)
               >
               > (alist) [a, 1, "b", 2, 3, 3]
               {"a", 1, "b", 2, "3", 3}
               > (list) { red, first, blue, second, white, third }
               ["blue", "second", "white", "third", "red", "first"]
               > $env = (alist) Env; $env["HOME"]
               "/home/bobo"
               > $env{HOME}
               {"HOME","/home/bobo"}

       VAR++, VAR--, primary
           The VAR++ and VAR-- operators are the postfix increment and decrement operators,
           respectively. These operators are preceded by a variable reference. The variable must
           have an integer value.

           A primary is a basic data type[21] (i.e., an integer or real number, string, bareword,
           or binary string), or a variable reference[22].

   Functions
       A function call is written as a function name, optionally followed by whitespace, a left
       parenthesis, zero or more comma-separated arguments, and a right parenthesis. A function
       name begins with either an alphabetic character or an underscore, followed by any number
       of alphanumerics and underscores. Additionally, a pair of colons may appear exactly once
       within the name (except at the beginning or end of the name). The number of arguments and
       their expected types depends on the particular function being called. The order in which
       the arguments to a function are evaluated is undefined. There is no mechanism for creating
       user-defined functions yet (they will eventually be available on some platforms through
       dynamically linked libraries).

       The result of a function call is one of the supported data types[21], or an error. An
       invalid function call, including those that fail during execution, yields a False result.

       Function Index:ack: notice acknowledgement processing

       •   alist: create an alist

       •   alistref: create an alist reference

       •   bstring: convert a string to binary

       •   contains_any: count elements common to two lists

       •   counter: persistent integer counters

       •   dacs_admin: test if user is an administrator

       •   dacs_approval: create or test a signed authorization

       •   dacs_meta: get or update metadata

       •   dacsauth: perform authentication tests

       •   dacscheck: perform authorization tests

       •   debug: control debugging output

       •   decode: convert from a text representation

       •   digest: cryptographic hash functions

       •   encode: convert to a text representation

       •   eval: evaluate a string

       •   exec: execute a program

       •   exit: terminate current evaluation

       •   expand: variable interpolation

       •   file: perform an operation on a file

       •   file_group: test if user is associated with file's group

       •   file_owner: test if user is associated with file's owner

       •   from: test where the current request comes from

       •   get: read the contents of a file or VFS object

       •   hash: fast hashes

       •   hmac: secure keyed-hashes

       •   http: invoke an HTTP request

       •   index: search a string or list

       •   info: information about namespaces and variables

       •   keysof: extract keys from an alist

       •   ldap: extract a component from an LDAP name

       •   length: string length

       •   list: create a list

       •   listref: dereference a list

       •   on_success: evaluate an expression if authentication or authorization succeeds

       •   password: compute or check a password hash

       •   pathname: filename-based string interpolation

       •   pbkdf2: password-based key derivation

       •   print: display a string

       •   printf: display a formatted string

       •   random: generate random values

       •   redirect: redirect user after access is denied

       •   regmatch: string matching

       •   regsub: string substitution

       •   request_match: compare the current request to a URI

       •   return: terminate current evaluation

       •   rule: recursive authorization checking

       •   setvar: operations on namespaces

       •   sizeof: basic data type sizes

       •   sleep: suspend execution temporarily

       •   source: read and evaluate external expressions

       •   sprintf: format a string

       •   strchars: select characters from a string

       •   strchop: delete characters from the end of a string

       •   strftime: format the current date and time

       •   strptime: parse a date and time

       •   strrstr: locate the last instance of a substring

       •   strstr: locate the first instance of a substring

       •   strtolower: map uppercase to lowercase

       •   strtoupper: map lowercase to uppercase

       •   strtr: character transliteration

       •   subset: test if one set is a subset of another

       •   substr: extract a substring

       •   syntax: perform a syntax check on a string

       •   time: local time and date

       •   transform: filter text through rule-based transformations

       •   transform_config: set options for transform

       •   trim: delete trailing characters

       •   typeof: get or test data type

       •   undef: an undefined value

       •   user: test current user's identity

       •   ustamp: generate a unique stamp

       •   valuesof: extract values from an alist

       •   var: operations on individual variables

       •   vfs: perform a VFS operation

       ack(notice-uri[, ...][, EXACT_MATCH | ALL_MATCH])
           This function is associated with notice acknowledgement processing. The function
           indicates that the current service request has one or more notices associated with it
           (identified by a sequence of notice-uri arguments), each one represented by a URI that
           will return the text of a notice that must be acknowledged by the user. Following the
           last URI is an optional mode argument. The EXACT_MATCH mode is the default mode and
           requires a single acknowledgement to address all of the specified notices. The
           ALL_MATCH argument specifies a less stringent matching mode and requires any set of
           acknowledgements to collectively address all of the specified notices. See
           dacs_notices(8)[23].

       alist([key, value [, ...])
           This function is equivalent to the alist construction operator[24]. There must be an
           even number of arguments, or no arguments. If the first argument of each pair (the
           key) is not a string or literal, it will be converted to a string, if possible.

               alist(cars, 2, bikes, 5)

           is equivalent to the expression:

               {"cars", 2, "bikes", 5}

           And the call:

               alist(2, xx, [0, 1], yy)

           yields:

               {"2", xx, "[0,1]", yy}

       alistref(list)
           This function creates a new list that is equivalent to that of the special "brace
           syntax" subscript used to dereference an alist. This is currently useful only in
           conjunction with listref()[25].

               listref({"a", 1, "b", 2, "c", 3}, alistref(["b"]))

           is equivalent to the expression:

               {"a", 1, "b", 2, "c", 3}{"b"}

           the value of which is:

               {"b", 2}

       bstring(string, length)
           This function converts the first length characters of string (which may also be a
           bstring and which is converted to a string if necessary) into the binary type. The
           length argument may be less than the actual length of string; if it is zero, then
           actual length is computed, and if length is greater than the actual length, the actual
           length is used. The implicit null character on the end of string is not considered
           part of it.

               > bstring("\0\1\2", 4)
               "000102"
               > bstring("\0\1\2", 2)
               "0001"

       contains_any(format, test-set, target-set[, nocase])
           This function returns a count of the number of elements of test-set that appear in
           target-set at least once. Duplicate elements may appear in test-set and are considered
           to be distinct. The format indicates how to parse the set arguments. It can be the
           space, tab, or newline character, or any punctuation character. For both sets, it is
           currently interpreted as the character that separates elements. If the optional nocase
           literal argument is given, then set elements are compared case-insensitively. The
           greatest possible return value is the number of distinct elements in the third
           parameter.

               contains_any(",", ${Args::LAYERS:i}, "Nests,Secret_roads,Heritage")
               contains_any(",", "a,a,b,z", "a,a,a,b,b,b,a,z,z")

           The first expression returns 3 if every element in the third parameter appears at
           least once (case insensitive) in the second parameter, otherwise the value of the
           expression is 0. The second expression returns 4.

       counter(op, vfs-ref, counter_name [,value])
           This function is used to manage persistent integer counters, which can be useful for a
           variety of purposes, such as counting the number of logins for a particular identity,
           limiting the number of logins, or restricting the number of times a resource can be
           accessed. Internally, counter values are integers[21].

           The first argument specifies an operation and is case-insensitive. The second argument
           identifies a filestore (typically a file or database). It must be an indexed filestore
           scheme, such as dacs-kwv-fs or dacs-db (see VFS[26]). The third argument is the name
           of the counter, which acts as a key. The meaning of the fourth argument depends on the
           operation, but if present it must be an integer.

               Note
               The current implementation has a limitation; a counter name (key) can be any
               printable string but cannot contain a space character. You can work around this
               limitation by encoding all keys every time they are used in a filestore operation.

            1. counter(set, vfs-ref, counter_name, new-value)

               This is used to create a new counter or reset an existing counter. The counter's
               value will be new-value, which must be an integer, and is the return value.

            2. counter(create, vfs-ref, counter_name, initial-value)

               This is used to create a new counter if it does not already exist. The new
               counter's value will be initial-value, which must be an integer. If the counter
               exists, its value will not be changed and is returned.

            3. counter(del[ete], vfs-ref, counter_name)

               This operation deletes an existing counter. The operation can be del or delete.

            4. counter(exists, vfs-ref, counter_name)

               This operation returns 1 if a counter exists, 0 otherwise.

            5. counter(get, vfs-ref, counter_name)

               This operation returns the current counter value.

            6. counter(inc|dec, vfs-ref, counter_name[, amount])

               This operation increments or decrements an existing counter by amount, which must
               be an integer. If amount is not given, 1 is used. The updated counter value is
               returned.

            7. counter(decdel, vfs-ref, counter_name[, amount])

               This operation decrements an existing counter by amount, which must be an integer.
               If amount is not given, 1 is used. If the resulting value is zero or negative, the
               counter is deleted and zero is returned. If the counter is not deleted, its
               updated value is returned.

            8. counter(list, vfs-ref)

               This operation returns a list of counters as a string, newline separated, each
               with its current value.

           Operations that set or change the counter value return the new value.

           For filestores that support locking, read-only operations obtain a shared lock while
           the other operations obtain an exclusive lock.

           It is an error to reference a counter that does not exist unless the operation is set
           or exists.

               Note
               To some extent, this function is a poor substitute for a more general Perl-like
               tie() function. Such a function is being considered.

               Modifications to counters are not atomic. Amongst other things, this means that a
               crash may cause counter updates to be lost.
           A counter would typically be created by running dacsexpr(1)[1]:

               % dacsexpr -e 'counter(set, "dacs-kwv-fs:/usr/local/dacs/counters/logins", "EXAMPLE::EX:bob", 1)'

           The counter's value might then be tested in the revocation list[27] or by an access
           control rule[28], for instance:

               counter(exists, "dacs-kwv-fs:/usr/local/dacs/counters/logins", ${DACS::IDENTITY})

           The counter might be conditionally updated using the on_success()[29] function, or the
           AUTH_SUCCESS[30] or ACS_SUCCESS[31] directives, using an expression like:

               counter(decdel, "dacs-kwv-fs:/usr/local/dacs/counters/logins", ${DACS::IDENTITY})

       dacs_admin()
           This predicate returns True if the user making a service request has any credentials
           that match any specified by the ADMIN_IDENTITY[32] configuration directive.

       dacs_approval(op[, ...])
           This function is used to create an approval stamp[33] or inspect or validate one.

           The following operations are available:

           dacs_approval(approval, dacs64-approval-message, namespace)
               This operation parses the dacs64-approval-message (the value of DACS_APPROVAL),
               setting variables in namespace, after first dacs64 decoding[34] the argument. If
               namespace exists, its contents are deleted. Variables set are: j (jurisdiction
               name), h (hash/digest name), s (stamp), u (URI), m (HTTP method), and i (user
               identity). See dacs_acs(8)[33]. The signature is not checked. The function returns
               True (1) if the approval message is syntactically correct, otherwise False (0).

           dacs_approval(check, dacs64-approval-message)
               The dacs64-approval-message is decoded and parsed, and the signature is validated.
               The function returns True (1) only if the signature is correct, otherwise False
               (0).

               In the current implementation, the signature can only be validated by the
               jurisdiction that signed the message. This deficiency will be addressed in a
               future release and a web service will also supply this functionality. Ideally, for
               maximum convenience, availability, efficiency, and simplicity, the recipient of an
               approval message should be able to validate it directly if it has the appropriate
               public key, invoke a web service at any jurisdiction in the federation if public
               keys are distributed and kept current, or at the jurisdiction that signed the
               message.

           dacs_approval(create, uri, method, ident, digest-name)
               Create and return a dacs64-approval-message (as described above and in
               dacs_acs(8)[33]), formed from the given arguments and signed by the current
               jurisdiction.

       dacs_meta(op[, ...])
           This function returns information associated with the current federation, current
           jurisdiction, or other jurisdictions in the current federation. See
           dacs_list_jurisdictions(8)[35] for additional information.

           The following operations are available:

           dacs_meta(federation, namespace)
               Return metadata for the current federation, setting variables in namespace. If
               namespace exists, its contents are deleted. Variables set are: federation, domain,
               fed_id (if available), and fed_public_key (if available, in PEM format).

           dacs_meta(jname, jurisdiction-name, namespace)
               Return metadata for the jurisdiction named jurisdiction-name in the current
               federation. If namespace exists, its contents are deleted. Variables set are:
               jname, name, alt_name, dacs_url, authenticates, prompts, auxiliary (if available),
               and public_key (if available, in PEM format).

           dacs_meta(jurisdiction, namespace)
               This is equivalent to the jname operation with jurisdiction_name set to the name
               of the current jurisdiction.

           dacs_meta(list_jurisdictions)
               Return a newline-separated list of all jurisdiction names in the current
               federation. A local copy of the metadata is used.

           dacs_meta(update_jurisdiction, jname [,url])
               Not implemented. Intended to update the local metadata for the jurisdiction name
               jname. If url is absent, then the current jurisdiction must already have the
               correct dacs_url attribute in its entry for jname. If url is given, it is assumed
               to be the URL for dacs_list_jurisdictions and it is used instead of one formed
               from dacs_url for the jurisdiction.

           dacs_meta(update_jurisdictions, jname)
               Not implemented. Intended to update the local metadata for all of the
               jurisdictions. If jname looks like a URL (i.e., it begins with either "http" or
               "https", then it is assumed to be the URL for dacs_list_jurisdictions and it is
               used to obtain a fresh copy of the metadata; otherwise, jname is assumed to be a
               jurisdiction name for which the current jurisdiction already has a correct
               dacs_url attribute and metadata is retrieved from that jurisdiction.

       dacsauth(dacsauth-flags)
       dacsauth(arg1, arg2[, ...])
           This function provides an interface to dacsauth(1)[36]. In the first usage, the single
           string argument is parsed into space or tab separated flags. Single or double quotes
           are allowed. In the second usage, each flag is a separate string or literal argument
           and is not parsed.

           An alist is returned that has the following three elements:

           result
               An integer: 1 if authentication succeeded, 0 if it failed or was not requested,
               and -1 if an error occured.

           identity
               A string: if authentication was requested and succeeded, this is the corresponding
               identity, otherwise it is the empty string.

           roles
               A string: if roles were requested (and authentication succeeded, if requested),
               this is the role descriptor string, otherwise it is the empty string.

               Important
               This function should be considered experimental. Use it with caution. In version
               1.4.25 and earlier, this function returned an integer value (the result).

               Security
               Like dacsauth and dacs_authenticate, if a built-in module is used to perform
               authentication, this function must be run by a setuid or setgid process to obtain
               sufficient privileges to access the required files; this is true for Unix password
               authentication, for example.
           Examples: .sp .if n  .RS 4 . .nf > dacsauth("-m unix suff -user bobo -p apassword")
           {"result",0,"identity","","roles",""} > dacsauth("-m", "unix", "suff", "-user",
           "bobo", "-p", "bpassword") {"result",1,"identity","EXAMPLE::FEDROOT:bobo","roles",""}
           > dacsauth("-r unix
           -DVFS='[federation_keys]dacs-fs:/usr/local/dacs/federations/federation_keys' -u bobo")
           {"result",0,"identity","","roles","bobo,wheel,www,users"} .fi .if n  .RE . .sp

       dacscheck(dacscheck-flags)
       dacscheck(arg1, arg2[, ...])
           This function provides an interface to dacscheck(1)[37], returning 1 if access is
           granted, 0 if access is denied, and -1 if an error occurs. In the first usage, the
           single string argument is parsed into space or tab separated flags. Single or double
           quotes are allowed. In the second usage, each flag is a separate string or literal
           argument and is not parsed.

               Important
               This function should be considered experimental. Use it with caution.

       debug(type, value)
           This function enables, disables, or adjusts the amount of debugging output produced by
           the interpreter. Output type type is set to value, which may be "on", "off", or a
           non-negative integer level (the meaning of which depends on type.

           The following type names are recognized: TBD

       decode(encoding-type, string)
           This function performs the inverse of encode()[38] for the same encoding-type. The
           result is a bstring. The function will fail if its argument is not properly encoded.

           For the hex encoding type, alphabetic characters may be upper case or lower case.

       digest(msg, msg-len [, digest-name])
           This function computes a cryptographic hash[39] of msg (a string or bstring). The
           msg-len is the length of msg in bytes; if it is 0, its length is implicitly the entire
           length of msg. The hash algorithm can be any function provided by OpenSSL and may be
           specified as digest-name, case insensitively, otherwise "sha1" is used. The list of
           available digest algorithms is subject to change, but is likely to include "md5" (the
           128-bit MD5 Message-Digest Algorithm[40]), "sha" (the deprecated 160-bit SHA-0
           algorithm), "sha1" (the 160-bit SHA-1 Secure Hash Algorithm), "sha224", "sha256",
           "sha384", and "sha512". Algorithms "sha224" (SHA-224), "sha256" (SHA-256), "sha384"
           (SHA-384), and "sha512" (SHA-512) (224, 256, 384, and 512 bit values, respectively)
           comprise SHA-2. The function value is a bstring. If cryptographic strength is not
           required, see hash()[41].

               > digest("foo", 0, "md5")
               "acbd18db4cc2f85cedef654fccc4a4d8"
               > digest("Hello, world", 0, "SHA256")
               "4ae7c3b6ac0beff671efa8cf57386151c06e58ca53a78d83f36107316cec125f"

       encode(encoding-type, arg)
           This function converts arg, a string or bstring, into a printable text representation
           that depends on encoding-type. Applying decode()[42] with the same encoding-type to
           the output of this function will produce a value equivalent to the original arg. The
           result is a string.

           Note that encoding is only a representational or formatting change. If secrecy,
           authentication, or verification of integrity are required, use a cryptographic method.

           The following encoding types are recognized:

           encode(ascii85, arg)
               This encoding, also known as radix-85[43], uses nearly every printable character
               to obtain a compact encoding. But note that the resulting strings may be
               problematic in many contexts without additional encoding, which can largely defeat
               the reason for selecting this encoding in the first place. The start-of-data
               ("<~") and end-of-data ("~>") indicators that are sometimes used with this
               encoding are not included.

                   > encode(ascii85, decode(hex, "123456789a"))
                   "&i<X6RK"

           encode(cescape, arg)
               This encoding converts its argument into a C-style escaped string. Character
               escape codes are used when possible, numeric escape codes are used for other
               non-printable characters, and all other characters map to themselves.

                   > encode(cescape, bstring("hi\0\1\2\3\012", 7))
                   "hi\0\001\002\003\n"

           encode(dacs64, arg)
               This encoding type produces a base-64 encoding of arg using upper- and lower-case
               alphabetics, digits, '-', and '_'. It is similar to the mime encoding except that
               '-' and '_' are used in the encoding character set instead of '+' and '/'. This
               encoding is better suited for use in paths and URIs, for example, and is used
               extensively within DACS. It is sometimes referred to as "the dacs64 encoding" or
               just "dacs64" in the DACS documentation.

                   > encode(dacs64, bstring("\0\0\0\1", 4))
                   "_____-"

           encode(hex, arg)
               This encoding converts each byte in arg into a hexadecimal character pair.

                   > encode(hex, "Hello")
                   "48656c6c6f"

           encode(mime, arg)
               This encoding applies the MIME base-64 encoding function (RFC 2045[44], Section
               6.8) to its argument and returns the result.

                   > encode(mime, bstring("\0\0\0\1", 4))
                   "AAAAAQ=="

           encode(url, arg)
               This returns the URL-encoding of the argument (RFC 1738[45], RFC 2396[46] (Section
               2.4), and RFC 3986[47]).

                   > encode(url, bstring("a\0b", 3))
                   "a%00b"

       eval(expression)
           This function evaluates its string argument and returns the result.

           The call:

               > eval("length(\"abc\")")
               3

       exec(prog, ...)
           The exec function executes prog, waits (indefinitely) for it to terminate, and returns
           the program's standard output. A trailing newline in the output is deleted.
           Optionally, command line arguments to prog may be given; they are automatically
           converted to strings. By default, no environment variables are passed to the program;
           if the namespace ExecEnv exists, however, its contents are used as the executed
           program's environment variables. The exit status of prog is made available as the
           value of ${DACS::status}. The program is executed using the execv(3)[48] function, not
           a command shell.

           On POSIX systems, this call returns the string "1\n" on Thursdays, "0\n" on any other
           day:

               > exec("/bin/sh", "-c", "date | grep -c ^Thu")
               "0"

               > ${ExecEnv::PATH} = "/usr/bin";
               "/usr/bin"
               > exec("/bin/sh", "-c", "printenv");
               "PATH=/usr/bin"

               Security
               The program is executed as the same user and group IDs as the DACS program that
               calls exec(). Take appropriate precautions to prevent unauthorized users from
               modifying or replacing DACS configuration files, access control rules, and so on.

       exit(result)
           Equivalent to return, this function causes evaluation of the expression, block, or
           program being evaluated to terminate and returns result as the value of the expression
           or the program's exit status.

       expand(string)
           The argument, a string, is returned with variable references expanded. An undefined
           variable expands to the empty string.

               > ${a} = 17
               17
               > "${a}"
               "17"
               > '${a}'
               "${a}"
               > expand('${a}')
               "17"
               > ${b} = 1999, ${c} = expand('${a}, \${b}')
               "17, ${b}"
               > expand(${c})
               "17, 1999"

       file(op [,arg-list])
           This function performs various operations on files and filenames according to op,
           which is one of the following operation names, followed by command-specific arguments.
           All arguments must either be strings or literal words.

            1. file(basename, string [,suffix])

               This is used to extract the last component of a pathname and is equivalent to the
               basename(1)[49] command. It deletes any prefix that ends with the last slash
               character in string, after first stripping trailing slashes, and a suffix, if
               present. The suffix is not stripped, however, if it is identical to the remaining
               characters in string. A non-existent suffix is ignored. The value is the resulting
               string.

                   > file(basename,"/a/b/c")
                   "c"
                   > file(basename,"/a/b/c.c")
                   "c.c"
                   > file(basename,"/a/b/c.c", ".c")
                   "c"
                   > file(basename,"/a/b/c.c", "c")
                   "c."
                   > file(basename,"/a/b/c.c", "c.c")
                   "c.c"
                   > file(basename,"/a/b/c.c//", "c.c")
                   "c.c"

            2. file(chmod, abs-mode, file)

               Change the mode of file to abs-mode, which is an absolute (octal) file mode (note,
               however, that DACS always set the process umask to 07).

                   file(chmod, "0755", "/usr/local/dacs/tmp/foofile")

            3. file(dirname, string)

               Equivalent to the dirname(1)[50] command, its value is the string that remains
               after deleting the filename portion of string (a pathname), beginning with the
               last slash character to the end of string, after first stripping trailing slashes.

                   > file(dirname,"/usr/local/dacs/bin/dacsexpr")
                   "/usr/local/dacs/bin"
                   > file(dirname,"/usr/local/dacs///")
                   "/usr/local"

            4. file(extension, pathname)

               The returned value is all of the characters in pathname after and including the
               last dot in the last element. If there is no dot in the last element of pathname,
               the value is the empty string.

                   > file(extension,"acl-myapp.0")
                   ".0"

            5. file(lstat, fmt, file)

               This is like the stat[51] operation, except in the case where the named file is a
               symbolic link, in which case lstat returns information about the link, while stat
               returns information about the file the link references.

            6. file(mkdir, directory [,abs-mode])

               Create directory. If an absolute (octal) mode is given, the new directory will
               have that mode (note, however, that DACS always set the process umask to 07).

            7. file(readlink, file)

               If file is a symbolic link, print its contents.

            8. file(remove, file)

               Remove (delete) file.

            9. file(rename, source-file, target-file)

               Rename (mv) source-file to target-file.

           10. file(rmdir, directory)

               Remove (delete) directory, which must be empty.

           11. file(stat, fmt, file)

               Similar to the stat(1)[52] command available on some systems, this makes the
               functionality of the stat(2)[53] system call available. The fmt argument is a
               printf(3)[54]-type descriptor that indicates what file status information is
               wanted and how it is to be printed. Non-formatting characters, including \n, \t,
               and \\, are copied to the output verbatim.

               The following format specifiers are understood:

               •   %d

                   The value of st_dev.

               •   %i

                   The value of st_ino.

               •   %m

                   The value of st_mode in octal.

               •   %M

                   The value of st_mode as text.

               •   %l

                   The value of st_nlink.

               •   %u

                   The value of st_uid in decimal.

               •   %U

                   The value of st_uid as text.

               •   %g

                   The value of st_gid in decimal.

               •   %G

                   The value of st_gid as text.

               •   %r

                   The value of st_rdev.

               •   %s

                   The value of st_size.

               •   %b

                   The value of st_blksize.

               •   %n

                   The value of the file argument.

               •   %N

                   If the argument is a symbolic link, print the contents of the link, otherwise
                   print the file argument.

               •   %ta

                   The value of st_atime in decimal.

               •   %tA

                   The value of st_atime as text.

               •   %tm

                   The value of st_mtime in decimal.

               •   %tM

                   The value of st_mtime as text.

               •   %tc

                   The value of st_ctime in decimal.

               •   %tC

                   The value of st_ctime as text.

               •   %f

                   The name of the host (fileserver) where the file is stored.

               •   %%

                   A literal '%' character.

               This excerpt from an access control rule limits access to authenticated users for
               every file greater than 999 bytes in length that it DACS-wraps:

                   <allow>
                     user("auth")
                   </allow>

                   <allow>
                     user("any") and file(stat, "%s", ${DACS::FILENAME}) lt 1000
                   </allow>

           12. file(test, op [, args])

               Most of the file-testing predicates of the test(1)[55] command are available.

               •   -bfile

                   True if file exists and is a block special file.

               •   -cfile

                   True if file exists and is a character special file.

               •   -dfile

                   True if file exists and is a directory.

               •   -efile

                   True if file exists, regardless of its type.

               •   -ffile

                   True if file exists and is a regular file.

               •   -gfile

                   True if file exists and its set group ID flag is set.

               •   -kfile

                   True if file exists and its sticky bit is set.

               •   -pfile

                   True if file exists and is a named pipe (FIFO).

               •   -rfile

                   True if file exists and is readable (access(file, R_OK) == 0).

               •   -sfile

                   True if file exists and has a size greater than zero bytes.

               •   -ufile

                   True if file exists and its set user ID flag is set.

               •   -wfile

                   True if file exists and is writable (access(file, W_OK) == 0).

               •   -xfile

                   True if file exists and is executable (access(file, X_OK) == 0).

               •   -Lfile

                   True if file exists and is a symbolic link.

               •   -Ofile

                   True if file exists and its owner matches the effective user id of this
                   process.

               •   -Gfile

                   True if file exists and its group matches the effective group id of this
                   process.

               •   -Sfile

                   True if file exists and is a socket.

               •   -ntfile1file2

                   True if file1 and file2 exist and the former is newer than the latter.

               •   -otfile1file2

                   True if file1 and file2 exist and the former is older than the latter.

               •   -effile1file2

                   True if file1 and file2 exist and refer to the same file.

           13. file(touch, file [, abs-mode])

               If file does not exist, it is created; if an absolute (octal) mode is given, the
               new file will have that mode (note, however, that DACS always set the process
               umask to 07). If the file exists, its modification time will be set to the current
               date and time.

       file_group([path])
           Test if path (defaults to ${DACS::FILENAME}, which is equivalent to Apache's
           SCRIPT_FILENAME or REQUEST_FILENAME variables) has a group ownership with which the
           user making the request is associated. This is effectively the same as:

               file(test, "-e", ${DACS::FILENAME})
               and
               user("%" . ${Conf::JURISDICTION_NAME} . ":" \
                 . file(stat, "%G", ${DACS::FILENAME}))

           This predicate provides a simple way of limiting access to a file to its group
           membership with respect to file system permissions:

               <allow>
                 file_group()
               </allow>

           For example, if the user requesting access has been assigned the following roles by
           the current jurisdiction (e.g., through local_unix_roles):

               wheel,www,users

           and the resource being requested is the file:

               -rw-r--r--  1 bobo     www  75 Apr 11 12:41 htdocs/foo.html

           then this predicate would return True because the file has group ownership www and the
           user is associated with that role.

           There is an implicit assumption that the file in question is associated with the
           current jurisdiction; this might be problematic if more than one jurisdiction can
           claim this association.

       file_owner([path])
           Test if path (defaults to ${DACS::FILENAME}, which is equivalent to Apache's
           SCRIPT_FILENAME or REQUEST_FILENAME variables) is owned by the user making the
           request. This is effectively the same as:

               file(test, "-e", ${DACS::FILENAME})
               and
               user(${Conf::JURISDICTION_NAME} . ":" . file(stat, "%U", ${DACS::FILENAME}))

           This predicate provides a simple way of limiting access to a file to its owner with
           respect to file system permissions:

               <allow>
                 file_owner()
               </allow>

           There is an implicit assumption that the file in question is associated with the
           current jurisdiction; this might be problematic if more than one jurisdiction can
           claim this association.

       from(string)
           This predicate is used to test where a request comes from, based on the values of
           REMOTE_ADDR and REMOTE_HOST. These environment variables are passed to DACS from
           Apache. The supported argument types are similar to those recognized by the
           Apachemod_access[56] module's allow and deny directives. If either REMOTE_HOST or
           REMOTE_ADDR are needed to evaluate the argument but are not available, the result will
           be False.

           The string argument may be:

            1. a full or partially matching domain name:

                   from("metalogic.example.com")

               Here, the function yields True if the given domain name matches REMOTE_HOST or is
               a subdomain of REMOTE_HOST. Case-insensitive matching is performed (RFC 1035[57]).
               Only complete components are matched, so the above example will match
               foo.metalogic.example.com but not foonmetalogic.example.com. If REMOTE_ADDR is
               available but not REMOTE_HOST, a reverse DNS lookup will be performed on the
               domain name and all IP addresses that result will be tested against REMOTE_ADDR;
               if this lookup results in an error (i.e., it fails), then the function raises an
               error condition.

            2. a full IPv4 address in standard dot notation:

                   from("10.0.0.123")

            3. a partial IPv4 address (the first one, two, or three bytes) in standard dot
               notation:

                   from("10.0")

            4. a network/netmask pair:

                   from("10.0.0.0/255.255.0.0")

            5. a network/nnn pair using CIDR notation[58] (RFC 1338[59]):

                   from("10.0.0.0/8")

            6. a full or partial IPv4 address in standard dot notation where any address element
               can be a decimal number (0 through 255) or a range specification[60], similar to
               that used with strchars()[16]; note that the range separator in this context is
               ":" instead of "..": :

                   from("10.0.[0:100,255]")

               In the example above, the two high-order octets of ${DACS::REMOTE_ADDR} must be 10
               and 0, the value of the next octet must be between 0 and 100 (inclusive) or be 255
               (decimal), and the value of the fourth octet is unimportant. The following
               expressions are equivalent:

                   from("10")
                   from("10.")
                   from("[10]")
                   from("[10].")
                   from("10.0.0.0/8")
                   from("10.0.0.0/255.0.0.0")

            7. "all" (always yields True and is included for compatibility with Apache):

                   from("all")

           An alternative method is to perform a regular expression match against
           ${DACS::REMOTE_ADDR} using regmatch()[61].

               Tip
               To test where a client authenticated from, which is not necessarily the same as
               the place from which a request is sent, use the user()[62] function.

       get(vfs-ref [,key])
           The file or item specified by vfs-ref, which may be followed by a key if it is an
           indexed filestore, is read and returned. The vfs-ref may be an absolute pathname, an
           item type, or a vfs_uri[26], except if called from a standalone application without a
           key argument, in which case vfs-ref may also be a relative pathname.

               Note
               A proper I/O subsystem does not exist yet, but until then you may use the special
               item type stdin to read the standard input until end of file. This function will
               probably not work if a special file is used (e.g., /dev/stdin).

       hash(msg, msg-len [,hash-name])
           This function computes a fast hash of msg, a string or bstring. The msg-len is the
           length of msg in bytes; if it is 0, its length is computed. The hash-name can be the
           32-bit hash "hash32" (the default) or the 64-bit hash "hash64". The result is a
           string. Although the algorithms have been used extensively with very good results,
           they should not be used for cryptographic purposes; see digest()[63].

               > hash("Hello, world", 0)
               "3696529580"
               > hash("Hello, world", 0, hash64)
               "462009511995194717"

       hmac(msg, msg-len, key, key-len [, digest-name])
           This function computes a cryptographic message authentication code[64] - specifically,
           the Keyed-Hash Message Authentication Code (HMAC)[65] - of msg (a string or bstring),
           using key (a string or bstring). The msg-len is the length of msg in bytes; if it is
           0, its length is computed. Similarly, key-len is the length of key in bytes and if it
           is 0, its length is computed. Any of the available Secure Hash Standard functions[66],
           as well as MD5 (RFC 2104)[67], may be specified as digest-name (case insensitively),
           otherwise SHA1 is used. The list of available digest algorithms is subject to change,
           but is likely to include md5, sha1, sha224, sha256, sha384, and sha512. The function
           value is a bstring. Note that the function is not commutative; the key is the third
           argument, not the first. Although the MD5 hash function is deprecated for some
           purposes, it is still considered adequate in some applications and is required by many
           older protocols that are still in widespread use.

               > hmac("Sample #2", 0,
                 decode(hex, "303132333435363738393a3b3c3d3e3f40414243"), 0)
               "0922d3405faa3d194f82a45830737d5cc6c75d24"

       http(url, [method [,arglist]])
           This function sends an HTTP request to url, using a given method (GET, POST, HEAD,
           PUT, DELETE, or OPTIONS, case insensitively), and optionally passing parameters. If no
           method is given (and no arguments), GET is assumed. The value of the function is the
           message returned by the request. The url is in the usual syntax and must use either
           the http or https scheme (case insensitive). The argument list, if present, consists
           of some number of pairs, the first being the name of the parameter and the second the
           value of the parameter.

           The first statement sends an HTTP request to example.com and sets the variable to the
           message body (if any) that is returned. The second statement makes a GET request to
           port 8443 of example.com over SSL, passing it two parameters, FOO=17 and FOO=2:

               > ${x} = http("http://example.com")
               > http("https://example.com:8443/cgi-bin/dacs_prenv.cgi", "GET", "FOO", 17,
                     "BAZ", 1+1)

       index(string, character-class [, nocase])
       index(list, search_operand [, nocase])
           If the first argument is a string, this function returns the first position in string
           (counting from 1) where the first character in character-class was found, or 0.
           Case-sensitive character comparison is used unless the optional nocase literal
           argument is present.

           If the first argument is a list, the position of element search_operand (counting from
           1) in list is returned, or 0 if it is not found. During comparison, types are
           automatically converted as necessary. Case-sensitive character comparison is used
           unless the optional nocase literal argument is present.

           Examples:

               > index("abcdef", "abc")
               1
               > index("abcdef", "e")
               5
               > index("zzz", "abc")
               0
               > index([a, b, c, d, e], d)
               4
               > index(["hello", world, 2009, qUAKe], "quake", nocase)
               4
               > index([1.0, 2.2, 3.3, 4.4, 5.0, 6.6], "1")
               1
               > index(["apple", ["orange", "banana"], ["peach", "mango"]], "orange")
               0
               > index(["apple", ["orange", "banana"], ["peach", "mango"]], ["orange", "banana"])
               2

       info(namespaces)
       info(namespace, namespace-name)
           Return a string containing information about variables and namespaces. The first form
           returns a comma-separated list of known namespaces. The second form returns a list
           containing all variables in the given namespace and their values, one per line. This
           can be useful for debugging.

           Examples:

               info(namespaces)
               info(namespace, "Conf")

       keysof(alist)
           If its argument is a single pair, the pair's key is returned. If there is more than
           one pair in the argument, a list of keys is returned. To get the value component of a
           pair or set of pairs, use valuesof()[19].

           Examples:

               > keysof({red, 17})
               "red"
               > keysof({red, 17, blue, 100})
               ["red", "blue"]

       ldap(dn_length, dn-string)
       ldap(dn_index, dn-string, nth)
       ldap(rdn_length, rdn-string)
       ldap(rdn_index, rdn-string, nth)
       ldap(rdn_attrtype, rdn-string [, nth])
       ldap(rdn_attrvalue, rdn-string [, nth])
           The ldap function is used to extract components of LDAP names. Its first argument, a
           literal, determines the operation mode to be used and the semantics of the following
           arguments. Distinguished Name (DN) and Relative Distinguished Name (RDN) strings are
           as defined in RFC 2253[68].

           The dn_length mode returns the number of RDN components in its DN argument; -1 is
           returned if the argument is not a valid DN. The dn_index mode returns the nthRDN
           component of the DN, where nth is an integer greater than zero. If nth is greater than
           the number of components, the last component is returned.

           The rdn_length mode returns the number of AttributeTypeAndValue elements in its RDN
           argument; -1 is returned if the argument is not a valid RDN. The rdn_index mode
           returns the nthAttributeTypeAndValue component of the RDN, where nth is an integer
           greater than zero. If nth is greater than the number of components, the last component
           is returned.

           The rdn_attrtype mode returns the AttributeType of the nthAttributeTypeAndValue
           component of the RDN, where nth is an integer greater than zero. If nth is missing, it
           is taken to be 1. If nth is greater than the number of components, the last component
           is selected. The rdn_attrvalue mode is similar except that it returns the
           AttributeValue.

           The first and second expressions below return 2, the third expression returns
           Administrator:

               ldap(dn_length, "dc=example,dc=com")
               ldap(rdn_length, "foo=bar+bar=baz")
               ldap(rdn_attrvalue, ldap(dn_index, \
                   "CN=Administrator,CN=Users,DC=example,DC=com", 1))

       length(string)
       length(bstring)
       length(list)
       length(alist)
           This function returns the length, in characters, of string, the number of bytes in
           binary string bstring, the number of elements in list, or the number of pairs in
           alist.

       list([value [, ...])
           This function is equivalent to the list construction operator[69].

               list(1, 2, [hello, world], 5)

           is equivalent to the expression:

               [1, 2, [hello, world], 5]

       listref(list, list-ref [, ...])
           This function provides an alternate syntax to the language's list/array notation. For
           example, the function call:

               listref([1, 2, [3, 4], 5], 2, 1)

           is equivalent to the expression:

               [1, 2, [3, 4], 5][2][1]

           Note that a list reference may follow a list-valued expression (e.g., a list
           constructor, a list-valued variable, a function that returns a list) this syntax is
           valid:

               ($a . $b)[0]

           The parentheses are necessary here because the subscript binds more tightly than the
           concatenation operator. This expression can also be written as:

               listref($a . $b, 0)

       on_success(list-name [, expr])
           The list-name argument must be either acs or auth (case insensitive) to select the
           post-authorization list or the post-authentication list, respectively. For the former
           case, if authorization is successful, the expr argument (a string) will be evaluated
           by dacs_acs immediately after any ACS_SUCCESS[31] directive, and just prior to program
           termination. These expressions are not evaluated if authorization is denied, an
           authorization processing error occurs, or a DACS_ACS argument prevents execution of
           the request. For the latter case, if authentication is successful, the expr argument
           (a string) will be evaluated by dacs_authenticate(8)[9] immediately after any
           AUTH_SUCCESS[30] directive, and just prior to program termination. These expressions
           are not evaluated if authentication fails or an authentication processing error
           occurs.

           Once added to either list, an entry cannot be removed. The expressions are evaluated
           in the order in which on_success() was called. The values returned by the expressions
           are discarded and errors are ignored.

           If no expr is given, the current list of expressions is returned, one per line, in
           order of evaluation. With an expression argument, it returns the number of expressions
           in the list after any addition.

       password(op [, op-args])
           This function performs a variety of read-only operations on DACS accounts and their
           passwords. See dacspasswd(1)[70] and directives PASSWORD_DIGEST[71] and
           PASSWORD_SALT_PREFIX[72] for additional information.

           The following operations are available:

           password(check, given-password, password-digest [,alg-name])
               With the check operation, the digest of given-password is computed (as computed by
               the hash operation) and compared to password-digest, which was previously
               generated by the hash operation of this function, retrieved by the getdigest
               operation, or obtained using dacspasswd. This algorithm is identical to the one
               used by local_passwd_authenticate[73] to validate passwords. If alg-name is given,
               it names the digest algorithm to use instead of the one specified within
               password-digest. If given-password is correct (i.e., the same passwords were used
               to generate the two digest values), True (1) is returned, otherwise False (0) is
               returned.

           password(getdata, username [,vfs-ref])
               The getdata operation returns the private data associated with the account for
               username. The result is a bstring. If there is no private data, the length of the
               result will be zero (the length of the empty string is one). If a vfs-ref is
               given, it identifies the virtual filestore to use, otherwise the item type passwds
               is used. It is an error if the account does not exist, so a test operation will
               often be performed first.

           password(getdigest, username [,vfs-ref])
               The getdigest operation is similar to getdata except that the digest string for
               the account is returned; this digest string can be used with the check operation.

           password(hash, plain-password [,alg-name])
               With the hash operation, a digest of the string plain-password is returned as a
               printable string. The password hashing algorithm is identical to the one used by
               dacspasswd(1)[70]. If alg-name is given (see digest()[63]), it names the digest
               algorithm to use instead of the configured default.

           password(list [, vfs-ref])
               The list operation returns a list of account names, one per line. An empty string
               is returned if there are no accounts. If a vfs-ref is given, it identifies the
               virtual filestore to use, otherwise the item type passwds is used. To test if a
               password file exists, use vfs()[74].

           password(syntax, password [,constraints])
               The syntax operation tests if password satisfies the constraints argument, if
               provided, otherwise the value of the PASSWORD_CONSTRAINTS[75] directive. The
               constraints are specified in the same syntax as the PASSWORD_CONSTRAINTS
               directive. The function returns True (1) if the constraints are satisfied,
               otherwise False (0).

           password(test, test-op, username [,vfs-ref])
               The test operation applies test-op to the account entry for username in the
               virtual filestore vfs-ref (or item type passwds). It is an error if the password
               file does not exist or is unreadable. It returns True if the test is successful,
               otherwise the result is False. The recognized values of test-op are (case
               insensitively): data (to test if the account exists and has private data),
               disabled (to test if the account exists and is disabled), enabled (to test if the
               account exists and is enabled), or exists (to test if the account exists).

           Examples:

               > password(hash, "bobo")
               "2|XYZZYxBhU/7VgJAt2lc.G|HL4RQ2vo0uNoXlXnv.GcY3Vlf9."
               > password(check, "bobo", "2|XYZZYxBhU/7VgJAt2lc.G|HL4RQ2vo0uNoXlXnv.GcY3Vlf9.")
               1

       pathname(path, hostname, port)
           Perform string interpolation on path based on the other arguments. For details, please
           see dacs.conf(5)[76] (where hostname is SERVER_NAME).

       pbkdf2(password, salt, count, dklen)
           Apply a pseudorandom function (HMAC-SHA-1) to password and salt (both binary strings,
           or converted as required), modified by count iterations, returning a binary string of
           length dklen bytes. For details, please see RFC 2898[77] and RFC 3962[78].

               > pbkdf2("password", "ATHENA.MIT.EDUraeburn", 1200, 32)
               "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"
               > pbkdf2("password", decode(hex,"1234567878563412"), 5, 16)
               "d1daa78615f287e6a1c8b120d7062a49"

       print(...)
           Each argument is converted to a string, the strings are concatenated, a newline is
           appended, and the result is printed. The return type is void. If called from
           dacsexpr(1)[1], the string is printed to the standard output; otherwise, it is printed
           to the DACS log file (or stderr), which can be useful for debugging purposes. These
           log messages are associated with the user class (see the LOG_FILTER[79] directive).

       printf(fmt, ...)
           This is a slightly scaled-down version of the printf(3)[54] library function. If
           called from dacsexpr(1)[1], the string is printed to the standard output; otherwise,
           it is printed to the DACS log file (or stderr), which can be useful for debugging
           purposes. These log messages are associated with the user class (see the
           LOG_FILTER[79] directive). This can be useful for debugging purposes. If necessary and
           possible, arguments are converted to the type requested by a formatting specification.
           The return type is void.

       random(bytes, nbytes)
       random(uint, lo, hi)
       random(string, nbytes [, spec])
       random(stringc, nbytes, spec)
           The various forms of this function, distinguished by the first argument, return
           cryptographically strong pseudo-random values[80] in various formats. The starting
           point (seed value) for the pseudo-random sequence cannot be set, meaning that the
           sequence cannot be (intentionally) reproduced.

           The bytes operation requests nbytes bytes of random material. The result is a bstring
           of that length.

           The uint operation requests an unsigned random integer between lo and hi (both
           unsigned integers), inclusive. It is an error if lo is not greater than hi. The result
           is an (unsigned) integer.

           The string operation requests nbytes of random material, returned as a hex-encoded
           string. If a spec argument is present, it uses the character specification syntax of
           strtr()[81] to indicate the characters that can be used to encode the result. Only
           printable characters, excluding the space, are allowed in the result, regardless of
           the spec argument. Example:

               > random(string,12,"a-zA-Z0-9")
               "LgROshy6SMMH"
               > random(string,12,"a-z")
               "kehhvwydhhbk"

           The functionality of the stringc operation is identical to that of the three-argument
           instance of the string operation except that the sense of the spec argument is
           complemented to indicate those characters that may not be used in the encoding of the
           result.

       redirect(error-code, target)
       redirect(target)
           Permitted only within the context of an access control rule's deny clause, this
           function causes expression evaluation and rule processing to stop immediately, access
           to be denied, and the client to be redirected to target, a URL that may contain a
           query component. If the error-code is present, it must be an ACS error name or number
           (see the ACS_ERROR_HANDLER[82] directive), otherwise "BY_REDIRECT" is used.

               Note
               The URL must be properly escaped if it appears within an XML document, such as an
               access control rule; for example, if an ampersand occurs in the query component in
               a context where it must be escaped, it must appear as the five characters "&amp;".
           The target string is expected to have one of the syntaxes of the document component of
           Apache'sErrorDocument directive[83]. In essence, this function causes an
           ACS_ERROR_HANDLER directive to be created and triggered. The function returns the
           target string, although because of the function's run time behaviour the value cannot
           be used.

               Tip
               One application of this function is to create a short link, which is a relatively
               concise URL that acts as an "alias" for another, usually much longer URL (here,
               the target). The short link is made public. It must be DACS-wrapped; the target
               does not need to be. Any attempt to access the short link is denied by its rule,
               but the rule uses the redirect() function, probably with BY_SIMPLE_REDIRECT as the
               error-code (see dacs.conf(5)[82]), to redirect the user agent to the target.

               The following rule demonstrates how this can be done:

                   <acl_rule status="enabled">
                    <services>
                     <service url_pattern="/id/*"/>
                    </services>

                    <rule order="allow,deny">
                     <deny>
                       setvar(split, "X", ${Env::REQUEST_URI}, "/");
                       ${x} = var(get, X, ${X::#} - 1);
                       redirect(BY_SIMPLE_REDIRECT, "https://example.com/docs/${x}.html");
                     </deny>
                    </rule>
                   </acl_rule>

               With this rule in place, a request like:

                   https://example.com/id/17795821

               would result in a redirect to this target:

                   https://example.com/docs/17795821.html

               The target URL can depend on contextual elements, and it is straightforward to do
               things like make the target URL depend on the time of day, identity of the user,
               and so on. The technique can also be used with Rlinks[84].

               Because the rule associated with the short link can be changed at any time, this
               feature can be used to implement smart permalinks[85].

       regmatch(string, regex [, namespace] [, nocase])
           This is a pattern matching function. The first two arguments are coerced to strings,
           with the second one taken to be the regular expression, with a "^" (the
           start-of-string anchor) implicitly prepended. The string argument is then matched
           against the regular expression, which may contain subexpressions enclosed between '('
           and ')' (or '\(' and '\)'). If the match fails, the result is 0. If the match succeeds
           there are several possibilities:

           •   if there are no subexpressions in regex, the result is an integer that is the
               number of characters matched.

           •   if there is at least one subexpression in regex but no namespace (a string
               argument) is given, the result is the substring of string that was matched by the
               entire regular expression.

           •   if there is at least one subexpression in regex and a namespace argument is given,
               the result is an integer that is the number of characters matched by the entire
               regular expression. The value of the first matching subexpression is assigned to
               the variable named "1" in the namespace, the value of the second subexpression is
               assigned to a variable named "2" in the namespace, and so on up to the ninth
               subexpression. The variable named "0" in the namespace is assigned the substring
               of string that was matched by the entire regular expression. Following function
               evaluation in the context of ACL rule processing, namespace is accessible only
               within the predicate, allow, or deny element in which it appears.

           If the optional nocase literal argument is given, then matching is done
           case-insensitively. Only one parenthesized pair can be used. IEEE Std 1003.2
           ("POSIX.2") "extended" regular expressions are supported (regex(3)[86],
           re_format(7)[87]).

           Examples:

               > ${X} = "abfoo"
               "abfoo"
               > regmatch(${X}, ".*foo", nocase)
               5
               > regmatch("abcdefgzz", "(.*)g")
               "abcdefg"
               > regmatch("foo", "(bar)|(baz)|(foo)")
               "foo"
               > regmatch("abcdefgzz", "ab(.*)efg(.*)", "x")
               9
               > ${x::0}
               "abcdefgzz"
               > ${x::1}
               "cd"
               > ${x::2}
               "zz"
               > $addr = "192.168.7.3"
               "192.168.7.3"
               > regmatch($addr, "192\\.168\\.(.*)\\..*", "X")
               11
               > ${X::1}
               "7"

       regsub(string, regex, replacement [, nocase] [,repeat])
           This function matches regex against string, like regmatch()[61] does, and returns the
           string that results when the substitution specified by replacement is applied to the
           matched text. This is similar to the ed/vi/sed command "s/regex/replacement/" applied
           to string. If no match is found, the empty string is returned.

           The optional repeat literal argument causes the replacement to be applied to all
           matches; i.e., like the ed/vi/sed command "s/regex/replacement/g".

           Examples:

               > regsub("hello world", "world", "auggie")
               "hello auggie"
               > regsub("hello world", "auggie", "world")
               ""
               > regsub("hello", ".*", "& &")
               "hello hello"
               > regsub("one two three", "(.*) (.*) (.*)", "\${3} \${2} \${1}")
               "three two one"
               > regsub("one two three", "(.*) (.*) (.*)", '${3} ${2} ${1}')
               "three two one"
               > strtr(regsub("https://BOB.Example.com",
                   "\([^:]*\)://\([^.]*\)\\.\(.*\)", '${1}-${2}@${3}'),
                     "A-Z", "a-z")
               "https-bob@example.com"
               > regsub("one, bone, cone, hone", "one", "two", repeat)
               "two, btwo, ctwo, htwo"

       request_match(uri-string)
           This function is used to inspect the current request. The argument is either a valid
           URI or a path component that begins with a slash. In the latter case, the scheme and
           authority components of the current request are effectively prepended to the given
           path. The path component is like the url_pattern attribute used in access control
           rules[88] in that it can either specify an exact match or, by ending in "/*", a
           wildcard match. A query component is allowed but ignored. The function returns 0 if
           uri-string does not match the current request, otherwise it returns the number of path
           components of uri-string that match the current request. If the scheme and authority
           components are given in uri-string, they count as one naming component.

           Assuming that the current request is http://example.com:18123/a/b/c, we get:

               > request_match("http://example.com:18123/a/b/c")
               4
               > request_match("https://example.com:18123/a/b/c")
               0
               > request_match("http://example.com:18123/a/b/c/d")
               0
               > request_match("http://example.com:18123/a/b")
               0
               > request_match("http://example.com:18123/a/b/*")
               4
               > request_match("http://example.com:18123/*")
               2
               > request_match("http://example.com:18123")
               0
               > request_match("http://example.com")
               0
               > request_match("http://example.com/*")
               2
               > request_match("/*")
               1
               > request_match("/a/b/c")
               3
               > request_match("/a/b/*")
               3
               > request_match("/")
               0

       return(result)
           Equivalent to exit, this function causes evaluation of the expression to terminate and
           returns result as the value of the expression.

       rule(object, ruleset_vfs)
           The rule predicate is an interface to the DACS rule processing engine. It is used to
           test if the rule set ruleset_vfs authorizes object, much as dacscheck(1)[37] does. The
           object argument is the name to match against the services specified in access control
           rules and can either be a URI or an absolute pathname (one that begins with a slash
           character). It can have an optional query string component attached. An absolute
           pathname path is mapped internally to a URI as file://path; e.g., /myapp is
           interpreted as file:///myapp (see RFC 1738[45]).

           One application of this predicate is for a rule associated with a program to check
           that the user requesting access is entitled to use a data file needed by the program.

               Note
               Only the path component of the URI is considered when DACS matches an object's
               name against the url_pattern of an access control rule. At present, the object
               name is not automatically canonicalized or resolved (see RFC 3986[47]), as is
               usually done by a web server, so relative path components such as "." and ".."
               should be avoided.
           The ruleset_vfs is a URI in the syntax of the VFS[26] configuration directive.

           The various components of the URI that names the object are available as DACS
           variables and environment variables (see below). If a query string is given, it is
           parsed and the individual arguments are made available to rules through the Args
           namespace, just as for DACS-wrapped web services.

           Many variables normally set by a web server are instantiated based on the object name
           and the execution environment. These variables are available in the DACS namespace.
           For example, if the object name is
           https://example.com:8443/myapp/edit-menu?entry=item1, the following variables will be
           set as indicated:

               ${DACS::HTTPS}=on
               ${DACS::SERVER_NAME}=example.com
               ${DACS::SERVER_ADDR}=142.179.101.118
               ${DACS::HTTP_HOST}=example.com:8443
               ${DACS::SERVER_PORT}=8443
               ${DACS::REQUEST_URI}=/myapp/edit-menu
               ${DACS::DOCUMENT_ROOT}=/
               ${DACS::REQUEST_METHOD}=GET
               ${DACS::SERVER_SOFTWARE}=dacsexpr-1.4.14
               ${DACS::QUERY_STRING}=entry=item1
               ${DACS::ARG_COUNT}=1
               ${DACS::CURRENT_URI}=/myapp/edit-menu?entry=item1
               ${DACS::CURRENT_URI_NO_QUERY}=/myapp/edit-menu

           The value of ${Args::entry} will be item1. The request method is always GET. The
           variable ${DACS::REMOTE_USER} will be set if credentials are available in the
           execution environment.

           For example, assuming that the file /usr/local/exams/acls/acl-exams.17 contains:

               <acl_rule status="enabled">
                 <services>
                   <service url_pattern="/exam1.html"/>
                 </services>

                 <rule order="allow,deny">
                   <precondition><predicate>
                     ${Args::user} eq "teacher"
                   </predicate></precondition>

                   <allow>
                     time(hour) eq 17
                   </allow>
                 </rule>
               </acl_rule>

           The following call would only return True (1) any day between 5:00pm and 5:59pm:

               rule("/exam1.html?user=teacher", "dacs-fs:/usr/local/exams/acls");

               Note
               •   Since any rule can call the rule function, take care to avoid infinite
                   recursion.

               •   Although this function is similar in concept to the dacscheck(1)[37] command,
                   there are some significant differences, particularly with respect to the
                   context available during rule evaluation.

               •   The Env namespace is not reinitialized or altered during evaluation of rules
                   processed by rule. That is, the Env namespace is the same as the outer-most
                   one.

       setvar(op, dst-namespace [, args ...])
           This function, which performs various operations on namespaces, has several different
           syntaxes. The first argument always specifies the operation (case insensitively) and
           determines the meaning of the arguments that follow it. The second argument always
           specifies a namespace that is created or modified. If successful, the function returns
           the number of variables created (or replaced) in dst-namespace.

           The dst-namespace cannot be a read-only namespace[89]. Unless otherwise specified, if
           dst-namespace exists, variables are added to it, with any existing variable assigned
           its new value.

           The following operations are recognized:

           setvar(authorization, dst-namespace, auth-str)
               The auth-str argument, which is the value of an Authorization HTTP request header,
               is parsed into its component fields and assigned to variables in the destination
               namespace dst-namespace. If dst-namespace exists, its contents are deleted first.
               Corresponding to the field names used in RFC 2617[90] Section 3.2.2, the following
               variables are created: AUTH_SCHEME, USERNAME, PASSWORD, REALM, NONCE, DIGEST_URI,
               RESPONSE, ALGORITHM, CNONCE, OPAQUE, MESSAGE_QOP, NONCE_COUNT, and AUTH_PARAM. Any
               variable that corresponds to a non-existent field is assigned the empty string.

               The following call sets ${Foo::AUTH_SCHEME} to Basic, ${Foo::USERNAME} to Bobo,
               and ${Foo::PASSWORD} to myPassWord.

                   setvar(authorization, Foo, "Basic Qm9ibzpteVBhc3NXb3Jk")

           setvar(copy, dst-namespace, src-namespace)
               With the copy operation, all variables in an existing namespace src-namespace are
               copied to dst-namespace. If the latter exists, its contents are deleted, otherwise
               the namespace is created.

           setvar(delete, dst-namespace)
               The delete operation is used to delete dst-namespace and its contents.

           setvar(kwv, dst-namespace, assign-char, sep-chars, string)
               For the kwv operation, string is parsed, creating (or replacing) variables in
               dst-namespace. The string consists of zero or more keyword/value pairs. The
               keyword, which is used as the variable name, is separated by the value by the
               character assign-char. A keyword/value pair is separated from the next by any
               character that appears in sep-chars. Here is an example:

                   setvar(kwv, "Foo", "=", ", ", "a=b, c=d, e=f")

               The value of this call is 3 and it sets ${Foo::a} to "b", ${Foo::c} to "d", and
               ${Foo::e} to "f".

           setvar(load, dst-namespace, filename)
           setvar(load, dst-namespace, item_type, key)
           setvar(load, dst-namespace, vfs-ref, key)
               The contents of a text object, which can be specified using a filename, as an
               item_type, or using a vfs-ref (see vfs()[74]), is split into newline separated
               lines. The first line is assigned index 0 in dst-namespace, the second index 1,
               and so on.

               Consider the following example:

                   > setvar(load, PASSWD, "/etc/passwd")
                   25
                   > ${PASSWD::2}
                   "root:*:0:0:Charlie &amp;:/root:/bin/csh"

               Here, there are 25 lines in /etc/passwd and the third line is printed.

           setvar(loadi, dst-namespace, vfs-ref)
               Each item in the indexed text object specified by vfs-ref (an absolute pathname,
               an item type, or a VFS URI[26]) is copied to dst-namespace, with the same index.
               The index must be a valid variable-name.

                   > setvar(loadi, PASSWD, "dacs-kwv-fs:/etc/passwd")
                   23
                   > ${PASSWD::root}
                   "*:0:0:Charlie &amp;:/root:/bin/csh"
                   > ${PASSWD::bobo}
                   "bobo:*:1001:1001:Bobo &amp;:/home/bobo:/bin/tcsh"

               Here, 23 items are copied into the PASSWD namespace (the first two lines in this
               particular /etc/passwd are ignored because they are comments that are not
               recognized as items). The lines indexed by the keys root and bobo are printed.

           setvar(merge, dst-namespace, src-namespace)
               The merge operation is similar to copy[91] except that if dst-namespace exists its
               contents are not deleted.

           setvar(post, dst-namespace [, content-type, string])
               Like query[92], this operation parses its input into arguments in dst-namespace.
               The function reads its standard input, unless a string argument is given. The
               input is expected to be a correctly formatted application/x-www-form-urlencoded or
               multipart/form-data content type. If the standard input is read, both the
               CONTENT_TYPE and CONTENT_LENGTH environment variables must be set (as they are
               when Apache runs a script that is passed an entity-body).

               The form that takes string is not yet implemented.

           setvar(query, dst-namespace, query-string)
               For the query operation, query-string is parsed, creating variables in
               dst-namespace. This uses the same parsing algorithm employed by cgiparse(8)[93].
               In the case of a malformed query string, like "a&b", variables will be created but
               will have the empty string as their value. If successful, the function returns the
               number of variables created. The following call returns 3 and sets ${Foo::a} to
               "b", ${Foo::c} to "d", and ${Foo::e} to "f":

                   setvar(query, "Foo", "a=b&c=d&e=f")

               One application of this function it to distinguish query arguments (which are part
               of the requested resource's URI and made available through the environment
               variable QUERY_STRING) from arguments supplied in the body of a POST method (or
               other such method). For example:

                   setvar(query, "Qargs", "${Env::QUERY_STRING}")
                   if (${Qargs::foo:e}) {
                       /* "foo" is a query argument */
                   }
                   else {
                       /* "foo" is not a query argument */
                   }

                   if (${Args::foo:e} and not ${Qargs::foo:e}) {
                       /* "foo" is a POST argument */
                   }
                   else {
                       /* "foo" is not a POST argument */
                   }

           setvar(regsplit, dst-namespace, string, delimiter-regex [,limit])
               The regsplit operation is similar to split[94] except that substrings are
               separated by the regular expression delimiter-regex. IEEE Std 1003.2 ("POSIX.2")
               "extended" regular expressions are used (regex(3)[86]).

           setvar(rename, dst-namespace, src-namespace)
               The rename operation deletes dst-namespace, if it exists, and changes the name of
               src-namespace to dst-namespace. The two namespace arguments must be different.

           setvar(split, dst-namespace, string, delimiter [,limit [,dflag]])
               The split operation extracts substrings from string. Substrings are separated by
               the string delimiter. For example, this call separates a composite role string
               into individual basic roles:

                   setvar(split, "ROLES", ${DACS::ROLES}, ",")

               If the variable reference ${DACS::ROLES} has the value "root,wheel,www,users",
               then the example would return 4 and set ${ROLES::0} to "root", ${ROLES::1} to
               "wheel", and so on.

               If a limit is given, it is an integer that specifies the maximum number of
               substrings to extract. Once the maximum has been reached, the remainder of string
               that has not been split will be assigned to the last element. A limit of zero is
               equivalent to the default, which is for there to be no maximum. For instance,
               setvar(split, X, "a,b,c,d", ",", 2) will assign "a" to ${X::0} and "b,c,d" to
               ${X::1}.

               Here is another example:

                   > setvar(split, "X", "a\nb\nc\n", "\n")
                   "3"
                   > ${X::0}
                   "a"
                   > ${X::#}
                   "3"

               This function can be used to break a pathname into its individual components. For
               instance, the following call results in ${X::0} set to the empty string, ${X::1}
               set to "a", ${X::2} set to "long", and ${X::3} set to "path":

                   > setvar(split, "X", "/a/long/path", "/")
                   4

               (You may need to first remove redundant slashes in string using strtr()[81].)

               A dflag argument may follow the limit argument to indicate whether delimiter
               should not be included in substrings (dflag == 0, which is the default behavior),
               whether it should be included at the start of substrings with the possible
               exception of the first one (dflag > 0), or whether it should be included at the
               end of substrings with the possible exception of the last one (dflag < 0).

                   > setvar(split, P, "/a/long/path", "/", 0, 1)
                   3
                   > ${P::0}
                   "/a"
                   > ${P::1}
                   "/long"
                   > ${P::2}
                   "/path"
                   > setvar(split, P, "/a/long/path", "/", 0, -1)
                   4
                   > ${P::0}
                   "/"
                   > ${P::1}
                   "a/"
                   > ${P::2}
                   "long/"
                   > ${P::3}
                   "path"

           setvar(uri, dst-namespace, uri)
               The given uri, a URI conforming to RFC 2396[46] or RFC 3986[47], is parsed into
               its components. Variables in dst-namespace are set accordingly: SCHEME (mapped to
               lower case), HOST (mapped to lower case), AUTHORITY, PORT, SERVER, USERINFO, PATH,
               QUERY, and FRAGMENT. If a component is absent from uri, the corresponding variable
               will not be defined.

                   Security
                   It is possible for USERINFO to include a plaintext password.
               In addition, the URI's path component is split into its slash-delimited pieces.
               The variable PATH_LENGTH is set to the number of such pieces (it will be zero if
               there are none), and variables PATH_0, PATH_1, and so on are set to the first,
               second, and successive pieces. An "empty" path component is treated as a piece
               consisting of the empty string.

                   > setvar(uri, "X", "https://bar@foo.example.com:8443/cgi-bin/prog?a=17")
                   11
                   > info(namespace,X)
                   "SCHEME="https"
                   AUTHORITY="bar@foo.example.com:8443"
                   HOST="foo.example.com"
                   PORT="8443"
                   SERVER="foo.example.com:8443"
                   USERINFO="bar"
                   PATH="/cgi-bin/prog"
                   PATH_COUNT="2"
                   PATH_0="cgi-bin"
                   PATH_1="prog"
                   QUERY="a=17"
                   "

       sizeof(typename)
           This function returns the amount of memory in bytes, as an integer, used by typename,
           the name of a basic data type[21]. For the string and binary types, the returned value
           is the number of bytes used by each element of that type (1, typically). To find the
           number of elements in string or binary data, use length()[15].

               > sizeof(real)
               8

       sleep(seconds)
           The process is suspended for approximately seconds seconds, or until a signal is
           received and caught or the process terminated. It returns the "unslept" number of
           seconds, which will be zero if the process slept for the requested interval. This is
           an interface to sleep(3)[95]. It can be useful for inserting delays in conjunction
           with error handlers, for instance.

       source(vfs-ref [,key])
           The expressions in the file or item specified by vfs-ref, which may be followed by a
           key if it is an indexed filestore, are read and evaluated as a block. The vfs-ref can
           be an absolute pathname, an item type, or a VFS URI[26]. The value returned is that of
           the evaluated block. The following two expressions are essentially equivalent:

               source("/usr/local/dacs/scripts/script17")
               eval(get("/usr/local/dacs/scripts/script17"))

           This function is handy when a lengthy expression is needed but one does not want to
           clutter a configuration file or a rule.

       sprintf(fmt, ...)
           This is a slightly scaled-down version of the sprintf(3)[96] library function. If
           necessary and possible, arguments are converted to the type requested by a formatting
           specification. The formatted string is returned.

               ${a} = sprintf("Hello") . ", world."
               "Hello, world."
               length(sprintf("Hello") . ", world.")
               13

       strchars(str, range-spec [,...])
           This function returns a new string by selecting characters from str according to a
           sequence of one or more range specifications (each one a range-spec). A range-spec is
           a string argument that determines the indexes of characters to select within str.
           Indexes start at zero. The result of each successive range specification is appended
           to the previous result.

           A range-spec is an unordered set of one or more comma-separated elements, each of
           which is either an index or a range. An index may either be a non-negative integer or
           "#", which means "all indexes". A range represents a sequence of indexes and has the
           syntax:

               range-start ".." range-end

           A range-start may be a non-negative integer, the character "#" (which means "from the
           beginning"), or may be elided (also meaning "from the beginning"). A range-end may be
           a non-negative integer (not less than range-start, if it is also a non-negative
           integer), the character "#" (which means "to the end"), or may be omitted (also
           meaning "to the end").

               > $a = "abcdef"
               "abcdef"
               > strchars($a, 2)
               "c"
               > strchars($a, "1..4", "0")
               "bcdea"
               > strchars($a . $a, "5..#")
               "fabcdef"
               > strchars($a, "#")
               "abcdef"
               > strchars($a, "#..#")
               "abcdef"
               > strchars($a, "#..3")
               "abcd"
               > strchars($a, "..3")
               "abcd"
               > strchars($a, "..3", "#")
               "abcdabcdef"

       strchop(str, del-spec)
           This function deletes from the end of str a continuous run of any characters in
           del-spec.

               > strchop("foo4.859", ".56789")
               "foo4"
               > strchop("foo7.859", ".5679")
               "foo7.8"
               > strchop("hello    ", " ")
               "hello"
               > strchop("dogs rule\n\n", "\n")
               "dogs rule"

       strftime(format)
           This function is an interface to the strftime(3)[97] function. It is applied to the
           current date and time.

       strptime(date-str, date-format, namespace)
       strptime(namespace)
           This function is an interface to the strptime(3)[98] function. The date-str argument
           is a string representation of a date and/or time, with date-format describing its
           syntax. If the parse of date-str succeeds, the following elements of namespace are set
           from the corresponding fields of struct tm: tm_sec, tm_min, tm_hour, tm_mday, tm_mon,
           tm_year, tm_wday, tm_yday, tm_isdst, tm_zone, and tm_gmtoff. Additionally, a variable
           named clock is set to the Unix time that corresponds to the parsed date and time. Any
           existing elements of namespace are not modified. If date-str does not fully describe a
           date and time, it is taken to be relative to the current date and time (e.g., if only
           a time is given, "today's date" is used).

           In the single-argument usage, the current date and time are parsed and namespace is
           assigned values as previously described.

           The return value is the "Unix time" equivalent of the resulting time and date.

               > strptime("6 Dec 2001 12:33:45", "%d %b %Y %H:%M:%S", tm)
               1007670825
               > "${tm::tm_mon} ${tm::tm_mday} ${tm::tm_hour} ${tm::tm_min}"
               "11 6 12 33"
               > ${tm::clock}
               1007670825

       strrstr(string, substring [, nocase])
           Return the start of the last occurrence of substring within string. The empty string
           is returned if string is empty or if no occurrence of substring is found. If substring
           is empty, string is returned. The optional nocase literal argument requests
           case-insensitive comparison.

               > strrstr("afoofoofooz", "foo")
               "fooz"
               > strrstr("afOOfoofooz", "FooF", nocase)
               "foofooz"
               > strrstr("afOOfoofooz", "ofoo",nocase)
               "ofooz"

       strstr(string, substring [, nocase])
           Return the start of the first occurrence of substring within string. The empty string
           is returned if string is empty or if no occurrence of substring is found. If substring
           is empty, string is returned. The optional nocase literal argument requests
           case-insensitive comparison.

               > strstr("foobazbar", "baz")
               "bazbar"
               > strstr("foobazbar", "")
               "foobazbar"
               > strstr("foobazbar", "zzz")
               ""
               > strstr("", "zzz")
               ""
               > strstr("afoofoofooz", "foo")
               "foofoofooz"
               > strstr("fooZbar", "Ozb", nocase)
               "oZbar"

       strtolower(string)
           A new string is returned where each uppercase character in string is mapped to
           lowercase and all other characters are mapped to themselves. These two expressions are
           equivalent and have the value "hello, world 2008":

               strtolower("Hello, World 2008")
               strtr("Hello, World 2008", "A-Z", "a-z")

       strtoupper(string)
           A new string is returned where each lowercase character in string is mapped to
           uppercase and all other characters are mapped to themselves. These two expressions are
           equivalent and have the value "HELLO, WORLD 2008":

               strtoupper("Hello, World 2008")
               strtr("Hello, World 2008", "a-z", "A-Z")

       strtr(input-string, string1, [string2 [,cds]])
           This function performs string transliteration, like the tr(1)[99] command and Perl's
           tr and y operators. The result is the transliterated string. The first argument is the
           input string to be transliterated (stdin in the tr command). The second argument is
           the search list ("string1" in the tr command). The third argument is the (possibly
           empty) replacement list ("string2" in the tr command); it may be omitted if no flag
           string argument follows.

           The fourth, optional argument is a literal flag string made of the characters 'c',
           'd', and 's' (in any order), which correspond to the flags of the same name in the tr
           command:

           c
               Complement the set of values in string1.

           d
               Delete characters in string1 from the input string.

           s
               Squeeze multiple occurrences of the characters listed in the last operand (either
               string1 or string2) in the input into a single instance of the character. This
               occurs after all deletion and translation is completed.

               > strtr("AbCdEf", "A-Z", "a-z")
               "abcdef"
               > strtr("/a//b///c", "/", "", "s")
               "/a/b/c"

       subset(format, purported-subset, superset [, nocase])
           This function returns True if every element of the purported-subset appears in
           superset. The format indicates how to parse the set arguments. It can be the space,
           tab, or newline character, or any punctuation character. It is currently interpreted
           as the character that separates elements. If the optional nocase literal argument is
           given, then set elements are compared case-insensitively.

           Example:

               subset(",", ${Args::LAYERS:i}, "RELIEF:Foundation,GTOPO30:Foundation")

           This call returns True if every element of the LAYERS parameter (case insensitive)
           appears in the given list, otherwise the expression is False.

       substr(string, start-position, length)
           This function returns the substring of string beginning at start-position with length
           at most length characters. The first character is in position one. If start-position
           is negative, the position is relative to the end of string (-1 specifies the last
           character in string). If the effective starting position is outside of string, an
           empty string is returned. If length is negative, it means "the remainder of the
           string". It is an error if either numeric argument is zero. It is not an error if
           length exceeds the actual number of characters returned.

               > substr("foozle", 3, 4)
               "ozle"
               > substr("foobar", -3, 2)
               "ba"
               > substr("foobar", -5, -1)
               "oobar"
               > substr("foobar", 10, -1)
               ""
               > substr("foobar", -10, 3)
               ""

       syntax(type, name [, flag])
           This function performs a syntax test, specified by type, on name. It returns 0 if the
           test fails, 1 or a type-dependent, non-zero value if the test is successful. It can be
           useful for testing, catching errors, recognizing when a string must be mapped, and for
           learning about DACS. Note that these are purely syntactical checks. They do not test
           whether an object called name exists or is configured.

           The following tests are recognized:

           syntax(charset, name, charset_spec)
               Test if each of the characters in name is specified by charset_spec, which is a
               character set specification as used by strtr()[81] ("the search list").

           syntax(dacsname, name)
               Test if name is valid as a DACS name[100]. If the string is recognized, one of the
               following values is returned to classify it:

               •   1 if it is a DACS identity

               •   2 if it is a group name

               •   3 if it is a jurisdiction name

               •   4 if it is a federation name

               •   5 if it is an IP address in numeric dot notation

           syntax(emailaddr, name)
               Test if name is a syntactically valid RFC 822[101] email address. A successful
               test does not imply that a message can be delivered to the address.

                   Note
                   The implementation does not currently recognize valid addresses where the
                   local-part (the substring to the left of the '@' character) contains a
                   quoted-string component.

           syntax(expr, name)
               Test if name is a syntactically valid expression. The expression is not actually
               evaluated. A successful test does not imply that evaluation of the expression will
               necessarily be successful or error-free.

           syntax(domainname, name)
               Test if name is a syntactically valid domain name (RFC 952[102]). A successful
               test does not imply that name exists or has a DNS entry.

           syntax(federation, name)
               Test if name is valid as a federation name (e.g., as the value of
               FEDERATION_NAME[103]).

           syntax(group, name)
               Test if name is valid as a group name.

           syntax(hostname, name)
               Test if name is valid as a host name (an alphanumeric, followed by any number of
               alphanumerics and hyphens, but not ending with a hyphen; see RFC 952[102] and RFC
               1123[104]).

           syntax(ipaddr, name)
               Test if name is a valid Class C IPv4 address (RFC 790[105]).

           syntax(jurisdiction, name)
               Test if name is valid as a jurisdiction name (e.g., as the value of
               JURISDICTION_NAME[106]).

           syntax(namespace, name)
               Test if name is valid as the name of a namespace[22].

           syntax(role, name)
               Test if name is valid as a role descriptor string[100].

           syntax(uri, name)
               Test if name is a valid URI (RFC 2396[46], but partially RFC 3986[47]). It must
               consist of a scheme, authority component, path component, and optional query and
               fragment components.

           syntax(username, name)
               Test if name is valid as a username (e.g., as the value of the USERNAME argument
               to many DACS web services).

           syntax(variable, name)
               Test if name is valid as a variable reference[22]. This does not test if the named
               variable exists.

           syntax(varname, name)
               Test if name is a syntactically correct variable name[22], with or without a
               namespace. This does not test if the named variable exists.

               > syntax(federation, "FOO")
               1
               > syntax(dacsname, "FOO::BAZ:bar")
               1
               > syntax(dacsname, "FOO::")
               4
               > syntax(charset, "bobo17+", "a-z0-9")
               0
               > syntax(expr, '1 + 1 + 1')
               1
               > syntax(variable, '${1$}')
               0
               > syntax(variable, '${Foo::baz:z}')
               0
               > syntax(varname, 'Foo::baz')
               1
               > syntax(varname, "17")
               1
               > syntax(username, "/bobo/")
               0
               > syntax(group, "blop")
               1
               > syntax(group, "%blop")
               0
               > syntax(dacsname, "%blop:flop")
               1
               > syntax(uri,"https://foo.example.com:8443/cgi-bin/prog?a=17")
               1

       time(format [, timeval])
       time(format, namespace)
           This function returns time and date information, as specified by the first argument.
           The second argument, if present, either specifies the "Unix time" from which to obtain
           the time and date or a namespace that was returned by strptime()[107]. If the second
           argument is absent, the result is the same as if a second argument were given as
           time("now"). The localtime(3)[108] library function is used internally to perform the
           date calculations.

           The format argument, which is treated case-insensitively, can be any of the following:

           •   If the argument is "now", the function's value is the current "Unix time" (the
               value of time in seconds since 0 hours, 0 minutes, 0 seconds, January 1, 1970,
               Coordinated Universal Time). If the second argument is present, however, it is the
               function's value.

           •   If the argument is "sec" or "secs" or "seconds", the function's value is the
               system clock's seconds reading.

           •   If the argument is "min" or "mins" or "minutes", the function's value is the
               system clock's minutes reading.

           •   If the argument is "hour", the function's value is the system clock's hour reading
               (0 - 23).

           •   If the argument is "mday", the function's value is the day of the month (1 - 31).

           •   If the argument is "ismdaylast", the function's value is non-zero if this is the
               last day of the month.

           •   If the argument is "mon" or "month", the function's value is the month of the year
               (0 - 11).

           •   If the argument is "year", the function's value is the year (from 1900 onward).

           •   If the argument is "isleapyear", the function's value is non-zero if this is a
               leap year.

           •   If the argument is "wday", the function's value is the day of the week (Sunday is
               0).

           •   If the argument is "yday", the function's value is the day of the year (0 - 365).

           •   If the argument is "isdst", the function's value is non-zero if daylight saving
               time is in effect.

           •   If the argument is "zone", the function's value is system clock's time zone,
               abbreviated. If the time zone is not known, the value will be the empty string.

           •   If the argument is "gmtoff", the function's value is the offset (in seconds) of
               the system clock's time represented from UTC, with positive values indicating east
               of the Prime Meridian.

               Note
               A more powerful function is planned to test whether the current time and date
               satisfy a predicate. It might, for example, understand arguments such as "Tuesday"
               (True on any Tuesday), "last day of the month", "between midnight and 8:30am",
               "January 30, 2004 at 1:23pm", "between March 2 and April 1", "the second Tuesday
               of the month", or "within 15 days of April 30".

       transform(input,name,rules,docs [,idents])
       transform(input,config,name,rules,docs [,idents])
           This function provides a simplified API for dacstransform(1)[109] - refer to its
           description for additional details. The first form of the function uses compile-time
           defaults, unless they are overridden by configuration variables (e.g.,
           ${Conf::transform_prefix}). The second form passes a configuration object returned by
           transform_config()[110]. The input argument is the text to be passed through the
           function. The name argument is equivalent to the value of the dacstransform-name flag,
           rules is equivalent to the value of the -r flag, docs is equivalent to the value of
           the -docs flag, and the optional idents argument is a whitespace-separated list of
           identities in the concise user syntax[111]. The function returns the transformed
           input.

       transform_config(flags)
           This function returns a configuration object that is passed to subsequent calls to
           transform()[112] so that defaults can be overridden. The single string argument is
           parsed into whitespace-separated words. If a flag is repeated, the right-most
           occurrence is used.

           The following flags are recognized:

           •   -prefix prefix-string: The string used to introduce a directive, which must appear
               at the beginning of a line.

           •   -suffix suffix-string: The string used to end a directive.

           •   -rprefix regex-prefix: A line whose beginning matches the specified regular
               expression introduces a directive.

           •   -rsuffix regex-suffix: The end of a directive is found by matching the specified
               regular expression.

       trim(string, delete-set [,limit])
           Delete each character in delete-set that appears at the end of string, up to limit
           characters. The delete-set is a search list specification as used by strtr()[81]. If
           limit is missing or zero, all of the characters in string can potentially be deleted
           (leaving the empty string). The new string is returned.

               > trim("abceffff", "f")
               "abce"
               > trim("abceffff", abf)
               "abce"
               > trim("a\n\n\n", "\n")
               "a"
               > trim("a", "a-z")
               ""

       typeof([typename,] expression)
           If there are two arguments and the first is a recognized data type name[21], the
           return value is 1 (True) if expression has that type and 0 (False) otherwise. If there
           is one argument, the function yields a string that is the data type name of the
           evaluated expression.

               > typeof(4.5)
               "real"
               > typeof(integer, 4.5)
               0

       undef()
           This function returns a special value that represents the "undefined" value. It is
           used in certain circumstances to undefine a symbol. See dacs.conf(5)[10].

       user(string)
           This function compares its argument against each set of current credentials and
           returns the number of credentials that match. The argument is a user filter expression
           that must evaluate to True for a set of credentials for those credentials to match.
           See dacs(1)[100] for information about naming.

               Note
               In typical usage, each user will have only one set of credentials or will be
               unauthenticated. One should keep in mind, however, that multiple concurrent
               identities are allowed, subject to ACS_CREDENTIALS_LIMIT[113].
           The string argument (EXP) has the following syntax:

           Figure 2. User Filter Expression Grammar

               EXP -> E1
               E1  -> E2 | E2 OR E2
               E2  -> E3 | E3 AND E2
               E3  -> E4 | NOT E3
               E4  -> primary | "(" E1 ")"

               OR  -> "or"  | "||"
               AND -> "and" | "&&"
               NOT -> "not" | "!"

           Whitespace (spaces and tabs) is permitted before and after lexical elements. Keywords
           are case sensitive except when otherwise stated.

           A primary, which evaluates to True or False, is one of the following:

           username
               True if the DACS identity username matches.

                   user("METALOGIC:auggie")
                   user(":bobo")

               If the jurisdiction name or federation name components are omitted, the current
               federation and jurisdiction[100] are implied. The jurisdiction name component may
               be specified as "*" (e.g., *:username), in which case it will match any
               jurisdiction name in the current federation. In addition, both the federation name
               and the jurisdiction name components may be specified as "*" (e.g.,
               *::*:username), in which case it will match any federation name and any
               jurisdiction name.

           jurisdiction
               True if jurisdiction matches the name of the jurisdiction that created the
               credentials.

                   user("METALOGIC:")
                   user("DEMO::METALOGIC:")

           federation
               True if federation matches the name of the federation that created the
               credentials.

                   user("DEMO::")

           address
               Given an argument acceptable to the from()[114] predicate, the result is True if
               the credentials were generated by a user apparently located at address.

                   user("10.0.0.123")
                   user("10.0.0.0/24")
                   user("example.com")

           group
               True if the identity is a member of group, which is a DACS group.

                   user("%METALOGIC:admin")

               A group name may reference an explicit group membership list or a role-based
               group. Also, it is possible for an explicit group membership list to have the same
               name as a role-based group; if the name is referenced in a rule, the rule
               processing engine will first check if the user is associated with the role. If
               he's not, it will go on to check for an explicit group membership list with the
               same name. This allows an administrator to easily supplement the membership
               associated with a role-based group. Refer to dacs.groups(5)[115].

           namespace ns
               The value of each element in ns (a namespace) is evaluated as a primary. The order
               in which the list is evaluated is unspecified. Processing of the list terminates
               with the first primary that evaluates to True or when the list is exhausted. This
               primary can appear in an element (so that one list can reference other lists) but
               beware of infinite recursion.

               For example, if /usr/local/dacs/app_users consists of usernames, one per line, an
               access control rule can grant permission to any of the users by having an allow
               element containing the statements:

                   setvar(load, APP_USERS, "/usr/local/dacs/app_users");
                   user("namespace APP_USERS")

           style style-list
               The keyword style is followed by a list of one or more comma-separated,
               case-insensitive style keywords, described below. Each style keyword may be
               abbreviated up to the indicated minimum number of initial characters. Every set of
               credentials has one or more styles[116] associated with it that indicate which
               authentication method or methods were successfully applied and how (by what means)
               the credentials were generated within DACS. A primary is True if the tested
               credentials satisfy all of the keywords in the style-list.

               For example, this expression tests if both the passwd and certificate styles are
               associated with it:

                   user("style passwd,cert")

               This is equivalent to the following expression, which tests if the user was
               authenticated via a username/password style of authentication and a valid X.509
               client certificate was presented:

                   user("style passwd") and user("style CERT")

               The following style keywords are understood:

               acs
                   True if the credentials were created during an authorization check by dacs_acs

               admin
                   True if the credentials were created for use internal to DACS.

               alien
                   True if the credentials were imported by dacs_auth_agent(8)[117] in its
                   "alien" mode, or by dacs_auth_transfer(8)[118].

               cas
                   True if the user was authenticated using CAS.

               cert[ificate]
                   True if the user authenticated using an X.509 certificate.

               digest
                   True if the user authenticated using RFC 2617[90] Digest authentication.

               expr
                   True if the user was authenticated using an expression.

               gen[erated]
                   True if the credentials were generated by a DACS utility (e.g.,
                   dacscookie(1)[119]).

               import[ed]
                   True if the credentials were imported by dacs_auth_agent(8)[117] or
                   dacs_auth_transfer(8)[118].

               infocard
                   True if the user was authenticated using an InfoCard.

               nat[ive]
                   True if the user was authenticated using the native authentication style.

               managed_infocard
                   True if the user was authenticated using a managed InfoCard.

               pass[word]
               passwd
                   True if the user authenticated using a password.

               prompt[ed]
                   True if the user was authenticated using the prompted authentication style.

               rlink
                   True if the user was authenticated using an Rlink[84].

               selfissued_infocard
                   True if the user was authenticated using a self-issued InfoCard.

               simple
                   True if the user authenticated without using a password.

               This test can be used as part of a risk-based authentication configuration; a user
               with credentials obtained through an authentication style deemed not to be
               sufficiently secure with respect to a resource could be forced to reauthenticate
               using a stronger authentication method. See dacs_authenticate(8)[9] for additional
               information.

           importedby jurisdiction
               The keyword importedby is followed by the name of a jurisdiction within the
               current federation; the result is True if the credentials were imported using
               dacs_auth_transfer(8)[118] at that jurisdiction.

                   user("importedby METALOGIC")

           version protocol-version
               The keyword version is followed by a DACS protocol version number (every release
               of DACS defines this as the value of the compile-time symbol DACS_VERSION_NUMBER);
               the result is True if the credentials match that protocol version number.

                   user("version 1.4")

           authenticated, unauthenticated
               Either of two keywords: authenticated (or simply auth) or unauthenticated (or
               simply unauth). The former is True if the user is authenticated, while the latter
               is True if the user is not authenticated. A case-insensitive string comparison is
               used to match these special names.

                   user("auth")
                   user("unauth")

           mine
               The keyword "mine" (case insensitive) is True if the user was authenticated by the
               current jurisdiction.

                   user("mine")

           any
               The keyword "any" (case insensitive) is always True.

                   user("any")

           none
               The keyword "none" (case insensitive) is always False.

                   user("none")

           By default, an exact string comparison (case sensitive) is used to match name
           components other than the special names; this default behaviour can be overridden
           using the NAME_COMPARE configuration directive (dacs.conf(5)[10]). The method used to
           compare federation names, jurisdiction names, and usernames can also be specified by
           following the primary with a mode. If the value of mode (which is itself case
           insensitive) is case, then case-sensitive comparisons are used, if its value is
           nocase, then case-insensitive comparisons are used, and if its value is default, then
           the value of the NAME_COMPARE directive will be used if present, otherwise the
           application default is used (either case or the value selected by the application).

               Important
               Keep in mind that user() can return False because no credentials matched the user
               filter expression and because there are no credentials at all (i.e., the user is
               unauthenticated). For example,

                   user("not METALOGIC:rmorriso")

               will return True if the user's identity is not METALOGIC:rmorriso, even if the
               user is not authenticated. It may therefore be necessary to explicitly test for an
               authenticated user:

                   user("not METALOGIC:rmorriso and auth")
           Here are examples of the user() function. Note that any non-zero expression value
           implies True.

            1.

                   user("METALOGIC:")

               Return True if the client was authenticated by the jurisdiction METALOGIC in this
               federation

            2.

                   user("METALOGIC:rmorriso")

               Return True if the client was authenticated as the user METALOGIC:rmorriso

            3.

                   user("DEMO::METALOGIC:rmorriso")

               Return True if the client was authenticated by the given federation and
               jurisdiction as rmorriso

            4.

                   user("%METALOGIC:admin")

               Return True if the client is a member of the group METALOGIC:admin

            5.

                   user("*:rmorriso")

               Return True if the client was authenticated as the username rmorriso by any
               jurisdiction in this federation

            6.

                   user("auth")

               Return True if the client was authenticated anywhere

            7.

                   user("UnAuthenticated")

               Return True if the client is not authenticated

            8.

                   user("10.0.0.123")

               Return True if the client was authenticated through a request from a host having
               the IP address 10.0.0.123

            9.

                   user("not 10.0.0.123")

               Return True if the client is unauthenticated or was not authenticated through a
               request from a host having the IP address 10.0.0.123 (use user("auth and not
               10.0.0.123") to remove the unauthenticated case)

           10.

                   user("ANY")

               Always return True

           11.

                   user("any") gt 1

               Return True if the client has more than one set of current credentials (i.e., has
               authenticated as two or more identities)

           12.

                   user(":rmorriso")

               Return True if the client was authenticated as rmorriso by this jurisdiction

           13.

                   user(":rmorriso nocase")

               Return True if the client was authenticated as rmorriso, case-insensitively, by
               this jurisdiction

           14.

                   user("metalogic:RMORRISO nocase")

               Return True if the client was authenticated as the user metalogic:RMORRISO, but
               comparing the jurisdiction name, username, and implied federation name
               case-insensitively

           15.

                   user("METALOGIC:rmorriso default")

               Equivalent to user("METALOGIC:rmorriso"), return True if the client was
               authenticated as the user METALOGIC:rmorriso, but comparing the jurisdiction name,
               username, and implied federation name according to the NAME_COMPARE directive,
               otherwise using the application's default

               Tip
               The following two tests are not equivalent:

                   user("auth")
                   user("DSS:auth")

               The first is True if the user making the request has been authenticated; it does
               matter which jurisdiction authenticated the user or what the username is. The
               second test requires the user making the request to have a specific identity; she
               must have been authenticated by the jurisdiction DSS as the username auth.

       ustamp(op, vfs-ref [,args ...])
           This function generates a string called a stamp that is globally unique and sequenced,
           with high probability. It has the following syntax:

               h=hostid, s=seqno

           A hostid consists of one or more characters from the same set used for a DACS
           username[120]. A seqno consists of two elements, separated by a colon, each of which
           is an unsigned decimal value.

           The first component of a stamp, the hostid, is intended to be uniquely associated with
           the host that generates the stamp. By default, it is a 128-bit, cryptographically
           strong pseudo-random value. This value is stored in vfs-ref, which may be an absolute
           pathname, an item type, or a VFS URI[26]. If vfs-ref does not exist, it is created and
           a new value is stored in it.

           Note that by default, hostid identifies a host, not a jurisdiction. If required, it is
           possible to configure unique stamps for each jurisdiction on a host.

           The second component (seqno) is a sequence number string relative to hostid. Sequence
           numbers should never repeat with respect to a host and always increase in value so
           that any two sequence numbers created by the same host must be different. Successive
           sequence numbers need not increase by uniform steps. If stamp1 compares less than
           stamp2, then stamp1 was created before stamp2. Comparison of sequence numbers is
           performed on matching elements numerically, left to right. Two hostid components are
           compared case insensitively. No ordering is necessarily implied by stamps created by
           different hosts.

           Sequence number state information is stored in a file that must be specified using the
           configuration variable ${Conf::ustamp_seqno}; e.g.,

               EVAL ${Conf::ustamp_seqno} = "${Conf::DACS_HOME}/seqno"

           The variable must be set to the absolute pathname of a file that is readable and
           writable by any process that needs to generate a stamp. If this file is deleted, the
           sequence will be reinitialized. Note that updates to the state information are
           unlikely to be atomic, which means that in the event of a system crash the state
           information should be deleted so that a new stream of sequence numbers is generated.

           One application of these stamps is to provide an efficient way to detect replayed
           messages. A recipient may only need to keep track of the stamp sent with the last
           message received from a jurisdiction to detect an invalid stamp in any subsequent
           message. Cryptographic techniques can be employed to prevent a stamp from being
           altered or forged.

           The following operations are recognized:

           ustamp(clock, vfs-uri)
               The host's system clock is used for the stamp's sequence number. Its first element
               is the number of seconds since the start of the epoch and the second is a counter
               value. Note that if the system clock is reset to an earlier time, sequence numbers
               may repeat with unpredictable consequences; a future version of this function may
               detect a reset clock.

           ustamp(ntpclock, vfs-uri, ntp_host)
               This operation is not implemented. Rather than using the system clock, this
               operation obtains the current time from ntp_host, which is assumed to be more
               reliable than the system clock in that it will never be reset to an earlier time.
               The ntp_host argument is a hostname or IP address. The default port number (123)
               may be overridden by appending a colon and the port number to use.

           ustamp(user, vfs-uri, seqno)
               Instead of incorporating the current time into the stamp's sequence number, this
               operation uses a user-supplied string that is assumed to have the necessary syntax
               and characteristics.

           Examples:

               > ustamp(clock, "${Conf::DACS_HOME}/hostid")
               "h=2fbae312ddc1d2ae388cea1b57a47c66, s=1185565675:9"

       valuesof(alist)
           If its argument is a single pair, the pair's value is returned. If there is more than
           one pair in the argument, a list of values is returned. To get the key component of a
           pair or set of pairs, use keysof()[18].

           Examples:

               > valuesof({red, 17})
               17
               > valuesof({red, 17, blue, 100})
               [17, 100]

       var(op, namespace, variable-name [, args ...])
           This function performs various operations on a variable, some of which are awkward or
           impossible to do using the more concise variable reference syntax. For example, the
           namespace or variable name argument to var() can be specified by an expression.

           The following operations are available:

           var(delete, namespace, variable-name)
               Delete (undefine) the variable named variable-name within namespace. If the
               variable is deleted, 1 is returned, and if it does not exist, 0 is returned.

           var(exists, namespace, variable-name)
               Test if the variable named variable-name within namespace exists, returning 1 if
               so and 0 if not.

           var(get, namespace, variable-name [, altval])
               Return the value of the variable named variable-name within namespace. If the
               variable does not exist, altval is returned if given, otherwise the empty string
               is returned (which could potentially be confused with a legitimate value).

           var(set, namespace, variable-name, value)
               Set the value of the variable named variable-name within namespace to value. If
               namespace or variable-name do not exist they are created. If the variable already
               exists, its value is replaced. The function returns value.

           Examples:

               > ${Y::foo} = 17
               17
               > setvar(split, X, "/a/b/c/Y", "/")
               5
               > var(get, X, 4)
               "Y"
               > var(get, X, ${X::#} - 1)
               "Y"
               > var(get, var(get, X, "4"), "foo")
               "17"
               > var(set, Y, "f" . "o" . "o", 2007)
               2007
               > ${Y::foo}
               2007

       vfs(op, vfs-ref [, argument ...])
           This function is an interface to the DACS virtual filestore subsystem, described in
           dacs.vfs(5)[121]. Please refer to dacs.conf(5)[26] and dacsvfs(1)[122] for details and
           examples.

           The first argument specifies the operation to be performed. The second argument
           identifies a filestore (typically a file or database); it can be an absolute pathname,
           an item_type that has been configured through a VFS[26] directive, or a VFS URI[123].
           Zero or more arguments may follow, depending on op. For most operations, the third
           argument will be the key that identifies the object of interest. The underlying
           filestore is implicitly opened and closed.

           An operation that fails abnormally triggers a fatal error.

           The following operations (op) are available:

           vfs(control, vfs-ref, c_op [, argument ...]
               Perform a configuration operation, specified by c_op, on the filestore. Zero or
               more arguments may follow c_op, depending on the semantics of c_op. Not all c_op
               requests are valid for a given storage scheme or have an effect.  True is returned
               if successful, False otherwise. Recognized c_op specifiers are:

               flush
                   For dacs-kwv type schemes, if modified data has been buffered, write it to the
                   underlying storage layer.

               get_container
                   For most schemes, return a URI for the underlying object that stores the
                   instance's data (e.g., the URI for a filename).

               set_cookies
                   For an http or https scheme, each argument is an HTTP cookie to submit with
                   each request. (NOT IMPLEMENTED).

               set_field_sep field_sep
                   For dacs-kwv type schemes, set the field separator string to field_sep.

               set_lock mode
                   For a native filesystem object (the fs scheme), set or unset a lock on the
                   underlying storage object according to mode. (NOT IMPLEMENTED.)

           vfs(defined, item-type)
               Test if the specified item-type has been defined by a VFS[26] directive.

                   vfs(defined, "passwds")

           vfs(delete, vfs-ref [, key])
               Delete the referenced object.

           vfs(enabled [, store-name])
               With an argument, test if the specified store-name can be used. With no argument,
               return a list[69] of enabled store names.

                   vfs(enabled, "db") ? print("yes") : print("no");

           vfs(exists, vfs-ref [, key])
               Test whether the referenced object exists, returning True or False.

                   vfs(exists, "/usr/local/dacs/conf/passwd")
                   vfs(exists, "file:///usr/local/dacs/conf/passwd")

           vfs(get, vfs-ref [, key])
               Retrieve the referenced object.

           vfs(getsize, vfs-ref [, key])
               Return the length, in bytes, of the referenced object.

           vfs(list, vfs-ref)
               List the keys of all objects in the store.

           vfs(put, vfs-ref, value)
           vfs(put, vfs-ref, key, value)
               Store an item under the given key, replacing any existing instance. The value is
               null-terminated.

           vfs(rename, vfs-ref, oldkey, newkey)
               Change the key associated with an existing item from oldkey to newkey.

           vfs(uri, item-type)
               If item-type has been defined by a VFS[26] directive, return its URI, otherwise
               the empty string.

                   > vfs(uri, "passwds")
                   "[passwds]dacs-kwv-fs:/usr/local/dacs/conf/passwd?field_sep=:"

           This statement sets a variable to the contents of the file /tmp/somefile:

               ${somefile} = vfs(get, "file:///tmp/somefile")

           As do this equivalent statements:

               ${somefile} = vfs(get, "/tmp/somefile")
               ${somefile} = get("/tmp/somefile")

           This expression lists the files in the /tmp directory:

               vfs(list,"dacs-fs:/tmp")

           These expressions 1) add a key/value pair to a Berkeley DB database (/tmp/mydb.db),
           creating the database file if necessary, 2) retrieve the value, 3) rename the key, and
           4) list the keys in the database:

               vfs(put, "dacs-db:/tmp/mydb.db", "foo", "baz");
               vfs(get, "dacs-db:/tmp/mydb.db", "foo");
               vfs(rename, "dacs-db:/tmp/mydb.db", "foo", "bar");
               vfs(list, "dacs-db:/tmp/mydb.db");

           This rule fragment denies access if the user has already been granted access five
           times:

               <deny>
                 if (user("auth")) {
                   if (vfs(exists, counter_db, ${DACS::IDENTITY})) {
                     ${count} = vfs(get, counter_db, ${DACS::IDENTITY});
                   }
                   else {
                     ${count} = 0;
                   }
                   if (${count} gt 5) {
                     return(1);
                   }
                   vfs(put, counter_db, ${DACS::IDENTITY}, ++${count});
                   return(0);
                 }
               </deny>

           The item type counter_db would be configured in dacs.conf; e.g.,

               VFS "[counter_db]dacs-db:/usr/local/dacs/federations/counters.db"

SEE ALSO

       dacsexpr(1)[1]

BUGS

       Assorted clunky aspects of the language are likely to be replaced by simplified or more
       general approaches once requirements are clearer. The list and alist data types have not
       been fully developed and integrated. Assignment to a namespace would be a useful
       extension.

       A way to handle errors and exceptions (such as with try/catch/throw statements) would be
       nice. A switch statement and dynamically loaded functions are planned. A foreach statement
       might be useful, although the language has so far successfully avoided loop constructs as
       a way to limit its complexity.

       Various aspects of variables and namespaces are not implemented. A namespace cannot be
       copied by assignment; use setvar().

       Input and output processing is still rather limited.

       Having to use ":" instead of ".." when matching octet ranges with from()[114] is
       unfortunate but avoids pesky period proliferation.

       Some of the more esoteric functions and modes of operation exist primarily to expose DACS
       core code for testing purposes.

AUTHOR

       Distributed Systems Software (www.dss.ca[124])

COPYING

       Copyright2003-2013 Distributed Systems Software. See the LICENSE[125] file that
       accompanies the distribution for licensing information.

NOTES

        1. dacsexpr(1)
           http://dacs.dss.ca/man/dacsexpr.1.html

        2. Perl
           http://www.perl.org/

        3. PHP
           http://www.php.net

        4. Tcl
           http://www.tcl.tk/about

        5. isprint(3)
           http://www.freebsd.org/cgi/man.cgi?query=isprint&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

        6. print()
           http://dacs.dss.ca/man/#print

        7. cast
           http://dacs.dss.ca/man/#cast

        8. modifier flag
           http://dacs.dss.ca/man/#variable_modifiers

        9. dacs_authenticate(8)
           http://dacs.dss.ca/man/dacs_authenticate.8.html

       10. dacs.conf(5)
           http://dacs.dss.ca/man/dacs.conf.5.html

       11. dacs_acs(8)
           http://dacs.dss.ca/man/dacs_acs.8.html

       12. environ(7)
           http://www.freebsd.org/cgi/man.cgi?query=environ&apropos=0&sektion=7&manpath=FreeBSD+9.1-RELEASE&format=html

       13. exec()
           http://dacs.dss.ca/man/#exec

       14. list()
           http://dacs.dss.ca/man/#list

       15. length()
           http://dacs.dss.ca/man/#length

       16. strchars()
           http://dacs.dss.ca/man/#strchars

       17. expression grammar
           http://dacs.dss.ca/man/#expression_grammar

       18. keysof()
           http://dacs.dss.ca/man/#keysof

       19. valuesof()
           http://dacs.dss.ca/man/#valuesof

       20. alist()
           http://dacs.dss.ca/man/#alist

       21. supported data types
           http://dacs.dss.ca/man/#data_types

       22. variable reference
           http://dacs.dss.ca/man/#variables

       23. dacs_notices(8)
           http://dacs.dss.ca/man/dacs_notices.8.html

       24. alist construction operator
           http://dacs.dss.ca/man/#alists

       25. listref()
           http://dacs.dss.ca/man/#listref

       26. VFS
           http://dacs.dss.ca/man/dacs.conf.5.html#VFS

       27. revocation list
           http://dacs.dss.ca/man/dacs.acls.5.html#revocation_list

       28. access control rule
           http://dacs.dss.ca/man/dacs.acls.5.html

       29. on_success()
           http://dacs.dss.ca/man/#on_success

       30. AUTH_SUCCESS
           http://dacs.dss.ca/man/dacs.conf.5.html#AUTH_SUCCESS

       31. ACS_SUCCESS
           http://dacs.dss.ca/man/dacs.conf.5.html#ACS_SUCCESS

       32. ADMIN_IDENTITY
           http://dacs.dss.ca/man/dacs.conf.5.html#ADMIN_IDENTITY

       33. approval stamp
           http://dacs.dss.ca/man/dacs_acs.8.html#dacs_approval

       34. dacs64 decoding
           http://dacs.dss.ca/man/dacs.exprs.5.html#encode

       35. dacs_list_jurisdictions(8)
           http://dacs.dss.ca/man/dacs_list_jurisdictions.8.html

       36. dacsauth(1)
           http://dacs.dss.ca/man/dacsauth.1.html

       37. dacscheck(1)
           http://dacs.dss.ca/man/dacscheck.1.html

       38. encode()
           http://dacs.dss.ca/man/#encode

       39. cryptographic hash
           http://en.wikipedia.org/wiki/Message_digest

       40. MD5 Message-Digest Algorithm
           http://en.wikipedia.org/wiki/MD5

       41. hash()
           http://dacs.dss.ca/man/#hash

       42. decode()
           http://dacs.dss.ca/man/#decode

       43. radix-85
           http://en.wikipedia.org/wiki/Ascii85

       44. RFC 2045
           http://www.rfc-editor.org/rfc/rfc2045.txt

       45. RFC 1738
           http://www.rfc-editor.org/rfc/rfc1738.txt

       46. RFC 2396
           http://www.rfc-editor.org/rfc/rfc2396.txt

       47. RFC 3986
           http://www.rfc-editor.org/rfc/rfc3986.txt

       48. execv(3)
           http://www.freebsd.org/cgi/man.cgi?query=execv&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       49. basename(1)
           http://www.freebsd.org/cgi/man.cgi?query=basename&apropos=0&sektion=1&manpath=FreeBSD+9.1-RELEASE&format=html

       50. dirname(1)
           http://www.freebsd.org/cgi/man.cgi?query=dirname&apropos=0&sektion=1&manpath=FreeBSD+9.1-RELEASE&format=html

       51. stat
           http://dacs.dss.ca/man/#stat

       52. stat(1)
           http://www.freebsd.org/cgi/man.cgi?query=stat&apropos=0&sektion=1&manpath=FreeBSD+9.1-RELEASE&format=html

       53. stat(2)
           http://www.freebsd.org/cgi/man.cgi?query=stat&apropos=0&sektion=2&manpath=FreeBSD+9.1-RELEASE&format=html

       54. printf(3)
           http://www.freebsd.org/cgi/man.cgi?query=printf&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       55. test(1)
           http://www.freebsd.org/cgi/man.cgi?query=test&apropos=0&sektion=1&manpath=FreeBSD+9.1-RELEASE&format=html

       56. mod_access
           http://httpd.apache.org/docs-2.2/mod/mod_access.html

       57. RFC 1035
           http://www.rfc-editor.org/rfc/rfc1035.txt

       58. CIDR notation
           http://en.wikipedia.org/wiki/CIDR_notation

       59. RFC 1338
           http://www.rfc-editor.org/rfc/rfc1338.txt

       60. range specification
           http://dacs.dss.ca/man/#range-spec

       61. regmatch()
           http://dacs.dss.ca/man/#regmatch

       62. user()
           http://dacs.dss.ca/man/#user

       63. digest()
           http://dacs.dss.ca/man/#digest

       64. message authentication code
           http://en.wikipedia.org/wiki/Message_Authentication_Code

       65. Keyed-Hash Message Authentication Code (HMAC)
           http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf

       66. Secure Hash Standard functions
           http://dacs.dss.ca/man/dacs.conf.5.html#SHA_functions

       67. MD5 (RFC 2104)
           http://www.rfc-editor.org/rfc/rfc2104.txt

       68. RFC 2253
           http://www.rfc-editor.org/rfc/rfc2253.txt

       69. list construction operator
           http://dacs.dss.ca/man/#lists

       70. dacspasswd(1)
           http://dacs.dss.ca/man/dacspasswd.1.html

       71. PASSWORD_DIGEST
           http://dacs.dss.ca/man/dacs.conf.5.html#PASSWORD_DIGEST

       72. PASSWORD_SALT_PREFIX
           http://dacs.dss.ca/man/dacs.conf.5.html#PASSWORD_SALT_PREFIX

       73. local_passwd_authenticate
           http://dacs.dss.ca/man/dacs_authenticate.8.html#local_passwd_authenticate

       74. vfs()
           http://dacs.dss.ca/man/#vfs

       75. PASSWORD_CONSTRAINTS
           http://dacs.dss.ca/man/dacs.conf.5.html#PASSWORD_CONSTRAINTS

       76. dacs.conf(5)
           http://dacs.dss.ca/man/dacs.conf.5.html#interpolation

       77. RFC 2898
           http://www.rfc-editor.org/rfc/rfc2898.txt

       78. RFC 3962
           http://www.rfc-editor.org/rfc/rfc3962.txt

       79. LOG_FILTER
           http://dacs.dss.ca/man/dacs.conf.5.html#LOG_FILTER

       80. cryptographically strong pseudo-random values
           http://www.openssl.org/docs/crypto/RAND_bytes.html

       81. strtr()
           http://dacs.dss.ca/man/#strtr

       82. ACS_ERROR_HANDLER
           http://dacs.dss.ca/man/dacs.conf.5.html#ACS_ERROR_HANDLER

       83. ErrorDocument directive
           http://httpd.apache.org/docs-2.2/mod/core.html#errordocument

       84. Rlinks
           http://dacs.dss.ca/man/dacs_acs.8.html#rlinks

       85. permalinks
           http://en.wikipedia.org/wiki/Permalink

       86. regex(3)
           http://www.freebsd.org/cgi/man.cgi?query=regex&apropos=0&esektion=3&emanpath=FreeBSD+9.1-RELEASE&format=html

       87. re_format(7)
           http://www.freebsd.org/cgi/man.cgi?query=re_format&sektion=7&apropos=0&manpath=FreeBSD+9.1-RELEASE&format=html

       88. access control rules
           http://dacs.dss.ca/man/dacs.acls.5.html#elements

       89. read-only namespace
           http://dacs.dss.ca/man/#reserved_namespaces

       90. RFC 2617
           http://www.rfc-editor.org/rfc/rfc2617.txt

       91. copy
           http://dacs.dss.ca/man/#setvar-copy

       92. query
           http://dacs.dss.ca/man/#setvar-query

       93. cgiparse(8)
           http://dacs.dss.ca/man/cgiparse.8.html

       94. split
           http://dacs.dss.ca/man/#setvar-split

       95. sleep(3)
           http://www.freebsd.org/cgi/man.cgi?query=sleep&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       96. sprintf(3)
           http://www.freebsd.org/cgi/man.cgi?query=sprintf&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       97. strftime(3)
           http://www.freebsd.org/cgi/man.cgi?query=strftime&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       98. strptime(3)
           http://www.freebsd.org/cgi/man.cgi?query=strptime&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       99. tr(1)
           http://www.freebsd.org/cgi/man.cgi?query=tr&apropos=0&sektion=1&manpath=FreeBSD+9.1-RELEASE&format=html

       00. DACS name
           http://dacs.dss.ca/man/dacs.1.html#naming

       01. RFC 822
           http://www.rfc-editor.org/rfc/rfc822.txt

       02. RFC 952
           http://www.rfc-editor.org/rfc/rfc952.txt

       03. FEDERATION_NAME
           http://dacs.dss.ca/man/dacs.conf.5.html#FEDERATION_NAME

       04. RFC 1123
           http://www.rfc-editor.org/rfc/rfc1123.txt

       05. RFC 790
           http://www.rfc-editor.org/rfc/rfc790.txt

       06. JURISDICTION_NAME
           http://dacs.dss.ca/man/dacs.conf.5.html#JURISDICTION_NAME

       07. strptime()
           http://dacs.dss.ca/man/#strptime

       08. localtime(3)
           http://www.freebsd.org/cgi/man.cgi?query=localtime&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE&format=html

       09. dacstransform(1)
           http://dacs.dss.ca/man/dacstransform.1.html

       10. transform_config()
           http://dacs.dss.ca/man/#transform_config

       11. concise user syntax
           http://dacs.dss.ca/man/dacs.1.html#concise_user_syntax

       12. transform()
           http://dacs.dss.ca/man/#transform

       13. ACS_CREDENTIALS_LIMIT
           http://dacs.dss.ca/man/dacs.conf.5.html#ACS_CREDENTIALS_LIMIT

       14. from()
           http://dacs.dss.ca/man/#from

       15. dacs.groups(5)
           http://dacs.dss.ca/man/dacs.groups.5.html

       16. styles
           http://dacs.dss.ca/man/dacs_authenticate.8.html#STYLE

       17. dacs_auth_agent(8)
           http://dacs.dss.ca/man/dacs_auth_agent.8.html

       18. dacs_auth_transfer(8)
           http://dacs.dss.ca/man/dacs_auth_transfer.8.html

       19. dacscookie(1)
           http://dacs.dss.ca/man/dacscookie.1.html

       20. DACS username
           http://dacs.dss.ca/man/dacs.1.html#dacs_identity

       21. dacs.vfs(5)
           http://dacs.dss.ca/man/dacs.vfs.5.html

       22. dacsvfs(1)
           http://dacs.dss.ca/man/dacsvfs.1.html

       23. VFS URI
           http://dacs.dss.ca/man/man/dacs.conf.5.html#VFS

       24. www.dss.ca
           http://www.dss.ca

       25. LICENSE
           http://dacs.dss.ca/man/../misc/LICENSE