From: Alan T. DeKok Date: Wed, 24 Apr 2019 14:22:37 +0000 (-0400) Subject: fix up subrequest X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d825e1c98e06ddd08f10377baaffd12824233a67;p=thirdparty%2Ffreeradius-server.git fix up subrequest to allow for protocol changes, packet type changes, tests, and then re-add "originate-coa" which is much simpler --- diff --git a/man/man5/unlang.5 b/man/man5/unlang.5 index bff34293074..1d89c3c1668 100644 --- a/man/man5/unlang.5 +++ b/man/man5/unlang.5 @@ -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." to refer to an attribute in the +parent. .DS - subrequest { + subrequest { .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 index 00000000000..a5266ecebc6 --- /dev/null +++ b/raddb/sites-available/originate-coa @@ -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 + +} diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index fd76a1c8141..7473794de57 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -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 ."); 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; } diff --git a/src/lib/unlang/subrequest.c b/src/lib/unlang/subrequest.c index 023e10a131b..6c1917ac823 100644 --- a/src/lib/unlang/subrequest.c +++ b/src/lib/unlang/subrequest.c @@ -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); diff --git a/src/lib/unlang/unlang_priv.h b/src/lib/unlang/unlang_priv.h index b0534d5e54c..b9cc585b2f6 100644 --- a/src/lib/unlang/unlang_priv.h +++ b/src/lib/unlang/unlang_priv.h @@ -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. diff --git a/src/tests/keywords/subrequest b/src/tests/keywords/subrequest index df3a4739ea8..2a352bcfe71 100644 --- a/src/tests/keywords/subrequest +++ b/src/tests/keywords/subrequest @@ -1,4 +1,4 @@ -subrequest { +subrequest Access-Request { update parent.control { &Cleartext-Password := 'hello' } diff --git a/src/tests/keywords/subrequest-if b/src/tests/keywords/subrequest-if index 13804c10025..89e4bf773ea 100644 --- a/src/tests/keywords/subrequest-if +++ b/src/tests/keywords/subrequest-if @@ -1,7 +1,7 @@ # # PRE: subrequest # -subrequest { +subrequest Access-Request { update request { &User-Name := &parent.request:User-Name }