The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
edit.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: dccefced32820311bb0f271ff7f1812e2b8d01dc $
19  *
20  * @brief fr_pair_t editing
21  *
22  * @ingroup AVP
23  *
24  * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
25  */
26 RCSID("$Id: dccefced32820311bb0f271ff7f1812e2b8d01dc $")
27 
28 #include <freeradius-devel/server/base.h>
29 #include <freeradius-devel/server/tmpl_dcursor.h>
30 #include <freeradius-devel/util/debug.h>
31 #include <freeradius-devel/util/edit.h>
32 #include <freeradius-devel/util/calc.h>
33 #include <freeradius-devel/unlang/tmpl.h>
34 #include <freeradius-devel/unlang/edit.h>
35 #include <freeradius-devel/unlang/transaction.h>
36 #include <freeradius-devel/unlang/unlang_priv.h>
37 #include "edit_priv.h"
38 
39 #undef XDEBUG
40 #if 1
41 #define XDEBUG(...)
42 #else
43 #define XDEBUG DEBUG2
44 #endif
45 
46 #define RDEBUG_ASSIGN(_name, _op, _box) do { \
47  RDEBUG2(((_box)->type == FR_TYPE_STRING) ? "%s %s \"%pV\"" : "%s %s %pV", _name, fr_tokens[_op], _box); \
48 } while (0)
49 
50 typedef struct {
51  fr_value_box_list_t result; //!< result of expansion
52  tmpl_t const *vpt; //!< expanded tmpl
53  tmpl_t *to_free; //!< tmpl to free.
54  bool create; //!< whether we need to create the VP
55  fr_pair_t *vp; //!< VP referenced by tmpl.
56  fr_pair_t *vp_parent; //!< parent of the current VP
57  fr_pair_list_t pair_list; //!< for structural attributes
59 
60 typedef struct edit_map_s edit_map_t;
61 
63 
65 
66 struct edit_map_s {
67  fr_edit_list_t *el; //!< edit list
68 
69  TALLOC_CTX *ctx;
72 
73  map_list_t const *map_list;
74  map_t const *map; //!< the map to evaluate
75 
77 
78  edit_result_t lhs; //!< LHS child entries
79  edit_result_t rhs; //!< RHS child entries
80 
81  unlang_edit_expand_t func; //!< for process state
82  unlang_edit_expand_t check_lhs; //!< for special cases
83  unlang_edit_expand_t expanded_lhs; //!< for special cases
84 };
85 
86 /** State of an edit block
87  *
88  */
90  fr_edit_list_t *el; //!< edit list
91  bool *success; //!< whether or not the edit succeeded
92  bool ours;
93 
95 
96  edit_map_t *current; //!< what we're currently doing.
98 };
99 
100 #define MAP_INFO cf_filename(map->ci), cf_lineno(map->ci)
101 
102 static fr_pair_t *edit_list_pair_build(fr_pair_t *parent, fr_dcursor_t *cursor, fr_dict_attr_t const *da, void *uctx);
103 
104 /*
105  * Convert a value-box list to a LHS attribute #tmpl_t
106  */
107 static int tmpl_attr_from_result(TALLOC_CTX *ctx, map_t const *map, edit_result_t *out, request_t *request)
108 {
109  ssize_t slen;
110  fr_value_box_t *box = fr_value_box_list_head(&out->result);
111 
112  if (!box) {
113  RWDEBUG("%s %s ... - Assignment failed - No value on right-hand side", map->lhs->name, fr_tokens[map->op]);
114  return -1;
115  }
116 
117  /*
118  * Mash all of the results together.
119  */
120  if (fr_value_box_list_concat_in_place(box, box, &out->result, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
121  RWDEBUG("Failed converting result to string");
122  return -1;
123  }
124 
125  /*
126  * Parse the LHS as an attribute reference. It can't be
127  * anything else.
128  */
129  slen = tmpl_afrom_attr_str(ctx, NULL, &out->to_free, box->vb_strvalue,
130  &(tmpl_rules_t){
131  .attr = {
132  .dict_def = request->dict,
133  .list_def = request_attr_request,
134  .prefix = TMPL_ATTR_REF_PREFIX_NO
135  }
136  });
137  if (slen <= 0) {
138  RPEDEBUG("Expansion result \"%s\" is not an attribute reference", box->vb_strvalue);
139  return -1;
140  }
141 
142  out->vpt = out->to_free;
143  fr_value_box_list_talloc_free(&out->result);
144 
145  return 0;
146 }
147 
148 
149 /*
150  * Expand a tmpl.
151  */
152 static int tmpl_to_values(TALLOC_CTX *ctx, edit_result_t *out, request_t *request, tmpl_t const *vpt)
153 {
154  fr_assert(out->vpt == NULL);
155  fr_assert(out->to_free == NULL);
156 
157  switch (vpt->type) {
158  case TMPL_TYPE_DATA:
159  return 0;
160 
161  case TMPL_TYPE_ATTR:
162  out->vpt = vpt;
163  return 0;
164 
165  case TMPL_TYPE_EXEC:
166  if (unlang_tmpl_push(ctx, &out->result, request, vpt, NULL) < 0) return -1;
167  return 1;
168 
169  case TMPL_TYPE_XLAT:
170  if (unlang_xlat_push(ctx, NULL, &out->result, request, tmpl_xlat(vpt), false) < 0) return -1;
171  return 1;
172 
173  default:
174  /*
175  * The other tmpl types MUST have already been
176  * converted to the "realized" types.
177  */
178  tmpl_debug(vpt);
179  fr_assert(0);
180  break;
181  }
182 
183  return -1;
184 }
185 
186 static void edit_debug_attr_list(request_t *request, fr_pair_list_t const *list, map_t const *map);
187 
188 static void edit_debug_attr_vp(request_t *request, fr_pair_t *vp, map_t const *map)
189 {
190  fr_assert(vp != NULL);
191 
192  if (map) {
193  switch (vp->vp_type) {
194  case FR_TYPE_STRUCTURAL:
195  RDEBUG2("%s = {", map->lhs->name);
196  RINDENT();
197  edit_debug_attr_list(request, &vp->vp_group, map_list_head(&map->child));
198  REXDENT();
199  RDEBUG2("}");
200  break;
201 
202  default:
203  RDEBUG_ASSIGN(map->lhs->name, vp->op, &vp->data);
204  break;
205  }
206  } else {
207  switch (vp->vp_type) {
208  case FR_TYPE_STRUCTURAL:
209  RDEBUG2("%s = {", vp->da->name);
210  RINDENT();
211  edit_debug_attr_list(request, &vp->vp_group, NULL);
212  REXDENT();
213  RDEBUG2("}");
214  break;
215 
216  default:
217  RDEBUG_ASSIGN(vp->da->name, vp->op, &vp->data);
218  break;
219  }
220  }
221 }
222 
223 static void edit_debug_attr_list(request_t *request, fr_pair_list_t const *list, map_t const *map)
224 {
225  fr_pair_t *vp;
226  map_t const *child = NULL;
227 
228  if (map) child = map_list_head(&map->child);
229 
230  for (vp = fr_pair_list_next(list, NULL);
231  vp != NULL;
232  vp = fr_pair_list_next(list, vp)) {
233  edit_debug_attr_vp(request, vp, child);
234  if (map) child = map_list_next(&map->child, child);
235  }
236 }
237 
238 static int edit_create_lhs_vp(request_t *request, TALLOC_CTX *ctx, edit_map_t *current)
239 {
240  int err;
241  fr_pair_t *vp;
242  tmpl_dcursor_ctx_t lhs_cc;
243  fr_dcursor_t lhs_cursor;
244 
245  fr_assert(current->lhs.create);
246 
247  /*
248  * Now that we have the RHS values, go create the LHS vp. We delay creating it until
249  * now, because the RHS might just be nothing. In which case we don't want to create the
250  * LHS, and then discover that we need to delete it.
251  */
253  vp = tmpl_dcursor_build_init(&err, ctx, &lhs_cc, &lhs_cursor, request, current->lhs.vpt, edit_list_pair_build, current);
254  tmpl_dcursor_clear(&lhs_cc);
255  if (!vp) {
256  RWDEBUG("Failed creating attribute %s", current->lhs.vpt->name);
257  return -1;
258  }
259 
260  current->lhs.vp = vp;
261 
262  return 0;
263 }
264 
265 /* Apply the edits to a structural attribute..
266  *
267  * Figure out what edits to do, and then do them.
268  */
270 {
271  fr_pair_t *vp;
272  fr_pair_list_t *children;
273  int rcode;
274  map_t const *map = current->map;
276  fr_dcursor_t cursor;
277 
278  XDEBUG("apply_edits_to_list %s", map->lhs->name);
279 
280  /*
281  * RHS is a sublist, go apply that.
282  */
283  if (!map->rhs) {
284  children = &current->rhs.pair_list;
285  goto apply_list;
286  }
287 
288  /*
289  * For RHS of data, it should be a string which contains the pairs to use.
290  */
291  if (!current->rhs.vpt) {
292  fr_value_box_t *box;
293  fr_dict_attr_t const *da;
294  fr_pair_parse_t root, relative;
295 
296  if (tmpl_is_data(map->rhs)) {
297  box = tmpl_value(map->rhs);
298 
299  if (box->type != FR_TYPE_STRING) {
300  REDEBUG("Invalid data type for assignment to list");
301  return -1;
302  }
303 
304  } else {
305  box = fr_value_box_list_head(&current->rhs.result);
306 
307  /*
308  * Can't concatenate empty results.
309  */
310  if (!box) {
311  RWDEBUG("%s %s ... - Assignment failed to having no value on right-hand side", map->lhs->name, fr_tokens[map->op]);
312  return -1;
313  }
314 
315  /*
316  * Mash all of the results together.
317  */
318  if (fr_value_box_list_concat_in_place(box, box, &current->rhs.result, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
319  RWDEBUG("Failed converting result to string");
320  return -1;
321  }
322  }
323 
324  children = &current->rhs.pair_list;
325 
326  /*
327  * For exec, etc., parse the pair list from a string, in the context of the
328  * parent VP. Because we're going to be moving them to the parent VP at some
329  * point. The ones which aren't moved will get deleted in this function.
330  */
331  da = tmpl_attr_tail_da(current->lhs.vpt);
332  if (fr_type_is_group(da->type)) da = fr_dict_root(request->dict);
333 
334  root = (fr_pair_parse_t) {
335  .ctx = current->ctx,
336  .da = da,
337  .list = children,
338  .allow_compare = true,
339  .tainted = box->tainted,
340  };
341  relative = (fr_pair_parse_t) { };
342 
343  if (fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(box->vb_strvalue, box->vb_length)) < 0) {
344  RPEDEBUG("Failed parsing string '%pV' as attribute list", box);
345  return -1;
346  }
347 
348  goto apply_list;
349  }
350 
351  fr_assert(current->rhs.vpt);
352  fr_assert(tmpl_is_attr(current->rhs.vpt));
353 
354  /*
355  * Doing no modifications to a list is a NOOP.
356  */
357  vp = tmpl_dcursor_init(NULL, request, &cc, &cursor, request, current->rhs.vpt);
358  if (!vp) {
359  tmpl_dcursor_clear(&cc);
360  return 0;
361  }
362 
363  /*
364  * Remove an attribute from a list. The tmpl_dcursor and tmpl_parser ensures that the RHS
365  * references are done in the context of the LHS attribute.
366  */
367  if (map->op == T_OP_SUB_EQ) {
368  fr_pair_t *next;
369 
370  /*
371  * Loop over matching attributes, and delete them.
372  */
373  RDEBUG2("%s %s %s", current->lhs.vpt->name, fr_tokens[T_OP_SUB_EQ], current->rhs.vpt->name);
374 
375  for ( ; vp != NULL; vp = next) {
376  fr_pair_list_t *list;
377 
378  next = fr_dcursor_next(&cursor);
379 
380  list = fr_pair_parent_list(vp);
381  fr_assert(list != NULL);
382 
383  /*
384  * @todo - if this attribute is structural, then remove all children which aren't
385  * immutable. For now, this is good enough.
386  */
387  if (fr_pair_immutable(vp)) {
388  RWDEBUG("Not removing immutable %pP", vp);
389  continue;
390  }
391 
392  if (vp->vp_edit) {
393  RWDEBUG("Attribute cannot be removed, as it is being used in a 'foreach' loop - %pP", vp);
394  continue;
395  }
396 
397  if (fr_edit_list_pair_delete(current->el, list, vp) < 0) {
398  tmpl_dcursor_clear(&cc);
399  return -1;
400  }
401  }
402 
403  tmpl_dcursor_clear(&cc);
404  return 0;
405  }
406 
407  /*
408  * Check the RHS thing we're copying.
409  */
410  if (fr_type_is_structural(vp->vp_type)) {
411  tmpl_dcursor_clear(&cc);
412 
413  if (tmpl_attr_tail_num(current->rhs.vpt) == NUM_ALL) {
414  REDEBUG("%s[%d] Wildcard for structural attribute %s is not yet implemented.", MAP_INFO, current->rhs.vpt->name);
415  return -1;
416  }
417 
418  children = &vp->vp_group;
419  goto apply_list;
420  }
421 
422  /*
423  * Copy the attributes from the cursor to a temporary pair list.
424  */
425  fr_pair_list_init(&current->rhs.pair_list);
426  while (vp) {
427  fr_pair_t *copy;
428 
429  copy = fr_pair_copy(request, vp);
430  if (!copy) {
431  fr_pair_list_free(&current->rhs.pair_list);
432  tmpl_dcursor_clear(&cc);
433  return -1;
434  }
435  fr_pair_append(&current->rhs.pair_list, copy);
436 
437  vp = fr_dcursor_next(&cursor);
438  }
439  tmpl_dcursor_clear(&cc);
440 
441  children = &current->rhs.pair_list;
442 
443  /*
444  * Apply structural thingies!
445  */
446 apply_list:
447  fr_assert(children != NULL);
448 
449  /*
450  * If we have to create the LHS, then do so now.
451  */
452  if (current->lhs.create && (edit_create_lhs_vp(request, state, current) < 0)) {
453  return -1;
454  }
455 
456  fr_assert(current->lhs.vp != NULL);
457 
458 #ifdef STATIC_ANALYZER
459  if (!current->lhs.vp) return -1;
460 #endif
461 
462  /*
463  * Print the children before we do the modifications.
464  */
465  if (!current->parent) {
466  RDEBUG2("%s %s {", current->lhs.vpt->name, fr_tokens[map->op]);
467  if (fr_debug_lvl >= L_DBG_LVL_2) {
468  RINDENT();
469  edit_debug_attr_list(request, children, map);
470  REXDENT();
471  }
472  RDEBUG2("}");
473  }
474 
475  fr_pair_list_foreach(children, child) {
476  if (!fr_dict_attr_can_contain(current->lhs.vp->da, child->da)) {
477  RDEBUG("Cannot perform assignment: Attribute \"%s\" is not a child of parent \"%s\"",
478  child->da->name, current->lhs.vp->da->name);
479  rcode = -1;
480  goto done;
481  }
482  }
483 
484  if (map->op != T_OP_EQ) {
485  fr_assert(current->el != NULL);
486 
487  rcode = fr_edit_list_apply_list_assignment(current->el, current->lhs.vp, map->op, children,
488  (children != &current->rhs.pair_list));
489  if (rcode < 0) RPEDEBUG("Failed performing list '%s' operation", fr_tokens[map->op]);
490 
491  } else {
492 #if 0
493  /*
494  * The RHS list _should_ be a copy of the LHS list. But for some cases it's not. We
495  * should spend time tracking this down, but not today.
496  *
497  * For now, brute-force copy isn't wrong.
498  */
499  if (children == &current->rhs.pair_list) {
500  fr_pair_list_append(&current->lhs.vp->vp_group, children);
501  } else
502 #endif
503  (void) fr_pair_list_copy(current->lhs.vp, &current->lhs.vp->vp_group, children);
504 
505  PAIR_VERIFY(current->lhs.vp);
506  rcode = 0;
507  }
508 
509  /*
510  * If the child list wasn't copied, then we just created it, and we need to free it.
511  */
512 done:
513  if (children == &current->rhs.pair_list) fr_pair_list_free(children);
514  return rcode;
515 }
516 
517 static bool pair_is_editable(request_t *request, fr_pair_t *vp)
518 {
519  if (vp->vp_edit) {
520  RWDEBUG("Attribute cannot be removed, as it is being used in a 'foreach' loop - %s", vp->da->name);
521  return false;
522  }
523 
524  if (!fr_type_is_structural(vp->vp_type)) return true;
525 
526  fr_pair_list_foreach(&vp->vp_group, child) {
527  if (!pair_is_editable(request, child)) return false;
528  }
529 
530  return true;
531 }
532 
533 static int edit_delete_lhs(request_t *request, edit_map_t *current, bool delete)
534 {
536  fr_dcursor_t cursor;
537 
538  /*
539  * These are magic.
540  */
541  if (delete) {
542  fr_dict_attr_t const *da = tmpl_attr_tail_da(current->lhs.vpt);
543 
544  if (fr_type_is_structural(da->type) &&
545  ((da == request_attr_request) ||
546  (da == request_attr_reply) ||
547  (da == request_attr_control) ||
548  (da == request_attr_state))) {
549  delete = false;
550  }
551  }
552 
553  while (true) {
554  int err;
555  fr_pair_t *vp, *parent;
556 
557  /*
558  * Reinitialize the cursor for every VP. This is because fr_dcursor_remove() does not
559  * work with tmpl_dcursors, as the tmpl_dcursor code does not set the "remove" callback.
560  * And the tmpl is NUM_UNSPEC, which means "the first one", whereas for T_OP_SET_EQ, we
561  * really mean "delete all except the first one".
562  *
563  * Once that's implemented, we also need to update the edit list API to
564  * allow for "please delete children"?
565  */
566  vp = tmpl_dcursor_init(&err, current->ctx, &cc, &cursor, request, current->lhs.vpt);
567  if (!vp) break;
568 
570  fr_assert(parent != NULL);
571 
572  if (!pair_is_editable(request, vp)) {
573  tmpl_dcursor_clear(&cc);
574  return -1;
575  }
576 
577  if (!delete) {
578  if (fr_type_is_structural(vp->vp_type)) {
579 
580  if (fr_edit_list_free_pair_children(current->el, vp) < 0) return -1;
581  } else {
582  /*
583  * No need to save value, as fr_edit_list_apply_pair_assignment() will do
584  * that for us.
585  */
586  }
587 
588  current->lhs.vp = vp;
589  tmpl_dcursor_clear(&cc);
590  return 0;
591  }
592 
593  /*
594  * Delete all of them. We'll create one later for the SET operation.
595  */
596  if (fr_edit_list_pair_delete(current->el, &parent->vp_group, vp) < 0) return -1;
597  tmpl_dcursor_clear(&cc);
598  }
599 
600  return 0;
601 }
602 
603 /*
604  * Apply the edits to a leaf attribute. First we figure out where the results come from:
605  *
606  * single value-box (e.g. tmpl_is_data(vpt)
607  * rhs value-box result list (we create a dcursor)
608  * RHS attribute reference (we create a nested dcursor to get the values from the pair list)
609  *
610  * Then we figure out what to do with those values.
611  *
612  * if it needs to be created, then create it and just mash the results in place
613  * otherwise apply the edits (+=, etc.) to an existing attribute.
614  *
615  * @todo - move to using dcursors for all of the values. The dcursor should exist in current->rhs. It
616  * should be used even for TMPL_DATA and single value-boxes. Once that's done, it becomes easier to use
617  * dcursors for xlats, too.
618  */
620 {
621  fr_value_box_t *box = NULL;
623  fr_dcursor_t cursor;
624  fr_dcursor_t pair_cursor;
625  bool single = false, pair = false;
626  map_t const *map = current->map;
627 
628  XDEBUG("apply_edits_to_leaf %s", map->lhs->name);
629 
630  if (!tmpl_is_attr(current->lhs.vpt)) {
631  REDEBUG("%s[%d] The left side of an assignment must be an attribute reference", MAP_INFO);
632  return -1;
633  }
634 
635  /*
636  * &Foo := { a, b, c }
637  *
638  * There should be values in RHS result, all of value boxes.
639  */
640  if (!map->rhs) {
641  fr_assert(current->rhs.vpt == NULL);
642  goto rhs_list;
643 
644  }
645 
646  if (!current->rhs.vpt) {
647  /*
648  * There's no RHS tmpl, so the result must be in in the parent RHS tmpl as data, OR in
649  * the RHS result list.
650  */
651  if (tmpl_is_data(map->rhs)) {
652  box = tmpl_value(map->rhs);
653  single = true;
654 
655  } else if ((map->rhs->quote == T_SINGLE_QUOTED_STRING) || (map->rhs->quote == T_DOUBLE_QUOTED_STRING)) {
656  /*
657  * The caller asked for a string, so instead of returning a list, return a string.
658  *
659  * If there's no output, then it's an empty string.
660  *
661  * We have to check this here, because the quote is part of the tmpl, and we call
662  * xlat_push(), which doesn't know about the quote.
663  */
664  box = fr_value_box_list_head(&current->rhs.result);
665 
666  if (!box) {
667  MEM(box = fr_value_box_alloc(state, FR_TYPE_STRING, NULL));
668  fr_value_box_list_insert_tail(&current->rhs.result, box);
669 
670  } else if (fr_value_box_list_concat_in_place(box, box, &current->rhs.result, FR_TYPE_STRING,
671  FR_VALUE_BOX_LIST_FREE_BOX, true, 8192) < 0) {
672  RWDEBUG("Failed converting result to string");
673  return -1;
674  }
675  box = fr_value_box_list_head(&current->rhs.result);
676  single = true;
677 
678  } else {
679  rhs_list:
680  if (fr_value_box_list_num_elements(&current->rhs.result) == 1) {
681  box = fr_value_box_list_head(&current->rhs.result);
682  single = true;
683  } else {
684  box = fr_dcursor_init(&cursor, fr_value_box_list_dlist_head(&current->rhs.result));
685  }
686  }
687  } else {
688  fr_pair_t *vp;
689  int err;
690 
691  /*
692  * We have a temporary tmpl on the RHS. It MUST be an attribute, because everything else
693  * was expanded to a value-box list.
694  */
695  fr_assert(tmpl_is_attr(current->rhs.vpt));
696 
697  /*
698  * Get a cursor over the RHS pairs.
699  */
700  vp = tmpl_dcursor_init(&err, request, &cc, &pair_cursor, request, current->rhs.vpt);
701  if (!vp) {
702  tmpl_dcursor_clear(&cc);
703 
704  if (map->op != T_OP_SET) return 0;
705 
706  /*
707  * No RHS pairs means we can finally delete all of the LHS.
708  */
709  return edit_delete_lhs(request, current, true);
710  }
711 
712  box = fr_pair_dcursor_nested_init(&cursor, &pair_cursor); // the list is unused
713  pair = true;
714  }
715 
716  if (!box) {
717  if (map->op != T_OP_SET) {
718  RWDEBUG("%s %s ... - Assignment failed - No value on right-hand side", map->lhs->name, fr_tokens[map->op]);
719  return -1;
720  }
721 
722  /*
723  * Set is "delete, then add".
724  */
725  RDEBUG2("%s :=", current->lhs.vpt->name);
726  goto done;
727  }
728 
729  /*
730  * The parent is a structural type. The RHS is a temporary list or attribute, which we can just
731  * add to the parents pair list. The parent will then take care of merging that pair list into
732  * the appropriate place.
733  */
734  if (current->temporary_pair_list) {
735  fr_pair_list_t *list = &current->parent->rhs.pair_list;
736  fr_pair_t *vp;
737 
738  if (!current->parent->lhs.vp) {
739  if (edit_create_lhs_vp(request, request, current->parent) < 0) return -1;
740  }
741 
742  while (box) {
743  /*
744  * Create (or find) all intermediate attributes. The LHS map might have multiple
745  * attribute names in it.
746  *
747  * @todo - audit other uses of tmpl_attr_tail_da() and fr_pair_afrom_da() in this file.
748  */
749  if (pair_append_by_tmpl_parent(current->parent->lhs.vp, &vp, list, current->lhs.vpt, true) < 0) {
750  RWDEBUG("Failed creating attribute %s", current->lhs.vpt->name);
751  return -1;
752  }
753 
754  vp->op = map->op;
755  if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) return -1;
756 
757  if (single) break;
758 
759  box = fr_dcursor_next(&cursor);
760  }
761 
762  goto done;
763  }
764 
765  /*
766  * If we're supposed to create the LHS, then go do that.
767  */
768  if (current->lhs.create) {
769  fr_dict_attr_t const *da = tmpl_attr_tail_da(current->lhs.vpt);
770  fr_pair_t *vp;
771 
772  /*
773  * Something went wrong creating the value, it's a failure. Note that we fail _all_
774  * subsequent assignments, too.
775  */
776  if (fr_type_is_null(box->type)) goto fail;
777 
778  if (edit_create_lhs_vp(request, state, current) < 0) goto fail;
779 
780  fr_assert(current->lhs.vp_parent != NULL);
781  fr_assert(fr_type_is_structural(current->lhs.vp_parent->vp_type));
782 
783  vp = current->lhs.vp;
784 
785  /*
786  * There's always at least one LHS vp created. So we apply that first.
787  */
788  RDEBUG_ASSIGN(current->lhs.vpt->name, map->op, box);
789 
790  /*
791  * The VP has already been inserted into the edit list, so we don't need to edit it's
792  * value, we can just mash it in place.
793  */
794  if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) goto fail;
795  vp->op = T_OP_EQ;
796 
797  if (single) goto done;
798 
799  /*
800  * Now that the attribute has been created, go apply the rest of the values to the attribute.
801  */
802  if (!((map->op == T_OP_EQ) || (map->op == T_OP_SET))) {
803  box = fr_dcursor_next(&cursor);
804  if (!box) goto done;
805 
806  goto apply_op;
807  }
808 
809  if (current->lhs.vp->da->flags.local) {
810  if (fr_dcursor_next_peek(&cursor)) RWDEBUG("Ignoring extra values for local variable");
811  goto done;
812  }
813 
814  /*
815  * Loop over the remaining items, adding the VPs we've just created.
816  */
817  while ((box = fr_dcursor_next(&cursor)) != NULL) {
818  RDEBUG_ASSIGN(current->lhs.vpt->name, map->op, box);
819 
820  MEM(vp = fr_pair_afrom_da(current->lhs.vp_parent, da));
821  if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) goto fail;
822 
823  if (fr_edit_list_insert_pair_tail(state->el, &current->lhs.vp_parent->vp_group, vp) < 0) goto fail;
824  vp->op = T_OP_EQ;
825  }
826 
827  goto done;
828  }
829 
830  /*
831  * If we're not creating a temporary list, we must be editing an existing attribute on the LHS.
832  *
833  * We have two remaining cases. One is the attribute was just created with "=" or ":=", so we
834  * can just mash its value. The second is that the attribute already exists, and we're editing
835  * it's value using something like "+=".
836  */
837  fr_assert(current->lhs.vp != NULL);
838 
839 #ifdef STATIC_ANALYZER
840  if (!current->lhs.vp) return -1;
841 #endif
842 
843 apply_op:
844  /*
845  * All other operators are "modify in place", of the existing current->lhs.vp
846  */
847  while (box) {
848  RDEBUG_ASSIGN(current->lhs.vpt->name, map->op, box);
849 
850  /*
851  * The apply function also takes care of doing data type upcasting and conversion. So we don't
852  * have to check for compatibility of the data types on the LHS and RHS.
853  */
855  current->lhs.vp,
856  map->op,
857  box) < 0) {
858  fail:
859  RPEDEBUG("Assigning value to %s failed", map->lhs->name);
860  if (pair) tmpl_dcursor_clear(&cc);
861  return -1;
862  }
863 
864  if (single) break;
865 
866  box = fr_dcursor_next(&cursor);
867  }
868 
869 done:
870  if (pair) tmpl_dcursor_clear(&cc);
871  fr_value_box_list_talloc_free(&current->rhs.result);
872 
873  return 0;
874 }
875 
876 
877 /** Simple pair building callback for use with tmpl_dcursors
878  *
879  * Which always appends the new pair to the tail of the list
880  * since it is only called when no matching pairs were found when
881  * walking the list.
882  *
883  * Note that this function is called for all intermediate nodes which are built!
884  *
885  *
886  *
887  * @param[in] parent to allocate new pair within.
888  * @param[in,out] cursor to append new pair to.
889  * @param[in] da of new pair.
890  * @param[in] uctx unused.
891  * @return
892  * - newly allocated #fr_pair_t.
893  * - NULL on error.
894  */
896 {
897  fr_pair_t *vp;
899 
900  vp = fr_pair_afrom_da(parent, da);
901  if (!vp) return NULL;
902 
903  current->lhs.vp_parent = parent;
904  current->lhs.vp = vp;
905 
906  if (fr_edit_list_insert_pair_tail(current->el, &parent->vp_group, vp) < 0) {
907  talloc_free(vp);
908  return NULL;
909  }
910 
911  /*
912  * Tell the cursor that we appended a pair. This
913  * function only gets called when we've ran off of the
914  * end of the list, and can't find the thing we're
915  * looking for. So it's safe at set the current one
916  * here.
917  *
918  * @todo - mainly only because we don't allow creating
919  * foo[4] when there's <3 matching entries. i.e. the
920  * "arrays" here are really lists, so we can't create
921  * "holes" in the list.
922  */
923  fr_dcursor_set_current(cursor, vp);
924 
925  return vp;
926 }
927 
928 #define DECLARE(_x) static int _x(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
929 
936 
937 /*
938  * Clean up the current state, and go to the next map.
939  */
941 {
942  TALLOC_FREE(current->lhs.to_free);
943  TALLOC_FREE(current->rhs.to_free);
944  fr_pair_list_free(&current->rhs.pair_list);
945  current->lhs.vp = NULL;
946  current->lhs.vp_parent = NULL;
947  current->lhs.vpt = NULL;
948  current->rhs.vpt = NULL;
949 
950  current->map = map_list_next(current->map_list, current->map);
951  current->func = expand_lhs;
952 
953  /*
954  * Don't touch the other callbacks.
955  */
956 
957  return 0;
958 }
959 
960 /*
961  * Validate the RHS of an expansion.
962  */
964 {
965  map_t const *map = current->map;
966 
967  XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
968 
969  /*
970  * := is "remove all matching, and then add". So if even if we don't add anything, we still remove things.
971  *
972  * If we deleted the attribute when processing the LHS, then you couldn't reference an attribute
973  * in it's own assignment:
974  *
975  * &foo := %tolower(foo)
976  *
977  * so we have to delay the deletion until the RHS has been fully expanded. But we don't always
978  * delete everything. e.g. if the map is:
979  *
980  * &foo[1] := %tolower(foo[1])
981  *
982  * The we just apply the assignment to the LHS, over-writing it's value.
983  */
984  if ((map->op == T_OP_SET) &&
985  ((tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) || (tmpl_attr_tail_num(current->lhs.vpt) > 0) ||
986  !current->map->rhs)) {
987  if (edit_delete_lhs(request, current,
988  (tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) || !current->map->rhs) < 0) return -1;
989  }
990 
991  /*
992  * @todo - Realize the RHS box value. By moving the code in apply_edits_to_leaf() to a common function,
993  * and getting the box dcursor here.
994  *
995  * Then, get a cursor for the LHS vp, and loop over it, applying the edits in the operator, using
996  * the comparisons in the RHS box.
997  *
998  * This lets us use array indexes (or more complex things) on the LHS, and means that we don't
999  * have to realize the VPs and use horrible hacks.
1000  */
1001  if (current->parent && (current->parent->map->op == T_OP_SUB_EQ)) {
1002  fr_assert(current->temporary_pair_list);
1003  fr_assert(tmpl_is_attr(current->lhs.vpt)); /* can only apply edits to real attributes */
1004  fr_assert(map->rhs); /* can only filter on leaf attributes */
1005 
1006 #if 0
1007  {
1008  // dcursor_init over current->lhs.vpt, using children of current->parent.lhs_vp
1009  //
1010  // and then use the dcursor from the apply_edits_to_leaf() to get value-boxes
1011  rcode = fr_value_box_cmp_op(map->op, &vp->data, box);
1012  if (rcode < 0) return -1;
1013 
1014  if (!rcode) continue;
1015 
1016  if (fr_edit_list_pair_delete(el, list, vp) < 0) return -1;
1017  }
1018 
1019  return next_map(request, state, current);
1020 #endif
1021  }
1022 
1023  if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
1024  if (apply_edits_to_leaf(request, state, current) < 0) return -1;
1025  } else {
1026  if (apply_edits_to_list(request, state, current) < 0) return -1;
1027  }
1028 
1029  return next_map(request, state, current);
1030 }
1031 
1032 /*
1033  * The RHS map is a sublist. Go expand that by creating a child expansion context, and returning to the
1034  * main loop.
1035  */
1037 {
1038  map_t const *map = current->map;
1039  edit_map_t *child;
1040 
1041  XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1042 
1043  /*
1044  * If there's no RHS tmpl, then the RHS is a child list.
1045  */
1046  fr_assert(!map->rhs);
1047 
1048  /*
1049  * Fast path: child is empty, we don't need to do anything.
1050  */
1051  if (fr_dlist_empty(&map->child.head)) {
1052  if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type) && (map->op != T_OP_SET)) {
1053  REDEBUG("%s[%d] Cannot assign a list to the '%s' data type", MAP_INFO, fr_type_to_str(tmpl_attr_tail_da(current->lhs.vpt)->type));
1054  return -1;
1055  }
1056 
1057  return check_rhs(request, state, current);
1058  }
1059 
1060  /*
1061  * Allocate a new child structure if necessary.
1062  */
1063  child = current->child;
1064  if (!child) {
1065  MEM(child = talloc_zero(state, edit_map_t));
1066  current->child = child;
1067  child->parent = current;
1068  }
1069 
1070  /*
1071  * Initialize the child structure. There's no edit list here, as we're
1072  * creating a temporary pair list. Any edits to this list aren't
1073  * tracked, as it only exists in current->parent->rhs.pair_list.
1074  *
1075  * The parent edit_state_t will take care of applying any edits to the
1076  * parent vp. Any child pairs which aren't used will be freed.
1077  */
1078  child->el = NULL;
1079  child->map_list = &map->child;
1080  child->map = map_list_head(child->map_list);
1081  child->func = expand_lhs;
1082 
1083  if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
1084  child->ctx = child;
1085  child->check_lhs = check_lhs_value;
1087  } else {
1088  child->ctx = current->lhs.vp ? (TALLOC_CTX *) current->lhs.vp : (TALLOC_CTX *) child;
1089  child->check_lhs = check_lhs_nested;
1091  child->temporary_pair_list = true;
1092  }
1093 
1094  memset(&child->lhs, 0, sizeof(child->lhs));
1095  memset(&child->rhs, 0, sizeof(child->rhs));
1096 
1097  fr_pair_list_init(&child->rhs.pair_list);
1098  fr_value_box_list_init(&child->lhs.result);
1099  fr_value_box_list_init(&child->rhs.result);
1100 
1101  /*
1102  * Continue back with the RHS when we're done processing the
1103  * child. The go process the child.
1104  */
1105  current->func = check_rhs;
1106  state->current = child;
1107  RINDENT();
1108  return 0;
1109 }
1110 
1111 
1112 /*
1113  * Expand the RHS of an assignment operation.
1114  */
1116 {
1117  int rcode;
1118  map_t const *map = current->map;
1119 
1120  if (!map->rhs) return expand_rhs_list(request, state, current);
1121 
1122  XDEBUG("%s map %s %s %s", __FUNCTION__, map->lhs->name, fr_tokens[map->op], map->rhs->name);
1123 
1124  /*
1125  * Turn the RHS into a tmpl_t. This can involve just referencing an existing
1126  * tmpl in map->rhs, or expanding an xlat to get an attribute name.
1127  */
1128  rcode = tmpl_to_values(state, &current->rhs, request, map->rhs);
1129  if (rcode < 0) return -1;
1130 
1131  if (rcode == 1) {
1132  current->func = check_rhs;
1133  return 1;
1134  }
1135 
1136  return check_rhs(request, state, current);
1137 }
1138 
1139 /*
1140  * The LHS is a value, and the parent is a leaf. There is no RHS.
1141  *
1142  * Do some validations, and move the value-boxes to the parents result list.
1143  */
1145 {
1146  map_t const *map = current->map;
1147  fr_value_box_t *box;
1148  fr_pair_t *vp;
1149  tmpl_t const *vpt;
1150  tmpl_dcursor_ctx_t cc;
1151  fr_dcursor_t cursor;
1152 
1153  fr_assert(current->parent);
1154 
1155  XDEBUG("%s map %s", __FUNCTION__, map->lhs->name);
1156 
1157  if (tmpl_is_data(map->lhs)) {
1158  vpt = map->lhs;
1159 
1160  data:
1161  MEM(box = fr_value_box_alloc_null(state));
1162  if (fr_value_box_copy(box, box, tmpl_value(vpt)) < 0) return -1;
1163 
1164  fr_value_box_list_insert_tail(&current->parent->rhs.result, box);
1165 
1166  return next_map(request, state, current);
1167  }
1168 
1169  if (!current->lhs.vpt) {
1170  vpt = map->lhs;
1171 
1172  /*
1173  *
1174  */
1175  if (tmpl_is_xlat(vpt)) return next_map(request,state, current);
1176 
1177  attr:
1179 
1180  /*
1181  * Loop over the attributes, copying their value-boxes to the parent list.
1182  */
1183  vp = tmpl_dcursor_init(NULL, request, &cc, &cursor, request, vpt);
1184  while (vp) {
1185  MEM(box = fr_value_box_alloc_null(state));
1186  if (fr_value_box_copy(box, box, &vp->data) < 0) return -1;
1187 
1188  fr_value_box_list_insert_tail(&current->parent->rhs.result, box);
1189 
1190  vp = fr_dcursor_next(&cursor);
1191  }
1192  tmpl_dcursor_clear(&cc);
1193 
1194  return next_map(request, state, current);
1195  }
1196 
1197  vpt = current->lhs.vpt;
1198 
1199  if (tmpl_is_data(vpt)) goto data;
1200 
1201  goto attr;
1202 }
1203 
1204 /*
1205  * We've expanded the LHS (xlat or exec) into a value-box list. The result gets moved to the parent
1206  * result list.
1207  *
1208  * There's no RHS, so once the LHS has been expanded, we jump immediately to the next entry.
1209  */
1211 {
1212  fr_dict_attr_t const *da;
1213  fr_type_t type;
1214  fr_value_box_t *box = fr_value_box_list_head(&current->lhs.result);
1215  fr_value_box_t *dst;
1216  fr_sbuff_unescape_rules_t *erules = NULL;
1217 
1218  fr_assert(current->parent);
1219 
1220  if (!box) {
1221  RWDEBUG("Failed expanding result");
1222  return -1;
1223  }
1224 
1225  fr_assert(tmpl_is_attr(current->parent->lhs.vpt));
1226 
1227  /*
1228  * There's only one value-box, just use it as-is. We let the parent handler complain about being
1229  * able to parse (or not) the value.
1230  */
1231  if (!fr_value_box_list_next(&current->lhs.result, box)) goto done;
1232 
1233  /*
1234  * Figure out how to parse the string.
1235  */
1236  da = tmpl_attr_tail_da(current->parent->lhs.vpt);
1237  if (fr_type_is_structural(da->type)) {
1238  fr_assert(da->type == FR_TYPE_GROUP);
1239 
1240  type = FR_TYPE_STRING;
1241 
1242  } else if (fr_type_is_variable_size(da->type)) {
1243  type = da->type;
1244 
1245  } else {
1246  type = FR_TYPE_STRING;
1247  }
1248 
1249  /*
1250  * Mash all of the results together.
1251  */
1252  if (fr_value_box_list_concat_in_place(box, box, &current->lhs.result, type, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
1253  RWDEBUG("Failed converting result to '%s' - no memory", fr_type_to_str(type));
1254  return -1;
1255  }
1256 
1257  /*
1258  * Strings, etc. get assigned to the parent. Fixed-size things ger parsed according to their values / enums.
1259  */
1260  if (!fr_type_is_fixed_size(da->type)) {
1261  done:
1262  fr_value_box_list_move(&current->parent->rhs.result, &current->lhs.result);
1263  return next_map(request, state, current);
1264  }
1265 
1266  /*
1267  * Try to re-parse the box as the destination data type.
1268  */
1269  MEM(dst = fr_value_box_alloc(state, type, da));
1270 
1271  erules = fr_value_unescape_by_quote[current->map->lhs->quote];
1272 
1273  if (fr_value_box_from_str(dst, dst, da->type, da, box->vb_strvalue, box->vb_length, erules, box->tainted) < 0) {
1274  RWDEBUG("Failed converting result to '%s' - %s", fr_type_to_str(type), fr_strerror());
1275  return -1;
1276  }
1277 
1278  fr_value_box_list_talloc_free(&current->lhs.result);
1279  fr_value_box_list_insert_tail(&current->parent->rhs.result, dst);
1280  return next_map(request, state, current);
1281 }
1282 
1283 /*
1284  * Check the LHS of an assignment, for
1285  *
1286  * foo = { bar = baz } LHS bar
1287  *
1288  * There are more limitations here on the attr / op / value format then for the top-level check_lhs().
1289  */
1291 {
1292  map_t const *map = current->map;
1293 
1294  fr_assert(current->parent != NULL);
1295 
1296  XDEBUG("%s map %s", __FUNCTION__, map->lhs->name);
1297 
1298  /*
1299  * Don't create the leaf. The apply_edits_to_leaf() function will create them after the RHS has
1300  * been expanded.
1301  */
1302  if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
1303  return expand_rhs(request, state, current);
1304  }
1305 
1306  /*
1307  * We have a parent, so we know that attribute exist. Which means that we don't need to call a
1308  * cursor function to create this VP.
1309  */
1310 
1311  /*
1312  * We create this VP in the "current" context, so that it's freed on
1313  * error. If we create it in the LHS VP context, then we have to
1314  * manually free rhs.pair_list on any error. Creating it in the
1315  * "current" context means we have to reparent it when we move it to the
1316  * parent list, but fr_edit_list_apply_list_assignment() does that
1317  * anyways.
1318  */
1319  MEM(current->lhs.vp = fr_pair_afrom_da(current->ctx, tmpl_attr_tail_da(current->lhs.vpt)));
1320  fr_pair_append(&current->parent->rhs.pair_list, current->lhs.vp);
1321  current->lhs.vp->op = map->op;
1322 
1323  return expand_rhs(request, state, current);
1324 }
1325 
1326 /*
1327  * The LHS tmpl is now an attribute reference. Do some sanity checks on tmpl_attr_tail_num(), operators, etc.
1328  * Once that's done, go expand the RHS.
1329  */
1331 {
1332  map_t const *map = current->map;
1333  int err;
1334  fr_pair_t *vp;
1335  tmpl_dcursor_ctx_t cc;
1336  fr_dcursor_t cursor;
1337 
1338  current->lhs.create = false;
1339  current->lhs.vp = NULL;
1340 
1341  XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1342 
1343  /*
1344  * Create the attribute, including any necessary parents.
1345  */
1346  if ((map->op == T_OP_EQ) ||
1347  (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type) && fr_comparison_op[map->op])) {
1348  if (tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) {
1349  current->lhs.create = true;
1350 
1351  /*
1352  * Don't go to expand_rhs(), as we have to see if the attribute exists.
1353  */
1354  }
1355 
1356  } else if (map->op == T_OP_SET) {
1357  if (tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) {
1358  current->lhs.create = true;
1359  return expand_rhs(request, state, current);
1360  }
1361 
1362  /*
1363  * Else we're doing something like:
1364  *
1365  * &foo[1] := bar
1366  *
1367  * the attribute has to exist, and we modify its value as a leaf.
1368  *
1369  * If the RHS is a list, we can set the children for a LHS structural type.
1370  * But if the LHS is a leaf, then we can't do:
1371  *
1372  * &foo[3] := { a, b, c}
1373  *
1374  * because foo[3] is a single leaf value, not a list.
1375  */
1376  if (!map->rhs && fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type) &&
1377  (map_list_num_elements(&map->child) > 0)) {
1378  RWDEBUG("Cannot set one entry to multiple values for %s", current->lhs.vpt->name);
1379  return -1;
1380  }
1381 
1382  } else if (map->op == T_OP_ADD_EQ) {
1383  /*
1384  * For "+=", if there's no existing attribute, create one, and rewrite the operator we
1385  * apply to ":=". Which also means moving the operator be in edit_map_t, and then updating the
1386  * "apply" functions above to use that for the operations, but map->op for printing.
1387  *
1388  * This allows "foo += 4" to set "foo := 4" when the attribute doesn't exist. It also allows us
1389  * to do list appending to an empty list. But likely only for strings, octets, and numbers.
1390  * Nothing much else makes sense.
1391  */
1392 
1393  switch (tmpl_attr_tail_da(current->lhs.vpt)->type) {
1394  case FR_TYPE_NUMERIC:
1395  case FR_TYPE_OCTETS:
1396  case FR_TYPE_STRING:
1397  case FR_TYPE_STRUCTURAL:
1398  current->lhs.create = true;
1399  break;
1400 
1401  default:
1402  break;
1403  }
1404  }
1405 
1406  /*
1407  * Find the VP. If the operation is "=" or ":=", then it's OK for the VP to not exist.
1408  *
1409  * @todo - put the cursor into the LHS, and then set lhs.vp == NULL
1410  * use the cursor in apply_edits_to_leaf()
1411  */
1413  vp = tmpl_dcursor_init(&err, current->ctx, &cc, &cursor, request, current->lhs.vpt);
1414  tmpl_dcursor_clear(&cc);
1415  if (!vp) {
1416  if (!current->lhs.create) {
1417  RWDEBUG("Failed finding %s", current->lhs.vpt->name);
1418  return -1;
1419  }
1420 
1421  /*
1422  * Else we need to create it.
1423  */
1424  return expand_rhs(request, state, current);
1425 
1426  } else if (current->lhs.create) {
1427  /*
1428  * &foo[1] := bar
1429  * &foo = bar
1430  */
1431  current->lhs.create = false;
1432 
1433  if (map->rhs && fr_type_is_structural(vp->vp_type) && tmpl_is_exec(map->rhs)) {
1434  int rcode;
1435 
1436  current->lhs.vp = vp;
1437  current->lhs.vp_parent = fr_pair_parent(vp);
1438 
1439  rcode = tmpl_to_values(state, &current->rhs, request, map->rhs);
1440  if (rcode < 0) return -1;
1441 
1442  if (rcode == 1) {
1443  current->func = check_rhs;
1444  return 1;
1445  }
1446 
1447  return expand_rhs(request, state, current);
1448  }
1449 
1450  /*
1451  * We found it, but the attribute already exists. This
1452  * is a NOOP, where we ignore this assignment.
1453  */
1454  if (map->op == T_OP_EQ) {
1455  return next_map(request, state, current);
1456  }
1457 
1458  /*
1459  * &foo[1] exists, don't bother deleting it. Just over-write its value.
1460  */
1461  fr_assert((map->op == T_OP_SET) || (map->op == T_OP_ADD_EQ) || fr_comparison_op[map->op]);
1462 // fr_assert((map->op == T_OP_ADD_EQ) || tmpl_attr_tail_num(map->lhs) != NUM_UNSPEC);
1463 
1464  // &control := ...
1465  }
1466 
1467  /*
1468  * We forbid operations on immutable leaf attributes.
1469  *
1470  * If a list contains an immutable attribute, then we can still operate on the list, but instead
1471  * we look at each VP we're operating on.
1472  */
1473  if (fr_type_is_leaf(vp->vp_type) && vp->vp_immutable) {
1474  RWDEBUG("Cannot modify immutable value for %s", current->lhs.vpt->name);
1475  return -1;
1476  }
1477 
1478  /*
1479  * We found an existing attribute, with a modification operator.
1480  */
1481  current->lhs.vp = vp;
1482  current->lhs.vp_parent = fr_pair_parent(current->lhs.vp);
1483  return expand_rhs(request, state, current);
1484 }
1485 
1486 /*
1487  * We've expanding the LHS into a string. Now convert it to an attribute.
1488  *
1489  * foo := bar LHS foo
1490  * foo = { bar = baz } LHS bar
1491  */
1493 {
1494  REXDENT();
1495 
1496  if (tmpl_attr_from_result(state, current->map, &current->lhs, request) < 0) return -1;
1497 
1498  return current->check_lhs(request, state, current);
1499 }
1500 
1501 /*
1502  * Take the LHS of a map, and figure out what it is. Data and attributes are immediately processed.
1503  * xlats and execs are expanded, and then their expansion is checked.
1504  *
1505  * This function is called for all variants of the LHS:
1506  *
1507  * foo := bar LHS foo
1508  * foo = { bar = baz } LHS bar
1509  * foo = { 1, 2, 3, 4 } LHS 1, 2, etc.
1510  *
1511  */
1513 {
1514  int rcode;
1515  map_t const *map = current->map;
1516 
1517  XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1518 
1519  fr_assert(fr_value_box_list_empty(&current->lhs.result)); /* Should have been consumed */
1520  fr_assert(fr_value_box_list_empty(&current->rhs.result)); /* Should have been consumed */
1521 
1522  rcode = tmpl_to_values(state, &current->lhs, request, map->lhs);
1523  if (rcode < 0) return -1;
1524 
1525  if (rcode == 1) {
1526  current->func = current->expanded_lhs;
1527  return 1;
1528  }
1529 
1530  return current->check_lhs(request, state, current);
1531 }
1532 
1533 /** Apply a map (recursively) to a request.
1534  *
1535  * @param[out] p_result The rcode indicating what the result
1536  * of the operation was.
1537  * @param[in] request The current request.
1538  * @param[in] frame Current stack frame.
1539  * @return
1540  * - UNLANG_ACTION_CALCULATE_RESULT changes were applied.
1541  * - UNLANG_ACTION_PUSHED_CHILD async execution of an expansion is required.
1542  */
1544 {
1545  unlang_frame_state_edit_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
1546 
1547  /*
1548  * Keep running the "expand map" function until done.
1549  */
1550  while (state->current) {
1551  while (state->current->map) {
1552  int rcode;
1553 
1554  if (!state->current->map->rhs) {
1555  XDEBUG("MAP %s ...", state->current->map->lhs->name);
1556  } else {
1557  XDEBUG("MAP %s ... %s", state->current->map->lhs->name, state->current->map->rhs->name);
1558  }
1559 
1560  rcode = state->current->func(request, state, state->current);
1561  if (rcode < 0) {
1562  RINDENT_RESTORE(request, state);
1563 
1564  /*
1565  * Expansions, etc. failures are SOFT failures, which undo the edit
1566  * operations, but otherwise do not affect the interpreter.
1567  *
1568  * However, if the caller asked for the actual result, return that, too.
1569  */
1570  if (state->success) *state->success = false;
1571 
1572  if (state->ours) fr_edit_list_abort(state->el);
1573  TALLOC_FREE(frame->state);
1574  repeatable_clear(frame);
1575  *p_result = RLM_MODULE_FAIL;
1576 
1578  }
1579 
1580  if (rcode == 1) {
1581  repeatable_set(frame);
1583  }
1584  }
1585 
1586  /*
1587  * Stop if there's no parent to process.
1588  */
1589  if (!state->current->parent) break;
1590 
1591  state->current = state->current->parent;
1592  REXDENT(); /* "push child" has called RINDENT */
1593  }
1594 
1595  /*
1596  * Freeing the edit list will automatically commit the edits. i.e. trash the undo list, and
1597  * leave the edited pairs in place.
1598  */
1599 
1600  RINDENT_RESTORE(request, state);
1601 
1602  *p_result = RLM_MODULE_NOOP;
1603  if (state->success) *state->success = true;
1605 }
1606 
1607 static void edit_state_init_internal(request_t *request, unlang_frame_state_edit_t *state, fr_edit_list_t *el, map_list_t const *map_list)
1608 {
1609  edit_map_t *current = &state->first;
1610 
1611  state->current = current;
1612  fr_value_box_list_init(&current->lhs.result);
1613  fr_value_box_list_init(&current->rhs.result);
1614 
1615  /*
1616  * The edit list creates a local pool which should
1617  * generally be large enough for most edits.
1618  */
1619  if (!el) {
1620  MEM(state->el = fr_edit_list_alloc(state, map_list_num_elements(map_list), NULL));
1621  state->ours = true;
1622  } else {
1623  state->el = el;
1624  state->ours = false;
1625  }
1626 
1627  current->ctx = state;
1628  current->el = state->el;
1629  current->map_list = map_list;
1630  current->map = map_list_head(current->map_list);
1631  fr_pair_list_init(&current->rhs.pair_list);
1632  current->func = expand_lhs;
1633  current->check_lhs = check_lhs;
1634  current->expanded_lhs = expanded_lhs_attribute;
1635 
1636  /*
1637  * Save current indentation for the error path.
1638  */
1639  RINDENT_SAVE(state, request);
1640 }
1641 
1642 /** Execute an update block
1643  *
1644  * Update blocks execute in two phases, first there's an evaluation phase where
1645  * each input map is evaluated, outputting one or more modification maps. The modification
1646  * maps detail a change that should be made to a list in the current request.
1647  * The request is not modified during this phase.
1648  *
1649  * The second phase applies those modification maps to the current request.
1650  * This re-enables the atomic functionality of update blocks provided in v2.x.x.
1651  * If one map fails in the evaluation phase, no more maps are processed, and the current
1652  * result is discarded.
1653  */
1655 {
1657  unlang_frame_state_edit_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
1659 
1660  edit_state_init_internal(request, state, el, &edit->maps);
1661 
1662  /*
1663  * Call process_edit to do all of the work.
1664  */
1665  frame_repeat(frame, process_edit);
1666  return process_edit(p_result, request, frame);
1667 }
1668 
1669 
1670 /** Push a map onto the stack for edit evaluation
1671  *
1672  * If the "success" variable returns "false", the caller should call fr_edit_list_abort().
1673  *
1674  * If the "success" variable returns "true", the caller can free the edit list (or rely on talloc to do that)
1675  * and the transaction will be finalized.
1676  *
1677  * @param[in] request The current request.
1678  * @param[out] success Whether or not the edit succeeded
1679  * @param[in] el Edit list which can be used to apply multiple edits
1680  * @param[in] map_list The map list to process
1681  */
1682 int unlang_edit_push(request_t *request, bool *success, fr_edit_list_t *el, map_list_t const *map_list)
1683 {
1684  unlang_stack_t *stack = request->stack;
1685  unlang_stack_frame_t *frame;
1687 
1688  unlang_edit_t *edit;
1689 
1690  static unlang_t edit_instruction = {
1692  .name = "edit",
1693  .debug_name = "edit",
1694  .actions = {
1695  .actions = {
1696  [RLM_MODULE_REJECT] = 0,
1697  [RLM_MODULE_FAIL] = 0,
1698  [RLM_MODULE_OK] = 0,
1699  [RLM_MODULE_HANDLED] = 0,
1700  [RLM_MODULE_INVALID] = 0,
1701  [RLM_MODULE_DISALLOW] = 0,
1702  [RLM_MODULE_NOTFOUND] = 0,
1703  [RLM_MODULE_NOOP] = 0,
1704  [RLM_MODULE_UPDATED] = 0
1705  },
1706  .retry = RETRY_INIT,
1707  },
1708  };
1709 
1710  MEM(edit = talloc(stack, unlang_edit_t));
1711  *edit = (unlang_edit_t) {
1712  .self = edit_instruction,
1713  };
1714  map_list_init(&edit->maps);
1715 
1716  /*
1717  * Push a new edit frame onto the stack
1718  */
1719  if (unlang_interpret_push(request, unlang_edit_to_generic(edit),
1720  RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, false) < 0) return -1;
1721 
1722  frame = &stack->frame[stack->depth];
1723  state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
1724 
1725  edit_state_init_internal(request, state, el, map_list);
1726  state->success = success;
1727 
1728  return 0;
1729 }
1730 
1732 {
1734  &(unlang_op_t){
1735  .name = "edit",
1736  .interpret = unlang_edit_state_init,
1737  .frame_state_size = sizeof(unlang_frame_state_edit_t),
1738  .frame_state_type = "unlang_frame_state_edit_t",
1739  });
1740 }
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
@ UNLANG_ACTION_PUSHED_CHILD
unlang_t pushed a new child onto the stack, execute it instead of continuing.
Definition: action.h:39
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition: action.h:37
#define RCSID(id)
Definition: build.h:481
#define UNUSED
Definition: build.h:313
static void * fr_dcursor_next_peek(fr_dcursor_t *cursor)
Return the next iterator item without advancing the cursor.
Definition: dcursor.h:303
#define fr_dcursor_init(_cursor, _head)
Initialise a cursor.
Definition: dcursor.h:732
static void * fr_dcursor_set_current(fr_dcursor_t *cursor, void *item)
Set the cursor to a specified item.
Definition: dcursor.h:353
next
Definition: dcursor.h:178
fr_dcursor_eval_t void const * uctx
Definition: dcursor.h:546
fr_dcursor_iter_t void * current
Definition: dcursor.h:148
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition: dcursor.h:288
static fr_slen_t err
Definition: dict.h:821
bool fr_dict_attr_can_contain(fr_dict_attr_t const *parent, fr_dict_attr_t const *child)
See if a structural da is allowed to contain another da.
Definition: dict_util.c:4875
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition: dict_util.c:2400
static bool fr_dlist_empty(fr_dlist_head_t const *list_head)
Check whether a list has any items.
Definition: dlist.h:501
static unlang_t * unlang_edit_to_generic(unlang_edit_t const *p)
Definition: edit_priv.h:45
map_list_t maps
Head of the map list.
Definition: edit_priv.h:33
unlang_t self
Definition: edit_priv.h:32
static unlang_edit_t * unlang_generic_to_edit(unlang_t const *p)
Cast a generic structure to the edit extension.
Definition: edit_priv.h:39
int unlang_interpret_push(request_t *request, unlang_t const *instruction, rlm_rcode_t default_rcode, bool do_next_sibling, bool top_frame)
Push a new frame onto the stack.
Definition: interpret.c:161
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:443
#define RWDEBUG(fmt,...)
Definition: log.h:361
#define RINDENT_SAVE(_x, _request)
Save indentation for later restoral.
Definition: log.h:388
#define RINDENT_RESTORE(_request, _x)
Definition: log.h:392
#define RPEDEBUG(fmt,...)
Definition: log.h:376
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:430
Definition: log.h:40
void unlang_register(int type, unlang_op_t *op)
Register an operation with the interpreter.
Definition: base.c:63
talloc_free(reap)
int fr_debug_lvl
Definition: log.c:43
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition: log.h:71
static char * stack[MAX_STACK]
Definition: radmin.c:158
fr_type_t
Definition: merged_model.c:80
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_OCTETS
Raw octets.
Definition: merged_model.c:84
@ FR_TYPE_GROUP
A grouping of other attributes.
Definition: merged_model.c:124
long int ssize_t
Definition: merged_model.c:24
fr_pair_t * fr_pair_parent(fr_pair_t const *vp)
Return a pointer to the parent pair.
Definition: pair.c:942
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition: pair.c:283
int fr_pair_list_copy(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from)
Duplicate a list of pairs.
Definition: pair.c:2319
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition: pair.c:1345
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition: pair.c:46
bool fr_pair_immutable(fr_pair_t const *vp)
Definition: pair.c:2280
fr_pair_t * fr_pair_copy(TALLOC_CTX *ctx, fr_pair_t const *vp)
Copy a single valuepair.
Definition: pair.c:489
fr_pair_list_t * fr_pair_parent_list(fr_pair_t const *vp)
Return a pointer to the parent pair list.
Definition: pair.c:927
fr_value_box_t * fr_pair_dcursor_nested_init(fr_dcursor_t *cursor, fr_dcursor_t *parent)
Initialises a special dcursor over another cursor which returns fr_pair_t, but we return fr_value_box...
Definition: pair.c:1298
fr_slen_t fr_pair_list_afrom_substr(fr_pair_parse_t const *root, fr_pair_parse_t *relative, fr_sbuff_t *in)
Parse a fr_pair_list_t from a substring.
Definition: pair_legacy.c:150
struct fr_pair_parse_s fr_pair_parse_t
TALLOC_CTX * ctx
Definition: pair_legacy.h:43
static bool done
Definition: radclient.c:80
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define RDEBUG(fmt,...)
Definition: radclient.h:53
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
@ RLM_MODULE_INVALID
The module considers the request invalid.
Definition: rcode.h:45
@ RLM_MODULE_OK
The module is OK, continue.
Definition: rcode.h:43
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition: rcode.h:42
@ RLM_MODULE_DISALLOW
Reject the request (user is locked out).
Definition: rcode.h:46
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition: rcode.h:41
@ RLM_MODULE_NOTFOUND
User not found.
Definition: rcode.h:47
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition: rcode.h:49
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
Definition: rcode.h:51
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition: rcode.h:48
@ RLM_MODULE_HANDLED
The module handled the request, so stop.
Definition: rcode.h:44
fr_dict_attr_t const * request_attr_request
Definition: request.c:45
fr_dict_attr_t const * request_attr_control
Definition: request.c:47
fr_dict_attr_t const * request_attr_state
Definition: request.c:48
fr_dict_attr_t const * request_attr_reply
Definition: request.c:46
#define FR_SBUFF_IN(_start, _len_or_end)
Set of parsing rules for *unescape_until functions.
Definition: merged_model.c:163
static int16_t tmpl_attr_tail_num(tmpl_t const *vpt)
Return the last attribute reference's attribute number.
Definition: tmpl.h:896
#define tmpl_is_xlat(vpt)
Definition: tmpl.h:215
static fr_slen_t vpt
Definition: tmpl.h:1272
#define tmpl_value(_tmpl)
Definition: tmpl.h:948
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition: tmpl.h:812
#define tmpl_is_attr(vpt)
Definition: tmpl.h:213
#define NUM_ALL
Definition: tmpl.h:400
#define tmpl_is_exec(vpt)
Definition: tmpl.h:216
#define tmpl_xlat(_tmpl)
Definition: tmpl.h:941
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, char const *name, tmpl_rules_t const *rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
@ TMPL_TYPE_ATTR
Reference to one or more attributes.
Definition: tmpl.h:146
@ TMPL_TYPE_XLAT
Pre-parsed xlat expansion.
Definition: tmpl.h:150
@ TMPL_TYPE_EXEC
Callout to an external script or program.
Definition: tmpl.h:154
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition: tmpl.h:142
#define tmpl_is_data(vpt)
Definition: tmpl.h:211
void tmpl_debug(tmpl_t const *vpt)
#define NUM_UNSPEC
Definition: tmpl.h:399
int pair_append_by_tmpl_parent(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, tmpl_t const *vpt, bool skip_list))
Allocate and insert a leaf vp from a tmpl_t, building the parent vps if needed.
Definition: tmpl_eval.c:974
Optional arguments passed to vp_tmpl functions.
Definition: tmpl.h:341
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
fr_aka_sim_id_type_t type
fr_pair_t * vp
Value pair map.
Definition: map.h:77
fr_token_t op
The operator that controls insertion of the dst attribute.
Definition: map.h:82
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition: map.h:78
map_list_t child
parent map, for nested ones
Definition: map.h:89
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition: map.h:79
Stores an attribute, a value and various bits of other data.
Definition: pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition: pair.h:69
int unlang_tmpl_push(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *tmpl, unlang_tmpl_args_t *args)
Push a tmpl onto the stack for evaluation.
Definition: tmpl.c:259
void tmpl_dcursor_clear(tmpl_dcursor_ctx_t *cc)
Clear any temporary state allocations.
Definition: tmpl_dcursor.c:495
#define tmpl_dcursor_build_init(_err, _ctx, _cc, _cursor, _request, _vpt, _build, _uctx)
Definition: tmpl_dcursor.h:103
#define tmpl_dcursor_init(_err, _ctx, _cc, _cursor, _request, _vpt)
Definition: tmpl_dcursor.h:100
Maintains state between cursor calls.
Definition: tmpl_dcursor.h:62
goto success
Definition: tmpl_eval.c:1455
char const * fr_tokens[T_TOKEN_LAST]
Definition: token.c:78
const bool fr_comparison_op[T_TOKEN_LAST]
Definition: token.c:198
@ T_OP_SUB_EQ
Definition: token.h:70
@ T_SINGLE_QUOTED_STRING
Definition: token.h:122
@ T_OP_EQ
Definition: token.h:83
@ T_OP_SET
Definition: token.h:84
@ T_OP_ADD_EQ
Definition: token.h:69
@ T_DOUBLE_QUOTED_STRING
Definition: token.h:121
fr_edit_list_t * unlang_interpret_edit_list(request_t *request)
Definition: transaction.c:100
static fr_event_list_t * el
TALLOC_CTX * ctx
Definition: edit.c:69
fr_pair_t * vp_parent
parent of the current VP
Definition: edit.c:56
int(* unlang_edit_expand_t)(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:64
static int check_rhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:963
static unlang_action_t unlang_edit_state_init(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Execute an update block.
Definition: edit.c:1654
tmpl_t const * vpt
expanded tmpl
Definition: edit.c:52
edit_map_t * parent
Definition: edit.c:70
struct unlang_frame_state_edit_s unlang_frame_state_edit_t
Definition: edit.c:62
static int expand_rhs_list(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1036
static void edit_state_init_internal(request_t *request, unlang_frame_state_edit_t *state, fr_edit_list_t *el, map_list_t const *map_list)
Definition: edit.c:1607
static void edit_debug_attr_vp(request_t *request, fr_pair_t *vp, map_t const *map)
Definition: edit.c:188
static unlang_action_t process_edit(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Apply a map (recursively) to a request.
Definition: edit.c:1543
map_list_t const * map_list
Definition: edit.c:73
fr_pair_list_t pair_list
for structural attributes
Definition: edit.c:57
void unlang_edit_init(void)
Definition: edit.c:1731
static int expand_lhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1512
unlang_edit_expand_t func
for process state
Definition: edit.c:81
static int next_map(UNUSED request_t *request, UNUSED unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:940
tmpl_t * to_free
tmpl to free.
Definition: edit.c:53
static int check_lhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1330
rindent_t indent
Definition: edit.c:94
int unlang_edit_push(request_t *request, bool *success, fr_edit_list_t *el, map_list_t const *map_list)
Push a map onto the stack for edit evaluation.
Definition: edit.c:1682
#define XDEBUG(...)
fr_pair_t editing
Definition: edit.c:41
static int check_lhs_value(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1144
#define RDEBUG_ASSIGN(_name, _op, _box)
Definition: edit.c:46
static int check_lhs_nested(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1290
static int expand_rhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1115
#define MAP_INFO
Definition: edit.c:100
static int expanded_lhs_value(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1210
static int apply_edits_to_list(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:269
static int edit_delete_lhs(request_t *request, edit_map_t *current, bool delete)
Definition: edit.c:533
map_t const * map
the map to evaluate
Definition: edit.c:74
static fr_pair_t * edit_list_pair_build(fr_pair_t *parent, fr_dcursor_t *cursor, fr_dict_attr_t const *da, void *uctx)
Simple pair building callback for use with tmpl_dcursors.
Definition: edit.c:895
bool temporary_pair_list
Definition: edit.c:76
fr_edit_list_t * el
edit list
Definition: edit.c:67
fr_edit_list_t * el
edit list
Definition: edit.c:90
static bool pair_is_editable(request_t *request, fr_pair_t *vp)
Definition: edit.c:517
static int tmpl_attr_from_result(TALLOC_CTX *ctx, map_t const *map, edit_result_t *out, request_t *request)
Definition: edit.c:107
static void edit_debug_attr_list(request_t *request, fr_pair_list_t const *list, map_t const *map)
Definition: edit.c:223
bool create
whether we need to create the VP
Definition: edit.c:54
fr_value_box_list_t result
result of expansion
Definition: edit.c:51
unlang_edit_expand_t check_lhs
for special cases
Definition: edit.c:82
static int tmpl_to_values(TALLOC_CTX *ctx, edit_result_t *out, request_t *request, tmpl_t const *vpt)
Definition: edit.c:152
static int edit_create_lhs_vp(request_t *request, TALLOC_CTX *ctx, edit_map_t *current)
Definition: edit.c:238
static int expanded_lhs_attribute(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:1492
edit_map_t * child
Definition: edit.c:71
bool * success
whether or not the edit succeeded
Definition: edit.c:91
unlang_edit_expand_t expanded_lhs
for special cases
Definition: edit.c:83
edit_map_t * current
what we're currently doing.
Definition: edit.c:96
static int apply_edits_to_leaf(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition: edit.c:619
#define DECLARE(_x)
Definition: edit.c:928
edit_result_t rhs
RHS child entries.
Definition: edit.c:79
edit_result_t lhs
LHS child entries.
Definition: edit.c:78
fr_pair_t * vp
VP referenced by tmpl.
Definition: edit.c:55
edit_map_t first
Definition: edit.c:97
State of an edit block.
Definition: edit.c:89
int unlang_xlat_push(TALLOC_CTX *ctx, bool *p_success, fr_value_box_list_t *out, request_t *request, xlat_exp_head_t const *xlat, bool top_frame)
Push a pre-compiled xlat onto the stack for evaluation.
Definition: xlat.c:274
static void repeatable_clear(unlang_stack_frame_t *frame)
Definition: unlang_priv.h:351
void * state
Stack frame specialisations.
Definition: unlang_priv.h:296
#define UNLANG_NEXT_STOP
Definition: unlang_priv.h:92
@ UNLANG_TYPE_EDIT
edit VPs in place. After 20 years!
Definition: unlang_priv.h:76
static void frame_repeat(unlang_stack_frame_t *frame, unlang_process_t process)
Mark the current stack frame up for repeat, and set a new process function.
Definition: unlang_priv.h:518
unlang_t const * instruction
The unlang node we're evaluating.
Definition: unlang_priv.h:281
static void repeatable_set(unlang_stack_frame_t *frame)
Definition: unlang_priv.h:345
unlang_type_t type
The specialisation of this node.
Definition: unlang_priv.h:117
An unlang operation.
Definition: unlang_priv.h:204
A node in a graph of unlang_op_t (s) that we execute.
Definition: unlang_priv.h:112
Our interpreter stack, as distinct from the C stack.
Definition: unlang_priv.h:280
An unlang stack associated with a request.
Definition: unlang_priv.h:314
int fr_edit_list_apply_list_assignment(fr_edit_list_t *el, fr_pair_t *dst, fr_token_t op, fr_pair_list_t *src, bool copy)
Apply operators to lists.
Definition: edit.c:1487
int fr_edit_list_pair_delete(fr_edit_list_t *el, fr_pair_list_t *list, fr_pair_t *vp)
Delete a VP.
Definition: edit.c:575
void fr_edit_list_abort(fr_edit_list_t *el)
Abort the entries in an edit list.
Definition: edit.c:194
int fr_edit_list_free_pair_children(fr_edit_list_t *el, fr_pair_t *vp)
Free children of a structural pair.
Definition: edit.c:709
int fr_edit_list_apply_pair_assignment(fr_edit_list_t *el, fr_pair_t *vp, fr_token_t op, fr_value_box_t const *in)
Apply operators to pairs.
Definition: edit.c:976
fr_edit_list_t * fr_edit_list_alloc(TALLOC_CTX *ctx, int hint, fr_edit_list_t *parent)
Allocate an edit list.
Definition: edit.c:790
Track a series of edits.
Definition: edit.c:102
#define fr_edit_list_insert_pair_tail(_el, _list, _vp)
Definition: edit.h:51
fr_pair_t * fr_pair_list_next(fr_pair_list_t const *list, fr_pair_t const *item))
Get the next item in a valuepair list after a specific entry.
Definition: pair_inline.c:70
#define PAIR_VERIFY(_x)
Definition: pair.h:191
#define fr_pair_list_foreach(_list_head, _iter)
Iterate over the contents of a fr_pair_list_t.
Definition: pair.h:261
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
Definition: pair_inline.c:113
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
Definition: pair_inline.c:182
static fr_slen_t parent
Definition: pair.h:851
#define RETRY_INIT
Definition: retry.h:39
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition: strerror.c:577
char const * fr_strerror(void)
Get the last library error.
Definition: strerror.c:554
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition: types.h:433
#define fr_type_is_group(_x)
Definition: types.h:355
#define fr_type_is_variable_size(_x)
Definition: types.h:367
#define fr_type_is_structural(_x)
Definition: types.h:371
#define fr_type_is_fixed_size(_x)
Definition: types.h:366
#define FR_TYPE_STRUCTURAL
Definition: types.h:296
#define fr_type_is_null(_x)
Definition: types.h:326
#define fr_type_is_leaf(_x)
Definition: types.h:372
#define FR_TYPE_NUMERIC
Definition: types.h:286
ssize_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules, bool tainted)
Definition: value.c:5315
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition: value.c:3352
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
int fr_value_box_cmp_op(fr_token_t op, fr_value_box_t const *a, fr_value_box_t const *b)
Compare two attributes using an operator.
Definition: value.c:929
fr_sbuff_unescape_rules_t * fr_value_unescape_by_quote[T_TOKEN_LAST]
Definition: value.c:336
int fr_value_box_list_concat_in_place(TALLOC_CTX *ctx, fr_value_box_t *out, fr_value_box_list_t *list, fr_type_t type, fr_value_box_list_action_t proc_action, bool flatten, size_t max_size)
Concatenate a list of value boxes.
Definition: value.c:5777
@ FR_VALUE_BOX_LIST_FREE
Definition: value.h:221
@ FR_VALUE_BOX_LIST_FREE_BOX
Free each processed box.
Definition: value.h:218
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition: value.h:621
static fr_slen_t data
Definition: value.h:1265
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition: value.h:632
static size_t char ** out
Definition: value.h:997