/src/freeradius-server/src/lib/unlang/tmpl.c
Line | Count | Source |
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: 7a7d7dbeaa8ec60f93ec0314bb42683fbc0c0400 $ |
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 | | */ |
26 | | RCSID("$Id: 7a7d7dbeaa8ec60f93ec0314bb42683fbc0c0400 $") |
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 | | */ |
50 | | static void unlang_tmpl_signal(request_t *request, unlang_stack_frame_t *frame, fr_signal_t action) |
51 | 0 | { |
52 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, |
53 | 0 | unlang_frame_state_tmpl_t); |
54 | | |
55 | | /* |
56 | | * If we're cancelled, then kill any child processes |
57 | | */ |
58 | 0 | if ((action == FR_SIGNAL_CANCEL) && state->exec_result.request) fr_exec_oneshot_cleanup(&state->exec_result, SIGKILL); |
59 | |
|
60 | 0 | if (!state->signal) return; |
61 | | |
62 | 0 | 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 | 0 | if (action == FR_SIGNAL_CANCEL) state->signal = NULL; |
69 | 0 | } |
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 | | */ |
77 | | static unlang_action_t unlang_tmpl_resume(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
78 | 0 | { |
79 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t); |
80 | 0 | unlang_tmpl_t *ut = unlang_generic_to_tmpl(frame->instruction); |
81 | |
|
82 | 0 | if (tmpl_eval_cast_in_place(&state->list, request, ut->tmpl) < 0) { |
83 | 0 | RPEDEBUG("Failed casting expansion"); |
84 | 0 | RETURN_UNLANG_FAIL; |
85 | 0 | } |
86 | | |
87 | 0 | if (state->out) fr_value_box_list_move(state->out, &state->list); |
88 | |
|
89 | 0 | if (state->resume) return state->resume(p_result, request, state->rctx); |
90 | | |
91 | 0 | RETURN_UNLANG_OK; |
92 | 0 | } |
93 | | |
94 | | /** Wrapper to call exec after the program has finished executing |
95 | | * |
96 | | */ |
97 | | static unlang_action_t unlang_tmpl_exec_wait_final(unlang_result_t *p_result, request_t *request, |
98 | | unlang_stack_frame_t *frame) |
99 | 0 | { |
100 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, |
101 | 0 | unlang_frame_state_tmpl_t); |
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 | 0 | if (state->exec_result.failed) { |
108 | 0 | fr_value_box_list_talloc_free(&state->list); |
109 | 0 | goto resume; |
110 | 0 | } |
111 | | |
112 | 0 | fr_assert(state->exec_result.pid < 0); /* Assert this has been cleaned up */ |
113 | |
|
114 | 0 | if (!state->args.exec.stdout_on_error && (state->exec_result.status != 0)) { |
115 | 0 | fr_assert(fr_value_box_list_empty(&state->list)); |
116 | 0 | goto resume; |
117 | 0 | } |
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 | 0 | if (state->out) { |
130 | 0 | fr_type_t type = FR_TYPE_STRING; |
131 | 0 | fr_value_box_t *box; |
132 | | |
133 | | /* |
134 | | * Remove any trailing LF / CR |
135 | | */ |
136 | 0 | fr_sbuff_trim(&state->exec_result.stdout_buff, sbuff_char_line_endings); |
137 | |
|
138 | 0 | fr_value_box_list_init(&state->list); |
139 | 0 | MEM(box = fr_value_box_alloc(state->ctx, FR_TYPE_STRING, NULL)); |
140 | 0 | if (fr_value_box_from_str(state->ctx, box, type, NULL, |
141 | 0 | fr_sbuff_start(&state->exec_result.stdout_buff), |
142 | 0 | fr_sbuff_used(&state->exec_result.stdout_buff), |
143 | 0 | NULL) < 0) { |
144 | 0 | talloc_free(box); |
145 | 0 | RETURN_UNLANG_FAIL; |
146 | 0 | } |
147 | 0 | fr_value_box_list_insert_head(&state->list, box); |
148 | 0 | } |
149 | | |
150 | 0 | resume: |
151 | | /* |
152 | | * Inform the caller of the status if it asked for it |
153 | | */ |
154 | 0 | 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 | | */ |
159 | 0 | frame->process = unlang_tmpl_resume; |
160 | 0 | return unlang_tmpl_resume(p_result, request, frame); |
161 | 0 | } |
162 | | |
163 | | /** Wrapper to call after an xlat has been expanded |
164 | | * |
165 | | */ |
166 | | static unlang_action_t unlang_tmpl_xlat_resume(unlang_result_t *p_result, request_t *request, |
167 | | unlang_stack_frame_t *frame) |
168 | 0 | { |
169 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t); |
170 | |
|
171 | 0 | if (!XLAT_RESULT_SUCCESS(&state->xlat_result)) RETURN_UNLANG_FAIL; |
172 | | |
173 | | /* |
174 | | * Ensure that the callers resume function is called. |
175 | | */ |
176 | 0 | frame->process = unlang_tmpl_resume; |
177 | 0 | return unlang_tmpl_resume(p_result, request, frame); |
178 | 0 | } |
179 | | |
180 | | |
181 | | /** Wrapper to call exec after a tmpl has been expanded |
182 | | * |
183 | | */ |
184 | | static unlang_action_t unlang_tmpl_exec_wait_resume(unlang_result_t *p_result, request_t *request, |
185 | | unlang_stack_frame_t *frame) |
186 | 0 | { |
187 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t); |
188 | |
|
189 | 0 | if (!XLAT_RESULT_SUCCESS(&state->xlat_result)) RETURN_UNLANG_FAIL; |
190 | | |
191 | 0 | if (fr_exec_oneshot(state->ctx, &state->exec_result, request, |
192 | 0 | &state->list, |
193 | 0 | state->args.exec.env, false, false, |
194 | 0 | false, |
195 | 0 | (state->out != NULL), state, |
196 | 0 | state->args.exec.timeout) < 0) { |
197 | 0 | RPEDEBUG("Failed executing program"); |
198 | 0 | RETURN_UNLANG_FAIL; |
199 | 0 | } |
200 | | |
201 | 0 | fr_value_box_list_talloc_free(&state->list); /* this is the xlat expansion, and not the output string we want */ |
202 | 0 | frame_repeat(frame, unlang_tmpl_exec_wait_final); |
203 | |
|
204 | 0 | return UNLANG_ACTION_YIELD; |
205 | 0 | } |
206 | | |
207 | | |
208 | | static unlang_action_t unlang_tmpl(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
209 | 0 | { |
210 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t); |
211 | 0 | unlang_tmpl_t *ut = unlang_generic_to_tmpl(frame->instruction); |
212 | | |
213 | | /* |
214 | | * If we're not called from unlang_tmpl_push(), then |
215 | | * ensure that we clean up the resulting value boxes |
216 | | * and that the list to write the boxes in is initialised. |
217 | | */ |
218 | 0 | if (!state->ctx) { |
219 | 0 | state->ctx = state; |
220 | 0 | fr_value_box_list_init(&state->list); |
221 | 0 | } |
222 | | |
223 | | /* |
224 | | * Synchronous tmpls can just be resolved immediately, and directly to the output list. |
225 | | * |
226 | | * However, xlat expansions (including fully synchronous function calls!) need to be expanded by |
227 | | * the xlat framework. |
228 | | */ |
229 | 0 | if (!tmpl_async_required(ut->tmpl) && !tmpl_contains_xlat(ut->tmpl)) { |
230 | 0 | if (tmpl_eval(state->ctx, state->out, request, ut->tmpl) < 0) { |
231 | 0 | RPEDEBUG("Failed evaluating expansion"); |
232 | 0 | goto fail; |
233 | 0 | } |
234 | | |
235 | 0 | RETURN_UNLANG_OK; |
236 | 0 | } |
237 | | |
238 | | /* |
239 | | * XLAT structs are allowed. |
240 | | */ |
241 | 0 | if (tmpl_is_xlat(ut->tmpl)) { |
242 | 0 | frame_repeat(frame, unlang_tmpl_xlat_resume); |
243 | 0 | goto push; |
244 | 0 | } |
245 | | |
246 | 0 | fr_assert(tmpl_is_exec(ut->tmpl)); |
247 | | |
248 | | /* |
249 | | * Expand the arguments to the program we're executing. |
250 | | */ |
251 | 0 | frame_repeat(frame, unlang_tmpl_exec_wait_resume); |
252 | 0 | push: |
253 | 0 | if (unlang_xlat_push(state->ctx, &state->xlat_result, &state->list, request, tmpl_xlat(ut->tmpl), UNLANG_SUB_FRAME) < 0) { |
254 | 0 | fail: |
255 | 0 | RETURN_UNLANG_ACTION_FATAL; |
256 | 0 | } |
257 | | |
258 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
259 | 0 | } |
260 | | |
261 | | /** Push a tmpl onto the stack for evaluation |
262 | | * |
263 | | * @param[in] ctx To allocate value boxes and values in. |
264 | | * @param[out] p_result The frame result |
265 | | * @param[out] out The value_box created from the tmpl. May be NULL, |
266 | | * in which case the result is discarded. |
267 | | * @param[in] request The current request. |
268 | | * @param[in] tmpl the tmpl to expand |
269 | | * @param[in] args additional controls for expanding #TMPL_TYPE_EXEC, |
270 | | * and where the status of exited programs will be stored. |
271 | | * @param[in] top_frame If true, then this is the top frame of the sub-stack. |
272 | | * @return |
273 | | * - 0 on success |
274 | | * - -1 on failure |
275 | | */ |
276 | | int unlang_tmpl_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, request_t *request, |
277 | | tmpl_t const *tmpl, unlang_tmpl_args_t *args, bool top_frame) |
278 | 0 | { |
279 | 0 | unlang_stack_t *stack = request->stack; |
280 | 0 | unlang_stack_frame_t *frame; |
281 | 0 | unlang_frame_state_tmpl_t *state; |
282 | |
|
283 | 0 | unlang_tmpl_t *ut; |
284 | |
|
285 | 0 | static unlang_t const tmpl_instruction_return = { |
286 | 0 | .type = UNLANG_TYPE_TMPL, |
287 | 0 | .name = "tmpl", |
288 | 0 | .debug_name = "tmpl", |
289 | 0 | .actions = MOD_ACTIONS_FAIL_TIMEOUT_RETURN, |
290 | 0 | }; |
291 | |
|
292 | 0 | static const unlang_t tmpl_instruction_fail = { |
293 | 0 | .type = UNLANG_TYPE_TMPL, |
294 | 0 | .name = "tmpl", |
295 | 0 | .debug_name = "tmpl", |
296 | 0 | .actions = DEFAULT_MOD_ACTIONS, |
297 | 0 | }; |
298 | |
|
299 | 0 | if (tmpl_needs_resolving(tmpl)) { |
300 | 0 | REDEBUG("Expansion \"%pV\" needs to be resolved before it is used", fr_box_strvalue_len(tmpl->name, tmpl->len)); |
301 | 0 | return -1; |
302 | 0 | } |
303 | | |
304 | | /* |
305 | | * Avoid an extra stack frame and more work. But only if the caller hands us a result. |
306 | | * Otherwise, we have to return UNLANG_FAIL. |
307 | | */ |
308 | 0 | if (p_result && (tmpl_rules_cast(tmpl) == FR_TYPE_NULL) && tmpl_is_xlat(tmpl)) { |
309 | 0 | return unlang_xlat_push(ctx, p_result, out, request, tmpl_xlat(tmpl), UNLANG_SUB_FRAME); |
310 | 0 | } |
311 | | |
312 | 0 | fr_assert(!tmpl_contains_regex(tmpl)); |
313 | |
|
314 | 0 | MEM(ut = talloc(stack, unlang_tmpl_t)); |
315 | 0 | *ut = (unlang_tmpl_t){ |
316 | 0 | .self = p_result ? tmpl_instruction_fail : tmpl_instruction_return, |
317 | 0 | .tmpl = tmpl |
318 | 0 | }; |
319 | 0 | unlang_type_init(&ut->self, NULL, UNLANG_TYPE_TMPL); |
320 | | |
321 | | /* |
322 | | * Push a new tmpl frame onto the stack |
323 | | */ |
324 | 0 | if (unlang_interpret_push(p_result, request, unlang_tmpl_to_generic(ut), |
325 | 0 | FRAME_CONF(RLM_MODULE_NOT_SET, top_frame), UNLANG_NEXT_STOP) < 0) return -1; |
326 | | |
327 | 0 | frame = &stack->frame[stack->depth]; |
328 | 0 | state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t); |
329 | | |
330 | | /* |
331 | | * Set the frame as repeatable so that multiple tmpls can |
332 | | * be pushed on the stack before returning UNLANG_ACTION_PUSHED_CHILD |
333 | | */ |
334 | 0 | repeatable_set(frame); |
335 | |
|
336 | 0 | *state = (unlang_frame_state_tmpl_t) { |
337 | 0 | .vpt = tmpl, |
338 | 0 | .out = out, |
339 | 0 | .ctx = ctx, |
340 | 0 | }; |
341 | 0 | if (args) state->args = *args; /* Copy these because they're usually ephemeral/initialised as compound literal */ |
342 | | |
343 | | /* |
344 | | * Default to something sensible |
345 | | * instead of locking the same indefinitely. |
346 | | */ |
347 | 0 | if (!fr_time_delta_ispos(state->args.exec.timeout)) state->args.exec.timeout = fr_time_delta_from_sec(EXEC_TIMEOUT); |
348 | |
|
349 | 0 | fr_value_box_list_init(&state->list); |
350 | |
|
351 | 0 | return 0; |
352 | 0 | } |
353 | | |
354 | | static void unlang_tmpl_dump(request_t *request, unlang_stack_frame_t *frame) |
355 | 0 | { |
356 | 0 | unlang_frame_state_tmpl_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_tmpl_t); |
357 | |
|
358 | 0 | if (state->vpt) { |
359 | 0 | RDEBUG("tmpl %s", state->vpt->name); |
360 | 0 | } else { |
361 | 0 | unlang_tmpl_t *ut = unlang_generic_to_tmpl(frame->instruction); |
362 | 0 | RDEBUG("tmpl %s", ut->tmpl->name); |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | | void unlang_tmpl_init(void) |
367 | 4 | { |
368 | 4 | unlang_register(&(unlang_op_t){ |
369 | 4 | .name = "tmpl", |
370 | 4 | .type = UNLANG_TYPE_TMPL, |
371 | 4 | .flag = UNLANG_OP_FLAG_INTERNAL, |
372 | | |
373 | 4 | .interpret = unlang_tmpl, |
374 | 4 | .signal = unlang_tmpl_signal, |
375 | 4 | .dump = unlang_tmpl_dump, |
376 | | |
377 | 4 | .unlang_size = sizeof(unlang_tmpl_t), |
378 | 4 | .unlang_name = "unlang_tmpl_t", |
379 | | |
380 | 4 | .frame_state_size = sizeof(unlang_frame_state_tmpl_t), |
381 | 4 | .frame_state_type = "unlang_frame_state_tmpl_t", |
382 | 4 | }); |
383 | 4 | } |