NAME
unlang - FreeRADIUS Processing un-language
DESCRIPTION
FreeRADIUS supports a simple processing language in its configuration
files. We call it an "un-language" because the intention is NOT to
create yet another programming language. If you need something more
complicated than what is described here, we suggest using the Perl or
Python modules rlm_perl, or rlm_python.
The goal of the language is to allow simple policies to be written with
minimal effort. Those policies are then applied when a request is
being processed. Requests are processed through virtual servers
(including the default one), in the sections titled "authorize",
"authenticate", "post-auth", "preacct", "accounting", "pre-proxy",
"post-proxy", and "session".
These policies cannot be used in any other part of the configuration
files, such as module or client configuration.
KEYWORDS
The keywords for the language are a combination of pre-defined
keywords, and references to loadable module names. We document only
the pre-defined keywords here.
Subject to a few limitations described below, any keyword can appear in
any context. The language consists of a series of entries, each one
one line. Each entry begins with a keyword. Entries are organized
into lists. Processing of the language is line by line, from the start
of the list to the end. Actions are executed per-keyword.
module-name
A reference to the named module. When processing reaches this
point, the pre-compiled module is called. The module may
succeed or fail, and will return a status to "unlang" if so.
This status can be tested in a condition. See the "Simple
Conditions" text in the CONDITIONS section, and MODULE RETURN
CODES, below.
chap # call the CHAP module
sql # call the SQL module
...
if
Checks for a particular condition. If true, the block after the
condition is processed. Otherwise, the block is ignored. See
CONDITIONS, below, for documentation on the format of the
conditions.
if (condition) {
...
}
else
Define a block to be executed only if the previous "if"
condition returned false.
else {
...
}
elsif
Define a block to be executed only if the previous "if"
condition returned false, and if the specified condition
evaluates to true.
elsif (condition) {
...
}
switch
Evaluate the given string, and choose the first matching "case"
statement inside of the current block. If the string is
surrounded by double quotes, it is expanded as described in the
DATA TYPES section, below.
No statement other than "case" can appear in a "switch" block.
switch "string" {
...
}
case
Define a static string to match a parent "switch" statement.
The strings given here are not expanded as is done with the
parent "switch" statement.
A "case" statement cannot appear outside of a "switch" block.
case string {
...
}
A default entry can be defined by omitting the static string. This
entry will be used if no other "case" entry matches. Only one default
entry can exist in a "switch" section.
case {
...
}
update
Update a particular attribute list, based on the attributes
given in the current block.
update <list> {
attribute = value
...
}
The <list> can be one of "request", "reply", "proxy-request", "proxy-
reply", "coa", "disconnect", or "control". The "control" list is the
list of attributes maintainted internally by the server that controls
how the server processes the request. Any attribute that does not go
in a packet on the network will generally be placed in the "control"
list.
For backwards compatibility with older versions, "check" is accepted as
a synonym for "control". The use of "check" is deprecated, and will be
removed in a future release.
For EAP methods with tunneled authentication sessions (i.e. PEAP and
EAP-TTLS), the inner tunnel session can also reference "outer.request",
"outer.reply", and "outer.control". Those references allow you to
address the relevant list in the outer tunnel session.
The "coa" and "disconnect" sections 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.
The only contents permitted in an "update" section are attributes and
values. The contents of the "update" section are described in the
ATTRIBUTES section below.
redundant
This section contains a simple list of modules. The first
module is called when the section is being processed. If the
first module succeeds in its operation, then the server stops
processing the section, and returns to the parent section.
If, however, the module fails, then the next module in the list
is tried, as described above. The processing continues until
one module succeeds, or until the list has been exhausted.
Redundant sections can contain only a list of modules, and
cannot contain keywords that perform conditional operations (if,
else, etc) or update an attribute list.
redundant {
sql1 # try this
sql2 # try this only if sql1 fails.
...
}
load-balance
This section contains a simple list of modules. When the
section is entered, one module is chosen at random to process
the request. All of the modules in the list should be the same
type (e.g. ldap or sql). All of the modules in the list should
behave identically, otherwise the load-balance section will
return different results for the same request.
Load-balance sections can contain only a list of modules, and
cannot contain keywords that perform conditional operations (if,
else, etc) or update an attribute list.
load-balance {
ldap1 # 50% of requests go here
ldap2 # 50% of requests go here
}
In general, we recommend using "redundant-load-balance" instead of
"load-balance".
redundant-load-balance
This section contains a simple list of modules. When the
section is entered, one module is chosen at random to process
the request. If that module succeeds, then the server stops
processing the section. If, however, the module fails, then one
of the remaining modules is chosen at random to process the
request. This process repeats until one module succeeds, or
until the list has been exhausted.
All of the modules in the list should be the same type (e.g.
ldap or sql). All of the modules in the list should behave
identically, otherwise the load-balance section will return
different results for the same request.
Load-balance sections can contain only a list of modules, and
cannot contain keywords that perform conditional operations (if,
else, etc) or update an attribute list.
redundant-load-balance {
ldap1 # 50%, unless ldap2 is down, then 100%
ldap2 # 50%, unless ldap1 is down, then 100%
}
CONDITIONS
The conditions are similar to C conditions in syntax, though quoted
strings are supported, as with the Unix shell.
Simple conditions
(foo)
Evalutes to true if ’foo’ is a non-empty string (single quotes, double
quotes, or back-quoted). Also evaluates to true if ’foo’ is a non-zero
number. Note that the language is poorly typed, so the string "0000"
can be interpreted as a numerical zero. This issue can be avoided by
comparings strings to an empty string, rather than by evaluating the
string by itself.
If the word ’foo’ is not a quoted string, then it can be taken as a
reference to a named attribute. See "Referencing attribute lists",
below, for examples of attribute references. The condition evaluates
to true if the named attribute exists.
Otherwise, if the word ’foo’ is not a quoted string, and is not an
attribute reference, then it is interpreted as a reference to a module
return code. The condition evaluates to true if the most recent module
return code matches the name given here. Valid module return codes are
given in MODULE RETURN CODES, below.
Negation
(!foo)
Evalutes to true if ’foo’ evaluates to false, and vice-versa.
Short-circuit operators
(foo || bar)
(foo && bar)
"&&" and "||" are short-circuit operators. "&&" evaluates the
first condition, and evaluates the second condition if and only
if the result of the first condition is true. "||" is similar,
but executes the second command if and only if the result of the
first condition is false.
Comparisons
(foo == bar)
Compares ’foo’ to ’bar’, and evaluates to true if the comparison holds
true. Valid comparison operators are "==", "!=", "<", "<=", ">", ">=",
"=~", and "!~", all with their usual meanings. Invalid comparison
operators are ":=" and "=".
Conditions may be nested to any depth, subject only to line length
limitations (8192 bytes).
DATA TYPES
There are only a few data types supported in the language. Reference
to attributes, numbers, and strings. Any data type can appear in
stand-alone condition, in which case they are evaluated as described in
"Simple conditions", above. They can also appear (with some exceptions
noted below) on the left-hand or on the right-hand side of a
comparison.
numbers
Numbers are composed of decimal digits. Floating point, hex,
and octal numbers are not supported. The maximum value for a
number is machine-dependent, but is usually 32-bits, including
one bit for a sign value.
word
Text that is not enclosed in quotes is interpreted differently
depending on where it occurs in a condition. On the left hand
side of a condition, it is interpreted as a reference to an
attribute. On the right hand side, it is interpreted as a
simple string, in the same manner as a single-quoted string.
Using attribute references permits limited type-specific
comparisons, as seen in the examples below.
if (User-Name == "bob") {
...
if (Framed-IP-Address > 127.0.0.1) {
...
if (Service-Type == Login-User) {
"strings"
Double-quoted strings are expanded by inserting the value of any
variables (see VARIABLES, below) before being evaluated. If the
result is a number it is evaluated in a numerical context.
String length is limited by line-length, usually about 8000
characters. A double quote character can be used in a string
via the normal back-slash escaping method. ("like \"this\" !")
’strings’
Single-quoted strings are evaluated as-is. Their values are not
expanded as with double-quoted strings above, and they are not
interpreted as attribute references.
‘strings‘
Back-quoted strings are evaluated by expanding the contents of
the string, as described above for double-quoted strings. The
resulting command given inside of the string in a sub-shell, and
taking the output as a string. This behavior is much the same
as that of Unix shells.
Note that for security reasons, the input string is split into
command and arguments before variable expansion is done.
For performance reasons, we suggest 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 you believe that you need to execute many programs,
we suggest finding alternative ways to achieve the same result.
In some cases, using a real language may be sufficient.
/regex/i
These strings are valid only on the right-hand side of a
comparison, and then only when the comparison operator is "=~"
or "!~". They are regular expressions, as implemented by the
local regular expression library on the system. This is usually
Posix regular expressions.
The trailing ’i’ is optional, and indicates that the regular
expression match should be done in a case-insensitive fashion.
If the comparison operator is "=~", then parantheses in the
regular expression will define variables containing the matching
text, as described below in the VARIABLES section.
VARIABLES
Run-time variables are referenced using the following syntax
%{Variable-Name}
Note that unlike C, there is no way to declare variables, or to refer
to them outside of a string context. All references to variables MUST
be contained inside of a double-quoted or back-quoted string.
Many potential variables are defined in the dictionaries that accompany
the server. These definitions define only the name and type, and do
not define the value of the variable. When the server receives a
packet, it uses the packet contents to look up entries in the
dictionary, and instantiates variables with a name taken from the
dictionaries, and a value taken from the packet contents. This process
means that if a variable does not exist, it is usually because it was
not mentioned in a packet that the server received.
Once the variable is instantiated, it is added to an appropriate
attribute list, as described below. In many cases, attributes and
variables are inter-changeble, and are often talked about that way.
However, variables can also refer to run-time calls to modules, which
may perform operations like SQL SELECTs, and which may return the
result as the value of the variable.
Referencing attribute lists
Attribute lists may be referenced via the following syntax
%{<list>:Attribute-Name}
Where <list> is one of "request", "reply", "control", "proxy-
request", "proxy-reply", or "outer.request", "outer.reply",
"outer.control", "outer.proxy-request", or "outer.proxy-reply".
just as with the "update" section, above. The "<list>:" prefix
is optional, and if omitted, is assumed to refer to the
"request" list.
When a variable 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. Some examples are:
%{User-Name}
%{request:User-Name} # same as above
%{reply:User-Name}
%{outer.reqest:User-Name} # from inside of a TTLS/PEAP tunnel
Results of regular expression matches
If a regular expression match has previously been performed,
then the special variable %{0} will contain a copy of the input
string. The variables %{1} through %{8} will contain the
substring matches, starting from the left-most parantheses, and
onwards. If there are more than 8 parantheses, the additional
results will not be placed into any variables.
Obtaining results from databases
It is useful to query a database for some information, and to
use the result in a condition. The following syntax will call a
module, pass it the given string, and replace the variable
reference with the resulting string returned from the module.
%{module: string ...}
The syntax of the string is module-specific. Please read the
module documentation for additional details.
Conditional Syntax
Conditional syntax similar to that used in Unix shells may also
be used.
%{%{Foo}:-bar}
If %{Foo} has a value, returns that value.
Otherwise, returns literal string "bar".
%{%{Foo}:-%{Bar}}
If %{Foo} has a value, returns that value.
Otherwise, returns the expansion of %{Bar}.
These conditional expansions can be nested to almost any
depth, such as with %{%{One}:-%{%{Two}:-%{Three}}}
String lengths and arrays
Similar to a Unix shell, there are ways to reference string
lenths, and the second or more instance of an attribute in a
list. If you need this functionality, we recommend using a real
language.
%{#string}
The number of characters in %{string}. If %{string} is
not set, then the length is not set.
e.g. %{#Junk-junk:-foo} will yeild the string "foo".
%{Attribute-Name#}
Will print the integer value of the attribute, rather
than a decoded VALUE or date. This feature applies only
to attributes of type "date", "integer", "byte", and
"short". It has no effect on any other attributes. It
is used when the numerical value is needed (e.g. Unix
seconds), rather than a humanly-readable string.
e.g. If a request contains "Service-Type = Login-User",
the expansion of %{Service-Type#} will yeild "1".
%{Attribute-Name[index]}
Reference the N’th occurance of the given attribute. The
syntax %{<list>:Attribute-Name[index]} may also be used.
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 variable %{Cisco-AVPair[2]} will reference the value
of the THIRD Cisco-AVPair attribute (if it exists) in the
request packet,
%{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.
For most requests, %{request:User-Name[#]} == 1
%{Attribute-Name[*]}
Expands to a single string, with the value of each array
member separated by a newline.
%{#Attribute-Name[index]}
Expands to the length of the string %{Attribute-
Name[index]}.
ATTRIBUTES
The attribute lists described above may be edited by listing one or
more attributes in an "update" section. Once the attributes have been
defined, they may be referenced as described above in the VARIABLES
section.
The following syntax defines attributes in an "update" section. Each
attribute and value has to be all on one line in the configuration
file. There is no need for commas or semi-colons after the value.
Attribute-Name = value
Attribute names
The Attribute-Name must be a name previously defined in a
dictionary. If an undefined name is used, the server will
return an error, and will not start.
Operators
The operator used to assign the value of the attribute may be
one of the following, with the given meaning.
= 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.
Enforcement and Filtering Operators
The following operators may also be used in addition to the ones
listed above. Their function is to perform enforcement or
filtering on attributes in a list.
-= Remove all matching attributes from the list. Both the
attribute name and value have to match in order for the
attribute to be removed from the list.
== Keep all matching attributes. Both the attribute name
and value have to match in order for the attribute to
remain in the list.
Note that this operator is very different than the ’=’
operator listed above!
<= Keep all attributes having values less than, or equal to,
the value given here. Any larger value is replaced by
the value given here. If no attribute exists, it is
added with the value given here, as with "+=".
This operator is valid only for attributes of integer
type.
>= Keep all attributes having values greater than, or equal
to, the value given here. Any larger value is replaced
by the value given here. If no attribute exists, it is
added with the value given here, as with "+=".
This operator is valid only for attributes of integer
type.
!* Delete all occurances of the named attribute, no matter
what the value.
Values
The format of the value is attribute-specific, and is usually a
string, integer, IP address, etc. Prior to the attribute being
instantiated, the value may be expanded as described above in
the DATA TYPES section, above. This flexibility means that, for
example, you can assign an IP address value to an attribute by
specifying the IP address directly, or by having the address
returned from a database query, or by having the address
returned as the output of a program that is executed.
When string values are finally assigned to a variable, they can
have a maximum length of 253 characters. This limit is due in
part to both protocol and internal server requirements. That
is, the strings in the language can be nearly 8k in length, say
for a long SQL query. However, the output of that SQL query
should be no more than 253 characters in length.
OTHER KEYWORDS
Other keywords in the language are taken from the names of modules
loaded by the server. These keywords are dependent on both the
modules, and the local configuration.
Some use keywords that are defined in the default configuration file
are:
fail Cause 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 faiures are not
catastrophic.
reject Causes the request to be immediately rejected
MODULE RETURN CODES
When a module is called, it returns one of the following codes to
"unlang", with the following meaning.
notfound information was not found
noop the module did nothing
ok the module succeeded
updated the module updated the request
fail the module failed
reject the module rejected the request
userlock the user was locked out
invalid the configuration was invalid
handled the module has handled the request itself
These return codes can be tested for in a condition, as described above
in the CONDITIONS section.
See also the file doc/configurable_failover for additional methods of
trapping and modifying module return codes.
FILES
/etc/raddb/radiusd.conf
SEE ALSO
radiusd.conf(5), dictionary(5)
AUTHOR
Alan DeKok <aland@deployingradius.com>
19 May 2010 unlang(5)