OUR SITES NetworkRADIUS FreeRADIUS

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".

Example 1. Determining the length of fixed and variable length attributes
&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)"
}
Output
The length of Caipirinha is 10
The length of 192.168.0.2 is 4

%rand(<number>)

Generate random number from 0 to <number>-1.

Example 2. Generating a random number between 0 and 511
&reply.Reply-Message := "The random number is %rand(512}"
Output
The random number is 347

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.

Return: integer

It returns the number of attributes which were decoded.

Example
%dhcpv4.decode(0x520d0103abcdef0206010203040506)
%radius.decode(0x010641424344)
Output
&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.

Return: octets

It returns the raw encoded data

Example
%dhcpv4.encode(&Relay-Agent-Information.Circuit-Id = 0xabcdef  &Relay-Agent-Information.Remote-Id = 0x010203040506)
%radius.encode(&User-Name = "ABCD")
Output
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>.

%interpeter('module')

The current module being executed. If the expansions is done in an unlang statement and outside of any module, it returns the name of the previous module which was executed.

%interpeter('processing_stage')

Which section of a virtual server is processing the request.

%interpeter('rcode')

The current interpreter return code, e.g. handle, or ok, etc.

%interpeter('server')

The name of the virtual server which is running the request.

Server Configuration

%config(<key>)

Refers to a variable in the configuration file. See the documentation on configuration file references.

Example
"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

%client(<key>)

Refers to a variable that was defined in the client section for the current client. See the sections client { …​ } in clients.conf.

Example
"The client ipaddr is %client(ipaddr)"
Output
The client ipaddr is 192.168.5.9

%debug(<level>)

Dynamically change the debug level to something high, recording the old level.

Example
recv Access-Request {
    if (&request.User-Name == "bob") {
        "%debug(4)"
    } else {
        "%debug(0)"
    }
    ...
}
Output (extra informations only for that condition)
...
(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.

Example
recv Access-Request {
    if (&request.User-Name == "bob") {
        "%debug_attr(request[*])"
    }
    ...
}
Output
...
(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

Name of the instruction.

type

Unlang type.

depth

How deep the current stack is.

line

Line number of the current section.

filename

Filename of the current section.

Example
"Failure in test at line %interpreter(...filename):%interpreter(...line)"
Output
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.

Return: string

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 +.

Example
&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[*]}, ',')"
}
Output
aaa, bb, c
aaa,bb,c
Using "+"
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( …​ ).

Example
&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]}"
Output
Welcome bob.toba

%lpad(<string>, <val>, <char>)

Left-pad a string.

Example
&control.Tmp-String-0 := "123"

&reply.Reply-Message := "Maximum should be %lpad(%{control.Tmp-String-0}, 11, '0')"
Output
Maximum should be 00000000123

%rpad(<string>, <val>, <char>)

Right-pad a string.

Example
&control.Tmp-String-0 := "123"

&reply.Reply-Message := "Maximum should be %rpad(%{control.Tmp-String-0}, 11, '0')"
Output
Maximum should be 12300000000

%pairs(<list>.[*])

Serialize attributes as comma-delimited string.

Example
&control.Tmp-String-0 := { "This is a string", "This is another one" }
&reply.Reply-Message := "Serialize output: %pairs(&control.[*])"
Output
Serialize output: Tmp-String-0 = "\"This is a string\", Tmp-String-0 = \"This is another one\""

%randstr( …​)

Get random string built from character classes.

Example
&reply.Reply-Message := "The random string output is %randstr(aaaaaaaa}"
Output
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.

Example
&control.Tmp-String-0 := "CAIPIRINHA"
&reply.Reply-Message := "tolower of %{control.Tmp-String-0} is %tolower(%{control.Tmp-String-0})"
Output
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.

Example
&control.Tmp-String-0 := "caipirinha"
&reply.Reply-Message := "toupper of %{control.Tmp-String-0} is " + %toupper(%{control.Tmp-String-0})
Output
toupper of caipirinha is CAIPIRINHA

Data Conversion

%base64.encode( …​ )

Encode a string using Base64.

Example
&control.Tmp-String-0 := "Caipirinha"
&reply.Reply-Message := "The base64 of %{control.Tmp-String-0} is %base64.encode(%{control.Tmp-String-0})"
Output
The base64 of foo is Q2FpcGlyaW5oYQ==

%base64.decode( …​ )

Decode a string previously encoded using Base64.

Example
&control.Tmp-String-0 := "Q2FpcGlyaW5oYQ=="
&reply.Reply-Message := "The base64.decode of %{control.Tmp-String-0} is %base64.decode(%{control.Tmp-String-0})"
Output
The base64.decode of Q2FpcGlyaW5oYQ== is Caipirinha

%bin( …​ )

Convert string to binary.

Example
&control.Tmp-String-0 := "10"
&reply.Reply-Message := "The %{control.Tmp-String-0} in binary is %bin(%{control.Tmp-String-0})"
Output
The 10 in binary is \020

%hex( …​ )

Convert to hex.

Example
&control.Tmp-String-0 := "12345"
&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

%urlquote( …​ )

Quote URL special characters.

Example
&control.Tmp-String-0 := "http://example.org/"
&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

%urlunquote( …​ )

Unquote URL special characters.

Example
&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})"
}
Output
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.

Example
&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)"
}
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

%hmacsha1(<shared_key>, <string>)

Generate HMAC-SHA1 of string.

Example
&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}"
}
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

%md5( …​ )

Dynamically expands the string and performs an MD5 hash on it. The result is binary data.

Example
&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}))"
}
Output
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( …​ )

Example
&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}}}"
}
Output
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 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

%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.

Example
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}"
Output when &User-Name == bob
bob
Output when &User-Name == not bob
not bob!

%nexttime(<time>)

Calculate number of seconds until next n hour(s), day(s), week(s), year(s).

Example

With the current time at 16:18, %nexttime(1h) will expand to 2520.

&reply.Reply-Message := "You should wait for %nexttime(1h)s"
Output
You should wait for 2520s

%sub(<subject>, /<regex>/[flags], <replace>)

Substitute text just as easily as it can match it, even using regex patterns.

Example
&control.Tmp-String-0 := "Caipirinha is a light and refreshing drink!"
&reply.Reply-Message := "%sub(%{control.Tmp-String-0}, / /, ',')"
Output
Caipirinha,is,a,light,and,refreshing,drink!

%time()

Return the current time.

Return: date.

If no argument is passed, it returns the current time. Otherwise if the argument is:

  • dst - returns a bool indicating whether or not the system is running in daylight savings time.

  • mday_offset - returns the time_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 a time_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 than now!)

  • wday_offset - returns the time_delta offset since the start of the week.

  • any other string is parsed as type date, using the normal date parsing routines.

Example
&Acct-Start-Time := %time(now)

This expansion should be used in preference to the single letter expansions %l. That expansion returns integer seconds, and is not suitable for millisecond or microsecond resolution.

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

Example Calculating the UTC value of "tomorrow"
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.

Example Calculating the local value of "tomorrow"
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.

Example
&reply.Class := "%pack(%{reply.Framed-IP-Address}%{NAS-IP-Address}}"

It is easier to just use casting and string append:

Better example
&reply.Class := (octets) &Framed-IP-Address + (octets) &NAS-IP-Address.

%string(…​)

This expansion has been removed. Instead, just use %{(string) …​} with math inside of the brackets.

&reply.Reply-Message := "The printable version of Class is %{(string) &Class}"

%strlen( …​ )

This expansion is deprecated and will be removed. Just use %length(…​) instead.