#include <freeradius-devel/xlat.h>
#include <freeradius-devel/io/listen.h>
-/*
- * Some functions differ mainly in their parsing
- */
-#define unlang_redundant_load_balance unlang_load_balance
-#define unlang_policy unlang_group
-#define unlang_break unlang_return
-
static FR_NAME_NUMBER unlang_action_table[] = {
{ "calculate-result", UNLANG_ACTION_CALCULATE_RESULT },
{ "continue", UNLANG_ACTION_CONTINUE },
{ NULL, -1 }
};
-#define UNLANG_NEXT_STOP (false)
-#define UNLANG_NEXT_CONTINUE (true)
-
-#define UNLANG_TOP_FRAME (true)
-#define UNLANG_SUB_FRAME (false)
-
-#define UNLANG_DETACHABLE (true)
-#define UNLANG_NORMAL_CHILD (false)
-
-static void unlang_push_xlat(TALLOC_CTX *ctx, fr_value_box_t **out,
- REQUEST *request, xlat_exp_t const *exp, bool top_frame);
-
-static unlang_action_t unlang_resume(REQUEST *request, rlm_rcode_t *presult, int *priority);
-
-/*
- * Lock the mutex for the module
- */
-static inline void safe_lock(module_instance_t *instance)
-{
- if (instance->mutex) pthread_mutex_lock(instance->mutex);
-}
-
-/*
- * Unlock the mutex for the module
- */
-static inline void safe_unlock(module_instance_t *instance)
-{
- if (instance->mutex) pthread_mutex_unlock(instance->mutex);
-}
-
-/** Continue after creating a subrequest.
- *
- * Just run some "unlang", but don't do anything else.
- */
-static fr_io_final_t unlang_process_continue(REQUEST *request, fr_io_action_t action)
-{
- rlm_rcode_t rcode;
-
- REQUEST_VERIFY(request);
-
- /*
- * Pass this through asynchronously to the module which
- * is waiting for something to happen.
- */
- if (action != FR_IO_ACTION_RUN) {
- unlang_signal(request, (fr_state_action_t) action);
- return FR_IO_DONE;
- }
-
- rcode = unlang_interpret_continue(request);
-
- if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE;
-
- if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD;
-
- /*
- * Don't bother setting request->reply->code.
- */
- return FR_IO_DONE;
-}
-
#ifndef NDEBUG
-unlang_op_t unlang_ops[];
-
static void unlang_dump_instruction(REQUEST *request, unlang_t *instruction)
{
RINDENT();
#define DUMP_STACK
#endif
-
-/** Push a new frame onto the stack
- *
- * @param[in] stack to push the frame onto.
- * @param[in] program One or more unlang_t nodes describing the operations to execute.
- * @param[in] result The default result.
- * @param[in] do_next_sibling Whether to only execute the first node in the #unlang_t program
- * or to execute subsequent nodes.
- * @param[in] top_frame Return out of the unlang interpreter when popping this frame.
- * Hands execution back to whatever called the interpreter.
- */
-static inline void unlang_push(unlang_stack_t *stack, unlang_t *program,
- rlm_rcode_t result, bool do_next_sibling, bool top_frame)
-{
- unlang_stack_frame_t *frame;
-
- rad_assert(program || top_frame);
-
-#ifndef NDEBUG
- if (DEBUG_ENABLED5) DEBUG("unlang_push called with instruction %s - args %s %s",
- program ? program->debug_name : "<none>",
- do_next_sibling ? "UNLANG_NEXT_CONTINUE" : "UNLANG_NEXT_STOP",
- top_frame ? "UNLANG_TOP_FRAME" : "UNLANG_SUB_FRAME");
-#endif
-
- if (stack->depth >= (UNLANG_STACK_MAX - 1)) {
- ERROR("Internal sanity check failed: module stack is too deep");
- fr_exit(EXIT_FAILURE);
- }
-
- stack->depth++;
-
- /*
- * Initialize the next stack frame.
- */
- frame = &stack->frame[stack->depth];
-
- if (do_next_sibling) {
- rad_assert(program != NULL);
- frame->next = program->next;
- } else {
- frame->next = NULL;
- }
-
- frame->top_frame = top_frame;
- frame->instruction = program;
- frame->result = result;
- frame->priority = -1;
- frame->unwind = UNLANG_TYPE_NULL;
- frame->repeat = false;
- frame->state = NULL;
-}
-
-/** Pop a stack frame, removing any associated dynamically allocated state
- *
- * @param[in] stack frame to pop.
- */
-static inline void unlang_pop(unlang_stack_t *stack)
-{
- unlang_stack_frame_t *frame, *next;
-
- rad_assert(stack->depth > 1);
-
- frame = &stack->frame[stack->depth];
- if (frame->state) talloc_free(frame->state);
-
- frame = &stack->frame[--stack->depth];
- next = frame + 1;
-
- /*
- * Unwind back up the stack
- */
- if (next->unwind != 0) frame->unwind = next->unwind;
-}
-
-/*
- * Recursively collect active callers. Slow, but correct.
- */
-static uint64_t unlang_active_callers(unlang_t *instruction)
-{
- uint64_t active_callers;
- unlang_t *child;
- unlang_group_t *g;
-
- switch (instruction->type) {
- default:
- return 0;
-
- case UNLANG_TYPE_MODULE_CALL:
- {
- module_thread_instance_t *thread;
- unlang_module_call_t *sp;
-
- sp = unlang_generic_to_module_call(instruction);
- rad_assert(sp != NULL);
-
- thread = module_thread_instance_find(sp->module_instance);
- rad_assert(thread != NULL);
-
- return thread->active_callers;
- }
-
- case UNLANG_TYPE_GROUP:
- case UNLANG_TYPE_LOAD_BALANCE:
- case UNLANG_TYPE_REDUNDANT_LOAD_BALANCE:
- case UNLANG_TYPE_IF:
- case UNLANG_TYPE_ELSE:
- case UNLANG_TYPE_ELSIF:
- case UNLANG_TYPE_FOREACH:
- case UNLANG_TYPE_SWITCH:
- case UNLANG_TYPE_CASE:
- g = unlang_generic_to_group(instruction);
-
- active_callers = 0;
- for (child = g->children;
- child != NULL;
- child = child->next) {
- active_callers += unlang_active_callers(child);
- }
- break;
- }
-
- return active_callers;
-}
-
-static unlang_action_t unlang_load_balance(REQUEST *request,
- rlm_rcode_t *presult, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
-
- uint32_t count = 0;
-
- g = unlang_generic_to_group(instruction);
- rad_assert(g->children != NULL);
-
- /*
- * No frame? This is the first time we've been called.
- * Go find one.
- */
- if (!frame->repeat) {
- RDEBUG4("%s setting up", frame->instruction->debug_name);
-
- if (g->vpt) {
- uint32_t hash, start;
- ssize_t slen;
- char const *p = NULL;
- char buffer[1024];
-
- /*
- * Integer data types let the admin
- * select which frame is being used.
- */
- if ((g->vpt->type == TMPL_TYPE_ATTR) &&
- ((g->vpt->tmpl_da->type == FR_TYPE_UINT8) ||
- (g->vpt->tmpl_da->type == FR_TYPE_UINT16) ||
- (g->vpt->tmpl_da->type == FR_TYPE_UINT32) ||
- (g->vpt->tmpl_da->type == FR_TYPE_UINT64))) {
- VALUE_PAIR *vp;
-
- slen = tmpl_find_vp(&vp, request, g->vpt);
- if (slen < 0) {
- REDEBUG("Failed finding attribute %s", g->vpt->name);
- goto randomly_choose;
- }
-
- switch (g->vpt->tmpl_da->type) {
- case FR_TYPE_UINT8:
- start = ((uint32_t) vp->vp_uint8) % g->num_children;
- break;
-
- case FR_TYPE_UINT16:
- start = ((uint32_t) vp->vp_uint16) % g->num_children;
- break;
-
- case FR_TYPE_UINT32:
- start = vp->vp_uint32 % g->num_children;
- break;
-
- case FR_TYPE_UINT64:
- start = (uint32_t) (vp->vp_uint64 % ((uint64_t) g->num_children));
- break;
-
- default:
- goto randomly_choose;
- }
-
- } else {
- slen = tmpl_expand(&p, buffer, sizeof(buffer), request, g->vpt, NULL, NULL);
- if (slen < 0) {
- REDEBUG("Failed expanding template");
- goto randomly_choose;
- }
-
- hash = fr_hash(p, slen);
-
- start = hash % g->num_children;;
- }
-
- RDEBUG3("load-balance starting at child %d", (int) start);
-
- count = 0;
- for (frame->redundant.child = frame->redundant.found = g->children;
- frame->redundant.child != NULL;
- frame->redundant.child = frame->redundant.child->next) {
- count++;
- if (count == start) {
- frame->redundant.found = frame->redundant.child;
- break;
- }
- }
-
- } else {
- int num;
- uint64_t lowest_active_callers;
-
- randomly_choose:
- lowest_active_callers = ~(uint64_t ) 0;
-
- /*
- * Choose a child at random.
- */
- for (frame->redundant.child = frame->redundant.found = g->children, num = 0;
- frame->redundant.child != NULL;
- frame->redundant.child = frame->redundant.child->next, num++) {
- uint64_t active_callers;
- unlang_t *child = frame->redundant.child;
-
- if (child->type != UNLANG_TYPE_MODULE_CALL) {
- active_callers = unlang_active_callers(child);
- RDEBUG3("load-balance child %d sub-section has %" PRIu64 " active", num, active_callers);
-
- } else {
- module_thread_instance_t *thread;
- unlang_module_call_t *sp;
-
- sp = unlang_generic_to_module_call(child);
- rad_assert(sp != NULL);
-
- thread = module_thread_instance_find(sp->module_instance);
- rad_assert(thread != NULL);
-
- active_callers = thread->active_callers;
- RDEBUG3("load-balance child %d sub-module has %" PRIu64 " active", num, active_callers);
- }
-
-
- /*
- * Reset the found, and the count
- * of children with this level of
- * activity.
- */
- if (active_callers < lowest_active_callers) {
- RDEBUG3("load-balance choosing child %d as active %" PRIu64 " < %" PRIu64 "",
- num, active_callers, lowest_active_callers);
-
- count = 1;
- lowest_active_callers = active_callers;
- frame->redundant.found = frame->redundant.child;
- continue;
- }
-
- /*
- * Skip callers who are busier
- * than the one we found.
- */
- if (active_callers > lowest_active_callers) {
- RDEBUG3("load-balance skipping child %d, as active %" PRIu64 " > %" PRIu64 "",
- num, active_callers, lowest_active_callers);
- continue;
- }
-
- count++;
- RDEBUG3("load-balance found %d children with %" PRIu64 " active", count, active_callers);
-
- if ((count * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) {
- RDEBUG3("load-balance choosing random child %d", num);
- frame->redundant.found = frame->redundant.child;
- }
- }
- }
-
- if (instruction->type == UNLANG_TYPE_LOAD_BALANCE) {
- unlang_push(stack, frame->redundant.found, frame->result, UNLANG_NEXT_STOP, UNLANG_SUB_FRAME);
- return UNLANG_ACTION_PUSHED_CHILD;
- }
-
- /*
- * redundant-load-balance starts at this one.
- */
- frame->redundant.child = frame->redundant.found;
-
- } else {
- RDEBUG4("%s resuming", frame->instruction->debug_name);
-
- /*
- * We are in a resumed frame. The module we
- * chose failed, so we have to go through the
- * process again.
- */
-
- rad_assert(instruction->type != UNLANG_TYPE_LOAD_BALANCE); /* this is never called again */
-
- /*
- * We were called again. See if we're done.
- */
- if (frame->redundant.child->actions[*presult] == MOD_ACTION_RETURN) {
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * @todo - track the one we chose, and if it
- * fails, do the load-balancing again, except
- * this time skipping the failed module. AND,
- * keep track of multiple failed modules.
- * Probably in the unlang_resume_t, via a
- * uint64_t and bit mask for simplicity.
- */
-
- frame->redundant.child = frame->redundant.child->next;
- if (!frame->redundant.child) frame->redundant.child = g->children;
-
- if (frame->redundant.child == frame->redundant.found) {
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
- }
-
- /*
- * Push the child, and yield for a later return.
- */
- unlang_push(stack, frame->redundant.child, frame->result, UNLANG_NEXT_STOP, UNLANG_SUB_FRAME);
- frame->repeat = true;
-
- return UNLANG_ACTION_PUSHED_CHILD;
-}
-
-static unlang_action_t unlang_group(REQUEST *request,
- UNUSED rlm_rcode_t *result, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
-
- g = unlang_generic_to_group(instruction);
-
- /*
- * The compiler catches most of these, EXCEPT for the
- * top-level 'recv Access-Request' etc. Which can exist,
- * and can be empty.
- */
- if (!g->children) {
- RDEBUG2("} # %s ... <ignoring empty subsection>", instruction->debug_name);
- return UNLANG_ACTION_CONTINUE;
- }
-
- unlang_push(stack, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_SUB_FRAME);
- return UNLANG_ACTION_PUSHED_CHILD;
-}
-
-static rlm_rcode_t unlang_run(REQUEST *request);
-
-
/** Allocates and initializes an unlang_resume_t
*
* @param[in] request The current request.
return mr;
}
-
-/** Allocate a child request based on the parent.
- *
- */
-static REQUEST *unlang_child_alloc(REQUEST *request, unlang_t *instruction, rlm_rcode_t default_rcode, bool do_next_sibling, bool detachable)
-{
- REQUEST *child;
- unlang_stack_t *stack;
-
- if (!detachable) {
- child = request_alloc_fake(request);
- } else {
- child = request_alloc_detachable(request);
- }
- if (!child) return NULL;
-
- /*
- * Push the children, and set it's top frame to be true.
- */
- stack = child->stack;
- child->log.unlang_indent = request->log.unlang_indent;
- unlang_push(stack, instruction, default_rcode, do_next_sibling, UNLANG_SUB_FRAME);
- stack->frame[stack->depth].top_frame = true;
-
- /*
- * Initialize some basic information for the child.
- *
- * Note that we do NOT initialize child->backlog, as the
- * child is never resumable... the parent is resumable.
- */
- child->number = request->number;
- child->el = request->el;
- child->server_cs = request->server_cs;
-
- /*
- * Initialize all of the async fields.
- */
- child->async = talloc_zero(child, fr_async_t);
-
-#define COPY_FIELD(_x) child->async->_x = request->async->_x
- COPY_FIELD(original_recv_time);
- COPY_FIELD(recv_time);
- COPY_FIELD(listen);
-
- /*
- * Always set the "process" function to the local
- * bare-bones function which just runs on section of
- * "unlang", and doesn't send replies or anything else.
- */
- child->async->process = unlang_process_continue;
-
- /*
- * Note that we don't do time tracking on the child.
- * Instead, all of it is done in the context of the
- * parent.
- */
- FR_DLIST_INIT(child->async->time_order);
- FR_DLIST_INIT(child->async->tracking.list);
-
- /*
- * create {...} creates an empty copy.
- */
-
- return child;
-}
-
-
-/** Send a signal from parent request to subrequest
- *
- */
-static void unlang_subrequest_signal(UNUSED REQUEST *request, void *ctx, fr_state_action_t action)
-{
- REQUEST *child = talloc_get_type_abort(ctx, REQUEST);
-
- unlang_signal(child, action);
-}
-
-
-/** Resume a subrequest
+/** Push a new frame onto the stack
*
- */
-static unlang_action_t unlang_subrequest_resume(UNUSED REQUEST *request, rlm_rcode_t *presult, void *resume_ctx)
-{
- REQUEST *child = talloc_get_type_abort(resume_ctx, REQUEST);
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame;
-#ifndef NDEBUG
- unlang_resume_t *mr;
-#endif
-
- /*
- * Continue running the child.
- */
- *presult = unlang_run(child);
- if (*presult != RLM_MODULE_YIELD) {
- frame = &stack->frame[stack->depth];
- rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
-
- frame->instruction->type = UNLANG_TYPE_SUBREQUEST; /* for debug purposes */
- request_detach(child);
- talloc_free(child);
-
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
-#ifndef NDEBUG
- frame = &stack->frame[stack->depth];
- rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
-
- mr = unlang_generic_to_resume(frame->instruction);
- (void) talloc_get_type_abort(mr, unlang_resume_t);
-
- rad_assert(mr->callback == NULL);
- rad_assert(mr->resume_ctx == child);
-#endif
-
- /*
- * If the child yields, our current frame is still an
- * unlang_resume_t.
- */
-
- return UNLANG_ACTION_YIELD;
-}
-
-static unlang_action_t unlang_xlat_resume(REQUEST *request, UNUSED rlm_rcode_t *presult, UNUSED void *resume_ctx)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_resume_t *mr = unlang_generic_to_resume(instruction);
- unlang_stack_state_xlat_t *xs = talloc_get_type_abort(frame->state, unlang_stack_state_xlat_t);
- xlat_action_t xa;
-
- xa = ((xlat_resume_callback_t)mr->callback)(xs->ctx, &xs->values, request, NULL, NULL,
- &xs->result, mr->resume_ctx);
- switch (xa) {
- case XLAT_ACTION_YIELD:
- return UNLANG_ACTION_YIELD;
-
- case XLAT_ACTION_DONE:
- return UNLANG_ACTION_CALCULATE_RESULT;
-
- case XLAT_ACTION_PUSH_CHILD:
- rad_assert(0);
- /* FALL-THROUGH */
-
- case XLAT_ACTION_FAIL:
- *presult = RLM_MODULE_FAIL;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- rad_assert(0);
- return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-static void unlang_max_request_time(UNUSED fr_event_list_t *el, UNUSED struct timeval *now, void *uctx)
-{
- REQUEST *request = talloc_get_type_abort(uctx, REQUEST);
-
- RDEBUG("Reached Request-Lifetime. Forcibly stopping request");
-
- if (request->runnable_id >= 0) {
- rad_assert(request->backlog != NULL);
- (void) fr_heap_extract(request->backlog, request);
- }
-
- talloc_free(request);
-}
-
-
-static unlang_action_t unlang_detach(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- VALUE_PAIR *vp;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
-
- rad_assert(instruction->parent->type == UNLANG_TYPE_SUBREQUEST);
- RDEBUG2("%s", unlang_ops[instruction->type].name);
-
- if (request_detach(request) < 0) {
- ERROR("Failed detaching child");
- *presult = RLM_MODULE_FAIL;
- *priority = 0;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * Set Request Lifetime
- */
- vp = fr_pair_find_by_num(request->control, 0, FR_REQUEST_LIFETIME, TAG_ANY);
- if (!vp || (vp->vp_uint32 > 0)) {
- struct timeval when;
- const fr_event_timer_t **ev_p;
-
- gettimeofday(&when, NULL);
-
- if (!vp) {
- when.tv_sec += 30; /* default to 30s if not set */
-
- } else if (vp->vp_uint32 > 3600) {
- RWARN("Request-Timeout can be no more than 3600");
- when.tv_sec += 3600;
-
- } else if (vp->vp_uint32 < 5) {
- RWARN("Request-Timeout can be no less than 5");
- when.tv_sec += 5;
-
- } else {
- when.tv_sec += vp->vp_uint32;
- }
-
- ev_p = talloc_size(request, sizeof(*ev_p));
- memset(ev_p, 0, sizeof(*ev_p));
-
- (void) fr_event_timer_insert(request, request->el, ev_p,
- &when, unlang_max_request_time, request);
- }
-
- /*
- * request_detach() doesn't set the "detached" flag, but
- * it does set the backlog...
- */
- request->async->detached = true;
- rad_assert(request->backlog != NULL);
-
- *presult = RLM_MODULE_YIELD;
- return UNLANG_ACTION_YIELD;
-}
-
-static unlang_action_t unlang_call(REQUEST *request,
- UNUSED rlm_rcode_t *presult, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
- int indent;
- fr_io_final_t final;
- unlang_stack_t *current;
- CONF_SECTION *server_cs;
-
- g = unlang_generic_to_group(instruction);
- rad_assert(g->children != NULL);
-
- /*
- * @todo - allow for other process functions. Mostly
- * because we need to save and resume this function, and
- * we haven't bothered to do that so far.
- *
- * If we DO allow other functions, we need to replace
- * request->async->listener, as we want to pretend this
- * is a virtual request which didn't come in from the
- * network. i.e. the other virtual server shouldn't be
- * able to access request->async->listener, and muck with
- * it's statistics, see it's configuration, etc.
- */
- rad_assert(request->async->process == unlang_process_continue);
-
- /*
- * @todo - We probably want to just remove the 'stack'
- * parameter from the interpreter function arguments.
- * It's not needed there.
- */
- rad_assert(stack == request->stack);
-
- indent = request->log.unlang_indent;
- request->log.unlang_indent = 0; /* the process function expects this */
-
- current = request->stack;
- request->stack = talloc_zero(request, unlang_stack_t);
-
- server_cs = request->server_cs;
- request->server_cs = g->server_cs;
-
- request->async->process = g->process;
-
- RDEBUG("server %s {", cf_section_name2(g->server_cs));
-
- /*
- * @todo - we can't change protocols (e.g. RADIUS ->
- * DHCP) unless we're in a subrequest.
- *
- * @todo - we can't change packet types
- * (e.g. Access-Request -> Accounting-Request) unless
- * we're in a subrequest.
- */
- final = request->async->process(request, FR_IO_ACTION_RUN);
-
- RDEBUG("} # server %s", cf_section_name2(g->server_cs));
-
- /*
- * All other return codes are semantically equivalent for
- * our purposes. "DONE" means "stopped without reply",
- * and REPLY means "finished successfully". Neither of
- * those map well into module rcodes. Instead, we rely
- * on the caller to look at request->reply->code.
- */
- if (final == FR_IO_YIELD) {
- RDEBUG("Noo yield for you!");
- }
-
- /*
- * @todo - save these in a resume state somewhere...
- */
- request->log.unlang_indent = indent;
- request->async->process = unlang_process_continue;
- talloc_free(request->stack);
- request->stack = current;
- request->server_cs = server_cs;
-
- RDEBUG("Continuing with contents of %s { ...", instruction->debug_name);
-
- /*
- * And then call the children to process the answer.
- */
- unlang_push(stack, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_SUB_FRAME);
- return UNLANG_ACTION_PUSHED_CHILD;
-}
-
-/** Stub function for calling the xlat interpreter
- *
- * Calls the xlat interpreter and translates its wants and needs into
- * unlang_action_t codes.
- */
-static unlang_action_t unlang_xlat(REQUEST *request,
- rlm_rcode_t *presult, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_stack_state_xlat_t *xs = talloc_get_type_abort(frame->state, unlang_stack_state_xlat_t);
- xlat_exp_t const *child = NULL;
- xlat_action_t xa;
-
- if (frame->repeat) {
- fr_cursor_init(&xs->result, &xs->rhead);
- xa = xlat_frame_eval_repeat(xs->ctx, &xs->values,
- &child, &xs->alternate,
- request, &xs->exp,
- &xs->result);
- } else {
- xa = xlat_frame_eval(xs->ctx, &xs->values, &child, request, &xs->exp);
- }
-
- switch (xa) {
- case XLAT_ACTION_PUSH_CHILD:
- rad_assert(child);
-
- frame->repeat = true;
- unlang_push_xlat(xs->ctx, &xs->rhead, request, child, false);
- return UNLANG_ACTION_PUSHED_CHILD;
-
- case XLAT_ACTION_YIELD:
- return UNLANG_ACTION_YIELD;
-
- case XLAT_ACTION_DONE:
- return UNLANG_ACTION_CALCULATE_RESULT;
-
- case XLAT_ACTION_FAIL:
- *presult = RLM_MODULE_FAIL;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- rad_assert(0);
- return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-static unlang_action_t unlang_subrequest(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
- REQUEST *child;
- rlm_rcode_t rcode;
- unlang_resume_t *mr;
-
- g = unlang_generic_to_group(instruction);
- rad_assert(g->children != NULL);
-
- /*
- * Allocate the child request.
- */
- child = unlang_child_alloc(request, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_DETACHABLE);
- if (!child) {
- *presult = RLM_MODULE_FAIL;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- 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.
- *
- * 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.
- */
- rcode = unlang_run(child);
- if (rcode != RLM_MODULE_YIELD) {
- request_detach(child);
- talloc_free(child);
- *presult = rcode;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * As a special case, if the child instruction is
- * "detach", detach the child, insert the child into the
- * runnable queue, and keep going with the parent.
- *
- * The unlang_detach() interpreter function takes care of
- * calling request_detach() for the child.
- */
- {
- unlang_stack_t *child_stack = child->stack;
- unlang_stack_frame_t *child_frame = &child_stack->frame[child_stack->depth];
- unlang_t *child_instruction = child_frame->instruction;
-
- if (child_instruction->type == UNLANG_TYPE_DETACH) {
- rad_assert(child->backlog != NULL);
- fr_heap_insert(child->backlog, child);
-
- RDEBUG2("- detaching child request (%s)", child->name);
-
- /*
- * Tell the interpreter to skip the "detach"
- * stack frame when it continues.
- */
- child_frame->instruction = child_frame->next;
- if (child_frame->instruction) child_frame->next = child_frame->instruction->next;
-
- *presult = RLM_MODULE_NOOP;
- *priority = 0;
- return UNLANG_ACTION_CALCULATE_RESULT;
- } /* else the child yielded, so we have to yield */
- }
-
- /*
- * Create the "resume" stack frame, and have it replace our stack frame.
- */
- mr = unlang_resume_alloc(request, NULL, NULL, child);
- if (!mr) {
- *presult = RLM_MODULE_FAIL;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- *presult = RLM_MODULE_YIELD;
- return UNLANG_ACTION_YIELD;
-}
-
-/** Parallel children have states
- *
- */
-typedef enum unlang_parallel_child_state_t {
- CHILD_INIT = 0, //!< needs initialization
- CHILD_RUNNABLE,
- CHILD_YIELDED,
- CHILD_DONE
-} unlang_parallel_child_state_t;
-
-/** Each parallel child has a state, and an associated request
- *
- */
-typedef struct unlang_parallel_child_t {
- unlang_parallel_child_state_t state; //!< state of the child
- REQUEST *child; //!< child request
- unlang_t *instruction; //!< broken out of g->children
-} unlang_parallel_child_t;
-
-typedef struct unlang_parallel_t {
- rlm_rcode_t result;
- int priority;
-
- int num_children;
-
- unlang_group_t *g;
-
- unlang_parallel_child_t children[];
-} unlang_parallel_t;
-
-
-/** Run one or more sub-sections from the parallel section.
- *
- */
-static rlm_rcode_t unlang_parallel_run(REQUEST *request, unlang_parallel_t *state)
-{
- int i, priority;
- rlm_rcode_t result;
- unlang_parallel_child_state_t done = CHILD_DONE; /* hope that we're done */
-
- // @todo - rdebug running the request.
-
- /*
- * Loop over all the children.
- *
- * We always service the parallel section from top to
- * bottom, and we always service all of it.
- */
- for (i = 0; i < state->num_children; i++) {
- switch (state->children[i].state) {
- /*
- * Not ready to run.
- */
- case CHILD_YIELDED:
- RDEBUG3("parallel child %d is already YIELDED", i + 1);
- rad_assert(state->children[i].child != NULL);
- rad_assert(state->children[i].instruction != NULL);
- done = CHILD_YIELDED;
- continue;
-
- /*
- * Don't need to call this any more.
- */
- case CHILD_DONE:
- RDEBUG3("parallel child %d is already DONE", i + 1);
- rad_assert(state->children[i].child == NULL);
- rad_assert(state->children[i].instruction == NULL);
- continue;
-
- /*
- * Create the child and then run it.
- */
- case CHILD_INIT:
- RDEBUG3("parallel child %d is INIT", i + 1);
- rad_assert(state->children[i].instruction != NULL);
- state->children[i].child = unlang_child_alloc(request, state->children[i].instruction,
- RLM_MODULE_FAIL, /* @todo - fixme ? */
- UNLANG_NEXT_STOP, UNLANG_NORMAL_CHILD);
- state->children[i].state = CHILD_RUNNABLE;
- state->children[i].child->packet->code = request->packet->code;
-
- if (state->g->clone) {
- state->children[i].child->packet->vps = fr_pair_list_copy(state->children[i].child->packet,
- request->packet->vps);
- state->children[i].child->reply->vps = fr_pair_list_copy(state->children[i].child->reply,
- request->reply->vps);
- state->children[i].child->control = fr_pair_list_copy(state->children[i].child,
- request->control);
- }
-
- /* FALL-THROUGH */
-
- /*
- * Run this entry.
- */
- case CHILD_RUNNABLE:
- RDEBUG("parallel - running entry %d/%d", i + 1, state->num_children);
- result = unlang_run(state->children[i].child);
- if (result == RLM_MODULE_YIELD) {
- state->children[i].state = CHILD_YIELDED;
- done = CHILD_YIELDED;
- continue;
- }
-
- RDEBUG3("parallel child %d returns %s", i + 1,
- fr_int2str(mod_rcode_table, result, "<invalid>"));
-
- /*
- * Remember this before we delete the
- * reference to 'instruction'.
- */
- priority = state->children[i].instruction->actions[result];
-
- /*
- * Clean up the state entry.
- */
- state->children[i].state = CHILD_DONE;
- TALLOC_FREE(state->children[i].child);
- state->children[i].instruction = NULL;
-
- /*
- * return is "stop processing the
- * parallel section".
- */
- if (priority == MOD_ACTION_RETURN) {
- RDEBUG("child %d/%d says 'return' - skipping the remaining children",
- i + 1, state->num_children);
-
- /*
- * Fall through to processing the
- * priorities and return codes.
- */
- i = state->num_children;
- priority = 0;
- done = CHILD_DONE;
- }
-
- /*
- * Reject is just reject.
- */
- if (priority == MOD_ACTION_REJECT) {
- priority = 0;
- result = RLM_MODULE_REJECT;
- }
-
- /*
- * Do priority over-ride.
- */
- if (priority > state->priority) {
- unlang_stack_t *stack = request->stack;
-
- state->result = result;
- state->priority = priority;
-
- RDEBUG4("** [%i] %s - over-riding result from higher priority to (%s %d)",
- stack->depth, __FUNCTION__,
- fr_int2str(mod_rcode_table, result, "<invalid>"),
- priority);
- }
-
- /*
- * Another child has yielded, so we
- * remember the yield instead of the fact
- * that we're done.
- */
- if (done == CHILD_YIELDED) continue;
-
- rad_assert(done == CHILD_DONE);
- break;
- }
- }
-
- /*
- * Yield if necessary.
- */
- if (done == CHILD_YIELDED) {
- return RLM_MODULE_YIELD;
- }
-
- rad_assert(done == CHILD_DONE);
-
- /*
- * Clean up all of the child requests, because once we
- * return, no one can access their data any more.
- */
- for (i = 0; i < state->num_children; i++) {
- switch (state->children[i].state) {
- case CHILD_RUNNABLE:
- rad_assert(state->children[i].child->backlog == NULL);
- rad_assert(state->children[i].child->runnable_id < 0);
-
- /*
- * Un-detached children are never in the
- * runnable queue.
- */
- /* FALL-THROUGH */
-
- case CHILD_YIELDED:
- REQUEST_VERIFY(state->children[i].child);
- rad_assert(state->children[i].child->runnable_id < 0);
-
- /*
- * Signal the child that it's going to be
- * stopped. This tells any child modules
- * to clean up timers, etc.
- */
- unlang_signal(state->children[i].child, FR_ACTION_DONE);
- TALLOC_FREE(state->children[i].child);
- /* FALL-THROUGH */
-
- default:
- state->children[i].state = CHILD_DONE;
- state->children[i].child = NULL;
- state->children[i].instruction = NULL;
- break;
- }
- }
-
- /*
- * Return the final result. The caller will take care of
- * free'ing "state".
- */
- return state->result;
-}
-
-
-/** Send a signal from parent request to all of it's children
- *
- */
-static void unlang_parallel_signal(UNUSED REQUEST *request, void *resume_ctx, fr_state_action_t action)
-{
- int i;
- unlang_parallel_t *state = talloc_get_type_abort(resume_ctx, unlang_parallel_t);
-
- /*
- * Signal all of the children, if they exist.
- */
- for (i = 0; i < state->num_children; i++) {
- switch (state->children[i].state) {
- case CHILD_INIT:
- case CHILD_DONE:
- break;
-
- case CHILD_RUNNABLE:
- case CHILD_YIELDED:
- rad_assert(state->children[i].child != NULL);
- unlang_signal(state->children[i].child, action);
- break;
- }
- }
-}
-
-static unlang_action_t unlang_parallel_resume(REQUEST *request, rlm_rcode_t *presult, void *resume_ctx)
-{
- unlang_parallel_t *state = talloc_get_type_abort(resume_ctx, unlang_parallel_t);
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
-
-#ifndef NDEBUG
- unlang_resume_t *mr;
-#endif
-
- /*
- * Continue running the child.
- */
- *presult = unlang_parallel_run(request, state);
- if (*presult != RLM_MODULE_YIELD) {
- rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
-
- frame->instruction->type = UNLANG_TYPE_PARALLEL; /* for debug purposes */
- talloc_free(state);
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
-#ifndef NDEBUG
- rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
-
- mr = unlang_generic_to_resume(frame->instruction);
- (void) talloc_get_type_abort(mr, unlang_resume_t);
-
- rad_assert(mr->callback == NULL);
- rad_assert(mr->resume_ctx == state);
-#endif
-
- /*
- * If the child yields, our current frame is still an
- * unlang_resume_t.
- */
- return UNLANG_ACTION_YIELD;
-}
-
-static unlang_action_t unlang_parallel(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- int i;
- rlm_rcode_t rcode;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
- unlang_parallel_t *state;
- unlang_resume_t *mr;
-
- g = unlang_generic_to_group(instruction);
-
- if (!g->num_children) {
- *presult = RLM_MODULE_NOOP;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * Allocate an array for the children.
- */
- state = talloc_zero_size(request, sizeof(unlang_parallel_t) + sizeof(state->children[0]) * g->num_children);
- if (!state) {
- *presult = RLM_MODULE_FAIL;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- };
-
- (void) talloc_set_type(state, unlang_parallel_t);
- state->result = RLM_MODULE_FAIL;
- state->priority = -1; /* as-yet unset */
- state->g = g;
- state->num_children = g->num_children;
-
- /*
- * Initialize all of the children.
- */
- for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
- state->children[i].state = CHILD_INIT;
- state->children[i].instruction = instruction;
- }
-
- /*
- * Reset this...
- */
- instruction = frame->instruction;
-
- /*
- * Run the various children. On the off chance they're
- * all done, free things, and return.
- */
- rcode = unlang_parallel_run(request, state);
- if (rcode != RLM_MODULE_YIELD) {
- talloc_free(state);
- *presult = rcode;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * Create the "resume" stack frame, and have it replace our stack frame.
- */
- mr = unlang_resume_alloc(request, NULL, NULL, state);
- if (!mr) {
- *presult = RLM_MODULE_FAIL;
- *priority = instruction->actions[*presult];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- *presult = RLM_MODULE_YIELD;
- return UNLANG_ACTION_YIELD;
-}
-
-static unlang_action_t unlang_case(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
-
- g = unlang_generic_to_group(instruction);
-
- if (!g->children) {
- *presult = RLM_MODULE_NOOP;
- *priority = 0;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- return unlang_group(request, presult, priority);
-}
-
-static unlang_action_t unlang_return(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- int i;
- VALUE_PAIR **copy_p;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
-
- RDEBUG2("%s", unlang_ops[instruction->type].name);
-
- for (i = 8; i >= 0; i--) {
- copy_p = request_data_get(request, (void *)radius_get_vp, i);
- if (copy_p) {
- if (instruction->type == UNLANG_TYPE_BREAK) {
- RDEBUG2("# break Foreach-Variable-%d", i);
- break;
- }
- }
- }
-
- frame->unwind = instruction->type;
-
- *presult = frame->result;
- *priority = frame->priority;
-
- return UNLANG_ACTION_BREAK;
-}
-
-static unlang_action_t unlang_foreach(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- VALUE_PAIR *vp;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
-
- g = unlang_generic_to_group(instruction);
-
- if (!frame->repeat) {
- int i, foreach_depth = -1;
- VALUE_PAIR *vps;
-
- if (stack->depth >= UNLANG_STACK_MAX) {
- ERROR("Internal sanity check failed: module stack is too deep");
- fr_exit(EXIT_FAILURE);
- }
-
- /*
- * Figure out how deep we are in nesting by looking at request_data
- * stored previously.
- *
- * FIXME: figure this out by walking up the modcall stack instead.
- */
- for (i = 0; i < 8; i++) {
- if (!request_data_reference(request, (void *)radius_get_vp, i)) {
- foreach_depth = i;
- break;
- }
- }
-
- if (foreach_depth < 0) {
- REDEBUG("foreach Nesting too deep!");
- *presult = RLM_MODULE_FAIL;
- *priority = 0;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- /*
- * Copy the VPs from the original request, this ensures deterministic
- * behaviour if someone decides to add or remove VPs in the set were
- * iterating over.
- */
- if (tmpl_copy_vps(request, &vps, request, g->vpt) < 0) { /* nothing to loop over */
- *presult = RLM_MODULE_NOOP;
- *priority = instruction->actions[RLM_MODULE_NOOP];
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- rad_assert(vps != NULL);
- fr_pair_cursor_init(&frame->foreach.cursor, &vps);
-
- frame->foreach.depth = foreach_depth;
- frame->foreach.vps = vps;
-#ifndef NDEBUG
- frame->foreach.indent = request->log.unlang_indent;
-#endif
-
- vp = fr_pair_cursor_first(&frame->foreach.cursor);
-
- } else {
- vp = fr_pair_cursor_next(&frame->foreach.cursor);
-
- /*
- * We've been asked to unwind to the
- * enclosing "foreach". We're here, so
- * we can stop unwinding.
- */
- if (frame->unwind == UNLANG_TYPE_BREAK) {
- frame->unwind = UNLANG_TYPE_NULL;
- vp = NULL;
- }
-
- /*
- * Unwind all the way.
- */
- if (frame->unwind == UNLANG_TYPE_RETURN) {
- vp = NULL;
- }
-
- if (!vp) {
- /*
- * Free the copied vps and the request data
- * If we don't remove the request data, something could call
- * the xlat outside of a foreach loop and trigger a segv.
- */
- fr_pair_list_free(&frame->foreach.vps);
- request_data_get(request, (void *)radius_get_vp, frame->foreach.depth);
-
- *presult = frame->result;
- if (*presult != RLM_MODULE_UNKNOWN) *priority = instruction->actions[*presult];
-#ifndef NDEBUG
- rad_assert(frame->foreach.indent == request->log.unlang_indent);
-#endif
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
- }
-
-#ifndef NDEBUG
- if (DEBUG_ENABLED2) {
- char buffer[1024];
-
- fr_pair_value_snprint(buffer, sizeof(buffer), vp, '"');
- RDEBUG2("");
- RDEBUG2("# looping with: Foreach-Variable-%d = %s", frame->foreach.depth, buffer);
- }
-#endif
-
- /*
- * Add the vp to the request, so that
- * xlat.c, xlat_foreach() can find it.
- */
- frame->foreach.variable = vp;
- request_data_add(request, (void *)radius_get_vp, frame->foreach.depth, &frame->foreach.variable,
- false, false, false);
-
- /*
- * Push the child, and yield for a later return.
- */
- unlang_push(stack, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_SUB_FRAME);
- frame->repeat = true;
- return UNLANG_ACTION_PUSHED_CHILD;
-}
-
-static unlang_action_t unlang_xlat_inline(REQUEST *request,
- UNUSED rlm_rcode_t *presult, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_xlat_inline_t *mx = unlang_generic_to_xlat_inline(instruction);
-
- if (!mx->exec) {
- TALLOC_CTX *pool;
- unlang_stack_state_xlat_inline_t *state;
-
- MEM(frame->state = state = talloc_zero(stack, unlang_stack_state_xlat_inline_t));
- MEM(pool = talloc_pool(frame->state, 1024)); /* Pool to absorb some allocs */
-
- unlang_push_xlat(pool, &state->result, request, mx->exp, false);
- return UNLANG_ACTION_PUSHED_CHILD;
- } else {
- RDEBUG("`%s`", mx->xlat_name);
- radius_exec_program(request, NULL, 0, NULL, request, mx->xlat_name, request->packet->vps,
- false, true, EXEC_TIMEOUT);
- return UNLANG_ACTION_CONTINUE;
- }
-}
-
-static unlang_action_t unlang_switch(REQUEST *request,
- UNUSED rlm_rcode_t *presult, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_t *this, *found, *null_case;
- unlang_group_t *g, *h;
- fr_cond_t cond;
- fr_value_box_t data;
- vp_map_t map;
- vp_tmpl_t vpt;
-
- g = unlang_generic_to_group(instruction);
-
- memset(&cond, 0, sizeof(cond));
- memset(&map, 0, sizeof(map));
-
- cond.type = COND_TYPE_MAP;
- cond.data.map = ↦
-
- map.op = T_OP_CMP_EQ;
- map.ci = cf_section_to_item(g->cs);
-
- rad_assert(g->vpt != NULL);
-
- null_case = found = NULL;
- data.datum.ptr = NULL;
-
- /*
- * The attribute doesn't exist. We can skip
- * directly to the default 'case' statement.
- */
- if ((g->vpt->type == TMPL_TYPE_ATTR) && (tmpl_find_vp(NULL, request, g->vpt) < 0)) {
- find_null_case:
- for (this = g->children; this; this = this->next) {
- rad_assert(this->type == UNLANG_TYPE_CASE);
-
- h = unlang_generic_to_group(this);
- if (h->vpt) continue;
-
- found = this;
- break;
- }
-
- goto do_null_case;
- }
-
- /*
- * Expand the template if necessary, so that it
- * is evaluated once instead of for each 'case'
- * statement.
- */
- if ((g->vpt->type == TMPL_TYPE_XLAT_STRUCT) ||
- (g->vpt->type == TMPL_TYPE_XLAT) ||
- (g->vpt->type == TMPL_TYPE_EXEC)) {
- char *p;
- ssize_t len;
-
- len = tmpl_aexpand(request, &p, request, g->vpt, NULL, NULL);
- if (len < 0) goto find_null_case;
- data.vb_strvalue = p;
- tmpl_init(&vpt, TMPL_TYPE_UNPARSED, data.vb_strvalue, len, T_SINGLE_QUOTED_STRING);
- }
-
- /*
- * Find either the exact matching name, or the
- * "case {...}" statement.
- */
- for (this = g->children; this; this = this->next) {
- rad_assert(this->type == UNLANG_TYPE_CASE);
-
- h = unlang_generic_to_group(this);
-
- /*
- * Remember the default case
- */
- if (!h->vpt) {
- if (!null_case) null_case = this;
- continue;
- }
-
- /*
- * If we're switching over an attribute
- * AND we haven't pre-parsed the data for
- * the case statement, then cast the data
- * to the type of the attribute.
- */
- if ((g->vpt->type == TMPL_TYPE_ATTR) &&
- (h->vpt->type != TMPL_TYPE_DATA)) {
- map.rhs = g->vpt;
- map.lhs = h->vpt;
- cond.cast = g->vpt->tmpl_da;
-
- /*
- * Remove unnecessary casting.
- */
- if ((h->vpt->type == TMPL_TYPE_ATTR) &&
- (g->vpt->tmpl_da->type == h->vpt->tmpl_da->type)) {
- cond.cast = NULL;
- }
-
- /*
- * Use the pre-expanded string.
- */
- } else if ((g->vpt->type == TMPL_TYPE_XLAT_STRUCT) ||
- (g->vpt->type == TMPL_TYPE_XLAT) ||
- (g->vpt->type == TMPL_TYPE_EXEC)) {
- map.rhs = h->vpt;
- map.lhs = &vpt;
- cond.cast = NULL;
-
- /*
- * Else evaluate the 'switch' statement.
- */
- } else {
- map.rhs = h->vpt;
- map.lhs = g->vpt;
- cond.cast = NULL;
- }
-
- if (cond_eval_map(request, RLM_MODULE_UNKNOWN, 0,
- &cond) == 1) {
- found = this;
- break;
- }
- }
-
- if (!found) found = null_case;
-
-do_null_case:
- talloc_free(data.datum.ptr);
-
- /*
- * Nothing found. Just continue, and ignore the "switch"
- * statement.
- */
- if (!found) return UNLANG_ACTION_CONTINUE;
-
- unlang_push(stack, found, frame->result, UNLANG_NEXT_STOP, UNLANG_SUB_FRAME);
- return UNLANG_ACTION_PUSHED_CHILD;
-}
-
-
-static unlang_action_t unlang_update(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- int rcode;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g = unlang_generic_to_group(instruction);
- vp_map_t *map;
-
- for (map = g->map; map != NULL; map = map->next) {
- rcode = map_to_request(request, map, map_to_vp, NULL);
- if (rcode < 0) {
- *presult = (rcode == -2) ? RLM_MODULE_INVALID : RLM_MODULE_FAIL;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
- }
-
- *presult = RLM_MODULE_NOOP;
- *priority = instruction->actions[RLM_MODULE_NOOP];
- return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-
-static unlang_action_t unlang_map(REQUEST *request,
- rlm_rcode_t *presult, UNUSED int *priority)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g = unlang_generic_to_group(instruction);
-
- *presult = map_proc(request, g->proc_inst);
-
- return *presult == RLM_MODULE_YIELD ? UNLANG_ACTION_YIELD : UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-
-static unlang_action_t unlang_module_call(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
-{
- unlang_module_call_t *sp;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_stack_state_modcall_t *ms;
-
-#ifndef NDEBUG
- int unlang_indent = request->log.unlang_indent;
-#endif
-
- /*
- * Process a stand-alone child, and fall through
- * to dealing with it's parent.
- */
- sp = unlang_generic_to_module_call(instruction);
- rad_assert(sp);
-
- RDEBUG4("[%i] %s - %s (%s)", stack->depth, __FUNCTION__,
- sp->module_instance->name, sp->module_instance->module->name);
-
- /*
- * Return administratively configured return code
- */
- if (sp->module_instance->force) {
- *presult = request->rcode = sp->module_instance->code;
- goto done;
- }
-
- frame->state = ms = talloc_zero(stack, unlang_stack_state_modcall_t);
-
- /*
- * Grab the thread/module specific data if any exists.
- */
- ms->thread = module_thread_instance_find(sp->module_instance);
- rad_assert(ms->thread != NULL);
-
- /*
- * For logging unresponsive children.
- */
- request->module = sp->module_instance->name;
- ms->thread->total_calls++;
-
- /*
- * Lock is noop unless instance->mutex is set.
- */
- safe_lock(sp->module_instance);
- *presult = sp->method(sp->module_instance->dl_inst->data, ms->thread->data, request);
- safe_unlock(sp->module_instance);
-
- request->module = NULL;
-
- /*
- * Is now marked as "stop" when it wasn't before, we must have been blocked.
- */
- if (request->master_state == REQUEST_STOP_PROCESSING) {
- RWARN("Module %s became unblocked",
- sp->module_instance->module->name);
- return UNLANG_ACTION_STOP_PROCESSING;
- }
-
- if (*presult == RLM_MODULE_YIELD) {
- ms->thread->active_callers++;
- } else {
- rad_assert(unlang_indent == request->log.unlang_indent);
-
- rad_assert(*presult >= RLM_MODULE_REJECT);
- rad_assert(*presult < RLM_MODULE_NUMCODES);
- *priority = instruction->actions[*presult];
-
- request->rcode = *presult;
- }
-
-done:
- RDEBUG2("%s (%s)", instruction->name ? instruction->name : "",
- fr_int2str(mod_rcode_table, *presult, "<invalid>"));
-
- return *presult == RLM_MODULE_YIELD ? UNLANG_ACTION_YIELD : UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-/** Send a signal (usually stop) to a request
- *
- * This is typically called via an "async" action, i.e. an action
- * outside of the normal processing of the request.
- *
- * If there is no #fr_unlang_module_signal_t callback defined, the action is ignored.
- *
- * @param[in] request The current request.
- * @param[in] resume_ctx createed by #unlang_module_call.
- * @param[in] action to signal.
- */
-static void unlang_module_signal(REQUEST *request, void *resume_ctx, fr_state_action_t action)
-{
- unlang_stack_frame_t *frame;
- unlang_stack_t *stack = request->stack;
- unlang_resume_t *mr;
- unlang_module_call_t *mc;
-
- unlang_stack_state_modcall_t *ms = NULL;
-
- rad_assert(stack->depth > 0);
-
- frame = &stack->frame[stack->depth];
-
- mr = unlang_generic_to_resume(frame->instruction);
- if (!mr->signal) return;
-
- mc = unlang_generic_to_module_call(mr->parent);
- ms = talloc_get_type_abort(frame->state, unlang_stack_state_modcall_t);
-
- ((fr_unlang_module_signal_t)mr->signal)(request,
- mc->module_instance->dl_inst->data, ms->thread->data,
- resume_ctx, action);
-}
-
-static unlang_action_t unlang_module_resume(REQUEST *request, rlm_rcode_t *presult, UNUSED void *resume_ctx)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_resume_t *mr = unlang_generic_to_resume(instruction);
- unlang_module_call_t *mc = unlang_generic_to_module_call(mr->parent);
-
- unlang_stack_state_modcall_t *ms = NULL;
-
- rad_assert(mr->parent->type == UNLANG_TYPE_MODULE_CALL);
-
- ms = talloc_get_type_abort(frame->state, unlang_stack_state_modcall_t);
-
- /*
- * Lock is noop unless instance->mutex is set.
- */
- safe_lock(mc->module_instance);
- *presult = request->rcode = ((fr_unlang_module_resume_t)mr->callback)(request,
- mc->module_instance->dl_inst->data,
- ms->thread->data, mr->resume_ctx);
- safe_unlock(mc->module_instance);
-
- if (*presult != RLM_MODULE_YIELD) ms->thread->active_callers--;
-
- RDEBUG2("%s (%s)", instruction->name ? instruction->name : "",
- fr_int2str(mod_rcode_table, *presult, "<invalid>"));
-
- return *presult == RLM_MODULE_YIELD ? UNLANG_ACTION_YIELD : UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-static unlang_action_t unlang_if(REQUEST *request,
- rlm_rcode_t *presult, int *priority)
+ * @param[in] stack to push the frame onto.
+ * @param[in] program One or more unlang_t nodes describing the operations to execute.
+ * @param[in] result The default result.
+ * @param[in] do_next_sibling Whether to only execute the first node in the #unlang_t program
+ * or to execute subsequent nodes.
+ * @param[in] top_frame Return out of the unlang interpreter when popping this frame.
+ * Hands execution back to whatever called the interpreter.
+ */
+void unlang_push(unlang_stack_t *stack, unlang_t *program, rlm_rcode_t result, bool do_next_sibling, bool top_frame)
{
- int condition;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_group_t *g;
-
- g = unlang_generic_to_group(instruction);
- rad_assert(g->cond != NULL);
-
- condition = cond_eval(request, *presult, 0, g->cond);
- if (condition < 0) {
- switch (condition) {
- case -2:
- REDEBUG("Condition evaluation failed because a referenced attribute "
- "was not found in the request");
- break;
- default:
- case -1:
- REDEBUG("Condition evaluation failed because the value of an operand "
- "could not be determined");
- break;
- }
- condition = 0;
- }
+ unlang_stack_frame_t *frame;
- /*
- * Didn't pass. Remember that.
- */
- if (!condition) {
- RDEBUG2("...");
+ rad_assert(program || top_frame);
- if (*presult != RLM_MODULE_UNKNOWN) *priority = instruction->actions[*presult];
+#ifndef NDEBUG
+ if (DEBUG_ENABLED5) DEBUG("unlang_push called with instruction %s - args %s %s",
+ program ? program->debug_name : "<none>",
+ do_next_sibling ? "UNLANG_NEXT_CONTINUE" : "UNLANG_NEXT_STOP",
+ top_frame ? "UNLANG_TOP_FRAME" : "UNLANG_SUB_FRAME");
+#endif
- return UNLANG_ACTION_CONTINUE;
+ if (stack->depth >= (UNLANG_STACK_MAX - 1)) {
+ ERROR("Internal sanity check failed: module stack is too deep");
+ fr_exit(EXIT_FAILURE);
}
+ stack->depth++;
+
/*
- * Tell the main interpreter to skip over the else /
- * elsif blocks, as this "if" condition was taken.
+ * Initialize the next stack frame.
*/
- while (frame->next &&
- ((frame->next->type == UNLANG_TYPE_ELSE) ||
- (frame->next->type == UNLANG_TYPE_ELSIF))) {
- frame->next = frame->next->next;
+ frame = &stack->frame[stack->depth];
+
+ if (do_next_sibling) {
+ rad_assert(program != NULL);
+ frame->next = program->next;
+ } else {
+ frame->next = NULL;
}
- /*
- * We took the "if". Go recurse into its' children.
- */
- return unlang_group(request, presult, priority);
+ frame->top_frame = top_frame;
+ frame->instruction = program;
+ frame->result = result;
+ frame->priority = -1;
+ frame->unwind = UNLANG_TYPE_NULL;
+ frame->repeat = false;
+ frame->state = NULL;
}
-unlang_op_t unlang_ops[] = {
- [UNLANG_TYPE_MODULE_CALL] = {
- .name = "module-call",
- .func = unlang_module_call,
- .signal = unlang_module_signal,
- .resume = unlang_module_resume,
- .debug_braces = false
- },
- [UNLANG_TYPE_GROUP] = {
- .name = "group",
- .func = unlang_group,
- .debug_braces = true
- },
- [UNLANG_TYPE_LOAD_BALANCE] = {
- .name = "load-balance group",
- .func = unlang_load_balance,
- .debug_braces = true
- },
- [UNLANG_TYPE_REDUNDANT_LOAD_BALANCE] = {
- .name = "redundant-load-balance group",
- .func = unlang_redundant_load_balance,
- .debug_braces = true
- },
- [UNLANG_TYPE_PARALLEL] = {
- .name = "parallel",
- .func = unlang_parallel,
- .signal = unlang_parallel_signal,
- .resume = unlang_parallel_resume,
- .debug_braces = true
- },
-#ifdef WITH_UNLANG
- [UNLANG_TYPE_IF] = {
- .name = "if",
- .func = unlang_if,
- .debug_braces = true
- },
- [UNLANG_TYPE_ELSE] = {
- .name = "else",
- .func = unlang_group,
- .debug_braces = true
- },
- [UNLANG_TYPE_ELSIF] = {
- .name = "elsif",
- .func = unlang_if,
- .debug_braces = true
- },
- [UNLANG_TYPE_UPDATE] = {
- .name = "update",
- .func = unlang_update,
- .debug_braces = true
- },
- [UNLANG_TYPE_SWITCH] = {
- .name = "switch",
- .func = unlang_switch,
- .debug_braces = true
- },
- [UNLANG_TYPE_CASE] = {
- .name = "case",
- .func = unlang_case,
- .debug_braces = true
- },
- [UNLANG_TYPE_FOREACH] = {
- .name = "foreach",
- .func = unlang_foreach,
- .debug_braces = true
- },
- [UNLANG_TYPE_BREAK] = {
- .name = "break",
- .func = unlang_break,
- .debug_braces = false
- },
- [UNLANG_TYPE_RETURN] = {
- .name = "return",
- .func = unlang_return,
- .debug_braces = false
- },
- [UNLANG_TYPE_MAP] = {
- .name = "map",
- .func = unlang_map,
- .debug_braces = true
- },
- [UNLANG_TYPE_POLICY] = {
- .name = "policy",
- .func = unlang_policy,
- .debug_braces = true
- },
- [UNLANG_TYPE_SUBREQUEST] = {
- .name = "subrequest",
- .func = unlang_subrequest,
- .signal = unlang_subrequest_signal,
- .resume = unlang_subrequest_resume,
- .debug_braces = true
- },
- [UNLANG_TYPE_DETACH] = {
- .name = "detach",
- .func = unlang_detach,
- .debug_braces = false
- },
- [UNLANG_TYPE_CALL] = {
- .name = "call",
- .func = unlang_call,
- .debug_braces = true
- },
-#endif
- [UNLANG_TYPE_XLAT_INLINE] = {
- .name = "xlat_inline",
- .func = unlang_xlat_inline,
- .debug_braces = false
- },
- [UNLANG_TYPE_XLAT] = {
- .name = "xlat_eval",
- .func = unlang_xlat,
- .resume = unlang_xlat_resume,
- .debug_braces = false
- },
- [UNLANG_TYPE_RESUME] = {
- .name = "resume",
- .func = unlang_resume,
- .debug_braces = false
- },
- [UNLANG_TYPE_MAX] = { NULL, NULL, NULL, NULL, false }
-};
-
-/** Callback for handling resumption frames
- *
- * Resumption frames are added to track when a module, or other construct
- * has yielded control back to the interpreter.
- *
- * This function is called when the request has been marked as resumable
- * and a resumption frame was previously placed on the stack, i.e. when
- * the work that caused the request to be yielded initially has completed.
+/** Pop a stack frame, removing any associated dynamically allocated state
*
- * @param[in] request to be resumed.
- * @param[out] presult the rcode returned by the resume function.
- * @param[out] priority associated with the rcode.
+ * @param[in] stack frame to pop.
*/
-static unlang_action_t unlang_resume(REQUEST *request, rlm_rcode_t *presult, int *priority)
+static inline void unlang_pop(unlang_stack_t *stack)
{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_resume_t *mr = unlang_generic_to_resume(instruction);
- unlang_action_t action;
-
- RDEBUG3("Resuming in %s", mr->self.debug_name);
-
- if (!unlang_ops[mr->parent->type].resume) {
- *presult = RLM_MODULE_FAIL;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- request->module = mr->self.debug_name;
+ unlang_stack_frame_t *frame, *next;
- /*
- * Run the resume callback associated with
- * the original frame which was used to
- * create this resumption frame.
- */
- action = unlang_ops[mr->parent->type].resume(request, presult, mr->resume_ctx);
+ rad_assert(stack->depth > 1);
- request->module = NULL;
+ frame = &stack->frame[stack->depth];
+ if (frame->state) talloc_free(frame->state);
- /*
- * Leave mr alone, it will be freed when the request is done.
- */
+ frame = &stack->frame[--stack->depth];
+ next = frame + 1;
/*
- * Is now marked as "stop" when it wasn't before, we must have been blocked.
+ * Unwind back up the stack
*/
- if (request->master_state == REQUEST_STOP_PROCESSING) {
- RWARN("Module %s became unblocked", mr->self.debug_name);
- return UNLANG_ACTION_STOP_PROCESSING;
- }
-
- if (*presult != RLM_MODULE_YIELD) {
- rad_assert(*presult >= RLM_MODULE_REJECT);
- rad_assert(*presult < RLM_MODULE_NUMCODES);
- *priority = instruction->actions[*presult];
- }
-
- return action;
+ if (next->unwind != 0) frame->unwind = next->unwind;
}
/** Update the current result after each instruction, and after popping each stack frame
/*
* Interpret the various types of blocks.
*/
-static rlm_rcode_t unlang_run(REQUEST *request)
+rlm_rcode_t unlang_run(REQUEST *request)
{
int priority;
unlang_frame_action_t fa = UNLANG_FRAME_ACTION_CONTINUE;
DUMP_STACK;
}
-/** Static instruction for performing xlat evaluations
- *
- */
-static unlang_t xlat_instruction = {
- .type = UNLANG_TYPE_XLAT,
- .name = "xlat",
- .debug_name = "xlat",
- .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_USERLOCK] = 0,
- [RLM_MODULE_NOTFOUND] = 0,
- [RLM_MODULE_NOOP] = 0,
- [RLM_MODULE_UPDATED] = 0
- },
-};
-
-/** Push a pre-compiled xlat onto the stack for evaluation
- *
- * @param[in] ctx To allocate value boxes and values in.
- * @param[out] out Where to write the result of the expansion.
- * @param[in] request to push xlat onto.
- * @param[in] exp node to evaluate.
- * @param[in] top_frame Set to UNLANG_TOP_FRAME if this is the shallowest nesting level.
- * Set to UNLANG_SUB_FRAME if this is a nested expansion.
- */
-static void unlang_push_xlat(TALLOC_CTX *ctx, fr_value_box_t **out,
- REQUEST *request, xlat_exp_t const *exp, bool top_frame)
-{
-
- unlang_stack_state_xlat_t *state;
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame;
-
- /*
- * Push a new xlat eval frame onto the stack
- */
- unlang_push(stack, &xlat_instruction, RLM_MODULE_UNKNOWN, UNLANG_NEXT_STOP, top_frame);
- frame = &stack->frame[stack->depth];
-
- /*
- * Allocate its state, and setup a cursor for the xlat nodes
- */
- frame->state = state = talloc_zero(stack, unlang_stack_state_xlat_t);
- state->exp = exp;
-
- fr_cursor_init(&state->values, out);
-
- state->ctx = ctx;
-}
-
-/** 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
- * a resumption point, as the call to an xlat function may require yielding control
- * back to the interpreter.
- *
- * To simplify the calling conventions, this function is provided to first push a
- * resumption stack frame for the module, and then push an xlat stack frame.
- *
- * After pushing those frames the function updates the stack pointer to jump over
- * the resumption frame and execute the xlat interpreter.
- *
- * When the xlat interpreter finishes, and pops the xlat frame, the unlang interpreter
- * will then call the module resumption frame, allowing the module to continue exectuion.
- *
- * @param[in] ctx To allocate value boxes and values in.
- * @param[out] out Where to write the result of the expansion.
- * @param[in] request The current request.
- * @param[in] xlat to evaluate.
- * @param[in] callback to call on unlang_resumable().
- * @param[in] signal to call on unlang_action().
- * @param[in] uctx to pass to the callbacks.
- * @return
- * - RLM_MODULE_YIELD if the xlat would perform blocking I/O
- * - A return code representing the result of the xla
- */
-rlm_rcode_t unlang_push_module_xlat(TALLOC_CTX *ctx, fr_value_box_t **out,
- REQUEST *request, xlat_exp_t const *xlat,
- fr_unlang_module_resume_t callback,
- fr_unlang_module_signal_t signal, void *uctx)
-{
- /*
- * Push the resumption point
- */
- (void) unlang_module_yield(request, callback, signal, uctx);
-
- /*
- * Push the xlat function
- */
- unlang_push_xlat(ctx, out, request, xlat, true);
-
- /*
- * Execute the xlat frame we just pushed onto the stack.
- */
- return unlang_run(request);
-}
-
/** Continue interpreting after a previous push or yield.
*
*/
return 0;
}
+/** Send a signal (usually stop) to a request
+ *
+ * This is typically called via an "async" action, i.e. an action
+ * outside of the normal processing of the request.
+ *
+ * If there is no #fr_unlang_module_signal_t callback defined, the action is ignored.
+ *
+ * @param[in] request The current request.
+ * @param[in] action to signal.
+ */
+void unlang_signal(REQUEST *request, fr_state_action_t action)
+{
+ unlang_stack_frame_t *frame;
+ unlang_stack_t *stack = request->stack;
+ unlang_resume_t *mr;
+
+ rad_assert(stack->depth > 0);
+
+ frame = &stack->frame[stack->depth];
+
+ /*
+ * Be gracious in errors.
+ */
+ if (frame->instruction->type != UNLANG_TYPE_RESUME) return;
+
+ mr = unlang_generic_to_resume(frame->instruction);
+
+ /*
+ * No signal handler for this frame type
+ */
+ if (!unlang_ops[mr->parent->type].signal) return;
+
+ unlang_ops[mr->parent->type].signal(request, mr->resume_ctx, action);
+}
+
+int unlang_stack_depth(REQUEST *request)
+{
+ unlang_stack_t *stack = request->stack;
+
+ return stack->depth;
+}
+
+/*
+ * Temporary until the correct behaviour for unlang_resumable
+ * can be determined.
+ */
+
+/** Parallel children have states
+ *
+ */
+typedef enum unlang_parallel_child_state_t {
+ CHILD_INIT = 0, //!< needs initialization
+ CHILD_RUNNABLE,
+ CHILD_YIELDED,
+ CHILD_DONE
+} unlang_parallel_child_state_t;
+
+/** Each parallel child has a state, and an associated request
+ *
+ */
+typedef struct unlang_parallel_child_t {
+ unlang_parallel_child_state_t state; //!< state of the child
+ REQUEST *child; //!< child request
+ unlang_t *instruction; //!< broken out of g->children
+} unlang_parallel_child_t;
+
+typedef struct unlang_parallel_t {
+ rlm_rcode_t result;
+ int priority;
+
+ int num_children;
+
+ unlang_group_t *g;
+
+ unlang_parallel_child_t children[];
+} unlang_parallel_t;
/** Mark a request as resumable.
*
if (request->runnable_id < 0) fr_heap_insert(request->backlog, request);
}
-/** Send a signal (usually stop) to a request
- *
- * This is typically called via an "async" action, i.e. an action
- * outside of the normal processing of the request.
- *
- * If there is no #fr_unlang_module_signal_t callback defined, the action is ignored.
- *
- * @param[in] request The current request.
- * @param[in] action to signal.
- */
-void unlang_signal(REQUEST *request, fr_state_action_t action)
-{
- unlang_stack_frame_t *frame;
- unlang_stack_t *stack = request->stack;
- unlang_resume_t *mr;
-
- rad_assert(stack->depth > 0);
-
- frame = &stack->frame[stack->depth];
-
- /*
- * Be gracious in errors.
- */
- if (frame->instruction->type != UNLANG_TYPE_RESUME) return;
-
- mr = unlang_generic_to_resume(frame->instruction);
-
- /*
- * No signal handler for this frame type
- */
- if (!unlang_ops[mr->parent->type].signal) return;
-
- unlang_ops[mr->parent->type].signal(request, mr->resume_ctx, action);
-}
-
-int unlang_stack_depth(REQUEST *request)
-{
- unlang_stack_t *stack = request->stack;
-
- return stack->depth;
-}
-
/** Yield a request back to the interpreter from within a module
*
* This passes control of the request back to the unlang interpreter, setting
--- /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_interpret.c
+ * @brief Execute compiled unlang structures using an iterative interpreter.
+ *
+ * @copyright 2006-2016 The FreeRADIUS server project
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modpriv.h>
+#include <freeradius-devel/interpreter.h>
+#include <freeradius-devel/parser.h>
+#include <freeradius-devel/xlat.h>
+#include <freeradius-devel/io/listen.h>
+
+/*
+ * Some functions differ mainly in their parsing
+ */
+#define unlang_redundant_load_balance unlang_load_balance
+#define unlang_policy unlang_group
+#define unlang_break unlang_return
+
+/*
+ * Lock the mutex for the module
+ */
+static inline void safe_lock(module_instance_t *instance)
+{
+ if (instance->mutex) pthread_mutex_lock(instance->mutex);
+}
+
+/*
+ * Unlock the mutex for the module
+ */
+static inline void safe_unlock(module_instance_t *instance)
+{
+ if (instance->mutex) pthread_mutex_unlock(instance->mutex);
+}
+
+/*
+ * Recursively collect active callers. Slow, but correct.
+ */
+static uint64_t unlang_active_callers(unlang_t *instruction)
+{
+ uint64_t active_callers;
+ unlang_t *child;
+ unlang_group_t *g;
+
+ switch (instruction->type) {
+ default:
+ return 0;
+
+ case UNLANG_TYPE_MODULE_CALL:
+ {
+ module_thread_instance_t *thread;
+ unlang_module_call_t *sp;
+
+ sp = unlang_generic_to_module_call(instruction);
+ rad_assert(sp != NULL);
+
+ thread = module_thread_instance_find(sp->module_instance);
+ rad_assert(thread != NULL);
+
+ return thread->active_callers;
+ }
+
+ case UNLANG_TYPE_GROUP:
+ case UNLANG_TYPE_LOAD_BALANCE:
+ case UNLANG_TYPE_REDUNDANT_LOAD_BALANCE:
+ case UNLANG_TYPE_IF:
+ case UNLANG_TYPE_ELSE:
+ case UNLANG_TYPE_ELSIF:
+ case UNLANG_TYPE_FOREACH:
+ case UNLANG_TYPE_SWITCH:
+ case UNLANG_TYPE_CASE:
+ g = unlang_generic_to_group(instruction);
+
+ active_callers = 0;
+ for (child = g->children;
+ child != NULL;
+ child = child->next) {
+ active_callers += unlang_active_callers(child);
+ }
+ break;
+ }
+
+ return active_callers;
+}
+
+static unlang_action_t unlang_load_balance(REQUEST *request,
+ rlm_rcode_t *presult, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+
+ uint32_t count = 0;
+
+ g = unlang_generic_to_group(instruction);
+ rad_assert(g->children != NULL);
+
+ /*
+ * No frame? This is the first time we've been called.
+ * Go find one.
+ */
+ if (!frame->repeat) {
+ RDEBUG4("%s setting up", frame->instruction->debug_name);
+
+ if (g->vpt) {
+ uint32_t hash, start;
+ ssize_t slen;
+ char const *p = NULL;
+ char buffer[1024];
+
+ /*
+ * Integer data types let the admin
+ * select which frame is being used.
+ */
+ if ((g->vpt->type == TMPL_TYPE_ATTR) &&
+ ((g->vpt->tmpl_da->type == FR_TYPE_UINT8) ||
+ (g->vpt->tmpl_da->type == FR_TYPE_UINT16) ||
+ (g->vpt->tmpl_da->type == FR_TYPE_UINT32) ||
+ (g->vpt->tmpl_da->type == FR_TYPE_UINT64))) {
+ VALUE_PAIR *vp;
+
+ slen = tmpl_find_vp(&vp, request, g->vpt);
+ if (slen < 0) {
+ REDEBUG("Failed finding attribute %s", g->vpt->name);
+ goto randomly_choose;
+ }
+
+ switch (g->vpt->tmpl_da->type) {
+ case FR_TYPE_UINT8:
+ start = ((uint32_t) vp->vp_uint8) % g->num_children;
+ break;
+
+ case FR_TYPE_UINT16:
+ start = ((uint32_t) vp->vp_uint16) % g->num_children;
+ break;
+
+ case FR_TYPE_UINT32:
+ start = vp->vp_uint32 % g->num_children;
+ break;
+
+ case FR_TYPE_UINT64:
+ start = (uint32_t) (vp->vp_uint64 % ((uint64_t) g->num_children));
+ break;
+
+ default:
+ goto randomly_choose;
+ }
+
+ } else {
+ slen = tmpl_expand(&p, buffer, sizeof(buffer), request, g->vpt, NULL, NULL);
+ if (slen < 0) {
+ REDEBUG("Failed expanding template");
+ goto randomly_choose;
+ }
+
+ hash = fr_hash(p, slen);
+
+ start = hash % g->num_children;;
+ }
+
+ RDEBUG3("load-balance starting at child %d", (int) start);
+
+ count = 0;
+ for (frame->redundant.child = frame->redundant.found = g->children;
+ frame->redundant.child != NULL;
+ frame->redundant.child = frame->redundant.child->next) {
+ count++;
+ if (count == start) {
+ frame->redundant.found = frame->redundant.child;
+ break;
+ }
+ }
+
+ } else {
+ int num;
+ uint64_t lowest_active_callers;
+
+ randomly_choose:
+ lowest_active_callers = ~(uint64_t ) 0;
+
+ /*
+ * Choose a child at random.
+ */
+ for (frame->redundant.child = frame->redundant.found = g->children, num = 0;
+ frame->redundant.child != NULL;
+ frame->redundant.child = frame->redundant.child->next, num++) {
+ uint64_t active_callers;
+ unlang_t *child = frame->redundant.child;
+
+ if (child->type != UNLANG_TYPE_MODULE_CALL) {
+ active_callers = unlang_active_callers(child);
+ RDEBUG3("load-balance child %d sub-section has %" PRIu64 " active", num, active_callers);
+
+ } else {
+ module_thread_instance_t *thread;
+ unlang_module_call_t *sp;
+
+ sp = unlang_generic_to_module_call(child);
+ rad_assert(sp != NULL);
+
+ thread = module_thread_instance_find(sp->module_instance);
+ rad_assert(thread != NULL);
+
+ active_callers = thread->active_callers;
+ RDEBUG3("load-balance child %d sub-module has %" PRIu64 " active", num, active_callers);
+ }
+
+
+ /*
+ * Reset the found, and the count
+ * of children with this level of
+ * activity.
+ */
+ if (active_callers < lowest_active_callers) {
+ RDEBUG3("load-balance choosing child %d as active %" PRIu64 " < %" PRIu64 "",
+ num, active_callers, lowest_active_callers);
+
+ count = 1;
+ lowest_active_callers = active_callers;
+ frame->redundant.found = frame->redundant.child;
+ continue;
+ }
+
+ /*
+ * Skip callers who are busier
+ * than the one we found.
+ */
+ if (active_callers > lowest_active_callers) {
+ RDEBUG3("load-balance skipping child %d, as active %" PRIu64 " > %" PRIu64 "",
+ num, active_callers, lowest_active_callers);
+ continue;
+ }
+
+ count++;
+ RDEBUG3("load-balance found %d children with %" PRIu64 " active", count, active_callers);
+
+ if ((count * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) {
+ RDEBUG3("load-balance choosing random child %d", num);
+ frame->redundant.found = frame->redundant.child;
+ }
+ }
+ }
+
+ if (instruction->type == UNLANG_TYPE_LOAD_BALANCE) {
+ unlang_push(stack, frame->redundant.found, frame->result, UNLANG_NEXT_STOP, UNLANG_SUB_FRAME);
+ return UNLANG_ACTION_PUSHED_CHILD;
+ }
+
+ /*
+ * redundant-load-balance starts at this one.
+ */
+ frame->redundant.child = frame->redundant.found;
+
+ } else {
+ RDEBUG4("%s resuming", frame->instruction->debug_name);
+
+ /*
+ * We are in a resumed frame. The module we
+ * chose failed, so we have to go through the
+ * process again.
+ */
+
+ rad_assert(instruction->type != UNLANG_TYPE_LOAD_BALANCE); /* this is never called again */
+
+ /*
+ * We were called again. See if we're done.
+ */
+ if (frame->redundant.child->actions[*presult] == MOD_ACTION_RETURN) {
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * @todo - track the one we chose, and if it
+ * fails, do the load-balancing again, except
+ * this time skipping the failed module. AND,
+ * keep track of multiple failed modules.
+ * Probably in the unlang_resume_t, via a
+ * uint64_t and bit mask for simplicity.
+ */
+
+ frame->redundant.child = frame->redundant.child->next;
+ if (!frame->redundant.child) frame->redundant.child = g->children;
+
+ if (frame->redundant.child == frame->redundant.found) {
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+ }
+
+ /*
+ * Push the child, and yield for a later return.
+ */
+ unlang_push(stack, frame->redundant.child, frame->result, UNLANG_NEXT_STOP, UNLANG_SUB_FRAME);
+ frame->repeat = true;
+
+ return UNLANG_ACTION_PUSHED_CHILD;
+}
+
+static unlang_action_t unlang_group(REQUEST *request,
+ UNUSED rlm_rcode_t *result, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+
+ g = unlang_generic_to_group(instruction);
+
+ /*
+ * The compiler catches most of these, EXCEPT for the
+ * top-level 'recv Access-Request' etc. Which can exist,
+ * and can be empty.
+ */
+ if (!g->children) {
+ RDEBUG2("} # %s ... <ignoring empty subsection>", instruction->debug_name);
+ return UNLANG_ACTION_CONTINUE;
+ }
+
+ unlang_push(stack, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_SUB_FRAME);
+ return UNLANG_ACTION_PUSHED_CHILD;
+}
+
+/** Allocates and initializes an unlang_resume_t
+ *
+ * @param[in] request The current request.
+ * @param[in] callback to call on unlang_resumable().
+ * @param[in] signal call on unlang_action().
+ * @param[in] resume_ctx to pass to the callbacks.
+ * @return
+ * unlang_resume_t on success
+ * NULL on error
+ */
+static unlang_resume_t *unlang_resume_alloc(REQUEST *request,
+ void *callback,
+ fr_unlang_module_signal_t signal, void *resume_ctx)
+{
+ unlang_resume_t *mr;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+
+ mr = talloc_zero(request, unlang_resume_t);
+ if (!mr) return NULL;
+
+ /*
+ * Remember the parent.
+ */
+ mr->parent = frame->instruction;
+
+ /*
+ * Initialize parent ptr, next ptr, name, debug_name,
+ * type, actions, etc.
+ */
+ memcpy(&mr->self, frame->instruction, sizeof(mr->self));
+
+ /*
+ * But note that we're of type RESUME
+ */
+ mr->self.type = UNLANG_TYPE_RESUME;
+
+ /*
+ * Fill in the signal handlers and resumption ctx
+ */
+ mr->callback = callback;
+ mr->signal = signal;
+ mr->resume_ctx = resume_ctx;
+
+ /*
+ * Replaces the current stack frame with a RESUME frame.
+ */
+ frame->instruction = unlang_resume_to_generic(mr);
+
+ return mr;
+}
+
+/** Continue after creating a subrequest.
+ *
+ * Just run some "unlang", but don't do anything else.
+ */
+static fr_io_final_t unlang_process_continue(REQUEST *request, fr_io_action_t action)
+{
+ rlm_rcode_t rcode;
+
+ REQUEST_VERIFY(request);
+
+ /*
+ * Pass this through asynchronously to the module which
+ * is waiting for something to happen.
+ */
+ if (action != FR_IO_ACTION_RUN) {
+ unlang_signal(request, (fr_state_action_t) action);
+ return FR_IO_DONE;
+ }
+
+ rcode = unlang_interpret_continue(request);
+
+ if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE;
+
+ if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD;
+
+ /*
+ * Don't bother setting request->reply->code.
+ */
+ return FR_IO_DONE;
+}
+
+/** Allocate a child request based on the parent.
+ *
+ */
+static REQUEST *unlang_child_alloc(REQUEST *request, unlang_t *instruction, rlm_rcode_t default_rcode, bool do_next_sibling, bool detachable)
+{
+ REQUEST *child;
+ unlang_stack_t *stack;
+
+ if (!detachable) {
+ child = request_alloc_fake(request);
+ } else {
+ child = request_alloc_detachable(request);
+ }
+ if (!child) return NULL;
+
+ /*
+ * Push the children, and set it's top frame to be true.
+ */
+ stack = child->stack;
+ child->log.unlang_indent = request->log.unlang_indent;
+ unlang_push(stack, instruction, default_rcode, do_next_sibling, UNLANG_SUB_FRAME);
+ stack->frame[stack->depth].top_frame = true;
+
+ /*
+ * Initialize some basic information for the child.
+ *
+ * Note that we do NOT initialize child->backlog, as the
+ * child is never resumable... the parent is resumable.
+ */
+ child->number = request->number;
+ child->el = request->el;
+ child->server_cs = request->server_cs;
+
+ /*
+ * Initialize all of the async fields.
+ */
+ child->async = talloc_zero(child, fr_async_t);
+
+#define COPY_FIELD(_x) child->async->_x = request->async->_x
+ COPY_FIELD(original_recv_time);
+ COPY_FIELD(recv_time);
+ COPY_FIELD(listen);
+
+ /*
+ * Always set the "process" function to the local
+ * bare-bones function which just runs on section of
+ * "unlang", and doesn't send replies or anything else.
+ */
+ child->async->process = unlang_process_continue;
+
+ /*
+ * Note that we don't do time tracking on the child.
+ * Instead, all of it is done in the context of the
+ * parent.
+ */
+ FR_DLIST_INIT(child->async->time_order);
+ FR_DLIST_INIT(child->async->tracking.list);
+
+ /*
+ * create {...} creates an empty copy.
+ */
+
+ return child;
+}
+
+
+/** Send a signal from parent request to subrequest
+ *
+ */
+static void unlang_subrequest_signal(UNUSED REQUEST *request, void *ctx, fr_state_action_t action)
+{
+ REQUEST *child = talloc_get_type_abort(ctx, REQUEST);
+
+ unlang_signal(child, action);
+}
+
+
+/** Resume a subrequest
+ *
+ */
+static unlang_action_t unlang_subrequest_resume(UNUSED REQUEST *request, rlm_rcode_t *presult, void *resume_ctx)
+{
+ REQUEST *child = talloc_get_type_abort(resume_ctx, REQUEST);
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame;
+#ifndef NDEBUG
+ unlang_resume_t *mr;
+#endif
+
+ /*
+ * Continue running the child.
+ */
+ *presult = unlang_run(child);
+ if (*presult != RLM_MODULE_YIELD) {
+ frame = &stack->frame[stack->depth];
+ rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+
+ frame->instruction->type = UNLANG_TYPE_SUBREQUEST; /* for debug purposes */
+ request_detach(child);
+ talloc_free(child);
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+#ifndef NDEBUG
+ frame = &stack->frame[stack->depth];
+ rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+
+ mr = unlang_generic_to_resume(frame->instruction);
+ (void) talloc_get_type_abort(mr, unlang_resume_t);
+
+ rad_assert(mr->callback == NULL);
+ rad_assert(mr->resume_ctx == child);
+#endif
+
+ /*
+ * If the child yields, our current frame is still an
+ * unlang_resume_t.
+ */
+
+ return UNLANG_ACTION_YIELD;
+}
+
+static unlang_action_t unlang_xlat_resume(REQUEST *request, UNUSED rlm_rcode_t *presult, UNUSED void *resume_ctx)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_resume_t *mr = unlang_generic_to_resume(instruction);
+ unlang_stack_state_xlat_t *xs = talloc_get_type_abort(frame->state, unlang_stack_state_xlat_t);
+ xlat_action_t xa;
+
+ xa = ((xlat_resume_callback_t)mr->callback)(xs->ctx, &xs->values, request, NULL, NULL,
+ &xs->result, mr->resume_ctx);
+ switch (xa) {
+ case XLAT_ACTION_YIELD:
+ return UNLANG_ACTION_YIELD;
+
+ case XLAT_ACTION_DONE:
+ return UNLANG_ACTION_CALCULATE_RESULT;
+
+ case XLAT_ACTION_PUSH_CHILD:
+ rad_assert(0);
+ /* FALL-THROUGH */
+
+ case XLAT_ACTION_FAIL:
+ *presult = RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ rad_assert(0);
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+static void unlang_max_request_time(UNUSED fr_event_list_t *el, UNUSED struct timeval *now, void *uctx)
+{
+ REQUEST *request = talloc_get_type_abort(uctx, REQUEST);
+
+ RDEBUG("Reached Request-Lifetime. Forcibly stopping request");
+
+ if (request->runnable_id >= 0) {
+ rad_assert(request->backlog != NULL);
+ (void) fr_heap_extract(request->backlog, request);
+ }
+
+ talloc_free(request);
+}
+
+
+static unlang_action_t unlang_detach(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ VALUE_PAIR *vp;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+
+ rad_assert(instruction->parent->type == UNLANG_TYPE_SUBREQUEST);
+ RDEBUG2("%s", unlang_ops[instruction->type].name);
+
+ if (request_detach(request) < 0) {
+ ERROR("Failed detaching child");
+ *presult = RLM_MODULE_FAIL;
+ *priority = 0;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * Set Request Lifetime
+ */
+ vp = fr_pair_find_by_num(request->control, 0, FR_REQUEST_LIFETIME, TAG_ANY);
+ if (!vp || (vp->vp_uint32 > 0)) {
+ struct timeval when;
+ const fr_event_timer_t **ev_p;
+
+ gettimeofday(&when, NULL);
+
+ if (!vp) {
+ when.tv_sec += 30; /* default to 30s if not set */
+
+ } else if (vp->vp_uint32 > 3600) {
+ RWARN("Request-Timeout can be no more than 3600");
+ when.tv_sec += 3600;
+
+ } else if (vp->vp_uint32 < 5) {
+ RWARN("Request-Timeout can be no less than 5");
+ when.tv_sec += 5;
+
+ } else {
+ when.tv_sec += vp->vp_uint32;
+ }
+
+ ev_p = talloc_size(request, sizeof(*ev_p));
+ memset(ev_p, 0, sizeof(*ev_p));
+
+ (void) fr_event_timer_insert(request, request->el, ev_p,
+ &when, unlang_max_request_time, request);
+ }
+
+ /*
+ * request_detach() doesn't set the "detached" flag, but
+ * it does set the backlog...
+ */
+ request->async->detached = true;
+ rad_assert(request->backlog != NULL);
+
+ *presult = RLM_MODULE_YIELD;
+ return UNLANG_ACTION_YIELD;
+}
+
+static unlang_action_t unlang_call(REQUEST *request,
+ UNUSED rlm_rcode_t *presult, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+ int indent;
+ fr_io_final_t final;
+ unlang_stack_t *current;
+ CONF_SECTION *server_cs;
+
+ g = unlang_generic_to_group(instruction);
+ rad_assert(g->children != NULL);
+
+ /*
+ * @todo - allow for other process functions. Mostly
+ * because we need to save and resume this function, and
+ * we haven't bothered to do that so far.
+ *
+ * If we DO allow other functions, we need to replace
+ * request->async->listener, as we want to pretend this
+ * is a virtual request which didn't come in from the
+ * network. i.e. the other virtual server shouldn't be
+ * able to access request->async->listener, and muck with
+ * it's statistics, see it's configuration, etc.
+ */
+ rad_assert(request->async->process == unlang_process_continue);
+
+ /*
+ * @todo - We probably want to just remove the 'stack'
+ * parameter from the interpreter function arguments.
+ * It's not needed there.
+ */
+ rad_assert(stack == request->stack);
+
+ indent = request->log.unlang_indent;
+ request->log.unlang_indent = 0; /* the process function expects this */
+
+ current = request->stack;
+ request->stack = talloc_zero(request, unlang_stack_t);
+
+ server_cs = request->server_cs;
+ request->server_cs = g->server_cs;
+
+ request->async->process = g->process;
+
+ RDEBUG("server %s {", cf_section_name2(g->server_cs));
+
+ /*
+ * @todo - we can't change protocols (e.g. RADIUS ->
+ * DHCP) unless we're in a subrequest.
+ *
+ * @todo - we can't change packet types
+ * (e.g. Access-Request -> Accounting-Request) unless
+ * we're in a subrequest.
+ */
+ final = request->async->process(request, FR_IO_ACTION_RUN);
+
+ RDEBUG("} # server %s", cf_section_name2(g->server_cs));
+
+ /*
+ * All other return codes are semantically equivalent for
+ * our purposes. "DONE" means "stopped without reply",
+ * and REPLY means "finished successfully". Neither of
+ * those map well into module rcodes. Instead, we rely
+ * on the caller to look at request->reply->code.
+ */
+ if (final == FR_IO_YIELD) {
+ RDEBUG("Noo yield for you!");
+ }
+
+ /*
+ * @todo - save these in a resume state somewhere...
+ */
+ request->log.unlang_indent = indent;
+ request->async->process = unlang_process_continue;
+ talloc_free(request->stack);
+ request->stack = current;
+ request->server_cs = server_cs;
+
+ RDEBUG("Continuing with contents of %s { ...", instruction->debug_name);
+
+ /*
+ * And then call the children to process the answer.
+ */
+ unlang_push(stack, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_SUB_FRAME);
+ return UNLANG_ACTION_PUSHED_CHILD;
+}
+
+/** Static instruction for performing xlat evaluations
+ *
+ */
+static unlang_t xlat_instruction = {
+ .type = UNLANG_TYPE_XLAT,
+ .name = "xlat",
+ .debug_name = "xlat",
+ .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_USERLOCK] = 0,
+ [RLM_MODULE_NOTFOUND] = 0,
+ [RLM_MODULE_NOOP] = 0,
+ [RLM_MODULE_UPDATED] = 0
+ },
+};
+
+/** Push a pre-compiled xlat onto the stack for evaluation
+ *
+ * @param[in] ctx To allocate value boxes and values in.
+ * @param[out] out Where to write the result of the expansion.
+ * @param[in] request to push xlat onto.
+ * @param[in] exp node to evaluate.
+ * @param[in] top_frame Set to UNLANG_TOP_FRAME if this is the shallowest nesting level.
+ * Set to UNLANG_SUB_FRAME if this is a nested expansion.
+ */
+static void unlang_push_xlat(TALLOC_CTX *ctx, fr_value_box_t **out,
+ REQUEST *request, xlat_exp_t const *exp, bool top_frame)
+{
+
+ unlang_stack_state_xlat_t *state;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame;
+
+ /*
+ * Push a new xlat eval frame onto the stack
+ */
+ unlang_push(stack, &xlat_instruction, RLM_MODULE_UNKNOWN, UNLANG_NEXT_STOP, top_frame);
+ frame = &stack->frame[stack->depth];
+
+ /*
+ * Allocate its state, and setup a cursor for the xlat nodes
+ */
+ frame->state = state = talloc_zero(stack, unlang_stack_state_xlat_t);
+ state->exp = exp;
+
+ fr_cursor_init(&state->values, out);
+
+ state->ctx = ctx;
+}
+
+/** 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
+ * a resumption point, as the call to an xlat function may require yielding control
+ * back to the interpreter.
+ *
+ * To simplify the calling conventions, this function is provided to first push a
+ * resumption stack frame for the module, and then push an xlat stack frame.
+ *
+ * After pushing those frames the function updates the stack pointer to jump over
+ * the resumption frame and execute the xlat interpreter.
+ *
+ * When the xlat interpreter finishes, and pops the xlat frame, the unlang interpreter
+ * will then call the module resumption frame, allowing the module to continue exectuion.
+ *
+ * @param[in] ctx To allocate value boxes and values in.
+ * @param[out] out Where to write the result of the expansion.
+ * @param[in] request The current request.
+ * @param[in] xlat to evaluate.
+ * @param[in] callback to call on unlang_resumable().
+ * @param[in] signal to call on unlang_action().
+ * @param[in] uctx to pass to the callbacks.
+ * @return
+ * - RLM_MODULE_YIELD if the xlat would perform blocking I/O
+ * - A return code representing the result of the xla
+ */
+rlm_rcode_t unlang_push_module_xlat(TALLOC_CTX *ctx, fr_value_box_t **out,
+ REQUEST *request, xlat_exp_t const *xlat,
+ fr_unlang_module_resume_t callback,
+ fr_unlang_module_signal_t signal, void *uctx)
+{
+ /*
+ * Push the resumption point
+ */
+ (void) unlang_module_yield(request, callback, signal, uctx);
+
+ /*
+ * Push the xlat function
+ */
+ unlang_push_xlat(ctx, out, request, xlat, true);
+
+ /*
+ * Execute the xlat frame we just pushed onto the stack.
+ */
+ return unlang_run(request);
+}
+
+/** Stub function for calling the xlat interpreter
+ *
+ * Calls the xlat interpreter and translates its wants and needs into
+ * unlang_action_t codes.
+ */
+static unlang_action_t unlang_xlat(REQUEST *request,
+ rlm_rcode_t *presult, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_stack_state_xlat_t *xs = talloc_get_type_abort(frame->state, unlang_stack_state_xlat_t);
+ xlat_exp_t const *child = NULL;
+ xlat_action_t xa;
+
+ if (frame->repeat) {
+ fr_cursor_init(&xs->result, &xs->rhead);
+ xa = xlat_frame_eval_repeat(xs->ctx, &xs->values,
+ &child, &xs->alternate,
+ request, &xs->exp,
+ &xs->result);
+ } else {
+ xa = xlat_frame_eval(xs->ctx, &xs->values, &child, request, &xs->exp);
+ }
+
+ switch (xa) {
+ case XLAT_ACTION_PUSH_CHILD:
+ rad_assert(child);
+
+ frame->repeat = true;
+ unlang_push_xlat(xs->ctx, &xs->rhead, request, child, false);
+ return UNLANG_ACTION_PUSHED_CHILD;
+
+ case XLAT_ACTION_YIELD:
+ return UNLANG_ACTION_YIELD;
+
+ case XLAT_ACTION_DONE:
+ return UNLANG_ACTION_CALCULATE_RESULT;
+
+ case XLAT_ACTION_FAIL:
+ *presult = RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ rad_assert(0);
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+static unlang_action_t unlang_subrequest(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+ REQUEST *child;
+ rlm_rcode_t rcode;
+ unlang_resume_t *mr;
+
+ g = unlang_generic_to_group(instruction);
+ rad_assert(g->children != NULL);
+
+ /*
+ * Allocate the child request.
+ */
+ child = unlang_child_alloc(request, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_DETACHABLE);
+ if (!child) {
+ *presult = RLM_MODULE_FAIL;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ 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.
+ *
+ * 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.
+ */
+ rcode = unlang_run(child);
+ if (rcode != RLM_MODULE_YIELD) {
+ request_detach(child);
+ talloc_free(child);
+ *presult = rcode;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * As a special case, if the child instruction is
+ * "detach", detach the child, insert the child into the
+ * runnable queue, and keep going with the parent.
+ *
+ * The unlang_detach() interpreter function takes care of
+ * calling request_detach() for the child.
+ */
+ {
+ unlang_stack_t *child_stack = child->stack;
+ unlang_stack_frame_t *child_frame = &child_stack->frame[child_stack->depth];
+ unlang_t *child_instruction = child_frame->instruction;
+
+ if (child_instruction->type == UNLANG_TYPE_DETACH) {
+ rad_assert(child->backlog != NULL);
+ fr_heap_insert(child->backlog, child);
+
+ RDEBUG2("- detaching child request (%s)", child->name);
+
+ /*
+ * Tell the interpreter to skip the "detach"
+ * stack frame when it continues.
+ */
+ child_frame->instruction = child_frame->next;
+ if (child_frame->instruction) child_frame->next = child_frame->instruction->next;
+
+ *presult = RLM_MODULE_NOOP;
+ *priority = 0;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ } /* else the child yielded, so we have to yield */
+ }
+
+ /*
+ * Create the "resume" stack frame, and have it replace our stack frame.
+ */
+ mr = unlang_resume_alloc(request, NULL, NULL, child);
+ if (!mr) {
+ *presult = RLM_MODULE_FAIL;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ *presult = RLM_MODULE_YIELD;
+ return UNLANG_ACTION_YIELD;
+}
+
+/** Parallel children have states
+ *
+ */
+typedef enum unlang_parallel_child_state_t {
+ CHILD_INIT = 0, //!< needs initialization
+ CHILD_RUNNABLE,
+ CHILD_YIELDED,
+ CHILD_DONE
+} unlang_parallel_child_state_t;
+
+/** Each parallel child has a state, and an associated request
+ *
+ */
+typedef struct unlang_parallel_child_t {
+ unlang_parallel_child_state_t state; //!< state of the child
+ REQUEST *child; //!< child request
+ unlang_t *instruction; //!< broken out of g->children
+} unlang_parallel_child_t;
+
+typedef struct unlang_parallel_t {
+ rlm_rcode_t result;
+ int priority;
+
+ int num_children;
+
+ unlang_group_t *g;
+
+ unlang_parallel_child_t children[];
+} unlang_parallel_t;
+
+
+/** Run one or more sub-sections from the parallel section.
+ *
+ */
+static rlm_rcode_t unlang_parallel_run(REQUEST *request, unlang_parallel_t *state)
+{
+ int i, priority;
+ rlm_rcode_t result;
+ unlang_parallel_child_state_t done = CHILD_DONE; /* hope that we're done */
+
+ // @todo - rdebug running the request.
+
+ /*
+ * Loop over all the children.
+ *
+ * We always service the parallel section from top to
+ * bottom, and we always service all of it.
+ */
+ for (i = 0; i < state->num_children; i++) {
+ switch (state->children[i].state) {
+ /*
+ * Not ready to run.
+ */
+ case CHILD_YIELDED:
+ RDEBUG3("parallel child %d is already YIELDED", i + 1);
+ rad_assert(state->children[i].child != NULL);
+ rad_assert(state->children[i].instruction != NULL);
+ done = CHILD_YIELDED;
+ continue;
+
+ /*
+ * Don't need to call this any more.
+ */
+ case CHILD_DONE:
+ RDEBUG3("parallel child %d is already DONE", i + 1);
+ rad_assert(state->children[i].child == NULL);
+ rad_assert(state->children[i].instruction == NULL);
+ continue;
+
+ /*
+ * Create the child and then run it.
+ */
+ case CHILD_INIT:
+ RDEBUG3("parallel child %d is INIT", i + 1);
+ rad_assert(state->children[i].instruction != NULL);
+ state->children[i].child = unlang_child_alloc(request, state->children[i].instruction,
+ RLM_MODULE_FAIL, /* @todo - fixme ? */
+ UNLANG_NEXT_STOP, UNLANG_NORMAL_CHILD);
+ state->children[i].state = CHILD_RUNNABLE;
+ state->children[i].child->packet->code = request->packet->code;
+
+ if (state->g->clone) {
+ state->children[i].child->packet->vps = fr_pair_list_copy(state->children[i].child->packet,
+ request->packet->vps);
+ state->children[i].child->reply->vps = fr_pair_list_copy(state->children[i].child->reply,
+ request->reply->vps);
+ state->children[i].child->control = fr_pair_list_copy(state->children[i].child,
+ request->control);
+ }
+
+ /* FALL-THROUGH */
+
+ /*
+ * Run this entry.
+ */
+ case CHILD_RUNNABLE:
+ RDEBUG("parallel - running entry %d/%d", i + 1, state->num_children);
+ result = unlang_run(state->children[i].child);
+ if (result == RLM_MODULE_YIELD) {
+ state->children[i].state = CHILD_YIELDED;
+ done = CHILD_YIELDED;
+ continue;
+ }
+
+ RDEBUG3("parallel child %d returns %s", i + 1,
+ fr_int2str(mod_rcode_table, result, "<invalid>"));
+
+ /*
+ * Remember this before we delete the
+ * reference to 'instruction'.
+ */
+ priority = state->children[i].instruction->actions[result];
+
+ /*
+ * Clean up the state entry.
+ */
+ state->children[i].state = CHILD_DONE;
+ TALLOC_FREE(state->children[i].child);
+ state->children[i].instruction = NULL;
+
+ /*
+ * return is "stop processing the
+ * parallel section".
+ */
+ if (priority == MOD_ACTION_RETURN) {
+ RDEBUG("child %d/%d says 'return' - skipping the remaining children",
+ i + 1, state->num_children);
+
+ /*
+ * Fall through to processing the
+ * priorities and return codes.
+ */
+ i = state->num_children;
+ priority = 0;
+ done = CHILD_DONE;
+ }
+
+ /*
+ * Reject is just reject.
+ */
+ if (priority == MOD_ACTION_REJECT) {
+ priority = 0;
+ result = RLM_MODULE_REJECT;
+ }
+
+ /*
+ * Do priority over-ride.
+ */
+ if (priority > state->priority) {
+ unlang_stack_t *stack = request->stack;
+
+ state->result = result;
+ state->priority = priority;
+
+ RDEBUG4("** [%i] %s - over-riding result from higher priority to (%s %d)",
+ stack->depth, __FUNCTION__,
+ fr_int2str(mod_rcode_table, result, "<invalid>"),
+ priority);
+ }
+
+ /*
+ * Another child has yielded, so we
+ * remember the yield instead of the fact
+ * that we're done.
+ */
+ if (done == CHILD_YIELDED) continue;
+
+ rad_assert(done == CHILD_DONE);
+ break;
+ }
+ }
+
+ /*
+ * Yield if necessary.
+ */
+ if (done == CHILD_YIELDED) {
+ return RLM_MODULE_YIELD;
+ }
+
+ rad_assert(done == CHILD_DONE);
+
+ /*
+ * Clean up all of the child requests, because once we
+ * return, no one can access their data any more.
+ */
+ for (i = 0; i < state->num_children; i++) {
+ switch (state->children[i].state) {
+ case CHILD_RUNNABLE:
+ rad_assert(state->children[i].child->backlog == NULL);
+ rad_assert(state->children[i].child->runnable_id < 0);
+
+ /*
+ * Un-detached children are never in the
+ * runnable queue.
+ */
+ /* FALL-THROUGH */
+
+ case CHILD_YIELDED:
+ REQUEST_VERIFY(state->children[i].child);
+ rad_assert(state->children[i].child->runnable_id < 0);
+
+ /*
+ * Signal the child that it's going to be
+ * stopped. This tells any child modules
+ * to clean up timers, etc.
+ */
+ unlang_signal(state->children[i].child, FR_ACTION_DONE);
+ TALLOC_FREE(state->children[i].child);
+ /* FALL-THROUGH */
+
+ default:
+ state->children[i].state = CHILD_DONE;
+ state->children[i].child = NULL;
+ state->children[i].instruction = NULL;
+ break;
+ }
+ }
+
+ /*
+ * Return the final result. The caller will take care of
+ * free'ing "state".
+ */
+ return state->result;
+}
+
+
+/** Send a signal from parent request to all of it's children
+ *
+ */
+static void unlang_parallel_signal(UNUSED REQUEST *request, void *resume_ctx, fr_state_action_t action)
+{
+ int i;
+ unlang_parallel_t *state = talloc_get_type_abort(resume_ctx, unlang_parallel_t);
+
+ /*
+ * Signal all of the children, if they exist.
+ */
+ for (i = 0; i < state->num_children; i++) {
+ switch (state->children[i].state) {
+ case CHILD_INIT:
+ case CHILD_DONE:
+ break;
+
+ case CHILD_RUNNABLE:
+ case CHILD_YIELDED:
+ rad_assert(state->children[i].child != NULL);
+ unlang_signal(state->children[i].child, action);
+ break;
+ }
+ }
+}
+
+static void unlang_parallel_resumable(REQUEST *request, UNUSED void *resume_ctx)
+{
+ unlang_stack_t *stack;
+ unlang_stack_frame_t *frame;
+ unlang_resume_t *mr;
+ unlang_parallel_t *state;
+ int i;
+
+#ifndef NDEBUG
+ bool found = false;
+#endif
+
+ /*
+ * Child requests CANNOT be runnable. Only the
+ * parent request can be runnable. When it runs
+ * (eventually), the interpreter will walk back
+ * down the stack, resuming anything that needs resuming.
+ */
+ rad_assert(request->backlog == NULL);
+ rad_assert(request->runnable_id < 0);
+
+ /*
+ * Now look at the parents stack. It also must
+ * have been yielded in order for someone to mark
+ * the child as resumable.
+ */
+ stack = request->stack;
+ frame = &stack->frame[stack->depth];
+ rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+
+ mr = unlang_generic_to_resume(frame->instruction);
+ (void) talloc_get_type_abort(mr, unlang_resume_t);
+ state = mr->resume_ctx;
+
+ /*
+ * Find the child and mark it resumable
+ */
+ for (i = 0; i < state->num_children; i++) {
+ if (state->children[i].state != CHILD_YIELDED) continue;
+ if (state->children[i].child != request) continue;
+
+ state->children[i].state = CHILD_RUNNABLE;
+#ifndef NDEBUG
+ found = true;
+#endif
+ break;
+ }
+ rad_assert(found);
+}
+
+static unlang_action_t unlang_parallel_resume(REQUEST *request, rlm_rcode_t *presult, void *resume_ctx)
+{
+ unlang_parallel_t *state = talloc_get_type_abort(resume_ctx, unlang_parallel_t);
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+
+#ifndef NDEBUG
+ unlang_resume_t *mr;
+#endif
+
+ /*
+ * Continue running the child.
+ */
+ *presult = unlang_parallel_run(request, state);
+ if (*presult != RLM_MODULE_YIELD) {
+ rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+
+ frame->instruction->type = UNLANG_TYPE_PARALLEL; /* for debug purposes */
+ talloc_free(state);
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+#ifndef NDEBUG
+ rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+
+ mr = unlang_generic_to_resume(frame->instruction);
+ (void) talloc_get_type_abort(mr, unlang_resume_t);
+
+ rad_assert(mr->callback == NULL);
+ rad_assert(mr->resume_ctx == state);
+#endif
+
+ /*
+ * If the child yields, our current frame is still an
+ * unlang_resume_t.
+ */
+ return UNLANG_ACTION_YIELD;
+}
+
+static unlang_action_t unlang_parallel(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ int i;
+ rlm_rcode_t rcode;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+ unlang_parallel_t *state;
+ unlang_resume_t *mr;
+
+ g = unlang_generic_to_group(instruction);
+
+ if (!g->num_children) {
+ *presult = RLM_MODULE_NOOP;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * Allocate an array for the children.
+ */
+ state = talloc_zero_size(request, sizeof(unlang_parallel_t) + sizeof(state->children[0]) * g->num_children);
+ if (!state) {
+ *presult = RLM_MODULE_FAIL;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ };
+
+ (void) talloc_set_type(state, unlang_parallel_t);
+ state->result = RLM_MODULE_FAIL;
+ state->priority = -1; /* as-yet unset */
+ state->g = g;
+ state->num_children = g->num_children;
+
+ /*
+ * Initialize all of the children.
+ */
+ for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
+ state->children[i].state = CHILD_INIT;
+ state->children[i].instruction = instruction;
+ }
+
+ /*
+ * Reset this...
+ */
+ instruction = frame->instruction;
+
+ /*
+ * Run the various children. On the off chance they're
+ * all done, free things, and return.
+ */
+ rcode = unlang_parallel_run(request, state);
+ if (rcode != RLM_MODULE_YIELD) {
+ talloc_free(state);
+ *presult = rcode;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * Create the "resume" stack frame, and have it replace our stack frame.
+ */
+ mr = unlang_resume_alloc(request, NULL, NULL, state);
+ if (!mr) {
+ *presult = RLM_MODULE_FAIL;
+ *priority = instruction->actions[*presult];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ *presult = RLM_MODULE_YIELD;
+ return UNLANG_ACTION_YIELD;
+}
+
+static unlang_action_t unlang_case(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+
+ g = unlang_generic_to_group(instruction);
+
+ if (!g->children) {
+ *presult = RLM_MODULE_NOOP;
+ *priority = 0;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ return unlang_group(request, presult, priority);
+}
+
+static unlang_action_t unlang_return(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ int i;
+ VALUE_PAIR **copy_p;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+
+ RDEBUG2("%s", unlang_ops[instruction->type].name);
+
+ for (i = 8; i >= 0; i--) {
+ copy_p = request_data_get(request, (void *)radius_get_vp, i);
+ if (copy_p) {
+ if (instruction->type == UNLANG_TYPE_BREAK) {
+ RDEBUG2("# break Foreach-Variable-%d", i);
+ break;
+ }
+ }
+ }
+
+ frame->unwind = instruction->type;
+
+ *presult = frame->result;
+ *priority = frame->priority;
+
+ return UNLANG_ACTION_BREAK;
+}
+
+static unlang_action_t unlang_foreach(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ VALUE_PAIR *vp;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+
+ g = unlang_generic_to_group(instruction);
+
+ if (!frame->repeat) {
+ int i, foreach_depth = -1;
+ VALUE_PAIR *vps;
+
+ if (stack->depth >= UNLANG_STACK_MAX) {
+ ERROR("Internal sanity check failed: module stack is too deep");
+ fr_exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Figure out how deep we are in nesting by looking at request_data
+ * stored previously.
+ *
+ * FIXME: figure this out by walking up the modcall stack instead.
+ */
+ for (i = 0; i < 8; i++) {
+ if (!request_data_reference(request, (void *)radius_get_vp, i)) {
+ foreach_depth = i;
+ break;
+ }
+ }
+
+ if (foreach_depth < 0) {
+ REDEBUG("foreach Nesting too deep!");
+ *presult = RLM_MODULE_FAIL;
+ *priority = 0;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * Copy the VPs from the original request, this ensures deterministic
+ * behaviour if someone decides to add or remove VPs in the set were
+ * iterating over.
+ */
+ if (tmpl_copy_vps(request, &vps, request, g->vpt) < 0) { /* nothing to loop over */
+ *presult = RLM_MODULE_NOOP;
+ *priority = instruction->actions[RLM_MODULE_NOOP];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ rad_assert(vps != NULL);
+ fr_pair_cursor_init(&frame->foreach.cursor, &vps);
+
+ frame->foreach.depth = foreach_depth;
+ frame->foreach.vps = vps;
+#ifndef NDEBUG
+ frame->foreach.indent = request->log.unlang_indent;
+#endif
+
+ vp = fr_pair_cursor_first(&frame->foreach.cursor);
+
+ } else {
+ vp = fr_pair_cursor_next(&frame->foreach.cursor);
+
+ /*
+ * We've been asked to unwind to the
+ * enclosing "foreach". We're here, so
+ * we can stop unwinding.
+ */
+ if (frame->unwind == UNLANG_TYPE_BREAK) {
+ frame->unwind = UNLANG_TYPE_NULL;
+ vp = NULL;
+ }
+
+ /*
+ * Unwind all the way.
+ */
+ if (frame->unwind == UNLANG_TYPE_RETURN) {
+ vp = NULL;
+ }
+
+ if (!vp) {
+ /*
+ * Free the copied vps and the request data
+ * If we don't remove the request data, something could call
+ * the xlat outside of a foreach loop and trigger a segv.
+ */
+ fr_pair_list_free(&frame->foreach.vps);
+ request_data_get(request, (void *)radius_get_vp, frame->foreach.depth);
+
+ *presult = frame->result;
+ if (*presult != RLM_MODULE_UNKNOWN) *priority = instruction->actions[*presult];
+#ifndef NDEBUG
+ rad_assert(frame->foreach.indent == request->log.unlang_indent);
+#endif
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+ }
+
+#ifndef NDEBUG
+ if (DEBUG_ENABLED2) {
+ char buffer[1024];
+
+ fr_pair_value_snprint(buffer, sizeof(buffer), vp, '"');
+ RDEBUG2("");
+ RDEBUG2("# looping with: Foreach-Variable-%d = %s", frame->foreach.depth, buffer);
+ }
+#endif
+
+ /*
+ * Add the vp to the request, so that
+ * xlat.c, xlat_foreach() can find it.
+ */
+ frame->foreach.variable = vp;
+ request_data_add(request, (void *)radius_get_vp, frame->foreach.depth, &frame->foreach.variable,
+ false, false, false);
+
+ /*
+ * Push the child, and yield for a later return.
+ */
+ unlang_push(stack, g->children, frame->result, UNLANG_NEXT_CONTINUE, UNLANG_SUB_FRAME);
+ frame->repeat = true;
+ return UNLANG_ACTION_PUSHED_CHILD;
+}
+
+static unlang_action_t unlang_xlat_inline(REQUEST *request,
+ UNUSED rlm_rcode_t *presult, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_xlat_inline_t *mx = unlang_generic_to_xlat_inline(instruction);
+
+ if (!mx->exec) {
+ TALLOC_CTX *pool;
+ unlang_stack_state_xlat_inline_t *state;
+
+ MEM(frame->state = state = talloc_zero(stack, unlang_stack_state_xlat_inline_t));
+ MEM(pool = talloc_pool(frame->state, 1024)); /* Pool to absorb some allocs */
+
+ unlang_push_xlat(pool, &state->result, request, mx->exp, false);
+ return UNLANG_ACTION_PUSHED_CHILD;
+ } else {
+ RDEBUG("`%s`", mx->xlat_name);
+ radius_exec_program(request, NULL, 0, NULL, request, mx->xlat_name, request->packet->vps,
+ false, true, EXEC_TIMEOUT);
+ return UNLANG_ACTION_CONTINUE;
+ }
+}
+
+static unlang_action_t unlang_switch(REQUEST *request,
+ UNUSED rlm_rcode_t *presult, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_t *this, *found, *null_case;
+ unlang_group_t *g, *h;
+ fr_cond_t cond;
+ fr_value_box_t data;
+ vp_map_t map;
+ vp_tmpl_t vpt;
+
+ g = unlang_generic_to_group(instruction);
+
+ memset(&cond, 0, sizeof(cond));
+ memset(&map, 0, sizeof(map));
+
+ cond.type = COND_TYPE_MAP;
+ cond.data.map = ↦
+
+ map.op = T_OP_CMP_EQ;
+ map.ci = cf_section_to_item(g->cs);
+
+ rad_assert(g->vpt != NULL);
+
+ null_case = found = NULL;
+ data.datum.ptr = NULL;
+
+ /*
+ * The attribute doesn't exist. We can skip
+ * directly to the default 'case' statement.
+ */
+ if ((g->vpt->type == TMPL_TYPE_ATTR) && (tmpl_find_vp(NULL, request, g->vpt) < 0)) {
+ find_null_case:
+ for (this = g->children; this; this = this->next) {
+ rad_assert(this->type == UNLANG_TYPE_CASE);
+
+ h = unlang_generic_to_group(this);
+ if (h->vpt) continue;
+
+ found = this;
+ break;
+ }
+
+ goto do_null_case;
+ }
+
+ /*
+ * Expand the template if necessary, so that it
+ * is evaluated once instead of for each 'case'
+ * statement.
+ */
+ if ((g->vpt->type == TMPL_TYPE_XLAT_STRUCT) ||
+ (g->vpt->type == TMPL_TYPE_XLAT) ||
+ (g->vpt->type == TMPL_TYPE_EXEC)) {
+ char *p;
+ ssize_t len;
+
+ len = tmpl_aexpand(request, &p, request, g->vpt, NULL, NULL);
+ if (len < 0) goto find_null_case;
+ data.vb_strvalue = p;
+ tmpl_init(&vpt, TMPL_TYPE_UNPARSED, data.vb_strvalue, len, T_SINGLE_QUOTED_STRING);
+ }
+
+ /*
+ * Find either the exact matching name, or the
+ * "case {...}" statement.
+ */
+ for (this = g->children; this; this = this->next) {
+ rad_assert(this->type == UNLANG_TYPE_CASE);
+
+ h = unlang_generic_to_group(this);
+
+ /*
+ * Remember the default case
+ */
+ if (!h->vpt) {
+ if (!null_case) null_case = this;
+ continue;
+ }
+
+ /*
+ * If we're switching over an attribute
+ * AND we haven't pre-parsed the data for
+ * the case statement, then cast the data
+ * to the type of the attribute.
+ */
+ if ((g->vpt->type == TMPL_TYPE_ATTR) &&
+ (h->vpt->type != TMPL_TYPE_DATA)) {
+ map.rhs = g->vpt;
+ map.lhs = h->vpt;
+ cond.cast = g->vpt->tmpl_da;
+
+ /*
+ * Remove unnecessary casting.
+ */
+ if ((h->vpt->type == TMPL_TYPE_ATTR) &&
+ (g->vpt->tmpl_da->type == h->vpt->tmpl_da->type)) {
+ cond.cast = NULL;
+ }
+
+ /*
+ * Use the pre-expanded string.
+ */
+ } else if ((g->vpt->type == TMPL_TYPE_XLAT_STRUCT) ||
+ (g->vpt->type == TMPL_TYPE_XLAT) ||
+ (g->vpt->type == TMPL_TYPE_EXEC)) {
+ map.rhs = h->vpt;
+ map.lhs = &vpt;
+ cond.cast = NULL;
+
+ /*
+ * Else evaluate the 'switch' statement.
+ */
+ } else {
+ map.rhs = h->vpt;
+ map.lhs = g->vpt;
+ cond.cast = NULL;
+ }
+
+ if (cond_eval_map(request, RLM_MODULE_UNKNOWN, 0,
+ &cond) == 1) {
+ found = this;
+ break;
+ }
+ }
+
+ if (!found) found = null_case;
+
+do_null_case:
+ talloc_free(data.datum.ptr);
+
+ /*
+ * Nothing found. Just continue, and ignore the "switch"
+ * statement.
+ */
+ if (!found) return UNLANG_ACTION_CONTINUE;
+
+ unlang_push(stack, found, frame->result, UNLANG_NEXT_STOP, UNLANG_SUB_FRAME);
+ return UNLANG_ACTION_PUSHED_CHILD;
+}
+
+
+static unlang_action_t unlang_update(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ int rcode;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g = unlang_generic_to_group(instruction);
+ vp_map_t *map;
+
+ for (map = g->map; map != NULL; map = map->next) {
+ rcode = map_to_request(request, map, map_to_vp, NULL);
+ if (rcode < 0) {
+ *presult = (rcode == -2) ? RLM_MODULE_INVALID : RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+ }
+
+ *presult = RLM_MODULE_NOOP;
+ *priority = instruction->actions[RLM_MODULE_NOOP];
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+
+static unlang_action_t unlang_map(REQUEST *request,
+ rlm_rcode_t *presult, UNUSED int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g = unlang_generic_to_group(instruction);
+
+ *presult = map_proc(request, g->proc_inst);
+
+ return *presult == RLM_MODULE_YIELD ? UNLANG_ACTION_YIELD : UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+
+static unlang_action_t unlang_module_call(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ unlang_module_call_t *sp;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_stack_state_modcall_t *ms;
+
+#ifndef NDEBUG
+ int unlang_indent = request->log.unlang_indent;
+#endif
+
+ /*
+ * Process a stand-alone child, and fall through
+ * to dealing with it's parent.
+ */
+ sp = unlang_generic_to_module_call(instruction);
+ rad_assert(sp);
+
+ RDEBUG4("[%i] %s - %s (%s)", stack->depth, __FUNCTION__,
+ sp->module_instance->name, sp->module_instance->module->name);
+
+ /*
+ * Return administratively configured return code
+ */
+ if (sp->module_instance->force) {
+ *presult = request->rcode = sp->module_instance->code;
+ goto done;
+ }
+
+ frame->state = ms = talloc_zero(stack, unlang_stack_state_modcall_t);
+
+ /*
+ * Grab the thread/module specific data if any exists.
+ */
+ ms->thread = module_thread_instance_find(sp->module_instance);
+ rad_assert(ms->thread != NULL);
+
+ /*
+ * For logging unresponsive children.
+ */
+ request->module = sp->module_instance->name;
+ ms->thread->total_calls++;
+
+ /*
+ * Lock is noop unless instance->mutex is set.
+ */
+ safe_lock(sp->module_instance);
+ *presult = sp->method(sp->module_instance->dl_inst->data, ms->thread->data, request);
+ safe_unlock(sp->module_instance);
+
+ request->module = NULL;
+
+ /*
+ * Is now marked as "stop" when it wasn't before, we must have been blocked.
+ */
+ if (request->master_state == REQUEST_STOP_PROCESSING) {
+ RWARN("Module %s became unblocked",
+ sp->module_instance->module->name);
+ return UNLANG_ACTION_STOP_PROCESSING;
+ }
+
+ if (*presult == RLM_MODULE_YIELD) {
+ ms->thread->active_callers++;
+ } else {
+ rad_assert(unlang_indent == request->log.unlang_indent);
+
+ rad_assert(*presult >= RLM_MODULE_REJECT);
+ rad_assert(*presult < RLM_MODULE_NUMCODES);
+ *priority = instruction->actions[*presult];
+
+ request->rcode = *presult;
+ }
+
+done:
+ RDEBUG2("%s (%s)", instruction->name ? instruction->name : "",
+ fr_int2str(mod_rcode_table, *presult, "<invalid>"));
+
+ return *presult == RLM_MODULE_YIELD ? UNLANG_ACTION_YIELD : UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Send a signal (usually stop) to a request
+ *
+ * This is typically called via an "async" action, i.e. an action
+ * outside of the normal processing of the request.
+ *
+ * If there is no #fr_unlang_module_signal_t callback defined, the action is ignored.
+ *
+ * @param[in] request The current request.
+ * @param[in] resume_ctx createed by #unlang_module_call.
+ * @param[in] action to signal.
+ */
+static void unlang_module_signal(REQUEST *request, void *resume_ctx, fr_state_action_t action)
+{
+ unlang_stack_frame_t *frame;
+ unlang_stack_t *stack = request->stack;
+ unlang_resume_t *mr;
+ unlang_module_call_t *mc;
+
+ unlang_stack_state_modcall_t *ms = NULL;
+
+ rad_assert(stack->depth > 0);
+
+ frame = &stack->frame[stack->depth];
+
+ mr = unlang_generic_to_resume(frame->instruction);
+ if (!mr->signal) return;
+
+ mc = unlang_generic_to_module_call(mr->parent);
+ ms = talloc_get_type_abort(frame->state, unlang_stack_state_modcall_t);
+
+ ((fr_unlang_module_signal_t)mr->signal)(request,
+ mc->module_instance->dl_inst->data, ms->thread->data,
+ resume_ctx, action);
+}
+
+static unlang_action_t unlang_module_resume(REQUEST *request, rlm_rcode_t *presult, UNUSED void *resume_ctx)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_resume_t *mr = unlang_generic_to_resume(instruction);
+ unlang_module_call_t *mc = unlang_generic_to_module_call(mr->parent);
+
+ unlang_stack_state_modcall_t *ms = NULL;
+
+ rad_assert(mr->parent->type == UNLANG_TYPE_MODULE_CALL);
+
+ ms = talloc_get_type_abort(frame->state, unlang_stack_state_modcall_t);
+
+ /*
+ * Lock is noop unless instance->mutex is set.
+ */
+ safe_lock(mc->module_instance);
+ *presult = request->rcode = ((fr_unlang_module_resume_t)mr->callback)(request,
+ mc->module_instance->dl_inst->data,
+ ms->thread->data, mr->resume_ctx);
+ safe_unlock(mc->module_instance);
+
+ if (*presult != RLM_MODULE_YIELD) ms->thread->active_callers--;
+
+ RDEBUG2("%s (%s)", instruction->name ? instruction->name : "",
+ fr_int2str(mod_rcode_table, *presult, "<invalid>"));
+
+ return *presult == RLM_MODULE_YIELD ? UNLANG_ACTION_YIELD : UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+static unlang_action_t unlang_if(REQUEST *request,
+ rlm_rcode_t *presult, int *priority)
+{
+ int condition;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_group_t *g;
+
+ g = unlang_generic_to_group(instruction);
+ rad_assert(g->cond != NULL);
+
+ condition = cond_eval(request, *presult, 0, g->cond);
+ if (condition < 0) {
+ switch (condition) {
+ case -2:
+ REDEBUG("Condition evaluation failed because a referenced attribute "
+ "was not found in the request");
+ break;
+ default:
+ case -1:
+ REDEBUG("Condition evaluation failed because the value of an operand "
+ "could not be determined");
+ break;
+ }
+ condition = 0;
+ }
+
+ /*
+ * Didn't pass. Remember that.
+ */
+ if (!condition) {
+ RDEBUG2("...");
+
+ if (*presult != RLM_MODULE_UNKNOWN) *priority = instruction->actions[*presult];
+
+ return UNLANG_ACTION_CONTINUE;
+ }
+
+ /*
+ * Tell the main interpreter to skip over the else /
+ * elsif blocks, as this "if" condition was taken.
+ */
+ while (frame->next &&
+ ((frame->next->type == UNLANG_TYPE_ELSE) ||
+ (frame->next->type == UNLANG_TYPE_ELSIF))) {
+ frame->next = frame->next->next;
+ }
+
+ /*
+ * We took the "if". Go recurse into its' children.
+ */
+ return unlang_group(request, presult, priority);
+}
+
+
+/** Callback for handling resumption frames
+ *
+ * Resumption frames are added to track when a module, or other construct
+ * has yielded control back to the interpreter.
+ *
+ * This function is called when the request has been marked as resumable
+ * and a resumption frame was previously placed on the stack, i.e. when
+ * the work that caused the request to be yielded initially has completed.
+ *
+ * @param[in] request to be resumed.
+ * @param[out] presult the rcode returned by the resume function.
+ * @param[out] priority associated with the rcode.
+ */
+static unlang_action_t unlang_resume(REQUEST *request, rlm_rcode_t *presult, int *priority)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_resume_t *mr = unlang_generic_to_resume(instruction);
+ unlang_action_t action;
+
+ RDEBUG3("Resuming in %s", mr->self.debug_name);
+
+ if (!unlang_ops[mr->parent->type].resume) {
+ *presult = RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ request->module = mr->self.debug_name;
+
+ /*
+ * Run the resume callback associated with
+ * the original frame which was used to
+ * create this resumption frame.
+ */
+ action = unlang_ops[mr->parent->type].resume(request, presult, mr->resume_ctx);
+
+ request->module = NULL;
+
+ /*
+ * Leave mr alone, it will be freed when the request is done.
+ */
+
+ /*
+ * Is now marked as "stop" when it wasn't before, we must have been blocked.
+ */
+ if (request->master_state == REQUEST_STOP_PROCESSING) {
+ RWARN("Module %s became unblocked", mr->self.debug_name);
+ return UNLANG_ACTION_STOP_PROCESSING;
+ }
+
+ if (*presult != RLM_MODULE_YIELD) {
+ rad_assert(*presult >= RLM_MODULE_REJECT);
+ rad_assert(*presult < RLM_MODULE_NUMCODES);
+ *priority = instruction->actions[*presult];
+ }
+
+ return action;
+}
+
+unlang_op_t unlang_ops[] = {
+ [UNLANG_TYPE_MODULE_CALL] = {
+ .name = "module-call",
+ .func = unlang_module_call,
+ .signal = unlang_module_signal,
+ .resume = unlang_module_resume,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_GROUP] = {
+ .name = "group",
+ .func = unlang_group,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_LOAD_BALANCE] = {
+ .name = "load-balance group",
+ .func = unlang_load_balance,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_REDUNDANT_LOAD_BALANCE] = {
+ .name = "redundant-load-balance group",
+ .func = unlang_redundant_load_balance,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_PARALLEL] = {
+ .name = "parallel",
+ .func = unlang_parallel,
+ .signal = unlang_parallel_signal,
+ .resumable = unlang_parallel_resumable,
+ .resume = unlang_parallel_resume,
+ .debug_braces = true
+ },
+#ifdef WITH_UNLANG
+ [UNLANG_TYPE_IF] = {
+ .name = "if",
+ .func = unlang_if,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_ELSE] = {
+ .name = "else",
+ .func = unlang_group,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_ELSIF] = {
+ .name = "elsif",
+ .func = unlang_if,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_UPDATE] = {
+ .name = "update",
+ .func = unlang_update,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_SWITCH] = {
+ .name = "switch",
+ .func = unlang_switch,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_CASE] = {
+ .name = "case",
+ .func = unlang_case,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_FOREACH] = {
+ .name = "foreach",
+ .func = unlang_foreach,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_BREAK] = {
+ .name = "break",
+ .func = unlang_break,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_RETURN] = {
+ .name = "return",
+ .func = unlang_return,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_MAP] = {
+ .name = "map",
+ .func = unlang_map,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_POLICY] = {
+ .name = "policy",
+ .func = unlang_policy,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_SUBREQUEST] = {
+ .name = "subrequest",
+ .func = unlang_subrequest,
+ .signal = unlang_subrequest_signal,
+ .resume = unlang_subrequest_resume,
+ .debug_braces = true
+ },
+ [UNLANG_TYPE_DETACH] = {
+ .name = "detach",
+ .func = unlang_detach,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_CALL] = {
+ .name = "call",
+ .func = unlang_call,
+ .debug_braces = true
+ },
+#endif
+ [UNLANG_TYPE_XLAT_INLINE] = {
+ .name = "xlat_inline",
+ .func = unlang_xlat_inline,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_XLAT] = {
+ .name = "xlat_eval",
+ .func = unlang_xlat,
+ .resume = unlang_xlat_resume,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_RESUME] = {
+ .name = "resume",
+ .func = unlang_resume,
+ .debug_braces = false
+ },
+ [UNLANG_TYPE_MAX] = { NULL, NULL, NULL, NULL, false }
+};