Coverage Report

Created: 2026-05-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/limit.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: b715a702ef0052a76afee767462061130fc2bf3f $
19
 *
20
 * @file unlang/limit.c
21
 * @brief Unlang "limit" keyword evaluation.
22
 *
23
 * @copyright 2022 Network RADIUS SAS (legal@networkradius.com)
24
 */
25
RCSID("$Id: b715a702ef0052a76afee767462061130fc2bf3f $")
26
27
#include <freeradius-devel/server/rcode.h>
28
#include "group_priv.h"
29
#include "limit_priv.h"
30
31
typedef struct {
32
  uint32_t        active_callers;
33
} unlang_thread_limit_t;
34
35
typedef struct {
36
  unlang_thread_limit_t     *thread;
37
  uint32_t        limit;
38
  request_t       *request;
39
40
  fr_value_box_list_t     result;
41
} unlang_frame_state_limit_t;
42
43
/** Send a signal (usually stop) to a request
44
 *
45
 * @param[in] request   The current request.
46
 * @param[in] frame   current stack frame.
47
 * @param[in] action    to signal.
48
 */
49
static void unlang_limit_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
50
0
{
51
0
  unlang_frame_state_limit_t  *state = talloc_get_type_abort(frame->state, unlang_frame_state_limit_t);
52
53
0
  if (action == FR_SIGNAL_CANCEL) {
54
0
    state->thread->active_callers--;
55
0
  }
56
0
}
57
58
static unlang_action_t unlang_limit_resume_done(UNUSED unlang_result_t *p_result, UNUSED request_t *request, unlang_stack_frame_t *frame)
59
0
{
60
0
  unlang_frame_state_limit_t  *state = talloc_get_type_abort(frame->state, unlang_frame_state_limit_t);
61
62
0
  state->thread->active_callers--;
63
64
0
  return UNLANG_ACTION_CALCULATE_RESULT;
65
0
}
66
67
static unlang_action_t unlang_limit_enforce(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
68
0
{
69
0
  unlang_frame_state_limit_t  *state = talloc_get_type_abort(frame->state, unlang_frame_state_limit_t);
70
0
  unlang_action_t     action;
71
72
0
  state->thread = unlang_thread_instance(frame->instruction);
73
0
  fr_assert(state->thread != NULL);
74
75
0
  if (state->thread->active_callers >= state->limit) return UNLANG_ACTION_FAIL;
76
77
0
  frame_repeat(frame, unlang_limit_resume_done);
78
79
0
  action = unlang_interpret_push_children(NULL, request, RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP);
80
81
0
  state->thread->active_callers += (action == UNLANG_ACTION_PUSHED_CHILD);
82
83
0
  return action;
84
0
}
85
86
static unlang_action_t unlang_limit_xlat_done(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
87
0
{
88
0
  unlang_frame_state_limit_t  *state = talloc_get_type_abort(frame->state, unlang_frame_state_limit_t);
89
0
  fr_value_box_t      *box = fr_value_box_list_head(&state->result);
90
91
0
  if (unlikely(!box)) RETURN_UNLANG_FAIL;
92
  /*
93
   *  compile_limit() ensures that the tmpl is cast to uint32, so we don't have to do any more work here.
94
   */
95
0
  state->limit = box->vb_uint32;
96
97
0
  return unlang_limit_enforce(p_result, request, frame);
98
0
}
99
100
static unlang_action_t unlang_limit(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
101
0
{
102
0
  unlang_group_t      *g;
103
0
  unlang_limit_t      *gext;
104
0
  unlang_frame_state_limit_t  *state = talloc_get_type_abort(frame->state, unlang_frame_state_limit_t);
105
106
0
  g = unlang_generic_to_group(frame->instruction);
107
0
  gext = unlang_group_to_limit(g);
108
109
0
  state->request = request;
110
111
0
  if (!gext->vpt) {
112
0
    state->limit = gext->limit;
113
0
    return unlang_limit_enforce(p_result, request, frame);
114
0
  }
115
116
0
  fr_value_box_list_init(&state->result);
117
118
0
  if (unlang_tmpl_push(state, NULL, &state->result, request, gext->vpt, NULL, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL;
119
120
0
  frame_repeat(frame, unlang_limit_xlat_done);
121
122
0
  return UNLANG_ACTION_PUSHED_CHILD;
123
0
}
124
125
126
static unlang_t *unlang_compile_limit(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
127
0
{
128
0
  CONF_SECTION    *cs = cf_item_to_section(ci);
129
0
  char const    *name2;
130
0
  unlang_t    *c;
131
0
  unlang_group_t    *g;
132
0
  unlang_limit_t    *gext;
133
0
  tmpl_t      *vpt = NULL;
134
0
  fr_token_t    token;
135
0
  ssize_t     slen;
136
0
  tmpl_rules_t    t_rules;
137
138
  /*
139
   *  limit <number>
140
   */
141
0
  name2 = cf_section_name2(cs);
142
0
  if (!name2) {
143
0
    cf_log_err(cs, "You must specify a value for 'limit'");
144
0
  print_url:
145
0
    cf_log_err(ci, DOC_KEYWORD_REF(limit));
146
0
    return NULL;
147
0
  }
148
149
0
  if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
150
151
0
  g = unlang_group_allocate(parent, cs, UNLANG_TYPE_LIMIT);
152
0
  if (!g) return NULL;
153
154
0
  c = unlang_group_to_generic(g);
155
0
  c->name = "limit";
156
0
  c->debug_name = talloc_typed_asprintf(c, "limit %s", name2);
157
158
0
  gext = unlang_group_to_limit(g);
159
160
0
  token = cf_section_name2_quote(cs);
161
162
  /*
163
   *  We don't allow unknown attributes here.
164
   */
165
0
  t_rules = *(unlang_ctx->rules);
166
0
  t_rules.attr.allow_unknown = false;
167
0
  RULES_VERIFY(&t_rules);
168
169
0
  slen = tmpl_afrom_substr(gext, &vpt,
170
0
         &FR_SBUFF_IN_STR(name2),
171
0
         token,
172
0
         NULL,
173
0
         &t_rules);
174
0
  if (!vpt) {
175
0
  syntax_error:
176
0
    cf_canonicalize_error(cs, slen, "Failed parsing argument to 'limit'", name2);
177
0
  error:
178
0
    talloc_free(g);
179
0
    goto print_url;
180
0
  }
181
0
  gext->vpt = vpt;
182
183
  /*
184
   *  Fixup the tmpl so that we know it's somewhat sane.
185
   */
186
0
  if (!pass2_fixup_tmpl(gext, &vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
187
0
    goto error;
188
0
  }
189
190
0
  if (tmpl_is_list(vpt)) {
191
0
    cf_log_err(cs, "Cannot use list as argument for 'limit' statement");
192
0
    goto error;
193
0
  }
194
195
0
  if (tmpl_contains_regex(vpt)) {
196
0
    cf_log_err(cs, "Cannot use regular expression as argument for 'limit' statement");
197
0
    goto error;
198
0
  }
199
200
0
  if (tmpl_is_data(vpt) && (token == T_BARE_WORD)) {
201
0
    fr_value_box_t box;
202
203
0
    if (fr_value_box_cast(NULL, &box, FR_TYPE_UINT32, NULL, tmpl_value(vpt)) < 0) goto syntax_error;
204
205
0
    gext->limit = box.vb_uint32;
206
207
0
  } else {
208
    /*
209
     *  Attribute or xlat MUST be cast to a 32-bit unsigned number.
210
     */
211
0
    if (tmpl_cast_set(vpt, FR_TYPE_UINT32) < 0) {
212
0
      cf_log_perr(cs, "Failed setting cast type");
213
0
      goto syntax_error;
214
0
    }
215
0
  }
216
217
0
  return unlang_compile_children(g, unlang_ctx);
218
0
}
219
220
void unlang_limit_init(void)
221
0
{
222
0
  unlang_register(&(unlang_op_t){
223
0
      .name = "limit",
224
0
      .type = UNLANG_TYPE_LIMIT,
225
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
226
227
0
      .compile = unlang_compile_limit,
228
0
      .interpret = unlang_limit,
229
0
      .signal = unlang_limit_signal,
230
231
0
      .unlang_size = sizeof(unlang_limit_t),
232
0
      .unlang_name = "unlang_limit_t",
233
234
0
      .frame_state_size = sizeof(unlang_frame_state_limit_t),
235
0
      .frame_state_type = "unlang_frame_state_limit_t",
236
237
0
      .thread_inst_size = sizeof(unlang_thread_limit_t),
238
0
      .thread_inst_type = "unlang_thread_limit_t",
239
0
    });
240
0
}