caller.c \
compile.c \
condition.c \
+ detach.c \
foreach.c \
function.c \
group.c \
parallel.c \
return.c \
subrequest.c \
+ subrequest_child.c \
switch.c \
tmpl.c \
xlat.c \
static uint32_t instance_count;
+/** Different operations the interpreter can execute
+ */
+unlang_op_t unlang_ops[UNLANG_TYPE_MAX];
+
/** Return whether a section has unlang data associated with it
*
* @param[in] cs to check.
unlang_parallel_init();
unlang_return_init();
if (unlang_subrequest_op_init() < 0) return -1;
+ unlang_detach_init();
unlang_switch_init();
unlang_call_init();
unlang_caller_init();
g = unlang_generic_to_group(frame->instruction);
gext = unlang_group_to_call(g);
- /*
- * Push OUR subsection onto the childs stack frame.
- */
-
/*
* Work out the current request type.
*/
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/detach.c
+ * @brief Unlang detach keyword
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSID("$Id$")
+
+#include "unlang_priv.h"
+
+/** Signal a child to detach
+ *
+ */
+static unlang_action_t unlang_detach(rlm_rcode_t *p_result, request_t *request, UNUSED unlang_stack_frame_t *frame)
+{
+ /*
+ * Signal all frames in the child's stack
+ * that it's time to detach.
+ */
+ unlang_interpret_signal(request, FR_SIGNAL_DETACH);
+
+ /*
+ * Detach failed...
+ */
+ if (unlikely(request->parent)) {
+ *p_result = RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Initialise subrequest ops
+ *
+ */
+void unlang_detach_init(void)
+{
+ unlang_register(UNLANG_TYPE_DETACH,
+ &(unlang_op_t){
+ .name = "detach",
+ .interpret = unlang_detach,
+ });
+}
/**
* $Id$
*
- * @file unlang/interpret.h
- * @brief Declarations for the unlang interpreter.
+ * @file unlang/interpret_priv.h
+ * @brief Private declarations for the unlang interpreter.
*
- * @copyright 2019 The FreeRADIUS server project
+ * @copyright 2021 The FreeRADIUS server project
*/
#include <freeradius-devel/util/event.h>
#include <freeradius-devel/unlang/interpret.h>
+#include "interpret_priv.h"
#ifdef __cplusplus
extern "C" {
void *uctx;
};
-static inline void unlang_interpret_child_init(request_t *request)
+static inline void interpret_child_init(request_t *request)
{
unlang_interpret_t *intp = unlang_interpret_get(request);
return unlang_io_subrequest_alloc(parent, namespace, UNLANG_NORMAL_CHILD);
}
-/** Yield, spawning a child request, and resuming once the child request is complete
- *
- * @param[in] p_result Final rcode from when evaluation of the child request finishes.
- * @param[out] child to yield to. The child knows about the parent,
- * which is why the parent isn't passed explicitly.
- * @param[in] resume function to call when the child has finished executing.
- * @param[in] signal function to call if a signal is received.
- * @param[in] session control values. Whether we restore/store session info.
- * @param[in] rctx to pass to the resume() and signal() callbacks.
- * @return
- * - UNLANG_ACTION_YIELD.
- */
-unlang_action_t unlang_module_yield_to_subrequest(rlm_rcode_t *p_result, request_t *child,
- unlang_module_resume_t resume,
- unlang_module_signal_t signal,
- unlang_subrequest_session_t const *session,
- void *rctx)
-{
- /*
- * Push the resumption point BEFORE adding the subrequest
- * to the parents stack.
- */
- (void) unlang_module_yield(child->parent, resume, signal, rctx);
-
- /*
- * Push the subrequest and immediately run it.
- */
- if (unlang_subrequest_push(p_result, child, session, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_STOP_PROCESSING;
-
- return UNLANG_ACTION_YIELD;
-}
-
/** Push a pre-compiled xlat and resumption state onto the stack for evaluation
*
* In order to use the async unlang processor the calling module needs to establish
* Detach the child, and insert
* it into the backlog.
*/
- if (unlang_detached_child_init(child) < 0) {
+ if (unlang_subrequest_child_detach(child) < 0) {
talloc_free(child);
*p_result = RLM_MODULE_FAIL;
state->children[i].name = talloc_bstrdup(state, child->name);
state->children[i].request = child;
state->children[i].state = CHILD_RUNNABLE;
- unlang_interpret_child_init(child);
+
}
+ interpret_child_init(child);
/*
* Push the first instruction for
#include "unlang_priv.h"
#include "interpret_priv.h"
#include "subrequest_priv.h"
-
-/** Parameters for initialising the subrequest
- *
- * State of one level of nesting within an xlat expansion.
- */
-typedef struct {
- rlm_rcode_t *p_result; //!< Where to store the result.
- request_t *child; //!< Pre-allocated child request.
- bool free_child; //!< Whether we should free the child after
- ///< it completes.
- bool detachable; //!< Whether the request can be detached.
- unlang_subrequest_session_t session; //!< Session configuration.
-} unlang_frame_state_subrequest_t;
-
-static fr_dict_t const *dict_freeradius;
-
-extern fr_dict_autoload_t subrequest_dict[];
-fr_dict_autoload_t subrequest_dict[] = {
- { .out = &dict_freeradius, .proto = "freeradius" },
- { NULL }
-};
-
-static fr_dict_attr_t const *request_attr_request_lifetime;
-
-extern fr_dict_attr_autoload_t subrequest_dict_attr[];
-fr_dict_attr_autoload_t subrequest_dict_attr[] = {
- { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
- { NULL }
-};
-
-typedef struct {
- rlm_rcode_t rcode; //!< frame->result from before detach was called
-} unlang_frame_state_detach_t;
-
-static void unlang_max_request_time(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
-{
- request_t *request = talloc_get_type_abort(uctx, request_t);
-
- RDEBUG("Reached Request-Lifetime. Forcibly stopping request");
-
- /*
- * The request is scheduled and isn't running. Remove it
- * from the backlog.
- */
- if (unlang_request_is_scheduled(request)) {
- fr_assert(request->backlog != NULL);
- (void) fr_heap_extract(request->backlog, request);
- }
-
- talloc_free(request);
-}
+#include "subrequest_child_priv.h"
/** Send a signal from parent request to subrequest
*
*/
-static void unlang_subrequest_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_state_signal_t action)
+static void unlang_subrequest_parent_signal(UNUSED request_t *request, unlang_stack_frame_t *frame,
+ fr_state_signal_t action)
{
unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
request_t *child = talloc_get_type_abort(state->child, request_t);
+ /*
+ * Parent should never receive a detach
+ * signal whilst the child is running.
+ *
+ * Only the child receives a detach
+ * signal when the detach keyword is used.
+ */
+ fr_assert(action != FR_SIGNAL_DETACH);
+
+ /*
+ * Forward other signals to the child
+ */
unlang_interpret_signal(child, action);
}
-
-/** Process a subrequest until it either detaches, or is done.
+/** Parent being resumed after a child completes
*
*/
-static unlang_action_t unlang_subrequest_process(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+static unlang_action_t unlang_subrequest_parent_resume(rlm_rcode_t *p_result, request_t *request,
+ unlang_stack_frame_t *frame)
{
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
- request_t *child = talloc_get_type_abort(state->child, request_t);
- unlang_group_t *g = unlang_generic_to_group(frame->instruction);
- rlm_rcode_t rcode;
-
- fr_assert(child != NULL);
-
- rcode = unlang_interpret(child);
- if (rcode != RLM_MODULE_YIELD) {
- if (!fr_cond_assert(rcode < NUM_ELEMENTS(frame->instruction->actions))) return UNLANG_ACTION_STOP_PROCESSING;
-
- if (state->session.enable) fr_state_store_in_parent(child,
- state->session.unique_ptr,
- state->session.unique_int);
-
- /*
- * Copy the subrequest's reply list into
- * the parent, creating any n
- */
-
- calculate_result:
- if (child->reply) {
- unlang_subrequest_t *gext;
-
- /*
- * Copy reply attributes into the specified
- * destination.
- */
- gext = unlang_group_to_subrequest(g);
- if (gext->dst) {
- tmpl_attr_extent_t *extent = NULL;
- fr_dlist_head_t leaf;
- fr_dlist_head_t interior;
-
- fr_dlist_talloc_init(&leaf, tmpl_attr_extent_t, entry);
- fr_dlist_talloc_init(&interior, tmpl_attr_extent_t, entry);
-
- /*
- * Find out what we need to build and build it
- */
- if ((tmpl_extents_find(state, &leaf, &interior, request, gext->dst) < 0) ||
- (tmpl_extents_build_to_leaf(&leaf, &interior, gext->dst) < 0)) {
- RPDEBUG("Discarding subrequest attributes - Failed allocating groups");
- fr_dlist_talloc_free(&leaf);
- fr_dlist_talloc_free(&interior);
- goto done;
- }
- while ((extent = fr_dlist_tail(&leaf))) {
- fr_pair_list_copy(extent->list_ctx, extent->list, &child->reply_pairs);
- fr_dlist_talloc_free_tail(&leaf);
- }
- }
- }
-
- done:
- if (state->free_child) {
- unlang_subrequest_free(&child);
- state->child = NULL;
- frame->signal = NULL;
- }
-
- /*
- * Pass the result back to the module
- * that created the subrequest, or
- * use it to modify the current section
- * rcode.
- */
- if (state->p_result) *state->p_result = rcode;
+ unlang_group_t *g = unlang_generic_to_group(frame->instruction);
+ unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state,
+ unlang_frame_state_subrequest_t);
+ request_t *child = state->child;
+ unlang_subrequest_t *gext;
- *p_result = rcode;
-
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
+ RDEBUG3("Subrequest complete");
/*
- * The child has yielded, BUT has also detached itself.
- * We know this because it has reached into our state,
- * and removed itself as a child. We therefore just keep
- * running, and don't return yield.
+ * Child detached
*/
if (!state->child) {
- rcode = RLM_MODULE_NOOP;
- goto calculate_result;
+ RDEBUG3("Child has detached");
+ return UNLANG_ACTION_CALCULATE_RESULT;
}
/*
- * Else the child yielded, so we have to yield. Set up
- * the resume frame and continue.
+ * If there's a no destination tmpl, we're done.
*/
- return UNLANG_ACTION_YIELD;
-}
-
-
-static unlang_action_t unlang_subrequest_start(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
- request_t *child = state->child;
+ if (!child->reply) {
+ unlang_subrequest_free(&child);
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
/*
- * Restore state from the parent to the
- * subrequest.
+ * Otherwise... copy reply attributes into the
+ * specified destination.
*/
- if (state->session.enable) fr_state_restore_to_child(child,
- state->session.unique_ptr,
- state->session.unique_int);
+ gext = unlang_group_to_subrequest(g);
+ if (gext->dst) {
+ tmpl_attr_extent_t *extent = NULL;
+ fr_dlist_head_t leaf;
+ fr_dlist_head_t interior;
- REQUEST_VERIFY(child);
+ fr_dlist_talloc_init(&leaf, tmpl_attr_extent_t, entry);
+ fr_dlist_talloc_init(&interior, tmpl_attr_extent_t, entry);
- RDEBUG2("Creating subrequest (%s)", child->name);
- log_request_pair_list(L_DBG_LVL_1, request, NULL, &child->request_pairs, NULL);
+ /*
+ * Find out what we need to build and build it
+ */
+ if ((tmpl_extents_find(state, &leaf, &interior, request, gext->dst) < 0) ||
+ (tmpl_extents_build_to_leaf(&leaf, &interior, gext->dst) < 0)) {
+ RPDEBUG("Discarding subrequest attributes - Failed allocating groups");
+ fr_dlist_talloc_free(&leaf);
+ fr_dlist_talloc_free(&interior);
+ *p_result = RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+ while ((extent = fr_dlist_tail(&leaf))) {
+ fr_pair_list_copy(extent->list_ctx, extent->list, &child->reply_pairs);
+ fr_dlist_talloc_free_tail(&leaf);
+ }
+ }
- frame->process = unlang_subrequest_process;
- return unlang_subrequest_process(p_result, request, frame);
+ unlang_subrequest_free(&child);
+ return UNLANG_ACTION_CALCULATE_RESULT;
}
-
-static unlang_action_t unlang_subrequest_state_init(rlm_rcode_t *p_result, request_t *request,
- unlang_stack_frame_t *frame)
+static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, request_t *request,
+ unlang_stack_frame_t *frame)
{
unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
request_t *child;
-
rlm_rcode_t rcode;
fr_pair_t *vp;
* Set the packet type.
*/
MEM(vp = fr_pair_afrom_da(child->request_ctx, gext->attr_packet_type));
-
if (gext->type_enum) {
child->packet->code = vp->vp_uint32 = gext->type_enum->value->vb_uint32;
} else {
}
}
+ /*
+ * Setup the child so it'll inform us when
+ * it resumes, or if it detaches.
+ */
+ if (unlang_subrequest_child_push_resume(child, state) < 0) goto fail;
+
/*
* Push the first instruction the child's
* going to run.
*/
if (unlang_interpret_push(child, g->children, frame->result,
- UNLANG_NEXT_SIBLING, UNLANG_TOP_FRAME) < 0) goto fail;
+ UNLANG_NEXT_SIBLING, UNLANG_SUB_FRAME) < 0) goto fail;
- /*
- * Probably not a great idea to set state->p_result to
- * p_result, as it could be a pointer to an rlm_rcode_t
- * somewhere on the stack which could be invalidated
- * between unlang_subrequest being called and
- * unlang_subrequest_resume being called.
- *
- * ...so we just set it to NULL and interpret
- * that as use the p_result that was passed
- * in to the currently executing function.
- */
- state->p_result = NULL;
+ state->p_result = p_result;
state->free_child = true;
state->detachable = true;
state->session.unique_ptr = frame->instruction;
state->session.unique_int = 0;
- frame->process = unlang_subrequest_start;
- return unlang_subrequest_start(p_result, request, frame);
-}
+ frame->process = unlang_subrequest_parent_resume;
-/** Initialize a detached child
- *
- * Detach it from the parent, set up it's lifetime, and mark it as
- * runnable.
- */
-int unlang_detached_child_init(request_t *request)
-{
- fr_pair_t *vp;
-
- if (request_detach(request) < 0) {
- ERROR("Failed detaching child");
- return -1;
- }
-
- /*
- * Set Request Lifetime
- */
- vp = fr_pair_find_by_da(&request->control_pairs, request_attr_request_lifetime);
- if (!vp || (vp->vp_uint32 > 0)) {
- fr_time_delta_t when = 0;
- const fr_event_timer_t **ev_p;
-
- if (!vp) {
- when += fr_time_delta_from_sec(30); /* default to 30s if not set */
-
- } else if (vp->vp_uint32 > 3600) {
- RWDEBUG("Request-Timeout can be no more than 3600 seconds");
- when += fr_time_delta_from_sec(3600);
-
- } else if (vp->vp_uint32 < 5) {
- RWDEBUG("Request-Timeout can be no less than 5 seconds");
- when += fr_time_delta_from_sec(5);
-
- } else {
- when += fr_time_delta_from_sec(vp->vp_uint32);
- }
-
- ev_p = talloc_size(request, sizeof(*ev_p));
- memset(ev_p, 0, sizeof(*ev_p));
-
- (void) fr_event_timer_in(request, request->el, ev_p, when, unlang_max_request_time, request);
- }
-
- /*
- * Mark the child as runnable.
- */
- fr_assert(request->parent == NULL);
- unlang_interpret_child_init(request);
-
- return 0;
-}
-
-static unlang_action_t unlang_detach(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
- unlang_frame_state_detach_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_detach_t);
- unlang_stack_frame_t *parent_frame;
- unlang_frame_state_subrequest_t *parent_state;
-
- /*
- * We've detached, yielded, and now are continuing
- * processing. There's nothing more to do, so just
- * continue.
- */
- if (!request->parent) {
- *p_result = state->rcode;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * First time through, detach from the parent.
- */
- RDEBUG2("detach");
-
- fr_assert(request->parent != NULL);
-
- /*
- * Get the PARENT's stack.
- */
- parent_frame = frame_current(request->parent);
- parent_state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
-
- if (!parent_state->detachable) {
- RWDEBUG("Ignoring 'detach' as the request is not detachable");
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * If we can't detach the child OR we can't insert it
- * into the backlog, stop processing it.
- */
- if (unlang_detached_child_init(request) < 0) {
- return UNLANG_ACTION_STOP_PROCESSING;
- }
-
- /*
- * The parent frame no longer has a child, and therefore
- * can't be signaled.
- */
- fr_assert(parent_state->child == request);
- parent_state->child = NULL;
- parent_frame->signal = NULL;
-
- /*
- * Pass through whatever the previous instruction had as
- * the result.
- */
- state->rcode = *p_result;
-
- /*
- * Yield to the parent, who will discover that there's no
- * child, and return.
- */
- return UNLANG_ACTION_YIELD;
+ return unlang_subrequest_child_run(child); /* returns UNLANG_ACTION_YIELD */
}
/** Free a child request, detaching it from its parent and freeing allocated memory
*child = NULL;
}
-static unlang_group_t *subrequest_instruction;
-
-/** Push a pre-existing child back onto the stack as a subrequest
- *
- * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
- * that calls it.
- * Instruction *MUST* belong to the same virtual server as is set in the child.
- *
- * After the child is no longer required it *MUST* be freed with #unlang_subrequest_free.
- * It's not enough to free it with talloc_free.
+/** Initialise subrequest ops
*
- * @param[in] out Where to write the result of the subrequest.
- * @param[in] child to push.
- * @param[in] session control values. Whether we restore/store session info.
- * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return.
- * Set to UNLANG_SUB_FRAME if the interprer should continue.
- * @return
- * - 0 on success.
- * - -1 on failure.
*/
-int unlang_subrequest_push(rlm_rcode_t *out, request_t *child,
- unlang_subrequest_session_t const *session, bool top_frame)
-{
- unlang_stack_t *stack = child->parent->stack;
- unlang_stack_frame_t *frame;
- unlang_frame_state_subrequest_t *state;
-
- /*
- * Push a new subrequest frame onto the stack
- */
- if (unlang_interpret_push(child->parent, &subrequest_instruction->self,
- RLM_MODULE_UNKNOWN, UNLANG_NEXT_STOP, top_frame) < 0) return -1;
-
- frame = &stack->frame[stack->depth];
-
- /*
- * Allocate a state for the subrequest
- * This lets us override the normal request
- * the subrequest instruction would alloc.
- */
- state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
- state->p_result = out;
- state->child = child;
- state->free_child = false;
- state->detachable = false;
- if (session) state->session = *session;
-
- frame->process = unlang_subrequest_start;
-
- return 0;
-}
-
int unlang_subrequest_op_init(void)
{
- unlang_subrequest_t *gctx;
-
- if (fr_dict_autoload(subrequest_dict) < 0) {
- PERROR("%s", __FUNCTION__);
- return -1;
- }
- if (fr_dict_attr_autoload(subrequest_dict_attr) < 0) {
- PERROR("%s", __FUNCTION__);
- return -1;
- }
-
- /*
- * Needs to be dynamically allocated
- * so that talloc_get_type works
- * correctly.
- */
- gctx = talloc(NULL, unlang_subrequest_t);
- if (!gctx) {
- ERROR("%s: Out of memory", __FUNCTION__);
- return -1;
- }
- *gctx = (unlang_subrequest_t){
- .group = {
- .self = {
- .type = UNLANG_TYPE_SUBREQUEST,
- .name = "subrequest",
- .debug_name = "subrequest",
- .actions = {
- [RLM_MODULE_REJECT] = 0,
- [RLM_MODULE_FAIL] = MOD_ACTION_RETURN, /* Exit out of nested levels */
- [RLM_MODULE_OK] = 0,
- [RLM_MODULE_HANDLED] = 0,
- [RLM_MODULE_INVALID] = 0,
- [RLM_MODULE_DISALLOW] = 0,
- [RLM_MODULE_NOTFOUND] = 0,
- [RLM_MODULE_NOOP] = 0,
- [RLM_MODULE_UPDATED] = 0
- }
- }
- }
- };
-
- subrequest_instruction = unlang_subrequest_to_group(gctx);
unlang_register(UNLANG_TYPE_SUBREQUEST,
- &(unlang_op_t){
+ &(unlang_op_t){
.name = "subrequest",
- .interpret = unlang_subrequest_state_init,
- .signal = unlang_subrequest_signal,
+ .interpret = unlang_subrequest_parent_init,
+ .signal = unlang_subrequest_parent_signal,
.debug_braces = true,
.frame_state_size = sizeof(unlang_frame_state_subrequest_t),
.frame_state_name = "unlang_frame_state_subrequest_t",
- });
+ });
- unlang_register(UNLANG_TYPE_DETACH,
- &(unlang_op_t){
- .name = "detach",
- .interpret = unlang_detach,
- .frame_state_size = sizeof(unlang_frame_state_detach_t),
- .frame_state_name = "unlang_frame_state_detach_t",
- });
+ if (unlang_subrequest_child_op_init() < 0) return -1;
return 0;
}
void unlang_subrequest_op_free(void)
{
- talloc_free(subrequest_instruction);
- fr_dict_autofree(subrequest_dict);
+ unlang_subrequest_child_op_free();
}
* @file unlang/subrequest.h
*
* @copyright 2019 The FreeRADIUS server project
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
*/
#ifdef __cplusplus
extern "C" {
#endif
+#include <freeradius-devel/server/request.h>
+#include <freeradius-devel/server/rcode.h>
+#include <stdbool.h>
+
typedef struct {
bool enable; //!< Whether we should store/restore sessions.
void const *unique_ptr; //!< Session unique ptr identifier.
int unique_int; //!< Session unique int identifier.
} unlang_subrequest_session_t;
+unlang_action_t unlang_subrequest_child_run(request_t *child);
+
+int unlang_subrequest_child_detach(request_t *request);
+
+int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
+ unlang_subrequest_session_t const *session,
+ bool top_frame);
#ifdef __cplusplus
}
#endif
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/subrequest.c
+ * @brief Unlang "subrequest" and "detach" keyword evaluation.
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+#include <freeradius-devel/server/state.h>
+#include "interpret_priv.h"
+#include "subrequest_child_priv.h"
+#include "subrequest_priv.h"
+#include "unlang_priv.h"
+
+/** Holds a synthesised instruction that we insert into the parent request
+ *
+ */
+static unlang_subrequest_t *subrequest_instruction;
+
+static fr_dict_t const *dict_freeradius;
+
+extern fr_dict_autoload_t subrequest_dict[];
+fr_dict_autoload_t subrequest_dict[] = {
+ { .out = &dict_freeradius, .proto = "freeradius" },
+ { NULL }
+};
+
+static fr_dict_attr_t const *request_attr_request_lifetime;
+
+extern fr_dict_attr_autoload_t subrequest_dict_attr[];
+fr_dict_attr_autoload_t subrequest_dict_attr[] = {
+ { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
+ { NULL }
+};
+
+/** Event handler to free a detached child
+ *
+ */
+static void unlang_detached_max_request_time(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
+{
+ request_t *request = talloc_get_type_abort(uctx, request_t);
+
+ RDEBUG("Reached Request-Lifetime. Forcibly stopping request");
+
+ /*
+ * The request is scheduled and isn't running. Remove it
+ * from the backlog.
+ */
+ if (unlang_request_is_scheduled(request)) {
+ fr_assert(request->backlog != NULL);
+ (void) fr_heap_extract(request->backlog, request);
+ }
+
+ talloc_free(request);
+}
+
+/** Initialize a detached child
+ *
+ * Detach it from the parent, set up it's lifetime, and mark it as
+ * runnable.
+ */
+int unlang_subrequest_child_detach(request_t *request)
+{
+ fr_pair_t *vp;
+
+ if (request_detach(request) < 0) {
+ ERROR("Failed detaching child");
+ return -1;
+ }
+
+ /*
+ * Set Request Lifetime
+ */
+ vp = fr_pair_find_by_da(&request->control_pairs, request_attr_request_lifetime);
+ if (!vp || (vp->vp_uint32 > 0)) {
+ fr_time_delta_t when = 0;
+ const fr_event_timer_t **ev_p;
+
+ if (!vp) {
+ when += fr_time_delta_from_sec(30); /* default to 30s if not set */
+
+ } else if (vp->vp_uint32 > 3600) {
+ RWDEBUG("Request-Timeout can be no more than 3600 seconds");
+ when += fr_time_delta_from_sec(3600);
+
+ } else if (vp->vp_uint32 < 5) {
+ RWDEBUG("Request-Timeout can be no less than 5 seconds");
+ when += fr_time_delta_from_sec(5);
+
+ } else {
+ when += fr_time_delta_from_sec(vp->vp_uint32);
+ }
+
+ ev_p = talloc_size(request, sizeof(*ev_p));
+ memset(ev_p, 0, sizeof(*ev_p));
+
+ if (fr_event_timer_in(request, request->el, ev_p, when,
+ unlang_detached_max_request_time, request) < 0) {
+ talloc_free(ev_p);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/** Process a detach signal in the child
+ *
+ * This processes any detach signals the child receives
+ * The child doesn't actually do the detaching
+ */
+static void unlang_subrequest_child_signal(request_t *request, fr_state_signal_t action, UNUSED void *uctx)
+{
+ unlang_frame_state_subrequest_t *state;
+
+ /*
+ * Ignore signals which aren't detach,
+ * and ignore the signal if we have no parent.
+ */
+ if ((action != FR_SIGNAL_DETACH) || !request->parent) return;
+
+ state = talloc_get_type_abort(frame_current(request->parent), unlang_frame_state_subrequest_t);
+
+ /*
+ * Ignore signals if the parent didn't want the child
+ * to be detached.
+ */
+ if (!state->detachable) return;
+
+ /*
+ * Place child's state back inside the parent
+ */
+ if (state->session.enable) fr_state_store_in_parent(request,
+ state->session.unique_ptr,
+ state->session.unique_int);
+
+ if (!fr_cond_assert(unlang_subrequest_child_detach(request) == 0)) {
+ REDEBUG("Child could not be detached");
+ return;
+ }
+
+ /*
+ * Indicate to the parent there's no longer a child
+ */
+ state->child = NULL;
+
+ /*
+ * Tell the parent to resume
+ */
+ unlang_interpret_mark_runnable(request->parent);
+}
+
+/** When the child is done, tell the parent that we've exited.
+ *
+ * This is pushed as a frame at the top of the child's stack, so when
+ * the child is done executing, it runs this to inform the parent
+ * that its done.
+ */
+static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result,
+ UNUSED int *p_priority, request_t *request, void *uctx)
+{
+ unlang_frame_state_subrequest_t *state = talloc_get_type_abort(uctx, unlang_frame_state_subrequest_t);
+
+ /*
+ * Child was detached, nothing more to do.
+ */
+ if (!request->parent) return UNLANG_ACTION_CALCULATE_RESULT;
+
+ /*
+ * Place child state back inside the parent
+ */
+ if (state->session.enable) fr_state_store_in_parent(request,
+ state->session.unique_ptr,
+ state->session.unique_int);
+ /*
+ * Record the child's result
+ */
+ if (state->p_result) *state->p_result = *p_result;
+
+ /*
+ * Resume the parent
+ */
+ unlang_interpret_mark_runnable(request->parent);
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Push a resumption frame onto a child's stack
+ *
+ * This is necessary so that the child informs its parent when it's done/detached
+ * and so that the child responds to detach signals.
+ */
+int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state)
+{
+ /*
+ * Push a resume frame into the child
+ */
+ if (unlang_interpret_push_function(child, NULL,
+ unlang_subrequest_child_done,
+ unlang_subrequest_child_signal, UNLANG_TOP_FRAME, state) < 0) return -1;
+
+ return_point_set(frame_current(child)); /* Stop return going through the resumption frame */
+
+ return 0;
+}
+
+/** Function to call when the parent is ready to execute the child
+ *
+ * @param[in] child to begin executing.
+ */
+unlang_action_t unlang_subrequest_child_run(request_t *child)
+{
+ unlang_frame_state_subrequest_t *state;
+
+ /*
+ * No parent means this is a pre-detached child
+ * so the parent should continue executing.
+ */
+ if (!child->parent) return UNLANG_ACTION_CALCULATE_RESULT;
+
+ state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t);
+
+ /*
+ * Ensure we restore the session state information
+ * into the child.
+ */
+ if (state->session.enable) fr_state_restore_to_child(child,
+ state->session.unique_ptr,
+ state->session.unique_int);
+ /*
+ * Ensures the child is setup correctly and adds
+ * it into the runnable queue of whatever owns
+ * the interpreter.
+ */
+ interpret_child_init(child);
+
+ return UNLANG_ACTION_YIELD;
+}
+
+/** Push a pre-existing child back onto the stack as a subrequest
+ *
+ * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
+ * that calls it.
+ *
+ * After the child is no longer required it *MUST* be freed with #unlang_subrequest_free.
+ * It's not enough to free it with talloc_free.
+ *
+ * This function should be called _before_ pushing any additional frames onto the child's
+ * stack for it to execute.
+ *
+ * @param[in] out Where to write the result of the subrequest.
+ * @param[in] child to push.
+ * @param[in] session control values. Whether we restore/store session info.
+ * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return.
+ * Set to UNLANG_SUB_FRAME if the interprer should continue.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
+ unlang_subrequest_session_t const *session,
+ bool top_frame)
+{
+ unlang_frame_state_subrequest_t *state;
+
+ /*
+ * Push a new subrequest frame onto the stack
+ *
+ * This allocates memory for the frame state
+ * which we fill in below.
+ */
+ if (unlang_interpret_push(child->parent, &subrequest_instruction->group.self,
+ RLM_MODULE_UNKNOWN, UNLANG_NEXT_STOP, top_frame) < 0) {
+ return -1;
+ }
+
+ /*
+ * Setup the state for the subrequest
+ */
+ state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t);
+ state->p_result = out;
+ state->child = child;
+ state->session = *session;
+
+ /*
+ * Push a resumption frame onto the stack
+ * so the child calls its parent when it's
+ * complete.
+ */
+ if (unlang_subrequest_child_push_resume(child, state) < 0) return -1;
+
+ return 0;
+}
+
+int unlang_subrequest_child_op_init(void)
+{
+ if (fr_dict_autoload(subrequest_dict) < 0) {
+ PERROR("%s", __FUNCTION__);
+ return -1;
+ }
+ if (fr_dict_attr_autoload(subrequest_dict_attr) < 0) {
+ PERROR("%s", __FUNCTION__);
+ return -1;
+ }
+
+ /*
+ * Needs to be dynamically allocated
+ * so that talloc_get_type works
+ * correctly.
+ */
+ subrequest_instruction = talloc(NULL, unlang_subrequest_t);
+ if (!subrequest_instruction) {
+ ERROR("%s: Out of memory", __FUNCTION__);
+ return -1;
+ }
+ *subrequest_instruction = (unlang_subrequest_t){
+ .group = {
+ .self = {
+ .type = UNLANG_TYPE_SUBREQUEST,
+ .name = "subrequest",
+ .debug_name = "subrequest",
+ .actions = {
+ [RLM_MODULE_REJECT] = 0,
+ [RLM_MODULE_FAIL] = 0,
+ [RLM_MODULE_OK] = 0,
+ [RLM_MODULE_HANDLED] = 0,
+ [RLM_MODULE_INVALID] = 0,
+ [RLM_MODULE_DISALLOW] = 0,
+ [RLM_MODULE_NOTFOUND] = 0,
+ [RLM_MODULE_NOOP] = 0,
+ [RLM_MODULE_UPDATED] = 0
+ }
+ }
+ }
+ };
+
+ return 0;
+}
+
+void unlang_subrequest_child_op_free(void)
+{
+ fr_dict_autofree(subrequest_dict);
+ talloc_free(subrequest_instruction);
+}
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/subrequest_child_priv.h
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+#include <freeradius-devel/server/request.h>
+#include "subrequest_priv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state);
+
+int unlang_subrequest_child_op_init(void);
+
+void unlang_subrequest_child_op_free(void);
+
+#ifdef __cplusplus
+}
+#endif
#include <freeradius-devel/server/tmpl.h>
#include <freeradius-devel/util/dict.h>
+#include "unlang_priv.h"
typedef struct {
unlang_group_t group;
///< if the packet-type is static.
} unlang_subrequest_t;
+/** Parameters for initialising the subrequest (parent's frame state)
+ *
+ */
+typedef struct {
+ rlm_rcode_t *p_result; //!< Where to store the result.
+ request_t *child; //!< Pre-allocated child request.
+ bool free_child; //!< Whether we should free the child after
+ ///< it completes.
+ bool detachable; //!< Whether the request can be detached.
+ unlang_subrequest_session_t session; //!< Session configuration.
+} unlang_frame_state_subrequest_t;
+
/** Cast a group structure to the subrequest keyword extension
*
*/
void unlang_subrequest_free(request_t **child);
-int unlang_subrequest_push(rlm_rcode_t *out, request_t *child,
+int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
unlang_subrequest_session_t const *session, bool top_frame)
CC_HINT(warn_unused_result);
-int unlang_detached_child_init(request_t *request);
+int unlang_subrequest_detach_child(request_t *request);
#ifdef __cplusplus
}
void unlang_subrequest_op_free(void);
+void unlang_detach_init(void);
+
void unlang_switch_init(void);
void unlang_tmpl_init(void);
eap_session = talloc_get_type_abort(rctx, eap_session_t);
- (void)fr_cond_assert(request_detach(eap_session->subrequest) == 0);
TALLOC_FREE(eap_session->subrequest);
/*
/*
* Cleanup the subrequest
*/
- (void)fr_cond_assert(request_detach(eap_session->subrequest) == 0);
TALLOC_FREE(eap_session->subrequest);
/*
}
/*
- * Push the submodule into the child's stack
+ * Push a resumption frame for the parent
+ * This will get executed when the child is
+ * done (after the subrequest frame in the
+ * parent gets popped).
*/
- if (unlang_module_push(NULL, /* rcode should bubble up and be returned by yield_to_subrequest */
- eap_session->subrequest, method->submodule_inst, eap_session->process, true) < 0) {
+ (void)unlang_module_yield(request, mod_authenticate_result_async, mod_authenticate_cancel, eap_session);
+
+ /*
+ * This sets up a subrequest frame in the parent
+ * and a resumption frame in the child.
+ *
+ * This must be done before pushing frames onto
+ * the child's stack.
+ */
+ if (unlang_subrequest_child_push(&eap_session->submodule_rcode, eap_session->subrequest,
+ &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = eap_session },
+ UNLANG_SUB_FRAME) < 0) {
+ child_fail:
+ unlang_interpet_frame_discard(request); /* Ensure the yield frame doesn't stick around */
goto fail;
}
+ /*
+ * Push the EAP submodule into the child's stack
+ */
+ if (unlang_module_push(NULL, /* rcode should bubble up and be set in eap_session->submodule_rcode */
+ eap_session->subrequest, method->submodule_inst, eap_session->process, true) < 0) {
+ goto child_fail;
+ }
+
if (eap_session->identity) {
fr_pair_t *identity;
type_vp->vp_uint32 = eap_session->type;
}
- /*
- * Yield to the subrequest, and start executing it
- */
- return unlang_module_yield_to_subrequest(&eap_session->submodule_rcode, eap_session->subrequest,
- mod_authenticate_result_async, mod_authenticate_cancel,
- &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = eap_session },
- eap_session);
+ return unlang_subrequest_child_run(eap_session->subrequest);
}
static unlang_action_t mod_authenticate(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)