/*
* Callback when exec has completed. Record the status and tidy up.
*/
-static void exec_waitpid(fr_event_list_t *el, UNUSED pid_t pid, int status, void *uctx)
+static void exec_waitpid(fr_event_list_t *el, pid_t pid, int status, void *uctx)
{
fr_exec_state_t *exec = uctx; /* may not be talloced */
request_t *request = exec->request;
- int wait_status;
+ int wait_status = 0;
int ret;
+ if (!fr_cond_assert(pid == exec->pid)) RWDEBUG("Event PID %u and exec->pid %u do not match", pid, exec->pid);
+
/*
* Reap the process. This is needed so the processes
* don't stick around indefinitely. libkqueue/kqueue
*/
} else if (ret == 0) {
RWDEBUG("Something reaped PID %d before us!", exec->pid);
+ wait_status = status;
}
+ /*
+ * kevent should be returning an identical status value
+ * to waitpid.
+ */
+ if (wait_status != status) RWDEBUG("Exit status from waitpid (%d) and kevent (%d) disagree",
+ wait_status, status);
+
if (WIFEXITED(wait_status)) {
RDEBUG("Program exited with status code %d", WEXITSTATUS(wait_status));
exec->status = WEXITSTATUS(wait_status);
-
- if (exec->status != status) RWDEBUG("Exit status from waitpid (%d) and kevent (%d) disagree",
- exec->status, status);
} else if (WIFSIGNALED(wait_status)) {
RDEBUG("Program exited due to signal with status code %d", WTERMSIG(wait_status));
exec->status = -WTERMSIG(wait_status);
*/
int fr_exec_wait_start_io(TALLOC_CTX *ctx, fr_exec_state_t *exec, request_t *request,
fr_value_box_list_t *cmd, fr_pair_list_t *env_pairs,
- bool need_stdin, bool store_stdout, TALLOC_CTX *stdout_ctx,
+ bool need_stdin,
+ bool store_stdout, TALLOC_CTX *stdout_ctx,
fr_time_delta_t timeout)
{
int *stdout_fd = (store_stdout || RDEBUG_ENABLED2) ? &exec->stdout_fd : NULL;
bool failed; //!< due to exec timeout or buffer overflow
int status; //!< return code of the program
- int *status_p; //!< where we write the return status of the program
fr_pair_list_t *vps; //!< input VPs
int fr_exec_wait_start_io(TALLOC_CTX *ctx, fr_exec_state_t *exec, request_t *request,
fr_value_box_list_t *vb_list, fr_pair_list_t *env_pairs,
- bool need_stdin, bool store_stdout, TALLOC_CTX *stdout_ctx,
+ bool need_stdin,
+ bool store_stdout, TALLOC_CTX *stdout_ctx,
fr_time_delta_t timeout);
#ifdef __cplusplus
}
--- /dev/null
+#include <freeradius-devel/util/acutest.h>
+
+fr_time_t test_time;
+
+/** Allow us to arbitrarily manipulate time
+ *
+ */
+#define fr_time() test_time
+
+#include <state_test.c>
+
+/** Test functions that read from dbuffs.
+ *
+ */
+static void state_entry_create(void)
+{
+
+}
+
+TEST_LIST = {
+ /*
+ * Basic tests
+ */
+ { "state_entry_create", state_entry_create },
+ { "state_entry_too_many" }
+
+ { NULL }
+};
fr_dlist_head_t vlm_head; //!< Head of list of VP List Mod.
fr_value_box_list_t lhs_result; //!< Result of expanding the LHS
+ int lhs_exec_status; //!< status of program on LHS.
fr_value_box_list_t rhs_result; //!< Result of expanding the RHS.
+ int rhs_exec_status; //!< status of program on RHS.
unlang_update_state_t state; //!< What we're currently doing.
} unlang_frame_state_update_t;
case TMPL_TYPE_EXEC:
if (unlang_tmpl_push(update_state, &update_state->lhs_result,
- request, map->lhs, NULL, NULL) < 0) {
+ request, map->lhs,
+ NULL) < 0) {
*p_result = RLM_MODULE_FAIL;
return UNLANG_ACTION_STOP_PROCESSING;
}
case TMPL_TYPE_EXEC:
if (unlang_tmpl_push(update_state, &update_state->rhs_result,
- request, map->rhs, NULL, NULL) < 0) {
+ request, map->rhs, NULL) < 0) {
*p_result = RLM_MODULE_FAIL;
return UNLANG_ACTION_STOP_PROCESSING;
}
}
case TMPL_TYPE_EXEC:
if (unlang_tmpl_push(map_proc_state, &map_proc_state->src_result,
- request, inst->src, NULL, NULL) < 0) {
+ request, inst->src, NULL) < 0) {
*p_result = RLM_MODULE_FAIL;
return UNLANG_ACTION_STOP_PROCESSING;
}
*
* @param[in] ctx To allocate talloc value boxes and values in.
* @param[out] out Where to write the result of the expansion.
- * @param[out] status exit status of the program
* @param[in] request The current request.
* @param[in] vpt the tmpl to expand
- * @param[in] vps the input VPs. May be NULL
+ * @param[in] args Arguments which control how to evaluate the various
+ * types of xlats.
* @param[in] resume function to call when the XLAT expansion is complete.
* @param[in] signal function to call if a signal is received.
* @param[in] rctx to pass to the resume() and signal() callbacks.
* @return
* - UNLANG_ACTION_YIELD
*/
-unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t *out, int *status,
+unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t *out,
request_t *request, tmpl_t const *vpt,
- fr_pair_list_t *vps,
+ unlang_tmpl_args_t *args,
unlang_module_resume_t resume,
unlang_module_signal_t signal, void *rctx)
{
/*
* Push the xlat function
*/
- if (unlang_tmpl_push(ctx, out, request, vpt, vps, status) < 0) return UNLANG_ACTION_STOP_PROCESSING;
+ if (unlang_tmpl_push(ctx, out, request, vpt, args) < 0) {
+ return UNLANG_ACTION_STOP_PROCESSING;
+ }
return UNLANG_ACTION_PUSHED_CHILD;
}
#include <freeradius-devel/server/module.h>
#include <freeradius-devel/server/rcode.h>
#include <freeradius-devel/unlang/subrequest.h>
+#include <freeradius-devel/unlang/tmpl.h>
/** A callback when the the timeout occurs
*
unlang_module_resume_t resume,
unlang_module_signal_t signal, void *rctx);
-unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t *out, int *status,
- request_t *request, tmpl_t const *exp,
- fr_pair_list_t *vps,
+unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t *out,
+ request_t *request, tmpl_t const *vpt,
+ unlang_tmpl_args_t *args,
unlang_module_resume_t resume,
unlang_module_signal_t signal, void *rctx);
if (action == FR_SIGNAL_CANCEL) state->signal = NULL;
}
-/** Push a tmpl onto the stack for evaluation
- *
- * @param[in] ctx To allocate value boxes and values in.
- * @param[out] out The value_box created from the tmpl. May be NULL,
- * in which case the result is discarded.
- * @param[in] request The current request.
- * @param[in] tmpl the tmpl to expand
- * @param[in] vps the input VPs. May be NULL. Used only for #TMPL_TYPE_EXEC
- * @param[out] status where the status of exited programs will be stored.
- */
-int unlang_tmpl_push(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *tmpl, fr_pair_list_t *vps, int *status)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame;
- unlang_frame_state_tmpl_t *state;
-
- unlang_tmpl_t *ut;
-
- static unlang_t tmpl_instruction = {
- .type = UNLANG_TYPE_TMPL,
- .name = "tmpl",
- .debug_name = "tmpl",
- .actions = {
- .actions = {
- [RLM_MODULE_REJECT] = 0,
- [RLM_MODULE_FAIL] = 0,
- [RLM_MODULE_OK] = 0,
- [RLM_MODULE_HANDLED] = 0,
- [RLM_MODULE_INVALID] = 0,
- [RLM_MODULE_DISALLOW] = 0,
- [RLM_MODULE_NOTFOUND] = 0,
- [RLM_MODULE_NOOP] = 0,
- [RLM_MODULE_UPDATED] = 0
- },
- .retry = RETRY_INIT,
- },
- };
-
- MEM(ut = talloc(stack, unlang_tmpl_t));
- *ut = (unlang_tmpl_t){
- .self = tmpl_instruction,
- .tmpl = tmpl
- };
-
- /*
- * Push a new tmpl frame onto the stack
- */
- if (unlang_interpret_push(request, unlang_tmpl_to_generic(ut),
- RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, false) < 0) return -1;
-
- frame = &stack->frame[stack->depth];
- state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
-
- *state = (unlang_frame_state_tmpl_t) {
- .out = out,
- .ctx = ctx,
- .exec = {
- .vps = vps,
- .status_p = status,
- }
- };
- fr_value_box_list_init(&state->box);
-
- return 0;
-}
-
/** Wrapper to call a resumption function after a tmpl has been expanded
*
* If the resumption function returns YIELD, then this function is
goto resume;
}
- fr_assert(state->exec.pid < 0);
+ fr_assert(state->exec.pid < 0); /* Assert this has been cleaned up */
- if (state->exec.status != 0) {
+ if (!state->args.exec.stdout_on_error && (state->exec.status != 0)) {
fr_assert(fr_dlist_empty(&state->box));
goto resume;
}
* and not care about the output.
*
* If we do care about the output, it's unquoted, and tainted.
+ *
+ * FIXME - It would be much more efficient to just reparent
+ * the string buffer into the context of the box... but we'd
+ * need to fix talloc first.
*/
if (state->out) {
fr_type_t type = FR_TYPE_STRING;
resume:
/*
- * Save the *mangled* exit status, not the raw one.
+ * Inform the caller of the status if it asked for it
*/
- if (state->exec.status_p) *state->exec.status_p = state->exec.status;
+ if (state->args.exec.status_out) *state->args.exec.status_out = state->exec.status;
/*
* Ensure that the callers resume function is called.
{
unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
- /*
- * @todo - make the timeout configurable
- */
- if (fr_exec_wait_start_io(state->ctx, &state->exec, request, &state->box,
- state->exec.vps,
- false, (state->out != NULL), state,
- fr_time_delta_from_sec(EXEC_TIMEOUT)) < 0) {
+ if (fr_exec_wait_start_io(state->ctx, &state->exec, request,
+ &state->box, state->args.exec.env,
+ false,
+ (state->out != NULL), state,
+ state->args.exec.timeout) < 0) {
*p_result = RLM_MODULE_FAIL;
return UNLANG_ACTION_CALCULATE_RESULT;
}
return UNLANG_ACTION_PUSHED_CHILD;
}
+/** Push a tmpl onto the stack for evaluation
+ *
+ * @param[in] ctx To allocate value boxes and values in.
+ * @param[out] out The value_box created from the tmpl. May be NULL,
+ * in which case the result is discarded.
+ * @param[in] request The current request.
+ * @param[in] tmpl the tmpl to expand
+ * @param[in] args where the status of exited programs will be stored.
+ * Used only for #TMPL_TYPE_EXEC.
+ */
+int unlang_tmpl_push(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request,
+ tmpl_t const *tmpl, unlang_tmpl_args_t *args)
+{
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame;
+ unlang_frame_state_tmpl_t *state;
+
+ unlang_tmpl_t *ut;
+
+ static unlang_t tmpl_instruction = {
+ .type = UNLANG_TYPE_TMPL,
+ .name = "tmpl",
+ .debug_name = "tmpl",
+ .actions = {
+ .actions = {
+ [RLM_MODULE_REJECT] = 0,
+ [RLM_MODULE_FAIL] = 0,
+ [RLM_MODULE_OK] = 0,
+ [RLM_MODULE_HANDLED] = 0,
+ [RLM_MODULE_INVALID] = 0,
+ [RLM_MODULE_DISALLOW] = 0,
+ [RLM_MODULE_NOTFOUND] = 0,
+ [RLM_MODULE_NOOP] = 0,
+ [RLM_MODULE_UPDATED] = 0
+ },
+ .retry = RETRY_INIT,
+ },
+ };
+
+ MEM(ut = talloc(stack, unlang_tmpl_t));
+ *ut = (unlang_tmpl_t){
+ .self = tmpl_instruction,
+ .tmpl = tmpl
+ };
+
+ /*
+ * Push a new tmpl frame onto the stack
+ */
+ if (unlang_interpret_push(request, unlang_tmpl_to_generic(ut),
+ RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, false) < 0) return -1;
+
+ frame = &stack->frame[stack->depth];
+ state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
+
+ *state = (unlang_frame_state_tmpl_t) {
+ .out = out,
+ .ctx = ctx,
+ };
+ if (args) state->args = *args; /* Copy these because they're usually ephemeral/initialised as compound literal */
+
+ /*
+ * Default to something sensible
+ * instead of locking the same indefinitely.
+ */
+ if (!state->args.exec.timeout) state->args.exec.timeout = fr_time_delta_from_sec(EXEC_TIMEOUT);
+
+ fr_value_box_list_init(&state->box);
+
+ return 0;
+}
void unlang_tmpl_init(void)
{
#include <freeradius-devel/server/rcode.h>
#include <freeradius-devel/unlang/tmpl.h>
+/** Flags that show the type of arguments included
+ *
+ */
+typedef enum {
+ UNLANG_TMPL_ARGS_TYPE_EXEC = 1, //!< We have arguments for performing an exec.
+} unlang_tmpl_args_type_t;
+
+/** Arguments for evaluating different types of tmpls
+ *
+ */
+typedef struct {
+ unlang_tmpl_args_type_t type; //!< Flags field showing which argument structs
+ ///< were explicitly populated. Can help with
+ ///< setting defaults.
+
+ /** Exec specific arguments
+ *
+ */
+ struct {
+ fr_pair_list_t *env; //!< Environmental variables.
+ int *status_out; //!< Where to write the exit code or fatal signal
+ ///< that killed the process.
+ fr_time_delta_t timeout; //!< How long to wait for the process to finish.
+ bool stdout_on_error; //!< If true don't clear stdout if we get a non-zero
+ ///< status code. This allows more nuanced
+ ///< interpretation of status codes.
+ } exec;
+} unlang_tmpl_args_t;
+
+/** Create a temporary argument structure for evaluating an exec type tmpl
+ *
+ * @param[in] _env Environmental variables specified as a pair list.
+ * @param[in] _timeout How long to wait for program to complete.
+ * @param[in] _stdout_on_error If true we keep stdout even on non-zero status code.
+ * @param[out] _status_out Where to store the exit code of the program.
+ * - Will be positive if it's an exit code.
+ * - Will be negative if it's a fatal signal.
+ */
+#define TMPL_ARGS_EXEC(_env, _timeout, _stdout_on_error, _status_out) \
+ &(unlang_tmpl_args_t){ \
+ .type = UNLANG_TMPL_ARGS_TYPE_EXEC, \
+ .exec = { \
+ .env = _env, \
+ .timeout = _timeout, \
+ .stdout_on_error = _stdout_on_error, \
+ .status_out = _status_out \
+ }, \
+ }
+
/** A callback when the request gets a fr_state_signal_t.
*
* A module may call unlang_yeild(), but still need to do something on FR_SIGNAL_DUP. If so, it's
typedef unlang_action_t (*fr_unlang_tmpl_resume_t)(rlm_rcode_t *p_result, request_t *request, void *rctx);
int unlang_tmpl_push(TALLOC_CTX *ctx, fr_value_box_list_t *out,
- request_t *request, tmpl_t const *tmpl, fr_pair_list_t *vps, int *status)
+ request_t *request, tmpl_t const *tmpl, unlang_tmpl_args_t *args)
CC_HINT(warn_unused_result);
#ifdef __cplusplus
union {
fr_exec_state_t exec;
};
+
+ unlang_tmpl_args_t args; //!< Arguments that control how the
+ ///< tmpl is evaluated.
} unlang_frame_state_tmpl_t;
#ifdef __cplusplus
MEM(exec = talloc_zero(request, fr_exec_state_t)); /* Fixme - Should be frame ctx */
if (fr_exec_wait_start_io(exec, exec, request, in, input_pairs,
- false, inst->wait, ctx,
+ false,
+ inst->wait, ctx,
inst->timeout) < 0) {
talloc_free(exec);
return XLAT_ACTION_FAIL;
int status;
rlm_exec_ctx_t *m = talloc_get_type_abort(rctx, rlm_exec_ctx_t);
rlm_exec_t const *inst = talloc_get_type_abort_const(mctx->instance, rlm_exec_t);
+ rlm_rcode_t rcode;
- if (inst->output && !fr_dlist_empty(&m->box)) {
- TALLOC_CTX *ctx;
- fr_pair_list_t vps, *output_pairs;
- fr_value_box_t *box = fr_dlist_head(&m->box);
+ /*
+ * Also prints stdout as an error if there was any...
+ */
+ rcode = rlm_exec_status2rcode(request, fr_dlist_head(&m->box), m->status);
+ switch (rcode) {
+ case RLM_MODULE_OK:
+ case RLM_MODULE_UPDATED:
+ if (inst->output && !fr_dlist_empty(&m->box)) {
+ TALLOC_CTX *ctx;
+ fr_pair_list_t vps, *output_pairs;
+ fr_value_box_t *box = fr_dlist_head(&m->box);
- RDEBUG("EXEC GOT -- %pM", &m->box);
+ RDEBUG("EXEC GOT -- %pM", &m->box);
- fr_pair_list_init(&vps);
- output_pairs = tmpl_list_head(request, inst->output_list);
- fr_assert(output_pairs != NULL);
+ fr_pair_list_init(&vps);
+ output_pairs = tmpl_list_head(request, inst->output_list);
+ fr_assert(output_pairs != NULL);
- ctx = tmpl_list_ctx(request, inst->output_list);
+ ctx = tmpl_list_ctx(request, inst->output_list);
- fr_pair_list_afrom_box(ctx, &vps, request->dict, box);
- if (!fr_pair_list_empty(&vps)) fr_pair_list_move(output_pairs, &vps, T_OP_ADD);
+ fr_pair_list_afrom_box(ctx, &vps, request->dict, box);
+ if (!fr_pair_list_empty(&vps)) fr_pair_list_move(output_pairs, &vps, T_OP_ADD);
+
+ fr_dlist_talloc_free(&m->box); /* has been consumed */
+ }
+ break;
- fr_dlist_talloc_free(&m->box); /* has been consumed */
+ default:
+ break;
}
status = m->status;
* The status rcodes aren't quite the same as the rcode
* enumeration.
*/
- RETURN_MODULE_RCODE(rlm_exec_status2rcode(request, fr_dlist_head(&m->box), status));
+ RETURN_MODULE_RCODE(rcode);
}
/*
}
}
- m = talloc_zero(ctx, rlm_exec_ctx_t);
+ MEM(m = talloc_zero(ctx, rlm_exec_ctx_t));
fr_value_box_list_init(&m->box);
- return unlang_module_yield_to_tmpl(m, &m->box, &m->status, request, inst->tmpl, env_pairs, mod_exec_wait_resume, NULL, &m->box);
+ return unlang_module_yield_to_tmpl(m, &m->box,
+ request, inst->tmpl,
+ TMPL_ARGS_EXEC(env_pairs, 0, true, &m->status),
+ mod_exec_wait_resume,
+ NULL, &m->box);
}