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/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: 1f915a094e973050483243aa965c5c6458d8a7d3 $
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: 1f915a094e973050483243aa965c5c6458d8a7d3 $")
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
  RDEBUG4("Calling repeat function %p (%s)", REPEAT(state), state->repeat_name);
118
119
  /*
120
   *  Only called once...
121
   */
122
0
  func = state->repeat.wres;
123
0
  REPEAT(state) = NULL;
124
0
  state->repeat_name = NULL;
125
0
  ua = func(p_result, request, state->uctx);
126
0
  if (REPEAT(state)) { /* set again by func */
127
0
    switch (ua) {
128
0
    case UNLANG_ACTION_CALCULATE_RESULT:
129
0
      break;
130
131
0
    default:
132
0
      frame_repeat(frame, call_with_result_repeat);
133
0
    }
134
0
  }
135
136
0
done:
137
0
  RESTORE_CALLER;
138
139
0
  return ua;
140
0
}
141
142
/** Call a generic function that produces a result
143
 *
144
 * @param[out] p_result   The frame result.
145
 * @param[in] request   The current request.
146
 * @param[in] frame   The current frame.
147
 */
148
static unlang_action_t call_with_result(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
149
0
{
150
0
  unlang_action_t     ua;
151
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
152
153
0
  STORE_CALLER;
154
155
0
  RDEBUG4("Calling function %p (%s)", FUNC(state), state->func_name);
156
0
  ua = state->func.wres(p_result, request, state->uctx);
157
0
  FUNC(state) = NULL;
158
0
  state->func_name = NULL;
159
0
  if (REPEAT(state)) {
160
0
    switch (ua) {
161
0
    case UNLANG_ACTION_CALCULATE_RESULT:
162
0
      break;
163
164
0
    default:
165
0
      frame_repeat(frame, call_with_result_repeat);
166
0
    }
167
0
  }
168
0
  RESTORE_CALLER;
169
170
0
  return ua;
171
0
}
172
173
/** Call a generic function that produces a result
174
 *
175
 * @param[out] p_result   The frame result.
176
 * @param[in] request   The current request.
177
 * @param[in] frame   The current frame.
178
 */
179
static unlang_action_t call_no_result_repeat(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
180
0
{
181
0
  unlang_action_t     ua;
182
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
183
0
  unlang_function_no_result_t func;
184
185
0
  STORE_CALLER;
186
187
0
  if (!REPEAT(state)) {
188
0
    RDEBUG4("Repeat function is NULL, likely due to previous yield, skipping call");
189
0
    ua = UNLANG_ACTION_CALCULATE_RESULT;
190
0
    goto done;
191
0
  }
192
193
0
  RDEBUG4("Calling repeat function %p (%s)", REPEAT(state), state->repeat_name);
194
195
  /*
196
   *  Only called once...
197
   */
198
0
  func = state->repeat.nres;
199
0
  REPEAT(state) = NULL;
200
0
  state->repeat_name = NULL;
201
0
  ua = func(request, state->uctx);
202
0
  if (REPEAT(state)) { /* set again by func */
203
0
    switch (ua) {
204
0
    case UNLANG_ACTION_CALCULATE_RESULT:
205
0
      break;
206
207
0
    case UNLANG_ACTION_FAIL:
208
0
    no_action_fail:
209
0
      fr_assert_msg(0, "Function %s (%p) is not allowed to indicate failure via UNLANG_ACTION_FAIL",
210
0
              state->repeat_name, REPEAT(state));
211
0
      ua = UNLANG_ACTION_CALCULATE_RESULT;
212
0
      break;
213
214
0
    default:
215
0
      frame_repeat(frame, call_no_result_repeat);
216
0
    }
217
0
  }
218
219
0
  if (ua == UNLANG_ACTION_FAIL) goto no_action_fail;
220
221
0
done:
222
0
  RESTORE_CALLER;
223
224
0
  return ua;
225
0
}
226
227
/** Call a generic function that produces a result
228
 *
229
 * @param[out] p_result   The frame result.
230
 * @param[in] request   The current request.
231
 * @param[in] frame   The current frame.
232
 */
233
static unlang_action_t call_no_result(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
234
0
{
235
0
  unlang_action_t     ua;
236
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
237
238
0
  STORE_CALLER;
239
240
0
  RDEBUG4("Calling function %p (%s)", FUNC(state), state->func_name);
241
0
  ua = state->func.nres(request, state->uctx);
242
0
  FUNC(state) = NULL;
243
0
  state->func_name = NULL;
244
0
  if (REPEAT(state)) {
245
0
    switch (ua) {
246
0
    case UNLANG_ACTION_CALCULATE_RESULT:
247
0
      break;
248
249
0
    case UNLANG_ACTION_FAIL:
250
0
    no_action_fail:
251
0
      fr_assert_msg(0, "Function is not allowed to indicate failure via UNLANG_ACTION_FAIL");
252
0
      ua = UNLANG_ACTION_CALCULATE_RESULT;
253
0
      break;
254
255
0
    default:
256
0
      frame_repeat(frame, call_no_result_repeat);
257
0
    }
258
0
  }
259
0
  if (ua == UNLANG_ACTION_FAIL) goto no_action_fail;
260
261
0
  RESTORE_CALLER;
262
263
0
  return ua;
264
0
}
265
266
/** Clear pending repeat function calls, and remove the signal handler.
267
 *
268
 * The function frame being modified must be at the top of the stack.
269
 *
270
 * @param[in] request The current request.
271
 * @return
272
 *  - 0 on success.
273
 *      - -1 on failure.
274
 */
275
int unlang_function_clear(request_t *request)
276
0
{
277
0
  unlang_stack_t      *stack = request->stack;
278
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
279
0
  unlang_frame_state_func_t *state;
280
281
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
282
0
    RERROR("Can't clear function on non-function frame");
283
0
    return -1;
284
0
  }
285
286
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
287
0
  REPEAT(state) = NULL;
288
0
  state->signal = NULL;
289
290
0
  repeatable_clear(frame);
291
292
0
  return 0;
293
0
}
294
295
/** Clear the pending repeat function, and keep the signal handler
296
 *
297
 * If a unlang function returns UNLANG_ACTION_CALCULATE_RESULT with a repeat
298
 * function set, the repeat function is never executed, and the signal handler
299
 * for the frame (if set) is called to clean up state.
300
 *
301
 * If the returning function does not want the cancellation callback to execute
302
 * it should call `unlang_function_repeat_clear` to clear the repeat function.
303
 *
304
 * @param[in] request The current request.
305
 * @return
306
 *  - 0 on success.
307
 *      - -1 on failure.
308
 */
309
int unlang_function_repeat_clear(request_t *request)
310
0
{
311
0
  unlang_stack_t      *stack = request->stack;
312
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
313
314
0
  unlang_frame_state_func_t *state;
315
316
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
317
0
    RERROR("Can't clear repeat function on non-function frame");
318
0
    return -1;
319
0
  }
320
321
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
322
0
  REPEAT(state) = NULL;
323
0
  state->repeat_name = NULL;
324
0
  repeatable_clear(frame);
325
326
0
  return 0;
327
0
}
328
329
/** Set a new signal function for an existing function frame
330
 *
331
 * @private
332
 *
333
 * The function frame being modified must be at the top of the stack.
334
 *
335
 * @param[in] request   The current request.
336
 * @param[in] signal    The signal function to set.
337
 * @param[in] sigmask   Signals to block.
338
 * @param[in] signal_name Name of the signal function call (for debugging).
339
 * @return
340
 *  - 0 on success.
341
 *      - -1 on failure.
342
 */
343
int _unlang_function_signal_set(request_t *request, unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name)
344
0
{
345
0
  unlang_stack_t      *stack = request->stack;
346
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
347
0
  unlang_frame_state_func_t *state;
348
349
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
350
0
    RERROR("Can't set repeat function on non-function frame");
351
0
    return -1;
352
0
  }
353
354
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
355
356
  /*
357
   *  If we're inside unlang_function_call,
358
   *  it'll pickup state->repeat and do the right thing
359
   *  once the current function returns.
360
   */
361
0
  state->signal = signal;
362
0
  state->sigmask = sigmask;
363
0
  state->signal_name = signal_name;
364
365
0
  return 0;
366
0
}
367
368
/** Set a new repeat function for an existing function frame
369
 *
370
 * @private
371
 *
372
 * The function frame being modified must be at the top of the stack.
373
 *
374
 * @param[in] request   The current request.
375
 * @param[in] repeat    the repeat function to set.
376
 * @param[in] repeat_name Name of the repeat function call (for debugging).
377
 * @param[in] type    Type of repeat function (with or without result).
378
 * @return
379
 *  - 0 on success.
380
 *      - -1 on failure.
381
 */
382
int _unlang_function_repeat_set(request_t *request, void *repeat, char const *repeat_name, unlang_function_type_t type)
383
0
{
384
0
  unlang_stack_t      *stack = request->stack;
385
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
386
0
  unlang_frame_state_func_t *state;
387
388
0
  if (frame->instruction->type != UNLANG_TYPE_FUNCTION) {
389
0
    RERROR("Can't set repeat function on non-function frame");
390
0
    return -1;
391
0
  }
392
393
0
  state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
394
395
0
  if (unlikely(state->type != type)) {
396
0
    fr_assert_msg(0, "Function type mismatch \"%s\"", repeat_name);
397
0
    return -1;
398
0
  }
399
400
  /*
401
   *  If we're inside unlang_function_call,
402
   *  it'll pickup state->repeat and do the right thing
403
   *  once the current function returns.
404
   */
405
0
  REPEAT(state) = repeat;
406
0
  state->repeat_name = repeat_name;
407
0
  repeatable_set(frame);
408
409
0
  return 0;
410
0
}
411
412
static inline CC_HINT(always_inline)
413
unlang_action_t unlang_function_push_common(unlang_result_t *p_result,
414
              request_t *request,
415
              void *func,
416
              char const *func_name,
417
              void *repeat,
418
              char const *repeat_name,
419
              unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name,
420
              unlang_function_type_t type,
421
              bool top_frame,
422
              void *uctx)
423
0
{
424
0
  unlang_stack_t      *stack = request->stack;
425
0
  unlang_stack_frame_t    *frame;
426
0
  unlang_frame_state_func_t *state;
427
428
0
  if (!func && !repeat) {
429
0
    fr_assert_msg(0, "function push must push at least one function!");
430
0
    return UNLANG_ACTION_FAIL;
431
0
  }
432
433
  /*
434
   *  Push module's function
435
   */
436
0
  if (unlang_interpret_push(p_result, request, &function_instruction,
437
0
          FRAME_CONF(RLM_MODULE_NOOP, top_frame), UNLANG_NEXT_STOP) < 0) {
438
0
    return UNLANG_ACTION_FAIL;
439
0
  }
440
441
0
  frame = &stack->frame[stack->depth];
442
443
  /*
444
   *  Initialize state
445
   */
446
0
  state = frame->state;
447
0
  state->signal = signal;
448
0
  state->sigmask = sigmask;
449
0
  state->signal_name = signal_name;
450
0
  state->type = type;
451
0
  state->uctx = uctx;
452
453
0
  FUNC(state) = func;
454
0
  state->func_name = func_name;
455
0
  REPEAT(state) = repeat;
456
0
  state->repeat_name = repeat_name;
457
458
0
  if (repeat) repeatable_set(frame); /* execute on the way back up */
459
460
0
  return UNLANG_ACTION_PUSHED_CHILD;
461
0
}
462
463
/** Push a generic function onto the unlang stack with a result
464
 *
465
 * @private
466
 *
467
 * These can be pushed by any other type of unlang op to allow a submodule or function
468
 * deeper in the C call stack to establish a new resumption point.
469
 *
470
 * @param[in] p_result    Where to write the result of the function evaluation.
471
 *
472
 * @param[in] request   The current request.
473
 * @param[in] func    to call going up the stack.
474
 * @param[in] func_name   Name of the function call (for debugging).
475
 * @param[in] repeat    function to call going back down the stack (may be NULL).
476
 *        This may be the same as func.
477
 * @param[in] repeat_name Name of the repeat function call (for debugging).
478
 * @param[in] signal    function to call if the request is signalled.
479
 * @param[in] sigmask   Signals to block.
480
 * @param[in] signal_name Name of the signal function call (for debugging).
481
 * @param[in] top_frame   Return out of the unlang interpreter when popping this frame.
482
 * @param[in] uctx    to pass to func(s).
483
 * @return
484
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
485
 *  - UNLANG_ACTION_FAIL on failure.
486
 */
487
unlang_action_t _unlang_function_push_with_result(unlang_result_t *p_result,
488
              request_t *request,
489
              unlang_function_with_result_t func, char const *func_name,
490
              unlang_function_with_result_t repeat, char const *repeat_name,
491
              unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name,
492
              bool top_frame, void *uctx)
493
0
{
494
0
  unlang_action_t ua;
495
0
  unlang_stack_frame_t *frame;
496
497
0
  ua = unlang_function_push_common(p_result,
498
0
           request,
499
0
           (void *) func, func_name,
500
0
           (void *) repeat, repeat_name,
501
0
           signal, sigmask, signal_name,
502
0
           UNLANG_FUNCTION_TYPE_WITH_RESULT, top_frame, uctx);
503
504
0
  if (unlikely(ua == UNLANG_ACTION_FAIL)) return UNLANG_ACTION_FAIL;
505
506
0
  frame = frame_current(request);
507
0
  if (!func && repeat) {
508
0
    frame->process = call_with_result_repeat;
509
0
  } else {
510
0
    frame->process = call_with_result;
511
0
  }
512
513
0
  return ua;
514
0
}
515
516
/** Push a generic function onto the unlang stack
517
 *
518
 * @private
519
 *
520
 * These can be pushed by any other type of unlang op to allow a submodule or function
521
 * deeper in the C call stack to establish a new resumption point.
522
 *
523
 * @param[in] request   The current request.
524
 * @param[in] func    to call going up the stack.
525
 * @param[in] func_name   Name of the function call (for debugging).
526
 * @param[in] repeat    function to call going back down the stack (may be NULL).
527
 *        This may be the same as func.
528
 * @param[in] repeat_name Name of the repeat function call (for debugging).
529
 * @param[in] signal    function to call if the request is signalled.
530
 * @param[in] sigmask   Signals to block.
531
 * @param[in] signal_name Name of the signal function call (for debugging).
532
 * @param[in] top_frame   Return out of the unlang interpreter when popping this frame.
533
 * @param[in] uctx    to pass to func(s).
534
 * @return
535
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
536
 *  - UNLANG_ACTION_FAIL on failure.
537
 */
538
unlang_action_t _unlang_function_push_no_result(request_t *request,
539
            unlang_function_no_result_t func, char const *func_name,
540
            unlang_function_no_result_t repeat, char const *repeat_name,
541
            unlang_function_signal_t signal, fr_signal_t sigmask, char const *signal_name,
542
            bool top_frame, void *uctx)
543
0
{
544
0
  unlang_action_t ua;
545
0
  unlang_stack_frame_t *frame;
546
547
0
  ua = unlang_function_push_common(NULL,
548
0
           request,
549
0
           (void *) func, func_name,
550
0
           (void *) repeat, repeat_name,
551
0
           signal, sigmask, signal_name,
552
0
           UNLANG_FUNCTION_TYPE_NO_RESULT, top_frame, uctx);
553
554
0
  if (unlikely(ua == UNLANG_ACTION_FAIL)) return UNLANG_ACTION_FAIL;
555
556
0
  frame = frame_current(request);
557
0
  if (!func && repeat) {
558
0
    frame->process = call_no_result_repeat;
559
0
  }
560
561
  /* frame->process = call_no_result - This is the default, we don't need to set it again */
562
563
0
  return ua;
564
0
}
565
566
/** Custom frame state dumper
567
 *
568
 */
569
static void unlang_function_dump(request_t *request, unlang_stack_frame_t *frame)
570
0
{
571
0
  unlang_frame_state_func_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_func_t);
572
573
0
  RDEBUG2("frame state");
574
0
  if (FUNC(state))   RDEBUG2("function       %p (%s)", FUNC(state), state->func_name);
575
0
  if (REPEAT(state)) RDEBUG2("repeat         %p (%s)", REPEAT(state), state->repeat_name);
576
0
  if (state->signal) RDEBUG2("signal         %p (%s)", state->signal, state->signal_name);
577
0
}
578
579
void unlang_function_init(void)
580
4
{
581
4
  unlang_register(&(unlang_op_t){
582
4
      .name = "function",
583
4
      .type = UNLANG_TYPE_FUNCTION,
584
4
      .flag = (unlang_op_flag_t)(UNLANG_OP_FLAG_RETURN_POINT | UNLANG_OP_FLAG_INTERNAL),
585
586
4
      .interpret = call_no_result,
587
4
      .signal = unlang_function_signal,
588
4
      .dump = unlang_function_dump,
589
590
4
      .unlang_size = sizeof(unlang_group_t),
591
4
      .unlang_name = "unlang_group_t",
592
593
4
      .frame_state_size = sizeof(unlang_frame_state_func_t),
594
4
      .frame_state_type = "unlang_frame_state_func_t",
595
4
    });
596
4
}