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

.Return: _size_

.Determining the length of fixed and variable length attributes
====
[source,unlang]
----
&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`.

.Return: _uint64_

.Generating a random number between 0 and 511
====
[source,unlang]
----
&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

[source,unlang]
----
%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

[source,unlang]
----
%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.

.Return: _string_

.Example

[source,unlang]
----
"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`.

.Return: _string_

.Example

[source,unlang]
----
"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.

.Return: _string_

.Example

[source,unlang]
----
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.

.Return: _string_

.Example

[source,unlang]
----
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.

[options="header,autowidth"]
|===
| 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.
|===

.Return: _string_

.Example

[source,unlang]
----
"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

[source,unlang]
----
&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 "+"
[source,unlang]
----
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( ... )`.

.Return: _the number exploded list of strings_.

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _octal_

.Example

[source,unlang]
----
&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.

.Return: _string_

.Example

[source,unlang]
----
&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.

.Return: _string_.

.Example

[source,unlang]
----
&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.

.Return: _string_.

.Example

[source,unlang]
----
&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.

.Return: _octal_

.Example

[source,unlang]
----
&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.

.Return: _octal_

.Example

[source,unlang]
----
&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.

.Return: _binary data_

.Example

[source,unlang]
----
&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( ... )`

.Return: _octal_

.Example

[source,unlang]
----
&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 xref:xlat/builtin.adoc#_0_32[above].

Every time a regular expression is evaluated, whether it matches or not,
the named capture group values will be cleared.

[NOTE]
====
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.

.Return: _data_

.Example

[source,unlang]
----
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`).

.Return: _string_

.Example

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

[source,unlang]
----
&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.

.Return: _string_.

.Example

[source,unlang]
----
&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

[source,unlang]
----
&Acct-Start-Time := %time(now)
----

[NOTE]
====
This expansion should be used in preference to the xref:xlat/character.adoc[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"
[source,unlang]
----
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"
[source,unlang]
----
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.

[source,unlang]
----
%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.

[source,unlang]
----
%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 `%{ ... }`.

[source,unlang]
----
&reply.Reply-Message := "The 1 + 2 = %{1 + 2}"
----

=== %integer(..)

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

[source,unlang]
----
&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`.

.Return: _octets_

.Example

[source,unlang]
----
&reply.Class := "%pack(%{reply.Framed-IP-Address}%{NAS-IP-Address}}"
----

It is easier to just use casting and string append:

.Better example

[source,unlang]
----
&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.

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

=== %strlen( ... )

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



// Copyright (C) 2023 Network RADIUS SAS.  Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
