The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
xlat_purify.c
Go to the documentation of this file.
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: 486f2eb58a0ccd2e53ed6f2b3520dd2e82fc841a $
19 *
20 * @file xlat_purify.c
21 * @brief Purification functions for xlats
22 *
23 * @copyright 2022 The FreeRADIUS server project
24 * @copyright 2022 Network RADIUS SAS (legal@networkradius.com)
25 */
26
27RCSID("$Id: 486f2eb58a0ccd2e53ed6f2b3520dd2e82fc841a $")
28
29#include <freeradius-devel/server/base.h>
30#include <freeradius-devel/unlang/xlat_priv.h>
31#include <freeradius-devel/util/calc.h>
32#include <freeradius-devel/util/dict.h>
33
34static void xlat_value_list_to_xlat(xlat_exp_head_t *head, fr_value_box_list_t *list)
35{
36 fr_value_box_t *box;
37 xlat_exp_t *node;
38
39 while ((box = fr_value_box_list_pop_head(list)) != NULL) {
40 MEM(node = xlat_exp_alloc(head, XLAT_BOX, NULL, 0));
41 fr_value_box_copy(node, &node->data, box);
42
43 if (node->data.type == FR_TYPE_STRING) {
45 xlat_exp_set_name_buffer_shallow(node, node->data.vb_strvalue);
46 } else {
47 char *name;
48
49 node->quote = T_BARE_WORD;
50 MEM(fr_value_box_aprint(node, &name, box, NULL) >= 0);
52 }
53 talloc_free(box);
54
56 }
57}
58
59
61{
62 int rcode;
63 bool success;
64 fr_value_box_list_t list;
65 xlat_flags_t our_flags;
66
67 if (!head->flags.can_purify) return 0;
68
69 /*
70 * We can't purify things which need resolving,
71 */
72 if (head->flags.needs_resolving) return -1;
73
74 our_flags = head->flags;
75 our_flags.pure = true; /* we flip this if the children are not pure */
76
77 xlat_exp_foreach(head, node) {
78 if (!node->flags.can_purify) continue;
79
80 switch (node->type) {
81 case XLAT_TMPL:
82 if (tmpl_is_xlat(node->vpt)) {
83 xlat_exp_head_t *child = tmpl_xlat(node->vpt);
84
85 rcode = xlat_purify_list(child, request);
86 if (rcode < 0) return rcode;
87
88 node->flags = child->flags;
89 break;
90 }
92
93 default:
94 fr_strerror_printf("Internal error - cannot purify xlat");
95 return -1;
96
97 case XLAT_GROUP:
98 rcode = xlat_purify_list(node->group, request);
99 if (rcode < 0) return rcode;
100
101 node->flags = node->group->flags;
102 break;
103
104 case XLAT_FUNC:
105 /*
106 * If the node is not pure, then maybe there's a callback to purify it, OR maybe
107 * we can purify the function arguments.
108 */
109 if (!node->flags.pure) {
110 if (node->call.func->purify) {
111 fr_dict_t const *dict = request->dict;
112
113 /*
114 * Swap in the node specific dictionary.
115 *
116 * The previous code stored the dictionary in the xlat_exp_head_t,
117 * and whilst this wasn't wrong, it was duplicative.
118 *
119 * This allows future code to create inline definitions of local
120 * attributes, and have them work correctly, as more deeply nested
121 * expressions would swap in the correct dictionary.
122 */
123 request->dict = node->call.dict;
124 if (node->call.func->purify(node, node->call.inst->data, request) < 0) return -1;
125 request->dict = dict;
126 } else {
127 if (xlat_purify_list(node->call.args, request) < 0) return -1;
128 }
129
130 /*
131 * It may have been purified into an XLAT_BOX. But if not, ensure that
132 * the flags are all correct.
133 */
134 if (node->type == XLAT_FUNC) {
135 node->flags = node->call.func->flags;
136 xlat_exp_foreach(node->call.args, arg) {
137 xlat_flags_merge(&node->flags, &arg->flags);
138 }
139 }
140 break;
141 }
142
143 /*
144 * The node is entirely pure, we don't worry about any callbacks, we just
145 * evaluate the entire thing to purify it.
146 */
147 fr_assert(node->flags.pure);
148 fr_value_box_list_init(&list);
149 success = false;
150 if (unlang_xlat_push_node(head, &success, &list, request, node) < 0) {
151 return -1;
152 }
153
154 /*
155 * Hope to god it doesn't yield. :)
156 */
157
158 (void) unlang_interpret_synchronous(NULL, request);
159 if (!success) return -1;
160
161 /*
162 * The function call becomes a GROUP of boxes
163 */
165 xlat_exp_set_type(node, XLAT_GROUP); /* Frees the argument list */
166
167 xlat_value_list_to_xlat(node->group, &list);
168 node->flags = node->group->flags;
169 break;
170 }
171
172 node->flags.can_purify = false;
173 xlat_flags_merge(&our_flags, &node->flags);
174 }
175
176 /*
177 * Let's not call xlat_purify() repeatedly, so we clear the flag.
178 *
179 * @todo - if all of the children of "head" are "pure", then at the end of the purification
180 * process, there should only be one child, of type XLAT_BOX.
181 */
182 our_flags.can_purify = false;
183 head->flags = our_flags;
184
185 return 0;
186}
187
188/** Purify an xlat
189 *
190 * @param head the xlat to be purified
191 * @param intp the interpreter to use.
192 *
193 */
195{
196 int rcode;
197 request_t *request;
198
199 if (!head->flags.can_purify) return 0;
200
201 request = request_alloc_internal(NULL, (&(request_init_args_t){ .namespace = fr_dict_internal() }));
202 if (!request) return -1;
203
204 if (intp) unlang_interpret_set(request, intp);
205
206 rcode = xlat_purify_list(head, request);
207 talloc_free(request);
208
209 return rcode;
210}
211
213{
214#ifdef STATIC_ANALYZER
215 if (!node) return NULL;
216#endif
217
218 if (node->type == XLAT_BOX) {
219 return &node->data;
220
221 } else if ((node->type == XLAT_TMPL) && tmpl_is_data(node->vpt)) {
222 return tmpl_value(node->vpt);
223 }
224
225 return NULL;
226}
227
228
229static bool is_truthy(xlat_exp_t *node, bool *out)
230{
231 fr_value_box_t const *box;
232
233 box = xlat_value_box(node);
234 if (!box) {
235 *out = false;
236 return false;
237 }
238
240 return true;
241}
242
243/*
244 * Do some optimizations.
245 *
246 */
248{
249 bool value;
250
251 /*
252 * LHS isn't truthy, we can't do anything. If the LHS
253 * passes, we return the value of the LHS.
254 *
255 * FOO || ... --> FOO || ...
256 */
257 if (!is_truthy(lhs, &value)) {
258 /*
259 * FOO || 0 --> FOO much of the time
260 * FOO || 1 --> FOO much of the time
261 */
262 if (!is_truthy(rhs, &value)) return NULL;
263
264 /*
265 * BOOL || 1 --> 1
266 *
267 * Because if the LHS is 1, then we return the LHS (1)
268 * On the other hand, it the LHS is 0, then we return
269 * the RHS, which is also 1.
270 *
271 * But we can't do
272 *
273 * <type> || 1 --> 1
274 */
275 if (value && (lhs->type == XLAT_FUNC) && (lhs->call.func->return_type == FR_TYPE_BOOL)) {
276 talloc_free(lhs);
277 return rhs;
278 }
279
280 return NULL;
281 }
282
283 /*
284 * 1 || FOO --> 1
285 * 0 || FOO --> FOO
286 */
287 if (value) {
288 talloc_free(rhs);
289 return lhs;
290 }
291
292 talloc_free(lhs);
293 return rhs;
294}
295
296
297/*
298 * Do some optimizations.
299 *
300 */
302{
303 bool value;
304
305 /*
306 * LHS isn't truthy
307 *
308 * FOO && ... --> FOO && ...
309 */
310 if (!is_truthy(lhs, &value)) {
311 /*
312 * FOO && 0 --> 0
313 * FOO && 1 --> FOO
314 */
315 if (!is_truthy(rhs, &value)) return NULL;
316
317 if (!value) {
318 talloc_free(lhs);
319 return rhs;
320 }
321
322 talloc_free(rhs);
323 return lhs;
324 }
325
326 /*
327 * 0 && FOO --> 0
328 * 1 && FOO --> FOO
329 */
330 if (!value) {
331 talloc_free(rhs);
332 return lhs;
333 }
334
335 talloc_free(lhs);
336 return rhs;
337}
338
339/*
340 * Do peephole optimizations.
341 */
342static int binary_peephole_optimize(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t *lhs, fr_token_t op, xlat_exp_t *rhs)
343{
344 fr_value_box_t *lhs_box, *rhs_box;
345 fr_value_box_t box;
346 xlat_exp_t *node;
347 char *name;
348
349#if 0
350 /*
351 * @todo - more peephole optimizations here. We can't enable this code as yet, because of
352 * upcasting rules (e.g. calc.c) where comparisons between IP prefixes and IP addresses (or
353 * v4/v6) are upcast, and then the values compared.
354 *
355 * We should probably expose some of the upcast functionality in calc.c so that this function can
356 * use it.
357 */
358
359 /*
360 * Attribute op value.
361 */
362 if ((lhs->type == XLAT_TMPL) && tmpl_is_attr(lhs->vpt) &&
363 (rhs->type == XLAT_TMPL) && (tmpl_is_data_unresolved(rhs->vpt) || tmpl_is_data(rhs->vpt))) {
364 fr_type_t dst_type;
365 fr_dict_attr_t const *da;
366
367 resolve:
368 dst_type = tmpl_rules_cast(rhs->vpt);
369 da = tmpl_attr_tail_da(lhs->vpt);
370
371 /*
372 * Cast to the final type. If there are two different casts, we ignore the one for the
373 * data.
374 */
375 if (fr_type_is_null(dst_type)) {
376 dst_type = tmpl_rules_cast(lhs->vpt);
377 if (fr_type_is_null(dst_type)) dst_type = da->type;
378 }
379
380 if (tmpl_cast_in_place(rhs->vpt, dst_type, da) < 0) return -1;
381
382 rhs->flags.needs_resolving = false;
383 return 0;
384 }
385
386 /*
387 * value op attribute
388 *
389 * We just swap LHS and RHS without caring about the operator, because we don't use the
390 * operator, and the caller has no idea that we swapped the pointers..
391 */
392 if ((rhs->type == XLAT_TMPL) && tmpl_is_attr(rhs->vpt) &&
393 (lhs->type == XLAT_TMPL) && (tmpl_is_data_unresolved(lhs->vpt) || tmpl_is_data(lhs->vpt))) {
394 xlat_exp_t *tmp = lhs;
395 lhs = rhs;
396 rhs = tmp;
397 goto resolve;
398 }
399#endif
400
401 /*
402 * The tmpl_tokenize code takes care of resolving the data if there's a cast.
403 */
404 lhs_box = xlat_value_box(lhs);
405 if (!lhs_box) return 0;
406
407 rhs_box = xlat_value_box(rhs);
408 if (!rhs_box) return 0;
409
410 if (fr_value_calc_binary_op(lhs, &box, FR_TYPE_NULL, lhs_box, op, rhs_box) < 0) return -1;
411
412 MEM(node = xlat_exp_alloc(ctx, XLAT_BOX, NULL, 0));
413
414 if (box.type == FR_TYPE_BOOL) box.enumv = attr_expr_bool_enum;
415
416 MEM(fr_value_box_aprint(node, &name, &box, NULL) >= 0);
418 fr_value_box_copy(node, &node->data, &box);
419
420 *out = node;
421
422 return 1;
423}
424
425int xlat_purify_op(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t *lhs, fr_token_t op, xlat_exp_t *rhs)
426{
427 if (op == T_LOR) {
428 xlat_exp_t *node;
429
430 node = peephole_optimize_lor(lhs, rhs);
431 if (!node) return 0;
432
433 *out = node;
434 return 1;
435 }
436
437 if (op == T_LAND) {
438 xlat_exp_t *node;
439
440 node = peephole_optimize_land(lhs, rhs);
441 if (!node) return 0;
442
443 *out = node;
444 return 1;
445 }
446
447 return binary_peephole_optimize(ctx, out, lhs, op, rhs);
448}
#define RCSID(id)
Definition build.h:483
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:322
int fr_value_calc_binary_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
Calculate DST = A OP B.
Definition calc.c:1894
#define MEM(x)
Definition debug.h:36
fr_dict_t const * fr_dict_internal(void)
Definition dict_util.c:4610
Test enumeration values.
Definition dict_test.h:92
void unlang_interpret_set(request_t *request, unlang_interpret_t *intp)
Set a specific interpreter for a request.
Definition interpret.c:1745
rlm_rcode_t unlang_interpret_synchronous(fr_event_list_t *el, request_t *request)
Execute an unlang section synchronously.
talloc_free(reap)
fr_type_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
@ FR_TYPE_BOOL
A truth value.
#define fr_assert(_expr)
Definition rad_assert.h:38
#define request_alloc_internal(_ctx, _args)
Allocate a new internal request.
Definition request.h:305
Optional arguments for initialising requests.
Definition request.h:254
static char const * name
#define tmpl_is_xlat(vpt)
Definition tmpl.h:215
#define tmpl_value(_tmpl)
Definition tmpl.h:948
#define tmpl_is_attr(vpt)
Definition tmpl.h:213
#define tmpl_xlat(_tmpl)
Definition tmpl.h:941
#define tmpl_rules_cast(_tmpl)
Definition tmpl.h:953
int tmpl_cast_in_place(tmpl_t *vpt, fr_type_t type, fr_dict_attr_t const *enumv))
Convert tmpl_t of type TMPL_TYPE_DATA_UNRESOLVED or TMPL_TYPE_DATA to TMPL_TYPE_DATA of type specifie...
#define tmpl_is_data(vpt)
Definition tmpl.h:211
#define tmpl_is_data_unresolved(vpt)
Definition tmpl.h:222
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:812
goto success
Definition tmpl_eval.c:1455
enum fr_token fr_token_t
@ T_BARE_WORD
Definition token.h:120
@ T_LAND
Definition token.h:91
@ T_LOR
Definition token.h:92
@ T_DOUBLE_QUOTED_STRING
Definition token.h:121
int unlang_xlat_push_node(TALLOC_CTX *ctx, bool *p_success, fr_value_box_list_t *out, request_t *request, xlat_exp_t *node)
Push a pre-compiled xlat onto the stack for evaluation.
Definition xlat.c:306
static fr_slen_t head
Definition xlat.h:422
bool can_purify
if the xlat has a pure function with pure arguments.
Definition xlat.h:116
bool pure
has no external side effects, true for BOX, LITERAL, and some functions
Definition xlat.h:114
bool needs_resolving
Needs pass2 resolution.
Definition xlat.h:113
int xlat_instance_unregister_func(xlat_exp_t *node)
Remove a node from the list of xlat instance data.
Definition xlat_inst.c:550
Flags that control resolution and evaluation.
Definition xlat.h:112
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_type_is_null(_x)
Definition types.h:326
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:3740
bool fr_value_box_is_truthy(fr_value_box_t const *in)
Check truthiness of values.
Definition value.c:6326
static fr_slen_t fr_value_box_aprint(TALLOC_CTX *ctx, char **out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules) 1(fr_value_box_print
static size_t char ** out
Definition value.h:997
void xlat_exp_set_name_buffer_shallow(xlat_exp_t *node, char const *fmt)
Set the format string for an xlat node from a pre-existing buffer.
Definition xlat_alloc.c:213
fr_dict_attr_t const * attr_expr_bool_enum
Definition xlat_eval.c:49
xlat_flags_t flags
Flags that control resolution and evaluation.
Definition xlat_priv.h:157
xlat_flags_t flags
Flags that control resolution and evaluation.
Definition xlat_priv.h:190
fr_token_t quote
Type of quoting around XLAT_GROUP types.
Definition xlat_priv.h:155
@ XLAT_BOX
fr_value_box_t
Definition xlat_priv.h:107
@ XLAT_TMPL
xlat attribute
Definition xlat_priv.h:113
@ XLAT_FUNC
xlat module
Definition xlat_priv.h:109
@ XLAT_GROUP
encapsulated string of xlats
Definition xlat_priv.h:117
static void xlat_flags_merge(xlat_flags_t *parent, xlat_flags_t const *child)
Merge flags from child to parent.
Definition xlat_priv.h:227
#define xlat_exp_set_type(_node, _type)
Definition xlat_priv.h:274
xlat_type_t _CONST type
type of this expansion.
Definition xlat_priv.h:158
#define xlat_exp_alloc(_ctx, _type, _in, _inlen)
Definition xlat_priv.h:280
#define xlat_exp_foreach(_list_head, _iter)
Iterate over the contents of a list, only one level.
Definition xlat_priv.h:220
static int xlat_exp_insert_tail(xlat_exp_head_t *head, xlat_exp_t *node)
Definition xlat_priv.h:236
An xlat expansion node.
Definition xlat_priv.h:151
static xlat_exp_t * peephole_optimize_lor(xlat_exp_t *lhs, xlat_exp_t *rhs)
static int binary_peephole_optimize(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t *lhs, fr_token_t op, xlat_exp_t *rhs)
static fr_value_box_t * xlat_value_box(xlat_exp_t *node)
static bool is_truthy(xlat_exp_t *node, bool *out)
int xlat_purify_op(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t *lhs, fr_token_t op, xlat_exp_t *rhs)
int xlat_purify(xlat_exp_head_t *head, unlang_interpret_t *intp)
Purify an xlat.
int xlat_purify_list(xlat_exp_head_t *head, request_t *request)
Definition xlat_purify.c:60
static xlat_exp_t * peephole_optimize_land(xlat_exp_t *lhs, xlat_exp_t *rhs)
static void xlat_value_list_to_xlat(xlat_exp_head_t *head, fr_value_box_list_t *list)
Definition xlat_purify.c:34