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