summaryrefslogtreecommitdiffstats
path: root/raddb/policy.d
diff options
context:
space:
mode:
Diffstat (limited to 'raddb/policy.d')
-rw-r--r--raddb/policy.d/abfab-tr106
-rw-r--r--raddb/policy.d/accounting134
-rw-r--r--raddb/policy.d/canonicalization113
-rw-r--r--raddb/policy.d/control40
-rw-r--r--raddb/policy.d/cui131
-rw-r--r--raddb/policy.d/debug64
-rw-r--r--raddb/policy.d/dhcp327
-rw-r--r--raddb/policy.d/eap54
-rw-r--r--raddb/policy.d/filter211
-rw-r--r--raddb/policy.d/moonshot-targeted-ids249
-rw-r--r--raddb/policy.d/operator-name46
-rw-r--r--raddb/policy.d/rfc754246
12 files changed, 1521 insertions, 0 deletions
diff --git a/raddb/policy.d/abfab-tr b/raddb/policy.d/abfab-tr
new file mode 100644
index 0000000..3a08853
--- /dev/null
+++ b/raddb/policy.d/abfab-tr
@@ -0,0 +1,106 @@
+#
+# ABFAB Trust router policies.
+#
+# $Id$
+#
+
+
+#
+# Verify rp parameters
+#
+psk_authorize {
+ if (&TLS-PSK-Identity) {
+ # TODO: may need to check trust-router-apc as well
+ if ("%{psksql:select distinct keyid from authorizations_keys where keyid = '%{tls-psk-identity}' and '%{trust-router-coi}' like coi and '%{gss-acceptor-realm-name}' like acceptor_realm and '%{gss-acceptor-host-name}' like hostname;}") {
+ # do things here
+ }
+ else {
+ update reply {
+ Reply-Message = "RP not authorized for this ABFAB request"
+ }
+ reject
+ }
+ }
+}
+
+abfab_client_check {
+ # check that GSS-Acceptor-Host-Name is correct
+ if ("%{client:gss_acceptor_host_name}") {
+ if (&request:GSS-Acceptor-Host-Name) {
+ if (&request:GSS-Acceptor-Host-Name != "%{client:gss_acceptor_host_name}") {
+ update reply {
+ Reply-Message = "GSS-Acceptor-Host-Name incorrect"
+ }
+ reject
+ }
+ }
+ else {
+ # set GSS-Acceptor-Host-Name if it is not set by the mechanism
+ # but it is defined in the client configuration
+ update request {
+ GSS-Acceptor-Host-Name = "%{client:gss_acceptor_host_name}"
+ }
+ }
+ }
+
+ # set Trust-Router-COI attribute from the client configuration
+ if ("%{client:trust_router_coi}") {
+ update request {
+ Trust-Router-COI := "%{client:trust_router_coi}"
+ }
+ }
+
+ # set GSS-Acceptor-Realm-Name attribute from the client configuration
+ if ("%{client:gss_acceptor_realm_name}") {
+ update request {
+ GSS-Acceptor-Realm-Name := "%{client:gss_acceptor_realm_name}"
+ }
+ }
+
+ # set GSS-Acceptor-Service-Name attribute from the client configuration
+ if ("%{client:gss_acceptor_service_name}") {
+ update request {
+ GSS-Acceptor-Service-Name = "%{client:gss_acceptor_service_name}"
+ }
+ }
+
+}
+
+# A policy which is used to validate channel-bindings.
+#
+abfab_channel_bindings {
+ if (&GSS-Acceptor-Service-Name && (&outer.request:GSS-Acceptor-Service-Name != &GSS-Acceptor-Service-Name)) {
+ reject
+ }
+
+ if (&GSS-Acceptor-Host-Name && &outer.request:GSS-Acceptor-Host-Name != &GSS-Acceptor-Host-Name ) {
+ reject
+ }
+
+ if (&GSS-Acceptor-Realm-Name && &outer.request:GSS-Acceptor-Realm-Name != &GSS-Acceptor-Realm-Name ) {
+ reject
+ }
+
+ if (&GSS-Acceptor-Service-Name || &GSS-Acceptor-Realm-Name || &GSS-Acceptor-Host-Name) {
+ update control {
+ &Chbind-Response-Code := success
+ }
+
+ #
+ # ACK the attributes in the request.
+ #
+ # If any one of these attributes don't exist in the request,
+ # then they won't be copied to the reply.
+ #
+ update reply {
+ &GSS-Acceptor-Service-Name = &GSS-Acceptor-Service-Name
+ &GSS-Acceptor-Host-Name = &GSS-Acceptor-Host-Name
+ &GSS-Acceptor-Realm-Name = &GSS-Acceptor-Realm-Name
+ }
+ }
+
+ #
+ # Return "handled" so that the "authenticate" section isn't used.
+ #
+ handled
+}
diff --git a/raddb/policy.d/accounting b/raddb/policy.d/accounting
new file mode 100644
index 0000000..7c52637
--- /dev/null
+++ b/raddb/policy.d/accounting
@@ -0,0 +1,134 @@
+# We check for this prefix to determine whether the class value was
+# generated by this server. It should be changed so that it is
+# globally unique.
+class_value_prefix = 'ai:'
+
+#
+# Replacement for the old rlm_acct_unique module
+#
+acct_unique {
+ #
+ # If we have a class attribute in the format
+ # 'auth_id:[0-9a-f]{32}' it'll have a local value
+ # (defined by insert_acct_class), this ensures
+ # uniqueness and suitability.
+ #
+ # We could just use the Class attribute as
+ # Acct-Unique-Session-Id, but this may cause problems
+ # with NAS that carry Class values across between
+ # multiple linked sessions. So we rehash class with
+ # Acct-Session-ID to provide a truely unique session
+ # identifier.
+ #
+ # Using a Class/Session-ID combination is more robust
+ # than using elements in the Accounting-Request,
+ # which may be subject to change, such as
+ # NAS-IP-Address, Client-IP-Address and
+ # NAS-Port-ID/NAS-Port.
+ #
+ # This policy should ensure that session data is not
+ # affected if NAS IP addresses change, or the client
+ # roams to a different 'port' whilst maintaining its
+ # initial authentication session (Common in a
+ # wireless environment).
+ #
+ update request {
+ &Tmp-String-9 := "${policy.class_value_prefix}"
+ }
+
+ if (("%{hex:&Class}" =~ /^%{hex:&Tmp-String-9}/) && \
+ ("%{string:&Class}" =~ /^${policy.class_value_prefix}([0-9a-f]{32})/i)) {
+ update request {
+ &Acct-Unique-Session-Id := "%{md5:%{1},%{Acct-Session-ID}}"
+ }
+ }
+
+ #
+ # Not All devices respect RFC 2865 when dealing with
+ # the class attribute, so be prepared to use the
+ # older style of hashing scheme if a class attribute
+ # is not included
+ #
+ else {
+ update request {
+ &Acct-Unique-Session-Id := "%{md5:%{User-Name},%{Acct-Session-ID},%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}},%{NAS-Identifier},%{NAS-Port-ID},%{NAS-Port}}"
+ }
+ }
+
+ update request {
+ &Tmp-String-9 !* ANY
+ }
+}
+
+#
+# Insert a (hopefully unique) value into class
+#
+insert_acct_class {
+ update reply {
+ &Class = "${policy.class_value_prefix}%{md5:%t,%{Packet-Src-Port},%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}},%{NAS-IP-Address},%{Calling-Station-ID},%{User-Name},%{session-state:User-Name} }"
+ }
+}
+
+#
+# Merges Acct-[Input|Output]-Octets and Acct-[Input|Output]-Gigawords into Acct-[Input|Output]-Octets64
+#
+# If the &Attr-Foo doesn't exist, it's value is taken as zero.
+#
+acct_counters64.preacct {
+ update request {
+ &Acct-Input-Octets64 = "%{expr:(&Acct-Input-Gigawords << 32) | &Acct-Input-Octets}"
+ &Acct-Output-Octets64 = "%{expr:(&Acct-Output-Gigawords << 32) | &Acct-Output-Octets}"
+ }
+}
+
+#
+# There is a delay between sending the Access-Accept and receiving
+# the corresponding Accounting-Request "start" packet. This delay
+# can be leveraged by a user to bypass Simultaneous-Use checks.
+#
+# The user can start up multiple sessions at the same time. When
+# that happens, both Simultaneous-Use checks are performed before any
+# Accounting-Request packet is received. Both Simultaneous-Use
+# checks will result in "no user session" in the radacct table, and
+# both sessions will be allowed. At some point later in time, the
+# Accounting-Request packets are received. But by then it's too
+# late.
+#
+# The solution is to insert a temporary session into the "radacct"
+# table, during the "post-auth" section. This is done by
+# uncommenting the "sql_session_start" entry in
+# sites-enabled/default. Then, reading
+# raddb/mods-config/sql/main/*/queries.conf, and looking for the
+# "sql_session_start" comments. Follow the instructions there to
+# finalize the configuration.
+#
+# The server will then create a temporary entry in "radacct" before
+# it returns the Access-Request. Any other Access-Request which is
+# received at the same time will then have it's Simultaneous-Use
+# check see that entry, and will be rejected.
+#
+# Subsequent Accounting-Request packets for the first session will
+# then UPDATE (not INSERT) the data for the session.
+#
+# There is still a small race condition as the Simultaneous-Use
+# checks are not done at the same time as updating radacct. But the
+# window of opportunity is much smaller. i.e. milliseconds, instead
+# of seconds.
+#
+# This policy can also be used to "bootstrap" accounting sessions.
+# If there is data which is only available in the Access-Request,
+# it can be placed in the accounting table. Then, when accounting
+# packets are received, they will update the row which contains
+# the session information.
+#
+sql_session_start.post-auth {
+ acct_unique
+
+ #
+ # The SQL accounting queries need an Acct-Status-Type attribute
+ #
+ update request {
+ Acct-Status-Type := Start
+ }
+ sql.accounting
+}
diff --git a/raddb/policy.d/canonicalization b/raddb/policy.d/canonicalization
new file mode 100644
index 0000000..6d90e37
--- /dev/null
+++ b/raddb/policy.d/canonicalization
@@ -0,0 +1,113 @@
+#
+# Split User-Name in NAI format (RFC 4282) into components
+#
+# This policy writes the Username and Domain portions of the
+# NAI into the Stripped-User-Name and Stripped-User-Domain
+# attributes.
+#
+# The regular expression to do this is not strictly compliant
+# with the standard, but it is not possible to write a
+# compliant regexp without perl style regular expressions (or
+# at least not a legible one).
+#
+nai_regexp = '^([^@]*)(@([-[:alnum:]]+\.[-[:alnum:].]+))?$'
+
+split_username_nai {
+ if (&User-Name && (&User-Name =~ /${policy.nai_regexp}/)) {
+ update request {
+ &Stripped-User-Name := "%{1}"
+ }
+
+ # Only add the Stripped-User-Domain attribute if
+ # we have a domain. This means presence checks
+ # for Stripped-User-Domain work.
+ if ("%{3}" != '') {
+ update request {
+ &Stripped-User-Domain = "%{3}"
+ }
+ }
+
+ # If any of the expansions result in a null
+ # string, the update section may return
+ # something other than updated...
+ updated
+ }
+ else {
+ noop
+ }
+}
+
+#
+# If called in post-proxy we modify the proxy-reply message
+#
+split_username_nai.post-proxy {
+ if (&proxy-reply:User-Name && (&proxy-reply:User-Name =~ /${policy.nai_regexp}/)) {
+ update proxy-reply {
+ &Stripped-User-Name := "%{1}"
+ }
+
+ # Only add the Stripped-User-Domain attribute if
+ # we have a domain. This means presence checks
+ # for Stripped-User-Domain work.
+ if ("%{3}" != '') {
+ update proxy-reply {
+ &Stripped-User-Domain = "%{3}"
+ }
+ }
+ updated
+ }
+ else {
+ noop
+ }
+}
+
+#
+# Normalize the MAC Addresses in the Calling/Called-Station-Id
+#
+mac-addr-regexp = '([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})'
+
+#
+# Add "rewrite_called_station_id" in the "authorize" and
+# "preacct" sections.
+#
+# Makes Called-Station-ID conform to what RFC3580 says should
+# be provided by 802.1X authenticators.
+#
+rewrite_called_station_id {
+ if (&Called-Station-Id && (&Called-Station-Id =~ /^${policy.mac-addr-regexp}([^0-9a-f](.+))?$/i)) {
+ update request {
+ &Called-Station-Id := "%{toupper:%{1}-%{2}-%{3}-%{4}-%{5}-%{6}}"
+ }
+
+ # SSID component?
+ if ("%{8}") {
+ update request {
+ &Called-Station-SSID := "%{8}"
+ }
+ }
+ updated
+ }
+ else {
+ noop
+ }
+}
+
+#
+# Add "rewrite_calling_station_id" in the "authorize" and
+# "preacct" sections.
+#
+# Makes Calling-Station-ID conform to what RFC3580 says should
+# be provided by 802.1X authenticators.
+#
+rewrite_calling_station_id {
+ if (&Calling-Station-Id && (&Calling-Station-Id =~ /^${policy.mac-addr-regexp}$/i)) {
+ update request {
+ &Calling-Station-Id := "%{toupper:%{1}-%{2}-%{3}-%{4}-%{5}-%{6}}"
+ }
+ updated
+ }
+ else {
+ noop
+ }
+}
+
diff --git a/raddb/policy.d/control b/raddb/policy.d/control
new file mode 100644
index 0000000..b3f1e03
--- /dev/null
+++ b/raddb/policy.d/control
@@ -0,0 +1,40 @@
+#
+# If you want the server to pretend that it is dead,
+# then use the "do_not_respond" policy.
+#
+do_not_respond {
+ update control {
+ &Response-Packet-Type := Do-Not-Respond
+ }
+ handled
+}
+
+#
+# Send Access-Accept immediately
+#
+accept {
+ update control {
+ &Response-Packet-Type = Access-Accept
+ }
+ handled
+}
+
+#
+# Send Access-Challenge immediately
+#
+challenge {
+ update control {
+ &Response-Packet-Type = Access-Challenge
+ }
+ handled
+}
+
+#
+# Send an Accounting-Response immediately
+#
+acct_response {
+ update control {
+ &Response-Packet-Type = Accounting-Response
+ }
+ handled
+}
diff --git a/raddb/policy.d/cui b/raddb/policy.d/cui
new file mode 100644
index 0000000..08b2c91
--- /dev/null
+++ b/raddb/policy.d/cui
@@ -0,0 +1,131 @@
+#
+# The following policies are for the Chargeable-User-Identity
+# (CUI) configuration.
+#
+# The policies below can be called as just 'cui' (not
+# cui.authorize etc..) from the various config sections.
+#
+
+#
+# cui_hash_key definition
+# This key serves the purpose of protecting CUI values against
+# dictionary attacks, therefore should be chosen as a "random"
+# string and kept secret.
+#
+cui_hash_key = "changeme"
+
+#
+# cui_require_operator_name switch
+# If this is set to nonzero value then CUI will only be added
+# when a non-empty Operator-Name value is present in the request
+#
+cui_require_operator_name = "no"
+
+#
+# The client indicates it can do CUI by sending a CUI attribute
+# containing one zero byte.
+# A non-empty value in Operator-Name can be an additional requirement.
+# Normally CUI support is turned on only for such requests.
+# CUI support can be used for local clients which do not
+# supports CUI themselves, the server can simulate a CUI request
+# adding the missing NUL CUI value and the Operator-Name attribute.
+# Clients which are supposed to get this treatment should
+# be marked by add_cui flag in clients.conf
+# We assume that local clients are marked in the client.conf with
+# add_cui flag, e.g.
+# client xxxx {
+# ...
+# add_cui = yes
+# }
+#
+cui.authorize {
+ if ("%{client:add_cui}" == 'yes') {
+ update request {
+ &Chargeable-User-Identity := 0x00
+ }
+ }
+}
+
+#
+# Before proxing an Access-Request to a remote server, a NUL CUI
+# attribute should be added, unless it is already present in the request.
+#
+cui.pre-proxy {
+ if (("%{request:Packet-Type}" == 'Access-Request') && ("%{client:add_cui}" == 'yes')) {
+ update proxy-request {
+ &Chargeable-User-Identity = 0x00
+ }
+ }
+}
+
+
+#
+# Add a CUI attribute based on the User-Name, and a secret key
+# known only to this server.
+# For EAP-TTLS and EAP-PEAP methods
+# use_tunneled_reply parameter MUST be set to yes
+#
+cui.post-auth {
+ if (!&control:Proxy-To-Realm && &Chargeable-User-Identity && !&reply:Chargeable-User-Identity && \
+ (&Operator-Name || ('${policy.cui_require_operator_name}' != 'yes')) ) {
+ update reply {
+ &Chargeable-User-Identity = "%{sha1:${policy.cui_hash_key}%{tolower:%{User-Name}%{%{Operator-Name}:-}}}"
+ }
+ }
+
+ #
+ # The section below will store a CUI for the User in the DB and remove the
+ # User-Name attribute from the reply if a CUI is present.
+ #
+ # You need to configure the cuisql module and your database for this to work.
+ # If your NAS can do CUI based accounting themselves or you do not care about
+ # accounting, comment out the 'cuisql' line below.
+ #
+ if (&reply:Chargeable-User-Identity) {
+ # Force User-Name to be the User-Name from the request
+ update {
+ &reply:User-Name := &request:User-Name
+ }
+ cuisql
+ }
+}
+
+
+cui-inner.post-auth {
+ if (&outer.request:Chargeable-User-Identity && \
+ (&outer.request:Operator-Name || ('${policy.cui_require_operator_name}' != 'yes'))) {
+ update reply {
+ &Chargeable-User-Identity := "%{sha1:${policy.cui_hash_key}%{tolower:%{User-Name}%{%{outer.request:Operator-Name}:-}}}"
+ }
+ }
+}
+
+#
+# If your NAS can do CUI based accounting or you do not care about
+# accounting then just comment out the call to cui in ......
+#
+# If we had stored a CUI for the User, add it to the request.
+#
+cui.accounting {
+ #
+ # If the CUI isn't in the packet, see if we can find it
+ # in the DB.
+ #
+ if (!&Chargeable-User-Identity) {
+ update request {
+ &Chargeable-User-Identity := "%{cuisql:\
+ SELECT cui FROM cui \
+ WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \
+ AND callingstationid = '%{Calling-Station-Id}' \
+ AND username = '%{User-Name}'}"
+ }
+ }
+
+ #
+ # If it exists now, then write out when we last saw
+ # this CUI.
+ #
+ if (&Chargeable-User-Identity && (&Chargeable-User-Identity != '')) {
+ cuisql
+ }
+}
diff --git a/raddb/policy.d/debug b/raddb/policy.d/debug
new file mode 100644
index 0000000..26583f1
--- /dev/null
+++ b/raddb/policy.d/debug
@@ -0,0 +1,64 @@
+#
+# Outputs the contents of the control list in debugging (-X) mode
+#
+debug_control {
+ if("%{debug_attr:control:}" == '') {
+ noop
+ }
+}
+
+#
+# Outputs the contents of the request list in debugging (-X) mode
+#
+debug_request {
+ if("%{debug_attr:request:}" == '') {
+ noop
+ }
+}
+
+#
+# Outputs the contents of the coa list in debugging (-X) mode
+#
+debug_coa {
+ if("%{debug_attr:coa:}" == '') {
+ noop
+ }
+}
+
+#
+# Outputs the contents of the reply list in debugging (-X) mode
+#
+debug_reply {
+ if("%{debug_attr:reply:}" == '') {
+ noop
+ }
+}
+
+#
+# Outputs the contents of the session state list in debugging (-X) mode
+#
+debug_session_state {
+ if("%{debug_attr:session-state:}" == '') {
+ noop
+ }
+}
+
+#
+# Outputs the contents of the proxy-request state list in debugging (-X) mode
+#
+debug_proxy_request {
+ if("%{debug_attr:proxy-request:}" == '') {
+ noop
+ }
+}
+
+#
+# Outputs the contents of the main lists in debugging (-X) mode
+#
+debug_all {
+ debug_control
+ debug_request
+ debug_coa
+ debug_reply
+ debug_session_state
+}
diff --git a/raddb/policy.d/dhcp b/raddb/policy.d/dhcp
new file mode 100644
index 0000000..1752acb
--- /dev/null
+++ b/raddb/policy.d/dhcp
@@ -0,0 +1,327 @@
+# Assign common DHCP reply packet options
+dhcp_common {
+ # The contents here are invented. Change them!
+ update reply {
+ &DHCP-Domain-Name-Server = 127.0.0.1
+ &DHCP-Domain-Name-Server += 127.0.0.2
+ &DHCP-Subnet-Mask = 255.255.255.0
+ &DHCP-Router-Address = 192.0.2.1
+ &DHCP-Broadcast-Address = 192.0.2.255
+ &DHCP-IP-Address-Lease-Time = 7200
+ &DHCP-DHCP-Server-Identifier = &control:DHCP-DHCP-Server-Identifier
+ }
+}
+
+# Lookup DHCP group based options. This policy allows for membership
+# of multiple groups so can cover the ISC concepts of "group" and "class"
+# To use this enable the "dhcp_files" module
+#dhcp_group_options {
+# foreach &request:DHCP-Group-Name {
+# dhcp_set_group_options
+# }
+#}
+
+# Policy to override DHCP-Network-Subnet
+#
+# Some networks have a "shared-network" or "multinet" configuration (as
+# defined by some other DHCP servers) in which multiple IP subnets may
+# co-exist in a single Layer 2 network (or VLAN).
+#
+# In enterprise environments this is often for the purpose of providing loose
+# segregation between classes of devices such as local network-attached
+# storage or IP telephony. There are valid reasons why each of the subnets is
+# not seperately VLANed, such as to enable the use of ICMP redirects to avoid
+# hairpinning of cross-subnet traffic via a gateway.
+#
+# In ISP environments this is a common configuration for edge networks whose
+# access is provided by DOCSIS cable modems that share a VLAN with the devices
+# they provide a service to but are seperately addressed.
+#
+# Where it is necessary to force the selection of a particular subnet for a
+# device, multiple pools must be configured for each subnet and referenced
+# with unique identifiers in the *network-specific* section of
+# mods-config/files/dhcp.
+#
+# By default DHCP-Network-Subnet is populated such that it normally
+# refers to the Layer 2 network from which the DHCP query originates - we
+# cannot know the intended subnet for the device without additional input to
+# the policy.
+#
+# Override DHCP-Network-Subnet to be an address within the desired
+# network to force selection of a particular address pool and/or network
+# parameters.
+#
+# Note: If each subnet within a network is equally valid for the DHCP requests
+# originating from that network then you do not need to call this policy,
+# rather look at the examples concerning dhcp_subnet in
+# mods-config/files/dhcp instead, which use a single pool containing addresses
+# from all subnets then set the correct subnet-specific options based on the
+# randomly assigned IP address.
+#
+#dhcp_override_network {
+# if (&DHCP-Vendor-Class-Identifier && &DHCP-Vendor-Class-Identifier == "SIP100")
+# update request {
+# DHCP-Network-Subnet := 10.10.0.0
+# }
+# }
+#}
+
+
+# Policy that calls the files instance of the same name after first making
+# DHCP-Network-Subnet specific to the allocated IP address of the client.
+#dhcp_subnet {
+# update {
+# &DHCP-Network-Subnet := "%{%{reply:DHCP-Your-IP-Address}:-%{DHCP-Client-IP-Address}}"
+# }
+#
+# # Call the dhcp_subnet instance of the files module
+# dhcp_subnet
+#}
+
+# Assign compatibility data to request for sqlippool for DHCP Request
+dhcp_sqlippool_request {
+
+ #
+ # During initial address selection (DORA) the REQUEST is broadcast and
+ # requested-ip must be provided. We revoke any active offers for addresses
+ # not matching the requested-ip, i.e. those made by other servers when
+ # processing the DISCOVER.
+ #
+ # If there is only a single server then this optimisation can be disabled.
+ #
+ if (&DHCP-Requested-IP-Address) {
+ update request {
+ &Acct-Status-Type := Start
+ }
+ dhcp_sqlippool.accounting
+ }
+
+ # Extend an existing offer or active lease
+ update request {
+ &Acct-Status-Type := Alive
+ }
+ dhcp_sqlippool.accounting {
+ notfound = return
+ }
+
+ update reply {
+ &DHCP-Your-IP-Address := "%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}"
+ }
+
+}
+
+# Assign compatibility data to request for sqlippool for DHCP Release
+dhcp_sqlippool_release {
+
+ # Do some minor hacks to the request so that it looks
+ # like a RADIUS Accounting Stop request to the SQL IP Pool module.
+ update request {
+ &Acct-Status-Type = Stop
+ }
+
+ # Call the actual module in accounting context
+ dhcp_sqlippool.accounting
+
+}
+
+# Assign compatibility data to request for sqlippool for DHCP Decline
+dhcp_sqlippool_decline {
+
+ # Do a minor hack to the request so that it looks
+ # like a RADIUS Accounting Off request to the SQL IP Pool module.
+ update request {
+ &Acct-Status-Type = Accounting-Off
+ }
+
+ # Call the actual module in accounting context
+ dhcp_sqlippool.accounting
+
+}
+
+# Example policy for fetching option data from SQL
+dhcp_policy_sql {
+
+ #
+ # Network-specific options
+ #
+
+ #
+ # We want to lookup the Layer 2 network specific DHCP options to
+ # include in the reply. For this we need a stable identifier for the
+ # network from which the request is originating (based on
+ # DHCP-Network-Subnet) which can be used as the lookup key
+ # (DHCP-SQL-Option-Identifier) for the network options.
+ #
+ # Here we fabricate an example for the purpose of placing all
+ # configuration elements into SQL. We use a PostgreSQL query that
+ # returns the network identifier in the row containing the smallest
+ # enclosing CIDR, which assumes a schema such as the following:
+ #
+ # CREATE TABLE fr_network_to_identifier (network CIDR, network_id TEXT)
+ #
+ # Note: An rlm_files based lookup of the network_identifier (as per
+ # the examples in the dhcp virtual server) may be preferable to an ad
+ # hoc SQL query assuming that the network topology does not change
+ # frequently.
+ #
+# update control {
+# &control:Tmp-String-0 := "%{dhcp_sql:SELECT network_id \
+# FROM fr_network_to_identifier \
+# WHERE '%{DHCP-Network-Subnet}'::inet << network \
+# ORDER BY MASKLEN(network) DESC LIMIT 1;}"
+# }
+
+ #
+ # Use the network identifer to lookup the options specific to the
+ # originating network, using "network" context. Common network
+ # settings can be placed into a group and shared, with individual
+ # networks mapped to one or more option groups.
+ #
+ # - Place network-specific options in the dhcpreply table with
+ # "context = 'network'".
+ # - Add "Fall-Through := Yes" to the network options in the dhcpreply
+ # table to trigger group lookups for the network, which are
+ # disabled by default.
+ # - Place "identifier = <network_id>, groupname = <group>,
+ # priority = <priority>, context = 'network'" in the dhcpgroup
+ # table to map a network to a shared set of network options.
+ # - Place group-specific options in the dhcpgroupreply table with
+ # "context = 'network'".
+ #
+ # Note: In "shared-network" or "multinet" topologies you can instead
+ # just set all of the network options once in the subnet-specific
+ # options (after obtaining an IP address), below.
+ #
+# update control {
+# &DHCP-SQL-Option-Context := "network"
+# &DHCP-SQL-Option-Identifier := &control:Tmp-String-0
+# }
+# dhcp_sql.authorize
+
+
+ #
+ # Allocate IPs from the DHCP pool in SQL.
+ #
+ # Here we simply reuse the network_id (obtained previously) as the
+ # Pool-Name.
+ #
+# update control {
+# &Pool-Name := &control:Tmp-String-0
+# }
+# dhcp_sqlippool
+
+
+ #
+ # Subnet-specific options
+ #
+
+ #
+ # In "shared-network" or "multinet" topologies (in which a Layer 2
+ # network has a single pool that contains addresses from multiple
+ # subnets) it is necessary to set subnet-specific options based on the
+ # address that has just been allocated.
+ #
+ # Again, for this we need to derive a stable identifier for the subnet
+ # to which the IP address we are issuing belongs that will serve as a
+ # lookup key for the network options.
+ #
+ # Continuing our previous example, we can use a PostgreSQL query to
+ # find the subnet identifer in the row with the closest enclosing
+ # CIDR, which assumes a schema such as the following:
+ #
+ # CREATE TABLE fr_subnet_to_identifier (subnet CIDR, subnet_id TEXT)
+ #
+ # Note: An rlm_files based lookup of the subnet_identifier (as per the
+ # examples in the dhcp virtual server) is preferable to an ad hoc SQL
+ # query assuming that the network topology does not change frequently.
+ #
+# update control {
+# &control:Tmp-String-0 := "%{dhcp_sql:SELECT subnet_id \
+# FROM fr_subnet_to_identifier \
+# WHERE '%{reply:DHCP-Your-IP-Address}'::inet << subnet \
+# ORDER BY MASKLEN(subnet) DESC LIMIT 1;}"
+# }
+
+ #
+ # Use the subnet identifer to lookup the options specific to the
+ # subnet for the IP we are allocating, using "subnet" context. Common
+ # subnet settings can be placed into a group and shared, with
+ # individual subnets mapped to one or more option groups.
+ #
+ # - Place subnet-specific options in the dhcpreply table with
+ # "context = 'subnet'".
+ # - Add "Fall-Through := Yes" to the subnet options in the dhcpreply
+ # table to trigger group lookups for the subnet, which are
+ # disabled by default.
+ # - Place "identifier = <subnet_id>, groupname = <group>,
+ # priority = <priority>, context = 'subnet'" in the dhcpgroup
+ # table to map a subnet to a shared set of subnet options.
+ # - Place group-specific options in the dhcpgroupreply table with
+ # "context = 'subnet'".
+ #
+# update control {
+# &DHCP-SQL-Option-Context := "subnet"
+# &DHCP-SQL-Option-Identifier := &control:Tmp-String-0
+# }
+# dhcp_sql.authorize
+
+
+ #
+ # Host-specific and group-specific options
+ #
+
+ # "Groups" conventionally differentiate devices based on manual
+ # groupings using a device-specific identifier such as the MAC
+ # address.
+ #
+ # - Place host-specific options in the dhcpreply table with
+ # "context = 'group'".
+ # - Add "Fall-Through := Yes" to the device options in the dhcpreply
+ # table to trigger group lookups, which are disabled by default.
+ # - Place "identifier = <MAC-Address>, groupname = <group>,
+ # priority = <priority>, context='group'" in the dhcpgroup table
+ # to map a device to its groups.
+ # - Place group-specific options in the dhcpgroupreply table with
+ # "context = 'group'".
+ #
+# update control {
+# &DHCP-SQL-Option-Context := "group"
+# &DHCP-SQL-Option-Identifier := &request:DHCP-Client-Hardware-Address
+# }
+# dhcp_sql.authorize
+
+
+ #
+ # Class/subclass-specific options
+ #
+
+ #
+ # "Classes" conventionally differentiate devices based on all or part
+ # of one or more DHCP request options, or any combination of
+ # information that is available in the request or has already looked
+ # up from some datastore.
+ #
+ # Create multiple instances of the following block, one for each
+ # class. Differentiate between classes by setting
+ # DHCP-SQL-Option-Context uniquely.
+ #
+ # - Place "subclass"-specific options (i.e. each member of a class)
+ # in the dhcpreply table with "context = <class-name>".
+ # - For class-level options common to every member of a class,
+ # either:
+ # - Duplicate the options for each member of the subclass.
+ # or:
+ # - Add "Fall-Through := Yes" to each members options to trigger
+ # group lookups, which are disabled by default.
+ # - Map each member of the class to a group in the dhcpgroup
+ # table with context = '<class-name>';
+ # - Create the corresponding class in the dhcpgroupreply table
+ # with "context = '<class-name>'".
+ #
+# update control {
+# &DHCP-SQL-Option-Context := "class-vci-substring"
+# &DHCP-SQL-Option-Identifier := "%{substring %{request:DHCP-Vendor-Class-Identifier} 5 4}"
+# }
+# dhcp_sql.authorize
+
+}
diff --git a/raddb/policy.d/eap b/raddb/policy.d/eap
new file mode 100644
index 0000000..c8dac22
--- /dev/null
+++ b/raddb/policy.d/eap
@@ -0,0 +1,54 @@
+#
+# Forbid all EAP types. Enable this by putting "forbid_eap"
+# into the "authorize" section.
+#
+forbid_eap {
+ if (&EAP-Message) {
+ reject
+ }
+}
+
+#
+# Forbid all non-EAP types outside of an EAP tunnel.
+#
+permit_only_eap {
+ if (!&EAP-Message) {
+ # We MAY be inside of a TTLS tunnel.
+ # PEAP and EAP-FAST require EAP inside of
+ # the tunnel, so this check is OK.
+ # If so, then there MUST be an outer EAP message.
+ if (!&outer.request || !&outer.request:EAP-Message) {
+ reject
+ }
+ }
+}
+
+#
+# Remove Reply-Message from response if were doing EAP
+#
+# Be RFC 3579 2.6.5 compliant - EAP-Message and Reply-Message should
+# not be present in the same response.
+#
+remove_reply_message_if_eap {
+ if (&reply:EAP-Message && &reply:Reply-Message) {
+ update reply {
+ &Reply-Message !* ANY
+ }
+ }
+ else {
+ noop
+ }
+}
+
+verify_tls_client_common_name {
+ #
+ # If the User-Name is anonymized, then don't check it.
+ #
+ # But if User-Name is realm AND there's a certificate name, then check
+ # if they match. This is not always the case, but it is the case
+ # often enough that it matters.
+ #
+ if ((&User-Name !~ /^@/) && &TLS-Client-Cert-Common-Name && (&TLS-Client-Cert-Common-Name != &User-Name)) {
+ reject
+ }
+}
diff --git a/raddb/policy.d/filter b/raddb/policy.d/filter
new file mode 100644
index 0000000..ff8f531
--- /dev/null
+++ b/raddb/policy.d/filter
@@ -0,0 +1,211 @@
+#
+# Example of forbidding all attempts to login via
+# realms.
+#
+deny_realms {
+ if (&User-Name && (&User-Name =~ /@|\\/)) {
+ reject
+ }
+}
+
+#
+# Filter the username
+#
+# Force some sanity on User-Name. This helps to avoid issues
+# issues where the back-end database is "forgiving" about
+# what constitutes a user name.
+#
+filter_username {
+ if (&User-Name) {
+ #
+ # reject mixed case e.g. "UseRNaMe"
+ #
+ #if (&User-Name != "%{tolower:%{User-Name}}") {
+ # reject
+ #}
+
+ #
+ # reject all whitespace
+ # e.g. "user@ site.com", or "us er", or " user", or "user "
+ #
+ if (&User-Name =~ / /) {
+ update request {
+ &Module-Failure-Message += 'Rejected: User-Name contains whitespace'
+ }
+ reject
+ }
+
+ #
+ # reject Multiple @'s
+ # e.g. "user@site.com@site.com"
+ #
+ if (&User-Name =~ /@[^@]*@/ ) {
+ update request {
+ &Module-Failure-Message += 'Rejected: Multiple @ in User-Name'
+ }
+ reject
+ }
+
+ #
+ # reject double dots
+ # e.g. "user@site..com"
+ #
+ if (&User-Name =~ /\.\./ ) {
+ update request {
+ &Module-Failure-Message += 'Rejected: User-Name contains multiple ..s'
+ }
+ reject
+ }
+
+ #
+ # must have at least 1 string-dot-string after @
+ # e.g. "user@site.com"
+ #
+ if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) {
+ update request {
+ &Module-Failure-Message += 'Rejected: Realm does not have at least one dot separator'
+ }
+ reject
+ }
+
+ #
+ # Realm ends with a dot
+ # e.g. "user@site.com."
+ #
+ if (&User-Name =~ /\.$/) {
+ update request {
+ &Module-Failure-Message += 'Rejected: Realm ends with a dot'
+ }
+ reject
+ }
+
+ #
+ # Realm begins with a dot
+ # e.g. "user@.site.com"
+ #
+ if (&User-Name =~ /@\./) {
+ update request {
+ &Module-Failure-Message += 'Rejected: Realm begins with a dot'
+ }
+ reject
+ }
+ }
+}
+
+#
+# Filter the User-Password
+#
+# Some equipment sends passwords with embedded zeros.
+# This policy filters them out.
+#
+filter_password {
+ if (&User-Password && \
+ (&User-Password != "%{string:User-Password}")) {
+ update request {
+ &Tmp-String-0 := "%{string:User-Password}"
+ &User-Password := "%{string:Tmp-String-0}"
+ &Tmp-String-0 !* ""
+ }
+ }
+}
+
+filter_inner_identity {
+ #
+ # No names, reject.
+ #
+ if (!&outer.request:User-Name || !&User-Name) {
+ update request {
+ Module-Failure-Message = "User-Name is required for tunneled authentication"
+ }
+ reject
+ }
+
+ #
+ # Do detailed checks only if the inner and outer
+ # NAIs are different.
+ #
+ # If the NAIs are the same, it violates user privacy,
+ # but is allowed.
+ #
+ if (&outer.request:User-Name != &User-Name) {
+ #
+ # Get the outer realm.
+ #
+ if (&outer.request:User-Name =~ /@([^@]+)$/) {
+ update request {
+ Outer-Realm-Name = "%{1}"
+ }
+
+ #
+ # When we have an outer realm name, the user portion
+ # MUST either be empty, or begin with "anon".
+ #
+ # We don't check for the full "anonymous", because
+ # some vendors don't follow the standards.
+ #
+ if (&outer.request:User-Name !~ /^(anon|@)/) {
+ update request {
+ Module-Failure-Message = "User-Name is not anonymized"
+ }
+ reject
+ }
+ }
+
+ #
+ # There's no outer realm. The outer NAI is different from the
+ # inner NAI. The User-Name MUST be anonymized.
+ #
+ # Otherwise, you could log in as outer "bob", and inner "doug",
+ # and we'd have no idea which one was correct.
+ #
+ elsif (&outer.request:User-Name !~ /^anon/) {
+ update request {
+ Module-Failure-Message = "User-Name is not anonymized"
+ }
+ reject
+ }
+
+ #
+ # Get the inner realm.
+ #
+ if (&User-Name =~ /@([^@]+)$/) {
+ update request {
+ Inner-Realm-Name = "%{1}"
+ }
+
+ #
+ # Note that we do EQUALITY checks for realm names.
+ # There is no simple way to do case insensitive checks
+ # on internationalized domain names. There is no reason
+ # to allow outer "anonymous@EXAMPLE.COM" and inner
+ # "user@example.com". The user should enter the same
+ # realm for both identities.
+ #
+ # If the inner realm isn't the same as the outer realm,
+ # the inner realm MUST be a subdomain of the outer realm.
+ #
+ if (&Outer-Realm-Name && \
+ (&Inner-Realm-Name != &Outer-Realm-Name) && \
+ (&Inner-Realm-Name !~ /\.%{Outer-Realm-Name}$/)) {
+ update request {
+ Module-Failure-Message = "Inner realm '%{Inner-Realm-Name}' and outer realm '%{Outer-Realm-Name}' are not from the same domain."
+ }
+ reject
+ }
+
+ #
+ # It's OK to have an inner realm and no outer realm.
+ #
+ # That won't work for roaming, but the local RADIUS server
+ # can still authenticate the user.
+ #
+ }
+
+ #
+ # It's OK to have an outer realm and no inner realm.
+ #
+ # It will work for roaming, and the local RADIUS server
+ # can authenticate the user without the realm.
+ #
+ }
+}
diff --git a/raddb/policy.d/moonshot-targeted-ids b/raddb/policy.d/moonshot-targeted-ids
new file mode 100644
index 0000000..98ae4a1
--- /dev/null
+++ b/raddb/policy.d/moonshot-targeted-ids
@@ -0,0 +1,249 @@
+#
+# The following policies generate targeted IDs for ABFAB (Moonshot)
+#
+# This policy requires that the UUID package is installed on your platform
+# and that this is called from the inner-tunnel
+#
+# The following string attributes need to exist in the UKERNA dictionary
+# Moonshot-Host-TargetedId (138)
+# Moonshot-Realm-TargetedId (139)
+# Moonshot-TR-COI-TargetedId (140)
+# Moonshot-MSTID-GSS-Acceptor (141)
+# Moonshot-MSTID-Namespace (142)
+# Moonshot-MSTID-TargetedId (143)
+#
+# These attributes should also be listed in the attr_filter policies
+# post-proxy and pre-proxy when you use attribute filtering:
+# Moonshot-Host-TargetedId =* ANY,
+# Moonshot-Realm-TargetedId =* ANY,
+# Moonshot-TR-COI-TargetedId =* ANY,
+#
+
+#
+# targeted_id_salt definition
+# This salt serves the purpose of protecting targeted IDs against
+# dictionary attacks, therefore should be chosen as a "random"
+# string and kept secret.
+#
+# If you use special characters %, { and }, escape them with a \ first
+#
+targeted_id_salt = 'changeme'
+
+#
+# Moonshot namespaces
+# These namespaces are used for UUID generation.
+# They should not be changed by implementors
+#
+moonshot_host_namespace = 'a574a04e-b7ff-4850-aa24-a8599c7de1c6'
+moonshot_realm_namespace = 'dea5f26d-a013-4444-977d-d09fc990d2e6'
+moonshot_coi_namespace = '145d7e7e-7d54-43ee-bbcb-3c6ad9428247'
+
+
+# This policy generates a host-specific TargetedId
+#
+moonshot_host_tid.post-auth {
+ # retrieve or generate a UUID for Moonshot-Host-TargetedId
+ if (&outer.request:GSS-Acceptor-Host-Name) {
+ # prep some variables (used regardless of SQL backing or not!)
+ update control {
+ Moonshot-MSTID-GSS-Acceptor := "%{tolower:%{outer.request:GSS-Acceptor-Host-Name}}"
+ Moonshot-MSTID-Namespace := "${policy.moonshot_host_namespace}"
+ }
+
+ # if you want to use SQL-based backing, remove the comment from
+ # this line. You also have to configure and enable the
+ # moonshot-targeted-ids sql module in mods-enabled.
+ #
+# moonshot_get_targeted_id
+
+ # generate a UUID for Moonshot-Host-TargetedId
+ if (!&control:Moonshot-MSTID-TargetedId) {
+ # generate the TID
+ moonshot_make_targeted_id
+
+ # if you want to store your TargetedId in SQL-based backing,
+ # remove the comment from this line. You also have to configure
+ # and enable the moonshot-targeted-ids sql module in mods-enabled.
+ #
+# moonshot_tid_sql
+ }
+
+ # set the actual TargetedId in the session-state list
+ if (&control:Moonshot-MSTID-TargetedId) {
+ update outer.session-state {
+ Moonshot-Host-TargetedId := &control:Moonshot-MSTID-TargetedId
+ }
+ update control {
+ Moonshot-MSTID-TargetedId !* ANY
+ }
+ }
+
+ # Sanitise the control list to remove the internal attributes
+ update control {
+ Moonshot-MSTID-GSS-Acceptor !* ANY
+ Moonshot-MSTID-Namespace !* ANY
+ }
+ }
+}
+
+# This policy generates a realm-specific TargetedId
+#
+moonshot_realm_tid.post-auth {
+ # retrieve or generate a UUID for Moonshot-Realm-TargetedId
+ if (&outer.request:GSS-Acceptor-Realm-Name) {
+ # prep some variables (used regardless of SQL backing or not!)
+ update control {
+ Moonshot-MSTID-GSS-Acceptor := "%{tolower:%{outer.request:GSS-Acceptor-Realm-Name}}"
+ Moonshot-MSTID-Namespace := "${policy.moonshot_realm_namespace}"
+ }
+
+ # if you want to use SQL-based backing, remove the comment from
+ # this line. You also have to configure and enable the
+ # moonshot-targeted-ids sql module in mods-enabled.
+ #
+# moonshot_get_targeted_id
+
+ # generate a UUID for Moonshot-Realm-TargetedId
+ if (!&control:Moonshot-MSTID-TargetedId) {
+ # generate the TID
+ moonshot_make_targeted_id
+
+ # if you want to store your TargetedId in SQL-based backing,
+ # remove the comment from this line. You also have to configure
+ # and enable the moonshot-targeted-ids sql module in mods-enabled.
+ #
+# moonshot_tid_sql
+ }
+
+ # set the actual TargetedId in the session-state list
+ if (&control:Moonshot-MSTID-TargetedId) {
+ update outer.session-state {
+ Moonshot-Realm-TargetedId := &control:Moonshot-MSTID-TargetedId
+ }
+ update control {
+ Moonshot-MSTID-TargetedId !* ANY
+ }
+ }
+
+ # Sanitise the control list to remove the internal attributes
+ update control {
+ Moonshot-MSTID-GSS-Acceptor !* ANY
+ Moonshot-MSTID-Namespace !* ANY
+ }
+ }
+}
+
+# This policy generates a COI-specific targeted ID
+#
+moonshot_coi_tid.post-auth {
+ # retrieve or generate a UUID for Moonshot-TR-COI-TargetedId
+ if (&outer.request:Trust-Router-COI) {
+ # prep some variables (used regardless of SQL backing or not!)
+ update control {
+ Moonshot-MSTID-GSS-Acceptor := "%{tolower:%{outer.request:Trust-Router-COI}}"
+ Moonshot-MSTID-Namespace := "${policy.moonshot_coi_namespace}"
+ }
+
+ # if you want to use SQL-based backing, remove the comment from
+ # this line. You also have to configure and enable the
+ # moonshot-targeted-ids sql module in mods-enabled.
+ #
+# moonshot_get_targeted_id
+
+ # generate a UUID for Moonshot-TR-COI-TargetedId
+ if (!&control:Moonshot-MSTID-TargetedId) {
+ # generate the TID
+ moonshot_make_targeted_id
+
+ # if you want to store your TargetedId in SQL-based backing,
+ # remove the comment from this line. You also have to configure
+ # and enable the moonshot-targeted-ids sql module in mods-enabled.
+ #
+# moonshot_tid_sql
+ }
+
+ # set the actual TargetedId in the session-state list
+ if (&control:Moonshot-MSTID-TargetedId) {
+ update outer.session-state {
+ Moonshot-TR-COI-TargetedId := &control:Moonshot-MSTID-TargetedId
+ }
+ update control {
+ Moonshot-MSTID-TargetedId !* ANY
+ }
+ }
+
+ # Sanitise the control list to remove the internal attributes
+ update control {
+ Moonshot-MSTID-GSS-Acceptor !* ANY
+ Moonshot-MSTID-Namespace !* ANY
+ }
+ }
+}
+
+# This is the generic generation policy. It requires moonshot_host_tid, moonshot_realm_tid, or moonshot_coi_tid to set variables
+#
+moonshot_make_targeted_id.post-auth {
+ # uses variables set in the control list
+ #
+ if (&control:Moonshot-MSTID-Namespace && &control:Moonshot-MSTID-GSS-Acceptor) {
+ # targeted id = (uuid -v 5 [namespace] [username][salt][GSS acceptor value])@[IdP realm name]
+ #
+ if ("%{echo:/usr/bin/uuid -v 5 %{control:Moonshot-MSTID-Namespace} %{tolower:%{User-Name}}${policy.targeted_id_salt}%{control:Moonshot-MSTID-GSS-Acceptor}}" =~ /^([^ ]+)([ ]*)$/) {
+ update control {
+ Moonshot-MSTID-TargetedId := "%{1}@%{tolower:%{request:Realm}}"
+ }
+ if (&control:Moonshot-MSTID-TargetedId =~ /([\%\{\}]+)/) {
+ update control {
+ Moonshot-MSTID-TargetedId !* ANY
+ }
+ update outer.session-state {
+ Module-Failure-Message = 'Invalid TargetedId generated, check your targeted_id_salt!'
+ }
+ reject
+ }
+ }
+ else {
+ # we simply return the 'echo' error message as the Module-Failure-Message, usually a lack of 'uuid'
+ reject
+ }
+ }
+ else {
+ # Our variables were not set, so we'll throw an error because there's no point in continuing!
+ update outer.session-state {
+ Module-Failure-Message = 'Required variables for moonshot_make_targeted_id not set!'
+ }
+ reject
+ }
+}
+
+# This is the generic retrieval policy. It requires moonshot_host_tid, moonshot_realm_tid, or moonshot_coi_tid to set variables
+#
+moonshot_get_targeted_id.post-auth {
+ # uses variables set in the control list
+ #
+ if (&control:Moonshot-MSTID-Namespace && &control:Moonshot-MSTID-GSS-Acceptor) {
+ # retrieve the TargetedId
+ #
+ update control {
+ Moonshot-MSTID-TargetedId := "%{moonshot_tid_sql:\
+ SELECT targeted_id FROM moonshot_targeted_ids \
+ WHERE gss_acceptor = '%{control:Moonshot-MSTID-GSS-Acceptor}' \
+ AND namespace = '%{control:Moonshot-MSTID-Namespace}' \
+ AND username = '%{tolower:%{User-Name}}'}"
+ }
+
+ # if the value is empty, there's no point in setting it and delete it from the control list!
+ if (&control:Moonshot-MSTID-TargetedId == '') {
+ update control {
+ Moonshot-MSTID-TargetedId !* ANY
+ }
+ }
+ }
+ else {
+ # Our variables were not set, so we'll throw an error because there's no point in continuing!
+ update outer.session-state {
+ Module-Failure-Message = 'Required variables for moonshot_get_targeted_id not set!'
+ }
+ reject
+ }
+}
diff --git a/raddb/policy.d/operator-name b/raddb/policy.d/operator-name
new file mode 100644
index 0000000..6d042d4
--- /dev/null
+++ b/raddb/policy.d/operator-name
@@ -0,0 +1,46 @@
+#
+# The following policies are for the Operator-Name
+# configuration.
+#
+# The policies below can be called as just 'operator-name' (not
+# operator-name.authorize etc..) from the various config sections.
+#
+
+# If you require that the Operator-Name be set
+# for local clients then call the 'operator-name' policy
+# in the authorize section of the virtual-server for your clients in clients.conf
+
+# To inject an Operator-Name whilst proxying, call the
+# 'operator-name' policy in the pre-proxy section of the virtual server
+# No need to call this if you have already enabled this in
+# the authorize section.
+
+#
+# We assume that clients can have the operator-name definition
+# in the client.conf, e.g.
+# client xxxx {
+# ...
+# Operator-Name = 1your.domain
+# }
+# If this parameter is found for a client, then we add
+# an Operator-Name attribute
+#
+operator-name.authorize {
+ if ("%{client:Operator-Name}") {
+ update request {
+ &Operator-Name = "%{client:Operator-Name}"
+ }
+ }
+}
+
+#
+# Before proxing the client add an Operator-Name
+# attribute identifying this site if the operator-name is found for this client
+#
+operator-name.pre-proxy {
+ if (("%{request:Packet-Type}" == 'Access-Request') && "%{client:Operator-Name}") {
+ update proxy-request {
+ &Operator-Name := "%{client:Operator-Name}"
+ }
+ }
+}
diff --git a/raddb/policy.d/rfc7542 b/raddb/policy.d/rfc7542
new file mode 100644
index 0000000..84a5c17
--- /dev/null
+++ b/raddb/policy.d/rfc7542
@@ -0,0 +1,46 @@
+#
+# The following policy is for RFC7542-style bang path
+# management.
+#
+# It hands control from the standard 'suffix' realm
+# processor to the 'bangpath' processer, allowing the
+# definition of specific routing information in the
+# decoration of the User-Name.
+#
+# Use this with caution. In particular, read the following
+# RFC document sections for reasons why you shouldn't use
+# this, and also why this is used:
+#
+# 1. https://tools.ietf.org/html/rfc4282#section-2.7
+# 2. https://tools.ietf.org/html/rfc7542#section-3.3.1
+#
+# $Id$
+#
+
+# This is a |-separated list of realms this specific service
+# is responsible for. We cannot read this from the proxy.conf
+# file, so we turn this into an 'or list' regex.
+# Examples: rfc7542_realms = 'example.com'
+# rfc7542_realms = 'example.com|another.net|this.org'
+#
+rfc7542_realms = 'changeme'
+
+# This policy checks the User-Name attribute whether it is in
+# RFC7542 bang-path format. If it is, it lets the bangpath realm
+# processor handle it, otherwise it leaves it for suffix to handle
+#
+rfc7542.authorize {
+ # Format: not_local_realm!...@local_realm: Handle with bangpath
+ if ( (&request:User-Name =~ /(.+)!(.*)\@(${policy.rfc7542_realms})/) && \
+ !(&request:User-Name =~ /(${policy.rfc7542_realms})!(.*)\@(.+)/) ) {
+ bangpath
+ updated
+ }
+
+ # Format: local_realm!...@not_local_realm: Handle with bangpath
+ elsif ( (&request:User-Name =~ /(${policy.rfc7542_realms})!(.*)\@(.+)/) && \
+ !(&request:User-Name =~ /(.+)!(.*)\@(${policy.rfc7542_realms})/) ) {
+ bangpath
+ updated
+ }
+}