Coverage Report

Created: 2026-06-30 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/function.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: 09fe70cc6bebd98164cedd345174fe123cedae27 $
19
 *
20
 * @file unlang/function.c
21
 * @brief Unlang "function" keyword evaluation.
22
23
 * @copyright 2018,2021 The FreeRADIUS server project
24
 * @copyright 2018,2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
25
 */
26
RCSID("$Id: 09fe70cc6bebd98164cedd345174fe123cedae27 $")
27
28
#include "action.h"
29
#include "unlang_priv.h"
30
#include "function.h"
31
32
0
#define FUNC(_state) *((void **)&state->func)
33
0
#define REPEAT(_state) *((void **)&state->repeat)
34
35
/*
36
 *  Some functions differ mainly in their parsing
37
 */
38
typedef struct {
39
  union {
40
    unlang_function_no_result_t nres;   //!< To call when going down the stack.
41
    unlang_function_with_result_t wres;   //!< To call when going down the stack.
42
  } func;
43
  char const      *func_name;   //!< Debug name for the function.
44
45
  union {
46
    unlang_function_no_result_t nres;   //!< To call when going back up the stack.
47
    unlang_function_with_result_t wres;   //!< To call when going back up the stack.
48
  } repeat;
49
  unlang_function_type_t    type;     //!< Record whether we need to call the
50
  char const      *repeat_name;   //!< Debug name for the repeat function.
51
52
  unlang_function_signal_t  signal;     //!< Signal function to call.
53
  fr_signal_t     sigmask;    //!< Signals to block.
54
  char const      *signal_name;   //!< Debug name for the signal function.
55
  void        *uctx;      //!< Uctx to pass to function.
56
} unlang_frame_state_func_t;
57
58
/** Static instruction for allowing modules/xlats to call functions within themselves, or submodules
59
 *
60
 */
61
static unlang_t function_instruction = {
62
  .type = UNLANG_TYPE_FUNCTION,
63
  .name = "function",
64
  .debug_name = "function",
65
  .actions = DEFAULT_MOD_ACTIONS,
66
};
67
68
/** Generic signal handler
69
 *
70
 * @param[in] request   being signalled.
71
 * @param[in] frame   being signalled.
72
 * @param[in] action    Type of signal.
73
 */
74
static void unlang_function_signal(request_t *request,
75
           unlang_stack_frame_t *frame, fr_signal_t action)
76
0
{
77
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
78
79
0
  if (!state->signal || (action & state->sigmask)) return;
80
81
0
  state->signal(request, action, state->uctx);
82
0
}
83
84
85
/*
86
 *  Don't let the callback mess with the current
87
 *  module permanently.
88
 */
89
#define STORE_CALLER \
90
0
  char const *caller; \
91
0
  caller = request->module; \
92
0
  request->module = NULL
93
94
#define RESTORE_CALLER \
95
0
  request->module = caller;
96
97
/** Call a generic function that produces a result
98
 *
99
 * @param[out] p_result   The frame result.
100
 * @param[in] request   The current request.
101
 * @param[in] frame   The current frame.
102
 */
103
static unlang_action_t call_with_result_repeat(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
104
0
{
105
0
  unlang_action_t     ua;
106
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
107
0
  unlang_function_with_result_t func;
108
109
0
  STORE_CALLER;
110
111
0
  if (!REPEAT(state)) {
112
0
    RDEBUG4("Repeat function is NULL, likely due to previous yield, skipping call");
113
0
    ua = UNLANG_ACTION_CALCULATE_RESULT;
114
0
    goto done;
115
0
  }
116
117
0
again:
118
0
  RDEBUG4("Calling repeat function %p (%s)", REPEAT(state), state->repeat_name);
119
120
  /*
121
   *  Only called once...
122
   */
123
0
  func = state->repeat.wres;
124
0
  REPEAT(state) = NULL;
125
0
  state->repeat_name = NULL;
126
0
  ua = func(p_result, request, state->uctx);
127
0
  if (REPEAT(state)) { /* set again by func */
128
0
    switch (ua) {
129
0
    case UNLANG_ACTION_CALCULATE_RESULT:
130
0
      goto again;
131
132
0
    default:
133
0
      frame_repeat(frame, call_with_result_repeat);
134
0
    }
135
0
  }
136
137
0
done:
138
0
  RESTORE_CALLER;
139
140
0
  return ua;
141
0
}
142
143
/** Call a generic function that produces a result
144
 *
145
 * @param[out] p_result   The frame result.
146
 * @param[in] request   The current request.
147
 * @param[in] frame   The current frame.
148
 */
149
static unlang_action_t call_with_result(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
150
0
{
151
0
  unlang_action_t     ua;
152
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
153
154
0
  STORE_CALLER;
155
156
0
  RDEBUG4("Calling function %p (%s)", FUNC(state), state->func_name);
157
0
  ua = state->func.wres(p_result, request, state->uctx);
158
0
  FUNC(state) = NULL;
159
0
  state->func_name = NULL;
160
0
  if (REPEAT(state)) {
161
0
    switch (ua) {
162
0
    case UNLANG_ACTION_CALCULATE_RESULT:
163
0
      ua = call_with_result_repeat(p_result, request, frame);
164
0
      break;
165
166
0
    default:
167
0
      frame_repeat(frame, call_with_result_repeat);
168
0
    }
169
0
  }
170
0
  RESTORE_CALLER;
171
172
0
  return ua;
173
0
}
174
175
/** Call a generic function that produces a result
176
 *
177
 * @param[out] p_result   The frame result.
178
 * @param[in] request   The current request.
179
 * @param[in] frame   The current frame.
180
 */
181
static unlang_action_t call_no_result_repeat(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
182
0
{
183
0
  unlang_action_t     ua;
184
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
185
0
  unlang_function_no_result_t func;
186
187
0
  STORE_CALLER;
188
189
0
  if (!REPEAT(state)) {
190
0
    RDEBUG4("Repeat function is NULL, likely due to previous yield, skipping call");
191
0
    ua = UNLANG_ACTION_CALCULATE_RESULT;
192
0
    goto done;
193
0
  }
194
195
0
again:
196
0
  RDEBUG4("Calling repeat function %p (%s)", REPEAT(state), state->repeat_name);
197
198
  /*
199
   *  Only called once...
200
   */
201
0
  func = state->repeat.nres;
202
0
  REPEAT(state) = NULL;
203
0
  state->repeat_name = NULL;
204
0
  ua = func(request, state->uctx);
205
0
  if (REPEAT(state)) { /* set again by func */
206
0
    switch (ua) {
207
0
    case UNLANG_ACTION_CALCULATE_RESULT:
208
0
      goto again;
209
210
0
    case UNLANG_ACTION_FAIL:
211
0
    no_action_fail:
212
0
      fr_assert_msg(0, "Function %s (%p) is not allowed to indicate failure via UNLANG_ACTION_FAIL",
213
0
              state->repeat_name, REPEAT(state));
214
0
      ua = UNLANG_ACTION_CALCULATE_RESULT;
215
0
      break;
216
217
0
    default:
218
0
      frame_repeat(frame, call_no_result_repeat);
219
0
    }
220
0
  }
221
222
0
  if (ua == UNLANG_ACTION_FAIL) goto no_action_fail;
223
224
0
done:
225
0
  RESTORE_CALLER;
226
227
0
  return ua;
228
0
}
229
230
/** Call a generic function that produces a result
231
 *
232
 * @param[out] p_result   The frame result.
233
 * @param[in] request   The current request.
234
 * @param[in] frame   The current frame.
235
 */
236
static unlang_action_t call_no_result(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
237
0
{
238
0
  unlang_action_t     ua;
239
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
240
241
0
  STORE_CALLER;
242
243
0
  RDEBUG4("Calling function %p (%s)", FUNC(state), state->func_name);
244
0
  ua = state->func.nres(request, state->uctx);
245
0
  FUNC(state) = NULL;
246
0
  state->func_name = NULL;
247
0
  if (REPEAT(state)) {
248
0
    switch (ua) {
249
0
    case UNLANG_ACTION_CALCULATE_RESULT:
250
0
      ua = call_no_result_repeat(p_result, request, frame);
251
0
      break;
252
253
0
    case UNLANG_ACTION_FAIL:
254
0
    no_action_fail:
255
0
      fr_assert_msg(0, "Function is not allowed to indicate failure via UNLANG_ACTION_FAIL");
256
0
      ua = UNLANG_ACTION_CALCULATE_RESULT;
257
0
      break;
258
259
0
    default:
260
0
      frame_repeat(frame, call_no_result_repeat);
261
0
    }
262
0
  }
263
0
  if (ua == UNLANG_ACTION_FAIL) goto no_action_fail;
264
265
0
  RESTORE_CALLER;
266
267
0
  return ua;
268
0
}
269
270
/** Clear pending repeat function calls, and remove the signal handler.
271
 *
272
 * The function frame being modified must be at the top of the stack.
273
 *
274
 * @param[in] request The current request.
275
 * @return
276
 *  - 0 on success.
277
 *      - -1 on failure.
278
 */
279
int unlang_function_clear(request_t *request)
280
0
{
281
0
  unlang_stack_t      *stack = request->stack;
282
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
283
0
  unlang_frame_state_func_t *state;
284
285
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
286
0
    RERROR("Can't clear function on non-function frame");
287
0
    return -1;
288
0
  }
289
290
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
291
0
  REPEAT(state) = NULL;
292
0
  state->signal = NULL;
293
294
0
  repeatable_clear(frame);
295
296
0
  return 0;
297
0
}
298
299
/** Set a new signal function for an existing function frame
300
 *
301
 * @private
302
 *
303
 * The function frame being modified must be at the top of the stack.
304
 *
305
 * @param[in] request   The current request.
306
 * @param[in] signal    The signal function to set.
307
 * @param[in] sigmask   Signals to block.
308
 * @param[in] signal_name Name of the signal function call (for debugging).
309
 * @return
310
 *  - 0 on success.
311
 *      - -1 on failure.
312
 */
313
int _unlang_function_signal_set(request_t *request, unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name)
314
0
{
315
0
  unlang_stack_t      *stack = request->stack;
316
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
317
0
  unlang_frame_state_func_t *state;
318
319
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
320
0
    RERROR("Can't set repeat function on non-function frame");
321
0
    return -1;
322
0
  }
323
324
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
325
326
  /*
327
   *  If we're inside unlang_function_call,
328
   *  it'll pickup state->repeat and do the right thing
329
   *  once the current function returns.
330
   */
331
0
  state->signal = signal;
332
0
  state->sigmask = sigmask;
333
0
  state->signal_name = signal_name;
334
335
0
  return 0;
336
0
}
337
338
/** Set a new repeat function for an existing function frame
339
 *
340
 * @private
341
 *
342
 * The function frame being modified must be at the top of the stack.
343
 *
344
 * @param[in] request   The current request.
345
 * @param[in] repeat    the repeat function to set.
346
 * @param[in] repeat_name Name of the repeat function call (for debugging).
347
 * @param[in] type    Type of repeat function (with or without result).
348
 * @return
349
 *  - 0 on success.
350
 *      - -1 on failure.
351
 */
352
int _unlang_function_repeat_set(request_t *request, void *repeat, char const *repeat_name, unlang_function_type_t type)
353
0
{
354
0
  unlang_stack_t      *stack = request->stack;
355
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
356
0
  unlang_frame_state_func_t *state;
357
358
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
359
0
    RERROR("Can't set repeat function on non-function frame");
360
0
    return -1;
361
0
  }
362
363
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
364
365
0
  if (unlikely(state->type != type)) {
366
0
    fr_assert_msg(0, "Function type mismatch \"%s\"", repeat_name);
367
0
    return -1;
368
0
  }
369
370
  /*
371
   *  If we're inside unlang_function_call,
372
   *  it'll pickup state->repeat and do the right thing
373
   *  once the current function returns.
374
   */
375
0
  REPEAT(state) = repeat;
376
0
  state->repeat_name = repeat_name;
377
0
  repeatable_set(frame);
378
379
0
  return 0;
380
0
}
381
382
static inline CC_HINT(always_inline)
383
unlang_action_t unlang_function_push_common(unlang_result_t *p_result,
384
              request_t *request,
385
              void *func,
386
              char const *func_name,
387
              void *repeat,
388
              char const *repeat_name,
389
              unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name,
390
              unlang_function_type_t type,
391
              bool top_frame,
392
              void *uctx)
393
0
{
394
0
  unlang_stack_t      *stack = request->stack;
395
0
  unlang_stack_frame_t    *frame;
396
0
  unlang_frame_state_func_t *state;
397
398
0
  if (!func && !repeat) {
399
0
    fr_assert_msg(0, "function push must push at least one function!");
400
0
    return UNLANG_ACTION_FAIL;
401
0
  }
402
403
  /*
404
   *  Push module's function
405
   */
406
0
  if (unlang_interpret_push(p_result, request, &function_instruction,
407
0
          FRAME_CONF(RLM_MODULE_NOOP, top_frame), UNLANG_NEXT_STOP) < 0) {
408
0
    return UNLANG_ACTION_FAIL;
409
0
  }
410
411
0
  frame = &stack->frame[stack->depth];
412
413
  /*
414
   *  Initialize state
415
   */
416
0
  state = frame->state;
417
0
  state->signal = signal;
418
0
  state->sigmask = sigmask;
419
0
  state->signal_name = signal_name;
420
0
  state->type = type;
421
0
  state->uctx = uctx;
422
423
0
  FUNC(state) = func;
424
0
  state->func_name = func_name;
425
0
  REPEAT(state) = repeat;
426
0
  state->repeat_name = repeat_name;
427
428
0
  if (repeat) repeatable_set(frame); /* execute on the way back up */
429
430
0
  return UNLANG_ACTION_PUSHED_CHILD;
431
0
}
432
433
/** Push a generic function onto the unlang stack with a result
434
 *
435
 * @private
436
 *
437
 * These can be pushed by any other type of unlang op to allow a submodule or function
438
 * deeper in the C call stack to establish a new resumption point.
439
 *
440
 * @param[in] p_result    Where to write the result of the function evaluation.
441
 *
442
 * @param[in] request   The current request.
443
 * @param[in] func    to call going up the stack.
444
 * @param[in] func_name   Name of the function call (for debugging).
445
 * @param[in] repeat    function to call going back down the stack (may be NULL).
446
 *        This may be the same as func.
447
 * @param[in] repeat_name Name of the repeat function call (for debugging).
448
 * @param[in] signal    function to call if the request is signalled.
449
 * @param[in] sigmask   Signals to block.
450
 * @param[in] signal_name Name of the signal function call (for debugging).
451
 * @param[in] top_frame   Return out of the unlang interpreter when popping this frame.
452
 * @param[in] uctx    to pass to func(s).
453
 * @return
454
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
455
 *  - UNLANG_ACTION_FAIL on failure.
456
 */
457
unlang_action_t _unlang_function_push_with_result(unlang_result_t *p_result,
458
              request_t *request,
459
              unlang_function_with_result_t func, char const *func_name,
460
              unlang_function_with_result_t repeat, char const *repeat_name,
461
              unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name,
462
              bool top_frame, void *uctx)
463
0
{
464
0
  unlang_action_t ua;
465
0
  unlang_stack_frame_t *frame;
466
467
0
  ua = unlang_function_push_common(p_result,
468
0
           request,
469
0
           (void *) func, func_name,
470
0
           (void *) repeat, repeat_name,
471
0
           signal, sigmask, signal_name,
472
0
           UNLANG_FUNCTION_TYPE_WITH_RESULT, top_frame, uctx);
473
474
0
  if (unlikely(ua == UNLANG_ACTION_FAIL)) return UNLANG_ACTION_FAIL;
475
476
0
  frame = frame_current(request);
477
0
  if (!func && repeat) {
478
0
    frame->process = call_with_result_repeat;
479
0
  } else {
480
0
    frame->process = call_with_result;
481
0
  }
482
483
0
  return ua;
484
0
}
485
486
/** Push a generic function onto the unlang stack
487
 *
488
 * @private
489
 *
490
 * These can be pushed by any other type of unlang op to allow a submodule or function
491
 * deeper in the C call stack to establish a new resumption point.
492
 *
493
 * @param[in] request   The current request.
494
 * @param[in] func    to call going up the stack.
495
 * @param[in] func_name   Name of the function call (for debugging).
496
 * @param[in] repeat    function to call going back down the stack (may be NULL).
497
 *        This may be the same as func.
498
 * @param[in] repeat_name Name of the repeat function call (for debugging).
499
 * @param[in] signal    function to call if the request is signalled.
500
 * @param[in] sigmask   Signals to block.
501
 * @param[in] signal_name Name of the signal function call (for debugging).
502
 * @param[in] top_frame   Return out of the unlang interpreter when popping this frame.
503
 * @param[in] uctx    to pass to func(s).
504
 * @return
505
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
506
 *  - UNLANG_ACTION_FAIL on failure.
507
 */
508
unlang_action_t _unlang_function_push_no_result(request_t *request,
509
            unlang_function_no_result_t func, char const *func_name,
510
            unlang_function_no_result_t repeat, char const *repeat_name,
511
            unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name,
512
            bool top_frame, void *uctx)
513
0
{
514
0
  unlang_action_t ua;
515
0
  unlang_stack_frame_t *frame;
516
517
0
  ua = unlang_function_push_common(NULL,
518
0
           request,
519
0
           (void *) func, func_name,
520
0
           (void *) repeat, repeat_name,
521
0
           signal, sigmask, signal_name,
522
0
           UNLANG_FUNCTION_TYPE_NO_RESULT, top_frame, uctx);
523
524
0
  if (unlikely(ua == UNLANG_ACTION_FAIL)) return UNLANG_ACTION_FAIL;
525
526
0
  frame = frame_current(request);
527
0
  if (!func && repeat) {
528
0
    frame->process = call_no_result_repeat;
529
0
  }
530
531
  /* frame->process = call_no_result - This is the default, we don't need to set it again */
532
533
0
  return ua;
534
0
}
535
536
/** Custom frame state dumper
537
 *
538
 */
539
static void unlang_function_dump(request_t *request, unlang_stack_frame_t *frame)
540
0
{
541
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
542
543
0
  RDEBUG2("frame state");
544
0
  if (FUNC(state))   RDEBUG2("function       %p (%s)", FUNC(state), state->func_name);
545
0
  if (REPEAT(state)) RDEBUG2("repeat         %p (%s)", REPEAT(state), state->repeat_name);
546
0
  if (state->signal) RDEBUG2("signal         %p (%s)", state->signal, state->signal_name);
547
0
}
548
549
void unlang_function_init(void)
550
4
{
551
4
  unlang_register(&(unlang_op_t){
552
4
      .name = "function",
553
4
      .type = UNLANG_TYPE_FUNCTION,
554
4
      .flag = UNLANG_OP_FLAG_RETURN_POINT | UNLANG_OP_FLAG_INTERNAL,
555
556
4
      .interpret = call_no_result,
557
4
      .signal = unlang_function_signal,
558
4
      .dump = unlang_function_dump,
559
560
4
      .unlang_size = sizeof(unlang_group_t),
561
4
      .unlang_name = "unlang_group_t",
562
563
4
      .frame_state_size = sizeof(unlang_frame_state_func_t),
564
4
      .frame_state_type = "unlang_frame_state_func_t",
565
4
    });
566
4
}