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/server/pairmove.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: d945598cf34745f34e3849eb7601dc69802d83d9 $
19
 *
20
 * @file src/lib/server/pairmove.c
21
 * @brief Old style mapping code
22
 *
23
 * @copyright 2007 The FreeRADIUS server project
24
 * @copyright 2007 Alan DeKok (aland@deployingradius.com)
25
 */
26
RCSID("$Id: d945598cf34745f34e3849eb7601dc69802d83d9 $")
27
28
#include <freeradius-devel/server/paircmp.h>
29
#include <freeradius-devel/server/pairmove.h>
30
#include <freeradius-devel/server/tmpl_dcursor.h>
31
32
#include <freeradius-devel/util/debug.h>
33
#include <freeradius-devel/util/calc.h>
34
#include <freeradius-devel/util/pair_legacy.h>
35
36
#include <freeradius-devel/protocol/radius/rfc2865.h>
37
#include <freeradius-devel/protocol/freeradius/freeradius.internal.h>
38
39
/*
40
 *  @fixme - integrate this with the code calling it, so that we
41
 *  only fr_pair_list_copy() those attributes that we're really going to
42
 *  use.
43
 */
44
void radius_pairmove(request_t *request, fr_pair_list_t *to, fr_pair_list_t *from)
45
0
{
46
0
  int   i, j, count, to_count, tailto;
47
0
  fr_pair_t *from_vp, *next_from, *to_vp, *next_to = NULL;
48
0
  fr_pair_list_t  append, prepend;
49
0
  bool    *edited = NULL;
50
0
  bool    *deleted = NULL;
51
52
  /*
53
   *  Set up arrays for editing, to remove some of the
54
   *  O(N^2) dependencies.  These record which elements in
55
   *  the "to" list have been either edited or marked for
56
   *  deletion.
57
   *
58
   *  It also means that the operators apply ONLY to the
59
   *  attributes in the original list.
60
   *
61
   *  Also, the previous implementation did NOT implement
62
   *  "-=" correctly.  If two of the same attributes existed
63
   *  in the "to" list, and you tried to subtract something
64
   *  matching the *second* value, then the fr_pair_delete_by_da()
65
   *  function was called, and the *all* attributes of that
66
   *  number were deleted.  With this implementation, only
67
   *  the matching attributes are deleted.
68
   */
69
70
0
  fr_pair_list_init(&append);
71
0
  fr_pair_list_init(&prepend);
72
73
0
  to_count = fr_pair_list_num_elements(to);
74
0
  tailto = to_count;
75
0
  edited = talloc_zero_array(request, bool, to_count);
76
0
  deleted = talloc_zero_array(request, bool, to_count);
77
78
0
  count = to_count + fr_pair_list_num_elements(from);
79
80
0
  RDEBUG4("::: FROM %ld TO %d MAX %d", fr_pair_list_num_elements(from), to_count, count);
81
82
  /*
83
   *  Now that we have the lists initialized, start working
84
   *  over them.
85
   */
86
0
  for (i = 0, from_vp = fr_pair_list_head(from); from_vp; i++, from_vp = next_from) {
87
0
    int found;
88
    /* Find the next from pair before any manipulation happens */
89
0
    next_from = fr_pair_list_next(from, from_vp);
90
91
0
    RDEBUG4("::: Examining %s", from_vp->da->name);
92
93
    /*
94
     *  Attribute should be appended, OR the "to" list
95
     *  is empty, and we're supposed to replace or
96
     *  "add if not existing".
97
     */
98
0
    if (from_vp->op == T_OP_ADD_EQ) goto do_append;
99
100
    /*
101
     *  The attribute needs to be prepended to the "to"
102
     *  list - store it in the prepend list
103
     */
104
105
0
    if (from_vp->op == T_OP_PREPEND) {
106
0
      RDEBUG4("::: PREPENDING %s FROM %d", from_vp->da->name, i);
107
0
      fr_pair_remove(from, from_vp);
108
0
      fr_pair_prepend(&prepend, from_vp);
109
0
      from_vp->op = T_OP_EQ;
110
0
      continue;
111
0
    }
112
0
    found = false;
113
0
    j = 0;
114
0
    for (to_vp = fr_pair_list_head(to); to_vp; to_vp = next_to, j++) {
115
0
      next_to = fr_pair_list_next(to, to_vp);
116
0
      if (edited[j] || deleted[j] || !from_vp) continue;
117
118
      /*
119
       *  Attributes aren't the same, skip them.
120
       */
121
0
      if (from_vp->da != to_vp->da) {
122
0
        continue;
123
0
      }
124
125
      /*
126
       *  We don't use a "switch" statement here
127
       *  because we want to break out of the
128
       *  "for" loop over 'j' in most cases.
129
       */
130
131
      /*
132
       *  Over-write the FIRST instance of the
133
       *  matching attribute name.  We free the
134
       *  one in the "to" list, and move over
135
       *  the one in the "from" list.
136
       */
137
0
      if (from_vp->op == T_OP_SET) {
138
0
        RDEBUG4("::: OVERWRITING %s FROM %d TO %d",
139
0
               to_vp->da->name, i, j);
140
0
        fr_pair_remove(from, from_vp);
141
0
        fr_pair_replace(to, to_vp, from_vp);
142
0
        from_vp = NULL;
143
0
        edited[j] = true;
144
0
        break;
145
0
      }
146
147
      /*
148
       *  Add the attribute only if it does not
149
       *  exist... but it exists, so we stop
150
       *  looking.
151
       */
152
0
      if (from_vp->op == T_OP_EQ) {
153
0
        found = true;
154
0
        break;
155
0
      }
156
157
      /*
158
       *  Delete every attribute, independent
159
       *  of its value.
160
       */
161
0
      if (from_vp->op == T_OP_CMP_FALSE) {
162
0
        goto delete;
163
0
      }
164
165
      /*
166
       *  Delete all matching attributes from
167
       *  "to"
168
       */
169
0
      if ((from_vp->op == T_OP_SUB_EQ) ||
170
0
          (from_vp->op == T_OP_CMP_EQ) ||
171
0
          (from_vp->op == T_OP_LE) ||
172
0
          (from_vp->op == T_OP_GE)) {
173
0
        int rcode;
174
0
        int old_op = from_vp->op;
175
        /*
176
         *  Check for equality.
177
         */
178
0
        from_vp->op = T_OP_CMP_EQ;
179
180
        /*
181
         *  If equal, delete the one in
182
         *  the "to" list.
183
         */
184
0
        rcode = paircmp_pairs(NULL, from_vp, to_vp);
185
186
        /*
187
         *  We may want to do more
188
         *  subtractions, so we re-set the
189
         *  operator back to it's original
190
         *  value.
191
         */
192
0
        from_vp->op = old_op;
193
194
0
        switch (old_op) {
195
0
        case T_OP_CMP_EQ:
196
0
          if (rcode != 0) goto delete;
197
0
          break;
198
199
0
        case T_OP_SUB_EQ:
200
0
          if (rcode == 0) {
201
0
          delete:
202
0
            RDEBUG4("::: DELETING %s FROM %d TO %d",
203
0
                   from_vp->da->name, i, j);
204
            /*
205
             *  Mark that this will be deleted
206
             */
207
0
            deleted[j] = true;
208
0
          }
209
0
          break;
210
211
          /*
212
           *  Enforce <=.  If it's
213
           *  >, replace it.
214
           */
215
0
        case T_OP_LE:
216
0
          if (rcode > 0) {
217
0
            RDEBUG4("::: REPLACING %s FROM %d TO %d",
218
0
                   from_vp->da->name, i, j);
219
0
            goto replace;
220
0
          }
221
0
          break;
222
223
0
        case T_OP_GE:
224
0
          if (rcode < 0) {
225
0
            RDEBUG4("::: REPLACING %s FROM %d TO %d",
226
0
                   from_vp->da->name, i, j);
227
0
          replace:
228
0
            fr_pair_remove(from, from_vp);
229
0
            fr_pair_replace(to, to_vp, from_vp);
230
0
            from_vp = NULL;
231
0
            edited[j] = true;
232
0
          }
233
0
          break;
234
0
        }
235
236
0
        continue;
237
0
      }
238
239
0
      fr_assert(0 == 1); /* panic! */
240
0
    }
241
242
    /*
243
     *  We were asked to add it if it didn't exist,
244
     *  and it doesn't exist.  Move it over to the
245
     *  tail of the "to" list, UNLESS it was already
246
     *  moved by another operator.
247
     */
248
0
    if (!found && from_vp) {
249
0
      if ((from_vp->op == T_OP_EQ) ||
250
0
          (from_vp->op == T_OP_LE) ||
251
0
          (from_vp->op == T_OP_GE) ||
252
0
          (from_vp->op == T_OP_SET)) {
253
0
      do_append:
254
0
        RDEBUG4("::: APPENDING %s FROM %d TO %d",
255
0
               from_vp->da->name, i, tailto++);
256
0
        fr_pair_remove(from, from_vp);
257
0
        fr_pair_append(&append, from_vp);
258
0
        from_vp->op = T_OP_EQ;
259
0
      }
260
0
    }
261
0
  }
262
263
  /*
264
   *  Delete remaining attributes in the "from" list.
265
   */
266
0
  fr_pair_list_free(from);
267
268
0
  RDEBUG4("::: TO in %d out %d", to_count, tailto);
269
270
  /*
271
   *  Delete any "to" items marked for deletion
272
   */
273
274
0
  i = 0;
275
0
  for (to_vp = fr_pair_list_head(to); to_vp; to_vp = next_to, i++) {
276
0
    next_to = fr_pair_list_next(to, to_vp);
277
278
0
    if (deleted[i]) {
279
0
      fr_pair_remove(to, to_vp);
280
0
      continue;
281
0
    }
282
283
0
    RDEBUG4("::: to[%d] = %s", i, to_vp->da->name);
284
285
    /*
286
     *  Mash the operator to a simple '='.  The
287
     *  operators in the "to" list aren't used for
288
     *  anything.  BUT they're used in the "detail"
289
     *  file and debug output, where we don't want to
290
     *  see the operators.
291
     */
292
0
    to_vp->op = T_OP_EQ;
293
0
  }
294
295
  /*
296
   *  Now prepend any items in the "prepend" list to
297
   *  the head of the "to" list.
298
   */
299
0
  fr_pair_list_prepend(to, &prepend);
300
301
  /*
302
   *  And finally add in the attributes we're appending to
303
   *  the tail of the "to" list.
304
   */
305
0
  fr_pair_list_append(to, &append);
306
307
0
  fr_assert(request->packet != NULL);
308
309
0
  talloc_free(edited);
310
0
  talloc_free(deleted);
311
0
}
312
313
static int radius_legacy_map_to_vp(request_t *request, fr_pair_t *parent, map_t const *map)
314
0
{
315
0
  fr_pair_t *vp;
316
0
  fr_dict_attr_t const *da;
317
0
  fr_value_box_t *box, *to_free = NULL;
318
319
0
  RDEBUG("  %s %s %s", map->lhs->name, fr_tokens[map->op], map->rhs->name);
320
321
0
  da = tmpl_attr_tail_da(map->lhs);
322
0
  fr_assert(fr_type_is_leaf(da->type));
323
324
0
  if (tmpl_is_data(map->rhs)) {
325
0
    box = tmpl_value(map->rhs);
326
327
0
  } else if (tmpl_is_attr(map->rhs)) {
328
0
    fr_pair_t *rhs;
329
330
0
    if (tmpl_find_vp(&rhs, request, map->rhs) < 0) return -1;
331
332
0
    if (rhs->vp_type != da->type) {
333
0
      fr_strerror_const("Incompatible data types");
334
0
      return -1;
335
0
    }
336
337
0
    box = &rhs->data;
338
339
0
  } else if (tmpl_is_xlat(map->rhs)) {
340
0
    if (tmpl_aexpand(parent, &to_free, request, map->rhs, NULL, NULL) < 0) return -1;
341
342
0
    box = to_free;
343
344
0
  } else {
345
0
    fr_strerror_const("Unknown RHS");
346
0
    return -1;
347
0
  }
348
349
0
  if (fr_pair_append_by_da(parent, &vp, &parent->vp_group, da) < 0) return -1;
350
351
0
  if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) {
352
0
    TALLOC_FREE(to_free);
353
0
    return -1;
354
0
  }
355
356
0
  TALLOC_FREE(to_free);
357
0
  return 0;
358
0
}
359
360
361
static int CC_HINT(nonnull) radius_legacy_map_apply_structural(request_t *request, map_t const *map, fr_pair_t *vp)
362
0
{
363
0
  fr_value_box_t  *box, *to_free = NULL;
364
365
  /*
366
   *  No RHS map, but we have children.  Create them, and add them to the list.
367
   */
368
0
  if (!map->rhs) {
369
0
    map_t *child;
370
371
    /*
372
     *  Convert the child maps to VPs.  We know that
373
     *  we just created the pair, so there's no reason
374
     *  to apply operators to the children.
375
     */
376
0
    for (child = map_list_next(&map->child, NULL);
377
0
         child != NULL;
378
0
         child = map_list_next(&map->child, child)) {
379
0
      fr_assert(child->op == T_OP_EQ);
380
0
      if (radius_legacy_map_to_vp(request, vp, child) < 0) return -1;
381
0
    }
382
383
0
    return 0;
384
0
  }
385
386
  /*
387
   *  Copy an existing attribute.
388
   */
389
0
  if (tmpl_is_attr(map->rhs)) {
390
0
    fr_pair_t *rhs;
391
392
0
    if (tmpl_find_vp(&rhs, request, map->rhs) < 0) return -1;
393
394
0
    if (rhs->vp_type != vp->vp_type) {
395
0
      fr_strerror_const("Incompatible data types");
396
0
      return -1;
397
0
    }
398
399
0
    if (rhs == vp) {
400
0
      fr_strerror_const("Invalid self-reference");
401
0
      return -1;
402
0
    }
403
404
0
    return fr_pair_list_copy(vp, &vp->vp_group, &rhs->vp_group);
405
0
  }
406
407
  /*
408
   *  RHS is a string or an xlat expansion.
409
   */
410
0
  if (tmpl_is_data(map->rhs)) {
411
0
    box = tmpl_value(map->rhs);
412
413
0
  } else if (tmpl_is_xlat(map->rhs)) {
414
0
    if (tmpl_aexpand(request, &to_free, request, map->rhs, NULL, NULL) < 0) return -1;
415
416
0
    box = to_free;
417
418
0
  } else {
419
0
    fr_strerror_const("Unknown RHS");
420
0
    return -1;
421
0
  }
422
423
0
  if (box->type != FR_TYPE_STRING) {
424
0
    fr_strerror_const("Cannot parse child list");
425
0
    TALLOC_FREE(to_free);
426
0
    return -1;
427
0
  }
428
429
  /*
430
   *  If there's no value, just leave the list alone.
431
   *
432
   *  Otherwise parse the children in the context of the parent.
433
   */
434
0
  if (box->vb_strvalue[0]) {
435
0
    fr_pair_parse_t root, relative;
436
437
    /*
438
     *  Parse the string as a list of pairs.
439
     */
440
0
    root = (fr_pair_parse_t) {
441
0
      .ctx = vp,
442
0
        .da = vp->da,
443
0
        .list = &vp->vp_group,
444
0
        .dict = vp->da->dict,
445
0
        .internal = fr_dict_internal(),
446
0
        .allow_compare = false,
447
0
        .tainted = box->tainted,
448
0
    };
449
0
    relative = (fr_pair_parse_t) { };
450
451
0
    if (fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(box->vb_strvalue, box->vb_length)) < 0) {
452
0
      RPEDEBUG("Failed parsing string '%pV' as attribute list", box);
453
0
      TALLOC_FREE(to_free);
454
0
      return -1;
455
0
    }
456
0
  }
457
458
0
  TALLOC_FREE(to_free);
459
0
  return 0;
460
0
}
461
462
typedef struct {
463
  fr_edit_list_t  *el;
464
  fr_pair_t *vp;  /* the one we created */
465
} legacy_pair_build_t;
466
467
/** Build the relevant pairs at each level.
468
 *
469
 *  See edit_list_pair_build() for similar code.
470
 */
471
static fr_pair_t *legacy_pair_build(fr_pair_t *parent, fr_dcursor_t *cursor, fr_dict_attr_t const *da, void *uctx)
472
0
{
473
0
  fr_pair_t *vp;
474
0
  legacy_pair_build_t *lp = uctx;
475
476
0
  vp = fr_pair_afrom_da(parent, da);
477
0
  if (!vp) return NULL;
478
479
0
  if (fr_edit_list_insert_pair_tail(lp->el, &parent->vp_group, vp) < 0) {
480
0
    talloc_free(vp);
481
0
    return NULL;
482
0
  }
483
484
  /*
485
   *  Tell the cursor that we appended a pair.  This
486
   *  function only gets called when we've ran off of the
487
   *  end of the list, and can't find the thing we're
488
   *  looking for.  So it's safe at set the current one
489
   *  here.
490
   *
491
   *  @todo - mainly only because we don't allow creating
492
   *  foo[4] when there's <3 matching entries.  i.e. the
493
   *  "arrays" here are really lists, so we can't create
494
   *  "holes" in the list.
495
   */
496
0
  fr_dcursor_set_current(cursor, vp);
497
498
0
  lp->vp = vp;
499
500
0
  return vp;
501
0
}
502
503
504
/** Move a map using the operators from the old pairmove functionality.
505
 *
506
 */
507
int radius_legacy_map_apply(request_t *request, map_t const *map, fr_edit_list_t *el)
508
0
{
509
0
  int16_t     num;
510
0
  int     err, rcode;
511
0
  bool      added = false;
512
0
  fr_pair_t   *vp = NULL, *next, *parent;
513
0
  fr_dict_attr_t const  *da;
514
0
  fr_pair_list_t    *list;
515
0
  TALLOC_CTX    *ctx;
516
0
  fr_value_box_t    *to_free = NULL;
517
0
  fr_value_box_t const  *box;
518
0
  tmpl_dcursor_ctx_t  cc;
519
0
  fr_dcursor_t    cursor;
520
521
  /*
522
   *  Find out where this attribute exists, or should exist.
523
   */
524
0
  tmpl_pair_list_and_ctx(ctx, list, request, tmpl_request(map->lhs), tmpl_list(map->lhs));
525
0
  if (!ctx) return -1; /* no request or list head exists */
526
527
0
  da = tmpl_attr_tail_da(map->lhs);
528
529
  /*
530
   *  These operations are the same for both leaf and structural types.
531
   */
532
0
  switch (map->op) {
533
0
  case T_OP_EQ:
534
0
    if (tmpl_find_vp(&vp, request, map->lhs) < -1) return -1;
535
0
    if (vp) return 0;
536
0
    goto add;
537
538
0
  case T_OP_SET:
539
    /*
540
     *  Set a value.  Note that we might do
541
     *
542
     *    &foo[1] := 1
543
     *
544
     *  In which case we don't want to delete the attribute, we just want to replace its
545
     *  value.
546
     *
547
     *  @todo - we can't do &foo[*].bar[*].baz = 1, as that's an implicit cursor, and we don't
548
     *  do that.
549
     */
550
0
    num = tmpl_attr_tail_num(map->lhs);
551
0
    if (num == NUM_COUNT) {
552
0
      fr_strerror_const("Invalid count in attribute reference");
553
0
      return -1;
554
0
    }
555
556
0
    vp = tmpl_dcursor_init(&err, ctx, &cc, &cursor, request, map->lhs);
557
558
    /*
559
     *  We're editing a specific number.  It must exist, otherwise the edit does nothing.
560
     */
561
0
    if ((num >= 0) || (num == NUM_LAST)) {
562
0
      if (!vp) return 0;
563
564
0
      if (fr_type_is_leaf(vp->vp_type)) {
565
0
        if (fr_edit_list_save_pair_value(el, vp) < 0) return -1;
566
0
      } else {
567
0
        fr_assert(fr_type_is_structural(vp->vp_type));
568
569
0
        if (fr_edit_list_free_pair_children(el, vp) < 0) return -1;
570
0
      }
571
0
      break;
572
0
    }
573
574
    /*
575
     *  We don't delete the main lists, we just modify their contents.
576
     */
577
0
    if (request_attr_is_list(da)) {
578
0
      fr_assert(vp != NULL);
579
580
0
      if (fr_edit_list_free_pair_children(el, vp) < 0) return -1;
581
0
      break;
582
0
    }
583
584
0
    if (!vp) goto add;
585
586
    /*
587
     *  Delete the first attribute we found.
588
     */
589
0
    parent = fr_pair_parent(vp);
590
0
    fr_assert(parent != NULL);
591
592
0
    if (fr_edit_list_pair_delete(el, &parent->vp_group, vp) < 0) return -1;
593
0
    tmpl_dcursor_clear(&cc);
594
595
    /*
596
     *  Delete all existing attributes.  Note that we re-initialize the cursor every time,
597
     *  because creating "foo := baz" means deleting ALL existing "foo".  But we can't use
598
     *  the tmpl as a cursor, because the tmpl containst NUM_UNSPEC, and the cursor needs
599
     *  NUM_ALL.  So we have to delete all existing attributes, and then add a new one.
600
     */
601
0
    while (true) {
602
0
      vp = tmpl_dcursor_init(&err, ctx, &cc, &cursor, request, map->lhs);
603
0
      if (!vp) break;
604
605
0
      parent = fr_pair_parent(vp);
606
0
      fr_assert(parent != NULL);
607
608
0
      if (fr_edit_list_pair_delete(el, &parent->vp_group, vp) < 0) return -1;
609
0
      tmpl_dcursor_clear(&cc);
610
0
    }
611
0
    FALL_THROUGH;
612
613
0
  case T_OP_ADD_EQ:
614
0
  add:
615
0
  {
616
0
    legacy_pair_build_t lp = (legacy_pair_build_t) {
617
0
      .el = el,
618
0
      .vp = NULL,
619
0
    };
620
621
0
    fr_strerror_clear();
622
0
    vp = tmpl_dcursor_build_init(&err, ctx, &cc, &cursor, request, map->lhs, legacy_pair_build, &lp);
623
0
    tmpl_dcursor_clear(&cc);
624
0
    if (!vp) {
625
0
      RWDEBUG("Failed creating attribute %s", map->lhs->name);
626
0
      return -1;
627
0
    }
628
629
    /*
630
     *  If we're adding and one already exists, create a new one in the same context.
631
     */
632
0
    if ((map->op == T_OP_ADD_EQ) && !lp.vp) {
633
0
      parent = fr_pair_parent(vp);
634
0
      fr_assert(parent != NULL);
635
636
0
      MEM(vp = fr_pair_afrom_da(parent, da));
637
0
      if (fr_edit_list_insert_pair_tail(el, &parent->vp_group, vp) < 0) return -1;
638
0
    }
639
640
0
    added = true;
641
0
  }
642
0
    break;
643
644
0
  case T_OP_LE:   /* replace if not <= */
645
0
  case T_OP_GE:   /* replace if not >= */
646
0
    if (fr_type_is_structural(da->type)) goto invalid_operator;
647
648
0
    if (tmpl_find_vp(&vp, request, map->lhs) < -1) return -1;
649
0
    if (!vp) goto add;
650
0
    break;
651
652
0
  case T_OP_SUB_EQ: /* delete if match, otherwise ignore */
653
0
    if (fr_type_is_structural(da->type)) {
654
0
    invalid_operator:
655
0
      fr_strerror_printf("Invalid operator '%s' for structural type", fr_type_to_str(da->type));
656
0
      return -1;
657
0
    }
658
659
0
    if (tmpl_find_vp(&vp, request, map->lhs) < -1) return -1;
660
0
    if (!vp) return 0;
661
0
    break;
662
663
0
  default:
664
0
    fr_strerror_printf("Invalid operator '%s'", fr_tokens[map->op]);
665
0
    return -1;
666
0
  }
667
668
0
  fr_assert(vp);
669
670
  /*
671
   *  We don't support operations on structural types.  Just creation, and assign values.
672
   *
673
   *  The code above has ensured that the structural type has been either saved or cleared via the
674
   *  edit list, so the next function doesn't need to do that.
675
   */
676
0
  if (fr_type_is_structural(tmpl_attr_tail_da(map->lhs)->type)) {
677
0
    fr_assert(added);
678
0
    return radius_legacy_map_apply_structural(request, map, vp);
679
0
  }
680
681
  /*
682
   *  We have now found the RHS.  Expand it.
683
   *
684
   *  Note that
685
   *
686
   *    &foo := %tolower(&foo)
687
   *
688
   *  works, as we save the value above in the T_OP_SET handler.  So we don't delete it.
689
   */
690
0
  if (tmpl_is_data(map->rhs)) {
691
0
    box = tmpl_value(map->rhs);
692
693
0
  } else if (tmpl_is_attr(map->rhs)) {
694
0
    fr_pair_t *rhs;
695
696
0
    if (tmpl_find_vp(&rhs, request, map->rhs) < 0) return -1;
697
698
0
    if (rhs->vp_type != da->type) {
699
0
      fr_strerror_const("Incompatible data types");
700
0
      return -1;
701
0
    }
702
703
0
    if (rhs == vp) {
704
0
      fr_strerror_const("Invalid self-reference");
705
0
      return -1;
706
0
    }
707
708
0
    box = &rhs->data;
709
710
0
  } else if (tmpl_is_xlat(map->rhs)) {
711
0
    if (tmpl_aexpand(ctx, &to_free, request, map->rhs, NULL, NULL) < 0) return -1;
712
713
0
    box = to_free;
714
715
0
  } else {
716
0
    fr_strerror_const("Unknown RHS");
717
0
    return -1;
718
0
  }
719
720
  /*
721
   *  We added a VP which hadn't previously existed.  Therefore just set the value and return.
722
   */
723
0
  if (added) {
724
0
    if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) {
725
0
    fail:
726
0
      TALLOC_FREE(to_free);
727
0
      return -1;
728
0
    }
729
730
0
    if (vp->da->flags.unsafe) fr_value_box_mark_unsafe(&vp->data);
731
0
    TALLOC_FREE(to_free);
732
0
    return 0;
733
0
  }
734
735
0
  while (vp) {
736
0
    next = fr_pair_find_by_da_nested(list, vp, da); /* could be deleted in the loop*/
737
738
0
    switch (map->op) {
739
0
    case T_OP_LE:   /* replace if not <= */
740
0
    case T_OP_GE:   /* replace if not >= */
741
0
      rcode = fr_value_box_cmp_op(map->op, &vp->data, box);
742
0
      if (rcode < 0) goto fail;
743
744
0
      if (rcode != 0) break;
745
746
0
      if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) goto fail;
747
0
      break;
748
749
0
    case T_OP_SUB_EQ: /* delete if match */
750
0
      rcode = fr_value_box_cmp_op(T_OP_CMP_EQ, &vp->data, box);
751
0
      if (rcode < 0) goto fail;
752
753
0
      if (rcode == 1) {
754
0
        fr_pair_list_t *parent_list = fr_pair_parent_list(vp);
755
756
0
              if (fr_edit_list_pair_delete(el, parent_list, vp) < 0) goto fail;
757
0
      }
758
0
      break;
759
760
0
    default:
761
0
      fr_assert(0);  /* should have been caught above */
762
0
      return -1;
763
0
    }
764
765
0
    vp = next;
766
0
  }
767
768
0
  TALLOC_FREE(to_free);
769
0
  return 0;
770
0
}
771
772
int radius_legacy_map_list_apply(request_t *request, map_list_t const *list, fr_edit_list_t *el)
773
0
{
774
0
  map_t const *map;
775
776
0
  for (map = map_list_head(list);
777
0
       map != NULL;
778
0
       map = map_list_next(list, map)) {
779
0
    RDEBUG2("%s %s %s", map->lhs->name, fr_tokens[map->op],
780
0
      map->rhs ? map->rhs->name : "{ ... }");
781
782
0
    if (radius_legacy_map_apply(request, map, el) < 0) {
783
0
      RPEDEBUG("Failed applying result");
784
0
      return -1;
785
0
    }
786
0
  }
787
788
0
  return 0;
789
0
}
790
791
int radius_legacy_map_cmp(request_t *request, map_t const *map)
792
0
{
793
0
  int     rcode;
794
0
  fr_pair_t   *vp;
795
0
  fr_value_box_t const  *box;
796
0
  fr_value_box_t    *to_free = NULL;
797
0
  fr_value_box_t    dst, str;
798
0
  fr_dcursor_t    cursor;
799
0
  tmpl_dcursor_ctx_t  cc;
800
801
0
  fr_assert(tmpl_is_attr(map->lhs));
802
0
  fr_assert(fr_comparison_op[map->op]);
803
804
0
  vp = tmpl_dcursor_init(NULL, request, &cc, &cursor, request, map->lhs);
805
806
0
  if (!vp) {
807
0
    tmpl_dcursor_clear(&cc);
808
0
    if (map->op == T_OP_CMP_FALSE) return true;
809
0
    return 0;
810
0
  }
811
812
0
  if (map->op == T_OP_CMP_TRUE){
813
0
    tmpl_dcursor_clear(&cc);
814
0
    return false;
815
0
  }
816
817
0
  if (fr_type_is_structural(vp->vp_type)) {
818
0
    fr_strerror_const("Invalid comparison for structural type");
819
0
  error:
820
0
    tmpl_dcursor_clear(&cc);
821
0
    return -1;
822
0
  }
823
824
0
  if (tmpl_is_data(map->rhs)) {
825
0
    box = tmpl_value(map->rhs);
826
827
0
  } else if (tmpl_is_attr(map->rhs)) {
828
0
    fr_pair_t *rhs;
829
830
0
    if (tmpl_find_vp(&rhs, request, map->rhs) < 0) goto error;
831
832
0
    box = &rhs->data;
833
834
0
  } else if (tmpl_contains_xlat(map->rhs)) {
835
0
    if (tmpl_aexpand(request, &to_free, request, map->rhs, NULL, NULL) < 0) goto error;
836
837
0
    box = to_free;
838
839
0
  } else if (tmpl_is_regex(map->rhs)) {
840
    /*
841
     *  @todo - why box it and parse it again, when we can just run the regex?
842
     */
843
0
    fr_value_box_strdup_shallow(&str, NULL, map->rhs->name, false);
844
0
    box = &str;
845
846
0
  } else {
847
0
    fr_strerror_const("Unknown RHS");
848
0
    goto error;
849
0
  }
850
851
  /*
852
   *  Check all possible vps matching the lhs
853
   *  Allows for comparisons such as &foo[*] == "bar" - i.e. true if any instance of &foo has the value "bar"
854
   */
855
0
  rcode = 0;
856
0
  while (vp) {
857
    /*
858
     *  Let the calculation code do upcasting as necessary.
859
     */
860
0
    rcode = fr_value_calc_binary_op(request, &dst, FR_TYPE_BOOL, &vp->data, map->op, box);
861
0
    if ((rcode >= 0) && dst.vb_bool) break;  // Found a "true" result, no need to check any further
862
0
    vp = fr_dcursor_next(&cursor);
863
0
  }
864
0
  TALLOC_FREE(to_free);
865
0
  tmpl_dcursor_clear(&cc);
866
867
0
  if (rcode < 0) return rcode;
868
869
0
  return dst.vb_bool;
870
0
}