]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Last remaining exec issues (hopefully)
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 30 Aug 2021 16:58:03 +0000 (11:58 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 30 Aug 2021 16:58:12 +0000 (11:58 -0500)
src/lib/server/exec.c
src/lib/server/exec.h
src/lib/server/state_test.c [new file with mode: 0644]
src/lib/unlang/map.c
src/lib/unlang/module.c
src/lib/unlang/module.h
src/lib/unlang/tmpl.c
src/lib/unlang/tmpl.h
src/lib/unlang/tmpl_priv.h
src/modules/rlm_exec/rlm_exec.c

index 152325ebebb6ea6d321e4e0fd1dac0c59f29f178..d8759b192df1068e5e0bf1e21720375d9e4a988b 100644 (file)
@@ -951,13 +951,15 @@ void fr_exec_cleanup(fr_exec_state_t *exec, int signal)
 /*
  *     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
@@ -975,14 +977,19 @@ static void exec_waitpid(fr_event_list_t *el, UNUSED pid_t pid, int status, void
         */
        } 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);
@@ -1190,7 +1197,8 @@ static void exec_stdout_read(UNUSED fr_event_list_t *el, int fd, int flags, void
  */
 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;
index f3963a04ba092a7c2698c96650bae2a15da088a5..2a01a91a8bb80d9ac76e8be225765d110ea58686 100644 (file)
@@ -70,7 +70,6 @@ typedef struct {
        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
 
@@ -99,7 +98,8 @@ int   fr_exec_wait_start(pid_t *pid_p, int *stdin_fd, int *stdout_fd, int *stderr_
 
 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
 }
diff --git a/src/lib/server/state_test.c b/src/lib/server/state_test.c
new file mode 100644 (file)
index 0000000..ea16218
--- /dev/null
@@ -0,0 +1,28 @@
+#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 }
+};
index 2bdadb5f0c50bb6cb34c24897359e1e9f0915fb2..7e6cfe7e7b109c4a06ed4ce5c4888d82da19618e 100644 (file)
@@ -53,7 +53,9 @@ typedef struct {
        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;
@@ -146,7 +148,8 @@ static unlang_action_t list_mod_create(rlm_rcode_t *p_result, request_t *request
 
                        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;
                                }
@@ -189,7 +192,7 @@ static unlang_action_t list_mod_create(rlm_rcode_t *p_result, request_t *request
 
                        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;
                                }
@@ -343,7 +346,7 @@ static unlang_action_t unlang_map_state_init(rlm_rcode_t *p_result, request_t *r
        }
        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;
                }
index 75e6d6004ac8d367128a9de7d4fcc45e13210436..a8e740614a2f544cdd4bd2403cd49fc47ec88422 100644 (file)
@@ -471,19 +471,19 @@ unlang_action_t unlang_module_yield_to_xlat(TALLOC_CTX *ctx, fr_value_box_list_t
  *
  * @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)
 {
@@ -496,7 +496,9 @@ unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t
        /*
         *      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;
 }
index ec6ece1eb919ed9a58bca953358dd00b72f5a3bb..f4ced552f787325b16f4ecf6cb26543515b00ee9 100644 (file)
@@ -33,6 +33,7 @@ extern "C" {
 #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
  *
@@ -129,9 +130,9 @@ unlang_action_t     unlang_module_yield_to_xlat(TALLOC_CTX *ctx, fr_value_box_list_t
                                            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);
 
index 861426d70e8f2e0f12a51a4e8aff71446a45c354..ba6e916e2bec85292ab7e7445f6c405b29e394ff 100644 (file)
@@ -69,72 +69,6 @@ static void unlang_tmpl_signal(request_t *request, unlang_stack_frame_t *frame,
        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
@@ -172,9 +106,9 @@ static unlang_action_t unlang_tmpl_exec_wait_final(rlm_rcode_t *p_result, reques
                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;
        }
@@ -184,6 +118,10 @@ static unlang_action_t unlang_tmpl_exec_wait_final(rlm_rcode_t *p_result, reques
         *      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;
@@ -208,9 +146,9 @@ static unlang_action_t unlang_tmpl_exec_wait_final(rlm_rcode_t *p_result, reques
 
 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.
@@ -228,13 +166,11 @@ static unlang_action_t unlang_tmpl_exec_wait_resume(rlm_rcode_t *p_result, reque
 {
        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;
        }
@@ -351,6 +287,76 @@ static unlang_action_t unlang_tmpl(rlm_rcode_t *p_result, request_t *request, un
        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)
 {
index d040efb578a53c5f3500c3446cfbfe3bf6fb203d..fbf145bf899cda32d187c82b41b9b4ffdf236a78 100644 (file)
@@ -34,6 +34,55 @@ extern "C" {
 #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
@@ -58,7 +107,7 @@ typedef void (*fr_unlang_tmpl_signal_t)(request_t *request, void *rctx, fr_state
 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
index 39410d28da92b0f3d6b93c5e0882fab0b392c8d3..08ef6b1c58b9068ea8c61efb08918271c7f40149 100644 (file)
@@ -46,6 +46,9 @@ typedef struct {
        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
index 5aa454412e167ccc7f59a02813ca42a31cf2f0b6..66578cefa1c4e73c6f9e4138b1cbee34924aacbb 100644 (file)
@@ -140,7 +140,8 @@ static xlat_action_t exec_xlat(TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, reques
        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;
@@ -373,24 +374,37 @@ static unlang_action_t mod_exec_wait_resume(rlm_rcode_t *p_result, module_ctx_t
        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;
@@ -404,7 +418,7 @@ static unlang_action_t mod_exec_wait_resume(rlm_rcode_t *p_result, module_ctx_t
         *      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);
 }
 
 /*
@@ -451,9 +465,13 @@ static unlang_action_t CC_HINT(nonnull) mod_exec_dispatch(rlm_rcode_t *p_result,
                }
        }
 
-       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);
 }