The users file
The users
file is managed by the files module. The format largely goes back to the original Livingston RADIUS server in 1991!
While the format is still called users
for historical reasons, the name of the file is controlled by the configuration of the files module, and can therefore something other than users
.
Format
The format is text-based. Empty lines, and lines which begin with a
comment (#
) or whitespace plus a comment are ignored. All other
text has to follow a fixed format:
name ... check items ... ... reply items ..., ... reply items ...
- name
-
The name of the entry. It must be an unquoted string such as
john
. The files module configuration expands thekey
configuration item, and then uses the result to match thename
of the entry. - check items
-
A comma-separated list of attributes to use for conditional matches, such as
Framed-IP-Address == 192.0.2.1
, orNAS-IP-Address == 192.0.2.2, NAS-Port = 15
. Multiple conditions are matched with a logical "and". There is no way to use a logical "or" between conditions.The conditional matching must be done via comparison operators such as
==
,<
,<=
,>
,>=
, etc.The list of check items can be empty, in which case the first line of the entry contains the
name
, and nothing more. - reply items
-
A comma-separated list of attributes which are added to the reply, such as
Reply-Message := "Hello"
, orFilter-Id := "foo", Filter-Id += "bar"
.The list of reply items can span multiple line, in which case each intermediate line must end with a comma, and the last line of the reply items must not end with a comma.
Each line which contains reply items must begin with a tab character.
The list of reply items can be empty, in which case there must be a blank line after the line which contains the
name
and check items.
There is no limit to the size of the file or number of entries in it, other than available memory. The file is read and cached when the server starts, and cannot be changed while the server is running.
The $INCLUDE directive
As a special case, the line can begin with the name $INCLUDE
. The $INCLUDE
name must be followed by a filename. An $INCLUDE
must be in the same location where a name
and check items entry is expected. It is not possible to put an $INCLUDE
in the list of reply items.
$INCLUDE next_users_file
The $INCLUDE
directive reads another file into the current users
file, as if the contents had been included in-place in the original file.
The filename can be absolute, and begin with /
. That usage is not recommended.
If the filename does not begin with /
, it is a relative filename. The module reads the file which is relative to the current file being processed. For example, if the current file is /etc/raddb/mods-config/files/users
and that file contains a directive $INCLUDE next
, the file which will be read is /etc/raddb/mods-config/files/next
.
The $INCLUDE
can be nested to any depth, subject only to available memory. The module does not cross-reference $INCLUDE
files, so it is possible for a file to $INCLUDE
itself in an infinite loop. This practice is not recommended. When this misconfiguration happens, the server will run out of memory processing the file, and then exit.
The purpose of $INCLUDE
is to allow the users
file to be broken up into multiple logical pieces. For example, a system mayneed to generate multiple different types of `users ` file entries, depending on the type of user. These different entries may be placed into different files, so that each different file can be regenerated independently.
Processing
The file is processed by using a key
to find one or more matching entries, subject to the additional functionality described below.
The entries are placed into a data structure which best leverages the data type of the key
:
- IP addresses and prefixes
-
Patricia trie.
- string, octets, date, time_delta
-
balanced binary tree.
- all other data types
-
hash.
- structural types
-
not allowed
These data structures allow the module to operate extremely quickly. When doing performance tests, there is very little difference between a users
file which contains one entry, and a users
file which contains a million entries.
Where multiple entries are matched, the entries are matched in the order that they appear in the file. That is, they are matched from the top of the file to the bottom of the file.
Matching an entry
An entry is found by using the key
to find a matching name
field. Once the entry is found, the module looks for and processes any check items. If no matching entry is found, the special name
of DEFAULT
is searched for. If no DEFAULT
is found, processing of the users
file stops.
Once an entry is found, the check items are matched. The list of check items can have zero, one, or many attribute comparisons listed. If no comparisons are listed, then the entry matches. Otherwise, all check items must match in order to continue processing the reply items.
The check items are matched using the same operations as for conditional comparisons. If there are multiple check items, they are matched with a logical "and". There is no way to use a logical "or" between conditions.
Only comparisons are allowed, the rest of the conditional syntax such as nested conditions (…) or || and && are not permitted.
|
If all of the check items, the module adds the reply items (if any) to the request. The reply items are added using the historival operator assignments. See the Item Operators section below for more information.
For historical compatibility, the check item list also supports some attribute assignments. Any check item which uses an assignment operator (=
, :=
, etc.) is added to the control
list.
Fall-Through
When processing the reply items, the attribute Fall-Through = yes
has special meaning. If it is found in list of reply items, then the module will continue processing the users
file, and will look for a subsequent matching entry. That is, an entry which is later in the file.
The Fall-Through
attribute is most commonly used to apply rules to specific users, but then also apply generic rules, as in the example below.
bob Password.Cleartext := "hello" Framed-IP-Address := 192.0.2.1, Fall-Through = yes DEFAULT Reply-Message := "Hello %{User-Name}"
In this example, the user bob
will have both the Framed-IP-Address
and Reply-Message
attributes added in any reply.
Next-Shortest-Prefix
If the key
field is an IP address or prefix data type, the module tracks the prefix. When an entry matches, the Next-Shortest-Prefix
attribute is also checked. If set to yes
, the module will decrease the value of the prefix, and look for a matching entry. i.e. the "next shortest prefix" after the current one.
The interaction between Fall-Through
and Next-Shortest-Prefix
allows the users
file to match both multiple entries for the current key
value, and also to apply rules to entire networks. However, the reply items for one entry should only contain one of Fall-Through
or Next-Shortest-Prefix
. If both appear in a reply item list, the Next-Shortest-Prefix
attribute is ignored.
192.0.2.1 Filter-Id := "foo", Next-Shortest-Prefix = yes 192.0.0.0/8 Reply-Message = "In the 192 network"
In this example, a key
of 192.0.2.1
will both a Filter-Id
attribute, and a Reply-Message
attribute. In contrast, a key
of 192.0.2.255
will only return a Reply-Message
attribute.
The DEFAULT name
If no entry matches, the special name DEFAULT
will be matched. The DEFAULT
entry can also match if a previous name
matched, and the reply items contained Fall-Through = yes
, or Next-Shortest-Prefix = yes
.
We recommend not using DEFAULT
when the key
is an IP address or prefix. It is better instead to use a network and mask, such as 192.0.2/24
, or 0/0
.
Success or Failure
When the users
file has no matches (even DEFAULT
), the files
module returns noop
. If one or more entries matched, then the module returns ok
. Note that it will return ok
even if no reply items are added.
When there is a failure processing the users
file, the module returns fail
. This situation can happen when the attribute assignment refers to a list which does not exist in the current context (e.g. parent.foo := …
), or when the value is a dynamic expansion which fails.
When the module returns fail
, then any changes which might have been made are reverted. The result is just the same as if the module never matched any entries. For example, the users
file below contains an SQL module query which might fail:
DEFAULT Reply-Message += "Default matched", Fall-Through = yes DEFAULT Reply-Message += "%sql(SELECT ...)"
When this file is processed, the first DEFAULT
entry will match, and a Reply-Message
will be added to the reply. The debug output will also show that the first DEFAULT
entry matched. However, when the second entry matches, the sql
module may not be able to make a connection to the database When that happens, the expansion will fail. The module will then return fail
.
Even though the first DEFAULT
entry matched, the reply will not contain an attribute Reply-Message = "Default matched"`. That is because on failure, the module will "undo" any changes it made, using the same framework as supported for the transaction keyword.
This behavior is different from previous versions, where all intermediate changes would remain, even if the module returned fail
.
Recommendations
Entries which reject requests should go at the top of the file, and
should not use Fall-Through
item. Entries for specific users who do
not have a Fall-Through
, should come next. Any DEFAULT
entries
should usually come last. This ordering means that it will be easier
to debug policies, and understand how the file works.
Check and Reply item format
Each check item or reply item must follow the same format, which is shown in the examples above. The format is an attribute name, followed by an operator, and then a value.
- attribute
-
An attribute name such as
Framed-IP-Address
- operator
-
A comparison operator (for check items), or an assignment operator. See the Item Operators section for a list of operators and their meaning.
- value
-
A value such as
192.0.2.1
,15
, or string"foo"
. Values can also be attribute references. See the Item Values section below for more information.
Item Attributes
The attribute
name for an item can be a simple name such as Filter-Id
. The name can also be an attribute reference such as reply.Reply-Message
.
Attribute references are supported for both the check items and reply items.
The default list for the check items is control
. Specifying another list means that the comparison is done instead on the referenced attribute.
The default list for the reply items is reply
. Specifying another list means that the other list is updated, instead of the reply
list.
Structural Data Types
Structural data types such as tlv
, group
, and struct
are handled somewhat oddly in the users
file. The reason for this behavior is due to the limitations of the users
file format. In contrast, nested attributes are handled simply and clearly by the new edit functionality. If there is any confusion or uncertainty about how the users
file operates, we recommend just using the new edit functionality.
It is not possible to perform comparisons structural data types. It is only possible to create and edit them.
Care should be taken when using =` with structural attributes. Unlike the xref:reference:unlang/edit.adoc[edit] operations `=
here means create a new structural attribute and append it. The =` operator does not mean _append the child attributes to the structural attribute_. The most common issue seen with using `=
is where it creates two Vendor-Specific
attributes, which will cause problems.
The solution instead is to use :=
when referring to structural attributes by name, or instead using the name of a leaf attribute (e.g. Vendor-Specific.Cisco.AVPair
), and then using +=
on the leaf.
In most situations, the simplest approach for structural data types is to just create the leaf attributes. e.g. &foo.bar.baz := 5
. If any parent attribute is missing, it will be automatically created. That is, operations on leaf types will just "do the right thing" most of the time, so there is no need to explicitly refer to a structural data type by name.
There are some situations where it is useful to refer to structural attributes by name, as given in the examples below.
Structural attributes can be copied from another attribute. Both source and destination attributes must have the same data type.
bob Password.Cleartext := "hello" Vendor-Specific.Cisco := &control.Vendor-Specific.Cisco
This example copies the Vendor-Specific.Cisco
group from the &control
list. If the attribute does not exist in the control list, nothing is done.
Structural attributes can be created from a string, as with the edit functionality. Note that the string should not contain brackets such as "{ AVPair = 'hello' }"
.
bob Password.Cleartext := "hello" Vendor-Specific.Cisco := "AVPair = 'hello'"
This example creates the reply attribute Vendor-Specific.Cisco.AVPair
, with value hello
. If the parent attributes Vendor-Specific
or Cisco
do not exist, they are created.
As the right-hand side is a double-quoted string, it is expanded via the normal xlat process as discussed below in Item Values.
All of the attributes inside of the double-quoted string must use the =
operator. Other operators are not supported.
Relative Attribute References
The format of the users
file is not well suited for structural data types. The edit functionality supports nested lists such as &foo = { &bar = 1 }
. That format cannot be supported here. Instead, the "dotted" notation of attribute references is leveraged to create relative attribute refefences. The example below shows how relative attribute references can be used.
bob Password.Cleartext := "hello" Vendor-Specific = {}, .Cisco = {}, .AVPair += "Hello"
In the above example, the reply items start out with a reference to a structural attribute which is at the "root" of the attribute tree. In this case, the attribute is Vendor-Specific
. The assignment uses the =
operator, which creates the attribute if it does not already exist. If the Vendor-Specific
attribute exists, no changes are made. The value assigned is an empty list, which (if necessary) will create an empty Vendor-Specific
attribute.
The next line contains a relative attribute reference: .Cisco
. The attribute reference is relative because it begins with a .
character. The relative attribute must be a child of the previous structural attribute, in this case Vendor-Specific
.
As with Vendor-Specific
, and .Cisco
attribute uses the =
operator, and an empty list to create the Cisco
attribute inside of the Vendor-Specific
one. An empty string ""
will also be accepted here.
The final line contains .AVPair = "Hello"`. This line also contains a _relative_ attribute reference. It creates a `AVPair` attribute inside of the `Cisco` attribute, and then assigns the value `"Hello"` to it. The created attribute is appended to the tail of the `Cisco` list, due to the use of the `=
operator.
The relative attributes work fairly simply, subject to the following rules:
-
a relative attribute
.foo
must have a structural attribute before it -
a relative attribute
.foo
must be a child of the previous structural attribute -
an absolute attribute can be used at any point, even if the previous attribute as relative.
-
using an absolute attribute will "reset" the reference for relative attributes, to be either itself (if it is structural), or nothing (if it is a leaf)
-
relative attributes can use multiple
.
to reference attributes "higher" in the tree
bob Password.Cleartext := "hello" Vendor-Specific = {}, .Cisco = {}, .AVPair += "Hello", ..HP = {}, .Privilege-Level += 1
In general, intermediate relative structural attributes should use = {}
, as with the .Cisco
example above. This assignment will create the attribute if needed, but will not modify existing attributes. The final relative "leaf" attribute should use +=
.
Item Operators
The list of comparison operators for check items is given in the conditional comparisons page. However, the users
file format does not support casting in a comparison.
As a special case for compatibility with previous versions, the users
file also supports two additional comparison operators:
Operator | Description |
---|---|
=* |
Matches if the attribute exists, no matter what the value is. |
!* |
Matches if the attribute does not exist, no matter what the value is. |
Due to limitations of the users
file format, a value must be specified for these additional comparison operators. The best practice is to use a meaningless special value ANY
, such as Framed-IP-Address !* ANY
.
The assignment operators follow the behavior of the user
file, and do not follow the new editing operators behavior. The reason for this mismatch is that we prefer to not break backwards compatibility for the users
file. Both because of how how the operators work, and because using the new-style operators in the users
format would require changing the users
file format so much that it would be unrecognizable, and likely not usable.
As a result, the attribute editing operators for the users
file follow the old-style functionality, as documented below.
Operator | Description |
---|---|
= |
Set the attribute to the contents of the value, if the given attribute does not exist. If the attribute already exists, nothing is done. If the attribute does not exist, it is created, and the contents set to the given value. |
:= |
Delete all existing copies of the named attribute, and create a new attribute with the contents set to the given value. |
+= |
Create the attribute using the given value, and append the attribute to the list (insert at tail). |
^= |
Create the attribute using the given value, and prepend the attribute to the list (insert at head). |
-= |
Delete all attributes which match the given value. |
There are also filtering operators. These operators ensure that the value of the attribute passes the filter. If the attribute being filtered does not exist, it is created.
Operator | Description |
---|---|
<= |
Ensure that the attribute exists, and has value less than or equal to the given value. |
>= |
Ensure that the attribute exists, and has value greater than than or equal to the given value. |
If the value does not pass the filter comparison, its value is replaced with the value from the filter comparison.
Item Values
The values for items can be a simple value such as 192.0.2.1
, an xlat string to expand such as "Hello %{User-Name}"
, or an attribute reference such as &request.Filter-Id
.
The attribute references should have an &
prefix, to more clearly separate them from enumeration values such as Service-Type := Framed-User
.
References can be to an attribute which has a different data type than the attribute named on the left-hand side of the check item or reply item. In which case the values will be automatically cast to the correct type, as documented in the conditional comparison and edit pages.
# # Match "bob", but only if he's logging in from this particular NAS # Send a user-specific Reply-Message, and ACK any Framed-IP-Address # which was requested. # bob NAS-IP-Address == 192.0.2.1, Password.Cleartext := "hello" Reply-Message := "Hello %{User-Name}", Framed-IP-Address := &request.Framed-IP-Address