summaryrefslogtreecommitdiffstats
path: root/doc/antora/modules/unlang
diff options
context:
space:
mode:
Diffstat (limited to 'doc/antora/modules/unlang')
-rw-r--r--doc/antora/modules/unlang/.gitignore1
-rw-r--r--doc/antora/modules/unlang/nav.adoc51
-rw-r--r--doc/antora/modules/unlang/pages/attr.adoc77
-rw-r--r--doc/antora/modules/unlang/pages/break.adoc28
-rw-r--r--doc/antora/modules/unlang/pages/case.adoc44
-rw-r--r--doc/antora/modules/unlang/pages/condition/and.adoc21
-rw-r--r--doc/antora/modules/unlang/pages/condition/cmp.adoc104
-rw-r--r--doc/antora/modules/unlang/pages/condition/eq.adoc30
-rw-r--r--doc/antora/modules/unlang/pages/condition/index.adoc85
-rw-r--r--doc/antora/modules/unlang/pages/condition/not.adoc19
-rw-r--r--doc/antora/modules/unlang/pages/condition/operands.adoc37
-rw-r--r--doc/antora/modules/unlang/pages/condition/or.adoc21
-rw-r--r--doc/antora/modules/unlang/pages/condition/para.adoc19
-rw-r--r--doc/antora/modules/unlang/pages/condition/regex.adoc180
-rw-r--r--doc/antora/modules/unlang/pages/condition/return_codes.adoc35
-rw-r--r--doc/antora/modules/unlang/pages/default.adoc47
-rw-r--r--doc/antora/modules/unlang/pages/else.adoc30
-rw-r--r--doc/antora/modules/unlang/pages/elsif.adoc43
-rw-r--r--doc/antora/modules/unlang/pages/foreach.adoc40
-rw-r--r--doc/antora/modules/unlang/pages/group.adoc39
-rw-r--r--doc/antora/modules/unlang/pages/if.adoc29
-rw-r--r--doc/antora/modules/unlang/pages/index.adoc162
-rw-r--r--doc/antora/modules/unlang/pages/keywords.adoc78
-rw-r--r--doc/antora/modules/unlang/pages/list.adoc72
-rw-r--r--doc/antora/modules/unlang/pages/load-balance.adoc32
-rw-r--r--doc/antora/modules/unlang/pages/module.adoc86
-rw-r--r--doc/antora/modules/unlang/pages/module_builtin.adoc42
-rw-r--r--doc/antora/modules/unlang/pages/module_method.adoc27
-rw-r--r--doc/antora/modules/unlang/pages/redundant-load-balance.adoc39
-rw-r--r--doc/antora/modules/unlang/pages/redundant.adoc42
-rw-r--r--doc/antora/modules/unlang/pages/return.adoc36
-rw-r--r--doc/antora/modules/unlang/pages/return_codes.adoc17
-rw-r--r--doc/antora/modules/unlang/pages/switch.adoc83
-rw-r--r--doc/antora/modules/unlang/pages/type/all_types.adoc80
-rw-r--r--doc/antora/modules/unlang/pages/type/double.adoc39
-rw-r--r--doc/antora/modules/unlang/pages/type/index.adoc117
-rw-r--r--doc/antora/modules/unlang/pages/type/ip.adoc15
-rw-r--r--doc/antora/modules/unlang/pages/type/numb.adoc11
-rw-r--r--doc/antora/modules/unlang/pages/type/string/backticks.adoc38
-rw-r--r--doc/antora/modules/unlang/pages/type/string/double.adoc68
-rw-r--r--doc/antora/modules/unlang/pages/type/string/escaping.adoc14
-rw-r--r--doc/antora/modules/unlang/pages/type/string/single.adoc19
-rw-r--r--doc/antora/modules/unlang/pages/type/string/unquoted.adoc21
-rw-r--r--doc/antora/modules/unlang/pages/update.adoc160
-rw-r--r--doc/antora/modules/unlang/pages/xlat/alternation.adoc24
-rw-r--r--doc/antora/modules/unlang/pages/xlat/attribute.adoc54
-rw-r--r--doc/antora/modules/unlang/pages/xlat/builtin.adoc891
-rw-r--r--doc/antora/modules/unlang/pages/xlat/character.adoc80
-rw-r--r--doc/antora/modules/unlang/pages/xlat/index.adoc56
-rw-r--r--doc/antora/modules/unlang/pages/xlat/module.adoc18
-rw-r--r--doc/antora/modules/unlang/partials/rcode_table.adoc39
51 files changed, 3440 insertions, 0 deletions
diff --git a/doc/antora/modules/unlang/.gitignore b/doc/antora/modules/unlang/.gitignore
new file mode 100644
index 0000000..c5722d7
--- /dev/null
+++ b/doc/antora/modules/unlang/.gitignore
@@ -0,0 +1 @@
+!*.adoc
diff --git a/doc/antora/modules/unlang/nav.adoc b/doc/antora/modules/unlang/nav.adoc
new file mode 100644
index 0000000..77be328
--- /dev/null
+++ b/doc/antora/modules/unlang/nav.adoc
@@ -0,0 +1,51 @@
+* xref:index.adoc[Unlang Policy Language]
+
+** xref:list.adoc[Attribute Lists]
+** xref:attr.adoc[Attribute References]
+** xref:return_codes.adoc[Return Codes]
+
+** xref:keywords.adoc[Keywords]
+*** xref:break.adoc[break]
+*** xref:case.adoc[case]
+*** xref:else.adoc[else]
+*** xref:elsif.adoc[elsif]
+*** xref:foreach.adoc[foreach]
+*** xref:group.adoc[group]
+*** xref:if.adoc[if]
+*** xref:load-balance.adoc[load-balance]
+*** xref:redundant-load-balance.adoc[redundant-load-balance]
+*** xref:redundant.adoc[redundant]
+*** xref:return.adoc[return]
+*** xref:switch.adoc[switch]
+*** xref:update.adoc[update]
+
+** xref:module.adoc[Modules]
+*** xref:module_method.adoc[Module Methods]
+*** xref:module_builtin.adoc[Built-in Modules]
+
+** xref:type/index.adoc[Data Types]
+*** xref:type/index.adoc[List of Data Types]
+*** xref:type/ip.adoc[IP Addresses]
+*** xref:type/numb.adoc[Numbers]
+*** xref:type/string/single.adoc[Single Quoted Strings]
+*** xref:type/string/double.adoc[Double Quoted Strings]
+*** xref:type/string/backticks.adoc[Backtick-quoted string]
+*** xref:type/string/unquoted.adoc[Unquoted Strings]
+
+** xref:condition/index.adoc[Conditional Expressions]
+*** xref:condition/cmp.adoc[Comparisons]
+*** xref:condition/operands.adoc[Operands]
+*** xref:condition/return_code.adoc[The Return Code Operator]
+*** xref:condition/eq.adoc[The '==' Operator]
+*** xref:condition/and.adoc[The '&&' Operator]
+*** xref:condition/or.adoc[The '||' Operator]
+*** xref:condition/not.adoc[The '!' Operator]
+*** xref:condition/para.adoc[The '( )' Operator]
+*** xref:condition/regex.adoc[Regular Expressions]
+
+** xref:xlat/index.adoc[String Expansion]
+*** xref:xlat/alternation.adoc[Alternation Syntax]
+*** xref:xlat/builtin.adoc[Built-in Expansions]
+*** xref:xlat/character.adoc[Single Letter Expansions]
+*** xref:xlat/attribute.adoc[Attribute References]
+*** xref:xlat/module.adoc[Module References]
diff --git a/doc/antora/modules/unlang/pages/attr.adoc b/doc/antora/modules/unlang/pages/attr.adoc
new file mode 100644
index 0000000..70afce4
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/attr.adoc
@@ -0,0 +1,77 @@
+= &Attribute References
+
+.Syntax
+[source,unlang]
+----
+[&]Attribute-Name
+----
+
+The `&Attribute-Name` operator returns a reference to the named
+attribute.
+
+When used as an existence check in a condition, the condition
+evaluates to `true` if the attribute exists. Otherwise, the condition
+evaluates to `false`.
+
+When used elsewhere, such as in xref:switch.adoc[switch], it returns
+the value of the named attribute.
+
+.Examples
+[source,unlang]
+----
+&User-Name
+&NAS-IP-Address
+----
+
+== Lists
+
+The attribute reference can also be qualified with a
+xref:list.adoc[list] reference. When no list is given, the server
+looks in the input packet list for the named attribute.
+
+.Examples
+
+[source,unlang]
+----
+&request:User-Name
+&reply:NAS-IP-Address
+----
+
+== Array References
+
+.Syntax
+[source,unlang]
+----
+&Attribute-Name[<integer>]
+----
+
+When an attribute appears multiple times in a list, this syntax allows
+you to address the attributes as if they were array entries. The
+`<integer>` value defines which attribute to address. The `[0]` value
+refers to the first attributes, `[1]` refers to the second attribute,
+etc.
+
+.Examples
+[source,unlang]
+----
+&EAP-Message[1]
+&reply:NAS-IP-Address[2]
+----
+
+== Removing ambuguity from the configuration files
+
+The server does not use the `&` character to distinguish attribute names
+from other strings.
+
+Without the `&`, it is more difficult to parse the configuration file
+clearly. You could interpret a string as `hello-there`
+either as a literal string "hello-there", or as a reference to an
+attribute named `hello-there`.
+
+Adding the leading `&` character means that attribute references are
+now easily distinguishable from literal strings. The use of the leading
+`&` character is highly recommended.
+
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/break.adoc b/doc/antora/modules/unlang/pages/break.adoc
new file mode 100644
index 0000000..01783ea
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/break.adoc
@@ -0,0 +1,28 @@
+= The break statement
+
+.Syntax
+[source,unlang]
+----
+break
+----
+
+The `break` statement is used to exit an enclosing
+xref:foreach.adoc[foreach] loop. The `break` statement only be used
+inside of a xref:foreach.adoc[foreach] loop.
+
+.Example
+[source,unlang]
+----
+foreach &Class {
+ if (&Foreach-Variable-0 == 0xabcdef) {
+ break
+ }
+
+ update reply {
+ Reply-Message += "Contains %{Foreach-Variable-0}"
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/case.adoc b/doc/antora/modules/unlang/pages/case.adoc
new file mode 100644
index 0000000..ba2b5fe
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/case.adoc
@@ -0,0 +1,44 @@
+= The case Statement
+
+.Syntax
+[source,unlang]
+----
+case [ <match> ] {
+ [ statements ]
+}
+----
+
+The `case` statement is used to match data inside of a
+xref:switch.adoc[switch] statement. The `case` statement cannot be used
+outside of a xref:switch.adoc[switch] statement.
+
+
+The `<match>` text can be an attribute reference such as `&User-Name`,
+or it can be a xref:type/string/index.adoc[string]. If the match
+text is a dynamically expanded string, then the match is performed on
+the output of the string expansion.
+
+If no `<match>` text is given, it means that the `case` statement is
+the "default" and will match all which is not matched by another
+`case` statement inside of the same xref:switch.adoc[switch].
+
+.Example
+[source,unlang]
+----
+switch &User-Name {
+ case "bob" {
+ reject
+ }
+
+ case &Filter-Id {
+ reject
+ }
+
+ case {
+ ok
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/and.adoc b/doc/antora/modules/unlang/pages/condition/and.adoc
new file mode 100644
index 0000000..50b3deb
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/and.adoc
@@ -0,0 +1,21 @@
+= The && Operator
+
+.Syntax
+[source,unlang]
+----
+(condition-1 && condition-2)
+----
+
+The `&&` operator performs a short-circuit "and" evaluation of the
+two conditions. This operator evaluates _condition-1_ and returns
+`false` if _condition-1_ returns `false`. Only if _condition-1_
+returns `true` is _condition-2_ evaluated and its result returned.
+
+.Examples
+[source,unlang]
+----
+if (&User-Name && &EAP-Message) { ...
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/cmp.adoc b/doc/antora/modules/unlang/pages/condition/cmp.adoc
new file mode 100644
index 0000000..4138b86
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/cmp.adoc
@@ -0,0 +1,104 @@
+= Comparisons
+
+.Syntax
+[source,unlang]
+----
+lhs OP rhs
+----
+
+The most common use-case for conditions is to perform comparisons.
+The `lhs` and `rhs` of a conditional comparison can be
+xref:attr.adoc[&Attribute-Name] or xref:type/index.adoc[data]. The
+the `OP` is an operator, commonly `==` or `\<=`. It is used to
+control how the two other portions of the condition are compared.
+
+== The Comparison Operators
+
+The comparison operators are given below.
+
+[options="header"]
+|=====
+| Operator | Description
+| < | less than
+| \<= | less than or equals
+| == | equals
+| != | not equals
+| >= | greater than or equals
+| > | greater than
+| xref:condition/regex.adoc[=~] | regular expression matches
+| xref:condition/regex.adoc[!~] | regular expression does not match
+|=====
+
+The comparison operators perform _type-specific_ comparisons. The
+only exceptions are the xref:condition/regex.adoc[regular expression] operators,
+which interpret the `lhs` as a printable string, and the `rhs` as a
+regular expression.
+
+== IP Address Comparisons
+
+The type-specific comparisons operate as expected for most data types.
+The only exception is data types that are IP addresses or IP address
+prefixes. For those data types, the comparisons are done via the
+following rules:
+
+* Any unqualified IP address is assumed to have a /32 prefix (IPv4)
+ or a /128 prefix (IPv6).
+
+* If the prefixes of the left and right sides are equal, then the comparisons
+ are performed on the IP address portion.
+
+* If the prefixes of the left and right sides are not equal, then the
+ comparisons are performed as _set membership checks_.
+
+The syntax allows conditions such as `192.0.2.1 < 192.0.2/24`. This
+condition will return `true`, as the IP address `192.0.2.1' is within
+the network `192.0.2/24`.
+
+== Casting
+
+In some situations, it is useful to force the left side to be
+interpreted as a particular data type.
+
+[NOTE]
+The data types used by the cast *must* be a type taken from the RADIUS
+dictionaries, e.g., `ipaddr`, `integer`, etc. These types are not the
+same as the xref:type/index.adoc[data types] used in the
+configuration files.
+
+.Syntax
+[source,unlang]
+----
+<cast>lhs OP rhs
+----
+
+The `cast` text can be any one of the standard RADIUS dictionary data
+types, as with the following example:
+
+.Example
+[source,unlang]
+----
+<ipaddr>&Class == 127.0.0.1
+----
+
+In this example, the `Class` attribute is treated as if it was an IPv4
+address and is compared to the address `127.0.0.1`
+
+Casting is most useful when the left side of a comparison is a
+dynamically expanded string. The cast ensures that the comparison is
+done in a type-safe manner, instead of performing a string comparison.
+
+.Example
+[source,unlang]
+----
+<integer>`/bin/echo 00` == 0
+----
+
+In this example, the string output of the `echo` program is interpreted as an
+integer. It is then compared to the right side via integer
+comparisons. Since the integer `00` is equivalent to the integer `0`,
+the comparison will match. If the comparison had been performed via
+string equality checks, then the comparison would fail, because the
+strings `00` and `0` are different.
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/eq.adoc b/doc/antora/modules/unlang/pages/condition/eq.adoc
new file mode 100644
index 0000000..d9e51f3
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/eq.adoc
@@ -0,0 +1,30 @@
+= The == Operator
+
+.Syntax
+`(data-1 == data-2)`
+
+The `==` operator compares the result of evaluating `data-1` and
+`data-2`. As discussed in xref:type/index.adoc[Data Types], the `data-1`
+field may be interpreted as a reference to an attribute.
+
+The `data-2` field is interpreted in a type-specific manner. For
+example, if `data-1` refers to an attribute of type `ipaddr`, then
+`data-2` is evaluated as an IP address. If `data-1` refers to an
+attribute of type `integer`, then `data-2` is evaluated as an integer
+or as a named enumeration defined by a `VALUE` statement in a
+dictionary. Similarly, if `data-1` refers to an attribute of type
+`date`, then `data-2` will be interpreted as a date string.
+
+If the resulting data evaluates to be the same, then the operator
+returns `true`; otherwise, it returns `false`.
+
+.Example
+[source,unlang]
+----
+if (User-Name == "bob") {
+ ...
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/index.adoc b/doc/antora/modules/unlang/pages/condition/index.adoc
new file mode 100644
index 0000000..b9d9d5f
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/index.adoc
@@ -0,0 +1,85 @@
+= Conditional Expressions
+
+Conditions are evaluated when parsing xref:if.adoc[if] and
+xref:elsif.adoc[elsif] statements. These conditions allow the server to
+make complex decisions based on one of a number of possible criteria.
+
+.Syntax
+[source,unlang]
+----
+if ( condition ) { ...
+
+elsif ( condition ) { ...
+----
+
+Conditions are expressed using the following syntax:
+
+[options="header"]
+|=====
+| Syntax | Description
+| xref:attr.adoc[&Attribute-Name] | Check for attribute existence.
+| xref:condition/return_code.adoc[rcode] | Check return code of a previous module.
+| xref:condition/operands.adoc[data] | Check value of data.
+| xref:condition/cmp.adoc[lhs OP rhs] | Compare two kinds of data.
+| xref:condition/para.adoc[( condition )] | Check sub-condition
+| xref:condition/not.adoc[! condition] | Negate a conditional check
+| xref:condition/and.adoc[( condition ) && ...] | Check a condition AND the next one
+| xref:condition/or.adoc[( condition ) \|\| ...] | Check a condition OR the next one
+|=====
+
+
+.Examples
+[source,unlang]
+----
+if ( &User-Name == "bob" ) {
+ ...
+}
+
+if ( &Framed-IP-Address == 127.0.0.1 ) {
+ ...
+}
+
+if ( &Calling-Station-Id == "%{sql:SELECT ...}" ) {
+ ...
+}
+----
+
+== Load-time Syntax Checks
+
+The server performs a number of checks when it loads the configuration
+files. Unlike version 2, all of the conditions are syntax checked
+when the server loads. This checking greatly aids in creating
+configurations that are correct. Where the configuration is
+incorrect, a descriptive error is produced.
+
+This error contains the filename and line number of the syntax error.
+In addition, it will print out a portion of the line that caused the
+error and will point to the exact character where the error was seen.
+These descriptive messages mean that most errors are easy to find and fix.
+
+== Load-time Optimizations
+
+The server performs a number of optimizations when it loads the
+configuration files. Conditions that have static values are
+evaluated and replaced with the result of the conditional comparison.
+
+.Example
+[source,unlang]
+----
+if ( 0 == 1 ) {
+ ...
+}
+----
+
+The condition `0 == 1` is static and will evaluate to `false`. Since
+it evaluates to `false`, the configuration inside of the `if`
+statement is ignored. Any modules referenced inside of the `if`
+statement will not be loaded.
+
+This optimization is most useful for creating configurations that
+selectively load (or not) certain policies. If the condition above
+was used in version 2, then the configuration inside of the `if` statement
+would be loaded, even though it would never be used.
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/not.adoc b/doc/antora/modules/unlang/pages/condition/not.adoc
new file mode 100644
index 0000000..bde038e
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/not.adoc
@@ -0,0 +1,19 @@
+= The ! Operator
+
+.Syntax
+[source,unlang]
+----
+! condition
+----
+
+The `!` operator negates the result of the following condition. It
+returns `true` when _condition_ returns `false`. It returns `false`
+when _condition_ returns `true`.
+
+.Examples
+
+`(! (foo == bar))` +
+`! &User-Name`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/operands.adoc b/doc/antora/modules/unlang/pages/condition/operands.adoc
new file mode 100644
index 0000000..4a2d00b
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/operands.adoc
@@ -0,0 +1,37 @@
+= Operands
+
+.Syntax
+[source,unlang]
+----
+string
+integer
+"double-quoted string"
+'single-quoted string'
+`back-quoted string`
+----
+
+Any text not matching xref:attr.adoc[&Attribute-Name] or
+xref:condition/return_code.adoc[Return Code] is interpreted as a value for a
+particular xref:type/index.adoc[data type].
+
+Double-quoted strings and back-quoted strings are dynamically expanded
+before the condition is evaluated. Single-quoted strings are static
+literals and are not dynamically expanded.
+
+When used as an existence check, the condition evaluates to `true` if
+the data is non-zero. Otherwise, the condition evaluates to `false`.
+
+For integer existence checks, `0` is `false`; all other values are `true`.
+
+For string existence checks, an empty string is `false`. All other
+strings are `true`.
+
+All other data types are disallowed in existence checks.
+
+.Examples
+
+`"hello there"` +
+`5`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/or.adoc b/doc/antora/modules/unlang/pages/condition/or.adoc
new file mode 100644
index 0000000..80c2cb4
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/or.adoc
@@ -0,0 +1,21 @@
+= The || Operator
+
+.Syntax
+[source,unlang]
+----
+(expression-1 || expression-2)
+----
+
+The `||` operator performs a short-circuit "or" evaluation of the two
+expressions. This operator evaluates _condition-1_ and returns `true`
+if _condition-1_ returns true. Only if _condition-1_ returns `false`
+is _condition-2_ evaluated and its result returned.
+
+.Examples
+[source,unlang]
+----
+if (&User-Name || &NAS-IP-Address) { ...
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/para.adoc b/doc/antora/modules/unlang/pages/condition/para.adoc
new file mode 100644
index 0000000..bdb3f01
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/para.adoc
@@ -0,0 +1,19 @@
+= The ( ) Operator
+
+.Syntax
+[source,unlang]
+----
+( condition )
+----
+
+The `( )` operator returns the result of evaluating the given
+`condition`. It is used to clarify policies or to explicitly define
+conditional precedence.
+
+.Examples
+
+`(foo)` +
+`(bar || (baz && dub))`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/regex.adoc b/doc/antora/modules/unlang/pages/condition/regex.adoc
new file mode 100644
index 0000000..038faa6
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/regex.adoc
@@ -0,0 +1,180 @@
+= Regular Expressions
+
+.Syntax
+====
+[source,unlang]
+----
+(<subject> =~ /<pattern>/)
+(<subject> =~ /<pattern>/[imsux])
+
+(<subject> !~ /<pattern>/)
+(<subject> !~ /<pattern>/[imsux])
+----
+====
+
+== Matching
+The regular expression operators perform regular expression matching
+on the data. The `<subject>` field can be an attribute reference or data,
+as with the other xref:condition/cmp.adoc[comparison] operators. The `/<pattern>/`
+field must be a valid regular expression.
+
+The `=~` operator evaluates to `true` when `data` matches the
+`/<pattern>/`. Otherwise, it evaluates to `false`.
+
+The `!~` operator evaluates to `true` when `data` does not match the
+`/<pattern>/`. Otherwise, it evaluates to `true`.
+
+The regular expression comparison is performed on the _string representation_
+of the left side of the comparison. That is, if the left side is an
+xref:type/numb.adoc[integer], the regular expression will behave is if the
+value `0` was the literal string `"0"`. Similarly, if the left side is an
+xref:attr.adoc[&Attribute-Name], then the regular expression will behave is if
+the attribute was printed to a string, and the match was performed on the
+resulting string.
+
+.Checking if the `User-Name` attribute contains a realm of example.com
+====
+[source,unlang]
+----
+if (&User-Name =~ /@example\.com$/) {
+ ...
+}
+----
+====
+
+== Dialects
+
+The syntax of the regular expression is defined by the regular
+expression library available on the local system.
+
+FreeRADIUS currently supports:
+
+* link:https://www.pcre.org/original/doc/html/[libpcre] and
+link:https://www.pcre.org/current/doc/html/[libpcre2] both of which
+provide
+link:https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions[Perl
+Compatible Regular expressions].
+* Regex support provided by the local libc implementation, usually
+link:http://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended[
+Posix regular expressions].
+
+[TIP]
+====
+Use the output of `radiusd -Xxv` to determine which regular expression library is in use.
+
+....
+...
+Debug : regex-pcre : no
+Debug : regex-pcre2 : yes
+Debug : regex-posix : no
+Debug : regex-posix-extended : no
+Debug : regex-binsafe : yes
+...
+Debug : pcre2 : 10.33 (2019-04-16) - retrieved at build time
+....
+====
+
+[WARNING]
+====
+Depending on the regular expression library or libc implementation the server
+was built against, the pattern matching function available may not be binary
+safe (see `regex-binsafe` in the output of `radiusd -Xxv`).
+
+If a binary safe regex match function is not available, and a match is
+attempted against a subject that contains one or more `NUL` ('\0') bytes, the
+match will be aborted, any condition that uses the result will evaluate to false,
+and a warning will be emitted.
+====
+
+== Flags
+
+The regular expression `/<pattern>/` may be followed by one or more flag
+characters. Again, which flags are available depends on the regular expression
+library the server was built with. Multiple flags may be specified per
+`/pattern/`.
+
+.Flags and their uses
+
+[options="header"]
+|=====
+| Flag Character | Available with | Effect
+| `i` | All | Enable case-insensitive matching.
+| `m` | All | '^' and '$' match newlines within the subject.
+| `s` | libpcre[2] | '.' matches anything, including newlines.
+| `u` | libpcre[2] | Treats subjects as UTF8. Invalid UTF8
+ sequences will result in the match failing.
+ |`x` | libpcre[2] | Allows comments in expressions by ignoring
+ whitespace, and text between '#' and the next
+ newline character.
+|=====
+
+== Subcapture groups
+
+When the `=~` or `!~` operators are used, then parentheses in the regular
+expression will sub capture groups, which contain part of the subject string.
+
+The special expansion `%{0}` expands to the portion of the subject that
+matched. The expansions +
+`%{1}`..`%{32}` expand to the contents of any subcapture groups.
+
+When using libpcre[2], named capture groups may also be accessed using the
+built-in expansion +
+`%{regex:<named capture group>}`.
+
+Please see the xref:xlat/builtin.adoc#_0_32[xlat documentation] for
+more information on regular expression matching.
+
+.Extracting the 'user' portion of a realm qualified string
+====
+[source,unlang]
+----
+if (&User-Name =~ /^(.*)@example\.com$/) {
+ update reply {
+ Reply-Message := "Hello %{1}"
+ }
+}
+----
+====
+
+== Pre-Compiled vs Runtime Compiled
+
+When the server starts any regular expressions comparisons it finds will be
+pre-compiled, and if support is available, JIT'd (converted to machine code)
+to ensure fast execution.
+
+If a pattern contains a xref:xlat/index.adoc[string expansion], the pattern
+cannot be compiled on startup, and will be compiled at runtime each time the
+expression is evaluated. The server will also turn off JITing for runtime
+compiled expressions, as the overhead is greater than the time that would be
+saved during evaluation.
+
+.A runtime compiled regular expression
+====
+[source,unlang]
+----
+if (&User-Name =~ /^@%{Tmp-String-0}$/) {
+ ...
+}
+----
+====
+
+To ensure optimal performance you should limit the number of patterns
+containing xref:xlat/index.adoc[string expansions], and if using PCRE, combine
+multiple expressions operating on the same subject into a single expression
+using the PCRE alternation '|' operator.
+
+.Using multiple string expansions and the PCRE alternation operator
+====
+[source,unlang]
+----
+if (&User-Name =~ /^@(%{Tmp-String-0}|%{Tmp-String-1})$/) {
+ ...
+}
+----
+====
+
+
+// Licenced under CC-by-NC 4.0.
+// Copyright (C) 2020 Network RADIUS SAS.
+// Copyright (C) 2019 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/condition/return_codes.adoc b/doc/antora/modules/unlang/pages/condition/return_codes.adoc
new file mode 100644
index 0000000..ebc49ed
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/condition/return_codes.adoc
@@ -0,0 +1,35 @@
+= The return code Operator
+
+.Syntax
+[source,unlang]
+----
+rcode
+----
+
+The Unlang interpreter tracks the return code of any module, string expansion
+or keyword that has been called.
+
+This return code can be checked in any condition. If the saved return code
+matches the `code` given here, then the condition evaluates to `true`.
+Otherwise, it evaluates to `false`.
+
+rcodes cannot be set in a condition. rcodes cannot be compared with anything else.
+
+The list of valid return codes is as follows:
+
+.Return Codes
+
+include::partial$rcode_table.adoc[]
+
+.Examples
+
+[source,unlang]
+----
+sql
+if (notfound) {
+ ...
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/default.adoc b/doc/antora/modules/unlang/pages/default.adoc
new file mode 100644
index 0000000..3b298f6
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/default.adoc
@@ -0,0 +1,47 @@
+= The case Statement
+
+.Syntax
+[source,unlang]
+----
+case [ <match> ] {
+ [ statements ]
+}
+----
+
+The `case` statement is used to match data inside of a
+xref:switch.adoc[switch] statement. The `case` statement cannot be used
+outside of a xref:switch.adoc[switch] statement.
+
+
+The `<match>` text can be an attribute reference such as `&User-Name`,
+or it can be a xref:type/string/index.adoc[string]. If the match
+text is a dynamically expanded string, then the match is performed on
+the output of the string expansion.
+
+The keyword `default` can be used to specify the default action to
+take inside of a xref:switch.adoc[switch] statement.
+
+If no `<match>` text is given, it means that the `case` statement is
+the "default" and will match all which is not matched by another
+`case` statement inside of the same xref:switch.adoc[switch].
+
+.Example
+[source,unlang]
+----
+switch &User-Name {
+ case "bob" {
+ reject
+ }
+
+ case &Filter-Id {
+ reject
+ }
+
+ default {
+ ok
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/else.adoc b/doc/antora/modules/unlang/pages/else.adoc
new file mode 100644
index 0000000..a795d0e
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/else.adoc
@@ -0,0 +1,30 @@
+= The else Statement
+
+.Syntax
+[source,unlang]
+----
+if (condition) {
+ [ statements ]
+}
+else {
+ [ statements ]
+}
+----
+
+An xref:if.adoc[if] statement can have an `else` clause. If _condition_
+evaluates to `false`, the statements in the xref:if.adoc[if] subsection are skipped
+and the statements within the `else` subsection are executed.
+
+.Example
+[source,unlang]
+----
+if (&User-Name == "bob") {
+ reject
+}
+else {
+ ok
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/elsif.adoc b/doc/antora/modules/unlang/pages/elsif.adoc
new file mode 100644
index 0000000..ff5799c
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/elsif.adoc
@@ -0,0 +1,43 @@
+= The elsif Statement
+
+.Syntax
+[source,unlang]
+----
+if (condition-1) {
+ [ statements-1 ]
+}
+elsif (condition-2) {
+ [ statements-2 ]
+}
+else {
+ [ statements-3 ]
+}
+----
+
+An `elsif` statement is used to evaluate a subsequent
+xref:condition/index.adoc[condition] after a preceding xref:if.adoc[if] statement
+evaluates to `false`. In the example above, when _condition-1_
+evaluates to false, then _statements-1_ are skipped and _condition-2_
+is checked. When _condition-2_ evaluates true, then _statements-2_
+are executed. When _condition-2_ evaluates false, then
+_statements-2_ are skipped and _statements-3_ are executed.
+
+As with xref:if.adoc[if], an `elsif` clause does not need to be followed by
+an xref:else.adoc[else] statement. However, any xref:else.adoc[else] statement
+must be the last statement in an `elsif` chain. An arbitrary number of
+`elsif` statements can be chained together to create a series of
+conditional checks and statements.
+
+.Example
+[source,unlang]
+----
+if (&User-Name == "bob") {
+ reject
+}
+elsif (&User-Name == "doug") {
+ ok
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/foreach.adoc b/doc/antora/modules/unlang/pages/foreach.adoc
new file mode 100644
index 0000000..6ed3ddf
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/foreach.adoc
@@ -0,0 +1,40 @@
+= The foreach Statement
+
+.Syntax
+[source,unlang]
+----
+foreach <attribute-reference> {
+ [ statements ]
+}
+----
+
+The `foreach` statement loops over a set of attributes as given by
+`<attribute-reference>`. The loop can be exited early by using the
+xref:break.adoc[break] keyword.
+
+<attribute-reference>::
+
+The xref:attr.adoc[attribute reference] which will will be looped
+over. The reference can be to one attribute, to an array, a child, or
+be a subset.
+
+Inside of the `foreach` block, the attribute that is being looped over
+can be referenced as `Foreach-Variable-0`, through
+`Foreach-Variable-9`. The last digit is the depth of the loop,
+starting at "0". The loops can be nested up to eight (8) deep, though
+this is not recommended.
+
+The attributes being looped over cannot be modified or deleted.
+
+.Example
+[source,unlang]
+----
+foreach &Class {
+ update reply {
+ Reply-Message += "Contains %{Foreach-Variable-0}"
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/group.adoc b/doc/antora/modules/unlang/pages/group.adoc
new file mode 100644
index 0000000..98801fd
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/group.adoc
@@ -0,0 +1,39 @@
+= The group Statement
+
+.Syntax
+[source,unlang]
+----
+group {
+ [ statements ]
+}
+----
+
+The `group` statement collects a series of statements into a list.
+The default processing sections of the server (`authorize`,
+`accounting`, etc.) are also `group` statements. Those sections are
+given different name for management reasons, but they behave
+internally exactly like a `group`.
+
+[ statements ]:: The `unlang` commands which will be executed.
+
+All of the statements inside of the `group` are executed in sequence.
+The `group` statement is not normally used, as the statements within
+it can just be placed inside of the enclosing section. However, the
+`group` statement is included in the `unlang` syntax for completeness.
+
+.Examples
+
+[source,unlang]
+----
+group {
+ sql
+ ldap
+ file
+ if (updated) {
+ ...
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/if.adoc b/doc/antora/modules/unlang/pages/if.adoc
new file mode 100644
index 0000000..ea549ef
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/if.adoc
@@ -0,0 +1,29 @@
+= The if Statement
+
+.Syntax
+[source,unlang]
+----
+if (condition) {
+ [ statements ]
+}
+----
+
+.Description
+The `if` statement evaluates a xref:condition/index.adoc[condition]. When the
+_condition_ evaluates to `true`, the statements within the subsection
+are executed. When the _condition_ evaluates to `false`, those
+statements are skipped.
+
+An `if` statement can optionally be followed by an xref:else.adoc[else] or
+an xref:elsif.adoc[elsif] statement.
+
+.Example
+[source,unlang]
+----
+if (&User-Name == "bob") {
+ reject
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/index.adoc b/doc/antora/modules/unlang/pages/index.adoc
new file mode 100644
index 0000000..fc812f8
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/index.adoc
@@ -0,0 +1,162 @@
+= Unlang Policy Language
+
+The server supports a simple processing language called "Unlang",
+which is short for "un-language". The original intention of using an
+"un-language" was to avoid creating yet another programming language.
+Instead, the `unlang` syntax allows for simple _if / then / else_
+checks, and attribute editing. It does not support recursion,
+subroutines, or more complex flow controls.
+
+Where more complicated functionality is required, we recommend using
+the `lua`, `perl` or `python` modules. Those modules allow the insertion of
+full-featured scripts at any point in the packet processing.
+
+NOTE: The documentation is this directory is _reference_
+documentation. That is, it describes the syntax of `unlang` keywords,
+using minimal examples. The reference documentation does not,
+however, describe _when_ to use the keywords, or _how_ to create
+policies. Please see the xref:howto:index.adoc[howto] directory for
+more in-depth "how to" guides.
+
+The documentation is organized so that each item is on its own page.
+The page beings with a description of the item, followed by some text
+explaining what the item does. The page concludes with one or more
+examples of using the item in `unlang` policies.
+
+The `unlang` processing can be split into some high-level concepts.
+
+== Keywords
+
+xref:keywords.adoc[Keywords], which are the basic statements of the
+language, e.g., xref:load-balance.adoc[load-balance],
+xref:if.adoc[if], xref:else.adoc[else], etc.
+
+.Example
+[source,unlang]
+----
+load-balance {
+ sql1
+ sql2
+ sql3
+}
+----
+
+== Conditional Expressions
+
+xref:condition/index.adoc[Conditional expressions], which are used to check
+if conditions evaluate to _true_ or _false_. Conditional expressions
+can be used to control the flow of processing.
+
+.Example
+[source,unlang]
+----
+if ((&User-Name == "bob") && (&Calling-Station-Id == "00:01:03:04:05")) {
+ ...
+}
+----
+
+== Update Statements
+
+xref:update.adoc[update] statements are used to edit attributes and
+lists of attributes.
+
+Most request packets will result in reply packets that contain
+additional information for the requestor. The `update` section allows
+policies to add attributes to requests, replies, or to other places.
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Session-Timeout := 3600
+ &Framed-IP-Address := 192.0.2.4
+}
+----
+
+== String Expansions
+
+xref:xlat/index.adoc[String expansion] Using `%{...}` to perform dynamic
+string expansions. (also known as xref:xlat/index.adoc[xlat])
+
+String expansions are usually performed in order to get additional
+information which is not immediately available to the policy. This
+information can be taken from almost any source, including other
+attributes, databases, and scripts.
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Framed-IP-Address := "%{sql:SELECT static_ip from table WHERE user = '%{User-Name}'}"
+}
+----
+
+== Data Types
+
+Each attribute used by the server has an associated
+xref:type/index.adoc[data type]. The `unlang` interpreter enforces
+restrictions on assignments, so that only valid data types can be
+assigned to an attribute. Invalid assignments result in a run-time
+error.
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Framed-IP-Address := 192.0.2.4
+ &Session-Timeout := 5
+ &Reply-Message := "hello"
+}
+----
+
+== Design Goals of Unlang
+
+The goal of `unlang` is to allow simple policies to be written with
+minimal effort. Conditional checks can be performed by the policies,
+which can then update the request or response attributes based on the
+results of those checks. `unlang` can only be used in a processing
+section, it cannot be used anywhere else, including in configuration
+sections for a client or a module. The reason for this limitation is
+that the language is intended to perform specific actions on requests
+and responses. The client and module sections contain definitions for
+a client or module; they do not define how a request is processed.
+
+`unlang` uses the same the basic syntax as the configuration files.
+The syntax of the configuration file for lines, comments, sections,
+sub-section, etc., all apply to `unlang`.
+
+Where `unlang` differs from the basic configuration file format is in
+complexity and operation. The normal configuration files are
+_declarative_ and they are _static_. That is, they declare variables
+and values for those variables. Those values do not change when the
+server is running.
+
+In contrast, `unlang` performs run-time processing of requests.
+Conditional statements such as xref:if.adoc[if] are evaluated for every
+packet that is received. Attribute editing statements such as
+xref:update.adoc[update] can be used to edit attribute contents or lists
+of attributes.
+
+.Example
+[source,unlang]
+----
+# First, the keyword 'if'
+
+# followed by condition which checks that the User-Name
+# attribute has value "bob"
+
+if (&User-Name == "bob") {
+ # keyword "update"
+
+ # followed by instructions to add the Reply-Message
+ # attribute to the "reply" list, with contents
+ # "Hello, bob"
+
+ update reply {
+ Reply-Message := "Hello, bob"
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/keywords.adoc b/doc/antora/modules/unlang/pages/keywords.adoc
new file mode 100644
index 0000000..e6411ea
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/keywords.adoc
@@ -0,0 +1,78 @@
+= Keywords
+
+The following tables list the keywords used in `Unlang`. These keywords
+implement the "flow control" of the policies.
+
+== Flow Control Keywords
+
+The _flow control_ keywords allow _if / then / else_ checks, simple
+looping, etc.
+
+.Flow Control
+[options="header"]
+[cols="30%,70%"]
+|=====
+| Keyword | Description
+| xref:break.adoc[break] | Exit early from a `foreach` loop.
+| xref:case.adoc[case] | Match inside of a `switch`.
+| xref:else.adoc[else] | Do something when an `if` does not match.
+| xref:elsif.adoc[elsif] | Check for condition when a previous `if` does not match.
+| xref:foreach.adoc[foreach] | Loop over a list of attributes.
+| xref:if.adoc[if] | Check for a condition, and execute a sub-policy if it matches.
+| xref:return.adoc[return] | Immediately stop processing a section.
+| xref:switch.adoc[switch] | Check for multiple values.
+|=====
+
+== Attribute Editing Keywords
+
+The _attribute editing_ keywords allow policies to add, delete, and
+modify attributes in any list or packet.
+
+.Attribute Editing
+[options="header"]
+[cols="30%,70%"]
+|=====
+| Keyword | Description
+| xref:update.adoc[update] | Add or filter attributes to a list
+|=====
+
+== Grouping Keywords
+
+The _grouping_ keywords allow policies to be organized into groups,
+including load-balancing.
+
+.Grouping
+[options="header"]
+[cols="30%,70%"]
+|=====
+| Keyword | Description
+| xref:group.adoc[group] | Group a series of statements.
+| xref:load-balance.adoc[load-balance] | Define a load balancing group without fail-over.
+| xref:redundant.adoc[redundant] | Define a redundant group with fail-over.
+| xref:redundant-load-balance.adoc[redundant-load-balance] | Define a redundant group with fail-over and load balancing.
+|=====
+
+== Module Keywords
+
+The _module_ keywords refer pre-packaged libraries that implement
+specific functionality, such as connecting to SQL, LDAP, etc. The
+name used here is not the literal string `module`. Instead, it is the
+name of an instance of a pre-packaged module such as `sql`, or `ldap`, or
+`eap`.
+
+The documentation below describes how to reference modules. That is,
+how to use `sql`, etc. in the policies. Please see
+`raddb/mods-available/` for configuration examples for each module.
+
+.Modules
+[options="header"]
+[cols="30%,70%"]
+|=====
+| Keyword | Description
+| xref:module.adoc[<module>] | Execute a named module, e.g., `sql`.
+| xref:module_method.adoc[<module>.<method>] | Execute a particular method of a named module, e.g., `pap.authorize`
+| xref:module_builtin.adoc[reject] | Built-in modules, e.g., `reject`, or `ok`, etc.
+|=====
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/list.adoc b/doc/antora/modules/unlang/pages/list.adoc
new file mode 100644
index 0000000..a55a54f
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/list.adoc
@@ -0,0 +1,72 @@
+= Attribute Lists
+
+An attribute list contains a set of attributes. The allowed lists
+are:
+
+`request`:: Attributes in the incoming request packet.
+
+`reply`:: Attributes in the outgoing reply packet.
+
+`control`:: Attributes in the internal "control" list that is
+associated with the request.
++
+The `control` attributes are used to manage how the request is
+processed. These attributes are never sent in any packet.
+
+`session-state`:: Attributes which are maintained across multi-packet
+exchanges.
+
+`proxy-request`:: Attributes in the proxied request packet to a home server.
+
+`proxy-reply`:: Attributes in the reply packet from the home server.
+
+`coa`:: Attributes in a CoA-Request packet which is sent to a home server.
+
+`disconnect`:: Attributes in a Disconnect-Request packet which is sent to a home server.
+
+There must be a colon `:` after the list name and before the attribute name.
+This syntax helps the server to distinguish between list names and attribute
+names.
+
+With the exception of `session-state`, all of the above lists are
+ephemeral. That is, they exist for one packet exchange, and only one
+packet exchange. When a reply is sent for a request, the above lists
+and all attributes are deleted. There is no way to reference an
+attribute from a previous packet. We recommend using a database to
+track complex state.
+
+The `coa` and `disconnect` lists can only be used when the server
+receives an Access-Request or Accounting-Request. Use `request` and
+`reply` instead of `coa` when the server receives a CoA-Request or
+Disconnect-Request packet.
+
+Adding one or more attributes to either of the `coa` or `disconnect`
+list causes server to originate a CoA-Request or Disconnect-Request
+packet. That packet is sent when the current Access-Request or
+Accounting-Request has been finished, and a reply sent to the NAS.
+See `raddb/sites-available/originate-coa` for additional information.
+
+In some cases, requests are associated a multi-packet exchange. For
+those situations, the `session-state` list is automatically saved when
+a reply is sent, and it is automatically restored when the next packet
+in sequence comes in. Once the packet exchange has been finished, the
+`session-state` list is deleted.
+
+In some cases, there is a parent-child relationship between requests.
+In those situations, it is possible for the policy rules in the child
+to refer to attributes in the parent. This reference can be made by
+prefixing the _<list>_ name with the `parent` qualifier. The key word
+`outer` is also a synonym for `parent`. If there are multiple
+parent-child relationships, the `parent` qualifier can be repeated.
+
+There is, however, no way for the parent to refer to the child. When
+the child is running, the parent is suspended. Once the child
+finishes, it is deleted, and is no longer accessible to the parent.
+
+.Examples
+`&parent.request.User-Name` +
+`&parent.reply.Reply-Message` +
+`&parent.parent.session-state.Filter-Id`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/load-balance.adoc b/doc/antora/modules/unlang/pages/load-balance.adoc
new file mode 100644
index 0000000..d64b161
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/load-balance.adoc
@@ -0,0 +1,32 @@
+= The load-balance Statement
+
+.Syntax
+[source,unlang]
+----
+load-balance {
+ [ statements ]
+}
+----
+
+The `load-balance` section is similar to the `redundant` section
+except that only one module in the subsection is ever called.
+
+In general, the
+xref:redundant-load-balance.adoc[redundant-load-balance] statement is
+more useful than this one.
+
+[ statements ]:: One or more `unlang` commands. Only one of the
+statements is executed.
+
+.Examples
+
+[source,unlang]
+----
+load-balance &User-Name {
+ sql1
+ sql2
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/module.adoc b/doc/antora/modules/unlang/pages/module.adoc
new file mode 100644
index 0000000..fd18f2f
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/module.adoc
@@ -0,0 +1,86 @@
+= Modules
+
+.Syntax
+[source,unlang]
+----
+<module>
+----
+
+The `<module>` statement is a reference to the named module. Common
+module names include `pap`, `chap`, `files`, `eap`, and `sql`. The
+`modules { ... }` subsection of `radiusd.conf` contains all of the
+valid modules.
+
+When processing reaches a named module, the pre-compiled module is
+called. The module may succeed or fail and will return a status code
+to the `unlang` interpreter detailing success or failure.
+
+.Example
+[source,unlang]
+----
+chap
+sql
+----
+
+== Module Return Codes
+
+When a module is called, it returns one of the following codes to
+the interpreter; the meaning of each code is detailed to the right of
+the source, below:
+
+.Module Return Codes
+
+The below table describes the purpose of the rcodes that may be returned
+by a module call. In this case the 'operation' referenced in the table is
+the current module call.
+
+include::partial$rcode_table.adoc[]
+
+These return codes can be used in a subsequent
+xref:condition/index.adoc[conditional expression] thus allowing policies to
+perform different actions based on the behaviour of the modules.
+
+.Example
+[source,unlang]
+----
+sql
+if (notfound) {
+ update reply {
+ Reply-Message = "We don't know who you are"
+ }
+ reject
+}
+----
+
+== Module Return Code priority overrides
+
+In the case of modules, rcodes can be modified on a per-call basis.
+
+Module priority overrides are specified in a block inline with the module call.
+The format of an override is `<rcode> = (0+|<rcode>|return)` - That is,
+a number greater than or equal to 0, the priority of another rcode, or the special
+priority `return` which causes the current section to immediately exit.
+
+[source, unlang]
+----
+ldap { <1>
+ fail = 1 <2>
+ ok = handled <3>
+ reject = return <4>
+}
+----
+
+<1> Call to the `ldap` module.
+<2> Sets the priority of the `fail` rcode to be `1`. If the priority
+ of the rcode for the request is `0`, then the request request rcode
+ will be set to `fail` if the module returns `fail`.
+<3> Sets the priority of the `ok` rcode to be whatever the default is for
+ `handled` in this section. As the default for `handled` is usually
+ `return`. If `ok` is returned, the current section will immediately
+ exit.
+<4> Sets the priority of `reject` to be `return`. If the module returns
+ `reject`, the current section will immediately exit.
+
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/module_builtin.adoc b/doc/antora/modules/unlang/pages/module_builtin.adoc
new file mode 100644
index 0000000..f21a128
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/module_builtin.adoc
@@ -0,0 +1,42 @@
+= Built-in Modules
+
+In some cases, it is useful to reject a request immediately or perform another
+action on it. The built-in modules can be used to perform these actions. These
+modules are named for the return codes given in the xref:module.adoc[module]
+section.
+
+In practice, these modules are implemented by the `always` module and
+exist so that a success or failure can be forced during the processing
+of a policy.
+
+The names and behaviours of these modules are given below:
+
+`fail`::
+Causes the request to be treated as if a database failure had
+occurred.
+
+`noop`::
+Do nothing. This also serves as an instruction to the
+configurable failover tracking that nothing was done in the current
+section.
+
+`ok`::
+Instructs the server that the request was processed properly. This keyword can be used to over-ride earlier failures if the local
+administrator determines that the failures are not catastrophic.
+
+`reject`::
+Causes the request to be immediately rejected.
+
+.Example
+[source,unlang]
+----
+if (!&User-Name) {
+ update reply {
+ Reply-Message := "We don't know who you are"
+ }
+ reject
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/module_method.adoc b/doc/antora/modules/unlang/pages/module_method.adoc
new file mode 100644
index 0000000..98cd375
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/module_method.adoc
@@ -0,0 +1,27 @@
+= Module methods
+
+.Syntax
+[source,unlang]
+----
+<module>.<method>
+----
+
+This variant of xref:module.adoc[<module>] is used in one processing
+section. It calls a module using the method of another processing
+section. For example, it can be used to call a module's `authorize`
+method while processing the `post-auth` section.
+
+The `<module>` portion must refer to an existing module; the
+`<method>` portion must refer to processing method supported by that
+module. Typically, the names of the processing method will be the
+same as the processing sections.
+
+.Example
+[source,unlang]
+----
+sql.recv.Accounting-Request
+files.recv.Access-Request
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/redundant-load-balance.adoc b/doc/antora/modules/unlang/pages/redundant-load-balance.adoc
new file mode 100644
index 0000000..2322f72
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/redundant-load-balance.adoc
@@ -0,0 +1,39 @@
+= The redundant-load-balance Statement
+
+.Syntax
+[source,unlang]
+----
+redundant-load-balance {
+ [ statements ]
+}
+----
+
+The `redundant-load-balance` section operates as a combination of the
+xref:redundant.adoc[redundant] and xref:load-balance.adoc[load-balance]
+sections.
+
+[ statements ]:: One or more `unlang` commands.
++
+If the selected statement succeeds, then the server stops processing
+the `redundant-load-balance` section. If, however, that statement fails,
+then the next statement in the list is chosen (wrapping around to the
+top). This process continues until either one statement succeeds or all
+of the statements have failed.
++
+All of the statements in the list should be modules, and of the same
+type (e.g., `ldap` or `sql`). All of the statements in the list should
+behave identically, otherwise different requests will be processed
+through different modules and will give different results.
+
+.Example
+[source,unlang]
+----
+redundant-load-balance &User-Name {
+ sql1
+ sql2
+ sql3
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/redundant.adoc b/doc/antora/modules/unlang/pages/redundant.adoc
new file mode 100644
index 0000000..e837d1f
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/redundant.adoc
@@ -0,0 +1,42 @@
+= The redundant Statement
+
+.Syntax
+[source,unlang]
+----
+redundant {
+ [ statements ]
+}
+----
+
+The `redundant` section executes a series of statements in sequence.
+As soon as one statement succeeds, the rest of the section is skipped.
+
+[ statements ]:: One or more `unlang` commands. Processing starts
+from the first statement in the list.
++
+If the selected statement succeeds, then the server stops processing
+the `redundant` section. If, however, that statement fails, then the
+next statement in the list is chosen. This process continues until
+either one statement succeeds or all of the statements have failed.
++
+All of the statements in the list should be modules, and of the same
+type (e.g., `ldap` or `sql`). All of the statements in the list should
+behave identically, otherwise different requests will be processed
+through different modules and will give different results.
+
+In general, we recommend using the
+xref:redundant-load-balance.adoc[redundant-load-balance] statement
+instead of `redundant`.
+
+.Example
+[source,unlang]
+----
+redundant {
+ sql1
+ sql2
+ sql3
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/return.adoc b/doc/antora/modules/unlang/pages/return.adoc
new file mode 100644
index 0000000..aea1bc2
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/return.adoc
@@ -0,0 +1,36 @@
+= The return Statement
+
+.Syntax
+[source,unlang]
+----
+return
+----
+
+The `return` statement is used to exit a processing section such as
+`authorize`. It behaves similarly to the
+xref:break.adoc[break] statement, except that it is not limited to
+being used inside of a xref:foreach.adoc[foreach] loop.
+
+The `return` statement is not strictly necessary. It is mainly used
+to simplify the layout of `unlang` policies. If the `return`
+statement did not exist, then every xref:if.adoc[if] statement might need
+to have a matching xref:else.adoc[else] statement.
+
+The `return` statement will also exit a policy which is defined in the
+`policy { ... } ` subsection. This behavior allows policies to be
+treated as a function call. Any `return` inside of the policy section
+will only return from that policy. The `return` will _not_ return
+from the enclosing processing section which called the policy.
+
+.Example
+[source,unlang]
+----
+sql
+if (&reply.Filter-Id == "hello") {
+ return
+}
+...
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/return_codes.adoc b/doc/antora/modules/unlang/pages/return_codes.adoc
new file mode 100644
index 0000000..3b09c2d
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/return_codes.adoc
@@ -0,0 +1,17 @@
+= Return codes
+
+Many operations in the server produce a return code (rcode).
+The different rcodes give a course indication of whether a particular operation
+(a module call, string expansion, or keyword) was successful.
+
+Unlike return values in other languages, FreeRADIUS' rcodes are are always taken
+from a fixed compiled-in set.
+
+include::partial$rcode_table.adoc[]
+
+Return codes propagate through nested unlang sections based on their priority.
+If a rcode returned by an operation has a higher priority than the current rcode
+associated with the request, then the request rcode is overwritten.
+
+Return code priorities are assigned by the section the module call, expansion or
+keyword was used in.
diff --git a/doc/antora/modules/unlang/pages/switch.adoc b/doc/antora/modules/unlang/pages/switch.adoc
new file mode 100644
index 0000000..deff703
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/switch.adoc
@@ -0,0 +1,83 @@
+= The switch Statement
+
+.Syntax
+[source,unlang]
+----
+switch <expansion> {
+ case <match-1> {
+ [ statements-1 ]
+ }
+ case <match-2> {
+ [ statements-2 ]
+ }
+ case {
+ [ statements-3 ]
+ }
+}
+----
+
+A `switch` statement causes the server to evaluate _expansion_, which
+can be an xref:attr.adoc[&Attribute-Name] or
+xref:condition/operands.adoc[data]. The result is compared against _match-1_
+and _match-2_ to find a match. If no string matches, then the server
+looks for the default xref:case.adoc[case] statement, which has no
+associated match.
+
+The matching is done via equality. The `switch` statement is mostly
+syntactic sugar and is used to simplify the visual form of the
+configuration. It is mostly equivalent to the following use of
+xref:if.adoc[if] statements:
+
+.Nearly equivalent syntax
+[source,unlang]
+----
+if (<expansion> == <match-1>) {
+ [ statements-1 ]
+}
+elsif (<expansion> == <match-2>) {
+ [ statements-2 ]
+}
+else {
+ [ statements-3 ]
+}
+----
+
+The only difference between the two forms is that for a `switch`
+statement, the _expansion_ is evaluated only once. For the equivalent
+xref:if.adoc[if] statement, the _expansion_ is evaluated again for every
+xref:if.adoc[if].
+
+If a matching xref:case.adoc[case] is found, the statements within
+that xref:case.adoc[case] are evaluated. If no matching
+xref:case.adoc[case] is found, the `case` section with no "match" is
+evaluated. If there is no such `case { ...}` subsection, then the
+`switch` statement behaves as if the `case {...}` section was empty.
+
+////
+For compatibility with version 3, and empty `case` statement can also
+be used instead of `default`.
+////
+
+The _match_ text for the xref:case.adoc[case] statement can be an
+xref:attr.adoc[&Attribute-Name] or xref:type/index.adoc[data].
+
+No statement other than xref:case.adoc[case] can appear in a `switch`
+statement, and the xref:case.adoc[case] statement cannot appear outside of a
+`switch` statement.
+
+.Example
+[source,unlang]
+----
+switch &User-Name {
+ case "bob" {
+ reject
+ }
+
+ case {
+ ok
+ }
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/all_types.adoc b/doc/antora/modules/unlang/pages/type/all_types.adoc
new file mode 100644
index 0000000..0bace01
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/all_types.adoc
@@ -0,0 +1,80 @@
+= List of Data Types
+
+The server support a wide range of data types, both in `unlang` and in
+the dictionaries. This page outlines the names and functionality of
+those data types.
+
+== Basic Data Types
+
+There are a number of "basic" data types. These data types are
+fixed-size, and encapsulate simple concepts such as "integer" or "IP
+address".
+
+Basic data types can be used in `unlang`, as they contain simple
+values which can be compared, or assigned to one attribute. In most
+cases, it is not necessary to know the name of the data type. It is
+possible to write values in the format you expect, The server will do
+"the right thing" when interpreting the values.
+
+.Basic Data Types
+[options="header"]
+[cols="15%,85%"]
+|=====
+| Data Type | Description
+| bool | boolean
+| date | calendar date
+| ethernet | Ethernet address
+| float32 | 32-bit floating point number
+| float64 | 64-bit floating point number
+| ifid | interface ID
+| int8 | 8-bit signed integer
+| int16 | 16-bit signed integer
+| int32 | 32-bit signed integer
+| int64 | 64-bit signed integer
+| ipaddr | IPv4 address
+| ipv6addr | IPv6 address
+| ipv4prefix | IPv4 network with address and prefix length
+| ipv6prefix | IPv6 network with address and prefix length
+| octets | raw binary, printed as hex strings
+| string | printable strings
+| time_delta | difference between two calendar dates
+| uint8 | 8-bit unsigned integer
+| uint16 | 16-bit unsigned integer
+| uint32 | 32-bit unsigned integer
+| uint64 | 64-bit unsigned integer
+|=====
+
+=== Structural Data Types
+
+The following data types are "structural", in that they form
+parent-child relationships between attributes. These data types can
+only be used in the dictionaries. They cannot be used in `unlang`
+statements.
+
+.Structural Data Types
+[options="header"]
+[cols="15%,85%"]
+|=====
+| Data Type | Description
+| struct | structure which contains fixed-width fields
+| tlv | type-length-value which contains other attributes
+| vsa | Encapsulation of vendor-specific attributes
+|=====
+
+=== Protocol-Specific Data Types
+
+The following data types are used only in certain protocols. These
+data types can be used only in the dictionaries. They cannot be used
+in `unlang` statements.
+
+.Protocol Specific Data Types
+[options="header"]
+[cols="15%,15%,70%"]
+|=====
+| Data Type | Protocol | Description
+| abinary | RADIUS | Ascend binary filters
+| extended | RADIUS | attributes which "extend" the number space
+|=====
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS
diff --git a/doc/antora/modules/unlang/pages/type/double.adoc b/doc/antora/modules/unlang/pages/type/double.adoc
new file mode 100644
index 0000000..6c3e708
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/double.adoc
@@ -0,0 +1,39 @@
+= Double-Quoted Strings
+
+.Syntax
+`"string"`
+
+A double-quoted string is interpreted via the usual rules in
+programming languages for double quoted strings. The double-quote
+character can be placed in a string by escaping it with a backslash.
+Carriage returns and line-feeds can also be used via the usual `\r` and
+`\n` syntax.
+
+The main difference between the single and double quoted strings is
+that the double quoted strings can be dynamically expanded. The syntax
+`${...}` is used for parse-time expansion and `%{...}` is used for
+run-time expansion. The difference between the two methods is that the
+`${...}` form is expanded when the server loads the configuration
+files and is valid anywhere in the configuration files. The `%{...}`
+link:xlat.adoc[string expansion] form is valid only in conditional
+expressions and attribute assignments.
+
+The output of the dynamic expansion can be interpreted as a string,
+a number, or an IP address, depending on its context.
+
+Note that the interpretation of text _strongly_ depends on the
+context. The text `"0000"` can be interpreted as a data type
+"integer", having value zero, or a data type "string", having value
+`"0000"`. In general when a particular piece of text is used, it is
+used with the context of a known attribute. That attribute has a
+link:data.adoc[data type], and the text will be interpreted as that
+data type.
+
+.Examples
+
+`"word"` +
+`"a string"` +
+`"this has embedded\ncharacters"`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/index.adoc b/doc/antora/modules/unlang/pages/type/index.adoc
new file mode 100644
index 0000000..7d0d70f
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/index.adoc
@@ -0,0 +1,117 @@
+= Data Types
+
+Unlang supports a number of data types. These data types are used in
+conditional expressions or when assigning a value to an attribute.
+
+== Using Data Types
+
+The server support a wide range of data types, as given in the
+xref:unlang/type/all_types.adoc[list of data types] page. The choice
+of which data type applies is determined by the context in which that
+data type is used. This context is usually taken from an attribute
+which is being assigned a value.
+
+The `unlang` interpreter uses pre-defined attributes which are defined
+in dictionaries. The dictionaries define both a name, and a data type
+for the attributes. In the interpreter, then, attributes can be
+assigned a value or compared to a value, without specifying the data
+type. The interpreter knows how to parse the value by using the data
+type assigned to the attribute.
+
+The result is that in most cases, it is not necessary to know the name
+of the data types. It is possible to write values in the format you
+expect, and he server will do "the right thing" when interpreting the
+values.
+
+.Attributes with Different Data Types
+[source,unlang]
+----
+Framed-IP-Address = 192.0.2.1
+Framed-IPv6-Address = 2001:db8::
+Reply-Message = "This is a reply"
+Port-Limit = 5
+Boolean = true
+Octets-Thing = 0xabcdef0102030405
+MAC-Address = 00:01:02:03:04:05
+----
+
+== Parsing Data Types
+
+The interpreter is flexible when parsing data types. So long as the
+value can be parsed as the given data type without error, the value
+will be accepted.
+
+For example, a particular attribute may be of data type `ipaddr` in
+order to store IPv4 addresses. The interpreter will then accept the
+following strings as valid IPv4 addresses:
+
+`192.168.0.2`:: xref:type/string/unquoted.adoc[Unquoted text], interpreted as the data type
+
+`'192.168.0.2'`:: xref:type/string/single.adoc[Single-quoted string], the contents of the string are parsed as the data type.
++
+The single-quoted string form is most useful when the data type
+contains special characters that may otherwise confuse the parser.
+
+`"192.168.0.2"`:: xref:type/string/double.adoc[Double-quoted string].
++
+The contents of the string are dynamically expanded as described in
+the xref:unlang/xlat/index.adoc[dynamic expansion] page. The
+resulting output is then interpreted as the given data type.
+
+`{backtick}/bin/echo 192.168.0.2{backtick}`:: xref:type/string/backticks.adoc[backtick-quoted string].
+Run a script, and parse the resulting string as the data type.
+
+Similar processing rules are applied when parsing assignments and
+comparisons, for all attributes and data types.
+
+=== Casting Data Types
+
+In some cases, it is necessary to parse values which do not refer to
+attributes. This situation usually occurs when two values need to be
+compared, as in the following example:
+
+[source,unlang]
+----
+if ("%{sql:SELECT ipaddress FROM table WHERE user=%{User-Name}}" == 192.0.2.1) }
+ ....
+}
+----
+
+Since there is no attribute on either side of the `==` operator, the
+interpreter has no way of knowing that the string `192.0.2.1` is an IP
+address. There is unfortunately no way of automatically parsing
+strings in order to determine the data type to use. Any such
+automatic parsing would work most of the time, but it would have
+error cases where the parsing was incorrect.
+
+The solution is to resolve these ambiguities by allowing the values to
+be cast to a particular type. Casting a value to a type tells the
+interpreter how that value should be parsed. Casting is done by
+prefixing a value with the type name, surrounded by angle brackets;
+`<...>`.
+
+.Syntax
+----
+<...>value
+----
+
+We can add a cast to the above example, as follows:
+
+[source,unlang]
+----
+if ("%{sql:SELECT ipaddress FROM table WHERE user=%{User-Name}}" == <ipaddr>192.0.2.1) }
+ ....
+}
+----
+
+In this example, we prefix the IP address with the string `<ipaddr>`.
+The interpreter then knows that the value `192.0.2.` should be
+interpreted as the data type `ipaddr`, and not as the literal string
+`"192.0.2."`.
+
+For a full list of data types which can be used in a cast, please see
+the xref:unlang/type/all_types.adoc[list of data types] page, and the
+"Basic Type Types" section.
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/ip.adoc b/doc/antora/modules/unlang/pages/type/ip.adoc
new file mode 100644
index 0000000..fc25ae8
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/ip.adoc
@@ -0,0 +1,15 @@
+= IP Addresses
+
+.Examples
+
+`192.0.2.16` +
+`::1` +
+`example.com`
+
+Depending on the context, a "simple word", as above, may be
+interpreted as an IPv4 or an IPv6 address. This interpretation is
+usually done when the string is used in the context of an attribute,
+or to compare two addresses or assign an address to an attribute.
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/numb.adoc b/doc/antora/modules/unlang/pages/type/numb.adoc
new file mode 100644
index 0000000..284cf81
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/numb.adoc
@@ -0,0 +1,11 @@
+= Numbers
+
+.Examples
+
+`0` +
+`563`
+
+Numbers are unsigned integers that are composed of decimal digits.
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/string/backticks.adoc b/doc/antora/modules/unlang/pages/type/string/backticks.adoc
new file mode 100644
index 0000000..9372b4c
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/string/backticks.adoc
@@ -0,0 +1,38 @@
+= Backtick-quoted string
+
+.Syntax
+`{backtick}string{backtick}`
+
+The backtick operator is used to perform a run-time expansion
+similar to what is done with the Unix shell. The contents of the string
+are split into one or more sub-strings, based on intermediate
+whitespace. Each substring is then expanded as described above for
+double quoted strings. The resulting set of strings is used to execute a
+program with the associated arguments.
+
+The output of the program is recorded, and the resulting data is
+used in place of the input string value. Where the output is composed of
+multiple lines, any carriage returns and line feeds are replaced by
+spaces.
+
+For safety reasons, the full path to the executed program should be
+given. In addition, the string is split into arguments _before_ the
+substrings are dynamically expanded. This step is done both to allow
+the substrings to contain spaces, and to prevent spaces in the
+expanded substrings from affecting the number of command-line
+arguments.
+
+For performance reasons, we recommend that the use of back-quoted
+strings be kept to a minimum. Executing external programs is
+relatively expensive, and executing a large number of programs for
+every request can quickly use all of the CPU time in a server. If many
+programs need to be executed, it is suggested that alternative ways to
+achieve the same result be found. In some cases, using a real
+programming language such as `lua`, `perl` or `python` may be better.
+
+.Examples
+
+`{backtick}/bin/echo hello{backtick}`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/string/double.adoc b/doc/antora/modules/unlang/pages/type/string/double.adoc
new file mode 100644
index 0000000..ea87bc5
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/string/double.adoc
@@ -0,0 +1,68 @@
+= Double Quoted Strings
+
+.Syntax
+`"string"`
+
+A double-quoted string allows escape sequences and xref:xlat/index.adoc[dynamic
+expansions]. As with xref:type/string/single.adoc[single-quoted strings], text
+within double quotes can include spaces.
+
+The main difference between the single and double quoted strings is
+that the double quoted strings can be dynamically expanded. The syntax
+`${...}` is used for parse-time expansion and `%{...}` is used for
+run-time expansion. The difference between the two methods is that the
+`${...}` form is expanded when the server loads the configuration
+files and is valid anywhere in the configuration files. The `%{...}`
+xref:xlat/index.adoc[string expansion] form is valid only in conditional
+expressions and attribute assignments.
+
+The output of the dynamic expansion can be interpreted as a string,
+a number, or an IP address, depending on its context.
+
+Note that the interpretation of text _strongly_ depends on the
+context. The text `"0000"` can be interpreted as a data type
+"integer", having value zero, or a data type "string", having value
+`"0000"`. In general when a particular piece of text is used, it is
+used with the context of a known attribute. That attribute has a
+xref:type/index.adoc[data type], and the text will be interpreted as that
+data type.
+
+NOTE: Most values retrieved from external datastores will be treated implicitly
+as double-quoted strings.
+
+== Escape sequences
+
+Escape sequences allow the inclusion of characters that may be difficult to
+represent in datastores, or the FreeRADIUS configuration files.
+
+.Escape sequences and their descriptions
+[options="header", cols="15%,85%"]
+|=====
+| Escape sequence | Character represented
+| `\\` | Literal backslash (0x5c)
+| `\r` | Carriage return (0x0d)
+| `\n` | Line feed (0x0a)
+| `\t` | Horizontal tab (0x09)
+| `\"` | Double quote (0x22)
+| `\x<hex><hex>` | A byte whose numerical value is given by `<hex><hex>` interpreted as a hexadecimal number.
+| `\x<oct><oct><oct>` | A byte whose numerical value is given by `<oct><oct><oct>` interpreted as an octal number.
+|=====
+
+.Examples
+
+`"word"` +
+`"a string"' +
+`"foo\"bar\""` +
+`"this is a long string"` +
+`"this has embedded\ncharacters"` +
+`"attribute\tvalue\nusername\t%{User-Name}\nreply-message\t%{reply.Reply-Message}"`
+`"The result of 'SELECT * FROM foo WHERE 1' is: %{sql:SELECT * FROM foo WHERE 1}"`
+
+// Licenced under CC-by-NC 4.0.
+// Copyright (C) 2019 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+// Copyright (C) 2019 The FreeRADIUS project.
+// Copyright (C) 2020 Network RADIUS SAS.
+
+
+
+
diff --git a/doc/antora/modules/unlang/pages/type/string/escaping.adoc b/doc/antora/modules/unlang/pages/type/string/escaping.adoc
new file mode 100644
index 0000000..e63a498
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/string/escaping.adoc
@@ -0,0 +1,14 @@
+= Character Escaping
+
+The quotation characters in the above string data types can be
+escaped by using the backslash, or `\,` character. The backslash
+character itself can be created by using `\\`. Carriage returns and
+line feeds can be created by using `\n` and `\r`.
+
+.Examples
+
+`"I say \"hello\" to you"` +
+`"This is split\nacross two lines"`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/string/single.adoc b/doc/antora/modules/unlang/pages/type/string/single.adoc
new file mode 100644
index 0000000..fa2ac05
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/string/single.adoc
@@ -0,0 +1,19 @@
+= Single Quoted Strings
+
+.Syntax
+`'string'`
+
+A single-quoted string is interpreted without any dynamic string
+expansion. The quotes allow the string to contain spaces, among other
+special characters. The single quote character can be placed in such a
+string by escaping it with a backslash.
+
+.Examples
+
+`'hello'` +
+`'foo bar`' +
+`'foo\\'bar'` +
+`'this is a long string'`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/type/string/unquoted.adoc b/doc/antora/modules/unlang/pages/type/string/unquoted.adoc
new file mode 100644
index 0000000..9dd6e55
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/type/string/unquoted.adoc
@@ -0,0 +1,21 @@
+= Unquoted Strings
+
+Where a series of characters cannot be parsed as a decimal number,
+they are interpreted as a simple string composed of one word. This
+word is delimited by spaces, or by other tokens, such as `)` in
+conditional expressions.
+
+This unquoted text is interpreted as simple strings and are generally
+equivalent to placing the string in single quotes.
+
+The interpretation of the text depends on the context, which is
+usually defined by an attribute which has a xref:type/index.adoc[data type].
+
+.Examples
+
+`Hello` +
+`192.168.0.1` +
+`00:01:02:03:04:05`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/update.adoc b/doc/antora/modules/unlang/pages/update.adoc
new file mode 100644
index 0000000..645f4d8
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/update.adoc
@@ -0,0 +1,160 @@
+= The update Statement
+
+.Syntax
+[source,unlang]
+----
+update [ <list> ] {
+ <server-attribute> <op> <value>
+ ...
+}
+----
+
+The `update` statement adds attributes to, or edits the attributes in,
+the named _<list>_.
+
+The `update` statement consists of the following syntax elements:
+
+<list>:: The attribute list which will be updated. The list is
+usually `request`, `reply`, or `control`.
++
+If the _<list>_ qualifier is omitted, then each entry inside of the
+`update` section *must* be prefixed with a list name. For example,
+`&request.User-Name ...`
+
+<server-attribute>:: The server attribute which is assigned the
+_<value>_.
+
+<op>:: The operator such as `=`, `:=`, etc.
+
+<value>:: The value which is assigned to the attribute. If the field
+is a double-quoted string, it undergoes xref:xlat/index.adoc[string
+expansion], and the resulting value is assigned to the attribute.
+
+The update process is atomic, in that either all of the attributes are
+modified, or none of them are modified. If the `update` fails for any
+reason, then all of the results are discarded, and the `update` does
+not affect any server attributes.
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Reply-Message := "Hello!"
+ &Framed-IP-Address := 192.0.2.4
+}
+----
+
+== Lists
+
+The _<list>_ field sets the attribute list that will be updated. If
+the _<list>_ qualifier is omitted, then each entry inside of the
+`update` section *must* be prefixed with a list name. For example,
+`&request.User-Name ...`
+
+Please see the xref:list.adoc[list] page for valid list names.
+
+== Server Attributes
+
+The _<server-attribute>_ field is an attribute name, such as
+`&Reply-Message`. The attribute name may also be prefixed with a
+_<list>_ qualifier, which overrides the _<list>_ given at the start
+of the `update` section.
+
+NOTE: In version 3, the leading `&` is optional but recommended.
+
+== Editing Operators
+
+The `<op>` field is used to define how the attribute is processed.
+Different operators allow attributes to be added, deleted, or
+replaced, as defined below.
+
+.Editing Operators
+[options="header"]
+[cols="10%,90%"]
+|=====
+| Operator | Description
+| = | Add the attribute to the list, if and only if an attribute of
+the same name is not already present in that list.
+| := | Add the attribute to the list. If any attribute of the same
+name is already present in that list, its value is replaced with the
+value of the current attribute.
+| += | Add the attribute to the tail of the list, even if attributes
+of the same name are already present in the list.
+| ^= | Add the attribute to the head of the list, even if attributes
+of the same name are already present in the list.
+| -= | Remove all attributes from the list that match _<value>_.
+| !* | Delete all occurances of the attribute, no matter what the value.
+|=====
+
+== Filtering Operators
+
+The following operators may also be used in addition to the ones
+listed above. These operators use the _<server-attribute>_ and
+_<value>_ fields to enforce limits on all attributes in the given
+_<list>_, and to edit attributes which have a matching
+_<server-attribute>_ name. All other attributes are ignored.
+
+.Filtering Operators
+[options="header]
+[cols="10%,90%"]
+|=====
+| Operator | Description
+| == | Keep only the attributes in the list that match _<value>_
+| < | Keep only the attributes in the list that have values less than _<value>_.
+| \<= | Keep only the attributes in the list that have values less than or equal to _<value>_.
+| > | Keep only the attributes in the list that have values greater than _<value>_.
+| >= | Keep only the attributes in the list that have values greater than or equal to _<value>_.
+| =~ | Keep only the attributes in the list which match the regular expression given in _<value>_.
+| !~ | Keep only the attributes in the list which do not match the regular expression given in _<value>_.
+|=====
+
+The `==` operator is very different from the `=` operator listed
+above. The `=` operator is used to add new attributes to the list,
+while the `==` operator removes all attributes that do not match the
+given value.
+
+The comparison operators `<`, `<=`, `>`, and `>=` have some additional
+side effects. Any non-matching value is replaced by the _<value>_
+given here. If no attribute exists, it is created with the given
+_<value>_.
+
+For IP addresses, the operators `>`, `>=`, `<`, and `\<=` check for
+membership in a network. The _<value>_ field should then be a IP
+network, given in `address/mask` format.
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Session-timeout := 86400
+}
+----
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Reply-Message += "Rejected: Also, realm does not end with ac.uk"
+}
+----
+
+== Values
+
+The _<value>_ field is the value which is assigned to the
+_<server-attribute>_. The interpretation of the _<value>_ field
+depends on the data type of the contents. For example, if the string
+`"192.0.2.1"` is assigned to an attribute of the `string` data type,
+then the result is an ASCII string containing that value. However, if
+the same string is assigned to an attribute of the `ipaddr` data type,
+then the result is a 32-bit IPv4 address, with binary value `0xc0000201`.
+
+.Example
+[source,unlang]
+----
+update reply {
+ &Session-Timeout <= 3600
+}
+----
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/xlat/alternation.adoc b/doc/antora/modules/unlang/pages/xlat/alternation.adoc
new file mode 100644
index 0000000..adb7604
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/xlat/alternation.adoc
@@ -0,0 +1,24 @@
+= Alternation Syntax
+
+Alternation syntax similar to that used in Unix shells may also be
+used:
+
+`%{%{Foo}:-bar}`
+
+This code returns the value of `%{Foo}`, if it has a value.
+Otherwise, it returns a literal string bar.
+
+`%{%{Foo}:-%{Bar}}`
+
+This code returns the value of `%{Foo}`, if it has a value.
+Otherwise, it returns the expansion of `%{Bar}`.
+
+These conditional expansions can be nested to almost any depth, such
+as with `%{%{One}:-%{%{Two}:-%{Three}}}`.
+
+.Examples
+`%{%{Stripped-User-Name}:-%{User-Name}}` +
+`%{%{Framed-IP-Address}:-<none>}`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/xlat/attribute.adoc b/doc/antora/modules/unlang/pages/xlat/attribute.adoc
new file mode 100644
index 0000000..a3ee29b
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/xlat/attribute.adoc
@@ -0,0 +1,54 @@
+= Attribute References
+
+Attributes in a list may be referenced via one of the following two
+syntaxes:
+
+`%{Attribute-Name}` +
+`%{<list>:Attribute-Name}`
+
+The `<list>:` prefix is optional. If given, it must be a valid
+reference to an xref:list.adoc[attribute list].
+
+If the `<list>:` prefix is omitted, then the `request` list is
+assumed.
+
+For EAP methods with tunneled authentication sessions (i.e. PEAP and
+EAP-TTLS), the inner tunnel session can refer to a list for the outer
+session by prefixing the list name with `outer.` ; for example,
+`outer.request`.
+
+When a reference is encountered, the given list is examined for an
+attribute of the given name. If found, the variable reference in the
+string is replaced with the value of that attribute. Otherwise, the
+reference is replacedd with an empty string.
+
+.Examples
+
+`%{User-Name}` +
+`%{request.User-Name} # same as above` +
+`%{reply.User-Name}` +
+`%{outer.request.User-Name} # from inside of a TTLS/PEAP tunnel`
+
+Examples of using references inside of a string:
+
+`"Hello %{User-Name}"` +
+`"You, %{User-Name} are not allowed to use %{NAS-IP-Address}"`
+
+== Additional Variations
+
+`%{Attribute-Name[#]}`::
+Returns an integer containing the number of named attributes
+
+`%{Attribute-Name[0]}`::
+
+When an attribute appears multiple times in a list, this syntax allows
+you to address the attributes as with array entries. `[0]` refers to
+the first attributes, `[1]` refers to the second attribute, etc.
+
+`%{Attribute-Name[*]}`::
+
+Returns a comma-separated string containing all values for the named
+attributes.
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/xlat/builtin.adoc b/doc/antora/modules/unlang/pages/xlat/builtin.adoc
new file mode 100644
index 0000000..f236a57
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/xlat/builtin.adoc
@@ -0,0 +1,891 @@
+= Built-In Expansions
+
+In addition to storing attribute references, the server has a number
+of built-in expansions. These expansions act largely as functions
+which operate on inputs, and produce an output.
+
+
+
+== Attribute Manipulation
+
+=== %{length: ... }
+
+The `length` expansion returns the size of the input as an integer.
+When the input is a string, then the output is identical to the
+`strlen` expansion.
+
+When the input is an attribute reference, the output is the size of
+the attributes data as encoded "on the wire".
+
+.Return: _size_
+
+.Determining the length of fixed and variable length attributes
+====
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "Caipirinha"
+ &Framed-IP-Address := 192.0.2.1
+}
+
+update reply {
+ &Reply-Message := "The length of %{control:Tmp-String-0} is %{length:&control:Tmp-String-0}"
+ &Reply-Message += "The length of %{control:Framed-IP-Address} is %{length:&control:Framed-IP-Address}"
+}
+----
+
+.Output
+....
+The length of Caipirinha is 10
+The length of 192.168.0.2 is 4
+....
+====
+
+`length` is built in to the server core.
+
+
+
+=== %{integer:<&ref>}
+
+Print the value of the attribute an integer.
+
+In normal operation, `integer` attributes are printed using the name
+given by a `VALUE` statement in a dictionary. Similarly, date
+attributes are printed as dates, i.e., "January 1 2010.
+
+The `integer` expansion applies only to attributes which can be
+converted to an integer. For all other inputs, it returns `0`.
+
+A common usage is to find the difference between two dates.
+
+For example, if a request contains `Service-Type = Login-User`, the
+expansion of `%{integer:&Service-Type}` will yield `1`, which is the
+value associated with the `Login-User` name. Using
+`%{integer:&Event-Timestamp}` will return the event timestamp as an
+unsigned 32-bit number.
+
+.Return: _string_
+
+.Determining the integer value of an enumerated attribute
+====
+[source,unlang]
+----
+update {
+ &control:Service-Type := Login-User
+}
+update reply {
+ &Reply-Message := "The value of Service-Type is %{integer:&control:Service-Type}"
+}
+----
+
+.Output
+
+```
+The value of Service-Type is 1
+```
+====
+
+`integer` is built in to the server core.
+
+
+
+=== %{rand:<number>}
+
+Generate random number from `0` to `<number>-1`.
+
+.Return: _uint64_
+
+.Generating a random number between 0 and 511
+====
+[source,unlang]
+----
+update reply {
+ &Reply-Message := "The random number is %{rand:512}"
+}
+----
+
+.Output
+
+```
+The random number is 347
+```
+====
+
+`rand` is provided by the `rlm_expr` module.
+
+
+
+=== %{tag:<attribute ref>}
+
+CAUTION: This expansion is deprecated and will likely be removed.
+
+Returns a list of tags for any attributes found using ``<attribute ref>``.
+
+.Return: _int8_
+
+.Determining the tag value of the second instance of the `radius.Tunnel-Server-Endpoint` attribute
+====
+[source,unlang]
+----
+update request {
+ &Tunnel-Server-Endpoint := '192.0.1.1'
+ &Tunnel-Server-Endpoint:1 := '192.0.5.2'
+ &Tunnel-Server-Endpoint:1 += '192.0.3.8'
+ &Tunnel-Server-Endpoint:2 := '192.0.2.1'
+ &Tunnel-Server-Endpoint:2 += '192.0.3.4'
+}
+
+update reply {
+ &Reply-Message := "The tag value of the second instance of Tunnel-Server-Enpoint is %{request:Tunnel-Server-Endpoint[1]}"
+}
+----
+
+.Output
+
+```
+The tag value of the second instance of Tunnel-Server-Enpoint is 192.0.5.2
+```
+====
+
+`tag` is built in to the server core.
+
+
+
+=== %{string:<data>}
+
+Convert input to a string (if possible). For _octets_ type attributes, this
+means interpreting the data as a UTF8 string, and inserting octal escape
+sequences where appropriate.
+
+For other types, this means printing the value in its _presentation_ format,
+i.e. dotted quads for IPv4 addresses, link:https://en.wikipedia.org/wiki/ISO_8601[ISO 8601]
+time for date types, enumeration values for attributes such as `radius.Service-Type` etc.
+
+.Return: _string_
+
+.String interpolation using the raw octets value of Tmp-Octets-0, and the stringified version
+====
+[source,unlang]
+----
+update control {
+ &Tmp-Octets-0 := 0x7465737431
+}
+update reply {
+ &Reply-Message := "The string value of %{control:Tmp-Octets-0} is %{string:%{control:Tmp-Octets-0}}"
+}
+----
+====
+
+.Output
+
+```
+The string value of 0x7465737431 is test1
+```
+
+`string` is built in to the server core.
+
+
+
+== Server Manipulation
+
+=== %{config:<key>}
+
+Refers to a variable in the configuration file. See the documentation
+on configuration file references.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+"Server installed in %{config:prefix}"
+"Module rlm_exec.shell_escape = %{config:modules.exec.shell_escape}"
+----
+
+.Output
+
+```
+Server installed in /opt/freeradius
+Module rlm_exec.shell_escape = yes
+```
+
+`config` is built in to the server core.
+
+
+
+=== %{client:<key>}
+
+Refers to a variable that was defined in the client section for the
+current client. See the sections `client { ... }` in `clients.conf`.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+"The client ipaddr is %{client:ipaddr}"
+----
+
+.Output
+
+```
+The client ipaddr is 192.168.5.9
+```
+
+`client` is built in to the server core.
+
+
+
+=== %{debug:<level>}
+
+Dynamically change the debug level to something high, recording the old level.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+authorize {
+ if (&request:User-Name == "bob") {
+ "%{debug:4}"
+ } else {
+ "%{debug:0}"
+ }
+ ...
+}
+----
+
+.Output (_extra informations only for that condition_)
+
+```
+...
+(0) authorize {
+(0) if (&request:User-Name == "bob") {
+(0) EXPAND %{debug:4}
+(0) --> 2
+(0) } # if (&request:User-Name == "bob") (...)
+(0) filter_username {
+(0) if (&State) {
+(0) ...
+(0) }
+...
+```
+
+`debug` is built in to the server core.
+
+
+
+=== %{debug_attr:<list:[index]>}
+
+Print to debug output all instances of current attribute, or all attributes in a list.
+expands to a zero-length string.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+authorize {
+ if (&request:User-Name == "bob") {
+ "%{debug_attr:request[*]}"
+ }
+ ...
+}
+----
+
+.Output
+
+```
+...
+(0) authorize {
+(0) if (&request:User-Name == "bob") {
+(0) Attributes matching "request[*]"
+(0) &request:User-Name = bob
+(0) &request:User-Password = hello
+(0) &request:NAS-IP-Address = 127.0.1.1
+(0) &request:NAS-Port = 1
+(0) &request:Message-Authenticator = 0x9210ee447a9f4c522f5300eb8fc15e14
+(0) EXPAND %{debug_attr:request[*]}
+(0) } # if (&request:User-Name == "bob") (...)
+...
+```
+
+`debug_attr` is built in to the server core.
+
+
+
+== String manipulation
+
+=== %{lpad:<&ref> <val> <char>}
+
+Left-pad a string.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "123"
+}
+update reply {
+ &Reply-Message := "Maximum should be %{lpad:&control:Tmp-String-0 11 0}"
+}
+----
+
+.Output
+
+```
+Maximum should be 00000000123
+```
+
+`lpad` is provided by the `rlm_expr` module.
+
+
+
+=== %{rpad:<&ref> <val> <char>}
+
+Right-pad a string.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "123"
+}
+update reply {
+ &Reply-Message := "Maximum should be %{rpad:&control:Tmp-String-0 11 0}"
+}
+----
+
+.Output
+
+```
+Maximum should be 12300000000
+```
+
+`rpad` is provided by the `rlm_expr` module.
+
+
+
+=== %{pairs:<&list:[*]>}
+
+Serialize attributes as comma-delimited string.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update {
+ &control:Tmp-String-0 := "This is a string"
+ &control:Tmp-String-0 += "This is another one"
+}
+
+update reply {
+ &Reply-Message := "Serialize output: %{pairs:&control[*]}"
+}
+----
+
+.Output
+
+```
+Serialize output: Tmp-String-0 = \"This is a string\"Tmp-String-0 = \"This is another one\"
+```
+
+`pairs` is provided by the `rlm_expr` module.
+
+
+
+=== %{randstr: ...}
+
+Get random string built from character classes.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update reply {
+ &Reply-Message := "The random string output is %{randstr:aaaaaaaa}"
+}
+----
+
+.Output
+
+```
+The random string output is 4Uq0gPyG
+```
+
+`randstr` is provided by the `rlm_expr` module.
+
+
+
+=== %{strlen: ... }
+
+Length of given string.
+
+.Return: _integer_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "Caipirinha"
+}
+update reply {
+ &Reply-Message := "The length of %{control:Tmp-String-0} is %{strlen:&control:Tmp-String-0}"
+}
+----
+
+.Output
+
+```
+The length of Caipirinha is 21
+```
+
+`strlen` is built in to the server core.
+
+
+
+=== %{tolower: ... }
+
+Dynamically expands the string and returns the lowercase version of
+it. This definition is only available in version 2.1.10 and later.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "CAIPIRINHA"
+}
+update reply {
+ &Reply-Message := "tolower of %{control:Tmp-String-0} is %{tolower:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+tolower of CAIPIRINHA is caipirinha
+```
+
+`tolower` is provided by the `rlm_expr` module.
+
+
+
+=== %{toupper: ... }
+
+Dynamically expands the string and returns the uppercase version of
+it. This definition is only available in version 2.1.10 and later.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "caipirinha"
+}
+update reply {
+ &Reply-Message := "toupper of %{control:Tmp-String-0} is %{toupper:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+toupper of caipirinha is CAIPIRINHA
+```
+
+`toupper` is provided by the `rlm_expr` module.
+
+
+
+== String Conversion
+
+=== %{base64: ... }
+
+Encode a string using Base64.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "Caipirinha"
+}
+update reply {
+ &Reply-Message := "The base64 of %{control:Tmp-String-0} is %{base64:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+The base64 of foo is Q2FpcGlyaW5oYQ==
+```
+
+`base64` is provided by the `rlm_expr` module.
+
+
+
+=== %{base64tohex: ... }
+
+Decode a base64 string (e.g. previously encoded using `base64`) to
+hex.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "Q2FpcGlyaW5oYQ=="
+}
+update reply {
+ &Reply-Message := "The base64tohex of %{control:Tmp-String-0} is %{base64tohex:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+The base64decode of Q2FpcGlyaW5oYQ== is 436169706972696e6861
+```
+
+`base64tohex` is provided by the `rlm_expr` module.
+
+
+
+=== %{hex: ... }
+
+Convert to hex.
+
+.Return: _string_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "12345"
+}
+update reply {
+ &Reply-Message := "The value of %{control:Tmp-String-0} in hex is %{hex:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+The value of 12345 in hex is 3132333435
+```
+
+`hex` is built in to the server core.
+
+
+
+=== %{urlquote: ... }
+
+Quote URL special characters.
+
+.Return: _string_.
+
+.Example
+
+[source,unlang]
+----
+update {
+ &control:Tmp-String-0 := "http://example.org/"
+}
+update reply {
+ &Reply-Message += "The urlquote of %{control:Tmp-String-0} is %{urlquote:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+The urlquote of http://example.org/ is http%3A%2F%2Fexample.org%2F
+```
+
+`urlquote` is provided by the `rlm_expr` module.
+
+
+
+=== %{urlunquote: ... }
+
+Unquote URL special characters.
+
+.Return: _string_.
+
+.Example
+
+[source,unlang]
+----
+update {
+ &control:Tmp-String-0 := "http%%3A%%2F%%2Fexample.org%%2F" # Attention for the double %.
+}
+update reply {
+ &Reply-Message += "The urlunquote of %{control:Tmp-String-0} is %{urlunquote:%{control:Tmp-String-0}}"
+}
+----
+
+.Output
+
+```
+The urlunquote of http%3A%2F%2Fexample.org%2F is http://example.org/
+```
+
+`urlunquote` is provided by the `rlm_expr` module.
+
+
+
+== Hashing and Encryption
+
+=== %{hmacmd5:<shared_key> <string>}
+
+Generate `HMAC-MD5` of string.
+
+.Return: _octal_
+
+.Example
+
+[source,unlang]
+----
+update {
+ &control:Tmp-String-0 := "mykey"
+ &control:Tmp-String-1 := "Caipirinha"
+}
+update {
+ &control:Tmp-Octets-0 := "%{hmacmd5:%{control:Tmp-String-0} %{control:Tmp-String-1}}"
+}
+
+update reply {
+ &Reply-Message := "The HMAC-MD5 of %{control:Tmp-String-1} in octets is %{control:Tmp-Octets-0}"
+ &Reply-Message += "The HMAC-MD5 of %{control:Tmp-String-1} in hex is %{hex:control:Tmp-Octets-0}"
+}
+----
+
+.Output
+
+```
+The HMAC-MD5 of Caipirinha in octets is \317}\264@K\216\371\035\304\367\202,c\376\341\203
+The HMAC-MD5 of Caipirinha in hex is 636f6e74726f6c3a546d702d4f63746574732d30
+```
+
+`hmacmd5` is provided by the `rlm_expr` module.
+
+
+
+=== %{hmacsha1:<shared_key> <string>}
+
+Generate `HMAC-SHA1` of string.
+
+.Return: _octal_
+
+.Example
+
+[source,unlang]
+----
+update {
+ &control:Tmp-String-0 := "mykey"
+ &control:Tmp-String-1 := "Caipirinha"
+}
+update {
+ &control:Tmp-Octets-0 := "%{hmacsha1:%{control:Tmp-String-0} %{control:Tmp-String-1}}"
+}
+
+update reply {
+ &Reply-Message := "The HMAC-SHA1 of %{control:Tmp-String-1} in octets is %{control:Tmp-Octets-0}"
+ &Reply-Message += "The HMAC-SHA1 of %{control:Tmp-String-1} in hex is %{hex:control:Tmp-Octets-0}"
+}
+----
+
+.Output
+
+```
+The HMAC-SHA1 of Caipirinha in octets is \311\007\212\234j\355\207\035\225\256\372ʙ>R\"\341\351O)
+The HMAC-SHA1 of Caipirinha in hex is 636f6e74726f6c3a546d702d4f63746574732d30
+```
+
+`hmacsha1` is provided by the `rlm_expr` module.
+
+
+
+=== %{md5: ... }
+
+Dynamically expands the string and performs an MD5 hash on it. The
+result is binary data.
+
+.Return: _binary data_
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "Caipirinha"
+}
+update reply {
+ &Reply-Message := "md5 of %{control:Tmp-String-0} is octal=%{md5:%{control:Tmp-String-0}}"
+ &Reply-Message := "md5 of %{control:Tmp-String-0} is hex=%{hex:%{md5:%{control:Tmp-String-0}}}"
+}
+----
+
+.Output
+
+```
+md5 of Caipirinha is octal=\024\204\013md||\230\243\3472\3703\330n\251
+md5 of Caipirinha is hex=14840b6d647c7c98a3e732f833d86ea9
+```
+
+`md5` is provided by the `rlm_expr` module.
+
+
+
+== Miscellaneous Expansions
+
+=== +%{0}+..+%{32}+
+
+`%{0}` expands to the portion of the subject that matched the last regular
+expression evaluated. `%{1}`..`%{32}` expand to the contents of any capture
+groups in the pattern.
+
+Every time a regular expression is evaluated, whether it matches or not,
+the numbered capture group values will be cleared.
+
+
+
+=== +%{regex:<named capture group>}+
+
+Return named subcapture value from the last regular expression evaluated.
+
+Results of named capture groups are available using the `%{regex:<named capture
+group>}` expansion. They will also be accessible using the numbered expansions
+described xref:builtin.adoc#_0_32[above].
+
+Every time a regular expression is evaluated, whether it matches or not,
+the named capture group values will be cleared.
+
+[NOTE]
+====
+This expansion is only available if the server is built with libpcre or libpcre2.
+Use the output of `radiusd -Xxv` to determine which regular expression library in use.
+
+....
+...
+Debug : regex-pcre : no
+Debug : regex-pcre2 : yes
+Debug : regex-posix : no
+Debug : regex-posix-extended : no
+Debug : regex-binsafe : yes
+...
+Debug : pcre2 : 10.33 (2019-04-16) - retrieved at build time
+....
+====
+
+`regex` is built in to the server core.
+
+
+
+=== +%{nexttime:<time>}+
+
+Calculate number of seconds until next n hour(`s`), day(`s`), week(`s`), year(`s`).
+
+.Return: _string_
+
+.Example:
+
+With the current time at 16:18, `%{nexttime:1h}` will expand to `2520`.
+
+[source,unlang]
+----
+update reply {
+ &Reply-Message := "You should wait for %{nexttime:1h}s"
+}
+----
+
+.Output
+
+```
+You should wait for 2520s
+```
+
+`nexttime` is provided by the `rlm_expr` module.
+
+
+
+=== +%{Packet-Type}+
+
+The packet type (`Access-Request`, etc.)
+
+
+
+=== +%{Packet-Src-IP-Address} and %{Packet-Src-IPv6-Address}+
+
+The source IPv4 or IPv6 address of the packet. See also the expansions
+`%{client:ipaddr}` and `%{client:ipv6addr}`. The two expansions
+should be identical, unless `%{client:ipaddr}` contains a DNS hostname.
+
+
+
+=== +%{Packet-Dst-IP-Address} and %{Packet-Dst-IPv6-Address}+
+
+The destination IPv4 or IPv6 address of the packet. See also the
+expansions `%{listen:ipaddr}` and `%{listen:ipv6addr}`. If the socket
+is listening on a "wildcard" address, then these two expansions will be
+different, as follows: the `%{listen:ipaddr}` will be the wildcard
+address and `%{Packet-DST-IP-Address}` will be the unicast address to
+which the packet was sent.
+
+
+
+=== +%{Packet-Src-Port} and %{Packet-Dst-Port}+
+
+The source/destination ports associated with the packet.
+
+.Return: _string_.
+
+.Example
+
+[source,unlang]
+----
+update control {
+ &Tmp-String-0 := "user@example.com"
+}
+
+if (&control:Tmp-String-0 =~ /^(?<login>(.*))@(?<domain>(.*))$/) {
+ update reply {
+ &Reply-Message := "The %{control:Tmp-String-0} { login=%{regex:login}, domain=%{regex:domain} }"
+ }
+}
+----
+
+.Output
+
+```
+The user@example.com { login=user, domain=example.com }
+```
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/xlat/character.adoc b/doc/antora/modules/unlang/pages/xlat/character.adoc
new file mode 100644
index 0000000..84a148c
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/xlat/character.adoc
@@ -0,0 +1,80 @@
+= Single Letter Expansions
+
+The following are single letter expansions. These expansions do not
+use the typical `%{...}` format. Instead, they are short-cuts for
+simple, common cases.
+
+== Miscellaneous
+
+`%%`::
+
+Returns `%`.
+
+
+== Current Time
+
+`%c`::
+
+The current Unix epoch time in seconds. This is an unsigned decimal number.
+It should be used with time-based calculations.
+
+`%C`::
+
+The microsecond component of the current epoch time. This is an unsigned
+decimal number. It should be used with time-based calculations.
+
+
+== Request Time
+
+`%l`::
+
+The Unix timestamp of when the request was received. This is an unsigned
+decimal number. It should be used with time-based calculations.
+
+`%Y`::
+
+Four-digit year when the request was received.
+
+`%m`::
+
+Numeric month when the request was received.
+
+`%d`::
+
+Numeric day of the month when the request was received.
+
+`%H`::
+
+Hour of the day when the request was received.
+
+`%G`::
+
+Minute component of the time when the request was received.
+
+`%e`::
+
+Second component of the time when the request was received.
+
+`%M`::
+
+Microsecond component of the time when the request was received.
+
+`%D`::
+
+Request date in the format `YYYYMMDD`.
+
+`%S`::
+
+Request timestamp in SQL format, `YYYY-mmm-ddd HH:MM:SS`.
+
+`%t`::
+
+Request timestamp in _ctime_ format, `Www Mmm dd HH:MM:SS YYYY`.
+
+`%T`::
+
+Request timestamp in ISO format, `YYYY-mm-ddTHH:MM:SS.000`.
+
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/xlat/index.adoc b/doc/antora/modules/unlang/pages/xlat/index.adoc
new file mode 100644
index 0000000..b42f725
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/xlat/index.adoc
@@ -0,0 +1,56 @@
+= String Expansion
+
+String expansion is a feature that allows strings to dynamically
+define their value at run time. For historical reasons, these string
+expansions are called "xlats".
+
+String expansion is performed via the following syntax:
+
+`%{...}`
+
+Where the `%{` signals the start of a dynamic expansion, and `}`
+signals the end of the dynamic expansion. The contents of the
+expansion can be many things:
+
+.String Expansions
+[options="header"]
+|=====
+| Keyword | Description
+| xref:xlat/attribute.adoc[attributes] | Expand the value of a named attribute.
+| xref:xlat/character.adoc[single character] | Single character expansions.
+| xref:xlat/module.adoc[modules] | Pass a string to a module such as `sql`.
+| xref:xlat/alternation.adoc[condition] | Conditionally expand a string.
+| xref:xlat/builtin.adoc[built-in expansions] | Such as string length, tolower, etc...
+|=====
+
+This feature is used to create policies which refer to concepts rather
+than to specific values. For example, a policy can be created that
+refers to the User-Name in a request, via:
+
+`%{User-Name}`
+
+This string expansion is done only for double-quoted strings and for
+the back-tick operator.
+
+== Caveats
+
+Unlike other languages, there is no way to define new variables. All
+of the string expansions must refer to attributes that already exist,
+or to modules that will return a string value.
+
+== Character Escaping
+
+Some characters need to be escaped within a dynamically expanded
+string `%{...}`. The `%` character is used for variable expansion, so a
+literal `%` character can be created by using `%%`.
+
+Other than within a dynamically expanded string, very little
+character escaping is needed. The rules of the enclosing string context
+determine whether or not a space or " character needs to be escaped.
+
+.Example
+
+`Reply-Message := "%{User-Name} with a literal %%`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/pages/xlat/module.adoc b/doc/antora/modules/unlang/pages/xlat/module.adoc
new file mode 100644
index 0000000..3ce4322
--- /dev/null
+++ b/doc/antora/modules/unlang/pages/xlat/module.adoc
@@ -0,0 +1,18 @@
+= Module References
+
+Individual modules may be referenced via the following syntax:
+
+`%{module:string}`
+
+These references are allowed only by a small number of modules that
+usually perform database lookups. The module name is the actual name of
+the module, as described earlier. The string portion is specific to each
+module and is not documented here. It is, however, usually dynamically
+expanded to allow for additional flexibility.
+
+.Examples
+
+`%{sql:SELECT name FROM mytable WHERE username = %{User-Name}}`
+
+// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Development of this documentation was sponsored by Network RADIUS SAS.
diff --git a/doc/antora/modules/unlang/partials/rcode_table.adoc b/doc/antora/modules/unlang/partials/rcode_table.adoc
new file mode 100644
index 0000000..e114e74
--- /dev/null
+++ b/doc/antora/modules/unlang/partials/rcode_table.adoc
@@ -0,0 +1,39 @@
+[options="header"]
+[cols="15%,85%"]
+|=====
+| Return code | Description
+| `fail` | The operation failed. Usually as a result of an
+ external dependency like a database being unavailable
+ or an internal error.
+| `handled` | The request has been "handled", no further policies
+ in the current section should be called, and the section
+ should immediately exit.
+| `invalid` | The request, or operation, was invalid. In the case of
+ requests this usually indicates absent or malformed
+ attribute values.
+| `noop` | The operation did nothing.
+| `notfound` | A 'lookup' operation returned no results.
+| `ok` | Operation completed successfully but did not change any
+ attributes in the request.
+| `reject` | The operation indicates the current request should be
+ 'rejected'. What this actually means is different from
+ protocol to protocol. It usually means that access to
+ the requested resource should be denied, or that the
+ current request should be NAKd. Usually returned when
+ provided credentials were invalid.
+| `updated` | The operation completed successfully and updated one
+ or more attributes in the request.
+| `disallow` | Access to a particular resource is
+ denied. This is similar to `reject` but is the result
+ of an authorizational check failing, as opposed to
+ credentials being incorrect.
+| `yield` | Returned by an operation when execution of a request should
+ be suspended.
+|=====
+
+[NOTE]
+====
+In versions ≤ v3.0.x the `disallow` rcode was called `userlock`. `disallow` and
+`userlock` have an identical meaning. `disallow` will be returned in any
+instance where `userlock` was returned in v3.0.x
+====