]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Use / require '@' in subrequest, when changing namespaces
authorAlan T. DeKok <aland@freeradius.org>
Fri, 17 Jan 2025 15:57:10 +0000 (10:57 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 17 Jan 2025 16:14:44 +0000 (11:14 -0500)
doc/antora/modules/reference/pages/unlang/subrequest.adoc
src/lib/unlang/compile.c
src/tests/modules/cache_rbtree/cache-not-radius.unlang

index fd75bd1c829bf744aa4fff9e87fe40e569400e83..f06a0ce8ef03709eae3460f06b6692687dcc7214 100644 (file)
@@ -11,26 +11,39 @@ subrequest <type> {
 The `subrequest` keyword creates a child request.  The child request
 is empty, and contains no attributes.  Attributes in the child can be
 copied from the parent via xref:unlang/edit.adoc[editing] statements.
-Please see the xref:unlang/list.adoc[list] syntax for a description of how to
-refer to parent requests.
+Please see the xref:unlang/list.adoc[list] syntax for a description of
+how to refer to parent requests.
 
 The `subrequest` keyword allows the server to receive one type of
-packet, and then to create a different type of packet.  `subrequest`
-is most commonly used in conjunction with the
+packet, and then to create a different type of packet.  For example,
+receive a RADIUS `Accounting-Request`, and then create a child
+`Disconnect-Request` to kick the user offline.  The `subrequest`
+keyword can also be used to change protocols.  For example, receive a
+DHCP `Discover` packet, and then send a RADIUS `Access-Request` packet
+to see if the MAC address is authorized.
+
+`subrequest` is most commonly used in conjunction with the
 xref:unlang/call.adoc[call] keyword to change packet types or
-protocols, and then run a different virtual server.  See the
-"Operation" section below for more information.
+protocols, and then xref:unlang/call.adoc[call] a different virtual
+server.  See the "Operation" section below for more information.
 
-<type>:: The type of the child request being created.
+<type>:: The packet type of the child request being created.
 +
-The _<type>_ field is a either an enumerated packet name such as `::Access-Request`,
-or a protocol name followed by a packet name, such as
-`dhcpv4::Discover`.  Please see the protocol dictionaries for a
-complete list of packet types for each protocol.
+If the _<type>_ field is omitted, then a child request is created
+using the same packet type as the current request.
++
+If the _<type>_ field contains a protocol reference such as `@dhcpv4`,
+then this creates a new child request of a different protocol.  The
+rest of the _<type>_ field is then parsed to see what kind of packet
+is created.
++
+The _<type> field is then parsed as an enumerated packet name such as
+`::Access-Request`, Please see the protocol dictionaries for a
+complete list of allowed packet types for each protocol.
 +
 The _<type>_ field cannot be a dynamic expansion.
 +
-The _<type> field can, however, be an attribute reference.  When an
+As an exception, the _<type>_ field can be an attribute reference.  When an
 attribute reference is used, the attribute must be of an integer type
 (8, 16, 32, or 64-bit), or of type `string`.  Integer types are
 resolved to known / allowed values of the `Packet-Type` attribute.
@@ -39,6 +52,10 @@ attribute.
 +
 When the _<type>_ field is an attribute reference, it is not
 possible to change the dictionary.
++
+We do not suggest using attribute references.  The functionality will
+be removed in a future release, and will be replaced with full support
+for dynamic expansions.
 
 [ statements ]:: The `unlang` commands which will be executed.
 
@@ -71,7 +88,7 @@ parsed in the context of the new protocol.
 .Example
 [source,unlang]
 ----
-subrequest dhcpv4::Discover {
+subrequest @dhcpv4::Discover {
     &Your-IP-Address := &parent.request.Framed-IP-Address
 
     ...
@@ -162,5 +179,5 @@ subrequest ::Disconnect-Request {
 }
 ----
 
-// Copyright (C) 2021 Network RADIUS SAS.  Licenced under CC-by-NC 4.0.
+// Copyright (C) 2025 Network RADIUS SAS.  Licenced under CC-by-NC 4.0.
 // This documentation was developed by Network RADIUS SAS.
index d65889d5a62669af28fdabaa9d1b7b2ca2b29b8b..a2a5402d95627f38cf404961f3eb0af9c5946f96 100644 (file)
@@ -3995,22 +3995,74 @@ static unlang_t *compile_subrequest(unlang_t *parent, unlang_compile_t *unlang_c
                return NULL;
        }
 
+       dict = unlang_ctx->rules->attr.dict_def;
+
+       /*
+        *      @foo is "dictionary foo", as with references in the dictionaries.
+        *
+        *      @foo::bar is "dictionary foo, Packet-Type = ::bar"
+        *
+        *      foo::bar is "dictionary foo, Packet-Type = ::bar"
+        *
+        *      ::bar is "this dictionary, Packet-Type = ::bar", BUT
+        *      we don't try to parse the new dictionary name, as it
+        *      doesn't exist.
+        */
+       if ((name2[0] == '@') ||
+           ((name2[0] != ':') && (name2[0] != '&') && (strchr(name2 + 1, ':') != NULL))) {
+               char *q;
+
+               if (name2[0] == '@') name2++;
+
+               MEM(namespace = talloc_strdup(parent, name2));
+               q = namespace;
+
+               while (fr_dict_attr_allowed_chars[(unsigned int) *q]) {
+                       q++;
+               }
+               *q = '\0';
+
+               dict = fr_dict_by_protocol_name(namespace);
+               if (!dict) {
+                       dict_ref = fr_dict_autoload_talloc(NULL, &dict, namespace);
+                       if (!dict_ref) {
+                               cf_log_err(cs, "Unknown namespace in '%s'", name2);
+                               talloc_free(namespace);
+                               return NULL;
+                       }
+               }
+
+               /*
+                *      Skip the dictionary name, and go to the thing
+                *      right after it.
+                */
+               name2 += (q - namespace);
+               TALLOC_FREE(namespace);
+       }
+
        /*
+        *      @dict::enum is "other dictionary, Packet-Type = ::enum"
         *      ::enum is this dictionary, "Packet-Type = ::enum"
         */
        if ((name2[0] == ':') && (name2[1] == ':')) {
-               dict = unlang_ctx->rules->attr.dict_def;
                packet_name = name2;
                goto get_packet_type;
        }
 
        /*
-        *      If !tmpl_require_enum_prefix, '&' means "attribute reference".
+        *      Can't do foo.bar.baz::foo, the enums are only used for Packet-Type.
+        */
+       if (strchr(name2, ':') != NULL) {
+               cf_log_err(cs, "Reference cannot contain enum value in '%s'", name2);
+               return NULL;
+       }
+
+       /*
+        *      '&' means "attribute reference"
         *
-        *      Or, bare word means "attribute reference".
+        *      Or, bare word an require_enum_prefix means "attribute reference".
         */
-       if ((name2[0] == '&') ||
-           (tmpl_require_enum_prefix && ((p = strchr(name2, ':')) == NULL))) {
+       if ((name2[0] == '&') || tmpl_require_enum_prefix) {
                ssize_t slen;
 
                slen = tmpl_afrom_attr_substr(parent, NULL, &vpt,
@@ -4045,47 +4097,19 @@ static unlang_t *compile_subrequest(unlang_t *parent, unlang_compile_t *unlang_c
        }
 
        /*
-        *      subrequest foo::bar { ... }
-        *
-        *      Change to dictionary "foo", packet type "bar".
-        */
-       if (p) {
-               if (p[1] != ':') {
-                       cf_log_err(cs, "Invalid syntax in namespace::enum");
-                       return NULL;
-               }
-
-               MEM(namespace = talloc_strdup(parent, name2)); /* get a modifiable copy */
-
-               p = namespace + (p - name2);
-               *p = '\0';
-               p += 2;
-               packet_name = p;
-
-               dict = fr_dict_by_protocol_name(namespace);
-               if (!dict) {
-                       dict_ref = fr_dict_autoload_talloc(NULL, &dict, namespace);
-                       if (!dict_ref) {
-                               cf_log_err(cs, "Unknown namespace '%s'", namespace);
-                               talloc_free(namespace);
-                               return NULL;
-                       }
-               }
-
-               goto get_packet_type;
-       }
-
-       /*
-        *      subrequest foo { ... }
-        *
-        *      Change packet types without changing dictionaries.
+        *      foo.bar without '&' and NOT require_enum_prefix is fugly, we should disallow it.
         */
        p = strchr(name2, '.');
        if (!p) {
+               cf_log_warn(cs, "Please upgrade configuration to use '::%s' when changing packet types",
+                           name2);
+
                dict = unlang_ctx->rules->attr.dict_def;
                packet_name = name2;
 
-       } else if (!tmpl_require_enum_prefix) {
+       } else {
+               cf_log_warn(cs, "Please upgrade configuration to use '@' when changing dictionaries");
+
                /*
                 *      subrequest foo.bar { ... }
                 *
@@ -4109,14 +4133,6 @@ static unlang_t *compile_subrequest(unlang_t *parent, unlang_compile_t *unlang_c
 
                WARN("Deprecated syntax 'subrequest %s ...'", name2);
                WARN(" please switch to 'subrequest %s::%s ...", namespace, packet_name);
-
-       } else {
-               /*
-                *      We have tmpl_require_enum prefix, so bare word foo.bar is "attribute foo,
-                *      sub-attribute bar"
-                */
-               dict = unlang_ctx->rules->attr.dict_def;
-               packet_name = name2;
        }
 
        /*
index 11ae7260b0c1ae798a6ed88ddd6c9844e0b11d02..89cb2d257f7603efd22b6e754de1b9c0f920bd48 100644 (file)
@@ -1,7 +1,7 @@
 # Verify that the cache update and key sections work with foreign attributes
 
-subrequest dhcpv4.Discover {
-       subrequest radius.Access-Request {
+subrequest @dhcpv4::Discover {
+       subrequest @radius::Access-Request {
                caller dhcpv4 {
                        &parent.Gateway-IP-Address = 127.0.0.1
                        &parent.control.Your-IP-Address = 127.0.0.2