The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
tmpl.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: c005709578a4e4c550cc078c1dd584e1ae75dc39 $
19 *
20 * @file unlang/tmpl.c
21 * @brief Defines functions for calling tmpl__t asynchronously
22 *
23 * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24 * @copyright 2020 Network RADIUS SAS (legal@networkradius.com)
25 */
26RCSID("$Id: c005709578a4e4c550cc078c1dd584e1ae75dc39 $")
27
28#include <freeradius-devel/unlang/tmpl.h>
29#include <freeradius-devel/server/exec.h>
30#include <freeradius-devel/util/syserror.h>
31#include <freeradius-devel/unlang/mod_action.h>
32#include "tmpl_priv.h"
33#include <signal.h>
34
35#if defined(__linux__) || defined(__FreeBSD__)
36#include <sys/wait.h>
37#endif
38
39/** Send a signal (usually stop) to a request
40 *
41 * This is typically called via an "async" action, i.e. an action
42 * outside of the normal processing of the request.
43 *
44 * If there is no #fr_unlang_tmpl_signal_t callback defined, the action is ignored.
45 *
46 * @param[in] request The current request.
47 * @param[in] frame being signalled.
48 * @param[in] action to signal.
49 */
50static void unlang_tmpl_signal(request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
51{
52 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state,
54
55 /*
56 * If we're cancelled, then kill any child processes
57 */
58 if ((action == FR_SIGNAL_CANCEL) && state->exec_result.request) fr_exec_oneshot_cleanup(&state->exec_result, SIGKILL);
59
60 if (!state->signal) return;
61
62 state->signal(request, state->rctx, action);
63
64 /*
65 * If we're cancelled then disable this signal handler.
66 * fr_exec_oneshot_cleanup should handle being called spuriously.
67 */
68 if (action == FR_SIGNAL_CANCEL) state->signal = NULL;
69}
70
71/** Wrapper to call a resumption function after a tmpl has been expanded
72 *
73 * If the resumption function returns YIELD, then this function is
74 * called repeatedly until the resumption function returns a final
75 * value.
76 */
78{
79 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
81
82 if (tmpl_eval_cast_in_place(&state->list, request, ut->tmpl) < 0) {
83 RPEDEBUG("Failed casting expansion");
85 }
86
87 if (state->out) fr_value_box_list_move(state->out, &state->list);
88
89 if (state->resume) return state->resume(p_result, request, state->rctx);
90
92}
93
94/** Wrapper to call exec after the program has finished executing
95 *
96 */
99{
100 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state,
102
103 /*
104 * The exec failed for some internal reason. We don't
105 * care about output, and we don't care about the programs exit status.
106 */
107 if (state->exec_result.failed) {
108 fr_value_box_list_talloc_free(&state->list);
109 goto resume;
110 }
111
112 fr_assert(state->exec_result.pid < 0); /* Assert this has been cleaned up */
113
114 if (!state->args.exec.stdout_on_error && (state->exec_result.status != 0)) {
115 fr_assert(fr_value_box_list_empty(&state->list));
116 goto resume;
117 }
118
119 /*
120 * We might want to just get the status of the program,
121 * and not care about the output.
122 *
123 * If we do care about the output, it's unquoted, and tainted.
124 *
125 * FIXME - It would be much more efficient to just reparent
126 * the string buffer into the context of the box... but we'd
127 * need to fix talloc first.
128 */
129 if (state->out) {
131 fr_value_box_t *box;
132
133 /*
134 * Remove any trailing LF / CR
135 */
136 fr_sbuff_trim(&state->exec_result.stdout_buff, sbuff_char_line_endings);
137
138 fr_value_box_list_init(&state->list);
139 MEM(box = fr_value_box_alloc(state->ctx, FR_TYPE_STRING, NULL));
140 if (fr_value_box_from_str(state->ctx, box, type, NULL,
141 fr_sbuff_start(&state->exec_result.stdout_buff),
142 fr_sbuff_used(&state->exec_result.stdout_buff),
143 NULL) < 0) {
144 talloc_free(box);
146 }
147 fr_value_box_list_insert_head(&state->list, box);
148 }
149
150resume:
151 /*
152 * Inform the caller of the status if it asked for it
153 */
154 if (state->args.exec.status_out) *state->args.exec.status_out = state->exec_result.status;
155
156 /*
157 * Ensure that the callers resume function is called.
158 */
160 return unlang_tmpl_resume(p_result, request, frame);
161}
162
163/** Wrapper to call after an xlat has been expanded
164 *
165 */
168{
169 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
170
171 if (!XLAT_RESULT_SUCCESS(&state->xlat_result)) RETURN_UNLANG_FAIL;
172
173 /*
174 * Ensure that the callers resume function is called.
175 */
177 return unlang_tmpl_resume(p_result, request, frame);
178}
179
180
181/** Wrapper to call exec after a tmpl has been expanded
182 *
183 */
186{
187 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
188
189 if (fr_exec_oneshot(state->ctx, &state->exec_result, request,
190 &state->list,
191 state->args.exec.env, false, false,
192 false,
193 (state->out != NULL), state,
194 state->args.exec.timeout) < 0) {
195 RPEDEBUG("Failed executing program");
197 }
198
199 fr_value_box_list_talloc_free(&state->list); /* this is the xlat expansion, and not the output string we want */
201
202 return UNLANG_ACTION_YIELD;
203}
204
205
207{
208 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
210
211 /*
212 * If we're not called from unlang_tmpl_push(), then
213 * ensure that we clean up the resulting value boxes
214 * and that the list to write the boxes in is initialised.
215 */
216 if (!state->ctx) {
217 state->ctx = state;
218 fr_value_box_list_init(&state->list);
219 }
220
221 /*
222 * Synchronous tmpls can just be resolved immediately, and directly to the output list.
223 *
224 * However, xlat expansions (including fully synchronous function calls!) need to be expanded by
225 * the xlat framework.
226 */
227 if (!tmpl_async_required(ut->tmpl) && !tmpl_contains_xlat(ut->tmpl)) {
228 if (tmpl_eval(state->ctx, state->out, request, ut->tmpl) < 0) {
229 RPEDEBUG("Failed evaluating expansion");
230 goto fail;
231 }
232
234 }
235
236 /*
237 * XLAT structs are allowed.
238 */
239 if (tmpl_is_xlat(ut->tmpl)) {
241 goto push;
242 }
243
245
246 /*
247 * Expand the arguments to the program we're executing.
248 */
250push:
251 if (unlang_xlat_push(state->ctx, &state->xlat_result, &state->list, request, tmpl_xlat(ut->tmpl), UNLANG_SUB_FRAME) < 0) {
252 fail:
254 }
255
257}
258
259/** Push a tmpl onto the stack for evaluation
260 *
261 * @param[in] ctx To allocate value boxes and values in.
262 * @param[out] p_result The frame result
263 * @param[out] out The value_box created from the tmpl. May be NULL,
264 * in which case the result is discarded.
265 * @param[in] request The current request.
266 * @param[in] tmpl the tmpl to expand
267 * @param[in] args additional controls for expanding #TMPL_TYPE_EXEC,
268 * and where the status of exited programs will be stored.
269 * @return
270 * - 0 on success
271 * - -1 on failure
272 */
273int unlang_tmpl_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, request_t *request,
274 tmpl_t const *tmpl, unlang_tmpl_args_t *args)
275{
276 unlang_stack_t *stack = request->stack;
279
280 unlang_tmpl_t *ut;
281
282 static unlang_t const tmpl_instruction_return = {
284 .name = "tmpl",
285 .debug_name = "tmpl",
286 .actions = {
287 .actions = {
288 [RLM_MODULE_REJECT] = 0,
290 [RLM_MODULE_OK] = 0,
291 [RLM_MODULE_HANDLED] = 0,
292 [RLM_MODULE_INVALID] = 0,
295 [RLM_MODULE_NOOP] = 0,
297 },
298 .retry = RETRY_INIT,
299 },
300 };
301
302 static const unlang_t tmpl_instruction_fail = {
304 .name = "tmpl",
305 .debug_name = "tmpl",
306 .actions = DEFAULT_MOD_ACTIONS,
307 };
308
309 if (tmpl_needs_resolving(tmpl)) {
310 REDEBUG("Expansion \"%pV\" needs to be resolved before it is used", fr_box_strvalue_len(tmpl->name, tmpl->len));
311 return -1;
312 }
313
314 /*
315 * Avoid an extra stack frame and more work. But only if the caller hands us a result.
316 * Otherwise, we have to return UNLANG_FAIL.
317 */
318 if (p_result && (tmpl_rules_cast(tmpl) == FR_TYPE_NULL) && tmpl_is_xlat(tmpl)) {
319 return unlang_xlat_push(ctx, p_result, out, request, tmpl_xlat(tmpl), UNLANG_SUB_FRAME);
320 }
321
323
324 MEM(ut = talloc(stack, unlang_tmpl_t));
325 *ut = (unlang_tmpl_t){
326 .self = p_result ? tmpl_instruction_fail : tmpl_instruction_return,
327 .tmpl = tmpl
328 };
329
330 /*
331 * Push a new tmpl frame onto the stack
332 */
333 if (unlang_interpret_push(p_result, request, unlang_tmpl_to_generic(ut),
334 FRAME_CONF(RLM_MODULE_NOT_SET, false), UNLANG_NEXT_STOP) < 0) return -1;
335
336 frame = &stack->frame[stack->depth];
337 state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
338
339 /*
340 * Set the frame as repeatable so that multiple tmpls can
341 * be pushed on the stack before returning UNLANG_ACTION_PUSHED_CHILD
342 */
343 repeatable_set(frame);
344
345 *state = (unlang_frame_state_tmpl_t) {
346 .vpt = tmpl,
347 .out = out,
348 .ctx = ctx,
349 };
350 if (args) state->args = *args; /* Copy these because they're usually ephemeral/initialised as compound literal */
351
352 /*
353 * Default to something sensible
354 * instead of locking the same indefinitely.
355 */
356 if (!fr_time_delta_ispos(state->args.exec.timeout)) state->args.exec.timeout = fr_time_delta_from_sec(EXEC_TIMEOUT);
357
358 fr_value_box_list_init(&state->list);
359
360 return 0;
361}
362
364{
365 unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t);
366
367 if (state->vpt) {
368 RDEBUG("tmpl %s", state->vpt->name);
369 } else {
371 RDEBUG("tmpl %s", ut->tmpl->name);
372 }
373}
374
376{
378 .name = "tmpl",
379 .type = UNLANG_TYPE_TMPL,
381
382 .interpret = unlang_tmpl,
383 .signal = unlang_tmpl_signal,
384 .dump = unlang_tmpl_dump,
385
386 .unlang_size = sizeof(unlang_tmpl_t),
387 .unlang_name = "unlang_tmpl_t",
388
389 .frame_state_size = sizeof(unlang_frame_state_tmpl_t),
390 .frame_state_type = "unlang_frame_state_tmpl_t",
391 });
392}
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_PUSHED_CHILD
unlang_t pushed a new child onto the stack, execute it instead of continuing.
Definition action.h:39
@ UNLANG_ACTION_STOP_PROCESSING
Break out of processing the current request (unwind).
Definition action.h:42
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:41
va_list args
Definition acutest.h:770
#define RCSID(id)
Definition build.h:485
#define MEM(x)
Definition debug.h:36
int fr_exec_oneshot(TALLOC_CTX *ctx, fr_exec_state_t *exec, request_t *request, fr_value_box_list_t *args, fr_pair_list_t *env_pairs, bool env_escape, bool env_inherit, bool need_stdin, bool store_stdout, TALLOC_CTX *stdout_ctx, fr_time_delta_t timeout)
Call an child program, optionally reading it's output.
Definition exec.c:982
void fr_exec_oneshot_cleanup(fr_exec_state_t *exec, int signal)
Cleans up an exec'd process on error.
Definition exec.c:664
#define EXEC_TIMEOUT
Default wait time for exec calls (in seconds).
Definition exec.h:32
int unlang_interpret_push(unlang_result_t *p_result, request_t *request, unlang_t const *instruction, unlang_frame_conf_t const *conf, bool do_next_sibling)
Push a new frame onto the stack.
Definition interpret.c:280
#define FRAME_CONF(_default_rcode, _top_frame)
Definition interpret.h:152
#define UNLANG_SUB_FRAME
Definition interpret.h:37
#define RPEDEBUG(fmt,...)
Definition log.h:376
void unlang_register(unlang_op_t *op)
Register an operation with the interpreter.
Definition base.c:56
talloc_free(reap)
static char * stack[MAX_STACK]
Definition radmin.c:159
fr_type_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
#define DEFAULT_MOD_ACTIONS
Definition mod_action.h:68
@ MOD_ACTION_RETURN
stop processing the section, and return the rcode with unset priority
Definition mod_action.h:40
#define fr_assert(_expr)
Definition rad_assert.h:38
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG(fmt,...)
Definition radclient.h:53
#define RETURN_UNLANG_FAIL
Definition rcode.h:59
#define RETURN_UNLANG_OK
Definition rcode.h:60
@ RLM_MODULE_INVALID
The module considers the request invalid.
Definition rcode.h:47
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:45
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:44
@ RLM_MODULE_DISALLOW
Reject the request (user is locked out).
Definition rcode.h:48
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:43
@ RLM_MODULE_NOTFOUND
User not found.
Definition rcode.h:49
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition rcode.h:51
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
Definition rcode.h:41
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition rcode.h:50
@ RLM_MODULE_HANDLED
The module handled the request, so stop.
Definition rcode.h:46
bool const sbuff_char_line_endings[UINT8_MAX+1]
Definition sbuff.c:104
size_t fr_sbuff_trim(fr_sbuff_t *sbuff, bool const to_trim[static UINT8_MAX+1])
Trim trailing characters from a string we're composing.
Definition sbuff.c:2161
#define fr_sbuff_start(_sbuff_or_marker)
#define fr_sbuff_used(_sbuff_or_marker)
#define tmpl_contains_xlat(vpt)
Definition tmpl.h:227
#define tmpl_is_xlat(vpt)
Definition tmpl.h:210
#define tmpl_contains_regex(vpt)
Definition tmpl.h:226
#define tmpl_is_exec(vpt)
Definition tmpl.h:211
bool tmpl_async_required(tmpl_t const *vpt)
Return whether or not async is required for this tmpl.
#define tmpl_xlat(_tmpl)
Definition tmpl.h:930
#define tmpl_rules_cast(_tmpl)
Definition tmpl.h:942
int tmpl_eval_cast_in_place(fr_value_box_list_t *out, request_t *request, tmpl_t const *vpt)
Casts a value or list of values according to the tmpl.
Definition tmpl_eval.c:1231
int tmpl_eval(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *vpt)
Gets the value of a tmpl.
Definition tmpl_eval.c:1103
#define tmpl_needs_resolving(vpt)
Definition tmpl.h:223
Signals that can be sent to a request.
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
fr_aka_sim_id_type_t type
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_delta_ispos(_a)
Definition time.h:290
static unlang_action_t unlang_tmpl_xlat_resume(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Wrapper to call after an xlat has been expanded.
Definition tmpl.c:166
void unlang_tmpl_init(void)
Definition tmpl.c:375
static void unlang_tmpl_signal(request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
Send a signal (usually stop) to a request.
Definition tmpl.c:50
static unlang_action_t unlang_tmpl_exec_wait_final(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Wrapper to call exec after the program has finished executing.
Definition tmpl.c:97
static unlang_action_t unlang_tmpl_exec_wait_resume(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Wrapper to call exec after a tmpl has been expanded.
Definition tmpl.c:184
int unlang_tmpl_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, request_t *request, tmpl_t const *tmpl, unlang_tmpl_args_t *args)
Push a tmpl onto the stack for evaluation.
Definition tmpl.c:273
static unlang_action_t unlang_tmpl(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Definition tmpl.c:206
static void unlang_tmpl_dump(request_t *request, unlang_stack_frame_t *frame)
Definition tmpl.c:363
static unlang_action_t unlang_tmpl_resume(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Wrapper to call a resumption function after a tmpl has been expanded.
Definition tmpl.c:77
Declarations for the unlang tmpl interface.
fr_value_box_list_t * out
output list if the exec succeeds
Definition tmpl_priv.h:40
unlang_tmpl_args_t args
Arguments that control how the tmpl is evaluated.
Definition tmpl_priv.h:52
TALLOC_CTX * ctx
for allocating value boxes
Definition tmpl_priv.h:38
fr_unlang_tmpl_resume_t resume
resumption handler
Definition tmpl_priv.h:44
tmpl_t const * vpt
the thing being expanded
Definition tmpl_priv.h:39
fr_value_box_list_t list
our intermediate working list
Definition tmpl_priv.h:41
void * rctx
for resume
Definition tmpl_priv.h:43
fr_unlang_tmpl_signal_t signal
signal handler
Definition tmpl_priv.h:45
A tmpl stack entry.
Definition tmpl_priv.h:37
struct unlang_tmpl_args_t::@105 exec
Exec specific arguments.
Arguments for evaluating different types of tmpls.
Definition tmpl.h:48
int unlang_xlat_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, request_t *request, xlat_exp_head_t const *xlat, bool top_frame)
Push a pre-compiled xlat onto the stack for evaluation.
Definition xlat.c:283
#define XLAT_RESULT_SUCCESS(_p_result)
Definition xlat.h:503
void * state
Stack frame specialisations.
#define UNLANG_NEXT_STOP
Definition unlang_priv.h:97
unlang_t self
static unlang_t * unlang_tmpl_to_generic(unlang_tmpl_t const *p)
@ UNLANG_TYPE_TMPL
asynchronously expand a tmpl_t
Definition unlang_priv.h:80
static void frame_repeat(unlang_stack_frame_t *frame, unlang_process_t process)
Mark the current stack frame up for repeat, and set a new process function.
unlang_t const * instruction
The unlang node we're evaluating.
static unlang_tmpl_t * unlang_generic_to_tmpl(unlang_t const *p)
tmpl_t const * tmpl
@ UNLANG_OP_FLAG_INTERNAL
it's not a real keyword
static void repeatable_set(unlang_stack_frame_t *frame)
unlang_process_t process
function to call for interpreting this stack frame
unlang_type_t type
The specialisation of this node.
An unlang operation.
A node in a graph of unlang_op_t (s) that we execute.
Our interpreter stack, as distinct from the C stack.
An unlang stack associated with a request.
A naked xlat.
#define RETRY_INIT
Definition retry.h:39
ssize_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules)
Definition value.c:5459
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:640
#define fr_box_strvalue_len(_val, _len)
Definition value.h:305
static size_t char ** out
Definition value.h:1020