Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}