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

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

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)

The current time can also be compared to a known date:

Example
if (%time() < (date) 'Aug 1 2023 01:02:03 UTC') {
	...
}

The format of the date strings should be the same format as the server prints out. The parse will try to accept other date formats (raw integer, etc.), but not all formats are guaranteed to work. There are hundreds of different date formats used across the world, and the server cannot support them all.

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 functions (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.