DACS.EXPRS(5) | DACS Formats Manual | DACS.EXPRS(5) |
dacs.exprs - DACS expression language
These files are part of the DACS suite.
DPL (the DACS programming language) is used in access control rules, revocation lists, configuration files, for self-testing DACS, in general purpose scripts, and interactively. The ability to evaluate scripts gives DACS maximum run-time configurability and flexibility. A DPL expression 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.
The philosophy guiding the design of the DACS expression language is that it should remain small and limited to basic operations on elementary data types that can be expressed simply and evaluated efficiently. 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. A collection of utility and higher-level functions are provided for the typical kinds of tasks at hand. Only a subset of the functionality implemented within DACS is accessible through the language.
While fleshing out the language is not a priority, expression syntax and the set of functions are being extended as necessary. An extensibility mechanism has been designed 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.
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.
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:
> ${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> -->
The following basic data types are supported:
integer
int
real
double
string
${Foo::QUERY_STRING} = "a=1&b=2"
foo"baz" foo."baz"
binary
bstring
> "\0\1\2" "000102"
bareword
file(test, "-e", foo) file("test", "-e", "foo")
These two expressions are equivalent and yield "foobaz":
foo."baz" foo.baz
bool
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.
Support for binary data is only partially implemented.
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.
Variables having names that would ordinarily be invalid may be created during execution. Although they may be visible in some contexts, they cannot be directly referenced or modified.
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.
The variables within a namespace have no predictable natural ordering; the namespace can be thought of as an unordered set of variables. This of course does not preclude the application of a naming convention to effectively order the contents of the namespace. For example, we might do:
${Months::first} = "January" ${Months::second} = "February" ${Months::third} = "March"
Or perhaps:
${Months::0} = "January" ${Months::1} = "February" ${Months::2} = "March"
A variable reference may not contain any whitespace except when it appears after a ? or + modifier flag[8].
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
i
n
z
?
+
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
Argv
Auth
Conf
Cookies
DACS
Env
ExecEnv
Expr
LDAP
Temp
${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.
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
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
Individual characters and sequences of characters of a string-valued expression can be selected using strchars()[16], which uses a similar syntax.
> $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.
There is currently no way to delete an alist pair.
The following grammar is used to construct an expression (EXP) or sequence (S) of expressions.
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 VAR ASSIGN_OP E 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.
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}
Here are additional examples:
> "hello, " . (if (0) {b . y . e} else {"world"}) "hello, world" > encode(hex, if (1 ge 1) { "hi" } else {"bye"}) "6869"
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.
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.
In the examples that follow, the '>' character at the beginning of an input line is a prompt from dacsexpr(1)[1].
,
=, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=, .=
> ${foo::bar} = "hello" "hello" > ${foo::bar} .= ", world" "hello, world" > ${a} = [1, 2] [1,2] > ${a} .= [3, 4] [1,2,3,4]
?:
or, ||
and, &&
|
^
&
==, !=, eq, ne, eq:i, ne:i
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
If either argument is of type bstring 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.
.
> "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]]
4.5
will be scanned as a number whereas, for example, the input:
"4".5
will evaluate to the string "45".
<<, >>
+, -
*, /, %
**
> 2**10 1024
+, -, not, !, ~, ++VAR, --VAR, (type)
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
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].
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-uri[, ...][, EXACT_MATCH | ALL_MATCH])
alist([key, value [, ...])
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)
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}
argon2(password, salt, secret, ad, outlen, t_cost, m_cost, lanes, threads[, alg])
Argon2 was selected as the winner of the Password Hashing Competition[27] in 2015. For details, refer to draft-irtf-cfrg-argon2-03[28] and phc-winner-argon2[29].
> argon2( bstring("\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1", 0), bstring("\2\2\2\2\2\2\2\2\2\2\2\2\2\2\2\2", 0), bstring("\3\3\3\3\3\3\3\3",0), bstring("\4\4\4\4\4\4\4\4\4\4\4\4",0), 32, 3, 32, 4, 4) "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8"
bstring(string, length)
bstring(string)
> bstring("\0\1\2", 4) "000102" > bstring("\0\1\2", 2) "0001"
contains_any(format, test-set, target-set[, nocase])
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])
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[30]). 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.
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.
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.
This operation deletes an existing counter. The operation can be del or delete.
This operation returns 1 if a counter exists, 0 otherwise.
This operation returns the current counter value.
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.
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.
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.
Modifications to counters are not atomic. Amongst other things, this means that a crash may cause counter updates to be lost.
% 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[31] or by an access control rule[32], for instance:
counter(exists, "dacs-kwv-fs:/usr/local/dacs/counters/logins", ${DACS::IDENTITY})
The counter might be conditionally updated using the on_success()[33] function, or the AUTH_SUCCESS[34] or ACS_SUCCESS[35] directives, using an expression like:
counter(decdel, "dacs-kwv-fs:/usr/local/dacs/counters/logins", ${DACS::IDENTITY})
csv(input, ifs [,startq, endq])
dacs_admin()
dacs_approval(op[, ...])
The following operations are available:
dacs_approval(approval, dacs64-approval-message, namespace)
dacs_approval(check, dacs64-approval-message)
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)
dacs_meta(op[, ...])
The following operations are available:
dacs_meta(federation, namespace)
dacs_meta(jname, jurisdiction-name, namespace)
dacs_meta(jurisdiction, namespace)
dacs_meta(list_jurisdictions)
dacs_meta(update_jurisdiction, jname [,url])
dacs_meta(update_jurisdictions, jname)
dacsauth(dacsauth-flags)
dacsauth(arg1, arg2[, ...])
An alist is returned that has the following three elements:
result
identity
roles
dacscheck(dacscheck-flags)
dacscheck(arg1, arg2[, ...])
debug(type, value)
The following type names are recognized: TBD
decode(encoding-type, string)
For the hex encoding type, alphabetic characters may be upper case or lower case.
digest(msg, msg-len [, digest-name])
digest(msg, msg-len, "SHA-512/t", t)
digest(msg, msg-len, "Blake2" [, hlen [,
key, key-len]])
The following digest algorithms (digest-name) are available:
"md5"
"SHA"
"SHA1"
"SHA224"
"SHA256"
"SHA384"
"SHA512"
"SHA512/224"
"SHA512/256"
"SHA512/t"
"SHA3-224"
"SHA3-256"
"SHA3-384"
"SHA3-512"
"Blake2"
SHA1 is used by default.
The available digest functions are described by the "dacs --digests" command (see dacs(1)[50]) and listed by dacsversion(1)[51] and dacs_version(8)[52]. The available key derivation functions, such as pbkdf2()[53] and scrypt()[54], are also shown.
The digest-name is matched against the names of available hash functions case-insensitively. Non-consecutive hyphens, underscores, and slashes may be used as equivalent separators in digest-name. A separator may be omitted if doing so would not join two digits. An initial or trailing separator is disallowed. For example, "Sha224" matches "SHA-224", "md-5" matches "md5", "sha/512" matches "SHA-512", "sha-512t" matches "SHA512/t", and "SHA_512_224" matches "SHA512/224", but "sha3224" is invalid for "sha3-224", as are "sha3--224" and "sha3-224_".
The function value is a bstring and is always printed as a hex string.
If cryptographic strength is not required, see hash()[55].
> digest("foo", 0, "md5") "acbd18db4cc2f85cedef654fccc4a4d8" > digest("Hello, world", 0, "SHA256") "4ae7c3b6ac0beff671efa8cf57386151c06e58ca53a78d83f36107316cec125f" > digest("abc", 0, "sha512/t", 72) "644d768d5298864595" > digest("\x0c", 0, "SHA3-384",7) "b5a8cb0bf073b6b68d95cd33f5b09289670120bb931fc838b830d2592268b9e145a09088172b96eafb0093ef9a85df08" > substr(encode(hex, digest("one two three", 0, blake2, 64, "my secret", 0)), 1, 64) "fc182724dc024b95f62e606859ac806e4edca09a927f6bc8bccd07dade3e4f26"
encode(encoding-type, arg)
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)
> encode(ascii85, decode(hex, "123456789a")) "&i<X6RK"
encode(base32, arg)
> encode(base32, "Auggie") "IF2WOZ3JMU======" > decode(base32,"IF2WOZ3JMU======") "Auggie"
encode(cescape, arg)
> encode(cescape, bstring("hi\0\1\2\3\012", 7)) "hi\0\001\002\003\n"
encode(dacs64, arg)
> encode(dacs64, bstring("\0\0\0\1", 4)) "_____-"
encode(hex, arg)
> encode(hex, "Hello") "48656c6c6f"
encode(mime, arg)
> encode(mime, bstring("\0\0\0\1", 4)) "AAAAAQ=="
encode(percent, arg)
> encode(percent, "Hello, world.%") "%48%65%6C%6C%6F%2C%20%77%6F%72%6C%64%2E%25"
encode(url, arg)
> encode(url, bstring("a\0b", 3)) "a%00b"
eval(expression)
The call:
> eval("length(\"abc\")") 3
exec(prog, ...)
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"
exit(result)
expand(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 is used to extract the last component of a pathname and is equivalent to the basename(1)[64] 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"
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")
Equivalent to the dirname(1)[65] 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"
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"
This is like the stat[66] 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.
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).
If file is a symbolic link, print its contents.
Remove (delete) file.
Rename (mv) source-file to target-file.
Remove (delete) directory, which must be empty.
Similar to the stat(1)[67] command available on some systems, this makes the functionality of the stat(2)[68] system call available. The fmt argument is a printf(3)[69]-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:
The value of st_dev.
The value of st_ino.
The value of st_mode in octal.
The value of st_mode as text.
The value of st_nlink.
The value of st_uid in decimal.
The value of st_uid as text.
The value of st_gid in decimal.
The value of st_gid as text.
The value of st_rdev.
The value of st_size.
The value of st_blksize.
The value of the file argument.
If the argument is a symbolic link, print the contents of the link, otherwise print the file argument.
The value of st_atime in decimal.
The value of st_atime as text.
The value of st_mtime in decimal.
The value of st_mtime as text.
The value of st_ctime in decimal.
The value of st_ctime as text.
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>
Most of the file-testing predicates of the test(1)[70] command are available.
True if file exists and is a block special file.
True if file exists and is a character special file.
True if file exists and is a directory.
True if file exists, regardless of its type.
True if file exists and is a regular file.
True if file exists and its set group ID flag is set.
True if file exists and its sticky bit is set.
True if file exists and is a named pipe (FIFO).
True if file exists and is readable (access(file, R_OK) == 0).
True if file exists and has a size greater than zero bytes.
True if file exists and its set user ID flag is set.
True if file exists and is writable (access(file, W_OK) == 0).
True if file exists and is executable (access(file, X_OK) == 0).
True if file exists and is a symbolic link.
True if file exists and its owner matches the effective user id of this process.
True if file exists and its group matches the effective group id of this process.
True if file exists and is a socket.
True if file1 and file2 exist and the former is newer than the latter.
True if file1 and file2 exist and the former is older than the latter.
True if file1 and file2 exist and refer to the same file.
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])
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])
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)
The string argument may be:
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[72]). 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.
from("10.0.0.123")
from("10.0")
from("10.0.0.0/255.255.0.0")
from("10.0.0.0/8")
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")
from("all")
An alternative method is to perform a regular expression match against ${DACS::REMOTE_ADDR} using regmatch()[76].
get(vfs-ref [,key])
hash(msg, msg-len [,hash-name])
> hash("Hello, world", 0) "3696529580" > hash("Hello, world", 0, hash64) "462009511995194717"
hkdf(input, salt, info, length, digest-name)
The input, salt, and info arguments are of type string or bstring (converted as required). Only input must not be zero length. A bstring of length bytes is returned. Any HMAC compatible[81] cryptographic hash function can be given as digest-name.
> hkdf(decode(hex, "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), \
decode(hex, "000102030405060708090a0b0c"), \
decode(hex, "f0f1f2f3f4f5f6f7f8f9"), 42, "sha-256") "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865" > hkdf("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", \
"","",42,"sha-1") "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918"
hmac(msg, msg-len, key, key-len [, digest-name])
Note that the function is not commutative. The key is the third argument, not the first. If you are not getting the expected value from this function, try exchanging the msg and key arguments.
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" > hmac(decode(hex, "4869205468657265"), 0, \
decode(hex, "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), 0, "sha3-224") "3b16546bbc7be2706a031dcafd56373d9884367641d8c59af3c860f7"
http(url, [method [,arglist]])
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/TLS, 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 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)
Examples:
info(namespaces) info(namespace, "Conf")
keysof(alist)
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 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 nth RDN 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 nth AttributeTypeAndValue 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 nth AttributeTypeAndValue 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)
list([value [, ...])
list(1, 2, [hello, world], 5)
is equivalent to the expression:
[1, 2, [hello, world], 5]
listref(list, list-ref [, ...])
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])
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])
The following operations are available (the operation is specified by the first argument to the function):
password(check, given-password, password-digest [,alg-name])
password(getdata, username [,vfs-ref])
password(getdigest, username [,vfs-ref])
password(hash, plain-password [,alg-name])
password(list [, vfs-ref])
password(syntax, password [,constraints])
password(test, test-op, username [,vfs-ref])
Examples:
> password(hash, "bobo") "2|XYZZYxBhU/7VgJAt2lc.G|HL4RQ2vo0uNoXlXnv.GcY3Vlf9." > password(check, "bobo", "2|XYZZYxBhU/7VgJAt2lc.G|HL4RQ2vo0uNoXlXnv.GcY3Vlf9.") 1
pathname(path, hostname, port)
pbkdf2(password, salt, count, dklen [, digest-name])
> pbkdf2("password", "ATHENA.MIT.EDUraeburn", 1200, 32) "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13" > pbkdf2("password", decode(hex,"1234567878563412"), 5, 16) "d1daa78615f287e6a1c8b120d7062a49" > pbkdf2("password", "salt", 4096, 20, "SHA256") "c5e478d59288c841aa530db6845c4c8d962893a0" > pbkdf2("1", "2", 1024, 32, "SHA512") "a180451f4618df9515ab0be2c56ac3420287cb8fc015f78494c9394a62ef6e66"
print(...)
printf(fmt, ...)
random(bytes, nbytes)
random(uint, lo, hi)
random(string, nbytes [, spec])
random(stringc, nbytes, spec)
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()[98] 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.
readline()
redirect(error-code, target)
redirect(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[101].
Because the rule associated with the short link can be changed at any time, this feature can be used to implement smart permalinks[102].
This mechanism can also be used to implement a linkback[103] method in which an action is triggered (such as a notification) when a link is invoked.
regmatch(string, regex [, namespace] [, nocase])
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)[104], re_format(7)[105]).
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])
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)
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
request_match_url_patterns(namespace)
return(result)
rule(object, ruleset_vfs)
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.
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");
scrypt(password, salt, N, r, p, dklen)
> scrypt("password", "NaCl", 1024, 8, 16, 32) "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162"
setvar(op, dst-namespace [, args ...])
The dst-namespace cannot be a read-only namespace[110]. 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 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)
setvar(delete, dst-namespace)
setvar(kwv, dst-namespace, assign-char, sep-chars, string)
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 load operator reads the contents of an object that consists of newline-separated text. The first line is assigned the name "0" in dst-namespace, the second "1", and so on. The object can be specified using a filename, as an item_type, or using a vfs-ref (see vfs()[91]),
setvar(loadi, dst-namespace, vfs-ref)
> setvar(loadi, PASSWD, "dacs-kwv-fs:/etc/passwd") 23 > ${PASSWD::root} "*:0:0:Charlie &:/root:/bin/csh" > ${PASSWD::bobo} "bobo:*:1001:1001:Bobo &:/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)
setvar(post, dst-namespace [, content-type, string])
The form that takes string is not yet implemented.
setvar(query, dst-namespace, query-string)
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])
setvar(rename, dst-namespace, src-namespace)
setvar(split, dst-namespace, string, delimiter [,limit [,dflag]])
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()[98].)
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)
> 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" " > ${X::SERVER} "foo.example.com:8443"
sizeof(typename)
> sizeof(real) 8
sleep(seconds)
source(vfs-ref [,key])
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, ...)
${a} = sprintf("Hello") . ", world." "Hello, world." length(sprintf("Hello") . ", world.") 13
strchars(str, range-spec [,...])
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)
> strchop("foo4.859", ".56789") "foo4" > strchop("foo7.859", ".5679") "foo7.8" > strchop("hello ", " ") "hello" > strchop("dogs rule\n\n", "\n") "dogs rule"
strclone(str, count)
> strclone("abc", 1) "abc" > strclone("abc", 3) "abcabcabc" > strclone("\x0b",22) "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b" > strclone(decode(hex, "01"), 7) "01010101010101"
strftime(format)
strptime(date-str, date-format,
namespace)
strptime(namespace)
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])
> strrstr("afoofoofooz", "foo") "fooz" > strrstr("afOOfoofooz", "FooF", nocase) "foofooz" > strrstr("afOOfoofooz", "ofoo",nocase) "ofooz"
strstr(string, substring [, nocase])
> strstr("foobazbar", "baz") "bazbar" > strstr("foobazbar", "") "foobazbar" > strstr("foobazbar", "zzz") "" > strstr("", "zzz") "" > strstr("afoofoofooz", "foo") "foofoofooz" > strstr("fooZbar", "Ozb", nocase) "oZbar"
strtolower(string)
strtolower("Hello, World 2008") strtr("Hello, World 2008", "A-Z", "a-z")
strtoupper(string)
strtoupper("Hello, World 2008") strtr("Hello, World 2008", "a-z", "A-Z")
strtr(input-string, string1, [string2 [,cds]])
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
d
s
> strtr("AbCdEf", "A-Z", "a-z") "abcdef" > strtr("/a//b///c", "/", "", "s") "/a/b/c"
subset(format, purported-subset, superset [, nocase])
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)
> 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])
The following tests are recognized:
syntax(charset, name, charset_spec)
syntax(dacsname, name)
syntax(emailaddr, name)
syntax(expr, name)
syntax(domainname, name)
syntax(federation, name)
syntax(group, name)
syntax(hostname, name)
syntax(ipaddr, name)
syntax(jurisdiction, name)
syntax(namespace, name)
syntax(role, name)
syntax(uri, name)
syntax(username, name)
syntax(variable, name)
syntax(varname, name)
> 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)
The format argument, which is treated case-insensitively, can be any of the following:
transform(input,name,rules,docs
[,idents])
transform(input,config,name,rules,docs
[,idents])
transform_config(flags)
The following flags are recognized:
trim(string, delete-set [,limit])
> trim("abceffff", "f") "abce" > trim("abceffff", abf) "abce" > trim("a\n\n\n", "\n") "a" > trim("a", "a-z") ""
typeof([typename,] expression)
> typeof(4.5) "real" > typeof(integer, 4.5) 0
undef()
user(string)
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
user("METALOGIC:auggie") user(":bobo")
If the jurisdiction name or federation name components are omitted, the current federation and jurisdiction[121] 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
user("METALOGIC:") user("DEMO::METALOGIC:")
federation
user("DEMO::")
address
user("10.0.0.123") user("10.0.0.0/24") user("example.com")
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)[136].
namespace ns
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
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
admin
alien
cas
cert[ificate]
digest
expr
gen[erated]
import[ed]
infocard
nat[ive]
managed_infocard
pass[word]
passwd
prompt[ed]
rlink
selfissued_infocard
simple
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
user("importedby METALOGIC")
version protocol-version
user("version 1.4")
authenticated, unauthenticated
user("auth") user("unauth")
mine
user("mine")
any
user("any")
none
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).
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")
user("METALOGIC:")
Return True if the client was authenticated by the jurisdiction METALOGIC in this federation
user("METALOGIC:rmorriso")
Return True if the client was authenticated as the user METALOGIC:rmorriso
user("DEMO::METALOGIC:rmorriso")
Return True if the client was authenticated by the given federation and jurisdiction as rmorriso
user("%METALOGIC:admin")
Return True if the client is a member of the group METALOGIC:admin
user("*:rmorriso")
Return True if the client was authenticated as the username rmorriso by any jurisdiction in this federation
user("auth")
Return True if the client was authenticated anywhere
user("UnAuthenticated")
Return True if the client is not authenticated
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
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)
user("ANY")
Always return True
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)
user(":rmorriso")
Return True if the client was authenticated as rmorriso by this jurisdiction
user(":rmorriso nocase")
Return True if the client was authenticated as rmorriso, case-insensitively, by this jurisdiction
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
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
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 ...])
h=hostid, s=seqno
A hostid consists of one or more characters from the same set used for a DACS username[141]. 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[30]. 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)
ustamp(ntpclock, vfs-uri, ntp_host)
ustamp(user, vfs-uri, seqno)
Examples:
> ustamp(clock, "${Conf::DACS_HOME}/hostid") "h=2fbae312ddc1d2ae388cea1b57a47c66, s=1185565675:9"
valuesof(alist)
Examples:
> valuesof({red, 17}) 17 > valuesof({red, 17, blue, 100}) [17, 100]
var(op, namespace, variable-name [, args ...])
The following operations are available:
var(delete, namespace, variable-name)
var(exists, namespace, variable-name)
var(get, namespace, variable-name [, altval])
var(set, namespace, variable-name, 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 ...])
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[30] directive, or a VFS URI[144]. 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 ...]
flush
get_container
set_cookies
set_field_sep field_sep
set_lock mode
vfs(defined, item-type)
vfs(defined, "passwds")
vfs(delete, vfs-ref [, key])
vfs(enabled [, store-name])
vfs(enabled, "db") ? print("yes") : print("no");
vfs(exists, vfs-ref [, key])
vfs(exists, "/usr/local/dacs/conf/passwd") vfs(exists, "file:///usr/local/dacs/conf/passwd")
vfs(get, vfs-ref [, key])
vfs(getsize, vfs-ref [, key])
vfs(list, vfs-ref)
vfs(load, vfs-ref [, key])
vfs(loadrc, vfs-ref [, key])
Consider the following example:
> ($pwd = vfs(loadrc, "/etc/passwd")) && "ok" 1 > length($pwd) 33 > $pwd[0] "root:*:0:0:Charlie &:/root:/bin/csh"
Here, there are 33 non-comment, non-empty lines in /etc/passwd and the first one is printed.
vfs(put, vfs-ref, value)
vfs(put, vfs-ref, key, value)
vfs(rename, vfs-ref, oldkey, newkey)
vfs(uri, item-type)
> 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"
dacsexpr(1)[1]
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. Over time, regular-ness of the language has drifted a little.
A way to handle errors and exceptions (such as with try/catch/throw statements) would be nice. Even a simple "trap" function would be useful. 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().
Real numbers are second-class citizens. There are no square root or trig functions, for instance.
Some functionality, such as generation of one-time passwords using HOTP and TOTP (see dacstoken(1)[145]), has not been provided.
Input and output processing is still rather limited, but maybe that's a feature.
Having to use ":" instead of ".." when matching octet ranges with from()[135] 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. Some of these functions do not have particularly efficient implementations.
Expressions are not intended to be used for long-running programs, only short computations.
The string length argument taken by a few functions (like digest()[78]) is probably unnecessary in practice since the substr()[146] function can be used. The length argument should be removed or perhaps replaced by a more versatile range-specifying argument.
Distributed Systems Software (www.dss.ca[147])
Copyright © 2003-2018 Distributed Systems Software. See the LICENSE[148] file that accompanies the distribution for licensing information.
02/19/2019 | DACS 1.4.40 |