Coverage Report

Created: 2026-03-31 06:21

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