Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/switch.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: 355fbca719c22c9144aa12f3c2c975fdee7c2bb1 $
19
 *
20
 * @file unlang/switch.c
21
 * @brief Unlang "switch" keyword evaluation.
22
 *
23
 * @copyright 2006-2019 The FreeRADIUS server project
24
 */
25
RCSID("$Id: 355fbca719c22c9144aa12f3c2c975fdee7c2bb1 $")
26
27
#include <freeradius-devel/server/rcode.h>
28
#include "group_priv.h"
29
#include "switch_priv.h"
30
#include "xlat_priv.h"
31
32
static unlang_action_t unlang_switch(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
33
0
{
34
0
  unlang_t    *found;
35
36
0
  unlang_group_t    *switch_g;
37
0
  unlang_switch_t   *switch_gext;
38
39
0
  fr_value_box_t const  *box = NULL;
40
41
0
  fr_pair_t   *vp;
42
43
  /*
44
   *  Mock up an unlang_cast_t.  Note that these on-stack
45
   *  buffers are the reason why case_cmp(), case_hash(),
46
   *  and case_to_key() use direct casts, and not the
47
   *  "generic to x" functions.
48
   */
49
0
  tmpl_t      case_vpt = (tmpl_t) {
50
0
          .type = TMPL_TYPE_DATA,
51
0
        };
52
0
  unlang_case_t   my_case = (unlang_case_t) {
53
0
          .group = (unlang_group_t) {
54
0
            .self = (unlang_t) {
55
0
              .type = UNLANG_TYPE_CASE,
56
0
            },
57
0
          },
58
0
          .vpt = &case_vpt,
59
0
        };
60
61
0
  switch_g = unlang_generic_to_group(frame->instruction);
62
0
  switch_gext = unlang_group_to_switch(switch_g);
63
64
0
  found = NULL;
65
66
  /*
67
   *  The attribute doesn't exist.  We can skip
68
   *  directly to the default 'case' statement.
69
   */
70
0
  if (tmpl_is_attr(switch_gext->vpt)) {
71
0
    if (tmpl_find_vp(&vp, request, switch_gext->vpt) < 0) {
72
0
      found = switch_gext->default_case;
73
0
      goto do_null_case;
74
0
    } else {
75
0
      box = &vp->data;
76
0
    }
77
78
  /*
79
   *  Expand the template if necessary, so that it
80
   *  is evaluated once instead of for each 'case'
81
   *  statement.
82
   */
83
0
  } else if (tmpl_is_xlat(switch_gext->vpt) ||
84
0
       tmpl_is_exec(switch_gext->vpt)) {
85
0
    ssize_t slen;
86
87
0
    slen = tmpl_aexpand_type(unlang_interpret_frame_talloc_ctx(request), &box, FR_TYPE_VALUE_BOX,
88
0
           request, switch_gext->vpt);
89
0
    if (slen < 0) {
90
0
      RDEBUG("Switch failed expanding %s - %s", switch_gext->vpt->name, fr_strerror());
91
0
      goto find_null_case;
92
0
    }
93
0
  } else if (!fr_cond_assert_msg(0, "Invalid tmpl type %s", tmpl_type_to_str(switch_gext->vpt->type))) {
94
0
    return UNLANG_ACTION_FAIL;
95
0
  }
96
97
  /*
98
   *  case_gext->vpt.data.literal is an in-line box, so we
99
   *  have to make a shallow copy of its contents.
100
   *
101
   *  Note: We do not pass a ctx here as we don't want to
102
   *  create a reference.
103
   */
104
0
  fr_value_box_copy_shallow(NULL, &case_vpt.data.literal, box);
105
0
  found = fr_htrie_find(switch_gext->ht, &my_case);
106
0
  if (!found) {
107
0
  find_null_case:
108
0
    found = switch_gext->default_case;
109
0
  }
110
111
0
do_null_case:
112
  /*
113
   *  Nothing found.  Just continue, and ignore the "switch"
114
   *  statement.
115
   */
116
0
  if (!found) {
117
0
    if (box) {
118
0
      RWDEBUG("Failed to find 'case' target for value %pV", box);
119
0
    } else {
120
0
      RWDEBUG("Failed to find 'default' target when expansion of %s returning no value",
121
0
        switch_gext->vpt->name);
122
0
    }
123
0
    return UNLANG_ACTION_EXECUTE_NEXT;
124
0
  }
125
126
0
  if (unlang_interpret_push(NULL, request, found, FRAME_CONF(RLM_MODULE_NOT_SET, UNLANG_SUB_FRAME), UNLANG_NEXT_STOP) < 0) {
127
0
    RETURN_UNLANG_ACTION_FATAL;
128
0
  }
129
130
0
  return UNLANG_ACTION_PUSHED_CHILD;
131
0
}
132
133
134
static unlang_action_t unlang_case(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
135
0
{
136
0
  unlang_group_t    *g = unlang_generic_to_group(frame->instruction);
137
138
0
  if (unlang_list_empty(&g->children)) RETURN_UNLANG_NOOP;
139
140
0
  return unlang_group(p_result, request, frame);
141
0
}
142
143
144
static unlang_t *unlang_compile_case(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
145
0
{
146
0
  CONF_SECTION    *cs = cf_item_to_section(ci);
147
0
  int     i;
148
0
  char const    *name2;
149
0
  unlang_t    *c;
150
0
  unlang_group_t    *case_g;
151
0
  unlang_case_t   *case_gext;
152
0
  tmpl_t      *vpt = NULL;
153
0
  tmpl_rules_t    t_rules;
154
155
  /*
156
   *  We allow unknown attributes here.
157
   */
158
0
  t_rules = *(unlang_ctx->rules);
159
0
  t_rules.attr.allow_unknown = true;
160
0
  RULES_VERIFY(&t_rules);
161
162
0
  if (!parent || (parent->type != UNLANG_TYPE_SWITCH)) {
163
0
    cf_log_err(cs, "\"case\" statements may only appear within a \"switch\" section");
164
0
    cf_log_err(ci, DOC_KEYWORD_REF(case));
165
0
    return NULL;
166
0
  }
167
168
  /*
169
   *  case THING means "match THING"
170
   *  case       means "match anything"
171
   */
172
0
  name2 = cf_section_name2(cs);
173
0
  if (name2) {
174
0
    ssize_t     slen;
175
0
    fr_token_t    quote;
176
0
    unlang_group_t    *switch_g;
177
0
    unlang_switch_t   *switch_gext;
178
179
0
    switch_g = unlang_generic_to_group(parent);
180
0
    switch_gext = unlang_group_to_switch(switch_g);
181
182
0
    fr_assert(switch_gext->vpt != NULL);
183
184
    /*
185
     *  We need to cast case values to match
186
     *  what we're switching over, otherwise
187
     *  integers of different widths won't
188
     *  match.
189
     */
190
0
    t_rules.cast = tmpl_expanded_type(switch_gext->vpt);
191
192
    /*
193
     *  Need to pass the attribute from switch
194
     *  to tmpl rules so we can convert the
195
     *  case string to an integer value.
196
     */
197
0
    if (tmpl_is_attr(switch_gext->vpt)) {
198
0
      fr_dict_attr_t const *da = tmpl_attr_tail_da(switch_gext->vpt);
199
0
      if (da->flags.has_value) t_rules.enumv = da;
200
0
    }
201
202
0
    quote = cf_section_name2_quote(cs);
203
204
0
    slen = tmpl_afrom_substr(cs, &vpt,
205
0
           &FR_SBUFF_IN_STR(name2),
206
0
           quote,
207
0
           NULL,
208
0
           &t_rules);
209
0
    if (!vpt) {
210
0
      cf_canonicalize_error(cs, slen, "Failed parsing argument to 'case'", name2);
211
0
      return NULL;
212
0
    }
213
214
    /*
215
     *  Bare word strings are attribute references
216
     */
217
0
    if (tmpl_is_attr(vpt) || tmpl_is_attr_unresolved(vpt)) {
218
0
    fail_attr:
219
0
      cf_log_err(cs, "arguments to 'case' statements MUST NOT be attribute references.");
220
0
      goto fail;
221
0
    }
222
223
0
    if (!tmpl_is_data(vpt) || tmpl_is_data_unresolved(vpt)) {
224
0
      cf_log_err(cs, "arguments to 'case' statements MUST be static data.");
225
0
    fail:
226
0
      talloc_free(vpt);
227
0
      return NULL;
228
0
    }
229
230
    /*
231
     *  References to unresolved attributes are forbidden.  They are no longer "bare word
232
     *  strings".
233
     */
234
0
    if ((quote == T_BARE_WORD) && (tmpl_value_type(vpt) == FR_TYPE_STRING)) {
235
0
      goto fail_attr;
236
0
    }
237
238
0
  } /* else it's a default 'case' statement */
239
240
  /*
241
   *  If we were asked to match something, then we MUST
242
   *  match it, even if the section is empty.  Otherwise we
243
   *  will silently skip the match, and then fall through to
244
   *  the "default" statement.
245
   */
246
0
  c = unlang_compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_CASE);
247
0
  if (!c) {
248
0
    talloc_free(vpt);
249
0
    return NULL;
250
0
  }
251
252
0
  case_g = unlang_generic_to_group(c);
253
0
  case_gext = unlang_group_to_case(case_g);
254
0
  case_gext->vpt = talloc_steal(case_gext, vpt);
255
256
  /*
257
   *  Set all of its codes to return, so that
258
   *  when we pick a 'case' statement, we don't
259
   *  fall through to processing the next one.
260
   */
261
0
  for (i = 0; i < RLM_MODULE_NUMCODES; i++) c->actions.actions[i] = MOD_ACTION_RETURN;
262
263
0
  return c;
264
0
}
265
266
static int8_t case_cmp(void const *one, void const *two)
267
0
{
268
0
  unlang_case_t const *a = (unlang_case_t const *) one; /* may not be talloc'd! See switch.c */
269
0
  unlang_case_t const *b = (unlang_case_t const *) two; /* may not be talloc'd! */
270
271
0
  return fr_value_box_cmp(tmpl_value(a->vpt), tmpl_value(b->vpt));
272
0
}
273
274
static uint32_t case_hash(void const *data)
275
0
{
276
0
  unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */
277
278
0
  return fr_value_box_hash(tmpl_value(a->vpt));
279
0
}
280
281
static int case_to_key(uint8_t **out, size_t *outlen, void const *data)
282
0
{
283
0
  unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */
284
285
0
  return fr_value_box_to_key(out, outlen, tmpl_value(a->vpt));
286
0
}
287
288
static unlang_t *unlang_compile_switch(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
289
0
{
290
0
  CONF_SECTION    *cs = cf_item_to_section(ci);
291
0
  CONF_ITEM   *subci;
292
0
  fr_token_t    token;
293
0
  char const    *name1, *name2;
294
0
  char const    *type_name;
295
296
0
  unlang_group_t    *g;
297
0
  unlang_switch_t   *gext;
298
299
0
  unlang_t    *c;
300
0
  ssize_t     slen;
301
302
0
  tmpl_rules_t    t_rules;
303
304
0
  fr_type_t   type;
305
0
  fr_htrie_type_t   htype;
306
307
  /*
308
   *  We allow unknown attributes here.
309
   */
310
0
  t_rules = *(unlang_ctx->rules);
311
0
  t_rules.attr.allow_unknown = true;
312
0
  RULES_VERIFY(&t_rules);
313
314
0
  name2 = cf_section_name2(cs);
315
0
  if (!name2) {
316
0
    cf_log_err(cs, "You must specify a variable to switch over for 'switch'");
317
0
  print_url:
318
0
    cf_log_err(ci, DOC_KEYWORD_REF(switch));
319
0
    return NULL;
320
0
  }
321
322
0
  if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
323
324
0
  g = unlang_group_allocate(parent, cs, UNLANG_TYPE_SWITCH);
325
0
  if (!g) return NULL;
326
327
0
  gext = unlang_group_to_switch(g);
328
329
  /*
330
   *  Create the template.  All attributes and xlats are
331
   *  defined by now.
332
   *
333
   *  The 'case' statements need g->vpt filled out to ensure
334
   *  that the data types match.
335
   */
336
0
  token = cf_section_name2_quote(cs);
337
338
0
  if ((token == T_BARE_WORD) && (name2[0] != '%')) {
339
0
    slen = tmpl_afrom_attr_substr(gext, NULL, &gext->vpt,
340
0
                &FR_SBUFF_IN_STR(name2),
341
0
                NULL,
342
0
                &t_rules);
343
0
  } else {
344
0
    slen = tmpl_afrom_substr(gext, &gext->vpt,
345
0
           &FR_SBUFF_IN_STR(name2),
346
0
           token,
347
0
           NULL,
348
0
           &t_rules);
349
0
  }
350
0
  if (!gext->vpt) {
351
0
    cf_canonicalize_error(cs, slen, "Failed parsing argument to 'switch'", name2);
352
0
    talloc_free(g);
353
0
    return NULL;
354
0
  }
355
356
0
  c = unlang_group_to_generic(g);
357
0
  c->name = "switch";
358
0
  c->debug_name = talloc_typed_asprintf(c, "switch %s", name2);
359
360
  /*
361
   *  Fixup the template before compiling the children.
362
   *  This is so that compile_case() can do attribute type
363
   *  checks / casts against us.
364
   */
365
0
  if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
366
0
    talloc_free(g);
367
0
    return NULL;
368
0
  }
369
370
0
  if (tmpl_is_list(gext->vpt)) {
371
0
    cf_log_err(cs, "Cannot use list for 'switch' statement");
372
0
  error:
373
0
    talloc_free(g);
374
0
    goto print_url;
375
0
  }
376
377
0
  if (tmpl_contains_regex(gext->vpt)) {
378
0
    cf_log_err(cs, "Cannot use regular expression for 'switch' statement");
379
0
    goto error;
380
0
  }
381
382
0
  if (tmpl_is_data(gext->vpt)) {
383
0
    cf_log_err(cs, "Cannot use constant data for 'switch' statement");
384
0
    goto error;
385
0
  }
386
387
0
  if (tmpl_is_xlat(gext->vpt)) {
388
0
    xlat_exp_head_t *xlat = tmpl_xlat(gext->vpt);
389
390
0
    if (xlat->flags.constant || xlat->flags.pure) {
391
0
      cf_log_err(cs, "Cannot use constant data for 'switch' statement");
392
0
      goto error;
393
0
    }
394
0
  }
395
396
397
0
  if (tmpl_needs_resolving(gext->vpt)) {
398
0
    cf_log_err(cs, "Cannot resolve key for 'switch' statement");
399
0
    goto error;
400
0
  }
401
402
0
  type_name = cf_section_argv(cs, 0); /* AFTER name1, name2 */
403
0
  if (type_name) {
404
0
    type = fr_table_value_by_str(fr_type_table, type_name, FR_TYPE_NULL);
405
406
    /*
407
     *  Should have been caught in cf_file.c, process_switch()
408
     */
409
0
    fr_assert(type != FR_TYPE_NULL);
410
0
    fr_assert(fr_type_is_leaf(type));
411
412
0
  do_cast:
413
0
    if (tmpl_cast_set(gext->vpt, type) < 0) {
414
0
      cf_log_perr(cs, "Failed setting cast type");
415
0
      goto error;
416
0
    }
417
418
0
  } else {
419
    /*
420
     *  Get the return type of the tmpl.  If we don't know,
421
     *  mash it all to string.
422
     */
423
0
    type = tmpl_data_type(gext->vpt);
424
0
    if ((type == FR_TYPE_NULL) || (type == FR_TYPE_VOID)) {
425
0
      type = FR_TYPE_STRING;
426
0
      goto do_cast;
427
0
    }
428
0
  }
429
430
0
  htype = fr_htrie_hint(type);
431
0
  if (htype == FR_HTRIE_INVALID) {
432
0
    cf_log_err(cs, "Invalid data type '%s' used for 'switch' statement",
433
0
          fr_type_to_str(type));
434
0
    goto error;
435
0
  }
436
437
0
  gext->ht = fr_htrie_alloc(gext, htype,
438
0
          (fr_hash_t) case_hash,
439
0
          (fr_cmp_t) case_cmp,
440
0
          (fr_trie_key_t) case_to_key,
441
0
          NULL);
442
0
  if (!gext->ht) {
443
0
    cf_log_err(cs, "Failed initializing internal data structures");
444
0
    talloc_free(g);
445
0
    return NULL;
446
0
  }
447
448
  /*
449
   *  Walk through the children of the switch section,
450
   *  ensuring that they're all 'case' statements, and then compiling them.
451
   */
452
0
  for (subci = cf_item_next(cs, NULL);
453
0
       subci != NULL;
454
0
       subci = cf_item_next(cs, subci)) {
455
0
    CONF_SECTION *subcs;
456
0
    unlang_t *single;
457
0
    unlang_case_t *case_gext;
458
459
0
    if (!cf_item_is_section(subci)) {
460
0
      if (!cf_item_is_pair(subci)) continue;
461
462
0
      cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections");
463
0
      goto error;
464
0
    }
465
466
0
    subcs = cf_item_to_section(subci);  /* can't return NULL */
467
0
    name1 = cf_section_name1(subcs);
468
469
0
    if (strcmp(name1, "case") != 0) {
470
      /*
471
       *  We finally support "default" sections for "switch".
472
       */
473
0
      if (strcmp(name1, "default") == 0) {
474
0
        if (cf_section_name2(subcs) != NULL) {
475
0
          cf_log_err(subci, "\"default\" sections cannot have a match argument");
476
0
          goto error;
477
0
        }
478
0
        goto handle_default;
479
0
      }
480
481
0
      cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections");
482
0
      goto error;
483
0
    }
484
485
0
    name2 = cf_section_name2(subcs);
486
0
    if (!name2) {
487
0
    handle_default:
488
0
      if (gext->default_case) {
489
0
        cf_log_err(subci, "Cannot have two 'default' case statements");
490
0
        goto error;
491
0
      }
492
0
    }
493
494
    /*
495
     *  Compile the subsection.
496
     */
497
0
    single = unlang_compile_case(c, unlang_ctx, subci);
498
0
    if (!single) goto error;
499
500
0
    fr_assert(single->type == UNLANG_TYPE_CASE);
501
502
    /*
503
     *  Remember the "default" section, and insert the
504
     *  non-default "case" into the htrie.
505
     */
506
0
    case_gext = unlang_group_to_case(unlang_generic_to_group(single));
507
0
    if (!case_gext->vpt) {
508
0
      gext->default_case = single;
509
510
0
    } else if (!fr_htrie_insert(gext->ht, single)) {
511
0
      single = fr_htrie_find(gext->ht, single);
512
513
      /*
514
       *  @todo - look up the key and get the previous one?
515
       */
516
0
      cf_log_err(subci, "Failed inserting 'case' statement.  Is there a duplicate?");
517
518
0
      if (single) cf_log_err(unlang_generic_to_group(single)->cs, "Duplicate may be here.");
519
520
0
      goto error;
521
0
    }
522
523
0
    unlang_list_insert_tail(&g->children, single);
524
0
  }
525
526
0
  return c;
527
0
}
528
529
void unlang_switch_init(void)
530
0
{
531
0
  unlang_register(&(unlang_op_t) {
532
0
      .name = "switch",
533
0
      .type = UNLANG_TYPE_SWITCH,
534
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
535
536
0
      .compile = unlang_compile_switch,
537
0
      .interpret = unlang_switch,
538
539
0
      .unlang_size = sizeof(unlang_switch_t),
540
0
      .unlang_name = "unlang_switch_t",
541
542
0
      .pool_headers = TMPL_POOL_DEF_HEADERS,
543
0
      .pool_len = TMPL_POOL_DEF_LEN
544
0
    });
545
546
547
0
  unlang_register(&(unlang_op_t){
548
0
      .name = "case",
549
0
      .type = UNLANG_TYPE_CASE,
550
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_BREAK_POINT,
551
552
0
      .compile = unlang_compile_case,
553
0
      .interpret = unlang_case,
554
555
0
      .unlang_size = sizeof(unlang_case_t),
556
0
      .unlang_name = "unlang_case_t",
557
0
    });
558
0
}