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".
&Tmp-String-0 := "Caipirinha"
&Framed-IP-Address := 192.0.2.1
&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)"
}
The length of Caipirinha is 10 The length of 192.168.0.2 is 4
Encoders and Decoders
The server supports manual encoding and decoding of a variety of protocols. These expansions can be used to manually encode and decode raw data.
In general, however, we recommend updating the dictionaries to automatically handle the relevant data. The dictionaries support structures, bit fields, and many many more features than previous versions of FreeRADIUS. There are few situations where manual encoding and decoding is necessary.
That being said, the main use of these expansions is for the "internal" protocol. This is a virtual protocol which can encode and decode any attribute from any protocol.
If you need to store attributes in an external database, then it is
possible to encode them via %internal.encode(…)
. The result will
be an opaque hex string which can be treated as an opaque blob, and
stored externally. Then, when the data is needed again, it can be
turned back into attributes via %internal.decode(…)
.
%PROTO.decode(<data>)
Decodes data as the named protocol. The data string can be an expansion, which is usually a reference to an attribute of type `octets.
The PROTO.decode
expansion is automatically registered for every
protocol which is used by the server.
Note that the output attributes must come from the same dictionary
as the request
they are being added to. For example, you cannot use
dhcpv4.decode
inside of a virtual server which has namespace =
radius
. Doing so would result in DHCPv4 attributes being inside of a
RADIUS virtual server, which is not allowed.
It returns the number of attributes which were decoded.
%dhcpv4.decode(0x520d0103abcdef0206010203040506)
%radius.decode(0x010641424344)
&Relay-Agent-Information.Circuit-Id = 0xabcdef, &Relay-Agent-Information.Remote-Id = 0x010203040506
&User-Name = "ABCD"
%PROTO.encode(<list>)
Encodes list as the named protocol. The list can also be a series of attributes.
The PROTO.encode
expansion is automatically registered for every
protocol which is used by the server.
It returns the raw encoded data
%dhcpv4.encode(&Relay-Agent-Information.Circuit-Id = 0xabcdef &Relay-Agent-Information.Remote-Id = 0x010203040506)
%radius.encode(&User-Name = "ABCD")
0x520d0103abcdef0206010203040506
0x010641424344
Interpreter State
The state of the interpreter can be queried via the
%interpeter(<name>)
expansion. The individual expansions are
documented below.
Each expansion given here can be prefixed with one or more dot (.
)
characters. These dots allow the expansion to refer to the current
request via a name
, or the parent request via .name
. If there is
no parent, the expansion returns the string <underflow>
.
Server Configuration
%config(<key>)
Refers to a variable in the configuration file. See the documentation on configuration file references.
"Server installed in %config('prefix')"
"Module rlm_exec.shell_escape = %config('modules.exec.shell_escape')"
Server installed in /opt/freeradius
Module rlm_exec.shell_escape = yes
%client(<key>)
Refers to a variable that was defined in the client section for the
current client. See the sections client { … }
in clients.conf
.
"The client ipaddr is %client(ipaddr)"
The client ipaddr is 192.168.5.9
%debug(<level>)
Dynamically change the debug level to something high, recording the old level.
recv Access-Request {
if (&request.User-Name == "bob") {
"%debug(4)"
} else {
"%debug(0)"
}
...
}
...
(0) recv Access-Request {
(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_attr(<list:[index]>)
Print to debug output all instances of current attribute, or all attributes in a list. expands to a zero-length string.
recv Access-Request {
if (&request.User-Name == "bob") {
"%debug_attr(request[*])"
}
...
}
...
(0) recv Access-Request {
(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") (...)
...
%interpreter(<state>)
Get information about the interpreter state.
State | Description |
---|---|
|
Name of the instruction. |
|
Unlang type. |
|
How deep the current stack is. |
|
Line number of the current section. |
|
Filename of the current section. |
"Failure in test at line %interpreter(...filename):%interpreter(...line)"
Failure in test at line /path/raddb/sites-enaled/default:231
String manipulation
%concat(<&ref:[idx]>, <delim>)
Used to join two or more attributes, separated by an optional delimiter.
In most cases, %concat(…)
is only useful inside of a dynamically
expanded string. If you need to concatenate strings together in a policy, just use +
.
&control += {
&Tmp-String-0 = "aaa"
&Tmp-String-0 = "bb"
&Tmp-String-0 = "c"
}
&reply += {
&Reply-Message = "%concat(%{control.Tmp-String-0[*]}, ', ')"
&Reply-Message = "%concat(%{control.Tmp-String-0[*]}, ',')"
}
aaa, bb, c
aaa,bb,c
string foo
&foo += { "a", "c", "c", "d" } # abcd
&foo += &control.Tmp-String-0[*]
%explode(<&ref>, <delim>)
Split an string into multiple new strings based on a delimiter.
This expansion is the opposite of %concat( … )
.
&control.Tmp-String-0 := "bob.toba@domain.com"
&control.Tmp-String-1 := "%explode(&control.Tmp-String-0, '@')"
&reply.Reply-Message := "Welcome %{control.Tmp-String-1[0]}"
Welcome bob.toba
%lpad(<string>, <val>, <char>)
Left-pad a string.
&control.Tmp-String-0 := "123"
&reply.Reply-Message := "Maximum should be %lpad(%{control.Tmp-String-0}, 11, '0')"
Maximum should be 00000000123
%rpad(<string>, <val>, <char>)
Right-pad a string.
&control.Tmp-String-0 := "123"
&reply.Reply-Message := "Maximum should be %rpad(%{control.Tmp-String-0}, 11, '0')"
Maximum should be 12300000000
%pairs(<list>.[*])
Serialize attributes as comma-delimited string.
&control.Tmp-String-0 := { "This is a string", "This is another one" }
&reply.Reply-Message := "Serialize output: %pairs(&control.[*])"
Serialize output: Tmp-String-0 = "\"This is a string\", Tmp-String-0 = \"This is another one\""
%randstr( …)
Get random string built from character classes.
&reply.Reply-Message := "The random string output is %randstr(aaaaaaaa}"
The random string output is 4Uq0gPyG
%tolower( … )
Dynamically expands the string and returns the lowercase version of it. This definition is only available in version 2.1.10 and later.
&control.Tmp-String-0 := "CAIPIRINHA"
&reply.Reply-Message := "tolower of %{control.Tmp-String-0} is %tolower(%{control.Tmp-String-0})"
tolower of CAIPIRINHA is caipirinha
%toupper( … )
Dynamically expands the string and returns the uppercase version of it. This definition is only available in version 2.1.10 and later.
&control.Tmp-String-0 := "caipirinha"
&reply.Reply-Message := "toupper of %{control.Tmp-String-0} is " + %toupper(%{control.Tmp-String-0})
toupper of caipirinha is CAIPIRINHA
Data Conversion
%base64.encode( … )
Encode a string using Base64.
&control.Tmp-String-0 := "Caipirinha"
&reply.Reply-Message := "The base64 of %{control.Tmp-String-0} is %base64.encode(%{control.Tmp-String-0})"
The base64 of foo is Q2FpcGlyaW5oYQ==
%base64.decode( … )
Decode a string previously encoded using Base64.
&control.Tmp-String-0 := "Q2FpcGlyaW5oYQ=="
&reply.Reply-Message := "The base64.decode of %{control.Tmp-String-0} is %base64.decode(%{control.Tmp-String-0})"
The base64.decode of Q2FpcGlyaW5oYQ== is Caipirinha
%bin( … )
Convert string to binary.
&control.Tmp-String-0 := "10"
&reply.Reply-Message := "The %{control.Tmp-String-0} in binary is %bin(%{control.Tmp-String-0})"
The 10 in binary is \020
%hex( … )
Convert to hex.
&control.Tmp-String-0 := "12345"
&reply.Reply-Message := "The value of %{control.Tmp-String-0} in hex is %hex(%{control.Tmp-String-0})"
The value of 12345 in hex is 3132333435
%urlquote( … )
Quote URL special characters.
&control.Tmp-String-0 := "http://example.org/"
&reply += {
&Reply-Message = "The urlquote of %{control.Tmp-String-0} is %urlquote(%{control.Tmp-String-0})"
}
The urlquote of http://example.org/ is http%3A%2F%2Fexample.org%2F
%urlunquote( … )
Unquote URL special characters.
&control.Tmp-String-0 := "http%%3A%%2F%%2Fexample.org%%2F" # Attention for the double %.
&reply += {
&Reply-Message = "The urlunquote of %{control.Tmp-String-0} is %urlunquote(%{control.Tmp-String-0})"
}
The urlunquote of http%3A%2F%2Fexample.org%2F is http://example.org/
Hashing and Encryption
%hmacmd5(<shared_key> <string>)
Generate HMAC-MD5
of string.
&control.Tmp-String-0 := "mykey"
&control.Tmp-String-1 := "Caipirinha"
&reply.control.Tmp-Octets-0 := "%hmacmd5(%{control.Tmp-String-0} %{control.Tmp-String-1})"
&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)"
}
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
%hmacsha1(<shared_key>, <string>)
Generate HMAC-SHA1
of string.
&control.Tmp-String-0 := "mykey"
&control.Tmp-String-1 := "Caipirinha"
&control.Tmp-Octets-0 := "%hmacsha1(%{control.Tmp-String-0}, %{control.Tmp-String-1})"
&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}"
}
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
%md5( … )
Dynamically expands the string and performs an MD5 hash on it. The result is binary data.
&control.Tmp-String-0 := "Caipirinha"
&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}))"
}
md5 of Caipirinha is octal=\024\204\013md||\230\243\3472\3703\330n\251
md5 of Caipirinha is hex=14840b6d647c7c98a3e732f833d86ea9
Other Hashing Functions
The following hashes are supported for all versions of OpenSSL.
-
%md2( … }
-
%md4( … }
-
%md5( … }
-
%sha1( … }
-
%sha224( … }
-
%sha256( … }
-
%sha384( … }
-
%sha512( … }
The following hashes are supported for when OpenSSL 1.1.1 or greater
is installed. This version adds support for the sha3
and blake
families of digest functions.
-
%blake2s_256( … )
-
%blake2b_512( … )
-
%sha2_224( … )
-
%sha2_256( … )
-
%sha2_384( … )
-
%sha2_512( … )
-
%sha3_224( … )
-
%sha3_256( … )
-
%sha3_384( … )
-
%sha3_512( … )
&control.Tmp-String-0 := "Caipirinha"
&reply += {
&Reply-Message = "The md5 of %{control.Tmp-String-0} in octal is %md5(%{control.Tmp-String-0}}"
&Reply-Message = "The md5 of %{control.Tmp-String-0} in hex is %hex(%md5(%{control.Tmp-String-0}}}"
}
The md5 of Caipirinha in octal is \024\204\013md||\230\243\3472\3703\330n\251
The md5 of Caipirinha in hex is 14840b6d647c7c98a3e732f833d86ea9
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 above.
Every time a regular expression is evaluated, whether it matches or not, the named capture group values will be cleared.
This expansion is only available if the server is built with libpcre or libpcre2.
Use the output of ... 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 |
%eval(<string>)
Evaluates the string as an expansion, and returns the result. The main difference between using this expansion and just using %{…}
is that the string being evaluated can be dynamically changed.
if (&User-Name == "bob") {
&request.Tmp-String-0 := "&User-Name"
} else {
&request.Tmp-String-0 := "not bob!"
}
&reply.Reply-Message := "%eval(&request.Tmp-String-0}"
&User-Name == bob
bob
&User-Name == not bob
not bob!
%nexttime(<time>)
Calculate number of seconds until next n hour(s
), day(s
), week(s
), year(s
).
With the current time at 16:18, %nexttime(1h)
will expand to 2520
.
&reply.Reply-Message := "You should wait for %nexttime(1h)s"
You should wait for 2520s
%sub(<subject>, /<regex>/[flags], <replace>)
Substitute text just as easily as it can match it, even using regex patterns.
&control.Tmp-String-0 := "Caipirinha is a light and refreshing drink!"
&reply.Reply-Message := "%sub(%{control.Tmp-String-0}, / /, ',')"
Caipirinha,is,a,light,and,refreshing,drink!
%time()
Return the current time.
If no argument is passed, it returns the current time. Otherwise if the argument is:
-
dst
- returns abool
indicating whether or not the system is running in daylight savings time. -
mday_offset
- returns thetime_delta
offset since the start of the month. Use%d
to get an integer representing the day of the month. -
now
- returns the current time -
offset
- returns atime_delta
of the current time zone offset. This value may be negative. -
request
- returns the time at which the request packet was received (always less thannow
!) -
wday_offset
- returns thetime_delta
offset since the start of the week. -
any other string is parsed as type
date
, using the normal date parsing routines.
&Acct-Start-Time := %time(now)
This expansion should be used in preference to the single letter expansions |
Due to limitations in the underlying time funtions (both system and FreeRADIUS), previous versions of FreeRADIUS did not always handle dates correctly. When print dates, the time zone information would sometimes not be printed, or the time zone would sometimes be ignored when parsed a date from a string.
Even if the time zone was used, the nature of time zones means that
there may be duplicate time zone names! For example, the time zone
CST
has three separate (and different) definitions.
The server now tracks all times internally as UTC, and by default prints times as UTC, or prints the time zone as a decimal offset from UTC, instead of printing an ambiguous name.
This handling of time zones has some minor side effects. When calculating values like "tomorrow", the default is to return the UTC version of "tomorrow". This value may not be what you want.
In order to correctly calculate the local value of "tomorrow", it is necessary to add the local time zone offset to the UTC time.
Note that the server will automatically determine (and use) any
daylight savings time differences. So the value of %time(offset)
may change while the server is running!
The following example calculates the correct value of "tomorrow" in UTC by using the following steps:
-
taking the current time of the request
-
calculating how long it has been since the start of the day as a
time_delta
-
subtracting that
time_delta
from the current time
group {
date now
date tomorrow
time_delta time_of_day
&now := %time('request')
# We are this many seconds into one day
&time_of_day := &now % (time_delta) 1d
# calculate the start of today, and then add one day to that
&tomorrow := &now - &time_of_day + (time_delta) 1d
}
The following example calculates the correct value of "tomorrow" in local time by using the preceding example, but then adding the local time zone offset to the final value.
group {
date now
date tomorrow
time_delta time_of_day
&now := %time('request')
# We are this many seconds into one day
&time_of_day := &now % (time_delta) 1d
# calculate the start of today, and then add one day to that
&tomorrow := &now - &time_of_day + (time_delta) 1d
# add in the time zone offset
&tomorrow += %time('offset')
}
This kind of math works well for "tomorrow", but it is less useful for
"next week Monday", or "start of next month". The %nexttime(…)
expansion above should be used for those time operations.
Logging
%log.debug(<string>)
Logs a message at a DEBUG level. This function returns nothing.
%log.debug("Now processing %interpreter(...filename):%interpreter(...line)")
The DEBUG messages are printed only when the server has the debug flag set.
%log.info(<string>)
Logs a message at a INFO level. This function returns nothing.
%log.info("Doing something useful now")
The INFO messages are always logged. We suggest using these messages only to log special or unusual events. Producing multiple log messages per packet is not recommended, and can have a surprisingly large (and negative) impact on performance.
Deprecated or removed Expansions.
%expr(<string>)
This expansion has been removed. Instead, just use %{ … }
.
&reply.Reply-Message := "The 1 + 2 = %{1 + 2}"
%integer(..)
This expansion has been removed. Instead, just use %{(integer) …}
with math inside of the brackets.
&reply.Reply-Message := "Value of Service-Type is %{(integer) &Service-Type}"
%pack(%{Attribute-Name}%{Attribute-Name}...)
Pack multiple multiple attributes and/or literals into a binary string.
For best results, each attribute passed to pack
should be fixed size.
That is, not data type octets
or string
as the length of those values
will not be encoded.
See also the unpack
module, which is the inverse to pack
.
&reply.Class := "%pack(%{reply.Framed-IP-Address}%{NAS-IP-Address}}"
It is easier to just use casting and string append:
&reply.Class := (octets) &Framed-IP-Address + (octets) &NAS-IP-Address.