Coverage Report

Created: 2026-09-28 06:27

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