Coverage Report

Created: 2026-03-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/try.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: 81f7daf8cbd3dd0d8f6f13399ea86d85e65a6d90 $
19
 *
20
 * @file unlang/try.c
21
 * @brief Unlang "try" keyword evaluation.
22
 *
23
 * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
24
 */
25
RCSID("$Id: 81f7daf8cbd3dd0d8f6f13399ea86d85e65a6d90 $")
26
27
#include "unlang_priv.h"
28
#include "try_priv.h"
29
30
static unlang_action_t skip_to_catch(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
31
0
{
32
0
  rlm_rcode_t   rcode = unlang_interpret_rcode(request);
33
0
  unlang_try_t const      *gext = unlang_generic_to_try(frame->instruction);
34
35
0
  fr_assert(frame->instruction->type == UNLANG_TYPE_TRY);
36
0
  fr_assert(rcode < RLM_MODULE_NUMCODES);
37
38
  /*
39
   *  Push the one "catch" section that we want to run.  Once it's done, it will pop, return to us,
40
   *  and we will continue with the next sibling.
41
   */
42
0
  if (gext->catch[rcode]) {
43
0
    if (unlang_interpret_push(NULL, request, gext->catch[rcode],
44
0
            FRAME_CONF(RLM_MODULE_NOT_SET, UNLANG_SUB_FRAME), false) < 0) {
45
0
      RETURN_UNLANG_ACTION_FATAL;
46
0
    }
47
48
0
    return UNLANG_ACTION_PUSHED_CHILD;
49
0
  }
50
51
0
  RDEBUG3("No catch section for %s",
52
0
    fr_table_str_by_value(mod_rcode_table, rcode, "<invalid>"));
53
54
  /*
55
   *  Go to the next sibling, which MUST NOT be a "catch".
56
   */
57
0
  return UNLANG_ACTION_CALCULATE_RESULT;
58
0
}
59
60
static unlang_action_t unlang_try(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
61
0
{
62
  /*
63
   *  When this frame finishes, jump ahead to the appropriate "catch".
64
   *
65
   *  All of the magic is done in the compile phase.
66
   */
67
0
  frame_repeat(frame, skip_to_catch);
68
69
0
  return unlang_interpret_push_children(NULL, request, RLM_MODULE_NOT_SET, UNLANG_NEXT_SIBLING);
70
0
}
71
72
static unlang_t *unlang_compile_try(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
73
0
{
74
0
  CONF_SECTION  *cs = cf_item_to_section(ci);
75
0
  unlang_group_t  *g;
76
0
  unlang_t  *c;
77
0
  CONF_SECTION  *parent_cs, *next;
78
0
  int   i;
79
0
  CONF_SECTION  *default_catch = NULL;
80
0
  unlang_compile_ctx_t unlang_ctx2;
81
0
  CONF_SECTION  *catcher[RLM_MODULE_NUMCODES] = {};
82
83
  /*
84
   *  The transaction is empty, ignore it.
85
   */
86
0
  if (!cf_item_next(cs, NULL)) {
87
0
    cf_log_err(cs, "'try' sections cannot be empty");
88
0
  print_url:
89
0
    cf_log_err(ci, DOC_KEYWORD_REF(try));
90
0
    return NULL;
91
0
  }
92
93
0
  if (cf_section_name2(cs) != NULL) {
94
0
    cf_log_err(cs, "Unexpected argument to 'try' section");
95
0
    goto print_url;
96
0
  }
97
98
0
  parent_cs = cf_item_to_section(cf_parent(cs));
99
0
  next = cf_section_next(parent_cs, cs);
100
101
0
  if (!next ||
102
0
      (strcmp(cf_section_name1(next), "catch") != 0)) {
103
0
    cf_log_err(cs, "'try' sections must be followed by a 'catch'");
104
0
    goto print_url;
105
0
  }
106
107
0
  g = unlang_group_allocate(parent, cs, UNLANG_TYPE_TRY);
108
0
  if (!g) return NULL;
109
110
0
  c = unlang_group_to_generic(g);
111
0
  c->debug_name = c->name = cf_section_name1(cs);
112
113
  /*
114
   *  Whatever the input compile ctx is, we over-ride the default actions.
115
   */
116
0
  unlang_compile_ctx_copy(&unlang_ctx2, unlang_ctx);
117
118
  /*
119
   *  Loop over all of the "catch" sections, figuring out which rcodes are being "catch"ed.
120
   */
121
0
  for (default_catch = NULL;
122
0
       next && (strcmp(cf_section_name1(next), "catch") == 0);
123
0
       next = cf_section_next(parent_cs, next)) {
124
0
    rlm_rcode_t rcode;
125
0
    char const *name;
126
127
    /*
128
     *  catch { ... } is the default, and we can't
129
     *  have anything after it.
130
     */
131
0
    if (default_catch) {
132
0
      cf_log_err(default_catch, "Invalid 'catch' - cannot have another 'catch' after a default 'catch { ... }'");
133
0
      cs = default_catch;
134
0
    print_catch_url:
135
0
      cf_log_err(cs, DOC_KEYWORD_REF(catch));
136
0
      talloc_free(g);
137
0
      return NULL;
138
0
    }
139
140
0
    name = cf_section_name2(next);
141
0
    if (!name) {
142
0
      default_catch = next;
143
0
      continue;
144
0
    }
145
146
0
    rcode = fr_table_value_by_str(mod_rcode_table, name, RLM_MODULE_NOT_SET);
147
0
    if (rcode == RLM_MODULE_NOT_SET) {
148
0
      cf_log_err(cs, "Invalid argument to 'catch' - unknown rcode '%s'.", name);
149
0
      goto print_catch_url;
150
0
    }
151
152
0
    if (catcher[rcode]) {
153
0
      cf_log_err(next, "Duplicate rcode '%s'", name);
154
0
      cf_log_err(catcher[rcode], "First instance is here");
155
0
      goto print_catch_url;
156
0
    }
157
0
    catcher[rcode] = next;
158
159
0
    for (i = 0; (name = cf_section_argv(next, i)) != NULL; i++) {
160
0
      rcode = fr_table_value_by_str(mod_rcode_table, name, RLM_MODULE_NOT_SET);
161
0
      if (rcode == RLM_MODULE_NOT_SET) {
162
0
        cf_log_err(cs, "Invalid argument to 'catch' - unknown rcode '%s'.", name);
163
0
        goto print_catch_url;
164
0
      }
165
166
0
      if (catcher[rcode]) {
167
0
        cf_log_err(next, "Duplicate rcode '%s'", name);
168
0
        cf_log_err(catcher[rcode], "First instance is here");
169
0
        goto print_catch_url;
170
0
      }
171
172
0
      catcher[rcode] = next;
173
0
    }
174
0
  }
175
176
  /*
177
   *  Check that the default will be used.
178
   *
179
   *  Note that we do NOT change the priorities for the defaults.
180
   */
181
0
  if (default_catch) {
182
0
    bool set = false;
183
184
0
    for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
185
0
      if (!catcher[i]) {
186
0
        set = true;
187
0
        break;
188
0
      }
189
0
    }
190
191
0
    if (!set) {
192
0
      cf_log_err(default_catch, "Invalid 'catch { ... }' - all rcodes had previously been used");
193
0
      goto print_catch_url;
194
0
    }
195
196
    /*
197
     *  Errors are still "return", even in a default "catch".
198
     *
199
     *  Normal rcodes will run to the end of the try section, and then be "catch"ed.
200
     */
201
0
    if (!catcher[RLM_MODULE_REJECT]) catcher[RLM_MODULE_REJECT] = default_catch;
202
0
    if (!catcher[RLM_MODULE_FAIL]) catcher[RLM_MODULE_FAIL] = default_catch;
203
0
    if (!catcher[RLM_MODULE_INVALID]) catcher[RLM_MODULE_INVALID] = default_catch;
204
0
    if (!catcher[RLM_MODULE_DISALLOW]) catcher[RLM_MODULE_DISALLOW] = default_catch;
205
0
    if (!catcher[RLM_MODULE_TIMEOUT]) catcher[RLM_MODULE_TIMEOUT] = default_catch;
206
0
  }
207
208
  /*
209
   *  Loop again over the rcodes, setting the child actions to RETURN if necessary.
210
   *
211
   *  If the child is returning for that action, ensure that _we_ aren't returning.
212
   *
213
   *  Note that as above, reject / fail / invalid / disallow / timeout are errors, and cause the
214
   *  child to immediately return.  All other rcodes
215
   */
216
0
  for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
217
    /*
218
     *  No one cares about this rcode.  It can return, reject, etc.
219
     */
220
0
    if (!catcher[i]) continue;
221
222
    /*
223
     *  Error rcodes cause the child section to bail immediately, but the "try" instruction
224
     *  does not bail.
225
     */
226
0
    if ((i == RLM_MODULE_REJECT) ||
227
0
        (i == RLM_MODULE_FAIL) ||
228
0
        (i == RLM_MODULE_INVALID) ||
229
0
        (i == RLM_MODULE_DISALLOW) ||
230
0
        (i == RLM_MODULE_TIMEOUT)) {
231
0
      unlang_ctx2.actions.actions[i] = MOD_ACTION_RETURN;
232
0
      c->actions.actions[i] = MOD_ACTION_NOT_SET;
233
0
      continue;
234
0
    }
235
236
    /*
237
     *  Normal rcodes cause the child section to run to completion, and the "try" section does
238
     *  not bail.
239
     */
240
0
    if ((unlang_ctx2.actions.actions[i] > MOD_ACTION_NOT_SET) &&
241
0
        (unlang_ctx2.actions.actions[i] < MOD_PRIORITY_MIN)) {
242
0
      unlang_ctx2.actions.actions[i] = MOD_ACTION_NOT_SET;
243
0
      c->actions.actions[i] = MOD_ACTION_NOT_SET;
244
0
    }
245
0
  }
246
247
  /*
248
   *  Compile the children using the new compile ctx.
249
   */
250
0
  return unlang_compile_children(g, &unlang_ctx2);
251
0
}
252
253
254
void unlang_try_init(void)
255
0
{
256
0
  unlang_register(&(unlang_op_t){
257
0
      .name = "try",
258
0
      .type = UNLANG_TYPE_TRY,
259
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
260
261
0
      .compile = unlang_compile_try,
262
0
      .interpret = unlang_try,
263
264
0
      .unlang_size = sizeof(unlang_try_t),
265
0
      .unlang_name = "unlang_try_t",
266
0
    });
267
0
}