/src/freeradius-server/src/lib/unlang/xlat_expr.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: 2a10724531ea38d0460c8a6f84e718a73793c681 $ |
19 | | * |
20 | | * @file xlat_expr.c |
21 | | * @brief Tokenizers and support functions for xlat expressions |
22 | | * |
23 | | * @copyright 2021 The FreeRADIUS server project |
24 | | * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) |
25 | | */ |
26 | | RCSID("$Id: 2a10724531ea38d0460c8a6f84e718a73793c681 $") |
27 | | |
28 | | #include <freeradius-devel/server/base.h> |
29 | | #include <freeradius-devel/unlang/xlat_priv.h> |
30 | | #include <freeradius-devel/util/calc.h> |
31 | | #include <freeradius-devel/util/debug.h> |
32 | | #include <freeradius-devel/server/tmpl_dcursor.h> |
33 | | |
34 | | #undef XLAT_DEBUG |
35 | | #ifdef DEBUG_XLAT |
36 | | # define XLAT_DEBUG(_fmt, ...) DEBUG3("%s[%i] "_fmt, __FILE__, __LINE__, ##__VA_ARGS__) |
37 | | #else |
38 | | # define XLAT_DEBUG(...) |
39 | | #endif |
40 | | |
41 | | /* |
42 | | * The new tokenizer accepts most things which are accepted by the old one. Many of the errors will be |
43 | | * different, though. |
44 | | * |
45 | | * @todo - add a "output" fr_type_t to xlat_t, which is mainly used by the comparison functions. Right |
46 | | * now it will happily parse things like: |
47 | | * |
48 | | * (1 < 2) < 3 |
49 | | * |
50 | | * though the result of (1 < 2) is a boolean, so the result is always true. We probably want to have |
51 | | * that as a compile-time error / check. This can probably just be done with xlat_purify() ? which |
52 | | * doesn't need to interpret the LHS, but just knows its limits. We perhaps want a "range compare" |
53 | | * function, which just checks ranges on one side against values on the right. |
54 | | * |
55 | | * Even worse, when we do "((bool) 1) < 3", the RHS is cast to the type of the LHS by |
56 | | * tmpl_afrom_substr(). This is because we pass the LHS data type recursively down, which works most of |
57 | | * the time, but not all of the time. There are currently hacks in the "upcast" code here to fix this, |
58 | | * but it's a hack. |
59 | | * |
60 | | * @todo - add instantiation routines for assignment operations. This lets us do things |
61 | | * like: |
62 | | * if ((&foo += 4) > 6) ... |
63 | | * |
64 | | * However, this would also require us adding an edit list pointer to the xlat evaluation functions, |
65 | | * which is not trivial. Or, maybe we attach it to the request somehow? |
66 | | */ |
67 | | |
68 | | static xlat_exp_t *xlat_exists_alloc(TALLOC_CTX *ctx, xlat_exp_t *child); |
69 | | |
70 | | static void xlat_func_append_arg(xlat_exp_t *head, xlat_exp_t *node, bool exists) |
71 | 468k | { |
72 | 468k | xlat_exp_t *group; |
73 | | |
74 | 468k | fr_assert(head->type == XLAT_FUNC); |
75 | | |
76 | | /* |
77 | | * Wrap existence checks for attribute reference. |
78 | | */ |
79 | 468k | if (exists && (node->type == XLAT_TMPL) && tmpl_contains_attr(node->vpt)) { |
80 | 1.16k | node = xlat_exists_alloc(head, node); |
81 | 1.16k | } |
82 | | |
83 | 468k | if (!head->call.args) { |
84 | 218k | MEM(head->call.args = xlat_exp_head_alloc(head)); |
85 | 218k | head->call.args->is_argv = true; |
86 | 218k | } |
87 | | |
88 | | /* |
89 | | * Wrap it in a group. |
90 | | */ |
91 | 468k | group = xlat_exp_alloc(head->call.args, XLAT_GROUP, NULL, 0); |
92 | 468k | group->quote = T_BARE_WORD; |
93 | | |
94 | 468k | xlat_exp_set_name_shallow(group, node->fmt); /* not entirely correct, but good enough for now */ |
95 | 468k | group->flags = node->flags; |
96 | | |
97 | 468k | talloc_steal(group->group, node); |
98 | 468k | xlat_exp_insert_tail(group->group, node); |
99 | | |
100 | 468k | xlat_exp_insert_tail(head->call.args, group); |
101 | | |
102 | 468k | xlat_flags_merge(&head->flags, &head->call.args->flags); |
103 | 468k | } |
104 | | |
105 | | |
106 | | /** Allocate a specific cast node. |
107 | | * |
108 | | * With the first argument being a UINT8 of the data type. |
109 | | * See xlat_func_cast() for the implementation. |
110 | | * |
111 | | */ |
112 | | static xlat_exp_t *xlat_exists_alloc(TALLOC_CTX *ctx, xlat_exp_t *child) |
113 | 1.81k | { |
114 | 1.81k | xlat_exp_t *node; |
115 | | |
116 | 1.81k | fr_assert(child->type == XLAT_TMPL); |
117 | 1.81k | fr_assert(tmpl_contains_attr(child->vpt)); |
118 | | |
119 | | /* |
120 | | * Create an "exists" node. |
121 | | */ |
122 | 1.81k | MEM(node = xlat_exp_alloc(ctx, XLAT_FUNC, "exists", 6)); |
123 | 1.81k | xlat_exp_set_name_shallow(node, child->vpt->name); |
124 | | |
125 | 1.81k | MEM(node->call.func = xlat_func_find("exists", 6)); |
126 | 1.81k | fr_assert(node->call.func != NULL); |
127 | | |
128 | | /* |
129 | | * The attribute may need resolving, in which case we have to set the flag as appropriate. |
130 | | */ |
131 | 1.81k | node->flags = (xlat_flags_t) { .needs_resolving = tmpl_needs_resolving(child->vpt)}; |
132 | | |
133 | 1.81k | if (!node->flags.needs_resolving) node->call.dict = tmpl_attr_tail_da(child->vpt)->dict; |
134 | | |
135 | 1.81k | xlat_func_append_arg(node, child, false); |
136 | | |
137 | 1.81k | XLAT_VERIFY(node); |
138 | | |
139 | 1.81k | return node; |
140 | 1.81k | } |
141 | | |
142 | | |
143 | | static fr_slen_t xlat_expr_print_unary(fr_sbuff_t *out, xlat_exp_t const *node, UNUSED void *inst, fr_sbuff_escape_rules_t const *e_rules) |
144 | 7.37k | { |
145 | 7.37k | size_t at_in = fr_sbuff_used_total(out); |
146 | | |
147 | 7.37k | FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]); |
148 | 7.10k | xlat_print_node(out, node->call.args, xlat_exp_head(node->call.args), e_rules, 0); |
149 | | |
150 | 7.10k | return fr_sbuff_used_total(out) - at_in; |
151 | 7.37k | } |
152 | | |
153 | | static fr_slen_t xlat_expr_print_binary(fr_sbuff_t *out, xlat_exp_t const *node, UNUSED void *inst, fr_sbuff_escape_rules_t const *e_rules) |
154 | 100k | { |
155 | 100k | size_t at_in = fr_sbuff_used_total(out); |
156 | 100k | xlat_exp_t *child; |
157 | | |
158 | 100k | XLAT_VERIFY(node); |
159 | | |
160 | 100k | child = xlat_exp_head(node->call.args); |
161 | 100k | fr_assert(child != NULL); |
162 | | |
163 | 100k | FR_SBUFF_IN_CHAR_RETURN(out, '('); |
164 | 99.3k | xlat_print_node(out, node->call.args, child, e_rules, 0); /* prints a space after the first argument */ |
165 | | |
166 | 99.3k | FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]); |
167 | 63.0k | FR_SBUFF_IN_CHAR_RETURN(out, ' '); |
168 | | |
169 | 62.9k | child = xlat_exp_next(node->call.args, child); |
170 | 62.9k | if (child) { |
171 | 62.9k | xlat_print_node(out, node->call.args, child, e_rules, 0); |
172 | 62.9k | } else { |
173 | 0 | FR_SBUFF_IN_STRCPY_RETURN(out, "???"); |
174 | 0 | } |
175 | | |
176 | 62.9k | FR_SBUFF_IN_CHAR_RETURN(out, ')'); |
177 | | |
178 | 62.4k | return fr_sbuff_used_total(out) - at_in; |
179 | 62.9k | } |
180 | | |
181 | | static int xlat_expr_resolve_binary(xlat_exp_t *node, UNUSED void *inst, xlat_res_rules_t const *xr_rules) |
182 | 0 | { |
183 | 0 | xlat_exp_t *arg1, *arg2; |
184 | 0 | xlat_exp_t *a, *b; |
185 | 0 | tmpl_res_rules_t my_tr_rules; |
186 | |
|
187 | 0 | XLAT_DEBUG("RESOLVE %s\n", node->fmt); |
188 | |
|
189 | 0 | arg1 = xlat_exp_head(node->call.args); |
190 | 0 | fr_assert(arg1); |
191 | 0 | fr_assert(arg1->type == XLAT_GROUP); |
192 | |
|
193 | 0 | arg2 = xlat_exp_next(node->call.args, arg1); |
194 | 0 | fr_assert(arg2); |
195 | 0 | fr_assert(arg2->type == XLAT_GROUP); |
196 | |
|
197 | 0 | a = xlat_exp_head(arg1->group); |
198 | 0 | b = xlat_exp_head(arg2->group); |
199 | | |
200 | | /* |
201 | | * We have many things here, just call resolve recursively. |
202 | | */ |
203 | 0 | if (xlat_exp_next(arg1->group, a) || (xlat_exp_next(arg2->group, b))) goto resolve; |
204 | | |
205 | | /* |
206 | | * Anything else must get resolved at run time. |
207 | | */ |
208 | 0 | if ((a->type != XLAT_TMPL) || (b->type != XLAT_TMPL)) goto resolve; |
209 | | |
210 | | /* |
211 | | * The tr_rules should always contain dict_def |
212 | | */ |
213 | 0 | fr_assert(xr_rules); /* always set by xlat_resolve() */ |
214 | 0 | if (xr_rules->tr_rules) { |
215 | 0 | my_tr_rules = *xr_rules->tr_rules; |
216 | 0 | } else { |
217 | 0 | my_tr_rules = (tmpl_res_rules_t) { }; |
218 | 0 | } |
219 | | |
220 | | /* |
221 | | * The LHS attribute dictates the enumv for the RHS one. |
222 | | */ |
223 | 0 | if (tmpl_contains_attr(a->vpt)) { |
224 | 0 | XLAT_DEBUG("\ta - %s %s\n", a->fmt, b->fmt); |
225 | |
|
226 | 0 | if (a->flags.needs_resolving) { |
227 | 0 | XLAT_DEBUG("\tresolve attr a\n"); |
228 | 0 | if (tmpl_resolve(a->vpt, &my_tr_rules) < 0) return -1; |
229 | 0 | a->flags.needs_resolving = false; |
230 | 0 | } |
231 | | |
232 | 0 | my_tr_rules.enumv = tmpl_attr_tail_da(a->vpt); |
233 | |
|
234 | 0 | XLAT_DEBUG("\tresolve other b\n"); |
235 | 0 | if (tmpl_resolve(b->vpt, &my_tr_rules) < 0) return -1; |
236 | | |
237 | 0 | b->flags.needs_resolving = false; |
238 | 0 | b->flags.pure = tmpl_is_data(b->vpt); |
239 | 0 | b->flags.constant = b->flags.pure; |
240 | 0 | goto flags; |
241 | 0 | } |
242 | | |
243 | 0 | if (tmpl_contains_attr(b->vpt)) { |
244 | 0 | XLAT_DEBUG("\tb - %s %s\n", a->fmt, b->fmt); |
245 | |
|
246 | 0 | if (b->flags.needs_resolving) { |
247 | 0 | XLAT_DEBUG("\tresolve attr b\n"); |
248 | 0 | if (tmpl_resolve(b->vpt, &my_tr_rules) < 0) return -1; |
249 | | |
250 | 0 | b->flags.needs_resolving = false; |
251 | 0 | } |
252 | | |
253 | 0 | my_tr_rules.enumv = tmpl_attr_tail_da(b->vpt); |
254 | |
|
255 | 0 | XLAT_DEBUG("\tresolve other a\n"); |
256 | 0 | if (tmpl_resolve(a->vpt, &my_tr_rules) < 0) return -1; |
257 | | |
258 | 0 | a->flags.needs_resolving = false; |
259 | 0 | a->flags.pure = tmpl_is_data(a->vpt); |
260 | 0 | a->flags.constant = a->flags.pure; |
261 | 0 | goto flags; |
262 | 0 | } |
263 | | |
264 | 0 | resolve: |
265 | | /* |
266 | | * This call will fix everything recursively. |
267 | | */ |
268 | 0 | return xlat_resolve(node->call.args, xr_rules); |
269 | | |
270 | 0 | flags: |
271 | 0 | arg1->flags = arg1->group->flags = a->flags; |
272 | 0 | arg2->flags = arg2->group->flags = b->flags; |
273 | 0 | xlat_flags_merge(&node->call.args->flags, &arg2->flags); |
274 | |
|
275 | 0 | fr_assert(!a->flags.needs_resolving); |
276 | 0 | fr_assert(!b->flags.needs_resolving); |
277 | |
|
278 | 0 | fr_assert(!arg1->flags.needs_resolving); |
279 | 0 | fr_assert(!arg2->flags.needs_resolving); |
280 | |
|
281 | 0 | node->call.args->flags.needs_resolving = false; |
282 | 0 | xlat_flags_merge(&node->flags, &node->call.args->flags); |
283 | |
|
284 | 0 | return 0; |
285 | 0 | } |
286 | | |
287 | | static void fr_value_box_init_zero(fr_value_box_t *vb, fr_type_t type) |
288 | 0 | { |
289 | 0 | switch (type) { |
290 | 0 | case FR_TYPE_STRING: |
291 | 0 | fr_value_box_strdup_shallow(vb, NULL, "", false); |
292 | 0 | break; |
293 | | |
294 | 0 | case FR_TYPE_OCTETS: |
295 | 0 | fr_value_box_memdup_shallow(vb, NULL, (void const *) "", 0, false); |
296 | 0 | break; |
297 | | |
298 | 0 | default: |
299 | 0 | fr_value_box_init(vb, type, NULL, false); |
300 | 0 | break; |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | | /* |
305 | | * While binary operations do require two arguments, handling that |
306 | | * with the xlat argument parser is too aggressive, and causes unlang like |
307 | | * `foo = bar + baz` to set the rcode to fail, rather than soft failing |
308 | | * as would be expected. |
309 | | */ |
310 | | static xlat_arg_parser_t const binary_op_xlat_args[] = { |
311 | | { .required = false, .type = FR_TYPE_VOID }, |
312 | | { .required = false, .type = FR_TYPE_VOID }, |
313 | | XLAT_ARG_PARSER_TERMINATOR |
314 | | }; |
315 | | |
316 | | static xlat_action_t xlat_binary_op(TALLOC_CTX *ctx, fr_dcursor_t *out, |
317 | | UNUSED xlat_ctx_t const *xctx, |
318 | | request_t *request, fr_value_box_list_t *in, |
319 | | fr_token_t op, |
320 | | fr_type_t default_type, fr_dict_attr_t const *enumv) |
321 | 0 | { |
322 | 0 | int rcode; |
323 | 0 | fr_value_box_t *dst, *a, *b; |
324 | 0 | fr_value_box_t one, two; |
325 | |
|
326 | 0 | MEM(dst = fr_value_box_alloc_null(ctx)); |
327 | | |
328 | | /* |
329 | | * Each argument is a FR_TYPE_GROUP, with one or more elements in a list. |
330 | | */ |
331 | 0 | a = fr_value_box_list_head(in); |
332 | 0 | if (!a) { |
333 | 0 | REDEBUG("Left argument to %s is missing", fr_tokens[op]); |
334 | 0 | fail: |
335 | 0 | talloc_free(dst); |
336 | 0 | return XLAT_ACTION_FAIL; |
337 | 0 | } |
338 | | |
339 | 0 | b = fr_value_box_list_next(in, a); |
340 | 0 | if (!b) { |
341 | 0 | REDEBUG("Right argument to %s is missing", fr_tokens[op]); |
342 | 0 | goto fail; |
343 | 0 | } |
344 | | |
345 | 0 | fr_assert(!fr_comparison_op[op]); |
346 | |
|
347 | 0 | if (fr_value_box_list_num_elements(&a->vb_group) > 1) { |
348 | 0 | REDEBUG("Expected one value as the first argument, got %u", |
349 | 0 | fr_value_box_list_num_elements(&a->vb_group)); |
350 | 0 | goto fail; |
351 | 0 | } |
352 | 0 | a = fr_value_box_list_head(&a->vb_group); |
353 | |
|
354 | 0 | if (fr_value_box_list_num_elements(&b->vb_group) > 1) { |
355 | 0 | REDEBUG("Expected one value as the second argument, got %u", |
356 | 0 | fr_value_box_list_num_elements(&b->vb_group)); |
357 | 0 | goto fail; |
358 | 0 | } |
359 | 0 | b = fr_value_box_list_head(&b->vb_group); |
360 | |
|
361 | 0 | if (!a) { |
362 | 0 | a = &one; |
363 | 0 | fr_value_box_init_zero(a, b ? b->type : default_type); |
364 | 0 | } |
365 | |
|
366 | 0 | if (!b) { |
367 | 0 | b = &two; |
368 | 0 | fr_value_box_init_zero(b, a ? a->type : default_type); |
369 | 0 | } |
370 | |
|
371 | 0 | rcode = fr_value_calc_binary_op(dst, dst, default_type, a, op, b); |
372 | 0 | if (rcode < 0) { |
373 | 0 | RPEDEBUG("Failed calculating '%pR %s %pR'", a, fr_tokens[op], b); |
374 | 0 | goto fail; |
375 | 0 | } |
376 | | |
377 | | /* |
378 | | * Over-write, but only if it's present. Otherwise leave |
379 | | * any existing enum alone. |
380 | | */ |
381 | 0 | if (enumv) dst->enumv = enumv; |
382 | 0 | fr_dcursor_append(out, dst); |
383 | |
|
384 | 0 | VALUE_BOX_LIST_VERIFY((fr_value_box_list_t *)fr_dcursor_list(out)); |
385 | 0 | return XLAT_ACTION_DONE; |
386 | 0 | } |
387 | | |
388 | | #define XLAT_BINARY_FUNC(_name, _op) \ |
389 | | static xlat_action_t xlat_func_ ## _name(TALLOC_CTX *ctx, fr_dcursor_t *out, \ |
390 | | xlat_ctx_t const *xctx, \ |
391 | 0 | request_t *request, fr_value_box_list_t *in) \ |
392 | 0 | { \ |
393 | 0 | return xlat_binary_op(ctx, out, xctx, request, in, _op, FR_TYPE_NULL, NULL); \ |
394 | 0 | } |
395 | | |
396 | 0 | XLAT_BINARY_FUNC(op_add, T_ADD) |
397 | 0 | XLAT_BINARY_FUNC(op_sub, T_SUB) |
398 | 0 | XLAT_BINARY_FUNC(op_mul, T_MUL) |
399 | 0 | XLAT_BINARY_FUNC(op_div, T_DIV) |
400 | 0 | XLAT_BINARY_FUNC(op_mod, T_MOD) |
401 | 0 | XLAT_BINARY_FUNC(op_and, T_AND) |
402 | 0 | XLAT_BINARY_FUNC(op_or, T_OR) |
403 | 0 | XLAT_BINARY_FUNC(op_xor, T_XOR) |
404 | 0 | XLAT_BINARY_FUNC(op_rshift, T_RSHIFT) |
405 | 0 | XLAT_BINARY_FUNC(op_lshift, T_LSHIFT) |
406 | | |
407 | | /* |
408 | | * While binary operations do require two arguments, handling that |
409 | | * with the xlat argument parser is too aggressive, and causes unlang like |
410 | | * `if (foo == bar)` to set the rcode to fail, rather than soft failing |
411 | | * as would be expected. |
412 | | */ |
413 | | static xlat_arg_parser_t const binary_cmp_xlat_args[] = { |
414 | | { .required = false, .type = FR_TYPE_VOID }, |
415 | | { .required = false, .type = FR_TYPE_VOID }, |
416 | | XLAT_ARG_PARSER_TERMINATOR |
417 | | }; |
418 | | |
419 | | /* |
420 | | * @todo - arguably this function should process its own arguments, like logical_or. |
421 | | * |
422 | | * That way it can return XLAT_ACTION_FAIL if either argument fails to be found, |
423 | | * or if the comparison matches, it returns the RHS value. This behavior will |
424 | | * let us to multiple comparisons, like: |
425 | | * |
426 | | * if (0 < x < 5) ... |
427 | | * |
428 | | * Which is then syntactic sugar for |
429 | | * |
430 | | * if ((0 < x) < 5) ... |
431 | | * |
432 | | * That means the comparisons no longer return "bool", so if we |
433 | | * want to do this, the change has to be made before v4 is |
434 | | * released. |
435 | | * |
436 | | * Other operators like "+=" would return their LHS value. But in order to do that, we would have to |
437 | | * update the expression parser to allow in-place edits, and that may be a fair bit of work. |
438 | | * |
439 | | * It also lets us do more interesting selectors, such as: |
440 | | * |
441 | | * foo || (0 < x) |
442 | | * |
443 | | * which if "foo" doesn't exist, evaluates the RHS, and then returns "x" only if x is greater than zero. |
444 | | * This short-hand can remove a lot of complex / nested "if" conditions. |
445 | | * |
446 | | * It could also allow us to do better attribute filtering: |
447 | | * |
448 | | * foo := (0 < foo < 5) |
449 | | * |
450 | | * Which ensures that "foo" has value only 1..4. |
451 | | * |
452 | | * It would be nice to have a syntax for "self", so we could instead do: |
453 | | * |
454 | | * foo := (0 < $$ < 5) |
455 | | * |
456 | | * Which could then also be used inside of attribute selectors: |
457 | | * |
458 | | * foreach foo (Vendor-Specific.Cisco.AVPair[$$ =~ /^x/]) { ... |
459 | | * |
460 | | * and now that we have pair cursors as value-boxes, this becomes a lot easier. |
461 | | * "$$" then becomes syntactic sugar for "the pair at the current cursor". |
462 | | * |
463 | | * It also means tracking somehow the value of $$ in the interpreter? Maybe as a short-hand, just update |
464 | | * the #request_t to add a #fr_pair_t of the current cursor value. This is a horrible hack, but would be |
465 | | * easy to do. It doesn't allow nested cursors, but whatever. The syntax for that would be hard to get |
466 | | * right. |
467 | | */ |
468 | | static xlat_action_t xlat_cmp_op(TALLOC_CTX *ctx, fr_dcursor_t *out, |
469 | | UNUSED xlat_ctx_t const *xctx, |
470 | | request_t *request, fr_value_box_list_t *in, |
471 | | fr_token_t op) |
472 | 0 | { |
473 | 0 | int rcode; |
474 | 0 | fr_value_box_t *dst, *a, *b; |
475 | | |
476 | | /* |
477 | | * Each argument is a FR_TYPE_GROUP, with one or more elements in a list. |
478 | | */ |
479 | 0 | a = fr_value_box_list_head(in); |
480 | 0 | if (!a) { |
481 | 0 | REDEBUG("Left argument to %s is missing", fr_tokens[op]); |
482 | 0 | return XLAT_ACTION_FAIL; |
483 | 0 | } |
484 | | |
485 | 0 | b = fr_value_box_list_next(in, a); |
486 | 0 | if (!b) { |
487 | 0 | REDEBUG("Right argument to %s is missing", fr_tokens[op]); |
488 | 0 | return XLAT_ACTION_FAIL; |
489 | 0 | } |
490 | | |
491 | 0 | fr_assert(a->type == FR_TYPE_GROUP); |
492 | 0 | fr_assert(b->type == FR_TYPE_GROUP); |
493 | |
|
494 | 0 | fr_assert(fr_comparison_op[op]); |
495 | |
|
496 | 0 | MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, attr_expr_bool_enum)); |
497 | |
|
498 | 0 | rcode = fr_value_calc_list_cmp(dst, dst, &a->vb_group, op, &b->vb_group); |
499 | 0 | if (rcode < 0) { |
500 | 0 | talloc_free(dst); |
501 | 0 | return XLAT_ACTION_FAIL; |
502 | 0 | } |
503 | | |
504 | 0 | fr_assert(dst->type == FR_TYPE_BOOL); |
505 | 0 | dst->enumv = attr_expr_bool_enum; |
506 | |
|
507 | 0 | fr_dcursor_append(out, dst); |
508 | 0 | VALUE_BOX_LIST_VERIFY((fr_value_box_list_t *)fr_dcursor_list(out)); |
509 | 0 | return XLAT_ACTION_DONE; |
510 | 0 | } |
511 | | |
512 | | |
513 | | #define XLAT_CMP_FUNC(_name, _op) \ |
514 | | static xlat_action_t xlat_func_ ## _name(TALLOC_CTX *ctx, fr_dcursor_t *out, \ |
515 | | xlat_ctx_t const *xctx, \ |
516 | 0 | request_t *request, fr_value_box_list_t *in) \ |
517 | 0 | { \ |
518 | 0 | return xlat_cmp_op(ctx, out, xctx, request, in, _op); \ |
519 | 0 | } Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_eq Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_ne Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_lt Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_le Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_gt Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_ge Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_eq_type Unexecuted instantiation: xlat_expr.c:xlat_func_cmp_ne_type |
520 | | |
521 | | XLAT_CMP_FUNC(cmp_eq, T_OP_CMP_EQ) |
522 | | XLAT_CMP_FUNC(cmp_ne, T_OP_NE) |
523 | | XLAT_CMP_FUNC(cmp_lt, T_OP_LT) |
524 | | XLAT_CMP_FUNC(cmp_le, T_OP_LE) |
525 | | XLAT_CMP_FUNC(cmp_gt, T_OP_GT) |
526 | | XLAT_CMP_FUNC(cmp_ge, T_OP_GE) |
527 | | XLAT_CMP_FUNC(cmp_eq_type, T_OP_CMP_EQ_TYPE) |
528 | | XLAT_CMP_FUNC(cmp_ne_type, T_OP_CMP_NE_TYPE) |
529 | | |
530 | | typedef struct { |
531 | | fr_token_t op; |
532 | | regex_t *regex; //!< precompiled regex |
533 | | xlat_exp_t *xlat; //!< to expand |
534 | | fr_regex_flags_t *regex_flags; |
535 | | } xlat_regex_inst_t; |
536 | | |
537 | | typedef struct { |
538 | | unlang_result_t last_result; |
539 | | fr_value_box_list_t list; |
540 | | } xlat_regex_rctx_t; |
541 | | |
542 | | static fr_slen_t xlat_expr_print_regex(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, fr_sbuff_escape_rules_t const *e_rules) |
543 | 2.67k | { |
544 | 2.67k | size_t at_in = fr_sbuff_used_total(out); |
545 | 2.67k | xlat_exp_t *child = xlat_exp_head(node->call.args); |
546 | 2.67k | xlat_regex_inst_t *inst = instance; |
547 | | |
548 | 2.67k | fr_assert(child != NULL); |
549 | | |
550 | 2.67k | FR_SBUFF_IN_CHAR_RETURN(out, '('); |
551 | 2.37k | xlat_print_node(out, node->call.args, child, e_rules, 0); |
552 | | |
553 | | /* |
554 | | * A space is printed after the first argument only if |
555 | | * there's a second one. So add one if we "ate" the second argument. |
556 | | */ |
557 | 2.37k | if (inst->xlat) FR_SBUFF_IN_CHAR_RETURN(out, ' '); |
558 | | |
559 | 2.37k | FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]); |
560 | 2.30k | FR_SBUFF_IN_CHAR_RETURN(out, ' '); |
561 | | |
562 | | /* |
563 | | * Regexes which aren't instantiated: only for unit tests. |
564 | | */ |
565 | 2.29k | if (!inst->xlat) { |
566 | 2.29k | child = xlat_exp_next(node->call.args, child); |
567 | | |
568 | 2.29k | if (!child) goto done; |
569 | 2.29k | fr_assert(!xlat_exp_next(node->call.args, child)); |
570 | 2.29k | fr_assert(child->type == XLAT_GROUP); |
571 | | |
572 | 2.29k | FR_SBUFF_IN_CHAR_RETURN(out, '/'); |
573 | 2.29k | FR_SBUFF_IN_STRCPY_RETURN(out, child->fmt); |
574 | 2.26k | FR_SBUFF_IN_CHAR_RETURN(out, '/'); |
575 | | |
576 | 2.26k | child = xlat_exp_head(child->group); |
577 | 2.26k | if (!child) goto done; |
578 | 2.26k | fr_assert(child->type == XLAT_TMPL); |
579 | | |
580 | | /* |
581 | | * The RHS may be a group |
582 | | */ |
583 | 2.26k | FR_SBUFF_RETURN(regex_flags_print, out, tmpl_regex_flags(child->vpt)); |
584 | 2.24k | goto done; |
585 | 2.26k | } |
586 | | |
587 | 0 | fr_assert(tmpl_contains_regex(inst->xlat->vpt)); |
588 | |
|
589 | 0 | if (inst->xlat->quote == T_SINGLE_QUOTED_STRING) FR_SBUFF_IN_CHAR_RETURN(out, 'm'); |
590 | 0 | FR_SBUFF_IN_CHAR_RETURN(out, fr_token_quote[inst->xlat->quote]); |
591 | 0 | FR_SBUFF_IN_STRCPY_RETURN(out, inst->xlat->vpt->name); |
592 | 0 | FR_SBUFF_IN_CHAR_RETURN(out, fr_token_quote[inst->xlat->quote]); |
593 | | |
594 | 0 | FR_SBUFF_RETURN(regex_flags_print, out, inst->regex_flags); |
595 | | |
596 | 2.24k | done: |
597 | 2.24k | FR_SBUFF_IN_CHAR_RETURN(out, ')'); |
598 | | |
599 | 2.24k | return fr_sbuff_used_total(out) - at_in; |
600 | 2.24k | } |
601 | | |
602 | | |
603 | | /* |
604 | | * Each argument is it's own head, because we do NOT always want |
605 | | * to go to the next argument. |
606 | | */ |
607 | | static int xlat_instantiate_regex(xlat_inst_ctx_t const *xctx) |
608 | 0 | { |
609 | 0 | xlat_regex_inst_t *inst = talloc_get_type_abort(xctx->inst, xlat_regex_inst_t); |
610 | 0 | xlat_exp_t *lhs, *rhs, *regex; |
611 | |
|
612 | 0 | lhs = xlat_exp_head(xctx->ex->call.args); |
613 | 0 | rhs = xlat_exp_next(xctx->ex->call.args, lhs); |
614 | |
|
615 | 0 | fr_assert(rhs); |
616 | 0 | fr_assert(rhs->type == XLAT_GROUP); |
617 | 0 | (void) fr_dlist_remove(&xctx->ex->call.args->dlist, rhs); |
618 | |
|
619 | 0 | regex = xlat_exp_head(rhs->group); |
620 | 0 | fr_assert(regex->type == XLAT_TMPL); |
621 | 0 | fr_assert(tmpl_contains_regex(regex->vpt)); |
622 | |
|
623 | 0 | inst->op = xctx->ex->call.func->token; |
624 | 0 | inst->regex_flags = tmpl_regex_flags(regex->vpt); |
625 | |
|
626 | 0 | inst->xlat = talloc_steal(inst, regex); |
627 | 0 | talloc_free(rhs); /* group wrapper is no longer needed */ |
628 | | |
629 | | /* |
630 | | * The RHS is more then just one regex node, it has to be dynamically expanded. |
631 | | */ |
632 | 0 | if (tmpl_contains_xlat(regex->vpt)) { |
633 | 0 | return 0; |
634 | 0 | } |
635 | | |
636 | 0 | if (tmpl_is_data_unresolved(regex->vpt)) { |
637 | 0 | fr_strerror_const("Regex must be resolved before instantiation"); |
638 | 0 | return -1; |
639 | 0 | } |
640 | | |
641 | | /* |
642 | | * Must have been caught in the parse phase. |
643 | | */ |
644 | 0 | fr_assert(tmpl_is_regex(regex->vpt)); |
645 | |
|
646 | 0 | inst->regex = tmpl_regex(regex->vpt); |
647 | |
|
648 | 0 | return 0; |
649 | 0 | } |
650 | | |
651 | | |
652 | | static xlat_arg_parser_t const regex_op_xlat_args[] = { |
653 | | { .required = true, .type = FR_TYPE_STRING }, |
654 | | { .concat = true, .type = FR_TYPE_STRING }, |
655 | | XLAT_ARG_PARSER_TERMINATOR |
656 | | }; |
657 | | |
658 | | static xlat_arg_parser_t const regex_search_xlat_args[] = { |
659 | | /* |
660 | | * A pattern to be compiled has to be escaped, unless it |
661 | | * is marked as REGEX_SAFE_FOR. |
662 | | */ |
663 | | { .required = true, .concat = true, .type = FR_TYPE_STRING, |
664 | | .func = xlat_regex_escape, .safe_for = FR_REGEX_SAFE_FOR }, /* regex string */ |
665 | | { .required = true, .concat = false, .type = FR_TYPE_STRING }, /* broken out things to match */ |
666 | | { .required = false, .concat = true, .type = FR_TYPE_STRING }, /* flags */ |
667 | | XLAT_ARG_PARSER_TERMINATOR |
668 | | }; |
669 | | |
670 | | |
671 | | /** Perform a regular expressions comparison between two operands |
672 | | * |
673 | | * @param[in] ctx to allocate resulting box in. |
674 | | * @param[in] request The current request. |
675 | | * @param[in] in list of item or items |
676 | | * @param[in,out] preg Pointer to pre-compiled or runtime-compiled |
677 | | * regular expression. In the case of runtime-compiled |
678 | | * the pattern may be stolen by the `regex_sub_to_request` |
679 | | * function as the original pattern is needed to resolve |
680 | | * capture groups. |
681 | | * The caller should only free the `regex_t *` if it |
682 | | * compiled it, and the pointer has not been set to NULL |
683 | | * when this function returns. |
684 | | * @param[out] out Where result is written. |
685 | | * @param[in] op the operation to perform. |
686 | | * @return |
687 | | * - -1 on failure. |
688 | | * - 0 for "no match". |
689 | | * - 1 for "match". |
690 | | */ |
691 | | static xlat_action_t xlat_regex_do_op(TALLOC_CTX *ctx, request_t *request, fr_value_box_list_t *in, regex_t **preg, |
692 | | fr_dcursor_t *out, fr_token_t op) |
693 | | { |
694 | | uint32_t subcaptures; |
695 | | int ret = 0; |
696 | | |
697 | | fr_regmatch_t *regmatch; |
698 | | fr_value_box_t *dst; |
699 | | fr_value_box_t *arg, *vb; |
700 | | fr_sbuff_t *agg; |
701 | | char const *subject; |
702 | | size_t len; |
703 | | fr_value_box_safety_t safety = {}; |
704 | | |
705 | | FR_SBUFF_TALLOC_THREAD_LOCAL(&agg, 256, 8192); |
706 | | |
707 | | arg = fr_value_box_list_head(in); |
708 | | fr_assert(arg != NULL); |
709 | | fr_assert(arg->type == FR_TYPE_GROUP); |
710 | | |
711 | | subcaptures = regex_subcapture_count(*preg); |
712 | | if (!subcaptures) subcaptures = REQUEST_MAX_REGEX + 1; /* +1 for %{0} (whole match) capture group */ |
713 | | MEM(regmatch = regex_match_data_alloc(NULL, subcaptures)); |
714 | | |
715 | | while ((vb = fr_value_box_list_pop_head(&arg->vb_group)) != NULL) { |
716 | | if (vb->type == FR_TYPE_STRING) { |
717 | | subject = vb->vb_strvalue; |
718 | | len = vb->vb_length; |
719 | | safety = vb->safety; |
720 | | |
721 | | } else { |
722 | | fr_value_box_list_t list; |
723 | | |
724 | | fr_value_box_list_init(&list); |
725 | | fr_value_box_list_insert_head(&list, vb); |
726 | | safety.safe_for = FR_VALUE_BOX_SAFE_FOR_ANY; |
727 | | |
728 | | vb = NULL; |
729 | | |
730 | | /* |
731 | | * Concatenate everything, and escape untrusted inputs. |
732 | | */ |
733 | | if (fr_value_box_list_concat_as_string(&safety, agg, &list, NULL, 0, ®ex_escape_rules, |
734 | | FR_VALUE_BOX_LIST_FREE_BOX, FR_REGEX_SAFE_FOR, true) < 0) { |
735 | | RPEDEBUG("Failed concatenating regular expression string"); |
736 | | talloc_free(regmatch); |
737 | | return XLAT_ACTION_FAIL; |
738 | | } |
739 | | |
740 | | subject = fr_sbuff_start(agg); |
741 | | len = fr_sbuff_used(agg); |
742 | | } |
743 | | |
744 | | /* |
745 | | * Evaluate the expression |
746 | | */ |
747 | | ret = regex_exec(*preg, subject, len, regmatch); |
748 | | talloc_free(vb); |
749 | | |
750 | | switch (ret) { |
751 | | default: |
752 | | RPEDEBUG("REGEX failed"); |
753 | | talloc_free(regmatch); |
754 | | return XLAT_ACTION_FAIL; |
755 | | |
756 | | case 0: |
757 | | regex_sub_to_request(request, NULL, NULL, NULL); /* clear out old entries */ |
758 | | continue; |
759 | | |
760 | | case 1: |
761 | | regex_sub_to_request(request, preg, ®match, &safety); |
762 | | goto done; |
763 | | } |
764 | | } |
765 | | |
766 | | done: |
767 | | talloc_free(regmatch); /* free if not consumed */ |
768 | | |
769 | | MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, attr_expr_bool_enum)); |
770 | | dst->vb_bool = (ret == (op == T_OP_REG_EQ)); |
771 | | |
772 | | fr_dcursor_append(out, dst); |
773 | | |
774 | | return XLAT_ACTION_DONE; |
775 | | } |
776 | | |
777 | | static xlat_action_t xlat_regex_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, |
778 | | xlat_ctx_t const *xctx, |
779 | | request_t *request, fr_value_box_list_t *in) |
780 | 0 | { |
781 | 0 | xlat_regex_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_regex_inst_t); |
782 | 0 | xlat_regex_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, xlat_regex_rctx_t); |
783 | 0 | ssize_t slen; |
784 | 0 | regex_t *preg = NULL; |
785 | 0 | fr_sbuff_t *agg; |
786 | |
|
787 | 0 | FR_SBUFF_TALLOC_THREAD_LOCAL(&agg, 256, 8192); |
788 | | |
789 | | /* |
790 | | * If the expansions fails, then we fail the entire thing. |
791 | | */ |
792 | 0 | if (!XLAT_RESULT_SUCCESS(&rctx->last_result)) { |
793 | 0 | talloc_free(rctx); |
794 | 0 | return XLAT_ACTION_FAIL; |
795 | 0 | } |
796 | | |
797 | | /* |
798 | | * Because we expanded the RHS ourselves, the "concat" |
799 | | * flag to the RHS argument is ignored. So we just |
800 | | * concatenate it here. We escape the various untrusted inputs. |
801 | | */ |
802 | 0 | if (fr_value_box_list_concat_as_string(NULL, agg, &rctx->list, NULL, 0, ®ex_escape_rules, |
803 | 0 | FR_VALUE_BOX_LIST_FREE_BOX, FR_REGEX_SAFE_FOR, true) < 0) { |
804 | 0 | RPEDEBUG("Failed concatenating regular expression string"); |
805 | 0 | return XLAT_ACTION_FAIL; |
806 | 0 | } |
807 | | |
808 | 0 | fr_assert(inst->regex == NULL); |
809 | |
|
810 | 0 | slen = regex_compile(rctx, &preg, fr_sbuff_start(agg), fr_sbuff_used(agg), |
811 | 0 | tmpl_regex_flags(inst->xlat->vpt), true, true); /* flags, allow subcaptures, at runtime */ |
812 | 0 | if (slen <= 0) return XLAT_ACTION_FAIL; |
813 | | |
814 | 0 | return xlat_regex_do_op(ctx, request, in, &preg, out, inst->op); |
815 | 0 | } |
816 | | |
817 | | static xlat_action_t xlat_regex_op(TALLOC_CTX *ctx, fr_dcursor_t *out, |
818 | | xlat_ctx_t const *xctx, |
819 | | request_t *request, fr_value_box_list_t *in, |
820 | | fr_token_t op) |
821 | 0 | { |
822 | 0 | xlat_regex_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_regex_inst_t); |
823 | 0 | xlat_regex_rctx_t *rctx; |
824 | 0 | regex_t *preg; |
825 | | |
826 | | /* |
827 | | * Just run precompiled regexes. |
828 | | */ |
829 | 0 | if (inst->regex) { |
830 | 0 | preg = tmpl_regex(inst->xlat->vpt); |
831 | |
|
832 | 0 | return xlat_regex_do_op(ctx, request, in, &preg, out, op); |
833 | 0 | } |
834 | | |
835 | 0 | MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), xlat_regex_rctx_t)); |
836 | 0 | fr_value_box_list_init(&rctx->list); |
837 | |
|
838 | 0 | if (unlang_xlat_yield(request, xlat_regex_resume, NULL, FR_SIGNAL_NONE, rctx) != XLAT_ACTION_YIELD) { |
839 | 0 | fail: |
840 | 0 | talloc_free(rctx); |
841 | 0 | return XLAT_ACTION_FAIL; |
842 | 0 | } |
843 | | |
844 | 0 | if (unlang_xlat_push(ctx, &rctx->last_result, &rctx->list, |
845 | 0 | request, tmpl_xlat(inst->xlat->vpt), UNLANG_SUB_FRAME) < 0) goto fail; |
846 | | |
847 | 0 | return XLAT_ACTION_PUSH_UNLANG; |
848 | 0 | } |
849 | | |
850 | | #define XLAT_REGEX_FUNC(_name, _op) \ |
851 | | static xlat_action_t xlat_func_ ## _name(TALLOC_CTX *ctx, fr_dcursor_t *out, \ |
852 | | xlat_ctx_t const *xctx, \ |
853 | 0 | request_t *request, fr_value_box_list_t *in) \ |
854 | 0 | { \ |
855 | 0 | return xlat_regex_op(ctx, out, xctx, request, in, _op); \ |
856 | 0 | } Unexecuted instantiation: xlat_expr.c:xlat_func_reg_eq Unexecuted instantiation: xlat_expr.c:xlat_func_reg_ne |
857 | | |
858 | | XLAT_REGEX_FUNC(reg_eq, T_OP_REG_EQ) |
859 | | XLAT_REGEX_FUNC(reg_ne, T_OP_REG_NE) |
860 | | |
861 | | static xlat_action_t xlat_func_regex_search(TALLOC_CTX *ctx, fr_dcursor_t *out, |
862 | | UNUSED xlat_ctx_t const *xctx, |
863 | | request_t *request, fr_value_box_list_t *in) |
864 | 0 | { |
865 | 0 | ssize_t slen; |
866 | 0 | regex_t *preg; |
867 | 0 | fr_value_box_t *regex; |
868 | 0 | xlat_action_t action; |
869 | |
|
870 | 0 | regex = fr_value_box_list_pop_head(in); |
871 | 0 | fr_assert(regex); |
872 | 0 | fr_assert(regex->type == FR_TYPE_STRING); |
873 | |
|
874 | 0 | slen = regex_compile(ctx, &preg, regex->vb_strvalue, regex->vb_length, |
875 | 0 | NULL, true, true); /* flags, allow subcaptures, at runtime */ |
876 | 0 | if (slen <= 0) { |
877 | 0 | RPEDEBUG("Failed parsing regular expression %pV", regex); |
878 | 0 | talloc_free(regex); |
879 | 0 | return XLAT_ACTION_FAIL; |
880 | 0 | } |
881 | | |
882 | 0 | action = xlat_regex_do_op(ctx, request, in, &preg, out, T_OP_REG_EQ); |
883 | 0 | talloc_free(regex); |
884 | 0 | talloc_free(preg); |
885 | 0 | return action; |
886 | 0 | } |
887 | | |
888 | | typedef struct { |
889 | | bool stop_on_match; |
890 | | xlat_func_t callback; |
891 | | int argc; |
892 | | xlat_exp_head_t **argv; |
893 | | } xlat_logical_inst_t; |
894 | | |
895 | | typedef struct { |
896 | | TALLOC_CTX *ctx; |
897 | | unlang_result_t last_result; |
898 | | fr_value_box_t *box; //!< output value-box |
899 | | int current; |
900 | | fr_value_box_list_t list; |
901 | | } xlat_logical_rctx_t; |
902 | | |
903 | | static fr_slen_t xlat_expr_print_nary(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, fr_sbuff_escape_rules_t const *e_rules) |
904 | 1.68k | { |
905 | 1.68k | size_t at_in = fr_sbuff_used_total(out); |
906 | 1.68k | xlat_logical_inst_t *inst = instance; |
907 | 1.68k | xlat_exp_head_t *head; |
908 | | |
909 | 1.68k | FR_SBUFF_IN_CHAR_RETURN(out, '('); |
910 | | |
911 | | /* |
912 | | * We might get called before the node is instantiated. |
913 | | */ |
914 | 1.47k | if (!inst->argv) { |
915 | 1.47k | head = node->call.args; |
916 | | |
917 | 1.47k | fr_assert(head != NULL); |
918 | | |
919 | 4.54k | xlat_exp_foreach(head, child) { |
920 | 4.54k | xlat_print_node(out, head, child, e_rules, 0); |
921 | | |
922 | 4.54k | if (!xlat_exp_next(head, child)) break; |
923 | | |
924 | 3.19k | FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]); |
925 | 3.08k | FR_SBUFF_IN_CHAR_RETURN(out, ' '); |
926 | 3.08k | } |
927 | 1.47k | } else { |
928 | 0 | int i; |
929 | |
|
930 | 0 | for (i = 0; i < inst->argc; i++) { |
931 | 0 | xlat_print(out, inst->argv[i], e_rules); |
932 | 0 | if (i == (inst->argc - 1)) break; |
933 | | |
934 | 0 | FR_SBUFF_IN_CHAR_RETURN(out, ' '); |
935 | 0 | FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]); |
936 | 0 | if ((i + 1) < inst->argc) FR_SBUFF_IN_CHAR_RETURN(out, ' '); |
937 | 0 | } |
938 | 0 | } |
939 | | |
940 | 1.35k | FR_SBUFF_IN_CHAR_RETURN(out, ')'); |
941 | | |
942 | 1.21k | return fr_sbuff_used_total(out) - at_in; |
943 | 1.35k | } |
944 | | |
945 | | /* |
946 | | * This returns "false" for "ignore this argument" |
947 | | * |
948 | | * result is "false" for "delete this argument" |
949 | | * result is "true" for "return this argument". |
950 | | */ |
951 | | static bool xlat_node_matches_bool(bool *result, xlat_exp_t *parent, xlat_exp_head_t *head, bool sense) |
952 | | { |
953 | | fr_value_box_t *box; |
954 | | xlat_exp_t *node; |
955 | | |
956 | | if (!head->flags.pure) return false; |
957 | | |
958 | | node = xlat_exp_head(head); |
959 | | if (!node || xlat_exp_next(head, node)) { |
960 | | return false; |
961 | | } |
962 | | |
963 | | if (node->type == XLAT_BOX) { |
964 | | box = &node->data; |
965 | | goto check; |
966 | | } |
967 | | |
968 | | if (node->type != XLAT_TMPL) { |
969 | | return false; |
970 | | } |
971 | | |
972 | | if (!tmpl_is_data(node->vpt)) { |
973 | | return false; |
974 | | } |
975 | | |
976 | | box = tmpl_value(node->vpt); |
977 | | |
978 | | check: |
979 | | /* |
980 | | * On "true", replace the entire logical operation with the value-box. |
981 | | * |
982 | | * On "false", omit this argument, and go to the next one. |
983 | | */ |
984 | | *result = (fr_value_box_is_truthy(box) == sense); |
985 | | |
986 | | if (!*result) return true; |
987 | | |
988 | | xlat_instance_unregister_func(parent); |
989 | | |
990 | | xlat_exp_set_type(parent, XLAT_BOX); |
991 | | if (!fr_cond_assert(fr_value_box_copy(parent, &parent->data, box) == 0)) return false; |
992 | | |
993 | | return true; |
994 | | } |
995 | | |
996 | | /** Undo work which shouldn't have been done. :( |
997 | | * |
998 | | */ |
999 | | static void xlat_ungroup(xlat_exp_head_t *head) |
1000 | 0 | { |
1001 | 0 | xlat_exp_t *group, *node; |
1002 | |
|
1003 | 0 | group = xlat_exp_head(head); |
1004 | 0 | if (!group || xlat_exp_next(head, group)) return; |
1005 | | |
1006 | 0 | if (group->type != XLAT_GROUP) return; |
1007 | | |
1008 | 0 | node = xlat_exp_head(group->group); |
1009 | 0 | if (!node || xlat_exp_next(group->group, node)) return; |
1010 | | |
1011 | 0 | (void) fr_dlist_remove(&head->dlist, group); |
1012 | 0 | (void) fr_dlist_remove(&group->group->dlist, node); |
1013 | 0 | (void) talloc_steal(head, node); |
1014 | |
|
1015 | 0 | talloc_free(group); |
1016 | |
|
1017 | 0 | fr_dlist_insert_tail(&head->dlist, node); |
1018 | 0 | head->flags = node->flags; |
1019 | 0 | } |
1020 | | |
1021 | | /** If any argument resolves to inst->stop_on_match, the entire thing is a bool of inst->stop_on_match. |
1022 | | * |
1023 | | * If any argument resolves to !inst->stop_on_match, it is removed. |
1024 | | */ |
1025 | | static int xlat_expr_logical_purify(xlat_exp_t *node, void *instance, request_t *request) |
1026 | | { |
1027 | | int i, j; |
1028 | | int deleted = 0; |
1029 | | bool result; |
1030 | | xlat_logical_inst_t *inst = talloc_get_type_abort(instance, xlat_logical_inst_t); |
1031 | | xlat_exp_head_t *group; |
1032 | | |
1033 | | fr_assert(node->type == XLAT_FUNC); |
1034 | | |
1035 | | /* |
1036 | | * Don't check the last argument. If everything else gets deleted, |
1037 | | * then we just return the last argument. |
1038 | | */ |
1039 | | for (i = 0; i < inst->argc; i++) { |
1040 | | /* |
1041 | | * The argument is pure, so we purify it before |
1042 | | * doing any other checks. |
1043 | | */ |
1044 | | if (inst->argv[i]->flags.can_purify) { |
1045 | | if (xlat_purify_list(inst->argv[i], request) < 0) return -1; |
1046 | | |
1047 | | /* |
1048 | | * xlat_purify_list expects that its outputs will be arguments to functions, so |
1049 | | * they're grouped. We con't need that, so we ungroup them here. |
1050 | | */ |
1051 | | xlat_ungroup(inst->argv[i]); |
1052 | | } |
1053 | | |
1054 | | /* |
1055 | | * This returns "false" for "ignore". |
1056 | | * |
1057 | | * result is "false" for "delete this argument" |
1058 | | * result is "true" for "return this argument". |
1059 | | */ |
1060 | | if (!xlat_node_matches_bool(&result, node, inst->argv[i], inst->stop_on_match)) continue; |
1061 | | |
1062 | | /* |
1063 | | * 0 && EXPR --> 0. |
1064 | | * 1 || EXPR --> 1 |
1065 | | * |
1066 | | * Parent is now an XLAT_BOX, so we're done. |
1067 | | */ |
1068 | | if (result) return 0; |
1069 | | |
1070 | | /* |
1071 | | * We're at the last argument. If we've deleted everything else, then just leave the |
1072 | | * last argument alone. Otherwise some arguments remain, so we can delete the last one. |
1073 | | */ |
1074 | | if (((i + 1) == inst->argc) && (deleted == i)) break; |
1075 | | |
1076 | | TALLOC_FREE(inst->argv[i]); |
1077 | | deleted++; |
1078 | | } |
1079 | | |
1080 | | if (!deleted) return 0; |
1081 | | |
1082 | | /* |
1083 | | * Pack the array. We insert at i, and read from j. We don't need to read the deleted entries, |
1084 | | * as they all MUST be NULL. |
1085 | | */ |
1086 | | i = 0; |
1087 | | j = -1; |
1088 | | while (i < (inst->argc - deleted)) { |
1089 | | if (j >= inst->argc) break; |
1090 | | |
1091 | | if (inst->argv[i]) { |
1092 | | i++; |
1093 | | continue; |
1094 | | } |
1095 | | |
1096 | | /* |
1097 | | * Start searching from the next entry, OR start searching from where we left off before. |
1098 | | */ |
1099 | | if (j < 0) j = i + 1; |
1100 | | |
1101 | | /* |
1102 | | * Find the first non-NULL entry, and insert it in argv[i]. We search here until the end |
1103 | | * of the array, because we may have deleted entries from the start of the array. |
1104 | | */ |
1105 | | while (j < inst->argc) { |
1106 | | if (inst->argv[j]) break; |
1107 | | j++; |
1108 | | } |
1109 | | |
1110 | | /* |
1111 | | * Move the entry down, and clear out the tail end of the array. |
1112 | | */ |
1113 | | inst->argv[i++] = inst->argv[j]; |
1114 | | inst->argv[j++] = NULL; |
1115 | | } |
1116 | | |
1117 | | inst->argc -= deleted; |
1118 | | |
1119 | | if (inst->argc > 1) return 0; |
1120 | | |
1121 | | /* |
1122 | | * Only one argument left. We can hoist the child into ourselves, and omit the logical operation. |
1123 | | */ |
1124 | | group = inst->argv[0]; |
1125 | | fr_assert(group != NULL); |
1126 | | talloc_steal(node, group); |
1127 | | |
1128 | | xlat_instance_unregister_func(node); |
1129 | | xlat_exp_set_type(node, XLAT_GROUP); |
1130 | | |
1131 | | /* re-print, with purified nodes removed */ |
1132 | | { |
1133 | | char *name; |
1134 | | |
1135 | | MEM(xlat_aprint(node, &name, group, NULL) >= 0); |
1136 | | xlat_exp_set_name_shallow(node, name); |
1137 | | } |
1138 | | |
1139 | | talloc_free(node->group); |
1140 | | node->group = group; |
1141 | | node->flags = group->flags; |
1142 | | |
1143 | | return 0; |
1144 | | } |
1145 | | |
1146 | | /** Process one argument of a logical operation. |
1147 | | * |
1148 | | * If we see a list in a truthy context, then we DON'T expand the list. Instead, we return a bool which |
1149 | | * indicates if the list was empty (or not). This prevents us from returning a whole mess of value-boxes |
1150 | | * when the user just wanted to see if the list existed. |
1151 | | * |
1152 | | * Otherwise, we expand the xlat, and continue. |
1153 | | */ |
1154 | | static xlat_action_t xlat_logical_process_arg(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, |
1155 | | xlat_ctx_t const *xctx, |
1156 | | request_t *request, UNUSED fr_value_box_list_t *in) |
1157 | 0 | { |
1158 | 0 | xlat_logical_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_logical_inst_t); |
1159 | 0 | xlat_logical_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, xlat_logical_rctx_t); |
1160 | | |
1161 | | /* |
1162 | | * Push the xlat onto the stack for expansion. |
1163 | | */ |
1164 | 0 | if (unlang_xlat_yield(request, inst->callback, NULL, FR_SIGNAL_NONE, rctx) != XLAT_ACTION_YIELD) { |
1165 | 0 | fail: |
1166 | 0 | talloc_free(rctx->box); |
1167 | 0 | talloc_free(rctx); |
1168 | 0 | return XLAT_ACTION_FAIL; |
1169 | 0 | } |
1170 | | |
1171 | 0 | if (unlang_xlat_push(rctx, &rctx->last_result, &rctx->list, |
1172 | 0 | request, inst->argv[rctx->current], UNLANG_SUB_FRAME) < 0) goto fail; |
1173 | | |
1174 | 0 | return XLAT_ACTION_PUSH_UNLANG; |
1175 | 0 | } |
1176 | | |
1177 | | /** See if the input is truthy or not. |
1178 | | * |
1179 | | * @param[in] rctx our ctx |
1180 | | * @param[in] in list of value-boxes to check |
1181 | | * @return |
1182 | | * - false if there are no truthy values. The last box is copied to the rctx. |
1183 | | * This is to allow us to return default values which may not be truthy, |
1184 | | * e.g. %{&Counter || 0} or %{&Framed-IP-Address || 0.0.0.0}. |
1185 | | * If we don't copy the last box to the rctx, the expression just returns NULL |
1186 | | * which is never useful... |
1187 | | * - true if we find a truthy value. The first truthy box is copied to the rctx. |
1188 | | * |
1189 | | * Empty lists are not truthy. |
1190 | | */ |
1191 | | static bool xlat_logical_or(xlat_logical_rctx_t *rctx, fr_value_box_list_t const *in) |
1192 | | { |
1193 | | fr_value_box_t *last = NULL; |
1194 | | bool ret = false; |
1195 | | |
1196 | | /* |
1197 | | * Empty lists are !truthy. |
1198 | | */ |
1199 | | if (!fr_value_box_list_num_elements(in)) return false; |
1200 | | |
1201 | | /* |
1202 | | * Loop over the input list. We CANNOT do groups. |
1203 | | */ |
1204 | | fr_value_box_list_foreach(in, box) { |
1205 | | fr_assert(fr_type_is_leaf(box->type) || fr_type_is_null(box->type)); |
1206 | | |
1207 | | last = box; |
1208 | | |
1209 | | /* |
1210 | | * Remember the last box we found. |
1211 | | * |
1212 | | * If it's truthy, then we stop immediately. |
1213 | | */ |
1214 | | if (fr_value_box_is_truthy(box)) { |
1215 | | ret = true; |
1216 | | break; |
1217 | | } |
1218 | | } |
1219 | | |
1220 | | if (!rctx->box) { |
1221 | | MEM(rctx->box = fr_value_box_alloc_null(rctx->ctx)); |
1222 | | } else { |
1223 | | fr_value_box_clear(rctx->box); |
1224 | | } |
1225 | | if (last && !fr_cond_assert(fr_value_box_copy(rctx->box, rctx->box, last) == 0)) return false; |
1226 | | |
1227 | | return ret; |
1228 | | } |
1229 | | |
1230 | | /* |
1231 | | * We've evaluated an expression. Let's see if we need to continue with || |
1232 | | */ |
1233 | | static xlat_action_t xlat_logical_or_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1234 | | xlat_ctx_t const *xctx, |
1235 | | request_t *request, fr_value_box_list_t *in) |
1236 | 0 | { |
1237 | 0 | xlat_logical_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_logical_inst_t); |
1238 | 0 | xlat_logical_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, xlat_logical_rctx_t); |
1239 | 0 | bool match; |
1240 | | |
1241 | | /* |
1242 | | * If the expansions fails, then we fail the entire thing. |
1243 | | */ |
1244 | 0 | if (!XLAT_RESULT_SUCCESS(&rctx->last_result)) { |
1245 | 0 | talloc_free(rctx->box); |
1246 | 0 | talloc_free(rctx); |
1247 | 0 | return XLAT_ACTION_FAIL; |
1248 | 0 | } |
1249 | | |
1250 | | /* |
1251 | | * Recursively check groups. i.e. we effectively flatten each list. |
1252 | | * |
1253 | | * (a, b, c) || (d, e, f) == a || b || c || d || e || f |
1254 | | */ |
1255 | 0 | match = xlat_logical_or(rctx, &rctx->list); |
1256 | 0 | if (match) goto done; |
1257 | | |
1258 | 0 | fr_value_box_list_talloc_free(&rctx->list); |
1259 | |
|
1260 | 0 | rctx->current++; |
1261 | | |
1262 | | /* |
1263 | | * Nothing to expand, return the final value we saw. |
1264 | | */ |
1265 | 0 | if (rctx->current >= inst->argc) { |
1266 | | /* |
1267 | | * Otherwise we stop on failure, with the boolean |
1268 | | * we just updated. |
1269 | | */ |
1270 | 0 | done: |
1271 | 0 | if (rctx->box) fr_dcursor_append(out, rctx->box); |
1272 | |
|
1273 | 0 | talloc_free(rctx); |
1274 | 0 | return XLAT_ACTION_DONE; |
1275 | 0 | } |
1276 | | |
1277 | 0 | return xlat_logical_process_arg(ctx, out, xctx, request, in); |
1278 | 0 | } |
1279 | | |
1280 | | /** See if the input is truthy or not. |
1281 | | * |
1282 | | * @param[in] rctx our ctx |
1283 | | * @param[in] in list of value-boxes to check |
1284 | | * @return |
1285 | | * - false on failure |
1286 | | * - true for match, with dst updated to contain the relevant box. |
1287 | | * |
1288 | | * Empty lists are not truthy. |
1289 | | */ |
1290 | | static bool xlat_logical_and(xlat_logical_rctx_t *rctx, fr_value_box_list_t const *in) |
1291 | | { |
1292 | | fr_value_box_t *found = NULL; |
1293 | | |
1294 | | /* |
1295 | | * Empty lists are !truthy. |
1296 | | */ |
1297 | | if (!fr_value_box_list_num_elements(in)) return false; |
1298 | | |
1299 | | /* |
1300 | | * Loop over the input list. We CANNOT do groups. |
1301 | | */ |
1302 | | fr_value_box_list_foreach(in, box) { |
1303 | | fr_assert(fr_type_is_leaf(box->type)); |
1304 | | |
1305 | | /* |
1306 | | * Remember the last box we found. |
1307 | | * |
1308 | | * If it's truthy, then we keep going either |
1309 | | * until the end, or until we get a "false". |
1310 | | */ |
1311 | | if (fr_value_box_is_truthy(box)) { |
1312 | | found = box; |
1313 | | continue; |
1314 | | } |
1315 | | |
1316 | | /* |
1317 | | * Stop on the first "false" |
1318 | | */ |
1319 | | return false; |
1320 | | } |
1321 | | |
1322 | | if (!found) return false; |
1323 | | |
1324 | | if (!rctx->box) { |
1325 | | MEM(rctx->box = fr_value_box_alloc_null(rctx->ctx)); |
1326 | | } else { |
1327 | | fr_value_box_clear(rctx->box); |
1328 | | } |
1329 | | if (!fr_cond_assert(fr_value_box_copy(rctx->box, rctx->box, found) == 0)) return false; |
1330 | | |
1331 | | return true; |
1332 | | } |
1333 | | |
1334 | | /* |
1335 | | * We've evaluated an expression. Let's see if we need to continue with && |
1336 | | */ |
1337 | | static xlat_action_t xlat_logical_and_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1338 | | xlat_ctx_t const *xctx, |
1339 | | request_t *request, fr_value_box_list_t *in) |
1340 | 0 | { |
1341 | 0 | xlat_logical_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_logical_inst_t); |
1342 | 0 | xlat_logical_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, xlat_logical_rctx_t); |
1343 | 0 | bool match; |
1344 | | |
1345 | | /* |
1346 | | * If the expansions fails, then we fail the entire thing. |
1347 | | */ |
1348 | 0 | if (!XLAT_RESULT_SUCCESS(&rctx->last_result)) { |
1349 | 0 | talloc_free(rctx->box); |
1350 | 0 | talloc_free(rctx); |
1351 | 0 | return XLAT_ACTION_FAIL; |
1352 | 0 | } |
1353 | | |
1354 | | /* |
1355 | | * Recursively check groups. i.e. we effectively flatten each list. |
1356 | | * |
1357 | | * (a, b, c) && (d, e, f) == a && b && c && d && e && f |
1358 | | */ |
1359 | 0 | match = xlat_logical_and(rctx, &rctx->list); |
1360 | 0 | if (!match) { |
1361 | 0 | TALLOC_FREE(rctx->box); /* parented from ctx */ |
1362 | 0 | goto done; |
1363 | 0 | } |
1364 | | |
1365 | 0 | fr_value_box_list_talloc_free(&rctx->list); |
1366 | |
|
1367 | 0 | rctx->current++; |
1368 | | |
1369 | | /* |
1370 | | * Nothing to expand, return the final value we saw. |
1371 | | */ |
1372 | 0 | if (rctx->current >= inst->argc) { |
1373 | | /* |
1374 | | * Otherwise we stop on failure, with the boolean |
1375 | | * we just updated. |
1376 | | */ |
1377 | 0 | fr_assert(rctx->box != NULL); |
1378 | 0 | fr_dcursor_append(out, rctx->box); |
1379 | |
|
1380 | 0 | done: |
1381 | 0 | talloc_free(rctx); |
1382 | 0 | return XLAT_ACTION_DONE; |
1383 | 0 | } |
1384 | | |
1385 | 0 | return xlat_logical_process_arg(ctx, out, xctx, request, in); |
1386 | 0 | } |
1387 | | |
1388 | | /* |
1389 | | * Each argument is it's own head, because we do NOT always want |
1390 | | * to go to the next argument. |
1391 | | */ |
1392 | | static int xlat_instantiate_logical(xlat_inst_ctx_t const *xctx) |
1393 | 0 | { |
1394 | 0 | xlat_logical_inst_t *inst = talloc_get_type_abort(xctx->inst, xlat_logical_inst_t); |
1395 | 0 | ssize_t argc; |
1396 | |
|
1397 | 0 | argc = xlat_flatten_to_argv(inst, &inst->argv, xctx->ex->call.args); |
1398 | 0 | if (argc < 0) return -1; |
1399 | 0 | inst->argc = (int)argc; |
1400 | 0 | if (xctx->ex->call.func->token == T_LOR) { |
1401 | 0 | inst->callback = xlat_logical_or_resume; |
1402 | 0 | inst->stop_on_match = true; |
1403 | 0 | } else { |
1404 | 0 | inst->callback = xlat_logical_and_resume; |
1405 | 0 | inst->stop_on_match = false; |
1406 | 0 | } |
1407 | |
|
1408 | 0 | return 0; |
1409 | 0 | } |
1410 | | |
1411 | | |
1412 | | /** Process logical &&, || |
1413 | | * |
1414 | | */ |
1415 | | static xlat_action_t xlat_func_logical(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1416 | | xlat_ctx_t const *xctx, |
1417 | | request_t *request, fr_value_box_list_t *in) |
1418 | | { |
1419 | | xlat_logical_rctx_t *rctx; |
1420 | | xlat_logical_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_logical_inst_t); |
1421 | | |
1422 | | MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), xlat_logical_rctx_t)); |
1423 | | rctx->ctx = ctx; |
1424 | | rctx->current = 0; |
1425 | | |
1426 | | if (inst->stop_on_match) { |
1427 | | rctx->box = NULL; |
1428 | | } else { |
1429 | | MEM(rctx->box = fr_value_box_alloc(ctx, FR_TYPE_BOOL, attr_expr_bool_enum)); |
1430 | | rctx->box->vb_bool = true; |
1431 | | } |
1432 | | fr_value_box_list_init(&rctx->list); |
1433 | | |
1434 | | (UNCONST(xlat_ctx_t *, xctx))->rctx = rctx; /* ensure it's there before a resume! */ |
1435 | | |
1436 | | return xlat_logical_process_arg(ctx, out, xctx, request, in); |
1437 | | } |
1438 | | |
1439 | | |
1440 | | static xlat_arg_parser_t const unary_op_xlat_args[] = { |
1441 | | { .required = true, .single = true, .concat = true }, |
1442 | | XLAT_ARG_PARSER_TERMINATOR |
1443 | | }; |
1444 | | |
1445 | | static xlat_action_t xlat_func_unary_op(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1446 | | UNUSED xlat_ctx_t const *xctx, |
1447 | | request_t *request, fr_value_box_list_t *in, fr_token_t op) |
1448 | 0 | { |
1449 | 0 | int rcode; |
1450 | 0 | fr_value_box_t *dst, *group, *vb; |
1451 | | |
1452 | | /* |
1453 | | * We do some basic type checks here. |
1454 | | */ |
1455 | 0 | group = fr_value_box_list_head(in); |
1456 | 0 | vb = fr_value_box_list_head(&group->vb_group); |
1457 | | |
1458 | | /* |
1459 | | * -NULL is an error |
1460 | | * ~NULL is an error |
1461 | | * !NULL is handled by xlat_func_unary_not |
1462 | | */ |
1463 | 0 | if (!vb) { |
1464 | 0 | fr_strerror_printf("Input is empty"); |
1465 | 0 | return XLAT_ACTION_FAIL; |
1466 | 0 | } |
1467 | | |
1468 | 0 | if (!fr_type_is_leaf(vb->type) || fr_type_is_variable_size(vb->type)) { |
1469 | 0 | REDEBUG("Cannot perform operation on data type %s", fr_type_to_str(vb->type)); |
1470 | 0 | return XLAT_ACTION_FAIL; |
1471 | 0 | } |
1472 | | |
1473 | 0 | MEM(dst = fr_value_box_alloc_null(ctx)); |
1474 | | |
1475 | | /* |
1476 | | * We rely on this function to do the remainder of the type checking. |
1477 | | */ |
1478 | 0 | rcode = fr_value_calc_unary_op(dst, dst, op, vb); |
1479 | 0 | if ((rcode < 0) || fr_type_is_null(dst->type)) { |
1480 | 0 | talloc_free(dst); |
1481 | 0 | return XLAT_ACTION_FAIL; |
1482 | 0 | } |
1483 | | |
1484 | 0 | fr_dcursor_append(out, dst); |
1485 | 0 | return XLAT_ACTION_DONE; |
1486 | 0 | } |
1487 | | |
1488 | | |
1489 | | static xlat_action_t xlat_func_unary_not(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1490 | | UNUSED xlat_ctx_t const *xctx, |
1491 | | UNUSED request_t *request, fr_value_box_list_t *in) |
1492 | 0 | { |
1493 | 0 | fr_value_box_t *dst, *group, *vb; |
1494 | |
|
1495 | 0 | group = fr_value_box_list_head(in); |
1496 | 0 | vb = fr_value_box_list_head(&group->vb_group); |
1497 | | |
1498 | | /* |
1499 | | * Don't call calc_unary_op(), because we want the enum names. |
1500 | | */ |
1501 | 0 | MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, attr_expr_bool_enum)); |
1502 | | |
1503 | | /* |
1504 | | * !NULL = true |
1505 | | */ |
1506 | 0 | if (!vb) { |
1507 | 0 | dst->vb_bool = true; |
1508 | 0 | } else { |
1509 | 0 | dst->vb_bool = !fr_value_box_is_truthy(vb); |
1510 | 0 | } |
1511 | |
|
1512 | 0 | fr_dcursor_append(out, dst); |
1513 | 0 | return XLAT_ACTION_DONE; |
1514 | 0 | } |
1515 | | |
1516 | | static xlat_action_t xlat_func_unary_minus(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1517 | | xlat_ctx_t const *xctx, |
1518 | | request_t *request, fr_value_box_list_t *in) |
1519 | 0 | { |
1520 | 0 | return xlat_func_unary_op(ctx, out, xctx, request, in, T_SUB); |
1521 | 0 | } |
1522 | | |
1523 | | static xlat_action_t xlat_func_unary_complement(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1524 | | xlat_ctx_t const *xctx, |
1525 | | request_t *request, fr_value_box_list_t *in) |
1526 | 0 | { |
1527 | 0 | return xlat_func_unary_op(ctx, out, xctx, request, in, T_COMPLEMENT); |
1528 | 0 | } |
1529 | | |
1530 | | static xlat_arg_parser_t const xlat_func_expr_rcode_arg[] = { |
1531 | | { .concat = true, .type = FR_TYPE_STRING }, |
1532 | | XLAT_ARG_PARSER_TERMINATOR |
1533 | | }; |
1534 | | |
1535 | | /** Holds the result of pre-parsing the rcode on startup |
1536 | | */ |
1537 | | typedef struct { |
1538 | | rlm_rcode_t rcode; //!< The preparsed rcode. |
1539 | | } xlat_rcode_inst_t; |
1540 | | |
1541 | | /** Convert static expr_rcode arguments into rcodes |
1542 | | * |
1543 | | * This saves doing the lookup at runtime, which given how frequently this xlat is used |
1544 | | * could get quite expensive. |
1545 | | */ |
1546 | | static int xlat_instantiate_expr_rcode(xlat_inst_ctx_t const *xctx) |
1547 | 0 | { |
1548 | 0 | xlat_rcode_inst_t *inst = talloc_get_type_abort(xctx->inst, xlat_rcode_inst_t); |
1549 | 0 | xlat_exp_t *arg; |
1550 | 0 | xlat_exp_t *rcode_arg; |
1551 | 0 | fr_value_box_t *rcode; |
1552 | | |
1553 | | /* |
1554 | | * If it's literal data, then we can pre-resolve it to |
1555 | | * a rcode now, and skip that at runtime. |
1556 | | */ |
1557 | 0 | arg = xlat_exp_head(xctx->ex->call.args); |
1558 | 0 | fr_assert(arg->type == XLAT_GROUP); |
1559 | | |
1560 | | /* |
1561 | | * We can only pre-parse if this if the value is |
1562 | | * in a single box... |
1563 | | */ |
1564 | 0 | if (fr_dlist_num_elements(&arg->group->dlist) != 1) return 0; |
1565 | 0 | rcode_arg = xlat_exp_head(arg->group); |
1566 | | |
1567 | | /* |
1568 | | * We can only pre-parse is this is a static value. |
1569 | | */ |
1570 | 0 | if (rcode_arg->type != XLAT_BOX) return 0; |
1571 | | |
1572 | 0 | rcode = &rcode_arg->data; |
1573 | |
|
1574 | 0 | switch (rcode->type) { |
1575 | 0 | case FR_TYPE_STRING: |
1576 | 0 | inst->rcode = fr_table_value_by_str(rcode_table, rcode->vb_strvalue, RLM_MODULE_NOT_SET); |
1577 | 0 | if (inst->rcode == RLM_MODULE_NOT_SET) { |
1578 | 0 | unknown: |
1579 | 0 | ERROR("Unknown rcode '%pV'", rcode); |
1580 | 0 | return -1; |
1581 | 0 | } |
1582 | 0 | break; |
1583 | | |
1584 | 0 | case FR_TYPE_INT8: |
1585 | 0 | case FR_TYPE_INT16: |
1586 | 0 | case FR_TYPE_INT32: |
1587 | 0 | case FR_TYPE_INT64: |
1588 | 0 | case FR_TYPE_UINT16: |
1589 | 0 | case FR_TYPE_UINT32: |
1590 | 0 | case FR_TYPE_UINT64: |
1591 | 0 | case FR_TYPE_SIZE: |
1592 | 0 | if (fr_value_box_cast_in_place(rcode_arg, rcode, FR_TYPE_UINT8, NULL) < 0) { |
1593 | 0 | invalid: |
1594 | 0 | ERROR("Invalid value for rcode '%pV'", rcode); |
1595 | 0 | return -1; |
1596 | 0 | } |
1597 | 0 | FALL_THROUGH; |
1598 | |
|
1599 | 0 | case FR_TYPE_UINT8: |
1600 | 0 | if (rcode->vb_uint8 >= RLM_MODULE_NUMCODES) goto invalid; |
1601 | 0 | inst->rcode = (rlm_rcode_t)rcode->vb_uint8; |
1602 | 0 | break; |
1603 | | |
1604 | 0 | default: |
1605 | 0 | goto unknown; |
1606 | 0 | } |
1607 | | |
1608 | | /* |
1609 | | * No point in creating useless boxes at runtime, |
1610 | | * nuke the argument now. |
1611 | | */ |
1612 | 0 | (void) fr_dlist_remove(&xctx->ex->call.args->dlist, arg); |
1613 | 0 | talloc_free(arg); |
1614 | |
|
1615 | 0 | return 0; |
1616 | 0 | } |
1617 | | |
1618 | | static fr_slen_t xlat_expr_print_rcode(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, UNUSED fr_sbuff_escape_rules_t const *e_rules) |
1619 | 4.41k | { |
1620 | 4.41k | size_t at_in = fr_sbuff_used_total(out); |
1621 | 4.41k | xlat_rcode_inst_t *inst = instance; |
1622 | | |
1623 | 4.41k | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(out, "%interpreter.rcode('"); |
1624 | 4.30k | if (xlat_exp_head(node->call.args)) { |
1625 | 4.30k | ssize_t slen; |
1626 | | |
1627 | 4.30k | xlat_exp_foreach(node->call.args, child) { |
1628 | 4.30k | slen = xlat_print_node(out, node->call.args, child, NULL, 0); |
1629 | 4.30k | if (slen < 0) return slen; |
1630 | 4.30k | } |
1631 | 4.30k | } else { |
1632 | 0 | FR_SBUFF_IN_STRCPY_RETURN(out, fr_table_str_by_value(rcode_table, inst->rcode, "<INVALID>")); |
1633 | 0 | } |
1634 | 4.30k | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(out, "')"); |
1635 | | |
1636 | 4.28k | return fr_sbuff_used_total(out) - at_in; |
1637 | 4.30k | } |
1638 | | |
1639 | | /** Match the passed rcode against request->rcode |
1640 | | * |
1641 | | * Example: |
1642 | | @verbatim |
1643 | | %interpreter.rcode('handled') == true |
1644 | | |
1645 | | # ...or how it's used normally used |
1646 | | if (handled) { |
1647 | | ... |
1648 | | } |
1649 | | @endverbatim |
1650 | | * |
1651 | | * @ingroup xlat_functions |
1652 | | */ |
1653 | | static xlat_action_t xlat_func_expr_rcode(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1654 | | xlat_ctx_t const *xctx, |
1655 | | request_t *request, fr_value_box_list_t *args) |
1656 | | { |
1657 | | xlat_rcode_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_rcode_inst_t); |
1658 | | fr_value_box_t *arg_rcode; |
1659 | | rlm_rcode_t rcode; |
1660 | | fr_value_box_t *vb; |
1661 | | |
1662 | | /* |
1663 | | * If we have zero args, it's because the instantiation |
1664 | | * function consumed them. Unless the user read the debug |
1665 | | * output, and tried to see what the rcode is, in case we |
1666 | | */ |
1667 | | if (fr_value_box_list_num_elements(args) == 0) { |
1668 | | if (inst->rcode == RLM_MODULE_NOT_SET) { |
1669 | | RDEBUG("Request rcode is '%s'", |
1670 | | fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>")); |
1671 | | return XLAT_ACTION_DONE; |
1672 | | } |
1673 | | |
1674 | | rcode = inst->rcode; |
1675 | | } else { |
1676 | | XLAT_ARGS(args, &arg_rcode); |
1677 | | rcode = fr_table_value_by_str(rcode_table, arg_rcode->vb_strvalue, RLM_MODULE_NOT_SET); |
1678 | | if (rcode == RLM_MODULE_NOT_SET) { |
1679 | | REDEBUG("Invalid rcode '%pV'", arg_rcode); |
1680 | | return XLAT_ACTION_FAIL; |
1681 | | } |
1682 | | } |
1683 | | |
1684 | | RDEBUG3("Request rcode is '%s'", |
1685 | | fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>")); |
1686 | | |
1687 | | MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_BOOL, attr_expr_bool_enum)); |
1688 | | fr_dcursor_append(out, vb); |
1689 | | vb->vb_bool = (request->rcode == rcode); |
1690 | | |
1691 | | return XLAT_ACTION_DONE; |
1692 | | } |
1693 | | |
1694 | | /** Takes no arguments |
1695 | | */ |
1696 | | static xlat_arg_parser_t const xlat_func_rcode_arg[] = { |
1697 | | XLAT_ARG_PARSER_TERMINATOR, /* Coverity gets tripped up by only having a single entry here */ |
1698 | | XLAT_ARG_PARSER_TERMINATOR |
1699 | | }; |
1700 | | |
1701 | | /** Return the current rcode as a string |
1702 | | * |
1703 | | * Example: |
1704 | | @verbatim |
1705 | | "%rcode()" == "handled" |
1706 | | @endverbatim |
1707 | | * |
1708 | | * @ingroup xlat_functions |
1709 | | */ |
1710 | | static xlat_action_t xlat_func_rcode(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1711 | | UNUSED xlat_ctx_t const *xctx, |
1712 | | request_t *request, UNUSED fr_value_box_list_t *args) |
1713 | 0 | { |
1714 | 0 | fr_value_box_t *vb; |
1715 | | |
1716 | | /* |
1717 | | * FIXME - This should really be an enum |
1718 | | */ |
1719 | 0 | MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_STRING, NULL)); |
1720 | 0 | if (fr_value_box_strdup(vb, vb, NULL, fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"), false) < 0) { |
1721 | 0 | talloc_free(vb); |
1722 | 0 | return XLAT_ACTION_FAIL; |
1723 | 0 | } |
1724 | 0 | fr_dcursor_append(out, vb); |
1725 | |
|
1726 | 0 | return XLAT_ACTION_DONE; |
1727 | 0 | } |
1728 | | |
1729 | | typedef struct { |
1730 | | tmpl_t const *vpt; //!< the attribute reference |
1731 | | } xlat_exists_inst_t; |
1732 | | |
1733 | | static xlat_arg_parser_t const xlat_func_exists_arg[] = { |
1734 | | { .type = FR_TYPE_VOID }, |
1735 | | XLAT_ARG_PARSER_TERMINATOR |
1736 | | }; |
1737 | | |
1738 | | /* |
1739 | | * We just print the node as-is. |
1740 | | */ |
1741 | | static fr_slen_t xlat_expr_print_exists(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, fr_sbuff_escape_rules_t const *e_rules) |
1742 | 656 | { |
1743 | 656 | size_t at_in = fr_sbuff_used_total(out); |
1744 | 656 | xlat_exists_inst_t *inst = instance; |
1745 | | |
1746 | 656 | if (inst->vpt) { |
1747 | 0 | FR_SBUFF_IN_STRCPY_RETURN(out, inst->vpt->name); |
1748 | 656 | } else { |
1749 | 656 | xlat_print_node(out, node->call.args, xlat_exp_head(node->call.args), e_rules, 0); |
1750 | 656 | } |
1751 | | |
1752 | 656 | return fr_sbuff_used_total(out) - at_in; |
1753 | 656 | } |
1754 | | |
1755 | | /* |
1756 | | * Don't expand the argument if it's already an attribute reference. |
1757 | | */ |
1758 | | static int xlat_instantiate_exists(xlat_inst_ctx_t const *xctx) |
1759 | 0 | { |
1760 | 0 | xlat_exists_inst_t *inst = talloc_get_type_abort(xctx->inst, xlat_exists_inst_t); |
1761 | 0 | xlat_exp_t *arg, *node; |
1762 | |
|
1763 | 0 | arg = xlat_exp_head(xctx->ex->call.args); |
1764 | |
|
1765 | 0 | fr_assert(arg->type == XLAT_GROUP); |
1766 | 0 | node = xlat_exp_head(arg->group); |
1767 | | |
1768 | | /* |
1769 | | * @todo - add an escape callback to this xlat |
1770 | | * registration, so that it can take untrusted inputs. |
1771 | | */ |
1772 | 0 | if ((node->type != XLAT_TMPL) || !tmpl_contains_attr(node->vpt)) { |
1773 | 0 | fr_strerror_const("The %exists() function can only be used internally"); |
1774 | 0 | return -1; |
1775 | 0 | } |
1776 | | |
1777 | 0 | inst->vpt = talloc_steal(inst, node->vpt); |
1778 | | |
1779 | | /* |
1780 | | * Free the input arguments so that they don't get expanded. |
1781 | | */ |
1782 | 0 | while ((arg = fr_dlist_pop_head(&xctx->ex->call.args->dlist)) != NULL) { |
1783 | 0 | talloc_free(arg); |
1784 | 0 | } |
1785 | |
|
1786 | 0 | return 0; |
1787 | 0 | } |
1788 | | |
1789 | | static xlat_action_t xlat_attr_exists(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1790 | | request_t *request, tmpl_t const *vpt, bool do_free) |
1791 | 0 | { |
1792 | 0 | fr_pair_t *vp; |
1793 | 0 | fr_value_box_t *dst; |
1794 | 0 | fr_dcursor_t cursor; |
1795 | 0 | tmpl_dcursor_ctx_t cc; |
1796 | |
|
1797 | 0 | MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, attr_expr_bool_enum)); |
1798 | |
|
1799 | 0 | vp = tmpl_dcursor_init(NULL, NULL, &cc, &cursor, request, vpt); |
1800 | 0 | dst->vb_bool = (vp != NULL); |
1801 | |
|
1802 | 0 | if (do_free) talloc_const_free(vpt); |
1803 | 0 | tmpl_dcursor_clear(&cc); |
1804 | 0 | fr_dcursor_append(out, dst); |
1805 | 0 | return XLAT_ACTION_DONE; |
1806 | 0 | } |
1807 | | |
1808 | | /** See if a named attribute exists |
1809 | | * |
1810 | | * Example: |
1811 | | @verbatim |
1812 | | "%{exists:&Foo}" == true |
1813 | | @endverbatim |
1814 | | * |
1815 | | * @ingroup xlat_functions |
1816 | | */ |
1817 | | static xlat_action_t xlat_func_exists(TALLOC_CTX *ctx, fr_dcursor_t *out, |
1818 | | xlat_ctx_t const *xctx, |
1819 | | request_t *request, UNUSED fr_value_box_list_t *in) |
1820 | 0 | { |
1821 | 0 | xlat_exists_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_exists_inst_t); |
1822 | | |
1823 | | /* |
1824 | | * We return "true" if the attribute exists. Otherwise we return "false". |
1825 | | */ |
1826 | 0 | fr_assert(inst->vpt); |
1827 | |
|
1828 | 0 | return xlat_attr_exists(ctx, out, request, inst->vpt, false); |
1829 | 0 | } |
1830 | | |
1831 | | #undef XLAT_REGISTER_BINARY_OP |
1832 | 40 | #define XLAT_REGISTER_BINARY_OP(_op, _name) \ |
1833 | 40 | do { \ |
1834 | 40 | if (unlikely((xlat = xlat_func_register(NULL, "op_" STRINGIFY(_name), xlat_func_op_ ## _name, FR_TYPE_VOID)) == NULL)) return -1; \ |
1835 | 40 | xlat_func_args_set(xlat, binary_op_xlat_args); \ |
1836 | 40 | xlat_func_flags_set(xlat, (xlat_func_flags_t)(XLAT_FUNC_FLAG_PURE | XLAT_FUNC_FLAG_INTERNAL | XLAT_FUNC_FLAG_PRIVATE)); \ |
1837 | 40 | xlat_func_print_set(xlat, xlat_expr_print_binary); \ |
1838 | 40 | xlat->token = _op; \ |
1839 | 40 | } while (0) |
1840 | | |
1841 | | #undef XLAT_REGISTER_BINARY_CMP |
1842 | 32 | #define XLAT_REGISTER_BINARY_CMP(_op, _name) \ |
1843 | 32 | do { \ |
1844 | 32 | if (unlikely((xlat = xlat_func_register(NULL, "cmp_" STRINGIFY(_name), xlat_func_cmp_ ## _name, FR_TYPE_BOOL)) == NULL)) return -1; \ |
1845 | 32 | xlat_func_args_set(xlat, binary_cmp_xlat_args); \ |
1846 | 32 | xlat_func_flags_set(xlat, (xlat_func_flags_t)(XLAT_FUNC_FLAG_PURE | XLAT_FUNC_FLAG_INTERNAL | XLAT_FUNC_FLAG_PRIVATE)); \ |
1847 | 32 | xlat_func_print_set(xlat, xlat_expr_print_binary); \ |
1848 | 32 | xlat_func_resolve_set(xlat, xlat_expr_resolve_binary); \ |
1849 | 32 | xlat->token = _op; \ |
1850 | 32 | } while (0) |
1851 | | |
1852 | | #undef XLAT_REGISTER_NARY_OP |
1853 | 8 | #define XLAT_REGISTER_NARY_OP(_op, _name, _func_name) \ |
1854 | 8 | do { \ |
1855 | 8 | if (unlikely((xlat = xlat_func_register(NULL, STRINGIFY(_name), xlat_func_ ## _func_name, FR_TYPE_VOID)) == NULL)) return -1; \ |
1856 | 8 | xlat_func_instantiate_set(xlat, xlat_instantiate_ ## _func_name, xlat_ ## _func_name ## _inst_t, NULL, NULL); \ |
1857 | 8 | xlat_func_flags_set(xlat, (xlat_func_flags_t)(XLAT_FUNC_FLAG_PURE | XLAT_FUNC_FLAG_INTERNAL | XLAT_FUNC_FLAG_PRIVATE)); \ |
1858 | 8 | xlat_func_print_set(xlat, xlat_expr_print_nary); \ |
1859 | 8 | xlat_purify_func_set(xlat, xlat_expr_logical_purify); \ |
1860 | 8 | xlat->token = _op; \ |
1861 | 8 | } while (0) |
1862 | | |
1863 | | #undef XLAT_REGISTER_REGEX_OP |
1864 | 8 | #define XLAT_REGISTER_REGEX_OP(_op, _name) \ |
1865 | 8 | do { \ |
1866 | 8 | if (unlikely((xlat = xlat_func_register(NULL, STRINGIFY(_name), xlat_func_ ## _name, FR_TYPE_BOOL)) == NULL)) return -1; \ |
1867 | 8 | xlat_func_args_set(xlat, regex_op_xlat_args); \ |
1868 | 8 | xlat_func_flags_set(xlat, (xlat_func_flags_t)(XLAT_FUNC_FLAG_PURE | XLAT_FUNC_FLAG_INTERNAL | XLAT_FUNC_FLAG_PRIVATE)); \ |
1869 | 8 | xlat_func_instantiate_set(xlat, xlat_instantiate_regex, xlat_regex_inst_t, NULL, NULL); \ |
1870 | 8 | xlat_func_print_set(xlat, xlat_expr_print_regex); \ |
1871 | 8 | xlat->token = _op; \ |
1872 | 8 | } while (0) |
1873 | | |
1874 | 12 | #define XLAT_REGISTER_BOOL(_xlat, _func, _arg, _ret_type) \ |
1875 | 12 | do { \ |
1876 | 12 | if (unlikely((xlat = xlat_func_register(NULL, _xlat, _func, _ret_type)) == NULL)) return -1; \ |
1877 | 12 | xlat_func_args_set(xlat, _arg); \ |
1878 | 12 | xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL); \ |
1879 | 12 | } while (0) |
1880 | | |
1881 | 12 | #define XLAT_REGISTER_UNARY(_op, _xlat, _func) \ |
1882 | 12 | do { \ |
1883 | 12 | if (unlikely((xlat = xlat_func_register(NULL, _xlat, _func, FR_TYPE_VOID)) == NULL)) return -1; \ |
1884 | 12 | xlat_func_args_set(xlat, unary_op_xlat_args); \ |
1885 | 12 | xlat_func_flags_set(xlat, (xlat_func_flags_t)(XLAT_FUNC_FLAG_PURE | XLAT_FUNC_FLAG_INTERNAL | XLAT_FUNC_FLAG_PRIVATE)); \ |
1886 | 12 | xlat_func_print_set(xlat, xlat_expr_print_unary); \ |
1887 | 12 | xlat->token = _op; \ |
1888 | 12 | } while (0) |
1889 | | |
1890 | | int xlat_register_expressions(void) |
1891 | 4 | { |
1892 | 4 | xlat_t *xlat; |
1893 | | |
1894 | 4 | XLAT_REGISTER_BINARY_OP(T_ADD, add); |
1895 | 4 | XLAT_REGISTER_BINARY_OP(T_SUB, sub); |
1896 | 4 | XLAT_REGISTER_BINARY_OP(T_MUL, mul); |
1897 | 4 | XLAT_REGISTER_BINARY_OP(T_DIV, div); |
1898 | 4 | XLAT_REGISTER_BINARY_OP(T_MOD, mod); |
1899 | 4 | XLAT_REGISTER_BINARY_OP(T_AND, and); |
1900 | 4 | XLAT_REGISTER_BINARY_OP(T_OR, or); |
1901 | 4 | XLAT_REGISTER_BINARY_OP(T_XOR, xor); |
1902 | 4 | XLAT_REGISTER_BINARY_OP(T_RSHIFT, rshift); |
1903 | 4 | XLAT_REGISTER_BINARY_OP(T_LSHIFT, lshift); |
1904 | | |
1905 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_CMP_EQ, eq); |
1906 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_NE, ne); |
1907 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_LT, lt); |
1908 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_LE, le); |
1909 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_GT, gt); |
1910 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_GE, ge); |
1911 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_CMP_EQ_TYPE, eq_type); |
1912 | 4 | XLAT_REGISTER_BINARY_CMP(T_OP_CMP_NE_TYPE, ne_type); |
1913 | | |
1914 | 4 | XLAT_REGISTER_REGEX_OP(T_OP_REG_EQ, reg_eq); |
1915 | 4 | XLAT_REGISTER_REGEX_OP(T_OP_REG_NE, reg_ne); |
1916 | | |
1917 | 4 | if (unlikely((xlat = xlat_func_register(NULL, "regex.search", xlat_func_regex_search, FR_TYPE_BOOL)) == NULL)) return -1; |
1918 | 4 | xlat_func_args_set(xlat, regex_search_xlat_args); |
1919 | 4 | xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_PURE); |
1920 | | |
1921 | | /* |
1922 | | * &&, || |
1923 | | * |
1924 | | * @todo - remove tmpl_resolve() from tokenize_field(), and add xlat_resolve_logical_or() / xlat_resolve_logical_and() |
1925 | | * functions which do partial resolution. |
1926 | | */ |
1927 | 4 | XLAT_REGISTER_NARY_OP(T_LAND, logical_and, logical); |
1928 | 4 | XLAT_REGISTER_NARY_OP(T_LOR, logical_or, logical); |
1929 | | |
1930 | 4 | XLAT_REGISTER_BOOL("interpreter.rcode", xlat_func_expr_rcode, xlat_func_expr_rcode_arg, FR_TYPE_BOOL); |
1931 | 4 | xlat_func_instantiate_set(xlat, xlat_instantiate_expr_rcode, xlat_rcode_inst_t, NULL, NULL); |
1932 | 4 | xlat_func_print_set(xlat, xlat_expr_print_rcode); |
1933 | | |
1934 | 4 | XLAT_REGISTER_BOOL("expr.rcode", xlat_func_expr_rcode, xlat_func_expr_rcode_arg, FR_TYPE_BOOL); |
1935 | 4 | xlat->deprecated = true; |
1936 | 4 | xlat_func_instantiate_set(xlat, xlat_instantiate_expr_rcode, xlat_rcode_inst_t, NULL, NULL); |
1937 | 4 | xlat_func_print_set(xlat, xlat_expr_print_rcode); |
1938 | | |
1939 | 4 | XLAT_REGISTER_BOOL("exists", xlat_func_exists, xlat_func_exists_arg, FR_TYPE_BOOL); |
1940 | 4 | xlat_func_instantiate_set(xlat, xlat_instantiate_exists, xlat_exists_inst_t, NULL, NULL); |
1941 | 4 | xlat_func_print_set(xlat, xlat_expr_print_exists); |
1942 | 4 | xlat_func_flags_set(xlat, (xlat_func_flags_t)(XLAT_FUNC_FLAG_INTERNAL | XLAT_FUNC_FLAG_PRIVATE)); |
1943 | | |
1944 | 4 | if (unlikely((xlat = xlat_func_register(NULL, "rcode", xlat_func_rcode, FR_TYPE_STRING)) == NULL)) return -1; |
1945 | 4 | xlat_func_args_set(xlat, xlat_func_rcode_arg); |
1946 | 4 | xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL); |
1947 | | |
1948 | | /* |
1949 | | * -EXPR |
1950 | | * ~EXPR |
1951 | | * !EXPR |
1952 | | */ |
1953 | 4 | XLAT_REGISTER_UNARY(T_SUB, "unary_minus", xlat_func_unary_minus); |
1954 | 4 | XLAT_REGISTER_UNARY(T_COMPLEMENT, "unary_complement", xlat_func_unary_complement); |
1955 | 4 | XLAT_REGISTER_UNARY(T_NOT, "unary_not", xlat_func_unary_not); |
1956 | | |
1957 | 4 | return 0; |
1958 | 4 | } |
1959 | | |
1960 | | /* |
1961 | | * Must use the same names as above. |
1962 | | */ |
1963 | | static const fr_sbuff_term_elem_t binary_ops[T_TOKEN_LAST] = { |
1964 | | [ T_ADD ] = L("op_add"), |
1965 | | [ T_SUB ] = L("op_sub"), |
1966 | | [ T_MUL ] = L("op_mul"), |
1967 | | [ T_DIV ] = L("op_div"), |
1968 | | [ T_MOD ] = L("op_mod"), |
1969 | | [ T_AND ] = L("op_and"), |
1970 | | [ T_OR ] = L("op_or"), |
1971 | | [ T_XOR ] = L("op_xor"), |
1972 | | [ T_RSHIFT ] = L("op_rshift"), |
1973 | | [ T_LSHIFT ] = L("op_lshift"), |
1974 | | |
1975 | | [ T_LAND ] = L("logical_and"), |
1976 | | [ T_LOR ] = L("logical_or"), |
1977 | | |
1978 | | [ T_OP_CMP_EQ ] = L("cmp_eq"), |
1979 | | [ T_OP_NE ] = L("cmp_ne"), |
1980 | | [ T_OP_LT ] = L("cmp_lt"), |
1981 | | [ T_OP_LE ] = L("cmp_le"), |
1982 | | [ T_OP_GT ] = L("cmp_gt"), |
1983 | | [ T_OP_GE ] = L("cmp_ge"), |
1984 | | |
1985 | | [ T_OP_CMP_EQ_TYPE ] = L("cmp_eq_type"), |
1986 | | [ T_OP_CMP_NE_TYPE ] = L("cmp_ne_type"), |
1987 | | |
1988 | | [ T_OP_REG_EQ ] = L("reg_eq"), |
1989 | | [ T_OP_REG_NE ] = L("reg_ne"), |
1990 | | }; |
1991 | | |
1992 | | /* |
1993 | | * Which are logical operations |
1994 | | */ |
1995 | | static const bool logical_ops[T_TOKEN_LAST] = { |
1996 | | [T_LAND] = true, |
1997 | | [T_LOR] = true, |
1998 | | }; |
1999 | | |
2000 | | /* |
2001 | | * These operators can take multiple arguments. |
2002 | | * |
2003 | | * @todo - include T_ADD, T_SUB, T_MUL, T_AND, T_OR, T_XOR, here too. |
2004 | | * |
2005 | | * This array should contain a function pointer to the code which either appends the results, or does |
2006 | | * peephole optimizations to merge the arguments together. This merging will reduce run-time effort. |
2007 | | */ |
2008 | | static const bool multivalue_ops[T_TOKEN_LAST] = { |
2009 | | [T_LAND] = true, |
2010 | | [T_LOR] = true, |
2011 | | }; |
2012 | | |
2013 | | /* |
2014 | | * Allow for BEDMAS ordering. Gross ordering is first number, |
2015 | | * fine ordering is second number. Unused operators are assigned as zero. |
2016 | | * |
2017 | | * Larger numbers are higher precedence. |
2018 | | */ |
2019 | | #define P(_x, _y) (((_x) << 4) | (_y)) |
2020 | | |
2021 | | static const int precedence[T_TOKEN_LAST] = { |
2022 | | [T_INVALID] = 0, |
2023 | | |
2024 | | /* |
2025 | | * Assignment operators go here as P(1,n) |
2026 | | * |
2027 | | * += -= *= /= %= <<= >>= &= ^= |= |
2028 | | * |
2029 | | * We want the output of the assignment operators to be the result of the assignment. This means |
2030 | | * that the assignments can really only be done for simple attributes, and not tmpls with filters |
2031 | | * which select multiple attributes. |
2032 | | * |
2033 | | * Which (for now) means that we likely want to disallow assignments in expressions. That's |
2034 | | * fine, as this isn't C, and we're not sure that it makes sense to do something like: |
2035 | | * |
2036 | | * if ((&foo += 5) > 60) ... |
2037 | | * |
2038 | | * Or maybe it does. Who knows? |
2039 | | */ |
2040 | | |
2041 | | [T_LOR] = P(2,0), |
2042 | | [T_LAND] = P(2,1), |
2043 | | |
2044 | | [T_OR] = P(3,0), |
2045 | | [T_XOR] = P(3,1), |
2046 | | [T_AND] = P(3,2), |
2047 | | |
2048 | | [T_OP_REG_EQ] = P(4,0), |
2049 | | [T_OP_REG_NE] = P(4,0), |
2050 | | |
2051 | | [T_OP_CMP_EQ] = P(4,1), |
2052 | | [T_OP_NE] = P(4,1), |
2053 | | |
2054 | | [T_OP_CMP_EQ_TYPE] = P(4,1), |
2055 | | [T_OP_CMP_NE_TYPE] = P(4,1), |
2056 | | |
2057 | | [T_OP_LT] = P(5,0), |
2058 | | [T_OP_LE] = P(5,0), |
2059 | | [T_OP_GT] = P(5,0), |
2060 | | [T_OP_GE] = P(5,0), |
2061 | | |
2062 | | [T_RSHIFT] = P(6,0), |
2063 | | [T_LSHIFT] = P(6,0), |
2064 | | |
2065 | | [T_SUB] = P(7,0), |
2066 | | [T_ADD] = P(7,1), |
2067 | | |
2068 | | [T_MOD] = P(8,0), |
2069 | | [T_MUL] = P(8,1), |
2070 | | [T_DIV] = P(8,2), |
2071 | | |
2072 | | [T_LBRACE] = P(10,0), |
2073 | | }; |
2074 | | |
2075 | | #define fr_sbuff_skip_whitespace(_x) \ |
2076 | 2.76M | do { \ |
2077 | 2.76M | while (isspace(fr_sbuff_uint8(_x, '\0'))) fr_sbuff_advance(_x, 1); \ |
2078 | 2.76M | } while (0) |
2079 | | |
2080 | | static ssize_t tokenize_expression(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2081 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, |
2082 | | fr_token_t prev, fr_sbuff_parse_rules_t const *bracket_rules, |
2083 | | fr_sbuff_parse_rules_t const *input_rules, bool cond) CC_HINT(nonnull(1,2,3,4,5)); |
2084 | | |
2085 | | static ssize_t tokenize_field(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2086 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, |
2087 | | fr_sbuff_parse_rules_t const *bracket_rules, char *out_c, bool cond) CC_HINT(nonnull(1,2,3,4,5)); |
2088 | | |
2089 | | static fr_slen_t tokenize_unary(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2090 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, |
2091 | | fr_sbuff_parse_rules_t const *bracket_rules, char *out_c, bool cond) CC_HINT(nonnull(1,2,3,4,5)); |
2092 | | |
2093 | | static fr_table_num_sorted_t const expr_quote_table[] = { |
2094 | | { L("\""), T_DOUBLE_QUOTED_STRING }, /* Don't re-order, backslash throws off ordering */ |
2095 | | { L("'"), T_SINGLE_QUOTED_STRING }, |
2096 | | { L("/"), T_SOLIDUS_QUOTED_STRING }, |
2097 | | { L("`"), T_BACK_QUOTED_STRING } |
2098 | | }; |
2099 | | static size_t expr_quote_table_len = NUM_ELEMENTS(expr_quote_table); |
2100 | | |
2101 | | |
2102 | | /* |
2103 | | * Look for prefix operators |
2104 | | * |
2105 | | * + = ignore |
2106 | | * - = unary_minus(next) |
2107 | | * ! = unary_not(next) |
2108 | | * ~ = unary_xor(0, next) |
2109 | | * (expr) = recurse, and parse expr |
2110 | | * |
2111 | | * as a special case, <type> is a cast. Which lets us know how |
2112 | | * to parse the next thing we get. Otherwise, parse the thing as |
2113 | | * int64_t. |
2114 | | */ |
2115 | | static fr_slen_t tokenize_unary(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2116 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, |
2117 | | fr_sbuff_parse_rules_t const *bracket_rules, char *out_c, bool cond) |
2118 | 379k | { |
2119 | 379k | xlat_exp_t *node = NULL, *unary = NULL; |
2120 | 379k | xlat_t *func = NULL; |
2121 | 379k | fr_sbuff_t our_in = FR_SBUFF(in); |
2122 | 379k | char c = '\0'; |
2123 | 379k | tmpl_rules_t our_t_rules; |
2124 | | |
2125 | 379k | XLAT_DEBUG("UNARY <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in))); |
2126 | | |
2127 | 379k | tmpl_rules_copy_depth(&our_t_rules, t_rules); |
2128 | 379k | if (tmpl_rules_depth_exceeded(&our_t_rules)) FR_SBUFF_ERROR_RETURN(&our_in); |
2129 | 379k | t_rules = &our_t_rules; |
2130 | | |
2131 | | /* |
2132 | | * Handle !-~ by adding a unary function to the xlat |
2133 | | * node, with the first argument being the _next_ thing |
2134 | | * we allocate. |
2135 | | */ |
2136 | 379k | if (fr_sbuff_next_if_char(&our_in, '!')) { /* unary not */ |
2137 | 12.9k | func = xlat_func_find("unary_not", 9); |
2138 | 12.9k | fr_assert(func != NULL); |
2139 | 12.9k | c = '!'; |
2140 | 12.9k | goto check_for_double; |
2141 | | |
2142 | 12.9k | } |
2143 | 366k | else if (fr_sbuff_next_if_char(&our_in, '-')) { /* unary minus */ |
2144 | 41.2k | fr_sbuff_skip_whitespace(&our_in); |
2145 | | |
2146 | | /* |
2147 | | * -4 is a number, not minus(4). |
2148 | | */ |
2149 | 41.2k | if (fr_sbuff_is_digit(&our_in)) goto field; |
2150 | | |
2151 | 21.0k | func = xlat_func_find("unary_minus", 11); |
2152 | 21.0k | fr_assert(func != NULL); |
2153 | 21.0k | c = '-'; |
2154 | 21.0k | goto check_for_double; |
2155 | | |
2156 | 41.2k | } |
2157 | 324k | else if (fr_sbuff_next_if_char(&our_in, '~')) { /* unary complement */ |
2158 | 1.67k | func = xlat_func_find("unary_complement", 16); |
2159 | 1.67k | fr_assert(func != NULL); |
2160 | 1.67k | c = '~'; |
2161 | 1.67k | goto check_for_double; |
2162 | | |
2163 | 1.67k | } |
2164 | 323k | else if (fr_sbuff_next_if_char(&our_in, '+')) { /* ignore unary + */ |
2165 | 2.02k | c = '+'; |
2166 | | |
2167 | 37.6k | check_for_double: |
2168 | 37.6k | fr_sbuff_skip_whitespace(&our_in); |
2169 | 37.6k | fr_sbuff_skip_whitespace(&our_in); |
2170 | 37.6k | if (fr_sbuff_is_char(&our_in, c)) { |
2171 | 8 | fr_strerror_const("Double operator is invalid"); |
2172 | 8 | FR_SBUFF_ERROR_RETURN(&our_in); |
2173 | 8 | } |
2174 | 37.6k | } |
2175 | | |
2176 | | /* |
2177 | | * Maybe we have a unary not / etc. If so, make sure |
2178 | | * that we return that, and not the child node |
2179 | | */ |
2180 | 358k | if (!func) { |
2181 | 343k | field: |
2182 | 343k | return tokenize_field(head, out, in, p_rules, t_rules, bracket_rules, out_c, cond); |
2183 | 323k | } |
2184 | | |
2185 | | /* |
2186 | | * Tokenize_field may reset this if the operation is wrapped inside of another expression. |
2187 | | */ |
2188 | 35.6k | *out_c = c; |
2189 | | |
2190 | 35.6k | MEM(unary = xlat_exp_alloc(head, XLAT_FUNC, fr_tokens[func->token], strlen(fr_tokens[func->token]))); |
2191 | 35.6k | xlat_exp_set_func(unary, func, t_rules->attr.dict_def); |
2192 | 35.6k | MEM(unary->call.args = xlat_exp_head_alloc(unary)); |
2193 | 35.6k | unary->call.args->is_argv = true; |
2194 | | |
2195 | 35.6k | if (tokenize_field(unary->call.args, &node, &our_in, p_rules, t_rules, bracket_rules, out_c, (c == '!')) <= 0) { |
2196 | 1.38k | talloc_free(unary); |
2197 | 1.38k | FR_SBUFF_ERROR_RETURN(&our_in); |
2198 | 1.38k | } |
2199 | | |
2200 | 34.2k | if (!node) { |
2201 | 0 | fr_strerror_const("Empty expressions are invalid"); |
2202 | 0 | FR_SBUFF_ERROR_RETURN(&our_in); |
2203 | 0 | } |
2204 | | |
2205 | 34.2k | xlat_func_append_arg(unary, node, (c == '!')); |
2206 | 34.2k | unary->flags.can_purify = (unary->call.func->flags.pure && unary->call.args->flags.pure) | unary->call.args->flags.can_purify; |
2207 | | |
2208 | | /* |
2209 | | * Don't add it to head->flags, that will be done when it's actually inserted. |
2210 | | */ |
2211 | | |
2212 | 34.2k | XLAT_VERIFY(unary); |
2213 | 34.2k | *out = unary; |
2214 | | |
2215 | 34.2k | FR_SBUFF_SET_RETURN(in, &our_in); |
2216 | 34.2k | } |
2217 | | |
2218 | | /** Allocate a specific cast node. |
2219 | | * |
2220 | | * With the first argument being a UINT8 of the data type. |
2221 | | * See xlat_func_cast() for the implementation. |
2222 | | * |
2223 | | */ |
2224 | | static xlat_exp_t *expr_cast_alloc(TALLOC_CTX *ctx, fr_type_t type, xlat_exp_t *child) |
2225 | 4.78k | { |
2226 | 4.78k | xlat_exp_t *cast, *node; |
2227 | 4.78k | char const *str; |
2228 | | |
2229 | | /* |
2230 | | * Create a "cast" node. The first argument is a UINT8 value-box of the cast type. The RHS is |
2231 | | * whatever "node" comes next. |
2232 | | */ |
2233 | 4.78k | MEM(cast = xlat_exp_alloc(ctx, XLAT_FUNC, "cast", 4)); |
2234 | 4.78k | MEM(cast->call.func = xlat_func_find("cast", 4)); |
2235 | | // no need to set dict here |
2236 | 4.78k | fr_assert(cast->call.func != NULL); |
2237 | 4.78k | cast->flags = cast->call.func->flags; |
2238 | | |
2239 | | /* |
2240 | | * Create argv[0] UINT8, with "Cast-Base" as |
2241 | | * the "da". This allows the printing routines |
2242 | | * to print the name of the type, and not the |
2243 | | * number. |
2244 | | */ |
2245 | 4.78k | str = fr_type_to_str(type); |
2246 | 4.78k | fr_assert(str != NULL); |
2247 | | |
2248 | 4.78k | MEM(node = xlat_exp_alloc(cast, XLAT_BOX, NULL, 0)); |
2249 | 4.78k | xlat_exp_set_name(node, str, strlen(str)); |
2250 | | |
2251 | 4.78k | fr_value_box_init(&node->data, FR_TYPE_UINT8, attr_cast_base, false); |
2252 | 4.78k | node->data.vb_uint8 = type; |
2253 | | |
2254 | 4.78k | xlat_func_append_arg(cast, node, false); |
2255 | 4.78k | (void) talloc_steal(cast, child); |
2256 | 4.78k | xlat_func_append_arg(cast, child, false); |
2257 | | |
2258 | 4.78k | XLAT_VERIFY(cast); |
2259 | | |
2260 | 4.78k | return cast; |
2261 | 4.78k | } |
2262 | | |
2263 | | static fr_slen_t expr_cast_from_substr(fr_type_t *cast, fr_sbuff_t *in) |
2264 | 379k | { |
2265 | 379k | fr_sbuff_t our_in = FR_SBUFF(in); |
2266 | 379k | ssize_t slen; |
2267 | | |
2268 | 379k | if (!fr_sbuff_next_if_char(&our_in, '(')) { |
2269 | 363k | no_cast: |
2270 | 363k | *cast = FR_TYPE_NULL; |
2271 | 363k | return 0; |
2272 | 352k | } |
2273 | | |
2274 | | /* |
2275 | | * Check for an actual data type. |
2276 | | */ |
2277 | 26.9k | fr_sbuff_out_by_longest_prefix(&slen, cast, fr_type_table, &our_in, FR_TYPE_NULL); |
2278 | | |
2279 | | /* |
2280 | | * It's not a known data type, so it's not a cast. |
2281 | | */ |
2282 | 26.9k | if (*cast == FR_TYPE_NULL) { |
2283 | 11.0k | goto no_cast; |
2284 | 11.0k | } |
2285 | | |
2286 | | /* |
2287 | | * We're not allowed to start expressions with data types: |
2288 | | * |
2289 | | * (ipaddr ... |
2290 | | * (ipaddr+... |
2291 | | * (ipaddr(... |
2292 | | */ |
2293 | 15.9k | if (!fr_sbuff_next_if_char(&our_in, ')')) { |
2294 | 43 | if (!fr_sbuff_is_in_charset(&our_in, sbuff_char_word)) { |
2295 | 33 | fr_strerror_printf("Unexpected text after data type '%s'", fr_type_to_str(*cast)); |
2296 | 33 | FR_SBUFF_ERROR_RETURN(&our_in); |
2297 | 33 | } |
2298 | | |
2299 | 10 | goto no_cast; |
2300 | 43 | } |
2301 | | |
2302 | | /* |
2303 | | * We're not allowed to cast to a structural data type: (group) |
2304 | | * |
2305 | | * @todo - maybe cast to structural data type could mean "parse it as a string"? But then where |
2306 | | * do the pairs go.. |
2307 | | */ |
2308 | 15.8k | if (!fr_type_is_leaf(*cast)) { |
2309 | 2 | fr_strerror_printf("Invalid structural data type '%s' in cast", fr_type_to_str(*cast)); |
2310 | 2 | FR_SBUFF_ERROR_RETURN(&our_in); |
2311 | 2 | } |
2312 | | |
2313 | 15.8k | fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL); |
2314 | 15.8k | FR_SBUFF_SET_RETURN(in, &our_in); |
2315 | 15.8k | } |
2316 | | |
2317 | | /* |
2318 | | * Tokenize the RHS of a regular expression. |
2319 | | */ |
2320 | | static fr_slen_t tokenize_regex_rhs(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2321 | | tmpl_rules_t const *t_rules, |
2322 | | fr_sbuff_parse_rules_t const *bracket_rules) |
2323 | 8.01k | { |
2324 | 8.01k | ssize_t slen; |
2325 | 8.01k | xlat_exp_t *node = NULL; |
2326 | 8.01k | fr_sbuff_t our_in = FR_SBUFF(in); |
2327 | 8.01k | fr_sbuff_marker_t opand_m, flag; |
2328 | 8.01k | tmpl_t *vpt; |
2329 | 8.01k | fr_token_t quote = T_SOLIDUS_QUOTED_STRING; |
2330 | 8.01k | tmpl_rules_t our_t_rules; |
2331 | | |
2332 | 8.01k | XLAT_DEBUG("REGEX_RHS <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in))); |
2333 | | |
2334 | 8.01k | tmpl_rules_copy_depth(&our_t_rules, t_rules); |
2335 | 8.01k | if (tmpl_rules_depth_exceeded(&our_t_rules)) FR_SBUFF_ERROR_RETURN(&our_in); |
2336 | 8.01k | t_rules = &our_t_rules; |
2337 | | |
2338 | 8.01k | fr_sbuff_skip_whitespace(&our_in); |
2339 | | |
2340 | | /* |
2341 | | * Record where the operand begins for better error offsets later |
2342 | | */ |
2343 | 8.01k | fr_sbuff_marker(&opand_m, &our_in); |
2344 | | |
2345 | | /* |
2346 | | * Regexes cannot have casts or sub-expressions. |
2347 | | */ |
2348 | 8.01k | if (!fr_sbuff_next_if_char(&our_in, '/')) { |
2349 | | /* |
2350 | | * Allow for m'...' ala Perl |
2351 | | */ |
2352 | 3.08k | if (!fr_sbuff_is_str(&our_in, "m'", 2)) { |
2353 | 231 | fr_strerror_const("Expected regular expression"); |
2354 | 231 | goto error; |
2355 | 231 | } |
2356 | | |
2357 | 2.85k | fr_sbuff_advance(&our_in, 2); |
2358 | 2.85k | quote = T_SINGLE_QUOTED_STRING; |
2359 | 2.85k | } |
2360 | | |
2361 | | /* |
2362 | | * Allocate the xlat node now so the talloc hierarchy is correct |
2363 | | */ |
2364 | 7.78k | MEM(node = xlat_exp_alloc(head, XLAT_TMPL, NULL, 0)); |
2365 | | |
2366 | | /* |
2367 | | * tmpl_afrom_substr does pretty much all the work of parsing the operand. Note that we pass '/' |
2368 | | * as the quote, so that the tmpl gets parsed as a regex. |
2369 | | */ |
2370 | 7.78k | (void) tmpl_afrom_substr(node, &vpt, &our_in, T_SOLIDUS_QUOTED_STRING, value_parse_rules_quoted[quote], t_rules); |
2371 | 7.78k | if (!vpt) { |
2372 | 1.38k | error: |
2373 | 1.38k | talloc_free(node); |
2374 | 1.38k | FR_SBUFF_ERROR_RETURN(&our_in); |
2375 | 969 | } |
2376 | | |
2377 | | /* |
2378 | | * @todo - allow for the RHS to be an attribute, too? |
2379 | | */ |
2380 | | |
2381 | | /* |
2382 | | * It would be nice if tmpl_afrom_substr() did this :( |
2383 | | */ |
2384 | 6.81k | if (!fr_sbuff_next_if_char(&our_in, fr_token_quote[quote])) { |
2385 | 91 | fr_strerror_const("Unterminated regular expression"); |
2386 | 91 | goto error; |
2387 | 91 | } |
2388 | | |
2389 | | /* |
2390 | | * Remember where the flags start |
2391 | | */ |
2392 | 6.72k | fr_sbuff_marker(&flag, &our_in); |
2393 | 6.72k | if (tmpl_regex_flags_substr(vpt, &our_in, bracket_rules->terminals) < 0) { |
2394 | 17 | talloc_free(node); |
2395 | 17 | FR_SBUFF_ERROR_RETURN(&our_in); |
2396 | 17 | } |
2397 | | |
2398 | 6.70k | fr_sbuff_skip_whitespace(&our_in); |
2399 | | |
2400 | | /* |
2401 | | * Try to compile regular expressions, but only if |
2402 | | * they're not being dynamically expanded. |
2403 | | */ |
2404 | 6.70k | if (!tmpl_contains_xlat(vpt)) { |
2405 | 3.99k | slen = tmpl_regex_compile(vpt, true); |
2406 | 3.99k | if (slen <= 0) goto error; |
2407 | 3.99k | } |
2408 | | |
2409 | 6.61k | node->quote = quote; |
2410 | 6.61k | xlat_exp_set_vpt(node, vpt); |
2411 | | |
2412 | 6.61k | XLAT_VERIFY(node); |
2413 | 6.61k | *out = node; |
2414 | | |
2415 | 6.61k | FR_SBUFF_SET_RETURN(in, &our_in); |
2416 | 6.70k | } |
2417 | | |
2418 | | |
2419 | | static fr_slen_t tokenize_rcode(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, fr_sbuff_term_t const *terminals) |
2420 | 138k | { |
2421 | 138k | rlm_rcode_t rcode; |
2422 | 138k | ssize_t slen; |
2423 | 138k | xlat_t *func; |
2424 | 138k | xlat_exp_t *node, *arg; |
2425 | 138k | fr_sbuff_t our_in = FR_SBUFF(in); |
2426 | | |
2427 | 138k | fr_sbuff_out_by_longest_prefix(&slen, &rcode, rcode_table, &our_in, T_BARE_WORD); |
2428 | 138k | if (slen <= 0) return 0; |
2429 | | |
2430 | 6.57k | if (!fr_sbuff_is_terminal(&our_in, terminals)) { |
2431 | 12 | if (!fr_dict_attr_allowed_chars[fr_sbuff_uint8(&our_in, '\0')]) { |
2432 | 4 | fr_strerror_const("Unexpected text after return code"); |
2433 | 4 | FR_SBUFF_ERROR_RETURN(&our_in); |
2434 | 4 | } |
2435 | 8 | return 0; |
2436 | 12 | } |
2437 | | |
2438 | | /* |
2439 | | * We do NOT do math on return codes. But these two characters are allowed for attribute names. |
2440 | | * So we don't parse "Invalid-Packet" as "Invalid - packet". |
2441 | | */ |
2442 | 6.56k | if (fr_sbuff_is_char(&our_in, '-') || fr_sbuff_is_char(&our_in, '/')) { |
2443 | 5 | return 0; |
2444 | 5 | } |
2445 | | |
2446 | | /* |
2447 | | * @todo - allow for attributes to have the name "ok-foo" ??? |
2448 | | */ |
2449 | 6.56k | func = xlat_func_find("interpreter.rcode", -1); |
2450 | 6.56k | fr_assert(func != NULL); |
2451 | | |
2452 | 6.56k | MEM(node = xlat_exp_alloc(head, XLAT_FUNC, fr_sbuff_start(&our_in), slen)); |
2453 | 6.56k | node->call.func = func; |
2454 | | // no need to set dict here |
2455 | 6.56k | node->flags = func->flags; /* rcode is impure, but can be calculated statically */ |
2456 | | |
2457 | 6.56k | MEM(arg = xlat_exp_alloc(node, XLAT_BOX, fr_sbuff_start(&our_in), slen)); |
2458 | | |
2459 | | /* |
2460 | | * We need a string for unit tests, but this should really be just a number. |
2461 | | */ |
2462 | 6.56k | fr_value_box_init(&arg->data, FR_TYPE_STRING, NULL, false); |
2463 | 6.56k | (void) fr_value_box_bstrndup(arg, &arg->data, NULL, fr_sbuff_start(&our_in), slen, false); |
2464 | | |
2465 | 6.56k | xlat_func_append_arg(node, arg, false); |
2466 | | |
2467 | 6.56k | XLAT_VERIFY(node); |
2468 | 6.56k | *out = node; |
2469 | | |
2470 | 6.56k | FR_SBUFF_SET_RETURN(in, &our_in); |
2471 | 6.56k | } |
2472 | | |
2473 | | |
2474 | | /* |
2475 | | * Tokenize a field without unary operators. |
2476 | | */ |
2477 | | static fr_slen_t tokenize_field(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2478 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, |
2479 | | fr_sbuff_parse_rules_t const *bracket_rules, char *out_c, bool cond) |
2480 | 379k | { |
2481 | 379k | fr_slen_t slen; |
2482 | 379k | xlat_exp_t *node = NULL; |
2483 | 379k | fr_sbuff_t our_in = FR_SBUFF(in); |
2484 | 379k | fr_sbuff_marker_t opand_m; |
2485 | 379k | tmpl_rules_t our_t_rules; |
2486 | 379k | tmpl_t *vpt = NULL; |
2487 | 379k | fr_token_t quote; |
2488 | 379k | fr_type_t cast_type; |
2489 | 379k | fr_dict_attr_t const *enumv; |
2490 | | |
2491 | 379k | XLAT_DEBUG("FIELD <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in))); |
2492 | | |
2493 | | /* |
2494 | | * Allow for explicit casts. Non-leaf types are forbidden. |
2495 | | */ |
2496 | 379k | if (expr_cast_from_substr(&cast_type, &our_in) < 0) FR_SBUFF_ERROR_RETURN(&our_in); |
2497 | | |
2498 | | /* |
2499 | | * Do NOT pass the cast down to the next set of parsing routines. Instead, let the next data be |
2500 | | * parsed as whatever, and then add a cast, or cast in place as necessary. |
2501 | | */ |
2502 | 379k | tmpl_rules_copy_depth(&our_t_rules, t_rules); |
2503 | 379k | if (tmpl_rules_depth_exceeded(&our_t_rules)) FR_SBUFF_ERROR_RETURN(&our_in); |
2504 | | |
2505 | 379k | if (cast_type == FR_TYPE_NULL) { |
2506 | 363k | cast_type = our_t_rules.cast; |
2507 | 363k | enumv = our_t_rules.enumv; |
2508 | 363k | } else { |
2509 | 15.8k | enumv = NULL; |
2510 | 15.8k | } |
2511 | | |
2512 | 379k | our_t_rules.cast = FR_TYPE_NULL; |
2513 | | // our_t_rules.enumv = NULL; |
2514 | | |
2515 | | /* |
2516 | | * As a special case, we allow |
2517 | | * |
2518 | | * &reply = "foo = bar" |
2519 | | * |
2520 | | * and then we don't parse the RHS as any enum. |
2521 | | */ |
2522 | 379k | if ( our_t_rules.enumv && !fr_type_is_leaf(our_t_rules.enumv->type)) { |
2523 | 927 | our_t_rules.enumv = enumv = NULL; |
2524 | 927 | } |
2525 | | |
2526 | | /* |
2527 | | * If we still have '(', then recurse for other expressions |
2528 | | * |
2529 | | * Tokenize the sub-expression, ensuring that we stop at ')'. |
2530 | | * |
2531 | | * Note that if we have a sub-expression, then we don't use the hinting for "type". |
2532 | | * That's because we're parsing a complete expression here (EXPR). So the intermediate |
2533 | | * nodes in the expression can be almost anything. And we only cast it to the final |
2534 | | * value when we get the output of the expression. |
2535 | | */ |
2536 | 379k | if (fr_sbuff_next_if_char(&our_in, '(')) { |
2537 | 11.0k | our_t_rules.cast = FR_TYPE_NULL; |
2538 | 11.0k | our_t_rules.enumv = NULL; |
2539 | | |
2540 | 11.0k | fr_sbuff_skip_whitespace(&our_in); |
2541 | 11.0k | if (fr_sbuff_is_char(&our_in, ')')) { |
2542 | 5 | fr_strerror_printf("Empty expressions are invalid"); |
2543 | 5 | FR_SBUFF_ERROR_RETURN(&our_in); |
2544 | 5 | } |
2545 | | |
2546 | | /* |
2547 | | * No input rules means "ignore external terminal sequences, as we're expecting a ')' as |
2548 | | * our terminal sequence. |
2549 | | */ |
2550 | 11.0k | if (tokenize_expression(head, &node, &our_in, bracket_rules, &our_t_rules, T_INVALID, bracket_rules, NULL, cond) <= 0) { |
2551 | 5.72k | FR_SBUFF_ERROR_RETURN(&our_in); |
2552 | 5.72k | } |
2553 | | |
2554 | 5.32k | if (!fr_sbuff_next_if_char(&our_in, ')')) { |
2555 | 1.39k | fr_strerror_printf("Failed to find trailing ')'"); |
2556 | 1.39k | FR_SBUFF_ERROR_RETURN(&our_in); |
2557 | 1.39k | } |
2558 | | |
2559 | | /* |
2560 | | * We've parsed one "thing", so we stop. The next thing should be an operator, not |
2561 | | * another value. |
2562 | | * |
2563 | | * The nested call to tokenize_expression() can return >=0 if there are spaces followed by a |
2564 | | * terminal character. So "node" may be NULL; |
2565 | | */ |
2566 | 3.92k | if (!node) { |
2567 | 0 | fr_strerror_const("Empty expressions are invalid"); |
2568 | 0 | FR_SBUFF_ERROR_RETURN(&our_in); |
2569 | 0 | } |
2570 | | |
2571 | 3.92k | *out_c = '\0'; |
2572 | 3.92k | goto done; |
2573 | 3.92k | } |
2574 | | |
2575 | | /* |
2576 | | * Record where the operand begins for better error offsets later |
2577 | | */ |
2578 | 367k | fr_sbuff_skip_whitespace(&our_in); |
2579 | 367k | fr_sbuff_marker(&opand_m, &our_in); |
2580 | | |
2581 | 367k | fr_sbuff_out_by_longest_prefix(&slen, "e, expr_quote_table, &our_in, T_BARE_WORD); |
2582 | | |
2583 | 367k | switch (quote) { |
2584 | 326k | case T_BARE_WORD: |
2585 | 326k | p_rules = bracket_rules; |
2586 | | |
2587 | | /* |
2588 | | * Peek for rcodes. |
2589 | | */ |
2590 | 326k | if (cond) { |
2591 | 138k | slen = tokenize_rcode(head, &node, &our_in, p_rules->terminals); |
2592 | 138k | if (slen < 0) FR_SBUFF_ERROR_RETURN(&our_in); |
2593 | | |
2594 | 138k | if (slen > 0) { |
2595 | 6.56k | fr_assert(node != NULL); |
2596 | 6.56k | goto done; |
2597 | 6.56k | } |
2598 | 138k | } |
2599 | 319k | FALL_THROUGH; |
2600 | | |
2601 | 361k | default: |
2602 | 361k | slen = xlat_tokenize_word(head, &node, &our_in, quote, p_rules, &our_t_rules); |
2603 | 361k | if (slen <= 0) FR_SBUFF_ERROR_RETURN(&our_in); |
2604 | | |
2605 | 350k | fr_assert(node != NULL); |
2606 | 350k | break; |
2607 | 367k | } |
2608 | | |
2609 | | /* |
2610 | | * Cast value-box. |
2611 | | */ |
2612 | 350k | if (node->type == XLAT_BOX) { |
2613 | 245k | if (cast_type != FR_TYPE_NULL) { |
2614 | 13.0k | if (node->data.type != cast_type) { |
2615 | 12.8k | if (fr_value_box_cast_in_place(node, &node->data, cast_type, NULL) < 0) goto error; |
2616 | 12.8k | } |
2617 | | |
2618 | 11.5k | cast_type = FR_TYPE_NULL; |
2619 | 11.5k | } |
2620 | 245k | } |
2621 | | |
2622 | | /* |
2623 | | * Something other than a tmpl, we can just return. |
2624 | | */ |
2625 | 349k | if (node->type != XLAT_TMPL) { |
2626 | 326k | xlat_exp_set_name(node, fr_sbuff_current(&opand_m), fr_sbuff_behind(&opand_m)); |
2627 | 326k | goto done; |
2628 | 326k | } |
2629 | | |
2630 | 22.2k | vpt = node->vpt; |
2631 | | |
2632 | | /* |
2633 | | * The tmpl has a cast, and it's the same as the explicit cast we were given, we can sometimes |
2634 | | * discard the explicit cast. |
2635 | | */ |
2636 | 22.2k | if (cast_type != FR_TYPE_NULL) { |
2637 | 2.02k | if (tmpl_rules_cast(vpt) == cast_type) { |
2638 | 0 | fr_assert(0); |
2639 | 0 | cast_type = FR_TYPE_NULL; |
2640 | |
|
2641 | 2.02k | } else if (tmpl_is_attr(vpt)) { |
2642 | 462 | fr_dict_attr_t const *da; |
2643 | | |
2644 | 462 | fr_assert(!tmpl_is_attr_unresolved(vpt)); |
2645 | | |
2646 | 462 | da = tmpl_attr_tail_da(vpt); /* could be a list! */ |
2647 | | |
2648 | | /* |
2649 | | * Set the cast for attributes. Note that tmpl_cast_set() will take care of |
2650 | | * suppressing redundant casts. But it still allows (uint32)&Service-Type, |
2651 | | * which means "return the raw value", and not "return enum name". |
2652 | | */ |
2653 | 462 | if (da) { |
2654 | 462 | if (tmpl_cast_set(vpt, cast_type) < 0) { |
2655 | 1.53k | error: |
2656 | 1.53k | fr_sbuff_set(&our_in, &opand_m); |
2657 | 1.53k | talloc_free(node); |
2658 | 1.53k | FR_SBUFF_ERROR_RETURN(&our_in); |
2659 | 15 | } |
2660 | | |
2661 | 447 | cast_type = FR_TYPE_NULL; |
2662 | | |
2663 | 447 | } else { /* it's something like &reply. */ |
2664 | 0 | fr_assert(0); |
2665 | 0 | } |
2666 | | |
2667 | 1.56k | } else if (tmpl_is_data(vpt)) { |
2668 | 0 | fr_assert(!tmpl_is_data_unresolved(vpt)); |
2669 | | |
2670 | | /* |
2671 | | * Omit our cast type if the data is already of the right type. |
2672 | | * |
2673 | | * Otherwise if we have a cast, then convert the data now, and then reset the |
2674 | | * cast_type to nothing. This work allows for better errors at startup, and |
2675 | | * minimizes run-time work. |
2676 | | */ |
2677 | 0 | if (tmpl_value_type(vpt) == cast_type) { |
2678 | 0 | cast_type = FR_TYPE_NULL; |
2679 | |
|
2680 | 0 | } else if (tmpl_cast_in_place(vpt, cast_type, enumv) < 0) { |
2681 | 0 | fr_sbuff_set(&our_in, &opand_m); |
2682 | 0 | goto error; |
2683 | |
|
2684 | 0 | } else { |
2685 | | /* |
2686 | | * We've parsed the data as the new data type, so we don't need any more |
2687 | | * casting. |
2688 | | */ |
2689 | 0 | cast_type = FR_TYPE_NULL; |
2690 | 0 | } |
2691 | |
|
2692 | 1.56k | } else if (tmpl_contains_xlat(vpt)) { |
2693 | | /* |
2694 | | * (string) "foo %{...}" is redundant. Drop the cast. |
2695 | | */ |
2696 | 1.56k | if ((cast_type == FR_TYPE_STRING) && (vpt->quote != T_BARE_WORD)) { |
2697 | 261 | tmpl_cast_set(vpt, FR_TYPE_NULL); |
2698 | 261 | cast_type = FR_TYPE_NULL; |
2699 | | |
2700 | 1.30k | } else { |
2701 | | /* |
2702 | | * Push the cast to the tmpl. |
2703 | | */ |
2704 | 1.30k | tmpl_cast_set(vpt, cast_type); |
2705 | 1.30k | cast_type = FR_TYPE_NULL; |
2706 | 1.30k | } |
2707 | | |
2708 | 1.56k | } else if (tmpl_is_attr_unresolved(vpt)) { |
2709 | 0 | fr_assert(t_rules->attr.allow_unresolved); |
2710 | |
|
2711 | 0 | } else if (tmpl_is_data_unresolved(vpt)) { |
2712 | 0 | fr_assert(0); |
2713 | |
|
2714 | 0 | fr_assert(quote == T_BARE_WORD); |
2715 | 0 | fr_strerror_const("Failed parsing input"); |
2716 | 0 | fr_sbuff_set(&our_in, &opand_m); |
2717 | 0 | goto error; |
2718 | |
|
2719 | 0 | } else { |
2720 | | /* |
2721 | | * Regex? Or something else weird? |
2722 | | */ |
2723 | 0 | tmpl_debug(stderr, vpt); |
2724 | 0 | fr_assert(0); |
2725 | 0 | } |
2726 | 2.02k | } |
2727 | | |
2728 | 22.2k | fr_assert(!tmpl_contains_regex(vpt)); |
2729 | | |
2730 | 359k | done: |
2731 | | /* |
2732 | | * If there is a cast, then reparent the node with a cast wrapper. |
2733 | | */ |
2734 | 359k | if (cast_type != FR_TYPE_NULL) { |
2735 | 654 | xlat_exp_t *cast; |
2736 | | |
2737 | 654 | MEM(cast = expr_cast_alloc(head, cast_type, node)); |
2738 | 654 | node = cast; |
2739 | 654 | } |
2740 | | |
2741 | 359k | XLAT_VERIFY(node); |
2742 | 359k | *out = node; |
2743 | | |
2744 | 359k | fr_sbuff_skip_whitespace(&our_in); |
2745 | 359k | FR_SBUFF_SET_RETURN(in, &our_in); |
2746 | 22.2k | } |
2747 | | |
2748 | | /* |
2749 | | * A mapping of operators to tokens. |
2750 | | */ |
2751 | | static fr_table_num_ordered_t const expr_assignment_op_table[] = { |
2752 | | { L("!="), T_OP_NE }, |
2753 | | { L("!=="), T_OP_CMP_NE_TYPE }, |
2754 | | |
2755 | | { L("&"), T_AND }, |
2756 | | { L("&&"), T_LAND }, |
2757 | | { L("*"), T_MUL }, |
2758 | | { L("+"), T_ADD }, |
2759 | | { L("-"), T_SUB }, |
2760 | | { L("/"), T_DIV }, |
2761 | | { L("%"), T_MOD }, |
2762 | | { L("^"), T_XOR }, |
2763 | | |
2764 | | { L("|"), T_OR }, |
2765 | | { L("||"), T_LOR }, |
2766 | | |
2767 | | { L("<"), T_OP_LT }, |
2768 | | { L("<<"), T_LSHIFT }, |
2769 | | { L("<="), T_OP_LE }, |
2770 | | |
2771 | | { L("="), T_OP_EQ }, |
2772 | | { L("=="), T_OP_CMP_EQ }, |
2773 | | { L("==="), T_OP_CMP_EQ_TYPE }, |
2774 | | |
2775 | | { L("=~"), T_OP_REG_EQ }, |
2776 | | { L("!~"), T_OP_REG_NE }, |
2777 | | |
2778 | | { L(">"), T_OP_GT }, |
2779 | | { L(">="), T_OP_GE }, |
2780 | | { L(">>"), T_RSHIFT }, |
2781 | | |
2782 | | }; |
2783 | | static size_t const expr_assignment_op_table_len = NUM_ELEMENTS(expr_assignment_op_table); |
2784 | | |
2785 | | static bool valid_type(xlat_exp_t *node) |
2786 | 139k | { |
2787 | 139k | fr_dict_attr_t const *da; |
2788 | | |
2789 | | #ifdef STATIC_ANALYZER |
2790 | | if (!node) return false; |
2791 | | #endif |
2792 | | |
2793 | 139k | if (node->type != XLAT_TMPL) return true; |
2794 | | |
2795 | 11.1k | if (tmpl_is_list(node->vpt)) { |
2796 | 35 | list: |
2797 | 35 | fr_strerror_const("Cannot use list references in condition"); |
2798 | 35 | return false; |
2799 | 6 | } |
2800 | | |
2801 | 11.1k | if (!tmpl_is_attr(node->vpt)) return true; |
2802 | | |
2803 | 3.34k | da = tmpl_attr_tail_da(node->vpt); |
2804 | 3.34k | if (fr_type_is_structural(da->type)) { |
2805 | 29 | if (da->dict == fr_dict_internal()) goto list; |
2806 | | |
2807 | 0 | fr_strerror_const("Cannot use structural types in condition"); |
2808 | 0 | return false; |
2809 | 29 | } |
2810 | | |
2811 | 3.31k | return true; |
2812 | 3.34k | } |
2813 | | |
2814 | | |
2815 | | /** Tokenize a mathematical operation. |
2816 | | * |
2817 | | * (EXPR) |
2818 | | * !EXPR |
2819 | | * A OP B |
2820 | | * |
2821 | | * If "out" is NULL then the expression is added to "head". |
2822 | | * Otherwise, it's returned to the caller. |
2823 | | */ |
2824 | | static fr_slen_t tokenize_expression(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_t *in, |
2825 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, |
2826 | | fr_token_t prev, fr_sbuff_parse_rules_t const *bracket_rules, |
2827 | | fr_sbuff_parse_rules_t const *input_rules, bool cond) |
2828 | 379k | { |
2829 | 379k | xlat_exp_t *lhs = NULL, *rhs, *node; |
2830 | 379k | xlat_t *func = NULL; |
2831 | 379k | fr_token_t op; |
2832 | 379k | fr_slen_t slen; |
2833 | 379k | fr_sbuff_marker_t m_lhs, m_op, m_rhs; |
2834 | 379k | fr_sbuff_t our_in = FR_SBUFF(in); |
2835 | 379k | char c = '\0'; |
2836 | 379k | tmpl_rules_t our_t_rules; |
2837 | | |
2838 | 379k | XLAT_DEBUG("EXPRESSION <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in))); |
2839 | | |
2840 | 379k | tmpl_rules_copy_depth(&our_t_rules, t_rules); |
2841 | 379k | if (tmpl_rules_depth_exceeded(&our_t_rules)) FR_SBUFF_ERROR_RETURN(&our_in); |
2842 | 379k | t_rules = &our_t_rules; |
2843 | | |
2844 | 379k | fr_sbuff_skip_whitespace(&our_in); |
2845 | | |
2846 | 379k | fr_sbuff_marker(&m_lhs, &our_in); |
2847 | | |
2848 | | /* |
2849 | | * Get the LHS of the operation. |
2850 | | */ |
2851 | 379k | slen = tokenize_unary(head, &lhs, &our_in, p_rules, t_rules, bracket_rules, &c, cond); |
2852 | 379k | if (slen <= 0) FR_SBUFF_ERROR_RETURN(&our_in); |
2853 | | |
2854 | 359k | if (slen == 0) { |
2855 | 0 | fr_assert(lhs == NULL); |
2856 | 0 | *out = NULL; |
2857 | 0 | FR_SBUFF_SET_RETURN(in, &our_in); |
2858 | 0 | } |
2859 | | |
2860 | 637k | redo: |
2861 | 637k | rhs = NULL; |
2862 | | |
2863 | 637k | fr_sbuff_skip_whitespace(&our_in); |
2864 | | |
2865 | | /* |
2866 | | * No more input, we're done. |
2867 | | */ |
2868 | 637k | if (fr_sbuff_extend(&our_in) == 0) { |
2869 | 349k | done: |
2870 | | /* |
2871 | | * LHS may be NULL if the expression has spaces followed by a terminal character. |
2872 | | */ |
2873 | 349k | if (lhs) XLAT_VERIFY(lhs); |
2874 | 349k | *out = lhs; |
2875 | 349k | FR_SBUFF_SET_RETURN(in, &our_in); |
2876 | 18.4k | } |
2877 | | |
2878 | | /* |
2879 | | * ')' is a terminal, even if we didn't expect it. |
2880 | | * Because if we didn't expect it, then it's an error. |
2881 | | * |
2882 | | * If we did expect it, then we return whatever we found, |
2883 | | * and let the caller eat the ')'. |
2884 | | */ |
2885 | 619k | if (fr_sbuff_is_char(&our_in, ')')) { |
2886 | 27.5k | if (!bracket_rules) { |
2887 | 0 | fr_strerror_printf("Unexpected ')'"); |
2888 | 0 | FR_SBUFF_ERROR_RETURN(&our_in); |
2889 | 0 | } |
2890 | | |
2891 | 27.5k | goto done; |
2892 | 27.5k | } |
2893 | 591k | fr_sbuff_skip_whitespace(&our_in); |
2894 | | |
2895 | | /* |
2896 | | * We hit a terminal sequence, stop. |
2897 | | */ |
2898 | 591k | if (input_rules && fr_sbuff_is_terminal(&our_in, input_rules->terminals)) goto done; |
2899 | | |
2900 | | /* |
2901 | | * Remember where we were after parsing the LHS. |
2902 | | */ |
2903 | 540k | fr_sbuff_marker(&m_op, &our_in); |
2904 | | |
2905 | | /* |
2906 | | * Get the operator. |
2907 | | */ |
2908 | 540k | XLAT_DEBUG(" operator <-- %pV", fr_box_strvalue_len(fr_sbuff_current(&our_in), fr_sbuff_remaining(&our_in))); |
2909 | 540k | fr_sbuff_out_by_longest_prefix(&slen, &op, expr_assignment_op_table, &our_in, T_INVALID); |
2910 | 540k | if ((op == T_INVALID) || !binary_ops[op].str) { |
2911 | 131 | fr_strerror_const("Invalid operator"); |
2912 | 131 | fr_sbuff_set(&our_in, &m_op); |
2913 | 131 | talloc_free(lhs); |
2914 | 131 | FR_SBUFF_ERROR_RETURN(&our_in); |
2915 | 131 | } |
2916 | | |
2917 | | /* |
2918 | | * We can't (yet) do &list1 = &list2 + &list3 |
2919 | | */ |
2920 | 540k | if (fr_binary_op[op] && t_rules->enumv && fr_type_is_structural(t_rules->enumv->type)) { |
2921 | 2 | fr_strerror_const("Invalid operator for structural attribute"); |
2922 | 2 | fr_sbuff_set(&our_in, &m_op); |
2923 | 2 | talloc_free(lhs); |
2924 | 2 | FR_SBUFF_ERROR_RETURN(&our_in); |
2925 | 2 | } |
2926 | | |
2927 | 540k | fr_assert(precedence[op] != 0); |
2928 | | |
2929 | | /* |
2930 | | * a * b + c ... = (a * b) + c ... |
2931 | | * |
2932 | | * Feed the current expression to the caller, who will |
2933 | | * take care of continuing. |
2934 | | */ |
2935 | 540k | if (precedence[op] <= precedence[prev]) { |
2936 | 251k | fr_sbuff_set(&our_in, &m_op); |
2937 | 251k | goto done; |
2938 | 251k | } |
2939 | | |
2940 | | /* |
2941 | | * &Foo and !&Foo are permitted as the LHS of || and && |
2942 | | */ |
2943 | 288k | if (((c == '!') || (c == '~')) && (op != T_LAND) && (op != T_LOR)) { |
2944 | 13 | fr_strerror_printf("Operator '%c' is only applied to the left hand side of the '%s' operation, add (..) to evaluate the operation first", c, fr_tokens[op]); |
2945 | 1.57k | fail_lhs: |
2946 | 1.57k | fr_sbuff_set(&our_in, &m_lhs); |
2947 | 1.57k | FR_SBUFF_ERROR_RETURN(&our_in); |
2948 | 13 | } |
2949 | | |
2950 | 288k | fr_sbuff_skip_whitespace(&our_in); |
2951 | 288k | fr_sbuff_marker(&m_rhs, &our_in); |
2952 | | |
2953 | | /* |
2954 | | * We now parse the RHS, allowing a (perhaps different) cast on the RHS. |
2955 | | */ |
2956 | 288k | XLAT_DEBUG(" recurse RHS <-- %pV", fr_box_strvalue_len(fr_sbuff_current(&our_in), fr_sbuff_remaining(&our_in))); |
2957 | 288k | if ((op == T_OP_REG_EQ) || (op == T_OP_REG_NE)) { |
2958 | 8.04k | fr_type_t type; |
2959 | | |
2960 | | /* |
2961 | | * @todo - LHS shouldn't be anything else. |
2962 | | */ |
2963 | 8.04k | switch (lhs->type) { |
2964 | 873 | case XLAT_TMPL: |
2965 | 873 | type = tmpl_cast_get(lhs->vpt); |
2966 | 873 | if ((type != FR_TYPE_NULL) && (type != FR_TYPE_STRING)) { |
2967 | 2 | fr_strerror_const("Casts cannot be used with regular expressions"); |
2968 | 2 | fr_sbuff_set(&our_in, &m_lhs); |
2969 | 2 | FR_SBUFF_ERROR_RETURN(&our_in); |
2970 | 2 | } |
2971 | | |
2972 | | /* |
2973 | | * Cast the LHS to a string, if it's not already one! |
2974 | | */ |
2975 | 871 | if (lhs->vpt->quote == T_BARE_WORD) tmpl_cast_set(lhs->vpt, FR_TYPE_STRING); |
2976 | 871 | break; |
2977 | | |
2978 | 2.84k | case XLAT_BOX: |
2979 | | /* |
2980 | | * 192.168.0.1 =~ /foo/ |
2981 | | * |
2982 | | * Gets the LHS automatically converted to a string. |
2983 | | */ |
2984 | 2.84k | if (lhs->data.type != FR_TYPE_STRING) { |
2985 | 2.42k | if (fr_value_box_cast_in_place(lhs, &lhs->data, FR_TYPE_STRING, NULL) < 0) { |
2986 | 4 | fr_sbuff_set(&our_in, &m_lhs); |
2987 | 4 | FR_SBUFF_ERROR_RETURN(&our_in); |
2988 | 4 | } |
2989 | 2.42k | } |
2990 | 2.83k | break; |
2991 | | |
2992 | 4.13k | case XLAT_GROUP: |
2993 | | /* |
2994 | | * A hoisted xlat has no tmpl to hang a cast on, so wrap it in an explicit cast |
2995 | | * to a string. Without this, %hash.md5(...) =~ /foo/ would match the regex |
2996 | | * against octets rather than against a string. |
2997 | | */ |
2998 | 4.13k | MEM(lhs = expr_cast_alloc(head, FR_TYPE_STRING, lhs)); |
2999 | 4.13k | break; |
3000 | | |
3001 | 194 | default: |
3002 | 194 | type = xlat_node_data_type(lhs); |
3003 | 194 | if ((type != FR_TYPE_NULL) && (type != FR_TYPE_STRING)) { |
3004 | 17 | fr_strerror_const("Function (or cast) needs to return data type 'string' in order to be used with regular expressions"); |
3005 | 17 | fr_sbuff_set(&our_in, &m_lhs); |
3006 | 17 | FR_SBUFF_ERROR_RETURN(&our_in); |
3007 | 17 | } |
3008 | 177 | break; |
3009 | 8.04k | } |
3010 | | |
3011 | 8.01k | slen = tokenize_regex_rhs(head, &rhs, &our_in, t_rules, bracket_rules); |
3012 | 280k | } else { |
3013 | 280k | tmpl_rules_t rhs_t_rules = *t_rules; |
3014 | | |
3015 | | /* |
3016 | | * Pass the enumv down ONLY if the RHS name begins with "::". |
3017 | | * |
3018 | | * Otherwise, the terminal rules for expressions includes "-" and "+", both of which are |
3019 | | * allowed in enum names. If we pass the enumv down to the next function, it will see |
3020 | | * "Access-Accept", and then only parse "Access". Which is wrong. |
3021 | | */ |
3022 | 280k | if ((lhs->type == XLAT_TMPL) && tmpl_is_attr(lhs->vpt) && |
3023 | 4.21k | fr_sbuff_is_str_literal(&our_in, "::")) { |
3024 | 1.14k | rhs_t_rules.enumv = tmpl_attr_tail_da(lhs->vpt); |
3025 | 1.14k | } |
3026 | | |
3027 | 280k | slen = tokenize_expression(head, &rhs, &our_in, p_rules, &rhs_t_rules, op, bracket_rules, input_rules, cond); |
3028 | 280k | } |
3029 | 288k | if (slen <= 0) { |
3030 | 8.59k | talloc_free(lhs); |
3031 | 8.59k | FR_SBUFF_ERROR_RETURN(&our_in); |
3032 | 8.59k | } |
3033 | | |
3034 | | /* |
3035 | | * The nested call to tokenize_expression() can return >=0 if there are spaces followed by a |
3036 | | * terminal character. |
3037 | | */ |
3038 | 279k | if (!rhs) goto done; |
3039 | | |
3040 | 279k | XLAT_VERIFY(rhs); |
3041 | | |
3042 | 279k | func = xlat_func_find(binary_ops[op].str, binary_ops[op].len); |
3043 | 279k | fr_assert(func != NULL); |
3044 | | |
3045 | 279k | if (multivalue_ops[op]) { |
3046 | 28.3k | if ((lhs->type == XLAT_FUNC) && (lhs->call.func->token == op)) { |
3047 | 5.81k | xlat_func_append_arg(lhs, rhs, cond); |
3048 | | |
3049 | 5.81k | lhs->call.args->flags.can_purify |= rhs->flags.can_purify | rhs->flags.pure; |
3050 | 5.81k | lhs->flags.can_purify = lhs->call.args->flags.can_purify; |
3051 | 5.81k | goto redo; |
3052 | 5.81k | } |
3053 | 22.5k | goto purify; |
3054 | 28.3k | } |
3055 | | |
3056 | | /* |
3057 | | * Complain on comparisons between invalid data types. |
3058 | | * |
3059 | | * @todo - allow |
3060 | | * |
3061 | | * &structural == {} |
3062 | | * &structural != {} |
3063 | | * |
3064 | | * as special cases, so we can check lists for emptiness. |
3065 | | */ |
3066 | 251k | if (fr_comparison_op[op]) { |
3067 | 69.9k | if (!valid_type(lhs)) goto fail_lhs; |
3068 | 69.9k | if (!valid_type(rhs)) { |
3069 | 15 | fr_sbuff_set(&our_in, &m_rhs); |
3070 | 15 | FR_SBUFF_ERROR_RETURN(&our_in); |
3071 | 15 | } |
3072 | | |
3073 | | /* |
3074 | | * Peephole optimization. If both LHS |
3075 | | * and RHS are static values, then just call the |
3076 | | * relevant condition code to get the result. |
3077 | | */ |
3078 | 69.9k | if (cond) { |
3079 | 60.5k | int rcode; |
3080 | | |
3081 | 83.0k | purify: |
3082 | 83.0k | rcode = xlat_purify_op(head, &node, lhs, op, rhs); |
3083 | 83.0k | if (rcode < 0) goto fail_lhs; |
3084 | | |
3085 | 81.5k | if (rcode) { |
3086 | 67.1k | lhs = node; |
3087 | 67.1k | goto redo; |
3088 | 67.1k | } |
3089 | 81.5k | } |
3090 | 69.9k | } |
3091 | | |
3092 | | /* |
3093 | | * Create the function node, with the LHS / RHS arguments. |
3094 | | */ |
3095 | 205k | MEM(node = xlat_exp_alloc(head, XLAT_FUNC, fr_tokens[op], strlen(fr_tokens[op]))); |
3096 | 205k | xlat_exp_set_func(node, func, t_rules->attr.dict_def); |
3097 | | |
3098 | 205k | xlat_func_append_arg(node, lhs, logical_ops[op] && cond); |
3099 | 205k | xlat_func_append_arg(node, rhs, logical_ops[op] && cond); |
3100 | | |
3101 | 205k | fr_assert(xlat_exp_head(node->call.args) != NULL); |
3102 | | |
3103 | | /* |
3104 | | * Logical operations can be purified if ANY of their arguments can be purified. |
3105 | | */ |
3106 | 205k | if (logical_ops[op]) { |
3107 | 5.43k | xlat_exp_foreach(node->call.args, arg) { |
3108 | 5.43k | node->call.args->flags.can_purify |= arg->flags.can_purify | arg->flags.pure; |
3109 | 5.43k | if (node->call.args->flags.can_purify) break; |
3110 | 5.43k | } |
3111 | 5.43k | node->flags.can_purify = node->call.args->flags.can_purify; |
3112 | | |
3113 | 199k | } else { |
3114 | 199k | node->flags.can_purify = (node->call.func->flags.pure && node->call.args->flags.pure) | node->call.args->flags.can_purify; |
3115 | 199k | } |
3116 | | |
3117 | 205k | lhs = node; |
3118 | 205k | XLAT_VERIFY(lhs); |
3119 | 205k | goto redo; |
3120 | 251k | } |
3121 | | |
3122 | | static const fr_sbuff_term_t bracket_terms = FR_SBUFF_TERMS( |
3123 | | L(""), |
3124 | | L(")"), |
3125 | | ); |
3126 | | |
3127 | | static const fr_sbuff_term_t operator_terms = FR_SBUFF_TERMS( |
3128 | | L("\t"), |
3129 | | L("\n"), |
3130 | | L("\r"), |
3131 | | L(" "), |
3132 | | L("!"), |
3133 | | L("%"), |
3134 | | L("&"), |
3135 | | L("*"), |
3136 | | L("+"), |
3137 | | L("-"), |
3138 | | L("/"), |
3139 | | L("<"), |
3140 | | L("="), |
3141 | | L(">"), |
3142 | | L("^"), |
3143 | | L("|"), |
3144 | | L("~"), |
3145 | | ); |
3146 | | |
3147 | | static fr_slen_t xlat_tokenize_expression_internal(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, |
3148 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, bool cond) |
3149 | 87.8k | { |
3150 | 87.8k | fr_slen_t slen; |
3151 | 87.8k | fr_sbuff_parse_rules_t *bracket_rules = NULL; |
3152 | 87.8k | fr_sbuff_parse_rules_t *terminal_rules = NULL; |
3153 | 87.8k | tmpl_rules_t my_rules = { }; |
3154 | 87.8k | xlat_exp_head_t *head; |
3155 | 87.8k | xlat_exp_t *node = NULL; |
3156 | | |
3157 | | /* |
3158 | | * Whatever the caller passes, ensure that we have a |
3159 | | * terminal rule which ends on operators, and a terminal |
3160 | | * rule which ends on ')'. |
3161 | | */ |
3162 | 87.8k | MEM(bracket_rules = talloc_zero(ctx, fr_sbuff_parse_rules_t)); |
3163 | 87.8k | MEM(terminal_rules = talloc_zero(ctx, fr_sbuff_parse_rules_t)); |
3164 | 87.8k | if (p_rules) { |
3165 | 78.4k | *bracket_rules = *p_rules; |
3166 | 78.4k | *terminal_rules = *p_rules; |
3167 | | |
3168 | 78.4k | if (p_rules->terminals) { |
3169 | 78.4k | MEM(terminal_rules->terminals = fr_sbuff_terminals_amerge(terminal_rules, |
3170 | 78.4k | p_rules->terminals, |
3171 | 78.4k | &operator_terms)); |
3172 | 78.4k | } else { |
3173 | 0 | terminal_rules->terminals = &operator_terms; |
3174 | 0 | } |
3175 | 78.4k | } else { |
3176 | 9.42k | terminal_rules->terminals = &operator_terms; |
3177 | 9.42k | } |
3178 | 87.8k | MEM(bracket_rules->terminals = fr_sbuff_terminals_amerge(bracket_rules, |
3179 | 87.8k | terminal_rules->terminals, |
3180 | 87.8k | &bracket_terms)); |
3181 | | |
3182 | 87.8k | MEM(head = xlat_exp_head_alloc(ctx)); |
3183 | 87.8k | if (!t_rules) t_rules = &my_rules; |
3184 | | |
3185 | 87.8k | slen = tokenize_expression(head, &node, in, terminal_rules, t_rules, T_INVALID, bracket_rules, p_rules, cond); |
3186 | 87.8k | talloc_free(bracket_rules); |
3187 | 87.8k | talloc_free(terminal_rules); |
3188 | | |
3189 | 87.8k | if (slen <= 0) { |
3190 | 16.8k | talloc_free(head); |
3191 | 16.8k | return slen; |
3192 | 16.8k | } |
3193 | | |
3194 | 70.9k | if (!node) { |
3195 | 0 | XLAT_HEAD_VERIFY(head); |
3196 | 0 | *out = head; |
3197 | |
|
3198 | 0 | return slen; |
3199 | 0 | } |
3200 | | |
3201 | | /* |
3202 | | * If the tmpl is not resolved, then it refers to an attribute which doesn't exist. That's an |
3203 | | * error. |
3204 | | */ |
3205 | 70.9k | if (node->type == XLAT_TMPL) { |
3206 | 4.57k | if (tmpl_is_data_unresolved(node->vpt)) { |
3207 | 0 | fr_strerror_const("Unknown attribute"); |
3208 | 0 | return -1; |
3209 | 0 | } |
3210 | | |
3211 | | /* |
3212 | | * Convert raw existence checks to existence functions. |
3213 | | */ |
3214 | 4.57k | if (tmpl_contains_attr(node->vpt)) { |
3215 | 3.11k | if (cond) MEM(node = xlat_exists_alloc(head, node)); |
3216 | 3.11k | } |
3217 | 4.57k | } |
3218 | | |
3219 | 70.9k | xlat_exp_insert_tail(head, node); |
3220 | | |
3221 | 70.9k | XLAT_HEAD_VERIFY(head); |
3222 | 70.9k | *out = head; |
3223 | | |
3224 | 70.9k | return slen; |
3225 | 70.9k | } |
3226 | | |
3227 | | fr_slen_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, |
3228 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) |
3229 | 76.4k | { |
3230 | 76.4k | fr_slen_t slen; |
3231 | | |
3232 | 76.4k | slen = xlat_tokenize_expression_internal(ctx, out, in, p_rules, t_rules, false); |
3233 | 76.4k | if (slen < 0) return slen; |
3234 | | |
3235 | | #ifdef STATIC_ANALYZER |
3236 | | /* |
3237 | | * Coverity doesn't realise that out will be set by this point |
3238 | | * by a successful call to xlat_tokenize_expression_internal. |
3239 | | */ |
3240 | | if (!out) return -1; |
3241 | | #endif |
3242 | 66.4k | if (!*out) { |
3243 | 0 | fr_strerror_const("Empty expressions are invalid"); |
3244 | 0 | return -1; |
3245 | 0 | } |
3246 | | |
3247 | 66.4k | if (xlat_finalize(*out, t_rules->xlat.runtime_el) < 0) { |
3248 | 0 | TALLOC_FREE(*out); |
3249 | 0 | return -1; |
3250 | 0 | } |
3251 | | |
3252 | 66.4k | return slen; |
3253 | 66.4k | } |
3254 | | |
3255 | | fr_slen_t xlat_tokenize_condition(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, |
3256 | | fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) |
3257 | 11.3k | { |
3258 | 11.3k | fr_slen_t slen; |
3259 | | |
3260 | 11.3k | slen = xlat_tokenize_expression_internal(ctx, out, in, p_rules, t_rules, true); |
3261 | 11.3k | if (slen < 0) return slen; |
3262 | | |
3263 | | #ifdef STATIC_ANALYZER |
3264 | | if (!out) return -1; |
3265 | | #endif |
3266 | 4.52k | if (!*out) { |
3267 | 0 | fr_strerror_const("Empty conditions are invalid"); |
3268 | 0 | return -1; |
3269 | 0 | } |
3270 | | |
3271 | 4.52k | if (xlat_finalize(*out, t_rules->xlat.runtime_el) < 0) { |
3272 | 0 | TALLOC_FREE(*out); |
3273 | 0 | return -1; |
3274 | 0 | } |
3275 | | |
3276 | 4.52k | return slen; |
3277 | 4.52k | } |
3278 | | |
3279 | | /** Allow callers to see if an xlat is truthy |
3280 | | * |
3281 | | * So the caller can cache it, and needs to check fewer things at run |
3282 | | * time. |
3283 | | * |
3284 | | * @param[in] head of the xlat to check |
3285 | | * @param[out] out truthiness of the box |
3286 | | * @return |
3287 | | * - false - xlat is not truthy, *out is unchanged. |
3288 | | * - true - xlat is truthy, *out is the result of fr_value_box_is_truthy() |
3289 | | */ |
3290 | | bool xlat_is_truthy(xlat_exp_head_t const *head, bool *out) |
3291 | 0 | { |
3292 | 0 | xlat_exp_t const *node; |
3293 | 0 | fr_value_box_t const *box; |
3294 | | |
3295 | | /* |
3296 | | * Only pure / constant things can be truthy. |
3297 | | */ |
3298 | 0 | if (!head->flags.pure) goto return_false; |
3299 | | |
3300 | 0 | node = xlat_exp_head(head); |
3301 | 0 | if (!node) { |
3302 | 0 | *out = false; |
3303 | 0 | return true; |
3304 | 0 | } |
3305 | | |
3306 | 0 | if (xlat_exp_next(head, node)) goto return_false; |
3307 | | |
3308 | 0 | if (node->type == XLAT_BOX) { |
3309 | 0 | box = &node->data; |
3310 | |
|
3311 | 0 | } else if ((node->type == XLAT_TMPL) && tmpl_is_data(node->vpt)) { |
3312 | 0 | box = tmpl_value(node->vpt); |
3313 | |
|
3314 | 0 | } else { |
3315 | 0 | return_false: |
3316 | 0 | *out = false; |
3317 | 0 | return false; |
3318 | 0 | } |
3319 | | |
3320 | 0 | *out = fr_value_box_is_truthy(box); |
3321 | 0 | return true; |
3322 | 0 | } |