/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 | } |