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