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