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