]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
fix up subrequest
authorAlan T. DeKok <aland@freeradius.org>
Wed, 24 Apr 2019 14:22:37 +0000 (10:22 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 24 Apr 2019 14:22:37 +0000 (10:22 -0400)
to allow for protocol changes, packet type changes, tests,
and then re-add "originate-coa" which is much simpler

man/man5/unlang.5
raddb/sites-available/originate-coa [new file with mode: 0644]
src/lib/unlang/compile.c
src/lib/unlang/subrequest.c
src/lib/unlang/unlang_priv.h
src/tests/keywords/subrequest
src/tests/keywords/subrequest-if

index bff34293074e6c16a9daad54b579c2ddd34ec907..1d89c3c166868745e6644d3120a1f3033e063812 100644 (file)
@@ -283,12 +283,36 @@ The "subrequest" keyword creates an empty subrequest (i.e. child
 request).  Attributes in the child can be copied from the parent via
 an "update" block.
 
+The packet type and protocol can be changed in a subrequest, by
+changing the second paramater to the subrequest.  That parameter can
+be:
+
+* packet name, e.g. "Access-Request"
+* protocol followed by packet name, e.g. "dhcpv4.DHCP-Discover".
+
+This configuration allows the server to receive one type of packet,
+and then create another.  For example, the server can receive an
+Accounting-Request packet, and then create a subrequest that is a
+Disconnect-Request.  That subrequest can then be sent to a NAS.
+
+The subrequest is created with no attributes.  Any attributes needed
+by the subrequest should be manually copied from the parent request.
+
+The subrequest can also be used to change protocols.  For example, the
+server can receive a RADIUS Access-Request, and then create a DHCPv4
+packet of type DHCP-Discover.  Note that when the protocol changes,
+the attributes in the "subrequest" section are parsed in the context
+of the new protocol.
+
+The original request can be accessed from inside of q "subrequest"
+section.  Simple use "&parent.<ref>" to refer to an attribute in the
+parent.
 .DS
-       subrequest {
+       subrequest <type> {
 .br
                update request {
 .br
-                       User-Name = parent.request:User-Name
+                       &User-Name = &parent.request:User-Name
 .br
                        ...
 .br
diff --git a/raddb/sites-available/originate-coa b/raddb/sites-available/originate-coa
new file mode 100644 (file)
index 0000000..a5266ec
--- /dev/null
@@ -0,0 +1,100 @@
+#  -*- text -*-
+#
+#  :toc:
+#
+#  $Id$
+
+######################################################################
+#
+#  = Originate CoA-Request packets
+#
+#  The server can originate Change of Authorization (CoA) or
+#  Disconnect request packets.  These packets are used to dynamically
+#  change the parameters of a users session (bandwidth, etc.), or
+#  to forcibly disconnect the user.
+#
+#  There are some caveats.  Not all NAS vendors support this
+#  functionality.  Even for the ones that do, it may be difficult to
+#  find out what needs to go into a CoA-Request or Disconnect-Request
+#  packet.  All we can suggest is to read the NAS documentation
+#  available from the vendor.  That documentation SHOULD describe
+#  what information their equipment needs to see in a CoA packet.
+#
+#  This information is usually a list of attributes such as:
+#
+#      NAS-IP-Address (or NAS-IPv6 address)
+#      NAS-Identifier
+#      User-Name
+#      Acct-Session-Id
+#
+#  CoA packets can be originated when a normal Access-Request or
+#  Accounting-Request packet is received.  Simply create a subrequest,
+#  and call the `radius` module to send the packet.
+#
+#      subrequest Disconnect-Request {
+#             &User-Name = &parent.request:User-Name
+#             &Acct-Session-Id = &parent.request:Acct-Session-Id
+#             &NAS-IP-Address = &parent.NAS-IP-Address}
+#             ...
+#      }
+#
+#  Note that this functionality is configured differently from v3.
+#
+######################################################################
+
+#
+#  This is an *example* virtual server.  It accepts `Accounting-Request`
+#  packets.  It then sends a `Disconnect-Request` packet for every
+#  `Accounting-Request` packet it receives.
+#
+#  You should NOT enable this virtual server.  Instead, use it as an
+#  example, and copy the "subrequest" section to the virtual server
+#  that is actually receiving `Accounting-Request` packets.
+#
+server originate-coa.example.com {
+       namespace = radius
+
+       #  Listen on the Accounting port.
+       #
+       listen {
+               type = Accounting-Request
+               transport = udp
+
+               udp {
+                       ipaddr = *
+                       port = 1812
+               }
+       }
+
+recv Accounting-Request {
+       subrequest Disconnect-Request {
+               #
+               #  The subrequest begins empty, so copy all necessary
+               #  attributes over.
+               #
+               update request {
+                       &User-Name := &parent.request:User-Name
+                       &Acct-Session-Id := &parent.request:Acct-Session-Id
+                       &NAS-Identifier := &parent.request:NAS-Identifier
+                       &NAS-IP-Address := &parent.request:NAS-IP-Addres
+                       &NAS-IPv6-Address := &parent.request:NAS-IPv6-Address
+                       &NAS-Port := &parent.request:NAS-Port
+                       &Framed-IP-Address := &parent.request:Framed-IP-Address
+               }
+
+               #
+               #  Call the `radius` module to send a CoA packet.
+               #
+               #  Note that you MUST create an instance of the
+               #  `radius` module, called "radius.coa" in order for
+               #  this to work.
+               #
+               #  See the `radius` module for more documentation on
+               #  how it works.
+               #
+               radius.coa
+
+       }
+}  # recv Accounting-Request
+
+}
index fd76a1c81416aa598ac48b3e4b1d55f972279036..7473794de578710d37177b496d25cb6030e3bbea 100644 (file)
@@ -2731,22 +2731,18 @@ static unlang_t *compile_parallel(unlang_t *parent, unlang_compile_t *unlang_ctx
 static unlang_t *compile_subrequest(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs,
                                    unlang_group_type_t group_type, unlang_group_type_t parentgroup_type, unlang_type_t mod_type)
 {
-       char const *name2;
-       unlang_t *c;
-       unlang_group_t *g;
-
-       /*
-        *      async async is not allowed.  It will work, but we
-        *      don't know what it means.  If someone has a good
-        *      use-case, we can try enabling it.
-        */
-       for (c = parent; c != NULL; c = c->parent) {
-               if (c->type == UNLANG_TYPE_SUBREQUEST) {
-                       cf_log_err(cs, "'%s' sections cannot be nested inside of other '%s' sections",
-                                  unlang_ops[mod_type].name, unlang_ops[mod_type].name);
-                       return NULL;
-               }
-       }
+       char const              *name2;
+       unlang_t                *c;
+       unlang_group_t          *g;
+       unlang_compile_t        unlang_ctx2;
+       vp_tmpl_rules_t         parse_rules;
+       fr_dict_t const         *dict;
+       fr_dict_attr_t const    *da;
+       fr_dict_enum_t const    *type_enum;
+       char const              *namespace, *packet_name, *component_name, *p;
+       char                    buffer[64];
+       char                    buffer2[64];
+       char                    buffer3[64];
 
        g = group_allocate(parent, cs, group_type, mod_type);
        if (!g) return NULL;
@@ -2756,14 +2752,132 @@ static unlang_t *compile_subrequest(unlang_t *parent, unlang_compile_t *unlang_c
        c->debug_name = c->name;
 
        /*
-        *      subrequests can't have an argument.
+        *      subrequests can specify the dictionary if they want to.
         */
        name2 = cf_section_name2(cs);
-       if (name2) {
-               cf_log_err(cs, "Invalid argument '%s' to subrequest", name2);
+       if (!name2) {
+               cf_log_err(cs, "Invalid syntax: expected <namespace>.<packet>");
                return NULL;
        }
-       return compile_children(g, parent, unlang_ctx, group_type, parentgroup_type);
+
+       p = strchr(name2, '.');
+       if (!p) {
+               dict = unlang_ctx->rules->dict_def;
+               namespace = fr_dict_root(dict)->name;
+               packet_name = name2;
+
+       } else {
+               if ((size_t) (p - name2) >= sizeof(buffer)) {
+                       cf_log_err(cs, "Unknown namespace '%.*s'", (int) (p - name2), name2);
+                       return NULL;
+               }
+
+               memcpy(buffer, name2, p - name2);
+               buffer[p - name2] = '\0';
+
+               dict = fr_dict_by_protocol_name(buffer);
+               if (!dict) {
+                       cf_log_err(cs, "Unknown namespace '%.*s'", (int) (p - name2), name2);
+                       return NULL;
+               }
+
+               namespace = buffer;
+               p++;
+               packet_name = p;        // need to quiet a stupid compiler
+       }
+
+       da = fr_dict_attr_by_name(dict, "Packet-Type");
+       if (!da) {
+               cf_log_err(cs, "No such attribute 'Packet-Type' in namespace '%s'", namespace);
+               return NULL;
+       }
+
+       /*
+        *      Get the packet name.
+        */
+       if (p) {
+               packet_name = p;
+               p = strchr(packet_name, '.');
+               if (p) {
+                       if ((size_t) (p - packet_name) >= sizeof(buffer2)) {
+                               cf_log_err(cs, "No such value '%.*s' for attribute 'Packet-Type' in namespace '%s'",
+                                          (int) (p - packet_name), packet_name, namespace);
+                               return NULL;
+                       }
+
+                       memcpy(buffer2, packet_name, p - packet_name);
+                       buffer[p - packet_name] = '\0';
+                       packet_name = buffer2;
+                       p++;
+               }
+       }
+
+       type_enum = fr_dict_enum_by_alias(da, packet_name, -1);
+       if (!type_enum) {
+               cf_log_err(cs, "No such value '%s' for attribute 'Packet-Type' in namespace '%s'",
+                          packet_name, namespace);
+               return NULL;
+       }
+
+       unlang_ctx2.component = unlang_ctx->component;
+       unlang_ctx2.name = unlang_ctx->name;
+
+       /*
+        *      Figure out the component name we're supposed to call.
+        *      Which isn't necessarily the same as the one from the
+        *      parent request.
+        */
+       if (p) {
+               component_name = p;
+               p = strchr(component_name, '.');
+               if (p) {
+                       rlm_components_t i;
+
+                       if ((size_t) (p - component_name) >= sizeof(buffer3)) {
+                       unknown_component:
+                               cf_log_err(cs, "No such component '%.*s",
+                                          (int) (p - component_name), component_name);
+                               return NULL;
+                       }
+
+                       memcpy(buffer3, component_name, p - component_name);
+                       buffer[p - component_name] = '\0';
+                       component_name = buffer3;
+
+                       for (i = MOD_AUTHENTICATE; i < MOD_COUNT; i++) {
+                               if (strcmp(comp2str[i], component_name) == 0) {
+                               break;
+                               }
+                       }
+
+                       if (i == MOD_COUNT) goto unknown_component;
+
+                       unlang_ctx2.component = i;
+                       unlang_ctx2.name = component_name;
+               }
+       }
+
+       parse_rules = *unlang_ctx->rules;
+       parse_rules.dict_def = dict;
+
+       unlang_ctx2.actions = unlang_ctx->actions;
+       unlang_ctx2.section_name1 = "subrequest";
+       unlang_ctx2.section_name2 = name2;
+       unlang_ctx2.rules = &parse_rules;
+
+       /*
+        *      Compile the children of this subrequest in the context
+        *      of the dictionary && namespace that was given by the
+        *      subrequest.
+        */
+       c = compile_children(g, parent, &unlang_ctx2, group_type, parentgroup_type);
+       if (!c) return NULL;
+
+       g->dict = dict;
+       g->attr_packet_type = da;
+       g->type_enum = type_enum;
+
+       return c;
 }
 
 
index 023e10a131bce781e83f604a0a42f10f6d82bf6c..6c1917ac82330591d14c0a6185182ed4b8c7d271 100644 (file)
@@ -139,6 +139,7 @@ static unlang_action_t unlang_subrequest(REQUEST *request,
        rlm_rcode_t                     rcode;
        int                             priority;
        unlang_resume_t                 *mr;
+       VALUE_PAIR                      *vp;
 
        if (frame->state) state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
 
@@ -159,7 +160,7 @@ static unlang_action_t unlang_subrequest(REQUEST *request,
        if (!state) {
                frame->state = state = talloc(stack, unlang_frame_state_subrequest_t);
                state->child = unlang_io_child_alloc(request, g->children,
-                                                    request->server_cs, request->dict,
+                                                    request->server_cs, g->dict,
                                                     frame->result,
                                                     UNLANG_NEXT_SIBLING, UNLANG_DETACHABLE);
                if (!state->child) {
@@ -204,15 +205,20 @@ static unlang_action_t unlang_subrequest(REQUEST *request,
        RDEBUG2("- creating subrequest (%s)", child->name);
 
        /*
-        *      Run the child in the same section as the master.  If
-        *      we want to run a different virtual server, we have to
-        *      create a "server" keyword.
+        *      Set the packet type.
         *
-        *      The only difficult there is setting child->async
-        *      to... some magic value. :( That code should be in a
-        *      virtual server callback, and not directly in the
-        *      interpreter.
+        *      @todo - this doesn't strictly work for DHCP, which
+        *      uses DHCP-Message-Type, *and* which has message type
+        *      by a uint8.  We should likely have some other mapping
+        *      in the dictionary so that we can find the real
+        *      attribute.
         */
+       vp = fr_pair_afrom_da(child->packet, g->attr_packet_type);
+       if (vp) {
+               child->packet->code = vp->vp_uint32 = g->type_enum->value->vb_uint32;
+               fr_pair_add(&child->packet->vps, vp);
+       }
+
        rcode = unlang_interpret_run(child);
        if (rcode != RLM_MODULE_YIELD) {
                if (!state->persist) unlang_subrequest_free(&child);
index b0534d5e54cc8f9eed87dc5ab3b97bc5fbf13ad7..b9cc585b2f64e051e23d1c125e8f5cba5719f7bd 100644 (file)
@@ -150,7 +150,7 @@ typedef struct {
         */
        union {
                struct {
-                       vp_tmpl_t               *vpt;           //!< #UNLANG_TYPE_SWITCH, #UNLANG_TYPE_MAP, #UNLANG_TYPE_CALL
+                       vp_tmpl_t               *vpt;           //!< #UNLANG_TYPE_SWITCH, #UNLANG_TYPE_MAP
 
                        union {
                                struct {
@@ -160,6 +160,11 @@ typedef struct {
                                struct {
                                        CONF_SECTION            *server_cs;     //!< #UNLANG_TYPE_CALL
                                };
+                               struct {
+                                       fr_dict_t const         *dict;          //!< #UNLANG_TYPE_SUBREQUEST
+                                       fr_dict_attr_t const    *attr_packet_type;
+                                       fr_dict_enum_t const    *type_enum;
+                               };
                        };
                };
                fr_cond_t               *cond;          //!< #UNLANG_TYPE_IF, #UNLANG_TYPE_ELSIF.
index df3a4739ea84fd6775782387b7888eeff8114ef3..2a352bcfe71252997582c394cd83f719247433c5 100644 (file)
@@ -1,4 +1,4 @@
-subrequest {
+subrequest Access-Request {
        update parent.control {
                &Cleartext-Password := 'hello'
        }
index 13804c100258764a14d1e69c5d50bb431f3f962f..89e4bf773ea40088a7dbdd3d50d77102fa5956ac 100644 (file)
@@ -1,7 +1,7 @@
 #
 #  PRE: subrequest
 #
-subrequest {
+subrequest Access-Request {
        update request {
                &User-Name := &parent.request:User-Name
        }