FreeRADIUS Implementation Ch16
FreeRADIUS Implementation Ch16
Overview
Data types
Configuration file format
Module statements
String expansion
Programming statements
Conditions/conditional expressions
Attribute editing statements
Configurable failover
16.0 Overview
The server supports a simple processing language called Unlang, which is short for unlanguage. The
original intention of using an unlanguage was to avoid creating yet another programming language.
Unlang allows simple conditional checks and editing of attributes and attribute lists. Where more
complicated functionality is required, Perl or Python modules rlm_perl or rlm_python are recommended.
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 (e.g., authorize, authenticate, post-auth,
preacct, accounting, pre-proxy, post-proxy, and session); 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.
The Unlang syntax is based on reserved words, which dictate how the text that follows should be
interpreted. The syntax for each word is line oriented. Subsections are sometimes allowed, just like with
other portions of radiusd.conf. Where the lines are too long, the backslash character can be used to link
multiple lines together.
Note that unlike most programming languages, whitespace is important in Unlang. For example, the
following two statements are not identical. Although the only difference between the two statements is
the placement of the first hard return, the first will parse correctly, and the second will return a parse error:
if (foo) {
...
}
versus:
if (foo)
{
...
}
110 of 158
16.1.1 Numbers
Examples:
0
563
Numbers are unsigned integers that are composed of decimal digits. Signed numbers, floating point, hex,
and octal numbers are not supported in Unlang.
These limitations are largely a result of the limitations of the underlying protocol. There is no way to
transport signed numbers or floating point numbers in RADIUS, so there is no need to perform signed or
floating point calculations.
The maximum value for a number is machine-dependent but is at least 32 bits or 4,294,967,296. In some
cases, it may be necessary to perform calculations on numbers larger than 32 bits, as when calculating
total data traffic to or from a user. In those cases, the calculations should either be one carefully to avoid
32-bit overflow or be done using real programming language.
16.1.3 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.
111 of 158
A single quoted string is interpreted without any dynamic string expansion. The quotes allow the string
to contain spaces, which are not allowed in the word form described in the previous section. 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
112 of 158
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 language
may be sufficient.
This operator is permitted only in conditional expressions and when assigning values to an attribute. In
versions 2.1.11 and later, using it in an invalid context will return a syntax error, and the server will refuse
to start. In versions of the server prior to 2.1.11, the data was treated as a single quoted string, and no runtime expansion or program execution is performed.
Examples:
/bin/echo hello
16.2.1 Syntax
Long lines can be split:
foo = \
bar
Comments are ignored:
# this is a comment
Comments can be placed at the tail end of any valid line:
foo = bar # assign bar to variable foo
113 of 158
Examples:
prefix = "/usr/share/local"
max_request_time = 30
reject_delay = 1
reference
A reference to a named variable in the current section. If the variable does not exist, then the
variable is looked for in the main (i.e., global) configuration.
.reference
A reference to a named variable in the current section. The global configuration is not used.
..reference
A reference to the named variable found in the parent section instead of the current section. Any
number of dots (.) can be used, up to the global configuration.
reference1.reference2.reference3
A reference to the section named reference1, which contains a subsection named
reference2, which in turn contains a variable reference3. These references may be nested to
any depth.
reference1[name].reference2
A reference to a section named reference1, which has an instance called name, which in turn
contains a variable reference2.
The above capabilities allow any portion of the configuration files to reference values taken from any
other portion.
One additional expansion is supported.
$ENV{variable}
A reference to the environment variable named variable.
Examples:
foo = bar
baz = "${foo}" # assigns "bar" to "baz"
var = "a ${baz} string" # assigns "a bar string" to "var"
name = "${client[localhost].secret}"
ipaddr = $ENV{HOSTNAME}
114 of 158
The $INCLUDE directive includes a file into the current location of the configuration files, with the
contents of the file replacing the $INCLUDE directive. The path has one or more forms, with the following
meanings:
filename
Include a new filename relative to the current file being parsed.
/path/to/filename
Include filename using the given absolute path.
directory/
Include all of the files in the given directory. Any hidden files (i.e., with a leading dot (.) in their
name) are ignored. All other files are read, including common editor backup files, such as ones with
a trailing ~ in their name.
For that reason, this directive should be used with care.
The only limitation of $INCLUDE files is that a section cannot be split across multiple files.
Examples:
$INCLUDE clients.conf
$INCLUDE sites-enabled/
$INCLUDE ${raddbdir}/foo/bar/baz
16.2.5 Sections
section {
[ statements ]
}
A section is a way to group multiple statements together. Sections can be nested to any depth and may
contain any valid statement. Empty sections are allowed but serve no purpose.
Names of sections and variables are scoped within the context of their parent section. It is a syntax error
to have a section and variable with the same name in the same scope. The server looks for and parses
sections with known names. These known sections have pre-defined meanings, which cannot be
changed.
Examples:
foo {
bar = baz
}
115 of 158
The same instance-name applies to modules. The sql module defines interation with an SQL
database. Individual instances of the sql module can define interactions with different databases.
Examples:
client localhost {
ipaddr = 127.0.0.1
...
}
sql backup {
...
}
Meaning
reject
fail
ok
handled
116 of 158
Meaning
invalid
userlock
notfound
noop
updated
These return codes can be used in a subsequent conditional expression (see the processing section for a
more detailed description), thus allowing policies to perform different actions based on the behavior of
the modules.
ok Instructs the server that the request was processed properly. This keyword can be used to override earlier failures, if the local administrator determines that the failures are not catastrophic.
117 of 158
[ module-2 ]
The redundant section is used in place of a single module name and is used to configure inter-module
fail-over. When the redundant statement is reached, the first module in the section is called. If that
module succeeds, then the server returns from the redundant section and continues processing the
rest of the policy.
However, if that module fails, then the next module in the list is called and checked for failure as described
above. The processing continues until either a module succeeds or the end of the list has been reached.
There is no limit to the number of modules that can be listed inside of a redundant section.
Examples:
redundant {
sql1
sql2
ok
}
118 of 158
Examples:
redundant-load-balance {
sql1
sql2
sql3
}
119 of 158
%{<list>:Attribute-Name}
The <list>: prefix is optional. If given, it must be one of request, reply, proxy-request,
proxy-reply, coa, disconnect, or control. 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
replaced with an empty string.
Examples:
%{User-Name}
%{request:User-Name}
%{reply:User-Name}
%{outer.request:User-Name}
# same as above
# from inside of a TTLS/PEAP tunnel
120 of 158
%{conf:...}
Refers to a variable in the configuration file. See the section for documentation on configuration file
references.
%{client:...}
Refers to a variable that was defined in the client section for the current client.
%{listen:...}
Refers to a variable that was defined in the listen section that received the packet. This definition is
only available in version 2.1.11 and later.
%{0}
Refers to the string that was last used to match a regular expression. The variables %{1} through
%{8} refer to the matched substring in the regular expression.
%{md5:...}
Dynamically expands the string and performs an MD5 hash on it. The result is 32 hex digits.
%{Packet-Type}
The packet type (Access-Request, etc.)
%{Packet-SRC-IP-Address} %{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} %{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} %{Packet-DST-Port}
The source/destination ports associated with the packet.
%{tolower:...}
Dynamically expands the string and returns the lowercase version of it. This definition is only
available in version 2.1.10 and later.
%{toupper:...}
Dynamically expands the string and returns the uppercase version of it. This definition is only
available in version 2.1.10 and later.
121 of 158
%%
Returns %.
%d
Two-digit day of the month when the request was received.
%l
The Unix timestamp of when the request was received. This is an unsigned decimal number. It
should be used with time-based calculations.
%m
Two-digit month describing when the request was received.
%t
Timestamp in ctime format.
%D
Request date (YYYYMMDD)
%H
Two digit hour of the day describing when the request was received.
%S
Request timestamp in SQL format, YYYY-mmm-ddd HH:MM:SS
%T
Request timestamp in database format, YYYY-mmm-ddd HH.MM.SS.000000
%Y
Four digit request year.
122 of 158
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}}}.
String Length
%{#string}
The string length operator returns the number of characters in the given string as a decimal number. It
can be used with attribute or module references. If the string has no value, then the length evaluates to
zero.
Printing as Integers
%{Attribute-Name#}
Placing the hash character after an attribute name causes the attribute to be printed as 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.
This operator applies only to attributes of type date, integer, byte, and short. It has no effect on
other attribute types and cannot be used with module references. It is most commonly used to perform
calculations on dates, where the dates are treated as integers.
For example, if a request contains Service-Type = Login-User, the expansion of %{ServiceType#} will yield 1, which is the value associated with the Login-User name. Using %{EventTimestamp#} will return the event timestamp as an unsigned 32-bit decimal number.
Indexed Offsets
%{Attribute-Name[index]}
References the indexed occurrence of the given attribute. The indexes start at zero. This feature is NOT
available for non-attribute dynamic translations, like %{sql:...}.
For example, %{User-Name[0]} is the same as %{User-Name}. The reference %{CiscoAVPair[2]} will reference the value of the third Cisco-AVPair attribute (if it exists) in the request
packet.
In practical usage, this kind of reference is of limited use. A plugin for a full language such as Perl or Python
is recommended if this functionality is required.
Number of Attributes
%{Attribute-Name[#]}
Returns the total number of attributes of that name in the relevant attribute list. The number will usually
be between 0 and 200.
123 of 158
For most requests, %{User-Name[#]} will have a value of 1. As with the previous section, this reference
is of limited use.
124 of 158
else {
[ statements ]
}
A series of if statements can be used so that only one subsection is executed. In the preceding example,
if condition-1 evaluates to false, then the statements in the if subsection are skipped and
condition-2 is checked. If condition-2 evaluates true, then the statements in the elsif
subsection are executed. If condition-2 evaluates false, then the statements in the elsif subsection
are skipped, and the statements in the else subsection (if it exists) are executed.
An elsif clause does not need to be followed by an else clause. However, any else clause must be
the last clause in the chain. An arbitrary number of elsif clauses can be chained together to create a
series of conditional checks and statements.
The ( ) Operator
( condition )
The ( ) operator returns the result of evaluating the given condition. It is used to clarify policies or to
explicitly define conditional precedence.
125 of 158
Examples:
(foo)
(bar || (baz && dub))
Meaning
notfound
noop
ok
updated
fail
reject
userlock
invalid
handled
Examples:
if (notfound) {
126 of 158
...
127 of 158
The && operator performs a short-circuit and evaluation of the two expressions. This operator evaluates
expression-1 and returns false if expression-1 returns false. Only if expression-1
returns true is expression-2 evaluated and its result returned.
Examples:
if (User-Name && EAP-Message) { ...
The || Operator
(expression-1 || expression-2)
The || operator performs a short-circuit or evaluation of the two expressions. This operator evaluates
expression-1 and returns true if expression-1 returns true. Only if expression-1 returns
false is expression-2 evaluated and its result returned.
Examples:
The == Operator
(data-1 == data-2)
The == operator compares the result of evaluating data-1 and data-2. As discussed in 16.1 Data
Types on page 111, 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, 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.
Examples:
if (User-Name == "bob") { ...
< data-1)
<= data-1)
> data-1)
>= data-1)
The other comparison operators act in a fashion similar to the == operator except that the result of the
evaluation is the result of the given comparison. These operators are type-safe for integers, IP addresses,
dates, and strings.
Examples:
if (reply:Session-Timeout < 3600) { ...
128 of 158
129 of 158
Attribute Names
The Attribute-Name, described above in the Operator section, must be a name defined in a
dictionary. If an undefined name is used, the server will return an error and will not start. The
Attribute-Name must be a simple word, as described above in section 16.1 Data Types on page 111.
The Unlang interpreter does not perform any string expansion on the name, so it is not possible to define
names that refer to other attributes.
Examples:
User-Name
Reply-Message
Operators
The operator is used to define how the attribute is handled. Different operators allow attributes to be
added, deleted, or replaced.
= 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.
Examples:
User-Name = "bob"
NAS-IP-Address := 192.0.2.17
Reply-Message += "hello"
130 of 158
-= Remove all attributes from the list that match the given value.
== Keep only the attributes in the list that match the given value.
Note that this 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.
< Keep only the attributes in the list that have values less than the value given herein. Any larger
value is replaced by the value given. If no such Attribute-Name exists, the attribute is added
with the value given here, as with the += operator.
This operator is valid only for attributes that can be represented as an integer.
<= Keep only the attributes in the list that have values less than or equal to the value given here.
Any larger value is replaced by the value given here. If no such Attribute-Name exists, the
attribute is added with the value given here, as with the += operator.
This operator is valid only for attributes that can be represented as an integer.
> Keep only the attributes in the list that have values greater than the value given here. Any smaller
value is replaced by the value given here. If no such Attribute-Name exists, the attribute is
added with the value given here, as with the += operator.
This operator is valid only for attributes that can be represented as an integer.
>= Keep only the attributes in the list that have values greater than or equal to the value given here.
Any smaller value is replaced by the value given here. If no such Attribute-Name exists, the
attribute is added with the value given here, as with the += operator.
This operator is valid only for attributes that can be represented as an integer.
!* Delete all occurrences of the named attribute, no matter what value they have.
Examples:
Session-Timeout <= 3600
Reply-Message != *
Values
The value field contains data as given above in 16.1 Data Types on page 111. The format of the value
is attribute-specific and is usually a string, integer, IP address, or other.
Prior to the attribute being instantiated, the value may be expanded as described above in section 16.1
Data Types on page 111. The expansion of the value allows for greater flexibility; for example, an IP
address value can be assigned to an attribute by specifying the IP address directly, by having the address
returned from a database query, or by having the address returned as the output of an executed program.
When string values are assigned to an attribute, they can have a maximum length of 253 characters.
Any extra data is silently discarded, causing the string to be truncated.
This truncating behavior is due to limitations of the RADIUS protocol, which can only transport strings of
253 characters or less; thus, attributes can only contain strings of that length. The Unlang parser has no
131 of 158
such limit, however, so strings within the configuration files can have nearly arbitrary length. For example,
it is possible to write an SQL SELECT statement that is more than 1000 characters long. The result of the
SELECT statement will, however, be truncated to 253 characters.
Examples:
0
192.0.2.16
foo bar
"hello"
"%{sql: SELECT ... }"
132 of 158
decimal number The priority associated with this return code. The number may have any value
between 1 and 999999.
return Stop processing the current list, and return the current code.
reject Stop processing the current list, and return with a reject.
133 of 158
Examples:
files {
notfound = 1
noop = 2
ok = 3
updated = 4
default = return # all other codes are set to "return"
}
files {
default = return # all codes are set to "return"
notfound = 1 # these settings over-ride the "return"
noop = 2
ok = 3
updated = 4
}
134 of 158