/src/freeradius-server/src/lib/unlang/xlat.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: 1073c8a7bd467c2910720bef8efcd49622c4d882 $ |
19 | | * |
20 | | * @file unlang/xlat.c |
21 | | * @brief Integration between the unlang interpreter and xlats |
22 | | * |
23 | | * @copyright 2018 The FreeRADIUS server project |
24 | | * @copyright 2018 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
25 | | */ |
26 | | RCSID("$Id: 1073c8a7bd467c2910720bef8efcd49622c4d882 $") |
27 | | |
28 | | #include <freeradius-devel/server/base.h> |
29 | | |
30 | | #include <ctype.h> |
31 | | #include <freeradius-devel/unlang/xlat_priv.h> |
32 | | #include <freeradius-devel/util/debug.h> |
33 | | #include "unlang_priv.h" /* Fixme - Should create a proper semi-public interface for the interpret */ |
34 | | |
35 | | /** State of an xlat expansion |
36 | | * |
37 | | * State of one level of nesting within an xlat expansion. |
38 | | */ |
39 | | typedef struct { |
40 | | TALLOC_CTX *ctx; //!< to allocate boxes and values in. |
41 | | TALLOC_CTX *event_ctx; //!< for temporary events |
42 | | xlat_exp_head_t const *head; //!< of the xlat list |
43 | | xlat_exp_t const *exp; //!< current one we're evaluating |
44 | | fr_dcursor_t values; //!< Values aggregated so far. |
45 | | |
46 | | rindent_t indent; //!< indentation |
47 | | |
48 | | void *env_data; //!< Expanded per call environment tmpls. |
49 | | /* |
50 | | * For func and alternate |
51 | | */ |
52 | | fr_value_box_list_t out; //!< Head of the result of a nested |
53 | | ///< expansion. |
54 | | xlat_func_t resume; //!< called on resume |
55 | | xlat_func_signal_t signal; //!< called on signal |
56 | | fr_signal_t sigmask; //!< Signals to block |
57 | | void *rctx; //!< for resume / signal |
58 | | |
59 | | unlang_result_t *p_result; //!< If set, where to record the result |
60 | | ///< of the execution. |
61 | | } unlang_frame_state_xlat_t; |
62 | | |
63 | | /** Wrap an #fr_timer_t providing data needed for unlang events |
64 | | * |
65 | | */ |
66 | | typedef struct { |
67 | | request_t *request; //!< Request this event pertains to. |
68 | | int fd; //!< File descriptor to wait on. |
69 | | fr_unlang_xlat_timeout_t timeout; //!< Function to call on timeout. |
70 | | fr_unlang_xlat_fd_event_t fd_read; //!< Function to call when FD is readable. |
71 | | fr_unlang_xlat_fd_event_t fd_write; //!< Function to call when FD is writable. |
72 | | fr_unlang_xlat_fd_event_t fd_error; //!< Function to call when FD has errored. |
73 | | xlat_inst_t *inst; //!< xlat instance data. |
74 | | xlat_thread_inst_t *thread; //!< Thread specific xlat instance. |
75 | | void const *rctx; //!< rctx data to pass to callbacks. |
76 | | fr_timer_t *ev; //!< Event in this worker's event heap. |
77 | | } unlang_xlat_event_t; |
78 | | |
79 | | typedef struct { |
80 | | request_t *request; |
81 | | xlat_inst_t *inst; //!< xlat instance data. |
82 | | xlat_thread_inst_t *thread; //!< Thread specific xlat instance. |
83 | | |
84 | | fr_unlang_xlat_retry_t retry_cb; //!< callback to run on timeout |
85 | | void *rctx; //!< rctx data to pass to timeout callback |
86 | | |
87 | | fr_timer_t *ev; //!< retry timer just for this xlat |
88 | | fr_retry_t retry; //!< retry timers, etc. |
89 | | } unlang_xlat_retry_t; |
90 | | |
91 | | /** Frees an unlang event, removing it from the request's event loop |
92 | | * |
93 | | * @param[in] ev The event to free. |
94 | | * |
95 | | * @return 0 |
96 | | */ |
97 | | static int _unlang_xlat_event_free(unlang_xlat_event_t *ev) |
98 | 0 | { |
99 | 0 | FR_TIMER_DELETE(&(ev->ev)); |
100 | |
|
101 | 0 | if (ev->fd >= 0) { |
102 | 0 | (void) fr_event_fd_delete(unlang_interpret_event_list(ev->request), ev->fd, FR_EVENT_FILTER_IO); |
103 | 0 | } |
104 | |
|
105 | 0 | return 0; |
106 | 0 | } |
107 | | |
108 | | /** Call the callback registered for a timeout event |
109 | | * |
110 | | * @param[in] tl the event timer was inserted into. |
111 | | * @param[in] now The current time, as held by the event_list. |
112 | | * @param[in] uctx unlang_module_event_t structure holding callbacks. |
113 | | * |
114 | | */ |
115 | | static void unlang_xlat_event_timeout_handler(UNUSED fr_timer_list_t *tl, fr_time_t now, void *uctx) |
116 | 0 | { |
117 | 0 | unlang_xlat_event_t *ev = talloc_get_type_abort(uctx, unlang_xlat_event_t); |
118 | | |
119 | | /* |
120 | | * If the timeout's fired then the xlat must necessarily |
121 | | * be yielded, so it's fine to pass in its rctx. |
122 | | * |
123 | | * It should be able to free the rctx if it wants to. |
124 | | * We never free it explicitly, and instead rely on |
125 | | * talloc parenting. |
126 | | */ |
127 | 0 | ev->timeout(XLAT_CTX(ev->inst->data, |
128 | 0 | ev->thread->data, |
129 | 0 | NULL, |
130 | 0 | ev->thread->mctx, NULL, |
131 | 0 | UNCONST(void *, ev->rctx)), |
132 | 0 | ev->request, now); |
133 | | |
134 | | /* Remove old references from the request */ |
135 | 0 | talloc_free(ev); |
136 | 0 | } |
137 | | |
138 | | /** Add a timeout for an xlat handler |
139 | | * |
140 | | * @note The timeout is automatically removed when the xlat is cancelled or resumed. |
141 | | * |
142 | | * @param[in] request the request |
143 | | * @param[in] callback to run when the timeout hits |
144 | | * @param[in] rctx passed to the callback |
145 | | * @param[in] when when the timeout fires |
146 | | * @return |
147 | | * - <0 on error |
148 | | * - 0 on success |
149 | | */ |
150 | | int unlang_xlat_timeout_add(request_t *request, |
151 | | fr_unlang_xlat_timeout_t callback, void const *rctx, fr_time_t when) |
152 | 0 | { |
153 | 0 | unlang_stack_t *stack = request->stack; |
154 | 0 | unlang_stack_frame_t *frame = &stack->frame[stack->depth]; |
155 | 0 | unlang_xlat_event_t *ev; |
156 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
157 | |
|
158 | 0 | fr_assert(stack->depth > 0); |
159 | 0 | fr_assert(frame->instruction->type == UNLANG_TYPE_XLAT); |
160 | |
|
161 | 0 | if (!state->event_ctx) MEM(state->event_ctx = talloc_zero(state, bool)); |
162 | |
|
163 | 0 | MEM(ev = talloc_zero(state->event_ctx, unlang_xlat_event_t)); |
164 | 0 | ev->request = request; |
165 | 0 | ev->fd = -1; |
166 | 0 | ev->timeout = callback; |
167 | 0 | fr_assert(state->exp->type == XLAT_FUNC); |
168 | 0 | ev->inst = state->exp->call.inst; |
169 | 0 | ev->thread = xlat_thread_instance_find(state->exp); |
170 | 0 | ev->rctx = rctx; |
171 | |
|
172 | 0 | if (fr_timer_at(request, unlang_interpret_event_list(request)->tl, |
173 | 0 | &ev->ev, when, |
174 | 0 | false, unlang_xlat_event_timeout_handler, ev) < 0) { |
175 | 0 | RPEDEBUG("Failed inserting event"); |
176 | 0 | talloc_free(ev); |
177 | 0 | return -1; |
178 | 0 | } |
179 | | |
180 | 0 | talloc_set_destructor(ev, _unlang_xlat_event_free); |
181 | |
|
182 | 0 | return 0; |
183 | 0 | } |
184 | | |
185 | | /** Push a pre-compiled xlat onto the stack for evaluation |
186 | | * |
187 | | * @param[in] ctx To allocate value boxes and values in. |
188 | | * @param[out] p_result If set, rcodes and priorities will be written here and |
189 | | * not evaluated by the unlang interpreter. |
190 | | * @param[out] out Where to write the result of the expansion. |
191 | | * @param[in] request to push xlat onto. |
192 | | * @param[in] xlat head of list |
193 | | * @param[in] node to evaluate. |
194 | | * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return. |
195 | | * Set to UNLANG_SUB_FRAME if the interpreter should continue. |
196 | | * @return |
197 | | * - 0 on success. |
198 | | * - -1 on failure. |
199 | | */ |
200 | | static int unlang_xlat_push_internal(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, |
201 | | request_t *request, xlat_exp_head_t const *xlat, xlat_exp_t *node, bool top_frame) |
202 | 0 | { |
203 | | /** Static instruction for performing xlat evaluations |
204 | | * |
205 | | */ |
206 | 0 | static unlang_t xlat_instruction = { |
207 | 0 | .type = UNLANG_TYPE_XLAT, |
208 | 0 | .name = "xlat", |
209 | 0 | .debug_name = "xlat", |
210 | 0 | .actions = MOD_ACTIONS_FAIL_TIMEOUT_RETURN, |
211 | 0 | }; |
212 | |
|
213 | 0 | unlang_frame_state_xlat_t *state; |
214 | 0 | unlang_stack_t *stack = request->stack; |
215 | 0 | unlang_stack_frame_t *frame; |
216 | | |
217 | | /* |
218 | | * Push a new xlat eval frame onto the stack |
219 | | */ |
220 | 0 | if (unlang_interpret_push(p_result, request, &xlat_instruction, |
221 | 0 | FRAME_CONF(RLM_MODULE_NOT_SET, top_frame), UNLANG_NEXT_STOP) < 0) return -1; |
222 | 0 | frame = &stack->frame[stack->depth]; |
223 | | |
224 | | /* |
225 | | * Allocate its state, and setup a cursor for the xlat nodes |
226 | | */ |
227 | 0 | MEM(frame->state = state = talloc_zero(stack, unlang_frame_state_xlat_t)); |
228 | 0 | state->head = xlat; |
229 | 0 | state->exp = node; |
230 | 0 | state->p_result = p_result; |
231 | 0 | state->ctx = ctx; |
232 | |
|
233 | 0 | if (node) switch (node->type) { |
234 | 0 | case XLAT_GROUP: |
235 | 0 | case XLAT_BOX: |
236 | 0 | break; |
237 | | |
238 | 0 | case XLAT_TMPL: |
239 | 0 | if (tmpl_is_data(node->vpt)) break; |
240 | 0 | FALL_THROUGH; |
241 | |
|
242 | 0 | default: |
243 | 0 | RDEBUG("| %s", node->fmt); |
244 | 0 | break; |
245 | 0 | } |
246 | | |
247 | | /* |
248 | | * Initialise the input and output lists |
249 | | */ |
250 | 0 | fr_dcursor_init(&state->values, fr_value_box_list_dlist_head(out)); |
251 | 0 | fr_value_box_list_init(&state->out); |
252 | |
|
253 | 0 | return 0; |
254 | 0 | } |
255 | | |
256 | | /** Push a pre-compiled xlat onto the stack for evaluation |
257 | | * |
258 | | * @param[in] ctx To allocate value boxes and values in. |
259 | | * @param[out] p_result The frame result |
260 | | * @param[out] out Where to write the result of the expansion. |
261 | | * @param[in] request to push xlat onto. |
262 | | * @param[in] xlat to evaluate. |
263 | | * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return. |
264 | | * Set to UNLANG_SUB_FRAME if the interpreter should continue. |
265 | | * @return |
266 | | * - 0 on success. |
267 | | * - -1 on failure. |
268 | | */ |
269 | | int unlang_xlat_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, |
270 | | request_t *request, xlat_exp_head_t const *xlat, bool top_frame) |
271 | 0 | { |
272 | 0 | (void) talloc_get_type_abort_const(xlat, xlat_exp_head_t); |
273 | |
|
274 | 0 | return unlang_xlat_push_internal(ctx, p_result, out, request, xlat, xlat_exp_head(xlat), top_frame); |
275 | 0 | } |
276 | | |
277 | | /** Push a pre-compiled xlat onto the stack for evaluation |
278 | | * |
279 | | * @param[in] ctx To allocate value boxes and values in. |
280 | | * @param[out] p_result If set, and execution succeeds, true will be written |
281 | | * here. If execution fails, false will be written. |
282 | | * @param[out] out Where to write the result of the expansion. |
283 | | * @param[in] request to push xlat onto. |
284 | | * @param[in] node to evaluate. Only this node will be evaluated. |
285 | | * @return |
286 | | * - 0 on success. |
287 | | * - -1 on failure. |
288 | | */ |
289 | | int unlang_xlat_push_node(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, |
290 | | request_t *request, xlat_exp_t *node) |
291 | 0 | { |
292 | 0 | return unlang_xlat_push_internal(ctx, p_result, out, request, NULL, node, UNLANG_TOP_FRAME); |
293 | 0 | } |
294 | | |
295 | | static unlang_action_t unlang_xlat_repeat(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
296 | 0 | { |
297 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
298 | 0 | xlat_action_t xa; |
299 | 0 | xlat_exp_head_t const *child = NULL; |
300 | | |
301 | | /* |
302 | | * If the xlat is a function with a method_env, expand it before calling the function. |
303 | | */ |
304 | 0 | if ((state->exp->type == XLAT_FUNC) && state->exp->call.inst->call_env && !state->env_data) { |
305 | 0 | unlang_action_t ua = call_env_expand(state, request, NULL, &state->env_data, |
306 | 0 | state->exp->call.inst->call_env); |
307 | 0 | switch (ua) { |
308 | 0 | case UNLANG_ACTION_FAIL: |
309 | 0 | goto fail; |
310 | | |
311 | 0 | case UNLANG_ACTION_PUSHED_CHILD: |
312 | 0 | frame_repeat(frame, unlang_xlat_repeat); |
313 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
314 | | |
315 | 0 | default: |
316 | 0 | break; |
317 | 0 | } |
318 | 0 | } |
319 | | |
320 | 0 | xa = xlat_frame_eval_repeat(state->ctx, &state->values, &child, |
321 | 0 | request, state->head, &state->exp, state->env_data, &state->out); |
322 | 0 | switch (xa) { |
323 | 0 | case XLAT_ACTION_PUSH_CHILD: |
324 | 0 | fr_assert(child); |
325 | |
|
326 | 0 | repeatable_set(frame); /* Was cleared by the interpreter */ |
327 | | |
328 | | /* |
329 | | * Clear out the results of any previous expansions |
330 | | * at this level. A frame may be used to evaluate |
331 | | * multiple sibling nodes. |
332 | | */ |
333 | 0 | fr_value_box_list_talloc_free(&state->out); |
334 | 0 | if (unlang_xlat_push(state->ctx, p_result, &state->out, request, child, false) < 0) { |
335 | 0 | REXDENT(); |
336 | 0 | RETURN_UNLANG_ACTION_FATAL; |
337 | 0 | } |
338 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
339 | | |
340 | 0 | case XLAT_ACTION_PUSH_UNLANG: |
341 | 0 | repeatable_set(frame); /* Call the xlat code on the way back down */ |
342 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
343 | | |
344 | 0 | case XLAT_ACTION_YIELD: |
345 | 0 | if (!state->resume) { |
346 | 0 | RWDEBUG("Missing call to unlang_xlat_yield()"); |
347 | 0 | goto fail; |
348 | 0 | } |
349 | 0 | repeatable_set(frame); |
350 | 0 | return UNLANG_ACTION_YIELD; |
351 | | |
352 | 0 | case XLAT_ACTION_DONE: |
353 | 0 | *p_result = UNLANG_RESULT_RCODE(RLM_MODULE_OK); |
354 | 0 | REXDENT(); |
355 | 0 | return UNLANG_ACTION_CALCULATE_RESULT; |
356 | | |
357 | 0 | case XLAT_ACTION_FAIL: |
358 | 0 | fail: |
359 | 0 | REXDENT(); |
360 | 0 | return UNLANG_ACTION_FAIL; |
361 | | |
362 | 0 | default: |
363 | 0 | fr_assert(0); |
364 | 0 | goto fail; |
365 | 0 | } |
366 | 0 | } |
367 | | |
368 | | /** Stub function for calling the xlat interpreter |
369 | | * |
370 | | * Calls the xlat interpreter and translates its wants and needs into |
371 | | * unlang_action_t codes. |
372 | | */ |
373 | | static unlang_action_t unlang_xlat(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
374 | 0 | { |
375 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
376 | 0 | xlat_action_t xa; |
377 | 0 | xlat_exp_head_t const *child = NULL; |
378 | |
|
379 | 0 | RINDENT_SAVE(state, request); |
380 | 0 | RINDENT(); |
381 | |
|
382 | 0 | xa = xlat_frame_eval(state->ctx, &state->values, &child, request, state->head, &state->exp); |
383 | 0 | switch (xa) { |
384 | 0 | case XLAT_ACTION_PUSH_CHILD: |
385 | 0 | fr_assert(child); |
386 | |
|
387 | 0 | frame_repeat(frame, unlang_xlat_repeat); |
388 | | |
389 | | /* |
390 | | * Clear out the results of any previous expansions |
391 | | * at this level. A frame may be used to evaluate |
392 | | * multiple sibling nodes. |
393 | | */ |
394 | 0 | fr_value_box_list_talloc_free(&state->out); |
395 | 0 | if (unlang_xlat_push(state->ctx, p_result, &state->out, request, child, false) < 0) { |
396 | 0 | RINDENT_RESTORE(request, state); |
397 | 0 | RETURN_UNLANG_ACTION_FATAL; |
398 | 0 | } |
399 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
400 | | |
401 | 0 | case XLAT_ACTION_PUSH_UNLANG: |
402 | 0 | repeatable_set(frame); /* Call the xlat code on the way back down */ |
403 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
404 | | |
405 | 0 | case XLAT_ACTION_YIELD: |
406 | 0 | if (!state->resume) { |
407 | 0 | RWDEBUG("Missing call to unlang_xlat_yield()"); |
408 | 0 | goto fail; |
409 | 0 | } |
410 | 0 | repeatable_set(frame); |
411 | 0 | return UNLANG_ACTION_YIELD; |
412 | | |
413 | 0 | case XLAT_ACTION_DONE: |
414 | 0 | *p_result = UNLANG_RESULT_RCODE(RLM_MODULE_OK); |
415 | 0 | RINDENT_RESTORE(request, state); |
416 | 0 | return UNLANG_ACTION_CALCULATE_RESULT; |
417 | | |
418 | 0 | case XLAT_ACTION_FAIL: |
419 | 0 | fail: |
420 | 0 | RINDENT_RESTORE(request, state); |
421 | 0 | return UNLANG_ACTION_FAIL; |
422 | | |
423 | 0 | default: |
424 | 0 | fr_assert(0); |
425 | 0 | goto fail; |
426 | 0 | } |
427 | 0 | } |
428 | | |
429 | | /** Send a signal (usually stop) to a request that's running an xlat expansions |
430 | | * |
431 | | * This is typically called via an "async" action, i.e. an action |
432 | | * outside of the normal processing of the request. |
433 | | * |
434 | | * If there is no #xlat_func_signal_t callback defined, the action is ignored. |
435 | | * |
436 | | * @param[in] request The current request. |
437 | | * @param[in] frame The current stack frame. |
438 | | * @param[in] action What the request should do (the type of signal). |
439 | | */ |
440 | | static void unlang_xlat_signal(request_t *request, unlang_stack_frame_t *frame, fr_signal_t action) |
441 | 0 | { |
442 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
443 | | |
444 | | /* |
445 | | * Delete timers, etc. when the xlat is cancelled. |
446 | | */ |
447 | 0 | if (action == FR_SIGNAL_CANCEL) { |
448 | 0 | TALLOC_FREE(state->event_ctx); |
449 | 0 | } |
450 | |
|
451 | 0 | if (!state->signal || (state->sigmask & action)) return; |
452 | | |
453 | 0 | xlat_signal(state->signal, state->exp, request, state->rctx, action); |
454 | 0 | } |
455 | | |
456 | | /** Called when we're ready to resume processing the request |
457 | | * |
458 | | * @param[in] p_result the result of the xlat function. |
459 | | * - RLM_MODULE_OK on success. |
460 | | * - RLM_MODULE_FAIL on failure. |
461 | | * @param[in] request to resume processing. |
462 | | * @param[in] frame the current stack frame. |
463 | | * @return |
464 | | * - UNLANG_ACTION_YIELD if additional asynchronous |
465 | | * operations need to be performed. |
466 | | * - UNLANG_ACTION_CALCULATE_RESULT if done. |
467 | | */ |
468 | | static unlang_action_t unlang_xlat_resume(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
469 | 0 | { |
470 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
471 | 0 | xlat_action_t xa; |
472 | 0 | xlat_exp_head_t const *child = NULL; |
473 | |
|
474 | 0 | fr_assert(state->resume != NULL); |
475 | | |
476 | | /* |
477 | | * Delete timers, etc. when the xlat is resumed. |
478 | | */ |
479 | 0 | TALLOC_FREE(state->event_ctx); |
480 | |
|
481 | 0 | xa = xlat_frame_eval_resume(state->ctx, &state->values, &child, request, state->head, &state->exp, |
482 | 0 | &state->out, state->resume, state->rctx); |
483 | 0 | switch (xa) { |
484 | 0 | case XLAT_ACTION_YIELD: |
485 | 0 | repeatable_set(frame); |
486 | 0 | return UNLANG_ACTION_YIELD; |
487 | | |
488 | 0 | case XLAT_ACTION_DONE: |
489 | 0 | *p_result = UNLANG_RESULT_RCODE(RLM_MODULE_OK); |
490 | 0 | RINDENT_RESTORE(request, state); |
491 | 0 | return UNLANG_ACTION_CALCULATE_RESULT; |
492 | | |
493 | 0 | case XLAT_ACTION_PUSH_UNLANG: |
494 | 0 | repeatable_set(frame); |
495 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
496 | | |
497 | 0 | case XLAT_ACTION_PUSH_CHILD: |
498 | 0 | fr_assert(child); |
499 | |
|
500 | 0 | repeatable_set(frame); /* Was cleared by the interpreter */ |
501 | | |
502 | | /* |
503 | | * Clear out the results of any previous expansions |
504 | | * at this level. A frame may be used to evaluate |
505 | | * multiple sibling nodes. |
506 | | */ |
507 | 0 | fr_value_box_list_talloc_free(&state->out); |
508 | 0 | if (unlang_xlat_push(state->ctx, state->p_result, &state->out, request, child, false) < 0) { |
509 | 0 | RINDENT_RESTORE(request, state); |
510 | 0 | RETURN_UNLANG_ACTION_FATAL; |
511 | 0 | } |
512 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
513 | | |
514 | 0 | case XLAT_ACTION_FAIL: |
515 | 0 | RINDENT_RESTORE(request, state); |
516 | 0 | return UNLANG_ACTION_FAIL; |
517 | | /* DON'T SET DEFAULT */ |
518 | 0 | } |
519 | | |
520 | 0 | fr_assert(0); /* Garbage xlat action */ |
521 | |
|
522 | 0 | RINDENT_RESTORE(request, state); |
523 | 0 | return UNLANG_ACTION_FAIL; |
524 | 0 | } |
525 | | |
526 | | /** Yield a request back to the interpreter from within a module |
527 | | * |
528 | | * This passes control of the request back to the unlang interpreter, setting |
529 | | * callbacks to execute when the request is 'signalled' asynchronously, or whatever |
530 | | * timer or I/O event the module was waiting for occurs. |
531 | | * |
532 | | * @note The module function which calls #unlang_module_yield should return control |
533 | | * of the C stack to the unlang interpreter immediately after calling #unlang_xlat_yield. |
534 | | * A common pattern is to use ``return unlang_xlat_yield(...)``. |
535 | | * |
536 | | * @param[in] request The current request. |
537 | | * @param[in] resume Called on unlang_interpret_mark_runnable(). |
538 | | * @param[in] signal Called on unlang_action(). |
539 | | * @param[in] sigmask Signals to block. |
540 | | * @param[in] rctx to pass to the callbacks. |
541 | | * @return always returns XLAT_ACTION_YIELD |
542 | | */ |
543 | | xlat_action_t unlang_xlat_yield(request_t *request, |
544 | | xlat_func_t resume, xlat_func_signal_t signal, fr_signal_t sigmask, |
545 | | void *rctx) |
546 | 0 | { |
547 | 0 | unlang_stack_t *stack = request->stack; |
548 | 0 | unlang_stack_frame_t *frame = &stack->frame[stack->depth]; |
549 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
550 | |
|
551 | 0 | frame->process = unlang_xlat_resume; |
552 | | |
553 | | /* |
554 | | * Over-ride whatever functions were there before. |
555 | | */ |
556 | 0 | state->resume = resume; |
557 | 0 | state->signal = signal; |
558 | 0 | state->sigmask = sigmask; |
559 | 0 | state->rctx = rctx; |
560 | |
|
561 | 0 | return XLAT_ACTION_YIELD; |
562 | 0 | } |
563 | | |
564 | | /** Frees an unlang event, removing it from the request's event loop |
565 | | * |
566 | | * @param[in] ev The event to free. |
567 | | * |
568 | | * @return 0 |
569 | | */ |
570 | | static int _unlang_xlat_retry_free(unlang_xlat_retry_t *ev) |
571 | 0 | { |
572 | 0 | FR_TIMER_DELETE(&(ev->ev)); |
573 | |
|
574 | 0 | return 0; |
575 | 0 | } |
576 | | |
577 | | /** Call the callback registered for a timeout event |
578 | | * |
579 | | * @param[in] tl the event timer was inserted into. |
580 | | * @param[in] now The current time, as held by the event_list. |
581 | | * @param[in] uctx unlang_module_event_t structure holding callbacks. |
582 | | * |
583 | | */ |
584 | | static void unlang_xlat_event_retry_handler(UNUSED fr_timer_list_t *tl, fr_time_t now, void *uctx) |
585 | 0 | { |
586 | 0 | unlang_xlat_retry_t *ev = talloc_get_type_abort(uctx, unlang_xlat_retry_t); |
587 | 0 | request_t *request = ev->request; |
588 | |
|
589 | 0 | switch (fr_retry_next(&ev->retry, now)) { |
590 | 0 | case FR_RETRY_CONTINUE: |
591 | | /* |
592 | | * Call the module retry handler, with the state of the retry. On MRD / MRC, the |
593 | | * module is made runnable again, and the "resume" function is called. |
594 | | */ |
595 | 0 | ev->retry_cb(XLAT_CTX(ev->inst->data, |
596 | 0 | ev->thread->data, |
597 | 0 | NULL, |
598 | 0 | ev->thread->mctx, NULL, |
599 | 0 | UNCONST(void *, ev->rctx)), |
600 | 0 | ev->request, &ev->retry); |
601 | | |
602 | | /* |
603 | | * Reset the timer. |
604 | | */ |
605 | 0 | if (fr_timer_at(ev, unlang_interpret_event_list(request)->tl, &ev->ev, ev->retry.next, |
606 | 0 | false, unlang_xlat_event_retry_handler, ev) < 0) { |
607 | 0 | RPEDEBUG("Failed inserting event"); |
608 | 0 | talloc_free(ev); |
609 | 0 | unlang_interpret_mark_runnable(request); |
610 | 0 | } |
611 | 0 | return; |
612 | | |
613 | 0 | case FR_RETRY_MRD: |
614 | 0 | RDEBUG("Reached max_rtx_duration (%pVs > %pVs) - sending timeout", |
615 | 0 | fr_box_time_delta(fr_time_sub(now, ev->retry.start)), fr_box_time_delta(ev->retry.config->mrd)); |
616 | 0 | break; |
617 | | |
618 | 0 | case FR_RETRY_MRC: |
619 | 0 | RDEBUG("Reached max_rtx_count %u- sending timeout", |
620 | 0 | ev->retry.config->mrc); |
621 | 0 | break; |
622 | 0 | } |
623 | | |
624 | | /* |
625 | | * Run the retry handler on MRD / MRC, too. |
626 | | */ |
627 | 0 | ev->retry_cb(XLAT_CTX(ev->inst->data, |
628 | 0 | ev->thread->data, |
629 | 0 | NULL, |
630 | 0 | ev->thread->mctx, NULL, |
631 | 0 | UNCONST(void *, ev->rctx)), |
632 | 0 | ev->request, &ev->retry); |
633 | | |
634 | | /* |
635 | | * On final timeout, always mark the request as runnable. |
636 | | */ |
637 | 0 | talloc_free(ev); |
638 | 0 | unlang_interpret_mark_runnable(request); |
639 | 0 | } |
640 | | |
641 | | |
642 | | /** Yield a request back to the interpreter, with retries |
643 | | * |
644 | | * This passes control of the request back to the unlang interpreter, setting |
645 | | * callbacks to execute when the request is 'signalled' asynchronously, or when |
646 | | * the retry timer hits. |
647 | | * |
648 | | * @note The module function which calls #unlang_module_yield_to_retry should return control |
649 | | * of the C stack to the unlang interpreter immediately after calling #unlang_module_yield_to_retry. |
650 | | * A common pattern is to use ``return unlang_module_yield_to_retry(...)``. |
651 | | * |
652 | | * @param[in] request The current request. |
653 | | * @param[in] resume Called on unlang_interpret_mark_runnable(). |
654 | | * @param[in] retry Called on when a retry timer hits |
655 | | * @param[in] signal Called on unlang_action(). |
656 | | * @param[in] sigmask Set of signals to block. |
657 | | * @param[in] rctx to pass to the callbacks. |
658 | | * @param[in] retry_cfg to set up the retries |
659 | | * @return |
660 | | * - XLAT_ACTION_YIELD on success |
661 | | * - XLAT_ACTION_FAIL on failure |
662 | | */ |
663 | | xlat_action_t unlang_xlat_yield_to_retry(request_t *request, xlat_func_t resume, fr_unlang_xlat_retry_t retry, |
664 | | xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx, |
665 | | fr_retry_config_t const *retry_cfg) |
666 | 0 | { |
667 | 0 | unlang_stack_t *stack = request->stack; |
668 | 0 | unlang_stack_frame_t *frame = &stack->frame[stack->depth]; |
669 | 0 | unlang_xlat_retry_t *ev; |
670 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
671 | |
|
672 | 0 | fr_assert(stack->depth > 0); |
673 | 0 | fr_assert(frame->instruction->type == UNLANG_TYPE_XLAT); |
674 | |
|
675 | 0 | if (!state->event_ctx) MEM(state->event_ctx = talloc_zero(state, bool)); |
676 | |
|
677 | 0 | MEM(ev = talloc_zero(state->event_ctx, unlang_xlat_retry_t)); |
678 | |
|
679 | 0 | ev->request = request; |
680 | 0 | fr_assert(state->exp->type == XLAT_FUNC); |
681 | 0 | ev->inst = state->exp->call.inst; |
682 | 0 | ev->thread = xlat_thread_instance_find(state->exp); |
683 | 0 | ev->retry_cb = retry; |
684 | 0 | ev->rctx = rctx; |
685 | |
|
686 | 0 | fr_retry_init(&ev->retry, fr_time(), retry_cfg); |
687 | |
|
688 | 0 | if (fr_timer_at(request, unlang_interpret_event_list(request)->tl, |
689 | 0 | &ev->ev, ev->retry.next, |
690 | 0 | false, unlang_xlat_event_retry_handler, ev) < 0) { |
691 | 0 | RPEDEBUG("Failed inserting event"); |
692 | 0 | talloc_free(ev); |
693 | 0 | return XLAT_ACTION_FAIL; |
694 | 0 | } |
695 | | |
696 | 0 | talloc_set_destructor(ev, _unlang_xlat_retry_free); |
697 | |
|
698 | 0 | return unlang_xlat_yield(request, resume, signal, sigmask, rctx); |
699 | 0 | } |
700 | | |
701 | | /** Evaluate a "pure" (or not impure) xlat |
702 | | * |
703 | | * @param[in] ctx To allocate value boxes and values in. |
704 | | * @param[out] out Where to write the result of the expansion. |
705 | | * @param[in] request to push xlat onto. |
706 | | * @param[in] xlat to evaluate. |
707 | | * @return |
708 | | * - 0 on success. |
709 | | * - -1 on failure. |
710 | | */ |
711 | | int unlang_xlat_eval(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, xlat_exp_head_t const *xlat) |
712 | 0 | { |
713 | 0 | unlang_result_t result = UNLANG_RESULT_NOT_SET; |
714 | |
|
715 | 0 | if (xlat->flags.impure_func) { |
716 | 0 | fr_strerror_const("Expansion requires async operations"); |
717 | 0 | return -1; |
718 | 0 | } |
719 | | |
720 | 0 | if (unlang_xlat_push(ctx, &result, out, request, xlat, UNLANG_TOP_FRAME) < 0) return -1; |
721 | | |
722 | 0 | (void) unlang_interpret(request, UNLANG_REQUEST_RUNNING); |
723 | |
|
724 | 0 | if (!XLAT_RESULT_SUCCESS(&result)) return -1; |
725 | | |
726 | 0 | return 0; |
727 | 0 | } |
728 | | |
729 | | /** Evaluate a "pure" (or not impure) xlat |
730 | | * |
731 | | * @param[in] ctx To allocate value boxes and values in. |
732 | | * @param[out] vb output value-box |
733 | | * @param[in] type expected type |
734 | | * @param[in] enumv enum for type |
735 | | * @param[in] request to push xlat onto. |
736 | | * @param[in] xlat to evaluate. |
737 | | * @return |
738 | | * - 0 on success. |
739 | | * - -1 on failure. |
740 | | */ |
741 | | int unlang_xlat_eval_type(TALLOC_CTX *ctx, fr_value_box_t *vb, fr_type_t type, fr_dict_attr_t const *enumv, request_t *request, xlat_exp_head_t const *xlat) |
742 | 0 | { |
743 | 0 | fr_value_box_t *src; |
744 | 0 | fr_value_box_list_t list; |
745 | |
|
746 | 0 | if (fr_type_is_structural(type)) { |
747 | 0 | fr_strerror_const("Invalid type for output of evaluation"); |
748 | 0 | return -1; |
749 | 0 | } |
750 | | |
751 | 0 | fr_value_box_list_init(&list); |
752 | |
|
753 | 0 | if (unlang_xlat_eval(ctx, &list, request, xlat) < 0) return -1; |
754 | | |
755 | 0 | fr_value_box_init(vb, type, NULL, false); |
756 | |
|
757 | 0 | switch (type) { |
758 | 0 | default: |
759 | | /* |
760 | | * Take only the first entry from the list. |
761 | | */ |
762 | 0 | src = fr_value_box_list_head(&list); |
763 | 0 | if (!src) { |
764 | 0 | fr_strerror_const("Expression returned no results"); |
765 | 0 | fail: |
766 | 0 | fr_value_box_list_talloc_free(&list); |
767 | 0 | return -1; |
768 | 0 | } |
769 | | |
770 | 0 | if (fr_value_box_cast(ctx, vb, type, enumv, src) < 0) goto fail; |
771 | 0 | fr_value_box_list_talloc_free(&list); |
772 | 0 | break; |
773 | | |
774 | 0 | case FR_TYPE_STRING: |
775 | 0 | case FR_TYPE_OCTETS: |
776 | | /* |
777 | | * No output: create an empty string. |
778 | | * |
779 | | * The "concat in place" function returns an error for empty input, which is arguably not |
780 | | * what we want to do here. |
781 | | */ |
782 | 0 | if (fr_value_box_list_empty(&list)) { |
783 | 0 | break; |
784 | 0 | } |
785 | | |
786 | 0 | if (fr_value_box_list_concat_in_place(ctx, vb, &list, type, FR_VALUE_BOX_LIST_FREE_BOX, false, SIZE_MAX) < 0) { |
787 | 0 | goto fail; |
788 | 0 | } |
789 | 0 | break; |
790 | 0 | } |
791 | | |
792 | 0 | return 0; |
793 | 0 | } |
794 | | |
795 | | static void unlang_xlat_dump(request_t *request, unlang_stack_frame_t *frame) |
796 | 0 | { |
797 | 0 | unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); |
798 | 0 | xlat_exp_t const *exp = state->exp; |
799 | |
|
800 | 0 | if (exp) RDEBUG("expression %s", exp->fmt); |
801 | 0 | } |
802 | | /** Register xlat operation with the interpreter |
803 | | * |
804 | | */ |
805 | | void unlang_xlat_init(void) |
806 | 4 | { |
807 | 4 | unlang_register(&(unlang_op_t){ |
808 | 4 | .name = "xlat", |
809 | 4 | .type = UNLANG_TYPE_XLAT, |
810 | 4 | .flag = UNLANG_OP_FLAG_INTERNAL, |
811 | | |
812 | 4 | .interpret = unlang_xlat, |
813 | 4 | .signal = unlang_xlat_signal, |
814 | 4 | .dump = unlang_xlat_dump, |
815 | | |
816 | 4 | .frame_state_size = sizeof(unlang_frame_state_xlat_t), |
817 | 4 | .frame_state_type = "unlang_frame_state_xlat_t", |
818 | 4 | }); |
819 | 4 | } |