perl -p -i -e 's/\`&(?!\`)/\`/g' $(git grep -lE '&[a-zA-Z0-9]' doc/antora | egrep -v 'raddb|developers|interpreter|assets')
password "encryption" mechanism has been used (crypt, MD5, SHA, SSHA2,
SHA3, etc). All that is necessary is that the
xref:reference:raddb/mods-available/ldap.adoc[ldap module] be configured to map
-the `userPassword` LDAP field to the `&control.Password.With-Header`
+the `userPassword` LDAP field to the `control.Password.With-Header`
attribute in FreeRADIUS. FreeRADIUS will then "do the right thing" to
authenticate the user.
password "encryption" mechanism has been used (crypt, MD5, SHA, SSHA2,
SHA3, etc). All that is necessary is that the
xref:reference:raddb/mods-available/ldap.adoc[ldap module] be configured to map
-the `userPassword` LDAP field to the `&control:Password-With-Header`
+the `userPassword` LDAP field to the `control:Password-With-Header`
attribute in FreeRADIUS. FreeRADIUS will then "do the right thing" to
authenticate the user.
## Logging the initial EAP request
-The `&session-state:` list can be used to determine whether the
+The `session-state:` list can be used to determine whether the
incoming request is the first in an EAP transaction, as on the
first packet the list will be empty. Update the list at the same
time to ensure that it is not empty on the next round; we can use
There is no plan to require prefixes here.
As of v3, the preferred format for `unknown` attributes is
-`&Attr-oid.oid.oid`, e.g. `&Attr-26.11344.255`. However, v3 would
+`Attr-oid.oid.oid`, e.g. `Attr-26.11344.255`. However, v3 would
still parse (but not generate) attributes of the form
`Vendor-FreeRADIUS-Attr-255`. The `Vendor-` syntax has been removed in
version 4. The server would never produce such names, and allowing
=== rlm_cache
-`&control.Cache-Merge` has been renamed to
-`&control.Cache-Merge-New` and controls whether new entries are merged
+`control.Cache-Merge` has been renamed to
+`control.Cache-Merge-New` and controls whether new entries are merged
into the current request. It defaults to `no`. The primary use case,
is if you’re using xlat expansions in the cache module itself to
retrieve information for caching, and need the result of those
expansions to be available immediately.
-Two new control attributes `&control.Cache-Allow-Merge` and
-`&control.Cache-Allow-Insert` have been added. These control whether
+Two new control attributes `control.Cache-Allow-Merge` and
+`control.Cache-Allow-Insert` have been added. These control whether
existing entries are to be merged, and new entries created on the next
call to a cache module instance. Both default to `yes`.
=== rlm_eap
-All certificate attributes are available in the `&session-state.`
+All certificate attributes are available in the `session-state.`
list, immediately after they are parsed from their ASN1 form.
-The certificates are no longer added to the `&request.` list. Instead,
+The certificates are no longer added to the `request.` list. Instead,
they are added to the `session-state` list. You are advised to update
any references during the upgrade to 4.0:
=== rlm_rest
-`REST-HTTP-Code` is now inserted into the `&request.` list instead
-of the `&reply.` list, to be compliant with the
+`REST-HTTP-Code` is now inserted into the `request.` list instead
+of the `reply.` list, to be compliant with the
https://wiki.freeradius.org/guide/List-Usage[list usage] guidelines.
=== rlm_sql
`control` list after the module has run. This means it is possible to
do regular expression comparison on group names.
-It also means that any comparison of `&SQL-Group == "foo"` has to be
-updated to use `&control.SQL-Group == "foo"` instead.
+It also means that any comparison of `SQL-Group == "foo"` has to be
+updated to use `control.SQL-Group == "foo"` instead.
This caching also means that the group comparison will be done
internally, and will not result in a database lookup. This also means
This allows significantly greater flexibility, and better integration
with newer features in the server such as CoA, where reply_name can now
-be `&coa:Session-Timeout`. That allows the server to send a CoA packet
+be `coa:Session-Timeout`. That allows the server to send a CoA packet
which updates the `Session-Timeout` for the user.
In v4, when the `key` field was set to `User-Name`, the module would
Many "virtual" or "fake" attributes have been removed or renamed.
-`&Module-Return-Code` should be replaced by `%interpreter(rcode)`.
+`Module-Return-Code` should be replaced by `%interpreter(rcode)`.
-`&Response-Packet-Type` should be replaced by `&reply.Packet-Type`.
+`Response-Packet-Type` should be replaced by `reply.Packet-Type`.
-`&Virtual-Server` should be replaced by `%interpreter(server)`.
+`Virtual-Server` should be replaced by `%interpreter(server)`.
-`&Packet-Authentication-Vector` should be replaced by `%radius.packet.vector()`.
+`Packet-Authentication-Vector` should be replaced by `%radius.packet.vector()`.
-`&Packet-Dst-IP-Address` and `&Packet-Dst-IPv6-Address` should be replaced by `&Net.Dst.IP`.
+`Packet-Dst-IP-Address` and `Packet-Dst-IPv6-Address` should be replaced by `Net.Dst.IP`.
-`&Packet-Dst-Port` should be replaced by `&Net.Dst.Port`.
+`Packet-Dst-Port` should be replaced by `Net.Dst.Port`.
-`&Packet-Src-IP-Address` and `&Packet-Src-IPv6-Address` should be replaced by `&Net.Src.IP`.
+`Packet-Src-IP-Address` and `Packet-Src-IPv6-Address` should be replaced by `Net.Src.IP`.
-`&Packet-Src-Port` should be replaced by `&Net.Src.Port`.
+`Packet-Src-Port` should be replaced by `Net.Src.Port`.
== Recommended Changes
*** the password which it placed into `control.Password.With-Header`
*** as RADIUS attributes were changed, it returns `updated` as a result code to unlang
. the module `pap` was used
- ** it found a suitable password to use in `&Password.With-Header`
- *** populates `&control.Password.Cleartext`
- *** the module decides it has everything it needs to do authentication so sets `&control.Auth-Type = pap`
+ ** it found a suitable password to use in `Password.With-Header`
+ *** populates `control.Password.Cleartext`
+ *** the module decides it has everything it needs to do authentication so sets `control.Auth-Type = pap`
*** as RADIUS attributes were changed, it returns `updated` as a result code to unlang
- . the authenticate section runs and hands off to `pap` as `&control.Auth-Type = pap` was set earlier
- ** `&control.Password.Cleartext` is compared to `&request.User-Password`
+ . the authenticate section runs and hands off to `pap` as `control.Auth-Type = pap` was set earlier
+ ** `control.Password.Cleartext` is compared to `request.User-Password`
** matches so `ok` is returned
. we return `Access-Accept` as `ok` was returned to unlang
** found `uid=john,ou=people,dc=example,dc=com`
** did *not* find any useful attributes associated with that user
** module was successful in operation, but changed no RADIUS attributes so returns `ok`
- . `&control.Auth-Type := ldap` was set as the `ldap` module was successful in finding a user
+ . `control.Auth-Type := ldap` was set as the `ldap` module was successful in finding a user
. the module `expiration` was used, but it had no effect (`noop`)
. the module `pap` was used
** it finds no suitable password RADIUS attributes to use
** as it makes no changes, the module returns `noop`
- . the authenticate section runs and hands off to `ldap` as `&control.Auth-Type = ldap` was set earlier
+ . the authenticate section runs and hands off to `ldap` as `control.Auth-Type = ldap` was set earlier
** attempts to LDAP bind as `uid=john,ou=people,dc=example,dc=com`
** successful so `ok` is returned
. we return `Access-Accept` as `ok` was returned to unlang
No matter how the LDAP module is called (via its `authorize`, `authenticate`,
`accounting` methods or the `%ldap.group()` xlat) the first operation the
-module performs it to populate `&control.LDAP-UserDN` with the location of
+module performs it to populate `control.LDAP-UserDN` with the location of
the authenticating user's object in LDAP.
Below is an example of configuring a user section for OpenLDAP, with callouts
| Purpose | `ldap { user { ... } }` config item
| Specify where to search for users | ```base_dn = '<user_base_dn>'```
| Specify how to find a user | ```filter = "(&(<user_filter>)(<user_uid_attribute>=%{&Stripped-User-Name || &User-Name)"```
-| Retrieve a "known good" password | ```&control.Password.With-Header = <user_password_attribute>```
+| Retrieve a "known good" password | ```control.Password.With-Header = <user_password_attribute>```
| Allow accounts to be explicitly disabled | ```access_attribute = '<user_access_disabled_attribute>'``` +
```access_positive = 'no'```
| Require accounts to be explicitly enabled | ```access_attribute = '<user_access_enabled_attribute>'``` +
*** as RADIUS attributes were changed, it returns `updated` as a result code to unlang
. the modules `expiration` and `logintime` were used, but both had no effect (`noop`)
. the module `pap` was used
- ** it found a suitable password to use in `&Password-With-Header`
- *** populates `&control:Cleartext-Password`
- *** the module decides it has everything it needs to do authentication so sets `&control:Auth-Type = pap`
+ ** it found a suitable password to use in `Password-With-Header`
+ *** populates `control:Cleartext-Password`
+ *** the module decides it has everything it needs to do authentication so sets `control:Auth-Type = pap`
*** as RADIUS attributes were changed, it returns `updated` as a result code to unlang
- . the authenticate section runs and hands off to `pap` as `&control:Auth-Type = pap` was set earlier
- ** `&control:Cleartext-Password` is compared to `&request:User-Password`
+ . the authenticate section runs and hands off to `pap` as `control:Auth-Type = pap` was set earlier
+ ** `control:Cleartext-Password` is compared to `request:User-Password`
** matches so `ok` is returned
. we return `Access-Accept` as `ok` was returned to unlang
** found `uid=john,ou=people,dc=example,dc=com`
** did *not* find any useful attributes associated with that user
** module was successful in operation, but changed no RADIUS attributes so returns `ok`
- . `&control:Auth-Type := ldap` was set as the `ldap` module was successful in finding a user
+ . `control:Auth-Type := ldap` was set as the `ldap` module was successful in finding a user
. the modules `expiration` and `logintime` were used, but both had no effect (`noop`)
. the module `pap` was used
** it finds no suitable password RADIUS attributes to use
** as it makes no changes, the module returns `noop`
- . the authenticate section runs and hands off to `ldap` as `&control:Auth-Type = ldap` was set earlier
+ . the authenticate section runs and hands off to `ldap` as `control:Auth-Type = ldap` was set earlier
** attemps to LDAP bind as `uid=john,ou=people,dc=example,dc=com`
** successful so `ok` is returned
. we return `Access-Accept` as `ok` was returned to unlang
The attribute to set is the one referred to in the `check_name` module,
configuration, e.g. on the sample `dailycounter` module instance the attribute
-is `&control.Max-Daily-Session`.
+is `control.Max-Daily-Session`.
In the authorization policy, call the required `sqlcounter` module instance,
having made sure that the appropriate `check_name` attribute is set.
----
The output strings here (with casting) are the same as for the
-previous example. Note that we do not have to cast `&User-Name`,
+previous example. Note that we do not have to cast `User-Name`,
because it is already a string.
.Example of casting to 'octets'
"User-Name is %{(string) &Tmp-Octets-0}"
----
-if the `&Tmp-Octets-0` attribute has value `0x666f6f` (`foo`)
+if the `Tmp-Octets-0` attribute has value `0x666f6f` (`foo`)
In the first expansion, the resulting output is `User-Name is
0x666f6f`. In the second expansion, the resulting output is
&Attribute-Name
----
-The `&Attribute-Name` operator returns a reference to the named
+The `Attribute-Name` operator returns a reference to the named
attribute.
When used as an existence check in a condition, the condition
This syntax is most useful in xref:xlat/attribute.adoc[xlat attribute references].
-Note that the old syntax of `&request[...]` is disallowed.
+Note that the old syntax of `request[...]` is disallowed.
== Parent / child references
(condition-1 && condition-2)
----
-The `&&` operator performs a short-circuit "and" evaluation of the
+The `&` operator performs a short-circuit "and" evaluation of the
two conditions. This operator evaluates _condition-1_ and returns
`false` if _condition-1_ returns `false`. Only if _condition-1_
returns `true` is _condition-2_ evaluated and its result returned.
In general, the following rules apply:
-* There is no need to quote attributes. `&User-Name` is almost always preferable to `%{User-Name}`.
+* There is no need to quote attributes. `User-Name` is almost always preferable to `%{User-Name}`.
* There is no need to quote values, unless the value is a string. `192.0.2.1` is always preferable to `"192.0.2.1"`
processed.
Note that the pairs in the string _cannot_ have list qualifiers. That
-is, `&reply += "request.foo ..."` is not allowed.
+is, `reply += "request.foo ..."` is not allowed.
.Assigning attributes taken from a string
====
&Reply-Message := "foo" + "bar"
----
-Will result in `&Reply-Message == "foobar"`.
+Will result in `Reply-Message == "foobar"`.
The suffix can then be "subtracted" off, with:
&Reply-Message -= "bar"
----
-Will result in `&Reply-Message == "foo"` !.
+Will result in `Reply-Message == "foo"` !.
Other data types will generally yield results which make sense. For
example:
* `ipv4addr` types can be subtracted, and will return a number which is the number of IPs between the two values,
* two `date` values can be subtracted, and will return a `time_delta`,
* operations on integers are upgraded to the next largest integer size when necessary,
-* the logical operators `&&` and `||` return the value which caused them to succeed, e.g. `&Foo := (&User-Password || "help")` will return the contents of `&User-Name` if it exists, otherwise it will return the string `help`.
+* the logical operators `&` and `||` return the value which caused them to succeed, e.g. `Foo := (&User-Password || "help")` will return the contents of `User-Name` if it exists, otherwise it will return the string `help`.
In most cases, the data types are derived from the attribute
dictionaries. However, it is sometimes necessary to force the fields
}
----
-Once the loop has finished , the `&Tmp-Integer-0` attribute will have the following set of values.
+Once the loop has finished , the `Tmp-Integer-0` attribute will have the following set of values.
[source,unlang]
----
----
The same behavior applies to conditions with complex statements using
-`&&` and `||`. So long as the text between the `if` and the `{` is a
+`&` and `||`. So long as the text between the `if` and the `{` is a
valid condition, it will be parsed correctly.
=== Multi-line conditions
finishes, it is deleted, and is no longer accessible to the parent.
.Examples
-`&parent.request.User-Name` +
-`&parent.reply.Reply-Message` +
-`&parent.parent.session-state.Filter-Id`
+`parent.request.User-Name` +
+`parent.reply.Reply-Message` +
+`parent.parent.session-state.Filter-Id`
// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
Each module or subsection runs as a new child request, i.e. a
xref:unlang/subrequest.adoc[subrequest]. Each child request is an identical
copy of the parent request. Policies in the child can update the
-original parent by referencing `&parent.request`, or
-`&parent.reply`. Please see the xref:unlang/list.adoc[list] syntax for a
+original parent by referencing `parent.request`, or
+`parent.reply`. Please see the xref:unlang/list.adoc[list] syntax for a
more complete description of how to refer to parent requests.
The child requests are required because each subsection is run
.Example
In this example, if the SQL `select` statement fails, then the
-`&reply.Framed-IP-Address` attribute is _not_ updated, and the
+`reply.Framed-IP-Address` attribute is _not_ updated, and the
`transaction` section returns `fail`.
[source,unlang]
}
----
-This configuration can be interpreted as "edit the `&request` list,
+This configuration can be interpreted as "edit the `request` list,
but appending (`+=`) another list to it. The list being appended
contains four attributes".
=== Removing Attributes from a list: !*
-The following example removes all `&User-Name` attributes from the `request` list.
+The following example removes all `User-Name` attributes from the `request` list.
[source,unlang]
----
&reply.Reply-Message := "%eval(&request.Tmp-String-0}"
----
-.Output when `&User-Name == bob`
+.Output when `User-Name == bob`
```
bob
```
-.Output when `&User-Name == not bob`
+.Output when `User-Name == not bob`
```
not bob!
other participant in the group, into your backend datastore.
If the datastore provides no authentication method (`sql`, `redis`), you should
-use the value of `&Stripped-User-Name` as the key, and when the user attempts to
+use the value of `Stripped-User-Name` as the key, and when the user attempts to
login, retrieve the user's password and store it in
-`&control.Password.Cleartext`.
+`control.Password.Cleartext`.
== Authorization
First, in the `recv Access-Request { ... }` or `authorize { ... }` section of
of an appropriate virtual server, call the suffix module.
-If, after the suffix module has returned, the `&control.Proxy-To-Realm` attribute
+If, after the suffix module has returned, the `control.Proxy-To-Realm` attribute
is set, use the `return` keyword to exit from the section. This prevents local
modules being called, when we know the request will be handled by a remote proxy.
determine if the authenticating user exists in the datastore.
If the user does exist, and your module has an authenticate method (`ldap`,
-`rest`), set `&control.Auth-Type` to an appropriate value.
+`rest`), set `control.Auth-Type` to an appropriate value.
If the backend module does not have an authenticate method (`redis`, `files`,
`sql`), you should ensure the "known good" copy of the user's password is set in
-`&control.Password.Cleartext` and then call the pap module.
+`control.Password.Cleartext` and then call the pap module.
=== Bonus tasks
Create an instance of the sometimes module called `bad_ldap`.
Add unlang statements such that if the module returns `noop`, then a
-`&control.Password.Cleartext = 'hello'` attribute pair is added to the request,
+`control.Password.Cleartext = 'hello'` attribute pair is added to the request,
and the return code is set to `updated`.
Run three test requests using `bob.sh`, you should see that at least one request
(via capture groups).
If you've completed the xref:proxy.adoc[Proxy] exercise you'll have
-used the `suffix` module to split an incoming `&User-Name` value into
+used the `suffix` module to split an incoming `User-Name` value into
its components and setup the request for proxying.
-Create an unlang version of "suffix" that splits an incoming `&User-Name` into
+Create an unlang version of "suffix" that splits an incoming `User-Name` into
two components on the "@" separator.
-The first component should be written to the `&request.Stripped-User-Name`
+The first component should be written to the `request.Stripped-User-Name`
attribute and the second component should be written to the
-`&control.Stripped-User-Domain` attribute.
+`control.Stripped-User-Domain` attribute.
Use `bob@realm1.sh` and `bob@realm2.sh` to test your new policy to ensure
it works as expected.
If you've completed the xref:proxy.adoc[Proxy] tutorial and have test
realms setup, modify the policy code you have just written to proxy
-the request to the realm specified in the `&User-Name` attribute.
+the request to the realm specified in the `User-Name` attribute.
== Questions
Here, we will emulate that behaviour using the policy language.
* Create a condition (_condition 1_) to execute policy code if
-the `&User-Name` in the request is 'bob'.
-* Within that condition block, set the control attribute `&Password.Cleartext`
+the `User-Name` in the request is 'bob'.
+* Within that condition block, set the control attribute `Password.Cleartext`
to be 'hello', and instruct the server to run the the `authenticate { ... }`
subsection for `pap`.
* Use the `bob.sh` script to verify that you see an `Access-Accept` returned
To recap:
-* If an incoming request contains a `&User-Name` attribute with the value
- 'bob', and contains an attribute `&Framed-Protocol` with value `PPP`
- (_condition 2_), reply with a `&Framed-IP-Address` attribute with the value
+* If an incoming request contains a `User-Name` attribute with the value
+ 'bob', and contains an attribute `Framed-Protocol` with value `PPP`
+ (_condition 2_), reply with a `Framed-IP-Address` attribute with the value
`192.168.10.12`.
-* If an incoming request contains a `&Service-Type` attribute with a value
- of `Framed-User` (_condition 3_), reply with a `&Framed-Route` attribute
+* If an incoming request contains a `Service-Type` attribute with a value
+ of `Framed-User` (_condition 3_), reply with a `Framed-Route` attribute
assigning a default route of `192.168.10.1` (`0.0.0.0/0 192.168.10.1 1`) and
- a `&Framed-IP-Netmask` attribute with a value of `255.255.255.0`.
+ a `Framed-IP-Netmask` attribute with a value of `255.255.255.0`.
Again test the server with username "bob" and password "hello". Use the
debug output of the server to see which unlang conditions evaluated to
.Common control attributes
****
-Attributes in the `&control` list can control the behaviour of the server.
+Attributes in the `control` list can control the behaviour of the server.
Commonly used control attributes are:
-- `&control.Auth-Type` specifies the `authenticate <name> {}` section to run
+- `control.Auth-Type` specifies the `authenticate <name> {}` section to run
****
// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0.