Coverage Report

Created: 2025-12-31 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/dict_fixup.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
/** Code to apply fctx and finalisation steps to a dictionary
18
 *
19
 * @file src/lib/util/dict_fixup.c
20
 *
21
 * @copyright 2020 The FreeRADIUS server project
22
 * @copyright 2020,2024 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23
 */
24
RCSID("$Id: db267e02fa9e0a60d147a262f073a159de86d4e7 $")
25
26
#include <freeradius-devel/util/dict.h>
27
#include <freeradius-devel/util/file.h>
28
#include <freeradius-devel/util/value.h>
29
30
#include "dict_fixup_priv.h"
31
32
/** Common fields for every fixup structure
33
 *
34
 */
35
typedef struct {
36
  fr_dlist_t    entry;      //!< Entry in linked list of fctx.
37
} dict_fixup_common_t;
38
39
/** Add an enumeration value to an attribute that wasn't defined at the time the value was parsed
40
 *
41
 */
42
typedef struct {
43
  dict_fixup_common_t common;     //!< Common fields.
44
45
  char      *filename;    //!< where the line being fixed up.
46
  int     line;     //!< ditto.
47
48
  char      *alias;     //!< we need to create.
49
  fr_dict_attr_t    *alias_parent;    //!< Where to add the alias.
50
51
  char      *ref;     //!< what the alias references.
52
  fr_dict_attr_t    *ref_parent;    //!< Parent attribute to resolve the 'attribute' string in.
53
} dict_fixup_alias_t;
54
55
/** Add an enumeration value to an attribute that wasn't defined at the time the value was parsed
56
 *
57
 */
58
typedef struct {
59
  dict_fixup_common_t common;     //!< Common fields.
60
61
  char      *filename;    //!< where the line being fixed up.
62
  int     line;     //!< ditto.
63
64
  char      *attribute;   //!< we couldn't find (and will need to resolve later).
65
  char      *name;      //!< Raw enum name.
66
  char      *value;     //!< Raw enum value.  We can't do anything with this until
67
              //!< we know the attribute type, which we only find out later.
68
69
  fr_dict_attr_t const  *parent;    //!< Parent attribute to resolve the 'attribute' string in.
70
} dict_fixup_enumv_t;
71
72
/** Resolve a group reference
73
 *
74
 */
75
typedef struct {
76
  dict_fixup_common_t common;     //!< Common fields.
77
78
  fr_dict_attr_t    *da;      //!< FR_TYPE_GROUP to fix
79
  char      *ref;     //!< the reference name
80
} dict_fixup_group_t;
81
82
/** Clone operation from one tree node to another
83
 *
84
 */
85
typedef struct {
86
  dict_fixup_common_t common;     //!< Common fields.
87
88
  fr_dict_attr_t    *da;      //!< to populate with cloned information.
89
  char      *ref;     //!< the target attribute to clone
90
} dict_fixup_clone_t;
91
92
/** Run fixup callbacks for a VSA
93
 *
94
 */
95
typedef struct {
96
  dict_fixup_common_t common;     //!< Common fields.
97
98
  fr_dict_attr_t    *da;      //!< FR_TYPE_VSA to fix
99
} dict_fixup_vsa_t;
100
101
/** Dictionary attribute namespaces need their hash tables finalised
102
 *
103
 */
104
typedef struct {
105
  dict_fixup_common_t common;     //!< Common fields.
106
107
  fr_hash_table_t   *hash;      //!< We need to finalise.
108
} dict_fixup_hash_t;
109
110
/** Initialise common fields in fixup struct, and add it to a fixup list
111
 *
112
 * @param[in] fixup_list  to add fixup to.
113
 * @param[in] common    common header to populate.
114
 * @return
115
 *  - 0 on success.
116
 *  - -1 on out of memory.
117
 */
118
static inline CC_HINT(always_inline) int dict_fixup_common(fr_dlist_head_t *fixup_list, dict_fixup_common_t *common)
119
116
{
120
116
  fr_dlist_insert_tail(fixup_list, common);
121
122
116
  return 0;
123
116
}
124
125
/** Resolve a reference string to a dictionary attribute
126
 *
127
 * @param[out] da_p   Where the attribute will be stored
128
 * @param[in] rel   Relative attribute to resolve from.
129
 * @param[in] in    Reference string.
130
 * @return
131
 *  - <0 on error
132
 *  - 0 on parse OK, but *da_p is NULL;
133
 *  - 1 for parse OK, and *da_p is !NULL
134
 */
135
int fr_dict_protocol_reference(fr_dict_attr_t const **da_p, fr_dict_attr_t const *rel, fr_sbuff_t *in)
136
150
{
137
150
  fr_dict_t     *dict = fr_dict_unconst(rel->dict);
138
150
  fr_dict_attr_t const    *da = rel;
139
150
  ssize_t       slen;
140
141
150
  *da_p = NULL;
142
143
  /*
144
   *  Are we resolving a foreign reference?
145
   */
146
150
  if (fr_sbuff_next_if_char(in, '@')) {
147
102
    char proto_name[FR_DICT_ATTR_MAX_NAME_LEN + 1];
148
102
    fr_sbuff_t proto_name_sbuff = FR_SBUFF_OUT(proto_name, sizeof(proto_name));
149
150
    /*
151
     *  @.foo is "foo from the current root".
152
     *
153
     *  This is a bit clearer than "foo".
154
     */
155
102
    if (fr_sbuff_next_if_char(in, '.')) {
156
80
      if (fr_sbuff_is_char(in, '.')) goto above_root;
157
158
80
      da = rel->dict->root;
159
80
      goto more;
160
80
    }
161
162
22
    slen = dict_by_protocol_substr(NULL, &dict, in, NULL);
163
    /* Need to load it... */
164
22
    if (slen <= 0) {
165
      /* Quiet coverity */
166
4
      fr_sbuff_terminate(&proto_name_sbuff);
167
168
      /* Fixme, probably want to limit allowed chars */
169
4
      if (fr_sbuff_out_bstrncpy_until(&proto_name_sbuff, in, SIZE_MAX,
170
4
              &FR_SBUFF_TERMS(L(""), L(".")), NULL) <= 0) {
171
0
      invalid_name:
172
0
        fr_strerror_const("Invalid protocol name");
173
0
        return -1;
174
0
      }
175
176
      /*
177
       *  The filenames are lowercase.  The names in the dictionaries are case-insensitive.  So
178
       *  we mash the name to all lowercase.
179
       */
180
4
      fr_tolower(proto_name);
181
182
      /*
183
       *  Catch this early, so people don't do stupid things
184
       *  like put slashes in the references and then claim
185
       *  it's a security issue.
186
       */
187
4
      if (fr_dict_valid_oid_str(proto_name, -1) < 0) goto invalid_name;
188
189
      /*
190
       *  Load the new dictionary, and mark it as loaded from our dictionary.
191
       */
192
4
      if (fr_dict_protocol_afrom_file(&dict, proto_name, NULL, (rel->dict)->root->name) < 0) {
193
0
        fr_strerror_printf_push("Perhaps there is a '.' missing before the attribute name in %.*s ?",
194
0
              (int) fr_sbuff_used(in), fr_sbuff_start(in));
195
0
        return -1;
196
0
      }
197
198
4
      if (!fr_hash_table_insert((rel->dict)->autoref, dict)) {
199
0
        fr_strerror_const("Failed inserting into internal autoref table");
200
0
        return -1;
201
0
      }
202
4
    }
203
204
    /*
205
     *  Didn't stop at an attribute ref... we're done
206
     */
207
22
    if (fr_sbuff_eof(in)) {
208
22
      *da_p = dict->root;
209
22
      return 1;
210
22
    }
211
212
0
    da = dict->root;
213
0
  }
214
215
  /*
216
   *  ref=.foo is a ref from the current parent.
217
   *
218
   *  ref=@foo is a ref from the root of the tree.
219
   */
220
221
48
  if (!fr_sbuff_next_if_char(in, '.')) {
222
0
    fr_strerror_printf("Invalid reference '%s' - it should start with '@' (from the root), or '.' (from the parent)",
223
0
           fr_sbuff_start(in));
224
0
    return -1;
225
0
  }
226
227
  /*
228
   *  First '.' makes it relative, subsequent ones traverse up the tree.
229
   *
230
   *  No '.' means use the root.
231
   */
232
52
  while (fr_sbuff_next_if_char(in, '.')) {
233
4
    if (!da->parent) {
234
0
    above_root:
235
0
      fr_strerror_const("Reference attempted to navigate above dictionary root");
236
0
      return -1;
237
0
    }
238
4
    da = da->parent;
239
4
  }
240
241
  /*
242
   *  Look up the attribute.  Note that this call will
243
   *  update *da_p with a partial reference if it exists.
244
   */
245
128
more:
246
128
  slen = fr_dict_attr_by_oid_substr(NULL, da_p, da, in, NULL);
247
128
  if (slen < 0) return -1;
248
249
128
  if (slen == 0) {
250
26
    *da_p = NULL;
251
26
    return 0;
252
26
  }
253
254
102
  return 1;
255
128
}
256
257
/** Add an enumeration value to an attribute which has not yet been defined
258
 *
259
 * @param[in] fctx    Holds current dictionary parsing information.
260
 * @param[in] filename    this fixup relates to.
261
 * @param[in] line    this fixup relates to.
262
 * @param[in] attr    The OID string pointing to the attribute
263
 *        to add the enumeration value to.
264
 * @param[in] attr_len    The length of the attr string.
265
 * @param[in] name    The name of the enumv.
266
 * @param[in] name_len    Length of the name string.
267
 * @param[in] value   Value string.  This is kept as a string until we know
268
 *        what type we want to transform it into.
269
 * @param[in] value_len   Length of the value string.
270
 * @param[in] parent    of this attribute.
271
 * @return
272
 *  - 0 on success.
273
 *  - -1 on out of memory.
274
 */
275
int dict_fixup_enumv_enqueue(dict_fixup_ctx_t *fctx, char const *filename, int line,
276
           char const *attr, size_t attr_len,
277
           char const *name, size_t name_len,
278
           char const *value, size_t value_len,
279
           fr_dict_attr_t const *parent)
280
24
{
281
24
  dict_fixup_enumv_t *fixup;
282
283
24
  fixup = talloc(fctx->pool, dict_fixup_enumv_t);
284
24
  if (!fixup) {
285
0
  oom:
286
0
    fr_strerror_const("Out of memory");
287
0
    return -1;
288
0
  }
289
24
  *fixup = (dict_fixup_enumv_t) {
290
24
    .attribute = talloc_bstrndup(fixup, attr, attr_len),
291
24
    .name = talloc_bstrndup(fixup, name, name_len),
292
24
    .value = talloc_bstrndup(fixup, value, value_len),
293
24
    .parent = parent
294
24
  };
295
24
  if (!fixup->attribute || !fixup->name || !fixup->value) goto oom;
296
297
24
  fixup->filename = talloc_strdup(fixup, filename);
298
24
  if (!fixup->filename) goto oom;
299
24
  fixup->line = line;
300
301
24
  return dict_fixup_common(&fctx->enumv, &fixup->common);
302
24
}
303
304
/** Add a previously defined enumeration value to an existing attribute
305
 *
306
 * @param[in] fctx    Holds current dictionary parsing information.
307
 * @param[in] fixup   Hash table to fill.
308
 * @return
309
 *  - 0 on success.
310
 *  - -1 on failure.
311
 */
312
static inline CC_HINT(always_inline) int dict_fixup_enumv_apply(UNUSED dict_fixup_ctx_t *fctx, dict_fixup_enumv_t *fixup)
313
24
{
314
24
  fr_dict_attr_t    *da;
315
24
  fr_value_box_t    value = FR_VALUE_BOX_INITIALISER_NULL(value);
316
24
  fr_type_t   type;
317
24
  int     ret;
318
24
  fr_dict_attr_t const  *da_const;
319
320
24
  da_const = fr_dict_attr_by_oid(NULL, fixup->parent, fixup->attribute);
321
24
  if (!da_const) {
322
0
    fr_strerror_printf_push("Failed resolving ATTRIBUTE referenced by VALUE '%s' at %s[%d]",
323
0
          fixup->name, fr_cwd_strip(fixup->filename), fixup->line);
324
0
    return -1;
325
0
  }
326
24
  da = fr_dict_attr_unconst(da_const);
327
24
  type = da->type;
328
329
24
  if (fr_value_box_from_str(fixup, &value, type, NULL,
330
24
          fixup->value, talloc_array_length(fixup->value) - 1,
331
24
          NULL) < 0) {
332
0
    fr_strerror_printf_push("Invalid VALUE '%pV' for attribute '%s' at %s[%d]",
333
0
          fr_box_strvalue_buffer(fixup->value),
334
0
          da->name,
335
0
          fr_cwd_strip(fixup->filename), fixup->line);
336
0
    return -1;
337
0
  }
338
339
24
  ret = fr_dict_enum_add_name(da, fixup->name, &value, false, false);
340
24
  fr_value_box_clear(&value);
341
24
  da->flags.has_fixup = false;
342
343
24
  return ret;
344
24
}
345
346
/** Resolve a group reference
347
 *
348
 * This is required as the reference may point to another dictionary which
349
 * hasn't been loaded yet.
350
 *
351
 * @param[in] fctx    Holds current dictionary parsing information.
352
 * @param[in] da    The group dictionary attribute.
353
 * @param[in] ref   OID string representing what the group references.
354
 * @return
355
 *  - 0 on success.
356
 *  - -1 on out of memory.
357
 */
358
int dict_fixup_group_enqueue(dict_fixup_ctx_t *fctx, fr_dict_attr_t *da, char const *ref)
359
18
{
360
18
  dict_fixup_group_t *fixup;
361
362
18
  fixup = talloc(fctx->pool, dict_fixup_group_t);
363
18
  if (!fixup) {
364
0
    fr_strerror_const("Out of memory");
365
0
    return -1;
366
0
  }
367
18
  *fixup = (dict_fixup_group_t) {
368
18
    .da = da,
369
18
    .ref = talloc_strdup(fixup, ref),
370
18
  };
371
372
18
  da->flags.has_fixup = true;
373
374
18
  return dict_fixup_common(&fctx->group, &fixup->common);
375
18
}
376
377
/** Resolve a group reference
378
 *
379
 * @param[in] fctx    Holds current dictionary parsing information.
380
 * @param[in] fixup   Hash table to fill.
381
 * @return
382
 *  - 0 on success.
383
 *  - -1 on failure.
384
 */
385
static inline CC_HINT(always_inline) int dict_fixup_group_apply(UNUSED dict_fixup_ctx_t *fctx, dict_fixup_group_t *fixup)
386
18
{
387
18
  fr_dict_attr_t const *da;
388
389
18
  (void) fr_dict_protocol_reference(&da, fixup->da->parent, &FR_SBUFF_IN_STR(fixup->ref));
390
18
  if (!da) {
391
0
    fr_strerror_printf_push("Failed resolving reference for attribute %s at %s[%d]",
392
0
          fixup->da->name, fr_cwd_strip(fixup->da->filename), fixup->da->line);
393
0
    return -1;
394
0
  }
395
396
18
  if (da->type != FR_TYPE_TLV) {
397
0
    fr_strerror_printf("References MUST be to attributes of type 'tlv' at %s[%d]",
398
0
           fr_cwd_strip(fixup->da->filename), fixup->da->line);
399
0
    return -1;
400
0
  }
401
402
18
  if (fr_dict_attr_ref(da)) {
403
0
    fr_strerror_printf("References MUST NOT refer to an ATTRIBUTE which also has 'ref=...' at %s[%d]",
404
0
           fr_cwd_strip(fixup->da->filename), fixup->da->line);
405
0
    return -1;
406
0
  }
407
408
18
  fixup->da->flags.has_fixup = false;
409
410
18
  return dict_attr_ref_resolve(fixup->da, da);
411
18
}
412
413
/** Clone one area of a tree into another
414
 *
415
 * These must be processed later to ensure that we've finished building an
416
 * attribute by the time it has been cloned.
417
 *
418
 * @param[in] fctx    Holds current dictionary parsing information.
419
 * @param[in] da    The group dictionary attribute.
420
 * @param[in] ref   OID string representing what the group references..
421
 * @return
422
 *  - 0 on success.
423
 *  - -1 on out of memory.
424
 */
425
int dict_fixup_clone_enqueue(dict_fixup_ctx_t *fctx, fr_dict_attr_t *da, char const *ref)
426
8
{
427
8
  dict_fixup_clone_t *fixup;
428
429
8
  fr_assert(!fr_type_is_leaf(da->type));
430
431
  /*
432
   *  Delay type checks until we've loaded all of the
433
   *  dictionaries.  This means that errors are produced
434
   *  later, but that shouldn't matter for the default
435
   *  dictionaries.  They're supposed to work.
436
   */
437
8
  fixup = talloc(fctx->pool, dict_fixup_clone_t);
438
8
  if (!fixup) {
439
0
    fr_strerror_const("Out of memory");
440
0
    return -1;
441
0
  }
442
8
  *fixup = (dict_fixup_clone_t) {
443
8
    .da = da,
444
8
    .ref = talloc_typed_strdup(fixup, ref)
445
8
  };
446
447
8
  return dict_fixup_common(&fctx->clone, &fixup->common);
448
8
}
449
450
/** Clone a dictionary attribute from a ref
451
 *
452
 * @param[in] dst_p will either be inserted directly, with fields from the clone, or will be
453
 *      cloned, and then inserted.  In this case the original dst da will be freed
454
 *      and the new cloned attribute will be written back to dst_p.
455
 * @param[in] src to clone.
456
 * @return
457
 *  - 0 on success.
458
 *  - -1 on failure.
459
 */
460
int dict_fixup_clone(fr_dict_attr_t **dst_p, fr_dict_attr_t const *src)
461
62
{
462
62
  fr_dict_attr_t    *dst = *dst_p;
463
62
  fr_dict_t   *dict = fr_dict_unconst(dst->dict);
464
465
  /*
466
   *  @todo - allow this for structural attributes, so long as they don't have a child TLV.
467
   */
468
62
  if (src->dict->proto != dst->dict->proto) {
469
0
    fr_strerror_printf("Incompatible protocols.  Referenced '%s', referencing '%s'.  Defined at %s[%d]",
470
0
           src->dict->proto->name, dst->dict->proto->name, dst->filename, dst->line);
471
0
    return -1;
472
0
  }
473
474
  /*
475
   *  The referenced DA is higher than the one we're creating.  Ensure it's not a parent.
476
   *
477
   *  @todo - Do we want to require that aliases only go deeper in the tree?  Otherwise aliases can
478
   *  make the tree a lot more complicated.
479
   */
480
62
  if (src->depth < dst->depth) {
481
24
    fr_dict_attr_t const *parent;
482
483
130
    for (parent = dst->parent; !parent->flags.is_root; parent = parent->parent) {
484
106
      if (parent == src) {
485
0
        fr_strerror_printf("References MUST NOT be to a parent attribute %s at %s[%d]",
486
0
               parent->name, fr_cwd_strip(dst->filename), dst->line);
487
0
        return -1;
488
0
      }
489
106
    }
490
24
  }
491
492
62
  if (fr_dict_attr_ref(src)) {
493
0
    fr_strerror_printf("References MUST NOT refer to an ATTRIBUTE which itself has a 'ref=...' at %s[%d]",
494
0
           fr_cwd_strip(dst->filename), dst->line);
495
0
    return -1;
496
0
  }
497
498
  /*
499
   *  Leaf attributes can be cloned.  TLV and STRUCT can be cloned.  But all other data types cannot
500
   *  be cloned.
501
   *
502
   *  And while we're at it, copy the flags over.
503
   */
504
62
  switch (src->type) {
505
0
  default:
506
0
    fr_strerror_printf("References MUST NOT refer to an attribute of data type '%s' at %s[%d]",
507
0
           fr_type_to_str(src->type), fr_cwd_strip(dst->filename), dst->line);
508
0
    return -1;
509
510
54
  case FR_TYPE_TLV:
511
54
    dst->flags.type_size = src->flags.type_size;
512
54
    dst->flags.length = src->flags.length;
513
54
    FALL_THROUGH;
514
515
62
  case FR_TYPE_STRUCT:
516
62
    if (!dict_attr_children(src)) {
517
0
      fr_strerror_printf_push("Reference %s has no children defined at %s[%d]",
518
0
            src->name, fr_cwd_strip(dst->filename), dst->line);
519
0
      return -1;
520
0
    }
521
62
    break;
522
62
  }
523
524
62
  dst->flags.array = src->flags.array;
525
62
  dst->flags.is_known_width = src->flags.is_known_width;
526
62
  dst->flags.internal = src->flags.internal;
527
62
  dst->flags.name_only = src->flags.name_only;
528
529
  /*
530
   *  Clone the children from the source to the dst.
531
   *
532
   *  Note that the destination may already have children!
533
   */
534
62
  if (dict_attr_acopy_children(dict, dst, src) < 0) {
535
0
    fr_strerror_printf("Failed populating attribute '%s' with children of %s - %s", dst->name, src->name, fr_strerror());
536
0
    return -1;
537
0
  }
538
539
62
  return fr_dict_attr_add_initialised(dst);
540
62
}
541
542
/** Clone one are of a tree into another
543
 *
544
 * @param[in] fctx    Holds current dictionary parsing information.
545
 * @param[in] fixup   Containing source/destination of the clone.
546
 * @return
547
 *  - 0 on success.
548
 *  - -1 on failure.
549
 */
550
static inline CC_HINT(always_inline) int dict_fixup_clone_apply(UNUSED dict_fixup_ctx_t *fctx, dict_fixup_clone_t *fixup)
551
8
{
552
8
  fr_dict_attr_t const  *src;
553
554
8
  (void) fr_dict_protocol_reference(&src, fixup->da->parent, &FR_SBUFF_IN_STR(fixup->ref));
555
8
  if (!src) {
556
0
    fr_strerror_printf_push("Failed resolving reference for attribute %s at %s[%d]",
557
0
          fixup->da->name, fr_cwd_strip(fixup->da->filename), fixup->da->line);
558
0
    return -1;
559
0
  }
560
561
8
  fixup->da->flags.has_fixup = false;
562
8
  return dict_fixup_clone(&fixup->da, src);
563
8
}
564
565
/** Clone enumeration values from one attribute to another
566
 *
567
 * These must be processed later to ensure that we've finished building an
568
 * attribute by the time it has been cloned.
569
 *
570
 * @param[in] fctx    Holds current dictionary parsing information.
571
 * @param[in] da    The group dictionary attribute.
572
 * @param[in] ref   OID string representing what the group references..
573
 * @return
574
 *  - 0 on success.
575
 *  - -1 on out of memory.
576
 */
577
int dict_fixup_clone_enum_enqueue(dict_fixup_ctx_t *fctx, fr_dict_attr_t *da, char const *ref)
578
6
{
579
6
  dict_fixup_clone_t *fixup;
580
581
6
  fr_assert(fr_type_is_leaf(da->type));
582
583
  /*
584
   *  Delay type checks until we've loaded all of the
585
   *  dictionaries.  This means that errors are produced
586
   *  later, but that shouldn't matter for the default
587
   *  dictionaries.  They're supposed to work.
588
   */
589
6
  fixup = talloc(fctx->pool, dict_fixup_clone_t);
590
6
  if (!fixup) {
591
0
    fr_strerror_const("Out of memory");
592
0
    return -1;
593
0
  }
594
6
  *fixup = (dict_fixup_clone_t) {
595
6
    .da = da,
596
6
    .ref = talloc_typed_strdup(fixup, ref)
597
6
  };
598
599
6
  return dict_fixup_common(&fctx->clone_enum, &fixup->common);
600
6
}
601
602
/** Clone one are of a tree into another
603
 *
604
 * @param[in] fctx    Holds current dictionary parsing information.
605
 * @param[in] fixup   Containing source/destination of the clone.
606
 * @return
607
 *  - 0 on success.
608
 *  - -1 on failure.
609
 */
610
static inline CC_HINT(always_inline) int dict_fixup_clone_enum_apply(UNUSED dict_fixup_ctx_t *fctx, dict_fixup_clone_t *fixup)
611
6
{
612
6
  fr_dict_attr_t const  *src;
613
614
  /*
615
   *  This extension must already exist.
616
   */
617
6
  fr_assert(fr_dict_attr_ext(fixup->da, FR_DICT_ATTR_EXT_ENUMV));
618
619
  /*
620
   *  Find the referenced attribute, and validate it.
621
   */
622
6
  (void) fr_dict_protocol_reference(&src, fixup->da->parent, &FR_SBUFF_IN_STR(fixup->ref));
623
6
  if (!src) {
624
0
    fr_strerror_printf_push("Failed resolving reference for attribute %s at %s[%d]",
625
0
          fixup->da->name, fr_cwd_strip(fixup->da->filename), fixup->da->line);
626
0
    return -1;
627
0
  }
628
629
6
  if (!fr_dict_attr_ext(src, FR_DICT_ATTR_EXT_ENUMV)) {
630
0
    fr_strerror_printf_push("Reference %s has no VALUEs defined at %s[%d]",
631
0
          fixup->ref, fr_cwd_strip(fixup->da->filename), fixup->da->line);
632
0
    return -1;
633
0
  }
634
635
  /*
636
   *  Allow enums to be copied from any protocol, so long as the attribute is not a key, and not of
637
   *  type 'attribute'.
638
   */
639
6
  if (fr_dict_attr_is_key_field(src) || fr_dict_attr_is_key_field(fixup->da) || (src->type == FR_TYPE_ATTR)) {
640
0
    fr_strerror_printf("Cannot clone VALUEs from 'key=...' or type 'attribute' at %s[%d]",
641
0
           fixup->da->filename, fixup->da->line);
642
0
    return -1;
643
0
  }
644
645
6
  if (fr_dict_attr_ref(src)) {
646
0
    fr_strerror_printf("References MUST NOT refer to an ATTRIBUTE which itself has a 'ref=...' at %s[%d]",
647
0
           fr_cwd_strip(fixup->da->filename), fixup->da->line);
648
0
    return -1;
649
0
  }
650
651
6
  if (!dict_attr_ext_copy(&fixup->da, src, FR_DICT_ATTR_EXT_ENUMV)) {
652
0
    fr_strerror_printf("Reference copied no VALUEs from type type '%s' at %s[%d]",
653
0
          fr_type_to_str(fixup->da->type),
654
0
          fr_cwd_strip(fixup->da->filename), fixup->da->line);
655
0
    return -1;
656
0
  }
657
658
6
  fixup->da->flags.has_fixup = false;
659
6
  return 0;
660
6
}
661
662
/** Push a fixup for a VSA.
663
 *
664
 *  This is required so that we can define VENDORs for all VSAs, even
665
 *  if the dictionary doesn't contain VENDOR children for that VSA.
666
 *  This fixup means that we can define VENDORs elsewhere, and then
667
 *  use them in all VSA definitions.  It means that we don't have to
668
 *  do these lookups at run-time.
669
 *
670
 * @param[in] fctx    Holds current dictionary parsing information.
671
 * @param[in] da    The group dictionary attribute.
672
 * @return
673
 *  - 0 on success.
674
 *  - -1 on out of memory.
675
 */
676
int dict_fixup_vsa_enqueue(dict_fixup_ctx_t *fctx, fr_dict_attr_t *da)
677
20
{
678
20
  dict_fixup_vsa_t *fixup;
679
680
20
  fixup = talloc(fctx->pool, dict_fixup_vsa_t);
681
20
  if (!fixup) {
682
0
    fr_strerror_const("Out of memory");
683
0
    return -1;
684
0
  }
685
20
  *fixup = (dict_fixup_vsa_t) {
686
20
    .da = da,
687
20
  };
688
689
20
  return dict_fixup_common(&fctx->vsa, &fixup->common);
690
20
}
691
692
/** Run VSA fixups
693
 *
694
 * @param[in] fctx    Holds current dictionary parsing information.
695
 * @param[in] fixup   entry for fixup
696
 * @return
697
 *  - 0 on success.
698
 *  - -1 on failure.
699
 */
700
static inline CC_HINT(always_inline) int dict_fixup_vsa_apply(UNUSED dict_fixup_ctx_t *fctx, dict_fixup_vsa_t *fixup)
701
20
{
702
20
  fr_dict_vendor_t *dv;
703
20
  fr_dict_t *dict = fr_dict_unconst(fr_dict_by_da(fixup->da));
704
20
  fr_hash_iter_t iter;
705
706
20
  if (!dict->vendors_by_num) return 0;
707
708
20
  for (dv = fr_hash_table_iter_init(dict->vendors_by_num, &iter);
709
2.72k
       dv;
710
2.70k
       dv = fr_hash_table_iter_next(dict->vendors_by_num, &iter)) {
711
2.70k
    if (dict_attr_child_by_num(fixup->da, dv->pen)) continue;
712
713
2.29k
    if (fr_dict_attr_add(dict, fixup->da, dv->name, dv->pen, FR_TYPE_VENDOR, NULL) < 0) return -1;
714
2.29k
  }
715
716
20
  fixup->da->flags.has_fixup = false;
717
20
  return 0;
718
20
}
719
720
721
/** Resolve a group reference
722
 *
723
 * This is required as the reference may point to another dictionary which
724
 * hasn't been loaded yet.
725
 *
726
 * @param[in] fctx    Holds current dictionary parsing information.
727
 * @param[in] filename    this fixup relates to.
728
 * @param[in] line    this fixup relates to.
729
 * @param[in] alias_parent  where to add the alias.
730
 * @param[in] alias   alias to add.
731
 * @param[in] ref_parent  attribute that should contain the reference.
732
 * @param[in] ref   OID string representing what the group references.
733
 * @return
734
 *  - 0 on success.
735
 *  - -1 on out of memory.
736
 */
737
int dict_fixup_alias_enqueue(dict_fixup_ctx_t *fctx, char const *filename, int line,
738
           fr_dict_attr_t *alias_parent, char const *alias,
739
           fr_dict_attr_t *ref_parent, char const *ref)
740
40
{
741
40
  dict_fixup_alias_t *fixup;
742
743
40
  fixup = talloc(fctx->pool, dict_fixup_alias_t);
744
40
  if (!fixup) {
745
0
  oom:
746
0
    fr_strerror_const("Out of memory");
747
0
    return -1;
748
0
  }
749
40
  *fixup = (dict_fixup_alias_t) {
750
40
    .alias = talloc_typed_strdup(fixup, alias),
751
40
    .alias_parent = alias_parent,
752
40
    .ref = talloc_typed_strdup(fixup, ref),
753
40
    .ref_parent = ref_parent
754
40
  };
755
756
40
  fixup->filename = talloc_strdup(fixup, filename);
757
40
  if (!fixup->filename) goto oom;
758
40
  fixup->line = line;
759
760
40
  return dict_fixup_common(&fctx->alias, &fixup->common);
761
40
}
762
763
static inline CC_HINT(always_inline) int dict_fixup_alias_apply(UNUSED dict_fixup_ctx_t *fctx, dict_fixup_alias_t *fixup)
764
40
{
765
40
  fr_dict_attr_t const *da;
766
767
  /*
768
   *  The <ref> can be a name.
769
   */
770
40
  da = fr_dict_attr_by_oid(NULL, fixup->ref_parent, fixup->ref);
771
40
  if (!da) {
772
0
    fr_strerror_printf("Attribute '%s' aliased by '%s' doesn't exist in namespace '%s', at %s[%d]",
773
0
           fixup->ref, fixup->alias, fixup->ref_parent->name, fixup->filename, fixup->line);
774
0
    return -1;
775
0
  }
776
777
40
  fr_dict_attr_unconst(da)->flags.has_fixup = false;
778
40
  return dict_attr_alias_add(fixup->alias_parent, fixup->alias, da);
779
40
}
780
781
/** Initialise a fixup ctx
782
 *
783
 * @param[in] ctx to allocate the fixup pool in.
784
 * @param[in] fctx  to initialise.
785
 * @return
786
 *  - 0 on success.
787
 *  - -1 on failure.
788
 */
789
int dict_fixup_init(TALLOC_CTX *ctx, dict_fixup_ctx_t *fctx)
790
33
{
791
33
  if (fctx->pool) return 0;
792
793
33
  fr_dlist_talloc_init(&fctx->enumv, dict_fixup_enumv_t, common.entry);
794
33
  fr_dlist_talloc_init(&fctx->group, dict_fixup_group_t, common.entry);
795
33
  fr_dlist_talloc_init(&fctx->clone, dict_fixup_clone_t, common.entry);
796
33
  fr_dlist_talloc_init(&fctx->clone_enum, dict_fixup_clone_t, common.entry);
797
33
  fr_dlist_talloc_init(&fctx->vsa, dict_fixup_vsa_t, common.entry);
798
33
  fr_dlist_talloc_init(&fctx->alias, dict_fixup_alias_t, common.entry);
799
800
33
  fctx->pool = talloc_pool(ctx, DICT_FIXUP_POOL_SIZE);
801
33
  if (!fctx->pool) return -1;
802
803
33
  return 0;
804
33
}
805
806
/** Apply all outstanding fixes to a set of dictionaries
807
 *
808
 */
809
int dict_fixup_apply(dict_fixup_ctx_t *fctx)
810
50
{
811
812
300
#define APPLY_FIXUP(_fctx, _list, _func, _type) \
813
300
do { \
814
300
  _type *_fixup; \
815
416
  while ((_fixup = fr_dlist_head(&(_fctx)->_list))) { \
816
116
    if (_func(_fctx, _fixup) < 0) return -1; \
817
116
    fr_dlist_remove(&(_fctx)->_list, _fixup); \
818
116
    talloc_free(_fixup); \
819
116
  } \
820
300
} while (0)
821
822
  /*
823
   *  Apply all the fctx in order
824
   *
825
826
   *  - Enumerations first as they have no dependencies
827
   *  - Group references next, as group attributes may be cloned.
828
   *  - Clones last as all other references and additions should
829
   *    be applied before cloning.
830
   *  - Clone enum clones the enumeration values from a dedicated
831
   *    enum, or another attribute with enumerations.
832
   *  - VSAs
833
   *  - Aliases last as all attributes need to be defined.
834
   */
835
50
  APPLY_FIXUP(fctx, enumv, dict_fixup_enumv_apply, dict_fixup_enumv_t);
836
50
  APPLY_FIXUP(fctx, group, dict_fixup_group_apply, dict_fixup_group_t);
837
50
  APPLY_FIXUP(fctx, clone, dict_fixup_clone_apply, dict_fixup_clone_t);
838
50
  APPLY_FIXUP(fctx, clone_enum,  dict_fixup_clone_enum_apply, dict_fixup_clone_t);
839
50
  APPLY_FIXUP(fctx, vsa,   dict_fixup_vsa_apply,   dict_fixup_vsa_t);
840
50
  APPLY_FIXUP(fctx, alias, dict_fixup_alias_apply, dict_fixup_alias_t);
841
842
50
  TALLOC_FREE(fctx->pool);
843
844
50
  return 0;
845
50
}
846
847
/** Fixup all hash tables in the dictionary so they're suitable for threaded access
848
 *
849
 */
850
static int _dict_attr_fixup_hash_tables(fr_dict_attr_t const *da, UNUSED void *uctx)
851
0
{
852
0
  {
853
0
    fr_dict_attr_ext_enumv_t *ext;
854
855
0
    ext = fr_dict_attr_ext(da, FR_DICT_ATTR_EXT_ENUMV);
856
0
    if (ext) {
857
0
      if (ext->value_by_name) fr_hash_table_fill(ext->value_by_name);
858
0
      if (ext->name_by_value) fr_hash_table_fill(ext->name_by_value);
859
0
    }
860
0
  }
861
862
0
  {
863
0
    fr_hash_table_t *hash;
864
865
0
    hash = dict_attr_namespace(da);
866
0
    if (hash) fr_hash_table_fill(hash);
867
0
  }
868
869
0
  return 0;
870
0
}
871
872
/** Walk a dictionary finalising the hash tables in all attributes with a distinct namespace
873
 *
874
 * @param[in] dict  to finalise namespaces for.
875
 */
876
void dict_hash_tables_finalise(fr_dict_t *dict)
877
0
{
878
0
  fr_dict_attr_t *root = fr_dict_attr_unconst(fr_dict_root(dict));
879
880
0
  (void)_dict_attr_fixup_hash_tables(root, NULL);
881
882
0
  fr_dict_walk(root, _dict_attr_fixup_hash_tables, NULL);
883
884
  /*
885
   *  Walk over all of the hash tables to ensure they're
886
   *  initialized.  We do this because the threads may perform
887
   *  lookups, and we don't want multi-threaded re-ordering
888
   *  of the table entries.  That would be bad.
889
   */
890
0
  fr_hash_table_fill(dict->vendors_by_name);
891
0
  fr_hash_table_fill(dict->vendors_by_num);
892
0
}