The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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 
27 RCSID("$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 
34 static 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 
229 static 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 
239  *out = fr_value_box_is_truthy(box);
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  */
342 static 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 
425 int 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 }
static fr_dict_t * dict
Definition: fuzzer.c:46
#define RCSID(id)
Definition: build.h:444
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
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:1893
fr_dict_t const * fr_dict_internal(void)
Definition: dict_util.c:4204
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:1726
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
Definition: merged_model.c:80
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
Definition: merged_model.c:81
@ FR_TYPE_BOOL
A truth value.
Definition: merged_model.c:95
#define request_alloc_internal(_ctx, _args)
Allocate a new internal request.
Definition: request.h:304
Optional arguments for initialising requests.
Definition: request.h:253
static char const * name
#define tmpl_is_xlat(vpt)
Definition: tmpl.h:215
#define tmpl_value(_tmpl)
Definition: tmpl.h:932
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition: tmpl.h:796
#define tmpl_is_attr(vpt)
Definition: tmpl.h:213
#define tmpl_xlat(_tmpl)
Definition: tmpl.h:925
#define tmpl_rules_cast(_tmpl)
Definition: tmpl.h:937
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
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
goto success
Definition: tmpl_eval.c:1450
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:294
static fr_slen_t head
Definition: xlat.h:408
bool can_purify
if the xlat has a pure function with pure arguments.
Definition: xlat.h:114
bool pure
has no external side effects, true for BOX, LITERAL, and some functions
Definition: xlat.h:113
bool needs_resolving
Needs pass2 resolution.
Definition: xlat.h:112
int xlat_instance_unregister_func(xlat_exp_t *node)
Remove a node from the list of xlat instance data.
Definition: xlat_inst.c:545
Flags that control resolution and evaluation.
Definition: xlat.h:111
#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:3689
bool fr_value_box_is_truthy(fr_value_box_t const *in)
Check truthiness of values.
Definition: value.c:6274
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:984
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:51
xlat_flags_t flags
Flags that control resolution and evaluation.
Definition: xlat_priv.h:153
xlat_flags_t flags
Flags that control resolution and evaluation.
Definition: xlat_priv.h:186
fr_token_t quote
Type of quoting around XLAT_GROUP types.
Definition: xlat_priv.h:151
@ XLAT_BOX
fr_value_box_t
Definition: xlat_priv.h:103
@ XLAT_TMPL
xlat attribute
Definition: xlat_priv.h:109
@ XLAT_FUNC
xlat module
Definition: xlat_priv.h:105
@ XLAT_GROUP
encapsulated string of xlats
Definition: xlat_priv.h:113
static void xlat_flags_merge(xlat_flags_t *parent, xlat_flags_t const *child)
Merge flags from child to parent.
Definition: xlat_priv.h:223
#define xlat_exp_set_type(_node, _type)
Definition: xlat_priv.h:269
xlat_type_t _CONST type
type of this expansion.
Definition: xlat_priv.h:154
#define xlat_exp_alloc(_ctx, _type, _in, _inlen)
Definition: xlat_priv.h:275
#define xlat_exp_foreach(_list_head, _iter)
Iterate over the contents of a list, only one level.
Definition: xlat_priv.h:216
static int xlat_exp_insert_tail(xlat_exp_head_t *head, xlat_exp_t *node)
Definition: xlat_priv.h:231
An xlat expansion node.
Definition: xlat_priv.h:147
static int binary_peephole_optimize(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t *lhs, fr_token_t op, xlat_exp_t *rhs)
Definition: xlat_purify.c:342
static xlat_exp_t * peephole_optimize_land(xlat_exp_t *lhs, xlat_exp_t *rhs)
Definition: xlat_purify.c:301
static bool is_truthy(xlat_exp_t *node, bool *out)
Definition: xlat_purify.c:229
int xlat_purify_op(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t *lhs, fr_token_t op, xlat_exp_t *rhs)
Definition: xlat_purify.c:425
int xlat_purify(xlat_exp_head_t *head, unlang_interpret_t *intp)
Purify an xlat.
Definition: xlat_purify.c:194
int xlat_purify_list(xlat_exp_head_t *head, request_t *request)
Definition: xlat_purify.c:60
static fr_value_box_t * xlat_value_box(xlat_exp_t *node)
Definition: xlat_purify.c:212
static xlat_exp_t * peephole_optimize_lor(xlat_exp_t *lhs, xlat_exp_t *rhs)
Definition: xlat_purify.c:247
static void xlat_value_list_to_xlat(xlat_exp_head_t *head, fr_value_box_list_t *list)
Definition: xlat_purify.c:34