/src/freeradius-server/src/lib/unlang/timeout.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: ac9dfeb2bdff62b56e49c4938dff1745b057934f $ |
19 | | * |
20 | | * @file unlang/timeout.c |
21 | | * @brief Unlang "timeout" keyword evaluation. |
22 | | * |
23 | | * @copyright 2022 Network RADIUS SAS (legal@networkradius.com) |
24 | | */ |
25 | | RCSID("$Id: ac9dfeb2bdff62b56e49c4938dff1745b057934f $") |
26 | | |
27 | | #include <freeradius-devel/unlang/interpret.h> |
28 | | #include "group_priv.h" |
29 | | #include "timeout_priv.h" |
30 | | #include "mod_action.h" |
31 | | #include "unlang_priv.h" |
32 | | |
33 | | typedef struct { |
34 | | bool fired; |
35 | | int depth; |
36 | | fr_time_delta_t timeout; |
37 | | request_t *request; |
38 | | rindent_t indent; |
39 | | fr_timer_t *ev; |
40 | | |
41 | | fr_value_box_list_t result; |
42 | | } unlang_frame_state_timeout_t; |
43 | | |
44 | | /** Immediately cancel the timeout if the frame is cancelled |
45 | | */ |
46 | | static void unlang_timeout_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_signal_t action) |
47 | 0 | { |
48 | 0 | unlang_frame_state_timeout_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_timeout_t); |
49 | |
|
50 | 0 | if (action == FR_SIGNAL_CANCEL) { |
51 | 0 | TALLOC_FREE(state->ev); |
52 | 0 | } |
53 | 0 | } |
54 | | |
55 | | static void unlang_timeout_handler(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *ctx) |
56 | 0 | { |
57 | 0 | unlang_frame_state_timeout_t *state = talloc_get_type_abort(ctx, unlang_frame_state_timeout_t); |
58 | 0 | request_t *request = talloc_get_type_abort(state->request, request_t); |
59 | 0 | unlang_stack_t *stack = request->stack; |
60 | 0 | unlang_stack_frame_t *frame = &stack->frame[stack->depth]; |
61 | 0 | char const *module; |
62 | | |
63 | | /* |
64 | | * Don't log in the context of the request |
65 | | */ |
66 | 0 | module = request->module; |
67 | 0 | request->module = NULL; |
68 | 0 | RDEBUG("Timeout reached, signalling interpreter to cancel child section."); |
69 | 0 | request->module = module; |
70 | | |
71 | | /* |
72 | | * Signal all the frames upto, but not including the timeout |
73 | | * frame to cancel. |
74 | | * |
75 | | * unlang_timeout_resume_done then runs, and returns "timeout" |
76 | | */ |
77 | 0 | unlang_stack_signal(request, FR_SIGNAL_CANCEL, state->depth); |
78 | | |
79 | | /* |
80 | | * If the frame is yielded (needs to be resumed), but was cancelled |
81 | | * we now need to mark it runnable again so it's unwound. |
82 | | * |
83 | | * If the frame _isn't_ cancelled, then it's non-cancellable and |
84 | | * something else will run it to completion, and mark |
85 | | * the request as complete. |
86 | | */ |
87 | 0 | if (is_yielded(frame) && is_unwinding(frame) && !unlang_request_is_scheduled(request)) unlang_interpret_mark_runnable(request); |
88 | 0 | state->fired = true; |
89 | |
|
90 | 0 | RINDENT_RESTORE(request, state); |
91 | 0 | } |
92 | | |
93 | | static unlang_action_t unlang_timeout_resume_done(unlang_result_t *p_result, UNUSED request_t *request, unlang_stack_frame_t *frame) |
94 | 0 | { |
95 | 0 | unlang_frame_state_timeout_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_timeout_t); |
96 | | |
97 | | /* |
98 | | * No timeout, we go to the next instruction. |
99 | | * |
100 | | * Unless the next instruction is a "catch timeout", in which case we skip it. |
101 | | */ |
102 | 0 | if (!state->fired) return UNLANG_ACTION_EXECUTE_NEXT; /* Don't modify the return code*/ |
103 | | |
104 | 0 | RETURN_UNLANG_TIMEOUT; |
105 | 0 | } |
106 | | |
107 | | static unlang_action_t unlang_timeout_set(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
108 | 0 | { |
109 | 0 | unlang_frame_state_timeout_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_timeout_t); |
110 | | |
111 | | /* |
112 | | * Save current indentation for the error path. |
113 | | */ |
114 | 0 | RINDENT_SAVE(state, request); |
115 | |
|
116 | 0 | if (fr_timer_in(state, unlang_interpret_event_list(request)->tl, &state->ev, state->timeout, |
117 | 0 | false, unlang_timeout_handler, state) < 0) { |
118 | 0 | RPEDEBUG("Failed inserting event"); |
119 | 0 | RETURN_UNLANG_ACTION_FATAL; |
120 | 0 | } |
121 | | |
122 | 0 | frame_repeat(frame, unlang_timeout_resume_done); |
123 | |
|
124 | 0 | return unlang_interpret_push_children(NULL, request, RLM_MODULE_NOT_SET, UNLANG_NEXT_SIBLING); |
125 | 0 | } |
126 | | |
127 | | static unlang_action_t unlang_timeout_done(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
128 | 0 | { |
129 | 0 | unlang_frame_state_timeout_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_timeout_t); |
130 | 0 | fr_value_box_t *box = fr_value_box_list_head(&state->result); |
131 | |
|
132 | 0 | if (!box) return UNLANG_ACTION_FAIL; |
133 | | |
134 | | /* |
135 | | * compile_timeout() ensures that the tmpl is cast to time_delta, so we don't have to do any more work here. |
136 | | */ |
137 | 0 | state->timeout = box->vb_time_delta; |
138 | | |
139 | | /* |
140 | | * Negative times, and times less than 1us are treated as "zero". |
141 | | */ |
142 | 0 | if (fr_time_delta_lteq(state->timeout, fr_time_delta_from_usec(1))) { |
143 | 0 | RETURN_UNLANG_TIMEOUT; |
144 | 0 | } |
145 | | |
146 | 0 | return unlang_timeout_set(p_result, request, frame); |
147 | 0 | } |
148 | | |
149 | | static unlang_action_t unlang_timeout(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
150 | 0 | { |
151 | 0 | unlang_group_t *g; |
152 | 0 | unlang_timeout_t *gext; |
153 | 0 | unlang_frame_state_timeout_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_timeout_t); |
154 | 0 | unlang_stack_t *stack = request->stack; |
155 | |
|
156 | 0 | g = unlang_generic_to_group(frame->instruction); |
157 | 0 | gext = unlang_group_to_timeout(g); |
158 | | |
159 | | /* |
160 | | * +1 so we don't mark the timeout frame as cancelled, |
161 | | * we want unlang_timeout_resume_done to be called. |
162 | | */ |
163 | 0 | state->depth = stack->depth + 1; |
164 | 0 | state->request = request; |
165 | |
|
166 | 0 | if (!gext->vpt) { |
167 | 0 | state->timeout = gext->timeout; |
168 | 0 | return unlang_timeout_set(p_result, request, frame); |
169 | 0 | } |
170 | | |
171 | 0 | fr_value_box_list_init(&state->result); |
172 | |
|
173 | 0 | if (unlang_tmpl_push(state, NULL, &state->result, request, gext->vpt, NULL, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL; |
174 | | |
175 | 0 | frame_repeat(frame, unlang_timeout_done); |
176 | |
|
177 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
178 | 0 | } |
179 | | |
180 | | static unlang_t *unlang_compile_timeout(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci) |
181 | 0 | { |
182 | 0 | CONF_SECTION *cs = cf_item_to_section(ci); |
183 | 0 | char const *name2; |
184 | 0 | unlang_t *c; |
185 | 0 | unlang_group_t *g; |
186 | 0 | unlang_timeout_t *gext; |
187 | 0 | fr_time_delta_t timeout = fr_time_delta_from_sec(0); |
188 | 0 | tmpl_t *vpt = NULL; |
189 | 0 | fr_token_t token; |
190 | | |
191 | | /* |
192 | | * Timeout <time ref> |
193 | | */ |
194 | 0 | name2 = cf_section_name2(cs); |
195 | 0 | if (!name2) { |
196 | 0 | cf_log_err(cs, "You must specify a time value for 'timeout'"); |
197 | 0 | print_url: |
198 | 0 | cf_log_err(ci, DOC_KEYWORD_REF(timeout)); |
199 | 0 | return NULL; |
200 | 0 | } |
201 | | |
202 | 0 | if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE; |
203 | | |
204 | 0 | g = unlang_group_allocate(parent, cs, UNLANG_TYPE_TIMEOUT); |
205 | 0 | if (!g) return NULL; |
206 | | |
207 | 0 | gext = unlang_group_to_timeout(g); |
208 | |
|
209 | 0 | token = cf_section_name2_quote(cs); |
210 | |
|
211 | 0 | if ((token == T_BARE_WORD) && isdigit((uint8_t) *name2)) { |
212 | 0 | if (fr_time_delta_from_str(&timeout, name2, strlen(name2), FR_TIME_RES_SEC) < 0) { |
213 | 0 | cf_log_err(cs, "Failed parsing time delta %s - %s", |
214 | 0 | name2, fr_strerror()); |
215 | 0 | return NULL; |
216 | 0 | } |
217 | 0 | } else { |
218 | 0 | fr_slen_t slen; |
219 | 0 | tmpl_rules_t t_rules; |
220 | | |
221 | | /* |
222 | | * We don't allow unknown attributes here. |
223 | | */ |
224 | 0 | t_rules = *(unlang_ctx->rules); |
225 | 0 | t_rules.attr.allow_unknown = false; |
226 | 0 | RULES_VERIFY(&t_rules); |
227 | | |
228 | 0 | slen = tmpl_afrom_substr(gext, &vpt, |
229 | 0 | &FR_SBUFF_IN_STR(name2), |
230 | 0 | token, |
231 | 0 | NULL, |
232 | 0 | &t_rules); |
233 | 0 | if (!vpt) { |
234 | 0 | cf_canonicalize_error(cs, slen, "Failed parsing argument to 'timeout'", name2); |
235 | 0 | talloc_free(g); |
236 | 0 | return NULL; |
237 | 0 | } |
238 | | |
239 | | /* |
240 | | * Fixup the tmpl so that we know it's somewhat sane. |
241 | | */ |
242 | 0 | if (!pass2_fixup_tmpl(gext, &vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) { |
243 | 0 | talloc_free(g); |
244 | 0 | return NULL; |
245 | 0 | } |
246 | | |
247 | 0 | if (tmpl_is_list(vpt)) { |
248 | 0 | cf_log_err(cs, "Cannot use list as argument for 'timeout' statement"); |
249 | 0 | error: |
250 | 0 | talloc_free(g); |
251 | 0 | goto print_url; |
252 | 0 | } |
253 | | |
254 | 0 | if (tmpl_contains_regex(vpt)) { |
255 | 0 | cf_log_err(cs, "Cannot use regular expression as argument for 'timeout' statement"); |
256 | 0 | goto error; |
257 | 0 | } |
258 | | |
259 | | /* |
260 | | * Attribute or data MUST be cast to TIME_DELTA. |
261 | | */ |
262 | 0 | if (tmpl_cast_set(vpt, FR_TYPE_TIME_DELTA) < 0) { |
263 | 0 | cf_log_perr(cs, "Failed setting cast type"); |
264 | 0 | goto error; |
265 | 0 | } |
266 | 0 | } |
267 | | |
268 | | /* |
269 | | * Compile the contents of a "timeout". |
270 | | */ |
271 | 0 | c = unlang_compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_TIMEOUT); |
272 | 0 | if (!c) return NULL; |
273 | | |
274 | 0 | g = unlang_generic_to_group(c); |
275 | 0 | gext = unlang_group_to_timeout(g); |
276 | 0 | gext->timeout = timeout; |
277 | 0 | gext->vpt = vpt; |
278 | |
|
279 | 0 | return c; |
280 | 0 | } |
281 | | |
282 | | void unlang_timeout_init(void) |
283 | 4 | { |
284 | 4 | unlang_register(&(unlang_op_t){ |
285 | 4 | .name = "timeout", |
286 | 4 | .type = UNLANG_TYPE_TIMEOUT, |
287 | 4 | .flag = (unlang_op_flag_t)(UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET), |
288 | | |
289 | 4 | .compile = unlang_compile_timeout, |
290 | 4 | .interpret = unlang_timeout, |
291 | 4 | .signal = unlang_timeout_signal, |
292 | | |
293 | 4 | .unlang_size = sizeof(unlang_timeout_t), |
294 | 4 | .unlang_name = "unlang_timeout_t", |
295 | | |
296 | 4 | .frame_state_size = sizeof(unlang_frame_state_timeout_t), |
297 | 4 | .frame_state_type = "unlang_frame_state_timeout_t", |
298 | 4 | }); |
299 | 4 | } |