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
--- /dev/null
+# -*- 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
+
+}
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;
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;
}
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);
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) {
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);
*/
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 {
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.
-subrequest {
+subrequest Access-Request {
update parent.control {
&Cleartext-Password := 'hello'
}
#
# PRE: subrequest
#
-subrequest {
+subrequest Access-Request {
update request {
&User-Name := &parent.request:User-Name
}