perl -p -i -e 's/%{%{([^%{}]+)}:-%{([^%{}]+)}}/%{\&$1 || \&$2}/g' $(git grep -l ':-' raddb/)
and then re-build the antora docs from the raddb sources
```
cache cache_eap {
- key = "%{%{control.State}:-%{%{reply.State}:-%{State}}}"
+ key = "%{&control.State || &reply.State || &State}"
ttl = 15
update reply {
&reply += &reply
# send_hello = "false"
# detailed_errcodes = "true"
}
- acct_key = "radacct_%{%{Acct-Unique-Session-Id}:-%{Acct-Session-Id}}"
+ acct_key = "radacct_%{&Acct-Unique-Session-Id || &Acct-Session-Id}"
doctype = "radacct"
expire = 2592000
update {
&Acct-Output-Gigawords = 'outputGigawords'
&Event-Timestamp = 'lastUpdated'
}
- user_key = "raduser_%md5(%tolower(%{%{Stripped-User-Name}:-%{User-Name}}))"
+ user_key = "raduser_%md5(%tolower(%{&Stripped-User-Name || &User-Name}))"
# read_clients = no
client {
view = "_design/client/_view/by_id"
```
delay {
- delay = 1.0
+ delay = 1.0s
# force_reschedule = no
# relative = no
}
The `users` file as located in `raddb/mods-config/files/authorize`. (Livingston-style format).
-See "man 1 users" for more information.
+See `man 1 users` for more information.
+
+
+
+NOTE: Temporarily (2023-08-27), the check items only supports
+"real" attributes. This limitation will be removed when the module
+is rewritten to support xlat expressions for conditions.
+
+The module also does not support xlat expansions on the RHS.
```
files {
moddir = ${modconfdir}/${.:instance}
-# key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+# key = "%{&Stripped-User-Name || &User-Name}"
filename = ${moddir}/authorize
# usersfile = ${moddir}/authorize
acctusersfile = ${moddir}/accounting
# edir_autz = no
user {
base_dn = "${..base_dn}"
- filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
-# filter = "(&(objectClass=user)(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})(memberOf:1.2.840.113556.1.4.1941:=cn=group,${..base_dn}))"
+ filter = "(uid=%{&Stripped-User-Name || &User-Name})"
+# filter = "(&(objectClass=user)(sAMAccountName=%{&Stripped-User-Name || &User-Name})(memberOf:1.2.840.113556.1.4.1941:=cn=group,${..base_dn}))"
sasl {
# mech = 'PLAIN'
# authname = &User-Name
filter = '(objectClass=posixGroup)'
# scope = 'sub'
# name_attribute = cn
-# membership_filter = "(|(member=%{control.Ldap-UserDn})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))"
+# membership_filter = "(|(member=%{control.Ldap-UserDn})(memberUid=%{&Stripped-User-Name || &User-Name}))"
membership_attribute = 'memberOf'
# cacheable_name = 'no'
# cacheable_dn = 'no'
+header:: Optional header line format for file output
+
+If the destination is "file" and header is set, then this
+is expanded and output as the first line when a new file
+is created.
+
+
+
destination:: What should be done with log messages.
May be one of:
The connection pool for TCP and Unix socket connections.
-start::
+start:: Connections to create during module instantiation.
-Connections to create during module instantiation.
If the server cannot create specified number of
connections during instantiation it will exit.
-Set to 0 to allow the server to `start` without the
-web service being available.
+Set to `0` to allow the server to start without the
+external service being available.
If these connections are all in use and a new one
is requested, the request will NOT get a connection.
-Setting `max` to LESS than the number of threads means
+Setting `max` to *LESS* than the number of threads means
that some threads may starve, and you will see errors
-like 'No connections available and at `max` connection limit'
+like _No connections available and at max connection limit_.
Setting `max` to MORE than the number of threads means
that there are more connections than necessary.
+If `max` is not specified, then it defaults to the number
+of workers configured.
+
spare:: Spare connections to be left idle.
Access-Reject = "Sent reject: %{User-Name}"
Access-Challenge = "Sent challenge: %{User-Name}"
}
+# header = ""
destination = file
file {
filename = ${logdir}/linelog
Start = "Connect: [%{User-Name}] (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} ip %{Framed-IP-Address})"
Stop = "Disconnect: [%{User-Name}] (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} ip %{Framed-IP-Address}) %{Acct-Session-Time} seconds"
Interim-Update = ""
- Accounting-On = "NAS %%{Net.Src.IP} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) just came online"
- Accounting-Off = "NAS %{Net.Src.IP} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) just went offline"
- unknown = "NAS %{Net.Src.IP} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) sent unknown Acct-Status-Type %{Acct-Status-Type}"
+ Accounting-On = "NAS %{Net.Src.IP} (%{&NAS-IP-Address || &NAS-IPv6-Address}) just came online"
+ Accounting-Off = "NAS %{Net.Src.IP} (%{&NAS-IP-Address || &NAS-IPv6-Address}) just went offline"
+ unknown = "NAS %{Net.Src.IP} (%{&NAS-IP-Address || &NAS-IPv6-Address}) sent unknown Acct-Status-Type %{Acct-Status-Type}"
}
}
```
+
use_open_directory::
For Apple Server, when running on the same machine as Open Directory.
# require_encryption = yes
# require_strong = yes
# with_ntdomain_hack = no
-# ntlm_auth = "/path/to/ntlm_auth --request-nt-key --allow-mschapv2 --username=%{%{Stripped-User-Name}:-%{%{User-Name}:-None}} --challenge=%{%mschap(Challenge):-00} --nt-response=%{%mschap(NT-Response):-00}"
+# ntlm_auth = "/path/to/ntlm_auth --request-nt-key --allow-mschapv2 --username=%{&Stripped-User-Name || &User-Name || 'None'} --challenge=%{%mschap(Challenge) || 00} --nt-response=%{%mschap(NT-Response) || 00}"
# ntlm_auth_timeout = 10
winbind {
# username = "%mschap(User-Name)"
# ntlm_auth = "/usr/bin/ntlm_auth --helper-protocol=ntlm-change-password-1"
# ntlm_auth_username = "username: %mschap(User-Name)"
# ntlm_auth_domain = "nt-domain: %mschap(NT-Domain)"
-# local_cpw = "%exec(/path/to/script %mschap(User-Name) %{MS-CHAP-New-Password.Cleartext})"
-# local_cpw = "%sql(UPDATE radcheck set value='%{MS-CHAP-New-NT-Password}' where username='%{User-Name}' and attribute='Password.NT'}"
+# local_cpw = %exec('/path/to/script', %mschap(User-Name), %{MS-CHAP-New-Password.Cleartext})
+ local_cpw = %sql("UPDATE radcheck set value='%{MS-CHAP-New-NT-Password}' where username='%{User-Name}' and attribute='Password.NT'")
}
# use_open_directory = yes
# allow_retry = yes
structure is small, around `32` characters, so that will limit
the possible choices of keys.
-TIP: You may want instead: `%{%{Stripped-User-Name}:-%{User-Name}}`.
+TIP: You may want instead: `%{&Stripped-User-Name || &User-Name}`.
= REDIS Module
The `redis` module handles connections to a redis database,
-and the `%{redis: ...}` dynamic expansion.
+and the `%redis( ...)` dynamic expansion.
See also https://redis.io/documentation for documentation on the
Redis database.
# wait_timeout = 2
# gateway = &NAS-Identifier
owner = &Client-Hardware-Address
-# owner = "%{%{Client-Identifier}:-%{Client-Hardware-Address}}"
+# owner = "%{&Client-Identifier || &Client-Hardware-Address}"
# owner = "%{Vendor-Specific.ADSL-Forum.Agent-Circuit-ID} %{Calling-Station-Id}"
- requested_address = "%{%{Requested-IP-Address}:-%{Net.Src.IP}}"
+ requested_address = "%{&Requested-IP-Address || &Net.Src.IP}"
# ipv4_integer = yes
allocated_address_attr = &reply.Your-IP-Address
range_attr = &reply.IP-Pool.Range
trim_count = 15
expire_time = 86400
Start {
- insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
+ insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{&NAS-IP-Address || &NAS-IPv6-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
trim = "LTRIM %{User-Name} 0 ${..trim_count}"
expire = "EXPIRE %{User-Name} ${..expire_time}"
}
Interim-Update {
- insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
+ insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{&NAS-IP-Address || &NAS-IPv6-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
trim = "LTRIM %{User-Name} 0 ${..trim_count}"
expire = "EXPIRE %{User-Name} ${..expire_time}"
}
Stop {
- insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
+ insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{&NAS-IP-Address || &NAS-IPv6-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
trim = "LTRIM %{User-Name} 0 ${..trim_count}"
expire = "EXPIRE %{User-Name} ${..expire_time}"
}
it will be `redundant_sql`. You can then use this expansion
just like any other:
- &reply.Filter-Id := "%{redundant_sql: ... }"
+ &reply.Filter-Id := "%redundant_sql( ... )"
In this example, the expansion is done via module `sql1`, and if
that expansion fails, using module `sql2`.
| `data` | Send custom freeform data in the HTTP body. `Content-type`
may be specified with `body`. Will be expanded.
Values from expansion will not be escaped, this should be
- done using the appropriate `xlat` method e.g. `%{urlquote:<attr>}`
+ done using the appropriate `xlat` method e.g. `%urlquote(<attr>)`
| `force_to` | Force the response to be decoded with this decoder.
May be 'plain' (creates reply.REST-HTTP-Body), 'post' or 'json'.
| `tls` | TLS settings for HTTPS.
+NOTE: Temporarily (2023-08-27), the check items only support "real"
+attributes. This limitation will be removed when the module is
+rewritten to support maps (for assignments) and xlat expressions
+(for conditions).
+
+The module also does not support xlat expansions on the RHS.
+
+
+
## Configuration Settings
That check will be done internally, and will not result in a database lookup. This also means that
it is now possible to do group comparisons based on regular expressions.
-It is possible to force a dynamic group lookup via the expansion `%{sql.group:foo}`. This
+It is possible to force a dynamic group lookup via the expansion `%sql.group(foo)`. This
expansion returns `true` if the user is a member of that SQL group, and `false` otherwise.
NOTE: The `SQL-Group` attribute is only available after the SQL module has been run.
# sqlcounter <name> {
# sql_module_instance = sql_foo
# reset = 12h
-# key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+# key = "%{&Stripped-User-Name || &User-Name}"
# if (&control.Daily-Session-Time > 3600) {
# &Reply-Message := "You've used up more than one hour today"
# reject
counter_name = &control.Daily-Session-Time
check_name = &control.Max-Daily-Session
reply_name = &reply.Session-Timeout
- key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+ key = "%{&Stripped-User-Name || &User-Name}"
reset = daily
$INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
}
* `%{dhcpv4.Client-Hardware-Address}` which binds the lease to the
mac address of the user's device.
- * `%{%{dhcpv4.Client-Identifier}:-%{dhcpv4.Client-Hardware-Address}}`
+ * `%{&dhcpv4.Client-Identifier || &dhcpv4.Client-Hardware-Address}`
which binds the lease to either the custom identifier set by the
DHCP client, or if this is absent, the mac address of the user's
device.
For RADIUS the requested_address will almost always be `%{radius.Framed-IP-Address}`.
For DHCPv4 the requested_address will almost always be
-`%{%{dhcpv4.Requested-IP-Address}:-%{dhcpv4.Client-IP-Address}}`.
+`%{&dhcpv4.Requested-IP-Address || &dhcpv4.Client-IP-Address}`.
pool_name = IP-Pool.Name
allocated_address_attr = radius.Framed-IP-Address
owner = "%{radius.Calling-Station-ID}"
-# owner = "%{%{dhcpv4.Client-Identifier}:-%{dhcpv4.Client-Hardware-Address}}"
+# owner = "%{&dhcpv4.Client-Identifier || &dhcpv4.Client-Hardware-Address}"
requested_address = "%{radius.Framed-IP-Address}"
-# requested_address = "%{%{dhcpv4.Requested-IP-Address}:-%{dhcpv4.Client-IP-Address}}"
- gateway = "%{%{radius.NAS-Identifier}:-%{radius.NAS-IP-Address}}"
+# requested_address = "%{&dhcpv4.Requested-IP-Address || &dhcpv4.Client-IP-Address}"
+ gateway = "%{&radius.NAS-Identifier || &radius.NAS-IP-Address}"
messages {
exists = "Existing IP: %{reply.${..allocated_address_attr}} (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} user %{User-Name})"
success = "Allocated IP: %{reply.${..allocated_address_attr}} from %{control.IP-Pool.Name} (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} user %{User-Name})"
```
winbind {
- username = "%{%{Stripped-User-Name}:-%{User-Name}}"
+ username = "%{&Stripped-User-Name || &User-Name}"
# domain = ""
group {
- search_username = "%{%{Stripped-User-Name}:-%{User-Name}}"
+ search_username = "%{&Stripped-User-Name || &User-Name}"
# add_domain = yes
}
pool {
require_client_cert = yes
verify {
}
- psk_query = "%{psksql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}"
+ psk_query = %psksql("select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'")
}
virtual_server = abfab-idp
clients = radsec-abfab
+
If there's no State attribute, then this is the request from
the user.
# to be "hello". The server will send them a challenge
# consisting of a random number 0..9. The user has to respond
# with that number.
-listen {
- type = auth
- ipaddr = *
- port = 2000
- virtual_server = challenge
-}
server challenge {
+ namespace = radius
+ listen {
+ type = Access-Request
+ transport = udp
+ udp {
+ ipaddr = *
+ port = 2000
+ }
+ }
recv Access-Request {
if (!&State) {
&control.Auth-Type := Step1
}
authenticate step1 {
pap
- &session-state.Tmp-Integer-0 := "%{randstr:n}"
+ &session-state.Tmp-Integer-0 := "%randstr(n)"
&reply.Reply-Message := &session-state.Tmp-Integer-0
challenge
}
Call the radius client module instance for the NAS-IP-Address
-Repeat this for each NAS
+Repeat this block for each NAS
-Likely a missing "case" if we can't map NAS-IP-Address to a module
+Likely a missing "case" block if we can't map NAS-IP-Address to a module
}
}
recv CoA-Request {
-# &control.Tmp-String-0 := "%{sql:SELECT IFNULL(GROUP_CONCAT(CONCAT(nasipaddress,'#',acctsessionid) separator '|'),'') FROM (SELECT * FROM radacct WHERE ('%{User-Name}'='' OR UserName='%{User-Name}') AND ('%{Acct-Session-Id}'='' OR acctsessionid = '%{Acct-Session-Id}') AND AcctStopTime IS NULL) a}"
-# &control.Tmp-String-0 := "%{sql:SELECT STRING_AGG(CONCAT(nasipaddress,'#',acctsessionid),'|') FROM (SELECT * FROM radacct WHERE ('%{User-Name}'='' OR UserName='%{User-Name}') AND ('%{Acct-Session-Id}'='' OR acctsessionid = '%{Acct-Session-Id}') AND AcctStopTime IS NULL) a}"
+# &control.Tmp-String-0 := %sql("SELECT IFNULL(GROUP_CONCAT(CONCAT(nasipaddress,'#',acctsessionid) separator '|'),'') FROM (SELECT * FROM radacct WHERE ('%{User-Name}'='' OR UserName='%{User-Name}') AND ('%{Acct-Session-Id}'='' OR acctsessionid = '%{Acct-Session-Id}') AND AcctStopTime IS NULL) a")
+# &control.Tmp-String-0 := %sql("SELECT STRING_AGG(CONCAT(nasipaddress,'#',acctsessionid),'|') FROM (SELECT * FROM radacct WHERE ('%{User-Name}'='' OR UserName='%{User-Name}') AND ('%{Acct-Session-Id}'='' OR acctsessionid = '%{Acct-Session-Id}') AND AcctStopTime IS NULL) a")
&control.Tmp-Integer-0 := 0
- if ("%(explode:&control.Tmp-String-0 |)") {
+ if ("%explode(&control.Tmp-String-0, '|')") {
foreach &control.Tmp-String-0 {
if ("%{Foreach-Variable-0}" =~ /([^#]*)#(.*)/) {
- &control.Tmp-IP-Address-0 := "%{1}"
- &control.Tmp-String-1 := "%{2}"
+ &control.NAS-IP-Address := "%{1}"
+ &control.Acct-Session-Id := "%{2}"
subrequest CoA-Request {
&request := &parent.request
- &request.Acct-Session-Id := &parent.control.Tmp-String-1
+ &request.Acct-Session-Id := &parent.control.Acct-Session-Id
&request.Event-Timestamp := "%l"
&request.Message-Authenticator := 0x00
&request -= &SQL-User-Name[*]
&request -= &Acct-Delay-Time[*]
&request -= &Proxy-State[*]
- switch &parent.control.Tmp-IP-Address-0 {
+ switch &parent.control.NAS-IP-Address {
case "192.0.2.1" {
&parent.control.Tmp-Integer-0 += 1
radius-originate-coa-192.0.2.1
}
- case {
+ default {
&parent.control += {
- &Reply-Message = "Missing map for NAS: %{parent.control.Tmp-IP-Address-0}"
+ &Reply-Message = "Missing map for NAS: %{parent.control.NAS-IP-Address}"
}
}
} # subrequest
+WARNING: This configuration file has not been updated for v4,
+ and therefore WILL NOT WORK. Please do not use it.
}
send Access-Accept {
# if (!&reply.State) {
-# &reply.State := "0x%{randstr:16h}"
+# &reply.State := "0x%randstr(16h)"
# }
&reply += &session-state
eap
# &control.IP-Pool.Name := "local"
# dhcp_sqlippool
if (ok) {
- &reply.Your-IP-Address := "%{%{request.Requested-IP-Address}:-%{request.Client-IP-Address}}"
+ &reply.Your-IP-Address := "%{&request.Requested-IP-Address || &request.Client-IP-Address}"
}
# ok
}
ok
}
#recv Inform {
-# &reply.Packet-Dst-Port = 67
+# &reply.Net.Dst.Port = 67
# &reply.Message-Type = Ack
-# &reply.Server-Identifier = "%{Packet-Dst-IP-Address}"
+# &reply.Server-Identifier = "%{Net.Dst.IP}"
# &reply.Site-specific-28 = 0x0a00
# ok
#}
- The encrypt expansions of the `rlm_cipher` module.
- The `%(3gpp_pseudonym_encrypt:)` expansion.
-- The `%{rand:}` expansion.
+- The `%randstr()` expansion.
NOTE: Add a `&reply.Next-Pseudonym-Id` attribute in this section to
avoid having the permanent Id of the SIM exposed during subsequent
There's not much to do here as the majority of the work for
session resumption is done in `load session { ... }`.
-Add a `&reply.Next-Pseudonym-Id` attribute in this section if you want
+Add a `&reply.Next-Pseudonym` attribute in this section if you want
to allow avoid having the permanent Id of the SIM exposed during
subsequent authentication attempts.
attribute is found, this section will be called.
You should restore the contents of the following attributes using
-`&session-state.Session-ID` or `&reply.Next-Reauth-Id` as a key:
+`&request.Session-ID` as a key:
- `&session-state.Counter`:: How many times this session has
been resumed.
This section will be called prior to attempting re-authentication.
You should restore the contents of the following attributes using
-`&session-state.Session-ID` or `&reply.Next-Reauth-Id` as a key:
+`&request.Session-ID` as a key:
- `&session-state.Counter`:: How many times this session has
been resumed.
This section we be called if authentication or re-authentication fails.
-You should remove any session information stored against
-`&Next-Reauth-Id`.
+You should remove any session information stored against `&request.Session-ID`.
== Default Configuration
Sample virtual server for receiving entries from an LDAP directory
using the https://tools.ietf.org/html/rfc4533[RFC 4533] (LDAP Content Synchronization Operation) in
-refreshAndPersist mode.
+refreshAndPersist mode, Active Directory using its LDAP_SERVER_NOTIFY_OID
+server control, or a directory implementing Persistent Search as
+described in https://tools.ietf.org/id/draft-ietf-ldapext-psearch-03.txt
Persistent searches work in a similar way to normal searches except
to receive notifications of changes to entries so we can disseminate that
information or act on it.
+Note: Each of the three implementations of LDAP synchronisation behave
+differently:
+
+https://tools.ietf.org/html/rfc4533[RFC 4533]
+--------
+This provides a robust mechanism to allow clients to maintain a
+cached copy of a fragment of a directory by the use of cookies which
+can be returned to the server indicating the last successfully processed
+updates from the server.
+
+However, when an object is deleted from the directory, the entry which is
+received only contains the DN, or, if the deletion is reported as part of
+the initial refresh phase it may only be the UUID.
+
+Active Direcory
+---------------
+Active Directory will only provide updates from the time the query started;
+there is no mechanism to catch up on changes which occured while the
+client was not connected. In addition it is not possible to apply a
+filter to the query so that only a subset of objects are considered.
+
+If notification is required when objects are deleted, then the Recycle Bin
+has to be enabled on Active Directory and a query must be running which
+includes the Deleted Objects container.
+
+Active Directory will only perform persistent searches if the filter is
+(objectClass=*). To overcome this limitation, FreeRADIUS allows other
+LDAP filters to be specified which are applied in FreeRADIUS before passing
+packets to the relevant processing sections.
+
+This implementation of LDAP filters is not intended to be complete, but
+covers the most likely to be required.
+
+One key limitation, due to not having the LDAP schema to interpret attribute
+types, is that >=, <= and bitwise filters are assumed to be on integer values.
+
+If your Active Directory tree contains multiple domains, you will need a
+query for each domain that is of interest; running a query at the base
+of the tree with a scope of "sub" does not include any domains other than
+the base.
+
+Depending on the attributes of interest, the number of notifications of
+changes received can be reduced by running the LDAP query against the
+Global Catalog rather than the normal AD LDAP server.
+
+Persistent Search
+-----------------
+Servers implementing Persistent Search have the option to return the full
+directory contents, or simply start reporting changes from the point when
+the query was run.
+
+The draft says that servers SHOULD include a changeNumber when reporting
+changes to keep track of progress - however this has not been observed in
+any testing. If this is implemented, then it can behave in a similar
+manner to the cookie defined in https://tools.ietf.org/html/rfc4533[RFC 4533]; a search against the change log
+with a filter of (changeNumber>=n) could be used to read changes since
+change number 'n'.
+
+
+Note on user group membership
+-----------------------------
+Many directories provide a virtual memberOf attribute which lists
+which groups a user is a member of.
+
+With the directories which have been tested, including OpenLDAP and
+Active Directory it has been observed that modifying group member lists
+does not result in notification of changes to the users, even though
+other modifications to the user will result in a notification which
+can include the memberOf attribute.
+
+Instead group membeship changes are reported as changes to the group object.
+
+
Each virtual-server may have multiple listen sections, with each
listen section containing multiple sync sections.
Administrator account for persistent search.
If using SASL + KRB5 these should be commented out.
-How long to wait after failing to initialise a persistent search
-or after receiving malformed/unexpected messages from the LDAP server.
-How long to wait before reinitialising the connection if we become
-disconnected from the LDAP directory.
+timeouts may need to be longer than for normal LDAP queries
+if a refresh phase returns a lot of data.
+
SASL parameters to use for binding as the sync user.
SASL realm. Used for kerberos.
+How big the kernel's receive buffer should be.
+
+
+
+Maximum number of updates to have outstanding
+When this number is reached, no more are read, potentially
+causing the receive buffer to fill which will cause the
+change notifications to queue up on the LDAP server
+
+
+
+When directories provide cookies to track progress through
+the list of changes, these can be provided on every update,
+which can be an excessive rate.
+
+FreeRADIUS keeps track of pending change and will only call
+store Cookie once the preceding changes have been processed.
+
+These options rate limit how often cookies will be stored.
+Provided all preceding changes have been processed, cookie Store
+will be called on a timed interval or after a number of changes
+have been completed, whichever occurs first.
+
+How often to store cookies.
+
+
+
+Number of completed changes which will prompt the storing
+of a cookie
+
+
+
Persistent searches.
-You can configure an unlimited (within reason), number of syncs
-to retrieve entries from the LDAP directory.
+You can configure an unlimited (within reason, and any limitations
+of the directory you are querying), number of syncs to retrieve
+entries from the LDAP directory.
Where to start searching in the tree for entries
+
Only return entries matching this filter
Comment this out if all entries should be returned.
+
Search scope, may be 'base', 'one', 'sub' or 'children'
-Specify the list of attributes to return in entries,
-one config item per LDAP attribute.
-If no attr config items are specified, then all attributes
-will be returned.
+Specify a map of LDAP attributes to FreeRADIUS dictionary attributes.
+
+The result of this map determines how attriubtes from the LDAP
+query are presented in the requests processed by the various
+"recv" sections below.
+
+This is a very limited form of map:
+ - the left hand side must be an attribute reference.
+ - the right hand side is LDAP attributes which will be
+ included in the query.
+ - only = and += are valid operators with = setting a
+ single instance of the attriubte and += setting as
+ many as the LDAP query returns.
+
+Protocol specific attributes must be qualified e.g. &Proto.radius.User-Name
+
+
+
+
+Since there are likely to be multiple members of
+a given group, the += operator should be used when
+defining a mapping of LDAP attribute "member" to
+FreeRADIUS attributes.
+
+
+
+If you are querying Active Directory, you are likely to
+want two queries.
+
+It should be noted that Active Directory will only respond
+to queries with the LDAP_SERER_NOTIFICATION_OID control if
+the filter is (objectClass=*) - any other filter will result
+in an error.
+
+To overcome this limitation, a subset of LDAP filter handling
+has been implemented in FreeRADIUS allowing a filter to be
+specified in this configuration. The key limitation is
+<=, >= and bitwise filters assume attributes are numeric.
+
+The only extensible match filters supported are the Active
+Directory bitwise AND and OR.
+
+A suitable filter, to only receive notificaitons regarding
+normal user accounts could be:
+
+ (userAccountControl:1.2.840.113556.1.4.803:=512)
+
+In addition, there is nothing returned by Active Directory to
+distinguish between an object being added or being modified.
+All LDAP entries which are returned are therefore processed
+through the recv Modify section when the directory is Active
+Directory.
+
+By default Active Directory puts a limit of 5 on the number
+of persistent searches which can be active on a connection.
+
+To determine if an object is enabled or disabled, the attribute
+userAccountControl can be evaluated. This is returned as
+string data, so will want mapping to an integer attribute
+for processing.
+
+Firstly, one based on a naming context which covers all
+user objects. This will return results when objects are
+added, modified or restored from the Deleted Objects
+container.
+
+
+
+
+Secondly, if you have the Recycle Bin enabled in Active
+Directory and wish to be notified about deleted objects,
+add a search covering the Deleted Objects container.
+
+This will return results when an object is deleted, however
+the DN and CN of the object are changed. The attribute
+lastKnownParent identifies where the object was deleted
+from. However, lastKnownParent may not be returned when
+searching the Global Catalog.
+
Provides FreeRADIUS with the last cookie value we received for the sync
+This only applies to directories implementing RFC4533, such as OpenLDAP with
+the syncprov overlay enabled.
+
+For other directories, this will be called prior to the search query being
+sent to the server, so could be used for any initial setup of cache datastores.
+
A request will be generated with the following attributes:
-- &request.LDAP-Sync-DN the base_dn of the sync.
-- &request.LDAP-Sync-Filter the filter of the sync (optional).
-- &request.LDAP-Sync-Scope the scope of the sync (optional).
-- &request.LDAP-Sync-attr the attributes returned by the sync (optional).
+- &request.LDAP-Sync.DN the base_dn of the sync.
+- &request.LDAP-Sync.Filter the filter of the sync (optional).
+- &request.LDAP-Sync.Scope the scope of the sync (optional).
You should use these attributes to uniquely identify the sync when retrieving
previous cookie values.
+In addition the attribute &request.LDAP-Sync.Directory-Root-DN will be
+populated with the root DN of the directory to aid creating a cookie if one
+has not previously been stored.
+
Called for a sync when:
- FreeRADIUS first starts.
- If a sync experiences an error and needs to be restarted.
- If a connection experiences an error and needs to be restarted.
The section may return one of the following/codes attributes:
-- ok/updated and &reply.LDAP-Sync-Cookie to indicate a cookie value was loaded.
+- ok/updated and &reply.LDAP-Sync.Cookie to indicate a cookie value was loaded.
- noop to indicate that no cookie was found.
- A cookie value representing the current state of the LDAP server may then be
- optionally synthesised to avoid a complete refresh phase.
- Any other code to indicate failure.
+
+
+If no cookie is returned for https://tools.ietf.org/html/rfc4533[RFC 4533] servers, then the response
+to the initial search will be a complete copy of the directory.
+To avoid that, and just be notified about changes, a cookie which
+matches that which OpenLDAP expects can be mocked up with the following,
+presuming the ldap module is enabled and configured with the same
+server settings as ldap_sync.
+
+
+
+
Stores the latest cookie we've received for a sync
+This only applies to directories implementing RFC4533, such as OpenLDAP with
+the syncprov overlay enabled.
+
+For directories implementing persistent search, which return changeNumber
+in the Entry Change Notice control, this section will be called with the
+changeNumber in LDAP-Sync.Cookie.
+
A request will be generated with the following attributes:
-- &request.LDAP-Sync-DN the base_dn of the sync.
-- &request.LDAP-Sync-Cookie the cookie value to store.
-- &request.LDAP-Sync-Filter the filter of the sync (optional).
-- &request.LDAP-Sync-Scope the scope of the sync (optional).
-- &request.LDAP-Sync-attr the attributes returned by the sync (optional).
+- &request.LDAP-Sync.DN the base_dn of the sync.
+- &request.LDAP-Sync.Cookie the cookie value to store.
+- &request.LDAP-Sync.Filter the filter of the sync (optional).
+- &request.LDAP-Sync.Scope the scope of the sync (optional).
The return code of this section is ignored.
+
+
Notification that a new entry has been added to the LDAP directory
-It is recommended that cached entries use LDAP-Sync-Entry-UUID as the key.
-This can be usually be retrieved from the entryUUID operational attribute.
+For directories implementing https://tools.ietf.org/html/rfc4533[RFC 4533], it is recommended that cached entries
+use LDAP-Sync.Entry-UUID as the key.
+This can usually be retrieved from the entryUUID operational attribute.
+
+The entryUUID has the benefit that it will remain constant even if an object's
+DN is changed.
-Delete operations may not include the DN of the object if they occur during
-a refreshDeletes phase.
+Delete and Present operations may not include the DN of the object if they occur
+during a refresh stage.
A request will be generated with the following attributes:
-- &request.LDAP-Sync-DN the base_dn of the sync.
-- &request.LDAP-Sync-Entry-UUID the UUID of the object.
-- &request.LDAP-Sync-Entry-DN the DN of the object that was added.
+- &request.LDAP-Sync.DN the base_dn of the sync.
+- &request.LDAP-Sync.Entry-UUID the UUID of the object. (https://tools.ietf.org/html/rfc4533[RFC 4533] directories only)
+- &request.LDAP-Sync.Entry-DN the DN of the object that was added.
- &*:* attributes mapped from the LDAP entry to FreeRADIUS
attributes using the update section within the sync.
-- &request.LDAP-Sync-Filter the filter of the sync (optional).
-- &request.LDAP-Sync-Scope the scope of the sync (optional).
-- &request.LDAP-Sync-attr the attributes returned by the sync (optional).
+- &request.LDAP-Sync.Filter the filter of the sync (optional).
+- &request.LDAP-Sync.Scope the scope of the sync (optional).
+- &request.LDAP-Sync.Attr the attributes returned by the sync (optional).
The return code of this section is ignored (for now).
+
+
Notification that an entry has been modified in the LDAP directory
-It is recommended that cached entries use LDAP-Sync-Entry-UUID as the key.
-This can be usually be retrieved from the entryUUID operational attribute.
+For directories implementing https://tools.ietf.org/html/rfc4533[RFC 4533], it is recommended that cached entries
+use LDAP-Sync.Entry-UUID as the key.
+This can usually be retrieved from the entryUUID operational attribute.
-Delete operations may not include the DN of the object if they occur during
-a refreshDeletes phase.
+Delete and Present operations may not include the DN of the object if they occur
+during a refresh stage.
A request will be generated with the following attributes:
-- &request.LDAP-Sync-DN the base_dn of the sync.
-- &request.LDAP-Sync-Entry-UUID the UUID of the object.
-- &request.LDAP-Sync-Entry-DN the DN of the object that was added.
+- &request.LDAP-Sync.DN the base_dn of the sync.
+- &request.LDAP-Sync.Entry-UUID the UUID of the object. (https://tools.ietf.org/html/rfc4533[RFC 4533] directories only)
+- &request.LDAP-Sync.Entry-DN the DN of the object that was added.
- &*:* attributes mapped from the LDAP entry to FreeRADIUS
attributes using the update section within the sync.
-- &request.LDAP-Sync-Filter the filter of the sync (optional).
-- &request.LDAP-Sync-Scope the scope of the sync (optional).
-- &request.LDAP-Sync-attr the attributes returned by the sync (optional).
+- &request.LDAP-Sync.Filter the filter of the sync (optional).
+- &request.LDAP-Sync.Scope the scope of the sync (optional).
+- &request.LDAP-Sync.Original-DN the original DN of the object, if the object was renamed
+ and the directory returns this attribute.
The return code of this section is ignored (for now).
+
+
Notification that an entry has been modified in the LDAP directory
-It is recommended that cached entries use LDAP-Sync-Entry-UUID as the key.
-This can be usually be retrieved from the entryUUID operational attribute.
+It is recommended that cached entries use LDAP-Sync.Entry-UUID as the key.
+This can usually be retrieved from the entryUUID operational attribute.
+
+Delete and Present operations may not include the DN of the object if they occur
+during a refresh stage.
+
+A request will be generated with the following attributes:
+
+- &request.LDAP-Sync.DN the base_dn of the sync.
+- &request.LDAP-Sync.Entry-UUID the UUID of the object. (https://tools.ietf.org/html/rfc4533[RFC 4533] directories only)
+- &request.LDAP-Sync.Entry-DN the DN of the object that was removed (optional).
+- &*:* attributes mapped from the LDAP entry to FreeRADIUS
+ attributes using the update section within the sync,
+ if the attributes are returned by the directory.
+- &request.LDAP-Sync.Filter the filter of the sync (optional).
+- &request.LDAP-Sync.Scope the scope of the sync (optional).
+
+The return code of this section is ignored (for now).
+
-Delete operations may not include the DN of the object if they occur during
-a refreshDeletes phase.
+
+Notification that an entry is still present and unchanged in the LDAP directory.
+
+These only occur with https://tools.ietf.org/html/rfc4533[RFC 4533] servers during initial refresh when a sync starts
+and a cookie has been provided to indicate the last known state of the directory
+according to the client.
+
+It is recommended that cached entries use LDAP-Sync.Entry-UUID as the key.
+This can usually be retrieved from the entryUUID operational attribute.
+
+Delete and Present operations may not include the DN of the object if they occur
+during a refresh stage.
A request will be generated with the following attributes:
-- &request.LDAP-Sync-DN the base_dn of the sync.
-- &request.LDAP-Sync-Entry-UUID the UUID of the object.
-- &request.LDAP-Sync-Entry-DN the DN of the object that was added (optional).
-- &request.LDAP-Sync-Filter the filter of the sync (optional).
-- &request.LDAP-Sync-Scope the scope of the sync (optional).
-- &request.LDAP-Sync-attr the attributes returned by the sync (optional).
+- &request.LDAP-Sync.DN the base_dn of the sync.
+- &request.LDAP-Sync.Entry-UUID the UUID of the object. (https://tools.ietf.org/html/rfc4533[RFC 4533] directories only)
+- &request.LDAP-Sync.Entry-DN the DN of the object that is still present (optional).
+- &request.LDAP-Sync.Filter the filter of the sync (optional).
+- &request.LDAP-Sync.Scope the scope of the sync (optional).
The return code of this section is ignored (for now).
+
== Default Configuration
```
-server ldap {
- namespace = ldap
+server ldap_sync {
+ namespace = ldap_sync
listen {
- type = sync
- server = "localhost"
-# port = 389
-# identity = 'cn=admin,dc=example,dc=org'
-# password = mypass
-# sync_retry_interval = 5.0
-# conn_retry_interval = 5.0
- sasl {
-# mech = 'PLAIN'
-# proxy = 'autz_id'
-# realm = 'example.org'
+ transport = ldap
+ ldap {
+ server = "localhost"
+# port = 389
+ identity = 'cn=admin,dc=example,dc=com'
+ password = mypass
+ options {
+ res_timeout = 20
+ srv_timelimit = 120
+ idle = 60
+ probes = 3
+ interval = 3
+ reconnection_delay = 10
+ }
+ sasl {
+# mech = 'PLAIN'
+# proxy = 'autz_id'
+# realm = 'example.org'
+ }
+# recv_buff = 1048576
+# max_outstanding = 65536
}
+ cookie_interval = 10
+ cookie_changes = 100
sync {
- base_dn = "ou=people,dc=example,dc=org"
- filter = "(objectClass=PosixAccount)"
-# scope = 'sub'
-# attr = 'cn'
-# attr = 'foo'
+ base_dn = "ou=people,dc=example,dc=com"
+ filter = "(objectClass=posixAccount)"
+ scope = 'sub'
update {
- &User-Name := 'cn'
- &Password.With-Header := 'userPassword'
+ &Proto.radius.User-Name = 'uid'
+ &Password.With-Header = 'userPassword'
}
}
- sync {
- base_dn = "ou=groups,dc=example,dc=org"
- filter = "(objectClass=unixGroup}"
- }
+# sync {
+# base_dn = "ou=groups,dc=example,dc=com"
+# filter = "(objectClass=groupOfNames)"
+# scope = "sub"
+# update {
+# &Tmp-String-0 += "member"
+# }
+# }
+# sync {
+# base_dn = 'cn=Users,dc=example,dc=com'
+# filter = '(userAccountControl:1.2.840.113556.1.4.803:=512)'
+# scope = 'sub'
+# update {
+# &Proto.radius.User-Name = 'sAMAccountName'
+# &Tmp-Integer-0 = 'userAccountControl'
+# }
+# }
+# sync {
+# base_dn = "CN=Deleted Objects,dc=example,dc=com"
+# filter = '(userAccountControl:1.2.840.113556.1.4.803:=512)'
+# scope = "one"
+# update {
+# &Proto.radius.User-Name = 'sAMAccountName'
+# &Tmp-Integer-0 = 'userAccountControl'
+# &Tmp-String-0 = 'lastKnownParent'
+# }
+# }
}
load Cookie {
- debug_all
+ debug_request
+# if (!&reply.LDAP-Sync.Cookie) {
+# string csn
+# &csn := %ldap("ldap:///%{LDAP-Sync.Directory-Root-DN}?contextCSN?base")
+# if (&csn) {
+# &reply.LDAP-Sync.Cookie := "rid=000,csn=%{csn}"
+# updated
+# }
+# }
}
store Cookie {
- debug_all
+ debug_request
}
recv Add {
- debug_all
+ debug_request
}
recv Modify {
- debug_all
+ debug_request
}
recv Delete {
- debug_all
+ debug_request
+ }
+ recv Present {
+ debug_request
}
}
```
+WARNING: This configuration file has not been updated for v4,
+ and therefore WILL NOT WORK. Please do not use it.
+
pap
}
-
...
+ }
The configuration for this virtual server follows and should be amended as
-required...
+required.
type = Status-Server
}
recv Status-Server {
- if ("%{sql:SELECT pg_is_in_recovery()}" != "f") {
+ if ("%sql('SELECT pg_is_in_recovery()')" != "f") {
if ("%{db_online:}" != "fail") {
%{db_online:fail}
}
+WARNING: This configuration file has not been updated for v4,
+ and therefore WILL NOT WORK. Please do not use it.
+
&reply.Error-Code = No-Error
&reply.Cookie = &MAC-Address
&reply.VLAN-Name = "please_use_real_vlan_here"
-# &reply.VLAN-Name = "%{sql:select ... where mac='%{MAC-Address}'}"
+# &reply.VLAN-Name = %sql("select ... where mac='%{MAC-Address}'")
}
send Join-Response {
ok
#
# key:: Cache key.
#
- key = "%{%{control.State}:-%{%{reply.State}:-%{State}}}"
+ key = "%{&control.State || &reply.State || &State}"
#
# ttl:: TTL for cache entries.
#
# This key is dynamically expanded at run time.
#
- acct_key = "radacct_%{%{Acct-Unique-Session-Id}:-%{Acct-Session-Id}}"
+ acct_key = "radacct_%{&Acct-Unique-Session-Id || &Acct-Session-Id}"
#
# doctype:: Value for the 'docType' element in the json body for accounting documents.
#
# user_key:: Couchbase document key for user documents (`unlang` supported).
#
- user_key = "raduser_%md5(%tolower(%{%{Stripped-User-Name}:-%{User-Name}}))"
+ user_key = "raduser_%md5(%tolower(%{&Stripped-User-Name || &User-Name}))"
#
# read_clients:: Set to `yes` to read radius clients from the Couchbase view specified below.
#
# The content of this attribute is used to match the `name` of the entry.
#
-# key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+# key = "%{&Stripped-User-Name || &User-Name}"
#
# filename:: The old `users` style file is now located here.
# filter:: Filter for user objects, should be specific enough
# to identify a single user object.
#
- filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
+ filter = "(uid=%{&Stripped-User-Name || &User-Name})"
# For Active Directory nested group, you should comment out the previous 'filter = ...'
# and use the below. Where 'group' is the group you are querying for.
#
# See: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
#
-# filter = "(&(objectClass=user)(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})(memberOf:1.2.840.113556.1.4.1941:=cn=group,${..base_dn}))"
+# filter = "(&(objectClass=user)(sAMAccountName=%{&Stripped-User-Name || &User-Name})(memberOf:1.2.840.113556.1.4.1941:=cn=group,${..base_dn}))"
#
# sasl { ... }:: SASL parameters to use for user binds
# That is, group objects with attributes that identify
# members (the inverse of `membership_attribute`).
#
-# membership_filter = "(|(member=%{control.Ldap-UserDn})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))"
+# membership_filter = "(|(member=%{control.Ldap-UserDn})(memberUid=%{&Stripped-User-Name || &User-Name}))"
#
# membership_attribute:: The attribute, in user objects, which contain
# Don't log anything for these packets.
Interim-Update = ""
- Accounting-On = "NAS %{Net.Src.IP} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) just came online"
- Accounting-Off = "NAS %{Net.Src.IP} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) just went offline"
+ Accounting-On = "NAS %{Net.Src.IP} (%{&NAS-IP-Address || &NAS-IPv6-Address}) just came online"
+ Accounting-Off = "NAS %{Net.Src.IP} (%{&NAS-IP-Address || &NAS-IPv6-Address}) just went offline"
# Don't log anything for other `Acct-Status-Type` 's.
- unknown = "NAS %{Net.Src.IP} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) sent unknown Acct-Status-Type %{Acct-Status-Type}"
+ unknown = "NAS %{Net.Src.IP} (%{&NAS-IP-Address || &NAS-IPv6-Address}) sent unknown Acct-Status-Type %{Acct-Status-Type}"
}
}
# structure is small, around `32` characters, so that will limit
# the possible choices of keys.
#
- # TIP: You may want instead: `%{%{Stripped-User-Name}:-%{User-Name}}`.
+ # TIP: You may want instead: `%{&Stripped-User-Name || &User-Name}`.
#
username = %{User-Name}
# Otherwise the client hardware address must be used. To comply with
# this requirement use the following:
#
-# owner = "%{%{Client-Identifier}:-%{Client-Hardware-Address}}"
+# owner = "%{&Client-Identifier || &Client-Hardware-Address}"
#
# For purposes such as IP assignment using a RADIUS Framed-IP-Address
#
# requested_address:: The IP address being renewed or released.
#
- requested_address = "%{%{Requested-IP-Address}:-%{Net.Src.IP}}"
+ requested_address = "%{&Requested-IP-Address || &Net.Src.IP}"
#
# ipv4_integer:: Whether IPv4 addresses should be cast to integers, for renew operations.
# ### Start
#
Start {
- insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
+ insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{&NAS-IP-Address || &NAS-IPv6-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
trim = "LTRIM %{User-Name} 0 ${..trim_count}"
expire = "EXPIRE %{User-Name} ${..expire_time}"
}
# ### Interim-Update
#
Interim-Update {
- insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
+ insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{&NAS-IP-Address || &NAS-IPv6-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
trim = "LTRIM %{User-Name} 0 ${..trim_count}"
expire = "EXPIRE %{User-Name} ${..expire_time}"
}
# ### Stop
#
Stop {
- insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
+ insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{&NAS-IP-Address || &NAS-IPv6-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}"
trim = "LTRIM %{User-Name} 0 ${..trim_count}"
expire = "EXPIRE %{User-Name} ${..expire_time}"
}
# The `query` parameter specifies the SQL query used to get the current Counter value
# from the database.
#
-# key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+# key = "%{&Stripped-User-Name || &User-Name}"
#
# reset_period_start_name:: The name of the attribute which is used to store the
# time that the current reset period started.
counter_name = &control.Daily-Session-Time
check_name = &control.Max-Daily-Session
reply_name = &reply.Session-Timeout
- key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+ key = "%{&Stripped-User-Name || &User-Name}"
reset = daily
# * `%{dhcpv4.Client-Hardware-Address}` which binds the lease to the
# mac address of the user's device.
#
- # * `%{%{dhcpv4.Client-Identifier}:-%{dhcpv4.Client-Hardware-Address}}`
+ # * `%{&dhcpv4.Client-Identifier || &dhcpv4.Client-Hardware-Address}`
# which binds the lease to either the custom identifier set by the
# DHCP client, or if this is absent, the mac address of the user's
# device.
# owner = "%{radius.Vendor-Specific.ADSL-Forum.Agent-Circuit-ID}.%{radius.Calling-Station-Id}"
-# owner = "%{%{dhcpv4.Client-Identifier}:-%{dhcpv4.Client-Hardware-Address}}"
+# owner = "%{&dhcpv4.Client-Identifier || &dhcpv4.Client-Hardware-Address}"
#
# requested_address:: The IP address being renewed or released.
# For RADIUS the requested_address will almost always be `%{radius.Framed-IP-Address}`.
#
# For DHCPv4 the requested_address will almost always be
- # `%{%{dhcpv4.Requested-IP-Address}:-%{dhcpv4.Client-IP-Address}}`.
+ # `%{&dhcpv4.Requested-IP-Address || &dhcpv4.Client-IP-Address}`.
#
requested_address = "%{radius.Framed-IP-Address}"
-# requested_address = "%{%{dhcpv4.Requested-IP-Address}:-%{dhcpv4.Client-IP-Address}}"
+# requested_address = "%{&dhcpv4.Requested-IP-Address || &dhcpv4.Client-IP-Address}"
#
# gateway:: The device controlling access to the network or relaying
# For DHCPv4, this device is recorded so that we can respond correctly
# to lease queries.
#
- gateway = "%{%{radius.NAS-Identifier}:-%{radius.NAS-IP-Address}}"
+ gateway = "%{&radius.NAS-Identifier || &radius.NAS-IP-Address}"
# gateway = "%{dhcpv4.Gateway-IP-Address}"
#
# username:: The username to pass to `winbind` for authentication.
#
- username = "%{%{Stripped-User-Name}:-%{User-Name}}"
+ username = "%{&Stripped-User-Name || &User-Name}"
#
# domain:: The windows domain.
# This should generally not include a realm, so `Stripped-User-Name`
# is likely the best attribute if it exists.
#
- search_username = "%{%{Stripped-User-Name}:-%{User-Name}}"
+ search_username = "%{&Stripped-User-Name || &User-Name}"
#
# add_domain:: Include the domain in group searches.
# Replace the User-Name with the Stripped-User-Name, if it exists.
#
#DEFAULT
-# User-Name := "%{%{Stripped-User-Name}:-%{User-Name}}"
+# User-Name := "%{&Stripped-User-Name || &User-Name}"
(username, authdate, spi, mipkey, lifetime) \
VALUES ( \
'%{User-Name}', '%S' \
- '%{%{reply.WiMAX.MN-hHA-MIP4-SPI}:-%{reply.WiMAX.MN-hHA-MIP6-SPI}}', \
- '%{%{reply.WiMAX.MN-hHA-MIP4-Key}:-%{reply.WiMAX.MN-hHA-MIP6-Key}}', '%{%{reply.Session-Timeout}:-86400}' )"
+ '%{&reply.WiMAX.MN-hHA-MIP4-SPI || &reply.WiMAX.MN-hHA-MIP6-SPI}', \
+ '%{&reply.WiMAX.MN-hHA-MIP4-Key || &reply.WiMAX.MN-hHA-MIP6-Key}', '%{%{reply.Session-Timeout}:-86400}' )"
AcctTerminateCause = '%{%{Acct-Terminate-Cause}:-NAS-Reboot}', \
Class = '%{Class}' \
WHERE AcctStopTime IS NULL \
- AND NASIPAddress= '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \
+ AND NASIPAddress= '%{&NAS-IPv6-Address || &NAS-IP-Address}' \
AND AcctStartTime <= ${....event_timestamp}"
#
'%{Acct-Unique-Session-Id}', \
'%{SQL-User-Name}', \
NULLIF('%{Realm}', ''), \
- '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \
- NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \
+ '%{&NAS-IPv6-Address || &NAS-IP-Address}', \
+ NULLIF('%{&NAS-Port-ID || &NAS-Port}', ''), \
'%{NAS-Port-Type}', \
${....event_timestamp}, \
${....event_timestamp}, \
'%{Acct-Unique-Session-Id}', \
'%{SQL-User-Name}', \
NULLIF('%{Realm}', ''), \
- '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \
- NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \
+ '%{&NAS-IPv6-Address || &NAS-IP-Address}', \
+ NULLIF('%{&NAS-Port-ID || &NAS-Port}', ''), \
'%{NAS-Port-Type}', \
${....event_timestamp}, \
${....event_timestamp}, \
'%{Acct-Unique-Session-Id}', \
'%{SQL-User-Name}', \
NULLIF('%{Realm}', ''), \
- '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \
- NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \
+ '%{&NAS-IPv6-Address || &NAS-IP-Address}', \
+ NULLIF('%{&NAS-Port-ID || &NAS-Port}', ''), \
'%{NAS-Port-Type}', \
TO_TIMESTAMP(${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \
${....event_timestamp}, \
'%{SQL-User-Name}', \
'%{Realm}', \
'%{NAS-IP-Address}', \
- '%{%{NAS-Port-ID}:-%{NAS-Port}}', \
+ '%{&NAS-Port-ID || &NAS-Port}', \
'%{NAS-Port-Type}', \
${....event_timestamp}, \
${....event_timestamp}, \
'%{SQL-User-Name}', \
'%{Realm}', \
'%{NAS-IP-Address}', \
- '%{%{NAS-Port-ID}:-%{NAS-Port}}', \
+ '%{&NAS-Port-ID || &NAS-Port}', \
'%{NAS-Port-Type}', \
(${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \
${....event_timestamp}, \
'%{SQL-User-Name}', \
'%{Realm}', \
'%{NAS-IP-Address}', \
- '%{%{NAS-Port-ID}:-%{NAS-Port}}', \
+ '%{&NAS-Port-ID || &NAS-Port}', \
'%{NAS-Port-Type}', \
(${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \
${....event_timestamp}, \
# is not included
#
else {
- &request.Acct-Unique-Session-Id := "%hex(%md5(%string(%{User-Name},%{Acct-Multi-Session-ID},%{Acct-Session-ID},%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}},%{NAS-Identifier},%{NAS-Port-ID},%{NAS-Port})))"
+ &request.Acct-Unique-Session-Id := "%hex(%md5(%string(%{User-Name},%{Acct-Multi-Session-ID},%{Acct-Session-ID},%{&NAS-IPv6-Address || &NAS-IP-Address},%{NAS-Identifier},%{NAS-Port-ID},%{NAS-Port})))"
}
}
# dhcp_sqlippool
if (ok) {
- &reply.Your-IP-Address := "%{%{request.Requested-IP-Address}:-%{request.Client-IP-Address}}"
+ &reply.Your-IP-Address := "%{&request.Requested-IP-Address || &request.Client-IP-Address}"
}
# If Message-Type is not set, returning "ok" or