# define INTERNAL_IF_NULL(_dict) if (!_dict) _dict = fr_dict_internal
#endif
-static inline int dict_attr_child_add(fr_dict_attr_t *parent, fr_dict_attr_t *child);
-static int dict_from_file(fr_dict_t *dict,
- char const *dir_name, char const *filename,
- char const *src_file, int src_line);
-
/** Empty callback for hash table initialization
*
*/
return 0;
}
-static void _fr_dict_dump(fr_dict_attr_t const *da, unsigned int lvl)
-{
- unsigned int i;
- size_t len;
- fr_dict_attr_t const *p;
-
- printf("%p - %s (%u) %s\n", da, da->name, da->attr, fr_int2str(dict_attr_types, da->type, "<INVALID>"));
-
- len = talloc_array_length(da->children);
- for (i = 0; i < len; i++) {
- for (p = da->children[i]; p; p = p->next) {
- _fr_dict_dump(p, lvl + 1);
- }
- }
-
-}
-
-void fr_dict_dump(fr_dict_t *dict)
-{
- _fr_dict_dump(dict->root, 0);
-}
-
-/** Initialise the global protocol hashes
- *
- * @note Must be called before any other dictionary functions.
- *
- *
- * @param[in] ctx to allocate the hashes in.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int dict_global_init(TALLOC_CTX *ctx)
-{
- if (protocol_by_name && protocol_by_num) return 0;
-
- protocol_by_name = fr_hash_table_create(ctx, dict_protocol_name_hash, dict_protocol_name_cmp, NULL);
- if (!protocol_by_name) {
- fr_strerror_printf("Failed initializing protocol_by_name hash");
- return -1;
- }
- protocol_by_num = fr_hash_table_create(ctx, dict_protocol_num_hash, dict_protocol_num_cmp, NULL);
- if (!protocol_by_num) {
- fr_strerror_printf("Failed initializing protocol_by_num hash");
- return -1;
- }
-
- return 0;
-}
-
/** Validate a new attribute definition
*
* @todo we need to check length of none vendor attributes.
return (fr_dict_attr_t *)ref_n;
}
-/** Set a new root dictionary attribute
+/** Add a protocol to the global protocol table
*
- * @note Must only be called once per dictionary.
+ * Inserts a protocol into the global protocol table. Uses the root attributes
+ * of the dictionary for comparisons.
*
- * @param[in] dict to modify.
- * @param[in] name of dictionary root.
- * @param[in] protocol_number The artificial (or IANA allocated) number for the protocol.
- * This is only used for
+ * @param[in] dict of protocol we're inserting.
* @return
- * - 0 on success.
- * - -1 on failure.
+ * - 0 on success.
+ * - -1 on failure.
*/
-static int dict_root_set(fr_dict_t *dict, char const *name, unsigned int proto_number)
+static int dict_protocol_add(fr_dict_t *dict)
{
- fr_dict_attr_flags_t flags = {
- .is_root = 1,
- .type_size = 1,
- .length = 1
- };
+ if (!dict->root) return -1; /* Should always have root */
- if (!fr_cond_assert(!dict->root)) {
- fr_strerror_printf("Dictionary root already set");
- return -1;
- }
+ if (!fr_hash_table_insert(protocol_by_name, dict)) {
+ fr_dict_t *old_proto;
- dict->root = dict_attr_alloc_name(dict, name);
- if (!dict->root) return -1;
+ old_proto = fr_hash_table_finddata(protocol_by_name, dict);
+ if (!old_proto) {
+ fr_strerror_printf("%s: Failed inserting protocol name %s", __FUNCTION__, dict->root->name);
+ return -1;
+ }
- dict_attr_init(dict->root, NULL, proto_number, FR_TYPE_TLV, &flags);
- VERIFY_DA(dict->root);
+ if ((strcmp(old_proto->root->name, dict->root->name) == 0) &&
+ (old_proto->root->name == dict->root->name)) {
+ fr_strerror_printf("%s: Duplicate protocol name %s", __FUNCTION__, dict->root->name);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (!fr_hash_table_insert(protocol_by_num, dict)) {
+ fr_strerror_printf("%s: Duplicate protocol number %i", __FUNCTION__, dict->root->attr);
+ return -1;
+ }
return 0;
}
-/** Allocate a new dictionary
+/** Add a vendor to the dictionary
*
- * @param[in] ctx to allocate dictionary in.
+ * Inserts a vendor entry into the vendor hash table. This must be done before adding
+ * attributes under a VSA.
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] name of the vendor.
+ * @param[in] num Vendor's Private Enterprise Number.
* @return
- * - NULL on memory allocation error.
+ * - 0 on success.
+ * - -1 on failure.
*/
-static fr_dict_t *dict_alloc(TALLOC_CTX *ctx)
+static int dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int num)
{
- fr_dict_t *dict;
+ INTERNAL_IF_NULL(dict);
+ size_t len;
+ fr_dict_vendor_t *vendor;
- dict = talloc_zero(ctx, fr_dict_t);
- if (!dict) {
- error:
- fr_strerror_printf("Failed allocating memory for dictionary");
- talloc_free(dict);
- return NULL;
+ len = strlen(name);
+ if (len >= FR_DICT_VENDOR_MAX_NAME_LEN) {
+ fr_strerror_printf("%s: Vendor name too long", __FUNCTION__);
+ return -1;
}
- /*
- * Pre-Allocate 5MB of pool memory for rapid startup
- */
- dict->pool = talloc_pool(dict, (1024 * 1024 * 5));
- if (!dict->pool) goto error;
+#ifdef HAVE_TALLOC_POOLED_OBJECT
+ vendor = talloc_pooled_object(dict, fr_dict_vendor_t, 1, strlen(name) + 1);
+ memset(vendor, 0, sizeof(*vendor));
+#else
+ vendor = talloc_zero(dict, fr_dict_vendor_t);
+#endif
- /*
- * Create the table of vendor by name. There MAY NOT
- * be multiple vendors of the same name.
- */
- dict->vendors_by_name = fr_hash_table_create(dict, dict_vendor_name_hash, dict_vendor_name_cmp, hash_pool_free);
- if (!dict->vendors_by_name) goto error;
+ vendor->name = talloc_typed_strdup(vendor, name);
+ if (!vendor->name) {
+ talloc_free(vendor);
+ fr_strerror_printf("Out of memory");
+ return -1;
+ }
+ vendor->vendorpec = num;
+ vendor->type = vendor->length = 1; /* defaults */
- /*
- * Create the table of vendors by value. There MAY
- * be vendors of the same value. If there are, we
- * pick the latest one.
- */
- dict->vendors_by_num = fr_hash_table_create(dict, dict_vendor_vendorpec_hash, dict_vendor_vendorpec_cmp, NULL);
- if (!dict->vendors_by_num) goto error;
+ if (!fr_hash_table_insert(dict->vendors_by_name, vendor)) {
+ fr_dict_vendor_t *old_vendor;
- /*
- * Create the table of attributes by name. There MAY NOT
- * be multiple attributes of the same name.
- */
- dict->attributes_by_name = fr_hash_table_create(dict, dict_attr_name_hash, dict_attr_name_cmp, NULL);
- if (!dict->attributes_by_name) goto error;
+ old_vendor = fr_hash_table_finddata(dict->vendors_by_name, vendor);
+ if (!old_vendor) {
+ fr_strerror_printf("%s: Failed inserting vendor name %s", __FUNCTION__, name);
+ return -1;
+ }
+ if ((strcmp(old_vendor->name, vendor->name) == 0) && (old_vendor->vendorpec != vendor->vendorpec)) {
+ fr_strerror_printf("%s: Duplicate vendor name %s", __FUNCTION__, name);
+ return -1;
+ }
- /*
- * Horrible hacks for combo-IP.
- */
- dict->attributes_combo = fr_hash_table_create(dict, dict_attr_combo_hash, dict_attr_combo_cmp, hash_pool_free);
- if (!dict->attributes_combo) goto error;
+ /*
+ * Already inserted. Discard the duplicate entry.
+ */
+ talloc_free(vendor);
- dict->values_by_alias = fr_hash_table_create(dict, dict_enum_alias_hash, dict_enum_alias_cmp, hash_pool_free);
- if (!dict->values_by_alias) goto error;
+ return 0;
+ }
- dict->values_by_da = fr_hash_table_create(dict, dict_enum_value_hash, dict_enum_value_cmp, hash_pool_free);
- if (!dict->values_by_da) goto error;
+ /*
+ * Insert the SAME pointer (not free'd when this table is
+ * deleted), into another table.
+ *
+ * We want this behaviour because we want OLD names for
+ * the attributes to be read from the configuration
+ * files, but when we're printing them, (and looking up
+ * by value) we want to use the NEW name.
+ */
+ if (!fr_hash_table_replace(dict->vendors_by_num, vendor)) {
+ fr_strerror_printf("%s: Failed inserting vendor %s", __FUNCTION__, name);
+ return -1;
+ }
- return dict;
+ return 0;
}
-/** (re)initialize a protocol dictionary
- *
- * Initialize the directory, then fix the attr member of all attributes.
- *
- * First dictionary initialised will be set as the default internal dictionary.
+/** Add a child to a parent.
*
- * @param[in] ctx to allocate the dictionary from.
- * @param[out] out Where to write a pointer to the new dictionary.
- * Will free existing dictionary if files have
- * changed and *out is not NULL.
- * @param[in] dir to read dictionary files from.
- * @param[in] fn file name to read.
- * @param[in] name to use for the root attributes.
+ * @param[in] parent we're adding a child to.
+ * @param[in] child to add to parent.
* @return
* - 0 on success.
- * - -1 on failure.
+ * - -1 on failure (memory allocation error).
*/
-int fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *fn, char const *name)
+static inline int dict_attr_child_add(fr_dict_attr_t *parent, fr_dict_attr_t *child)
{
- static bool defined_cast_types;
- fr_dict_t *dict;
-
- dict = dict_alloc(ctx);
- if (!dict) return -1;
+ fr_dict_attr_t const * const *bin;
+ fr_dict_attr_t **this;
/*
- * Free the old dictionaries
+ * Setup fields in the child
*/
- if (*out == fr_dict_internal) fr_dict_internal = dict;
- TALLOC_FREE(*out);
+ child->parent = parent;
+ child->depth = parent->depth + 1;
- /*
- * Remove this at some point...
- */
- if (!fr_dict_internal) fr_dict_internal = dict;
+ VERIFY_DA(child);
/*
- * Magic dictionary root attribute
+ * We only allocate the pointer array *if* the parent has children.
*/
- dict_root_set(dict, name, 0);
+ if (!parent->children) parent->children = talloc_zero_array(parent, fr_dict_attr_t const *, UINT8_MAX + 1);
+ if (!parent->children) return -1;
/*
- * Add cast attributes. We do it this way,
- * so cast attributes get added automatically for new types.
- *
- * We manually add the attributes to the dictionary, and bypass
- * fr_dict_attr_add(), because we know what we're doing, and
- * that function does too many checks.
+ * Treat the array as a hash of 255 bins, with attributes
+ * sorted into bins using num % 255.
+ *
+ * Although the various protocols may define numbers higher than 255:
+ *
+ * RADIUS/DHCPv4 - 1-255
+ * Diameter/Internal - 1-4294967295
+ * DHCPv6 - 1-65535
+ *
+ * In reality very few will ever use attribute numbers > 500, so for
+ * the majority of lookups we get O(1) performance.
+ *
+ * Attributes are inserted into the bin in order of their attribute
+ * numbers to allow slightly more efficient lookups.
*/
- if (!defined_cast_types) {
- FR_NAME_NUMBER const *p;
- fr_dict_attr_flags_t flags;
- char *type_name;
-
- memset(&flags, 0, sizeof(flags));
-
- flags.internal = 1;
-
- for (p = dict_attr_types; p->name; p++) {
- fr_dict_attr_t *n;
-
- type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
-
- n = dict_attr_alloc(dict->pool, dict->root, type_name,
- FR_CAST_BASE + p->number, p->number, &flags);
- if (!n) {
- error:
- talloc_free(dict);
- return -1;
- }
+ bin = &parent->children[child->attr & 0xff];
+ for (;;) {
+ bool child_is_struct = false;
+ bool bin_is_struct = false;
- if (!fr_hash_table_insert(dict->attributes_by_name, n)) goto error;
+ if (!*bin) break;
- /*
- * Set up parenting for the attribute.
- */
- if (dict_attr_child_add(dict->root, n) < 0) goto error;
+ /*
+ * Workaround for vendors that overload the RFC space.
+ * Structural attributes always take priority.
+ */
+ switch (child->type) {
+ case FR_TYPE_STRUCTURAL:
+ child_is_struct = true;
+ break;
- talloc_free(type_name);
+ default:
+ break;
}
- defined_cast_types = true;
- }
-
- if (dict_from_file(dict, dir, fn, NULL, 0) < 0) goto error;
-
- /*
- * Resolve any VALUE aliases (enums) that were defined
- * before the attributes they reference.
- */
- if (dict->enum_fixup) {
- fr_dict_attr_t const *da;
- dict_enum_fixup_t *this, *next;
-
- for (this = dict->enum_fixup; this != NULL; this = next) {
- fr_value_box_t value;
- fr_type_t type;
-
- next = this->next;
- da = fr_dict_attr_by_name(dict, this->attribute);
- if (!da) {
- fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
- this->attribute, this->alias);
- goto error;
- }
- type = da->type;
-
- if (fr_value_box_from_str(this, &value, &type, NULL,
- this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
- fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
- goto error;
- }
- if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+ switch ((*bin)->type) {
+ case FR_TYPE_STRUCTURAL:
+ bin_is_struct = true;
+ break;
- /*
- * Just so we don't lose track of things.
- */
- dict->enum_fixup = next;
+ default:
+ break;
}
- }
- /*
- * Walk over all of the hash tables to ensure they're
- * initialized. We do this because the threads may perform
- * lookups, and we don't want multi-threaded re-ordering
- * of the table entries. That would be bad.
- */
- fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
- fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
+ if (child_is_struct && !bin_is_struct) break;
+ else if (fr_dict_vendor_num_by_da(child) <= fr_dict_vendor_num_by_da(*bin)) break; /* Prioritise RFC attributes */
+ else if (child->attr <= (*bin)->attr) break;
- fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
- fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
+ bin = &(*bin)->next;
+ }
- *out = dict;
+ memcpy(&this, &bin, sizeof(this));
+ child->next = *this;
+ *this = child;
return 0;
}
-/** (Re-)Initialize the special internal dictionary
- *
- * This dictionary has additional programatically generated attributes added to it.
+/** Add an attribute to the name table for the dictionary.
*
- * @param[in] ctx to allocate dictionary in.
- * @param[out] out Where to write pointer to the internal dictionary.
- * @param[in] dir dictionary is located in.
- * @param[in] internal_name name of the internal dictionary dir (may be NULL).
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] parent to add attribute under.
+ * @param[in] name of the attribute.
+ * @param[in] attr number.
+ * @param[in] type of attribute.
+ * @param[in] flags to set in the attribute.
* @return
* - 0 on success.
* - -1 on failure.
*/
-int fr_dict_internal_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *internal_name)
+static int dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t *da)
{
- fr_dict_t *dict = fr_dict_internal;
- char *dict_dir;
- char *tmp;
- FR_NAME_NUMBER const *p;
- fr_dict_attr_flags_t flags = { .internal = true };
- char *type_name;
-
- memcpy(&tmp, &dir, sizeof(tmp));
- dict_dir = internal_name ? talloc_asprintf(NULL, "%s%c%s", dir, FR_DIR_SEP, internal_name) : tmp;
-
- if ((!protocol_by_name || !protocol_by_num) && (dict_global_init(ctx) < 0)) return -1;
+ /*
+ * Insert the attribute, only if it's not a duplicate.
+ */
+ if (!fr_hash_table_insert(dict->attributes_by_name, da)) {
+ fr_dict_attr_t *a;
- if (!dict) {
- dict = dict_alloc(ctx);
- if (!dict) {
- error:
- if (!fr_dict_internal) talloc_free(dict);
- if (internal_name) talloc_free(dict_dir);
- return -1;
+ /*
+ * If the attribute has identical number, then
+ * error out. We don't allow duplicate attribute
+ * definitions.
+ */
+ a = fr_hash_table_finddata(dict->attributes_by_name, da);
+ if (a && (strcasecmp(a->name, da->name) == 0)) {
+ if ((a->attr != da->attr) || (a->parent != da->parent)) {
+ fr_strerror_printf("Duplicate attribute name");
+ error:
+ return -1;
+ }
}
/*
- * Set the root name of the dictionary
+ * Otherwise the attribute has been redefined later
+ * in the dictionary.
+ *
+ * The original fr_dict_attr_t remains in the
+ * dictionary but entry in the name hash table is
+ * updated to point to the new definition.
*/
- dict_root_set(dict, "internal", 0);
- } else {
- if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) {
- if (internal_name) talloc_free(dict_dir);
- return 0;
+ if (!fr_hash_table_replace(dict->attributes_by_name, da)) {
+ fr_strerror_printf("Internal error storing attribute");
+ goto error;
}
}
+
/*
- * Add cast attributes. We do it this way,
- * so cast attributes get added automatically for new types.
+ * Insert copies of the attribute into the
+ * polymorphic attribute table.
*
- * We manually add the attributes to the dictionary, and bypass
- * fr_dict_attr_add(), because we know what we're doing, and
- * that function does too many checks.
+ * This allows an abstract attribute type
+ * like combo IP to be resolved to a
+ * concrete one later.
*/
- for (p = dict_attr_types; p->name; p++) {
- fr_dict_attr_t *n;
+ switch (da->type) {
+ case FR_TYPE_COMBO_IP_ADDR:
+ {
+ fr_dict_attr_t *v4, *v6;
- type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+ v4 = dict_attr_acopy(dict->pool, da);
+ if (!v4) goto error;
+ v4->type = FR_TYPE_IPV4_ADDR;
- n = dict_attr_alloc(dict->pool, dict->root, type_name,
- FR_CAST_BASE + p->number, p->number, &flags);
- if (!n) goto error;
+ v6 = dict_attr_acopy(dict->pool, da);
+ if (!v6) goto error;
+ v6->type = FR_TYPE_IPV6_ADDR;
- if (!fr_hash_table_insert(dict->attributes_by_name, n)) {
- fr_strerror_printf("Failed inserting \"%s\" into internal dictionary", type_name);
+ if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
+ fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
goto error;
}
- /*
- * Set up parenting for the attribute.
- */
- if (dict_attr_child_add(dict->root, n) < 0) goto error;
-
- talloc_free(type_name);
+ if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
+ fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
+ goto error;
+ }
}
- if (dict_dir && dict_from_file(dict, dict_dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
+ case FR_TYPE_COMBO_IP_PREFIX:
+ {
+ fr_dict_attr_t *v4, *v6;
- *out = dict;
- if (!fr_dict_internal) fr_dict_internal = dict;
+ v4 = dict_attr_acopy(dict->pool, da);
+ if (!v4) goto error;
+ v4->type = FR_TYPE_IPV4_PREFIX;
+
+ v6 = dict_attr_acopy(dict->pool, da);
+ if (!v6) goto error;
+ v6->type = FR_TYPE_IPV6_PREFIX;
+
+ if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
+ fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
+ goto error;
+ }
+
+ if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
+ fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
+ goto error;
+ }
+ }
+ default:
+ break;
+ }
return 0;
}
-/** (Re)-initialize a protocol dictionary
- *
- * Initialize the directory, then fix the attr member of all attributes.
- *
- * First dictionary initialised will be set as the default internal dictionary.
+
+/** Add an reference to the dictionary
*
- * @param[in] ctx to allocate the dictionary from.
- * @param[out] out Where to write a pointer to the new dictionary. Will free existing
- * dictionary if files have changed and *out is not NULL.
- * @param[in] base_dir containing all the protocol directories.
- * @param[in] proto_name that we're loading the dictionary for.
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] parent to add attribute under.
+ * @param[in] name of the attribute.
+ * @param[in] attr number.
+ * @param[in] type of attribute.
+ * @param[in] flags to set in the attribute.
+ * @param[in] ref The attribute we're referencing. May be in a foreign
+ * dictionary.
* @return
* - 0 on success.
* - -1 on failure.
*/
-int fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
- char const *base_dir, char const *proto_name)
+static int dict_attr_ref_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
+ char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags,
+ fr_dict_attr_t const *ref)
{
- fr_dict_t *dict;
- char *dir;
- char *proto_dir;
- char *p;
-
- if (!protocol_by_name || !protocol_by_num) {
- fr_strerror_printf("Dictionary not yet initialized call fr_dict_internal_afrom_file first");
- return -1;
- }
+ fr_dict_attr_t *n;
+ fr_dict_attr_t *mutable;
+ fr_dict_attr_flags_t our_flags = *flags;
/*
- * Increment the reference count if the dictionary
- * has already been loaded.
+ * Check it's valid
*/
- if (!*out) {
- *out = fr_dict_by_protocol_name(proto_name);
- if (*out) {
- talloc_increase_ref_count(*out);
- return 0;
- }
- }
+ if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
/*
- * Replace '_' with '/'
+ * Check we're not creating a direct loop
*/
- proto_dir = talloc_strdup(ctx, proto_name);
- for (p = proto_dir; *p; p++) if (*p == '_') *p = FR_DIR_SEP;
+ if (ref->flags.is_reference) {
+ fr_dict_attr_ref_t const *to_ref = talloc_get_type_abort_const(ref, fr_dict_attr_ref_t);
- dir = talloc_asprintf(proto_dir, "%s%c%s", base_dir, FR_DIR_SEP, proto_dir);
- if (!*out) {
- dict = dict_alloc(ctx);
- if (!dict) {
- error:
- talloc_free(proto_dir);
+ if (to_ref->to == ref) {
+ fr_strerror_printf("Circular reference between \"%s\" and \"%s\"", name, ref->name);
return -1;
}
- } else {
- dict = *out;
- if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) return 0;
}
- dict->enum_fixup = NULL; /* just to be safe. */
-
- if (dict_from_file(dict, dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
-
- talloc_free(proto_dir);
-
/*
- * Resolve any VALUE aliases (enums) that were defined
- * before the attributes they reference.
+ * Check the referenced attribute is a root
+ * or a TLV attribute.
*/
- if (dict->enum_fixup) {
- fr_dict_attr_t const *da;
- dict_enum_fixup_t *this, *next;
-
- for (this = dict->enum_fixup; this != NULL; this = next) {
- fr_value_box_t value;
- fr_type_t type;
-
- next = this->next;
- da = fr_dict_attr_by_name(dict, this->attribute);
- if (!da) {
- fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
- this->attribute, this->alias);
- goto error;
- }
- type = da->type;
-
- if (fr_value_box_from_str(this, &value, &type, NULL,
- this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
- fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
- goto error;
- }
+ if (!ref->flags.is_root && (ref->type != FR_TYPE_TLV)) {
+ fr_strerror_printf("Referenced attribute \"%s\" is not a TLV", ref->name);
+ return -1;
+ }
- if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+ n = dict_attr_ref_alloc(dict->pool, parent, name, attr, type, &our_flags, ref);
+ if (!n) return -1;
- /*
- * Just so we don't lose track of things.
- */
- dict->enum_fixup = next;
- }
+ if (dict_attr_add_by_name(dict, n) < 0) {
+ error:
+ talloc_free(n);
+ return -1;
}
/*
- * Walk over all of the hash tables to ensure they're
- * initialized. We do this because the threads may perform
- * lookups, and we don't want multi-threaded re-ordering
- * of the table entries. That would be bad.
+ * Setup parenting for the attribute
*/
- fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
- fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
-
- fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
- fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
+ memcpy(&mutable, &parent, sizeof(mutable));
- *out = dict;
+ /*
+ * Add in by number
+ */
+ if (dict_attr_child_add(mutable, n) < 0) goto error;
return 0;
}
-/** Read supplementary attribute definitions into an existing dictionary
- *
- * @param[in] dict Existing dictionary.
- * @param[in] dir dictionary is located in.
- * @param[in] filename of the dictionary.
- */
-int fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename)
-{
- INTERNAL_IF_NULL(dict);
-
- if (!dict->attributes_by_name) {
- fr_strerror_printf("%s: Must call fr_dict_from_file() before fr_dict_read()", __FUNCTION__);
- return -1;
- }
-
- return dict_from_file(dict, dir, filename, NULL, 0);
-}
-
-/** Add a protocol to the global protocol table
- *
- * Inserts a protocol into the global protocol table. Uses the root attributes
- * of the dictionary for comparisons.
+/** Add an attribute to the dictionary
*
- * @param[in] dict of protocol we're inserting.
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] parent to add attribute under.
+ * @param[in] name of the attribute.
+ * @param[in] attr number.
+ * @param[in] type of attribute.
+ * @param[in] flags to set in the attribute.
* @return
- * - 0 on success.
- * - -1 on failure.
+ * - 0 on success.
+ * - -1 on failure.
*/
-static int dict_protocol_add(fr_dict_t *dict)
+int fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
+ char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags)
{
- if (!dict->root) return -1; /* Should always have root */
-
- if (!fr_hash_table_insert(protocol_by_name, dict)) {
- fr_dict_t *old_proto;
-
- old_proto = fr_hash_table_finddata(protocol_by_name, dict);
- if (!old_proto) {
- fr_strerror_printf("%s: Failed inserting protocol name %s", __FUNCTION__, dict->root->name);
- return -1;
- }
+ fr_dict_attr_t *n;
+ fr_dict_attr_t *mutable;
+ fr_dict_attr_flags_t our_flags = *flags;
- if ((strcmp(old_proto->root->name, dict->root->name) == 0) &&
- (old_proto->root->name == dict->root->name)) {
- fr_strerror_printf("%s: Duplicate protocol name %s", __FUNCTION__, dict->root->name);
- return -1;
- }
+ /*
+ * Check it's valid
+ */
+ if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
- return 0;
- }
+ n = dict_attr_alloc(dict->pool, parent, name, attr, type, &our_flags);
+ if (!n) return -1;
- if (!fr_hash_table_insert(protocol_by_num, dict)) {
- fr_strerror_printf("%s: Duplicate protocol number %i", __FUNCTION__, dict->root->attr);
+ if (dict_attr_add_by_name(dict, n) < 0) {
+ error:
+ talloc_free(n);
return -1;
}
+ /*
+ * Setup parenting for the attribute
+ */
+ memcpy(&mutable, &parent, sizeof(mutable));
+
+ /*
+ * Add in by number
+ */
+ if (dict_attr_child_add(mutable, n) < 0) goto error;
+
return 0;
}
-/** Add a vendor to the dictionary
+/** Add a value alias
*
- * Inserts a vendor entry into the vendor hash table. This must be done before adding
- * attributes under a VSA.
+ * Aliases are textual (string) aliases for a given value.
*
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] name of the vendor.
- * @param[in] num Vendor's Private Enterprise Number.
+ * Value aliases are not limited to integers, and may be added for any non-structural
+ * attribute type.
+ *
+ * @param[in] da to add enumeration value to.
+ * @param[in] alias Name of value alias.
+ * @param[in] value to associate with alias.
+ * @param[in] coerce if the type of the value does not match the
+ * type of the da, attempt to cast it to match
+ * the type of the da. If this is false and there's
+ * a type mismatch, we fail.
+ * We also fail if the value cannot be coerced to
+ * the attribute type.
+ * @param[in] takes_precedence This alias should take precedence over previous
+ * aliases for the same value, when resolving value
+ * to alias.
* @return
- * - 0 on success.
- * - -1 on failure.
+ * - 0 on success.
+ * - -1 on failure.
*/
-static int dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int num)
+int fr_dict_enum_add_alias(fr_dict_attr_t const *da, char const *alias,
+ fr_value_box_t const *value,
+ bool coerce, bool takes_precedence)
{
- INTERNAL_IF_NULL(dict);
size_t len;
- fr_dict_vendor_t *vendor;
+ fr_dict_t *dict;
+ fr_dict_enum_t *enumv = NULL;
+ fr_value_box_t *enum_value = NULL;
- len = strlen(name);
- if (len >= FR_DICT_VENDOR_MAX_NAME_LEN) {
- fr_strerror_printf("%s: Vendor name too long", __FUNCTION__);
+ if (!da) {
+ fr_strerror_printf("%s: Dictionary attribute not specified", __FUNCTION__);
return -1;
}
-#ifdef HAVE_TALLOC_POOLED_OBJECT
- vendor = talloc_pooled_object(dict, fr_dict_vendor_t, 1, strlen(name) + 1);
- memset(vendor, 0, sizeof(*vendor));
-#else
- vendor = talloc_zero(dict, fr_dict_vendor_t);
-#endif
+ if (!*alias) {
+ fr_strerror_printf("%s: Empty names are not permitted", __FUNCTION__);
+ return -1;
+ }
- vendor->name = talloc_typed_strdup(vendor, name);
- if (!vendor->name) {
- talloc_free(vendor);
- fr_strerror_printf("Out of memory");
+ len = strlen(alias);
+ if (len >= FR_DICT_ENUM_MAX_NAME_LEN) {
+ fr_strerror_printf("%s: Value name too long", __FUNCTION__);
return -1;
}
- vendor->vendorpec = num;
- vendor->type = vendor->length = 1; /* defaults */
- if (!fr_hash_table_insert(dict->vendors_by_name, vendor)) {
- fr_dict_vendor_t *old_vendor;
+ dict = fr_dict_by_da(da);
- old_vendor = fr_hash_table_finddata(dict->vendors_by_name, vendor);
- if (!old_vendor) {
- fr_strerror_printf("%s: Failed inserting vendor name %s", __FUNCTION__, name);
- return -1;
- }
- if ((strcmp(old_vendor->name, vendor->name) == 0) && (old_vendor->vendorpec != vendor->vendorpec)) {
- fr_strerror_printf("%s: Duplicate vendor name %s", __FUNCTION__, name);
+ enumv = talloc_zero(dict->pool, fr_dict_enum_t);
+ if (!enumv) {
+ fr_strerror_printf("%s: Out of memory", __FUNCTION__);
+ return -1;
+ }
+ enumv->alias = talloc_typed_strdup(enumv, alias);
+ enum_value = fr_value_box_alloc(enumv, da->type, NULL, false);
+
+ if (da->type != value->type) {
+ if (!coerce) {
+ fr_strerror_printf("%s: Type mismatch between attribute (%s) and enum (%s)",
+ __FUNCTION__,
+ fr_int2str(dict_attr_types, da->type, "<INVALID>"),
+ fr_int2str(dict_attr_types, value->type, "<INVALID>"));
return -1;
}
- /*
- * Already inserted. Discard the duplicate entry.
- */
- talloc_free(vendor);
+ if (fr_value_box_cast(enumv, enum_value, da->type, NULL, value) < 0) {
+ fr_strerror_printf_push("%s: Failed coercing enum type (%s) to attribute type (%s)",
+ __FUNCTION__,
+ fr_int2str(dict_attr_types, value->type, "<INVALID>"),
+ fr_int2str(dict_attr_types, da->type, "<INVALID>"));
- return 0;
+ return -1;
+ }
+ } else {
+ if (fr_value_box_copy(enum_value, enum_value, value) < 0) {
+ fr_strerror_printf_push("%s: Failed copying value into enum", __FUNCTION__);
+ return -1;
+ }
}
+ enumv->value = enum_value;
+ enumv->da = da;
+
/*
- * Insert the SAME pointer (not free'd when this table is
- * deleted), into another table.
- *
- * We want this behaviour because we want OLD names for
- * the attributes to be read from the configuration
- * files, but when we're printing them, (and looking up
- * by value) we want to use the NEW name.
+ * Add the value into the dictionary.
*/
- if (!fr_hash_table_replace(dict->vendors_by_num, vendor)) {
- fr_strerror_printf("%s: Failed inserting vendor %s", __FUNCTION__, name);
- return -1;
- }
+ {
+ fr_dict_attr_t *tmp;
+ memcpy(&tmp, &enumv, sizeof(tmp));
- return 0;
-}
+ if (!fr_hash_table_insert(dict->values_by_alias, tmp)) {
+ fr_dict_enum_t *old;
-/** Add a child to a parent.
- *
- * @param[in] parent we're adding a child to.
- * @param[in] child to add to parent.
- * @return
- * - 0 on success.
- * - -1 on failure (memory allocation error).
- */
-static inline int dict_attr_child_add(fr_dict_attr_t *parent, fr_dict_attr_t *child)
-{
- fr_dict_attr_t const * const *bin;
- fr_dict_attr_t **this;
+ /*
+ * Suppress duplicates with the same
+ * name and value. There are lots in
+ * dictionary.ascend.
+ */
+ old = fr_dict_enum_by_alias(dict, da, alias);
+ if (!fr_cond_assert(old)) return -1;
- /*
- * Setup fields in the child
- */
- child->parent = parent;
- child->depth = parent->depth + 1;
+ if (fr_value_box_cmp(old->value, enumv->value) == 0) {
+ talloc_free(enumv);
+ return 0;
+ }
- VERIFY_DA(child);
+ fr_strerror_printf("Duplicate VALUE alias \"%s\" for attribute \"%s\". "
+ "Old value was \"%pV\", new value was \"%pV\"", alias, da->name,
+ old->value, enumv->value);
+ talloc_free(enumv);
+ return -1;
+ }
+ }
/*
- * We only allocate the pointer array *if* the parent has children.
+ * There are multiple VALUE's, keyed by attribute, so we
+ * take care of that here.
*/
- if (!parent->children) parent->children = talloc_zero_array(parent, fr_dict_attr_t const *, UINT8_MAX + 1);
- if (!parent->children) return -1;
+ if (takes_precedence) {
+ if (!fr_hash_table_replace(dict->values_by_da, enumv)) {
+ fr_strerror_printf("%s: Failed inserting value %s", __FUNCTION__, alias);
+ return -1;
+ }
+ } else {
+ (void) fr_hash_table_insert(dict->values_by_da, enumv);
+ }
/*
- * Treat the array as a hash of 255 bins, with attributes
- * sorted into bins using num % 255.
- *
- * Although the various protocols may define numbers higher than 255:
- *
- * RADIUS/DHCPv4 - 1-255
- * Diameter/Internal - 1-4294967295
- * DHCPv6 - 1-65535
- *
- * In reality very few will ever use attribute numbers > 500, so for
- * the majority of lookups we get O(1) performance.
- *
- * Attributes are inserted into the bin in order of their attribute
- * numbers to allow slightly more efficient lookups.
+ * Mark the attribute up as having an enumv
*/
- bin = &parent->children[child->attr & 0xff];
- for (;;) {
- bool child_is_struct = false;
- bool bin_is_struct = false;
-
- if (!*bin) break;
-
- /*
- * Workaround for vendors that overload the RFC space.
- * Structural attributes always take priority.
- */
- switch (child->type) {
- case FR_TYPE_STRUCTURAL:
- child_is_struct = true;
- break;
+ {
+ fr_dict_attr_t *mutable;
- default:
- break;
- }
+ memcpy(&mutable, &da, sizeof(mutable));
- switch ((*bin)->type) {
- case FR_TYPE_STRUCTURAL:
- bin_is_struct = true;
- break;
+ mutable->flags.has_value = 1;
+ }
- default:
- break;
- }
+ return 0;
+}
- if (child_is_struct && !bin_is_struct) break;
- else if (fr_dict_vendor_num_by_da(child) <= fr_dict_vendor_num_by_da(*bin)) break; /* Prioritise RFC attributes */
- else if (child->attr <= (*bin)->attr) break;
+/** Copy a known or unknown attribute to produce an unknown attribute
+ *
+ * Will copy the complete hierarchy down to the first known attribute.
+ */
+fr_dict_attr_t *fr_dict_unknown_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
+{
+ fr_dict_attr_t *n, *new_parent = NULL;
+ fr_dict_attr_t const *parent;
- bin = &(*bin)->next;
+ if (da->parent->flags.is_unknown) {
+ new_parent = fr_dict_unknown_acopy(ctx, da->parent);
+ parent = new_parent;
+ } else {
+ parent = da->parent;
}
- memcpy(&this, &bin, sizeof(this));
- child->next = *this;
- *this = child;
+ n = dict_attr_alloc(ctx, parent, da->name, da->attr, da->type, &da->flags);
+ n->parent = parent;
+ n->depth = da->depth;
- return 0;
+ /*
+ * Inverted tallloc hierarchy.
+ */
+ if (new_parent) talloc_steal(n, parent);
+
+ return n;
}
-/** Add an attribute to the name table for the dictionary.
+/** Converts an unknown to a known by adding it to the internal dictionaries.
+ *
+ * Does not free old #fr_dict_attr_t, that is left up to the caller.
*
* @param[in] dict of protocol context we're operating in.
* If NULL the internal dictionary will be used.
- * @param[in] parent to add attribute under.
- * @param[in] name of the attribute.
- * @param[in] attr number.
- * @param[in] type of attribute.
- * @param[in] flags to set in the attribute.
+ * @param[in] old unknown attribute to add.
* @return
- * - 0 on success.
- * - -1 on failure.
+ * - Existing #fr_dict_attr_t if old was found in a dictionary.
+ * - A new entry representing old.
*/
-static int dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t *da)
+fr_dict_attr_t const *fr_dict_unknown_add(fr_dict_t *dict, fr_dict_attr_t const *old)
{
- /*
- * Insert the attribute, only if it's not a duplicate.
- */
- if (!fr_hash_table_insert(dict->attributes_by_name, da)) {
- fr_dict_attr_t *a;
+ fr_dict_attr_t const *da;
+ fr_dict_attr_t const *parent;
+ fr_dict_attr_flags_t flags;
- /*
- * If the attribute has identical number, then
- * error out. We don't allow duplicate attribute
- * definitions.
- */
- a = fr_hash_table_finddata(dict->attributes_by_name, da);
- if (a && (strcasecmp(a->name, da->name) == 0)) {
- if ((a->attr != da->attr) || (a->parent != da->parent)) {
- fr_strerror_printf("Duplicate attribute name");
- error:
- return -1;
- }
- }
+ if (!old) return NULL;
- /*
- * Otherwise the attribute has been redefined later
- * in the dictionary.
- *
- * The original fr_dict_attr_t remains in the
- * dictionary but entry in the name hash table is
- * updated to point to the new definition.
- */
- if (!fr_hash_table_replace(dict->attributes_by_name, da)) {
- fr_strerror_printf("Internal error storing attribute");
- goto error;
- }
- }
+ da = fr_dict_attr_by_name(dict, old->name);
+ if (da) return da;
/*
- * Insert copies of the attribute into the
- * polymorphic attribute table.
- *
- * This allows an abstract attribute type
- * like combo IP to be resolved to a
- * concrete one later.
+ * Define the complete unknown hierarchy
*/
- switch (da->type) {
- case FR_TYPE_COMBO_IP_ADDR:
- {
- fr_dict_attr_t *v4, *v6;
-
- v4 = dict_attr_acopy(dict->pool, da);
- if (!v4) goto error;
- v4->type = FR_TYPE_IPV4_ADDR;
-
- v6 = dict_attr_acopy(dict->pool, da);
- if (!v6) goto error;
- v6->type = FR_TYPE_IPV6_ADDR;
-
- if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
- fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
- goto error;
- }
-
- if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
- fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
- goto error;
+ if (old->parent && old->parent->flags.is_unknown) {
+ parent = fr_dict_unknown_add(dict, old->parent);
+ if (!parent) {
+ fr_strerror_printf_push("Failed adding parent \"%s\"", old->parent->name);
+ return NULL;
}
+ } else {
+ parent = old->parent;
}
- case FR_TYPE_COMBO_IP_PREFIX:
- {
- fr_dict_attr_t *v4, *v6;
-
- v4 = dict_attr_acopy(dict->pool, da);
- if (!v4) goto error;
- v4->type = FR_TYPE_IPV4_PREFIX;
-
- v6 = dict_attr_acopy(dict->pool, da);
- if (!v6) goto error;
- v6->type = FR_TYPE_IPV6_PREFIX;
+ memcpy(&flags, &old->flags, sizeof(flags));
+ flags.is_unknown = false;
+ flags.is_raw = true;
- if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
- fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
- goto error;
- }
+ /*
+ * If this is a vendor, we skip most of the sanity
+ * checks and add it to the vendor hash, and add it
+ * as a child attribute to the Vendor-Specific
+ * container.
+ */
+ if (old->type == FR_TYPE_VENDOR) {
+ fr_dict_attr_t *mutable, *n;
- if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
- fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
- goto error;
- }
- }
- default:
- break;
- }
+ if (dict_vendor_add(dict, old->name, old->attr) < 0) return NULL;
- return 0;
-}
+ n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
+ /*
+ * Setup parenting for the attribute
+ */
+ memcpy(&mutable, &old->parent, sizeof(mutable));
+ if (dict_attr_child_add(mutable, n) < 0) return NULL;
-/** Add an reference to the dictionary
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] parent to add attribute under.
- * @param[in] name of the attribute.
- * @param[in] attr number.
- * @param[in] type of attribute.
- * @param[in] flags to set in the attribute.
- * @param[in] ref The attribute we're referencing. May be in a foreign
- * dictionary.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int dict_attr_ref_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
- char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags,
- fr_dict_attr_t const *ref)
-{
- fr_dict_attr_t *n;
- fr_dict_attr_t *mutable;
- fr_dict_attr_flags_t our_flags = *flags;
+ return n;
+ }
/*
- * Check it's valid
+ * Look up the attribute by number. If it doesn't exist,
+ * add it both by name and by number. If it does exist,
+ * add it only by name.
*/
- if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
+ da = fr_dict_attr_child_by_num(parent, old->attr);
+ if (da) {
+ fr_dict_attr_t *n;
- /*
- * Check we're not creating a direct loop
- */
- if (ref->flags.is_reference) {
- fr_dict_attr_ref_t const *to_ref = talloc_get_type_abort_const(ref, fr_dict_attr_ref_t);
+ n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
+ if (!n) return NULL;
- if (to_ref->to == ref) {
- fr_strerror_printf("Circular reference between \"%s\" and \"%s\"", name, ref->name);
- return -1;
+ /*
+ * Add the unknown by NAME. e.g. if the admin does "Attr-26", we want
+ * to return "Attr-26", and NOT "Vendor-Specific". The rest of the server
+ * is responsible for converting "Attr-26 = 0x..." to an actual attribute,
+ * if it so desires.
+ */
+ if (dict_attr_add_by_name(dict, n) < 0) {
+ talloc_free(n);
+ return NULL;
}
- }
-
- /*
- * Check the referenced attribute is a root
- * or a TLV attribute.
- */
- if (!ref->flags.is_root && (ref->type != FR_TYPE_TLV)) {
- fr_strerror_printf("Referenced attribute \"%s\" is not a TLV", ref->name);
- return -1;
- }
-
- n = dict_attr_ref_alloc(dict->pool, parent, name, attr, type, &our_flags, ref);
- if (!n) return -1;
- if (dict_attr_add_by_name(dict, n) < 0) {
- error:
- talloc_free(n);
- return -1;
+ return n;
}
/*
- * Setup parenting for the attribute
+ * Add the attribute by both name and number.
*/
- memcpy(&mutable, &parent, sizeof(mutable));
+ if (fr_dict_attr_add(dict, parent, old->name, old->attr, old->type, &flags) < 0) return NULL;
/*
- * Add in by number
+ * For paranoia, return it by name.
*/
- if (dict_attr_child_add(mutable, n) < 0) goto error;
-
- return 0;
+ return fr_dict_attr_by_name(dict, old->name);
}
-/** Add an attribute to the dictionary
+/** Free dynamically allocated (unknown attributes)
*
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] parent to add attribute under.
- * @param[in] name of the attribute.
- * @param[in] attr number.
- * @param[in] type of attribute.
- * @param[in] flags to set in the attribute.
- * @return
- * - 0 on success.
- * - -1 on failure.
+ * If the da was dynamically allocated it will be freed, else the function
+ * will return without doing anything.
+ *
+ * @param[in] da to free.
*/
-int fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
- char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags)
+void fr_dict_unknown_free(fr_dict_attr_t const **da)
{
- fr_dict_attr_t *n;
- fr_dict_attr_t *mutable;
- fr_dict_attr_flags_t our_flags = *flags;
-
- /*
- * Check it's valid
- */
- if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
+ fr_dict_attr_t **tmp;
- n = dict_attr_alloc(dict->pool, parent, name, attr, type, &our_flags);
- if (!n) return -1;
+ if (!da || !*da) return;
- if (dict_attr_add_by_name(dict, n) < 0) {
- error:
- talloc_free(n);
- return -1;
+ /* Don't free real DAs */
+ if (!(*da)->flags.is_unknown) {
+ return;
}
- /*
- * Setup parenting for the attribute
- */
- memcpy(&mutable, &parent, sizeof(mutable));
-
- /*
- * Add in by number
- */
- if (dict_attr_child_add(mutable, n) < 0) goto error;
+ memcpy(&tmp, &da, sizeof(*tmp));
+ talloc_free(*tmp);
- return 0;
+ *tmp = NULL;
}
-/** Add a value alias
+/** Build an unknown vendor, parented by a VSA or EVS attribute
*
- * Aliases are textual (string) aliases for a given value.
+ * This allows us to complete the path back to the dictionary root in the case
+ * of unknown attributes with unknown vendors.
*
- * Value aliases are not limited to integers, and may be added for any non-structural
- * attribute type.
+ * @note Will return known vendors attributes where possible. Do not free directly,
+ * use #fr_dict_unknown_free.
*
- * @param[in] da to add enumeration value to.
- * @param[in] alias Name of value alias.
- * @param[in] value to associate with alias.
- * @param[in] coerce if the type of the value does not match the
- * type of the da, attempt to cast it to match
- * the type of the da. If this is false and there's
- * a type mismatch, we fail.
- * We also fail if the value cannot be coerced to
- * the attribute type.
- * @param[in] takes_precedence This alias should take precedence over previous
- * aliases for the same value, when resolving value
- * to alias.
+ * @param[in] ctx to allocate the vendor attribute in.
+ * @param[out] out Where to write point to new unknown dict attr
+ * representing the unknown vendor.
+ * @param[in] parent of the vendor attribute, either an EVS or VSA attribute.
+ * @param[in] vendor id.
* @return
* - 0 on success.
* - -1 on failure.
*/
-int fr_dict_enum_add_alias(fr_dict_attr_t const *da, char const *alias,
- fr_value_box_t const *value,
- bool coerce, bool takes_precedence)
+int fr_dict_unknown_vendor_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+ fr_dict_attr_t const *parent, unsigned int vendor)
{
- size_t len;
- fr_dict_t *dict;
- fr_dict_enum_t *enumv = NULL;
- fr_value_box_t *enum_value = NULL;
+ fr_dict_attr_flags_t flags = {
+ .is_unknown = true,
+ .is_raw = true,
+ .type_size = true,
+ .length = true
+ };
- if (!da) {
- fr_strerror_printf("%s: Dictionary attribute not specified", __FUNCTION__);
+ if (!fr_cond_assert(parent)) {
+ fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
return -1;
}
- if (!*alias) {
- fr_strerror_printf("%s: Empty names are not permitted", __FUNCTION__);
- return -1;
- }
+ *out = NULL;
- len = strlen(alias);
- if (len >= FR_DICT_ENUM_MAX_NAME_LEN) {
- fr_strerror_printf("%s: Value name too long", __FUNCTION__);
- return -1;
- }
+ /*
+ * Vendor attributes can occur under VSA or EVS attributes.
+ */
+ switch (parent->type) {
+ case FR_TYPE_VSA:
+ case FR_TYPE_EVS:
+ if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
- dict = fr_dict_by_da(da);
+ *out = dict_attr_alloc(ctx, parent, NULL, vendor, FR_TYPE_VENDOR, &flags);
- enumv = talloc_zero(dict->pool, fr_dict_enum_t);
- if (!enumv) {
- fr_strerror_printf("%s: Out of memory", __FUNCTION__);
+ return 0;
+
+ case FR_TYPE_VENDOR:
+ if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
+ fr_strerror_printf("Unknown vendor cannot be parented by another vendor");
return -1;
- }
- enumv->alias = talloc_typed_strdup(enumv, alias);
- enum_value = fr_value_box_alloc(enumv, da->type, NULL, false);
- if (da->type != value->type) {
- if (!coerce) {
- fr_strerror_printf("%s: Type mismatch between attribute (%s) and enum (%s)",
- __FUNCTION__,
- fr_int2str(dict_attr_types, da->type, "<INVALID>"),
- fr_int2str(dict_attr_types, value->type, "<INVALID>"));
- return -1;
- }
+ default:
+ fr_strerror_printf("Unknown vendors can only be parented by 'vsa' or 'evs' "
+ "attributes, not '%s'", fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
+ return -1;
+ }
+}
- if (fr_value_box_cast(enumv, enum_value, da->type, NULL, value) < 0) {
- fr_strerror_printf_push("%s: Failed coercing enum type (%s) to attribute type (%s)",
- __FUNCTION__,
- fr_int2str(dict_attr_types, value->type, "<INVALID>"),
- fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+/** Allocates an unknown attribute
+ *
+ * @copybrief fr_dict_unknown_from_fields
+ *
+ * @note If vendor != 0, an unknown vendor (may) also be created, parented by
+ * the correct EVS or VSA attribute. This is accessible via da->parent,
+ * and will be use the unknown da as its talloc parent.
+ *
+ * @param[in] ctx to allocate DA in.
+ * @param[in] parent of the unknown attribute (may also be unknown).
+ * @param[in] attr number.
+ * @param[in] vendor number.
+ * @return 0 on success.
+ */
+fr_dict_attr_t const *fr_dict_unknown_afrom_fields(TALLOC_CTX *ctx, fr_dict_attr_t const *parent,
+ unsigned int vendor, unsigned int attr)
+{
+ fr_dict_attr_t const *da;
+ fr_dict_attr_t *n;
+ fr_dict_attr_t *new_parent = NULL;
+ fr_dict_attr_flags_t flags = {
+ .is_unknown = true,
+ .is_raw = true,
+ };
- return -1;
- }
- } else {
- if (fr_value_box_copy(enum_value, enum_value, value) < 0) {
- fr_strerror_printf_push("%s: Failed copying value into enum", __FUNCTION__);
- return -1;
- }
+ if (!fr_cond_assert(parent)) {
+ fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+ return NULL;
}
- enumv->value = enum_value;
- enumv->da = da;
-
/*
- * Add the value into the dictionary.
+ * If there's a vendor specified, we check to see
+ * if the parent is a VSA or EVS, and if it is
+ * we either lookup the vendor to get the correct
+ * attribute, or bridge the gap in the tree, with an
+ * unknown vendor.
+ *
+ * We need to do the check, as the parent could be
+ * a TLV, in which case the vendor should be known
+ * and we don't need to modify the parent.
*/
- {
- fr_dict_attr_t *tmp;
- memcpy(&tmp, &enumv, sizeof(tmp));
-
- if (!fr_hash_table_insert(dict->values_by_alias, tmp)) {
- fr_dict_enum_t *old;
-
- /*
- * Suppress duplicates with the same
- * name and value. There are lots in
- * dictionary.ascend.
- */
- old = fr_dict_enum_by_alias(dict, da, alias);
- if (!fr_cond_assert(old)) return -1;
-
- if (fr_value_box_cmp(old->value, enumv->value) == 0) {
- talloc_free(enumv);
- return 0;
- }
-
- fr_strerror_printf("Duplicate VALUE alias \"%s\" for attribute \"%s\". "
- "Old value was \"%pV\", new value was \"%pV\"", alias, da->name,
- old->value, enumv->value);
- talloc_free(enumv);
- return -1;
+ if (vendor && ((parent->type == FR_TYPE_VSA) || (parent->type == FR_TYPE_EVS))) {
+ da = fr_dict_attr_child_by_num(parent, vendor);
+ if (!da) {
+ if (fr_dict_unknown_vendor_afrom_num(ctx, &new_parent, parent, vendor) < 0) return NULL;
+ da = new_parent;
}
+ parent = da;
+
+ /*
+ * Need to clone the unknown hierachy, as unknown
+ * attributes must parent the complete heirachy,
+ * and cannot share any parts with any other unknown
+ * attributes.
+ */
+ } else if (parent->flags.is_unknown) {
+ new_parent = fr_dict_unknown_acopy(ctx, parent);
+ parent = new_parent;
}
+ n = dict_attr_alloc(ctx, parent, NULL, attr, FR_TYPE_OCTETS, &flags);
+
/*
- * There are multiple VALUE's, keyed by attribute, so we
- * take care of that here.
+ * The config files may reference the unknown by name.
+ * If so, use the pre-defined name instead of an unknown
+ * one.
+ *
+ * @fixme: pass the root into this function!
*/
- if (takes_precedence) {
- if (!fr_hash_table_replace(dict->values_by_da, enumv)) {
- fr_strerror_printf("%s: Failed inserting value %s", __FUNCTION__, alias);
- return -1;
- }
- } else {
- (void) fr_hash_table_insert(dict->values_by_da, enumv);
+ da = fr_dict_attr_by_name(NULL, n->name);
+ if (da) {
+ fr_dict_unknown_free(&parent);
+ parent = n;
+ fr_dict_unknown_free(&parent);
+ return da;
}
/*
- * Mark the attribute up as having an enumv
+ * Ensure the parent is freed at the same time as the
+ * unknown DA. This should be OK as we never parent
+ * multiple unknown attributes off the same parent.
*/
- {
- fr_dict_attr_t *mutable;
+ if (new_parent && new_parent->flags.is_unknown) talloc_steal(n, new_parent);
- memcpy(&mutable, &da, sizeof(mutable));
+ return n;
+}
- mutable->flags.has_value = 1;
+/** Initialise a fr_dict_attr_t from an ASCII attribute and value
+ *
+ * Where the attribute name is in the form:
+ * - Attr-%d
+ * - Attr-%d.%d.%d...
+ *
+ * @copybrief fr_dict_unknown_from_fields
+ *
+ * @param[in] ctx to allocate the attribute in.
+ * @param[out] out Where to write the new attribute to.
+ * @param[in] parent of the unknown attribute (may also be unknown).
+ * @param[in] num of the unknown attribute.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int dict_unknown_attr_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+ fr_dict_attr_t const *parent, unsigned long num)
+{
+ fr_dict_attr_t *da;
+ fr_dict_attr_flags_t flags = {
+ .is_unknown = true,
+ .is_raw = true,
+ };
+
+ if (!fr_cond_assert(parent)) {
+ fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+ return -1;
}
+ *out = NULL;
+
+ da = dict_attr_alloc(ctx, parent, NULL, num, FR_TYPE_OCTETS, &flags);
+ if (!da) return -1;
+
+ *out = da;
+
return 0;
}
-/*
- * String split routine. Splits an input string IN PLACE
- * into pieces, based on spaces.
+/** Create a fr_dict_attr_t from an ASCII attribute and value
+ *
+ * Where the attribute name is in the form:
+ * - Attr-%d
+ * - Attr-%d.%d.%d...
+ *
+ * @copybrief fr_dict_unknown_from_fields
+ *
+ * @note If vendor != 0, an unknown vendor (may) also be created, parented by
+ * the correct EVS or VSA attribute. This is accessible via vp->parent,
+ * and will be use the unknown da as its talloc parent.
+ *
+ * @param[in] ctx to alloc new attribute in.
+ * @param[out] out Where to write the head of the chain unknown
+ * dictionary attributes.
+ * @param[in] parent Attribute to use as the root for resolving OIDs in.
+ * Usually the root of a protocol dictionary.
+ * @param[in] oid_str of attribute.
+ * @return
+ * - The number of bytes parsed on success.
+ * - <= 0 on failure. Negative offset indicates parse error position.
*/
-int fr_dict_str_to_argv(char *str, char **argv, int max_argc)
+ssize_t fr_dict_unknown_afrom_oid_str(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+ fr_dict_attr_t const *parent, char const *oid_str)
{
- int argc = 0;
+ char const *p = oid_str, *end = oid_str + strlen(oid_str);
+ fr_dict_attr_t const *our_parent = parent;
+ TALLOC_CTX *top_ctx = NULL, *our_ctx = ctx;
- while (*str) {
- if (argc >= max_argc) break;
+ fr_dict_attr_t *n = NULL;
- /*
- * Chop out comments early.
- */
- if (*str == '#') {
- *str = '\0';
- break;
- }
+ if (!fr_cond_assert(parent)) {
+ fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+ return -1;
+ }
- while ((*str == ' ') ||
- (*str == '\t') ||
- (*str == '\r') ||
- (*str == '\n'))
- *(str++) = '\0';
+ *out = NULL;
- if (!*str) break;
+ if (fr_dict_valid_name(oid_str, -1) < 0) return -1;
- argv[argc] = str;
- argc++;
-
- while (*str &&
- (*str != ' ') &&
- (*str != '\t') &&
- (*str != '\r') &&
- (*str != '\n'))
- str++;
+ /*
+ * All unknown attributes are of the form "Attr-#-#-#-#"
+ */
+ if (strncasecmp(p, "Attr-", 5) != 0) {
+ fr_strerror_printf("Unknown attribute '%s'", oid_str);
+ return 0;
}
+ p += 5;
- return argc;
-}
-
-static int dict_read_sscanf_i(unsigned int *pvalue, char const *str)
-{
- int rcode = 0;
- int base = 10;
- static char const *tab = "0123456789";
+ do {
+ unsigned int num;
+ fr_dict_attr_t const *da = NULL;
- if ((str[0] == '0') &&
- ((str[1] == 'x') || (str[1] == 'X'))) {
- tab = "0123456789abcdef";
- base = 16;
+ if (fr_dict_oid_component(&num, &p) < 0) {
+ error:
+ talloc_free(top_ctx);
+ return -(p - oid_str);
+ }
- str += 2;
- }
+ switch (*p) {
+ /*
+ * Structural attribute
+ */
+ case '.':
+ if (!our_parent) goto is_root;
- while (*str) {
- char const *c;
+ da = fr_dict_attr_child_by_num(our_parent, num);
+ if (!da) { /* Unknown component */
+ switch (our_parent->type) {
+ case FR_TYPE_EVS:
+ case FR_TYPE_VSA:
+ da = fr_dict_attr_child_by_num(our_parent, num);
+ if (!fr_cond_assert(!da || (da->type == FR_TYPE_VENDOR))) goto error;
- if (*str == '.') break;
+ if (!da) {
+ if (fr_dict_unknown_vendor_afrom_num(our_ctx, &n,
+ our_parent, num) < 0) {
+ goto error;
+ }
+ da = n;
+ }
+ break;
- c = memchr(tab, tolower((int)*str), base);
- if (!c) return 0;
+ case FR_TYPE_TLV:
+ case FR_TYPE_EXTENDED:
+ case FR_TYPE_LONG_EXTENDED:
+ is_root:
+ if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) {
+ goto error;
+ }
- rcode *= base;
- rcode += (c - tab);
- str++;
- }
+ da = n;
+ break;
- *pvalue = rcode;
- return 1;
-}
+ /*
+ * Can't have a FR_TYPE_STRING inside a
+ * FR_TYPE_STRING (for example)
+ */
+ default:
+ fr_strerror_printf("Parent OID component (%s) in \"%.*s\" specified a "
+ "non-structural type (%s)", our_parent->name,
+ (int)(p - oid_str), oid_str,
+ fr_int2str(dict_attr_types, our_parent->type, "<INVALID>"));
+ goto error;
+ }
+ }
+ our_parent = da;
-/** Parser context for dict_from_file
- *
- * Allows vendor and TLV context to persist across $INCLUDEs
- */
-typedef struct {
- fr_dict_t *dict; //!< Protocol dictionary we're inserting attributes into.
- fr_dict_t *old_dict; //!< The dictionary before the current BEGIN-PROTOCOL block.
+ if (n && n->flags.is_unknown) {
+ if (top_ctx == NULL) top_ctx = n; /* Track first unknown */
+ our_ctx = n;
+ }
- unsigned int block_vendor; //!< Vendor block we're inserting attributes into.
- //!< Can be removed once we remove the vendor field from
- //!< #fr_dict_attr_t.
- fr_dict_attr_t const *block_tlv[FR_DICT_TLV_NEST_MAX]; //!< Nested TLV block's we're
- //!< inserting attributes into.
- int block_tlv_depth; //!< Nested TLV block index we're inserting into.
+ break;
- fr_dict_attr_t const *parent; //!< Current parent attribute (root/vendor/tlv).
-} dict_from_file_ctx_t;
+ /*
+ * Leaf attribute
+ */
+ case '\0':
+ if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) goto error;
+ break;
+ }
+ p++;
+ } while (p < end);
-/** Lookup a dictionary reference
- *
- * Format is [<proto>].[<attr>]
- *
- * If protocol is omitted lookup is in the current dictionary.
- *
- * FIXME: Probably needs the dictionary equivalent of pass2, to fixup circular dependencies
- * DHCPv4->RADIUS and RADIUS->DHCPv4 are both valid.
- *
- * @param[in] dict The current dictionary we're parsing.
- * @param[in,out] ref The reference string. Pointer advanced to the end of the string.
- * @return
- * - NULL if the reference is invalid.
- * - A local or foreign attribute representing the target of the reference.
- */
-static fr_dict_attr_t const *dict_resolve_reference(fr_dict_t *dict, char const *ref)
-{
- char const *p = ref, *q, *end = p + strlen(ref);
- fr_dict_t *proto_dict;
- fr_dict_attr_t const *da;
+ if (!n) return 0;
/*
- * If the reference does not begin with .
- * then it's a reference into a foreign
- * protocol.
+ * Invert the talloc hierarchy, so that if the unknown
+ * attribute is freed, any unknown parents are also freed.
*/
- if (*p != '.') {
- char buffer[FR_DICT_PROTO_MAX_NAME_LEN + 1];
+ for (our_parent = n->parent, our_ctx = n;
+ our_parent && our_parent->flags.is_unknown;
+ our_parent = our_parent->parent) {
+ fr_dict_attr_t *tmp;
- q = strchr(p, '.');
- if (!q) q = end;
+ memcpy(&tmp, &our_parent, sizeof(tmp)); /* const issues *sigh* */
- if ((size_t)(q - p) > sizeof(buffer)) {
- fr_strerror_printf("Protocol name too long");
- return NULL;
- }
+ our_ctx = talloc_steal(our_ctx, tmp);
+ }
- strlcpy(buffer, p, (q - p + 1));
- p = q;
+ DA_VERIFY(n);
- dict = fr_dict_by_protocol_name(buffer);
- if (!dict) {
- fr_strerror_printf("Referenced protocol \"%s\" not found", buffer);
- return NULL;
- }
+ *out = n;
- return NULL;
- /*
- * If the reference string begins with .
- * then the reference is in the current
- * dictionary.
- */
- } else {
- proto_dict = dict;
- }
+ return end - oid_str;
+}
+
+/** Create a dictionary attribute by name embedded in another string
+ *
+ * Find the first invalid attribute name char in the string pointed to by name.
+ *
+ * Copy the characters between the start of the name string and the first none
+ * #fr_dict_attr_allowed_chars char to a buffer and initialise da as an unknown
+ * attribute.
+ *
+ * @param[in] ctx To allocate unknown #fr_dict_attr_t in.
+ * @param[out] out Where to write the head of the chain unknown
+ * dictionary attributes.
+ * @param[in] parent Attribute to use as the root for resolving OIDs in.
+ * Usually the root of a protocol dictionary.
+ * @param[in] name string start.
+ * @return
+ * - <= 0 on failure.
+ * - The number of bytes of name consumed on success.
+ */
+ssize_t fr_dict_unknown_afrom_oid_substr(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+ fr_dict_attr_t const *parent, char const *name)
+{
+ char const *p;
+ size_t len;
+ char buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
+ ssize_t slen;
+
+ if (!name || !*name) return 0;
/*
- * If there's a '.' after the dictionary, then
- * the reference is to a specific attribute.
+ * Advance p until we get something that's not part of
+ * the dictionary attribute name.
*/
- if (*p == '.') {
- p++;
+ for (p = name; fr_dict_attr_allowed_chars[(int)*p] || (*p == '.') || (*p == '-'); p++);
- da = fr_dict_attr_by_name_substr(proto_dict, &p);
- if (!da) {
- fr_strerror_printf("Referenced attribute \"%s\" not found", p);
- return NULL;
- }
+ len = p - name;
+ if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
+ fr_strerror_printf("Attribute name too long");
+ return 0;
}
-
- da = fr_dict_root(proto_dict);
- if (!da) {
- fr_strerror_printf("Dictionary missing attribute root");
- return NULL;
+ if (len == 0) {
+ fr_strerror_printf("Invalid attribute name");
+ return 0;
}
+ strlcpy(buffer, name, len + 1);
- return da;
+ slen = fr_dict_unknown_afrom_oid_str(ctx, out, parent, buffer);
+ if (slen <= 0) return slen;
+
+ return p - name;
}
-/*
- * Process the ATTRIBUTE command
+
+/** Check to see if we can convert a nested TLV structure to known attributes
+ *
+ * @param[in] dict to search in.
+ * @param[in] da Nested tlv structure to convert.
+ * @return
+ * - NULL if we can't.
+ * - Known attribute if we can.
*/
-static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
- unsigned int block_vendor, char **argv, int argc,
- fr_dict_attr_flags_t *base_flags)
+fr_dict_attr_t const *fr_dict_attr_known(fr_dict_t *dict, fr_dict_attr_t const *da)
{
- bool oid = false;
+ INTERNAL_IF_NULL(dict);
- unsigned int vendor = 0;
- unsigned int attr;
+ if (!da->flags.is_unknown) return da; /* It's known */
- int type;
- unsigned int length;
- fr_dict_attr_flags_t flags;
- fr_dict_attr_t const *ref = NULL;
- char *p;
+ if (da->parent) {
+ fr_dict_attr_t const *parent;
- if ((argc < 3) || (argc > 4)) {
- fr_strerror_printf("Invalid ATTRIBUTE syntax");
- return -1;
- }
+ parent = fr_dict_attr_known(dict, da->parent);
+ if (!parent) return NULL;
- /*
- * Dictionaries need to have real names, not shitty ones.
- */
- if (strncmp(argv[0], "Attr-", 5) == 0) {
- fr_strerror_printf("Invalid ATTRIBUTE name");
- return -1;
+ return fr_dict_attr_child_by_num(parent, da->attr);
}
- memcpy(&flags, base_flags, sizeof(flags));
+ if (dict->root == da) return dict->root;
+ return NULL;
+}
- /*
- * Look for OIDs before doing anything else.
- */
- if (!strchr(argv[1], '.')) {
- /*
- * Parse out the attribute number
- */
- if (!dict_read_sscanf_i(&attr, argv[1])) {
- fr_strerror_printf("Invalid ATTRIBUTE number");
- return -1;
- }
+static void dict_snprint_flags(char *out, size_t outlen, fr_dict_attr_flags_t flags)
+{
+ char *p = out, *end = p + outlen;
+ size_t len;
- /*
- * Got an OID string. Every attribute should exist other
- * than the leaf, which is the attribute we're defining.
- */
- } else {
- ssize_t slen;
+ out[0] = '\0';
- oid = true;
+#define FLAG_SET(_flag) \
+do { \
+ if (flags._flag) {\
+ p += strlcpy(p, STRINGIFY(_flag)",", end - p);\
+ if (p >= end) return;\
+ }\
+} while (0)
- slen = fr_dict_attr_by_oid(dict, &parent, &attr, argv[1]);
- if (slen <= 0) return -1;
+ FLAG_SET(is_root);
+ FLAG_SET(is_unknown);
+ FLAG_SET(is_raw);
+ FLAG_SET(internal);
+ FLAG_SET(has_tag);
+ FLAG_SET(array);
+ FLAG_SET(has_value);
+ FLAG_SET(concat);
+ FLAG_SET(virtual);
+ FLAG_SET(compare);
- if (!fr_cond_assert(parent)) return -1; /* Should have provided us with a parent */
+ if (flags.encrypt) {
+ p += snprintf(p, end - p, "encrypt=%i,", flags.encrypt);
+ if (p >= end) return;
}
- /*
- * Some types can have fixed length
- */
- p = strchr(argv[2], '[');
- if (p) *p = '\0';
+ if (flags.length) {
+ p += snprintf(p, end - p, "length=%i,", flags.length);
+ if (p >= end) return;
+ }
+
+ if (!out[0]) return;
/*
- * find the type of the attribute.
+ * Trim the comma
*/
- type = fr_str2int(dict_attr_types, argv[2], -1);
- if (type < 0) {
- fr_strerror_printf("Unknown data type '%s'", argv[2]);
- return -1;
- }
+ len = strlen(out);
+ if (out[len - 1] == ',') out[len - 1] = '\0';
+}
- if (p) {
- char *q;
+void fr_dict_print(fr_dict_attr_t const *da, int depth)
+{
+ char buff[256];
+ unsigned int i;
+ char const *name;
- q = strchr(p + 1, ']');
- if (!q) {
- fr_strerror_printf("Invalid format for '%s[...]'", argv[2]);
- return -1;
- }
+ dict_snprint_flags(buff, sizeof(buff), da->flags);
- *q = '\0';
+ switch (da->type) {
+ case FR_TYPE_VSA:
+ name = "VSA";
+ break;
- if (!dict_read_sscanf_i(&length, p + 1)) {
- fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
- return -1;
- }
+ case FR_TYPE_EXTENDED:
+ name = "EXTENDED";
+ break;
- if ((length == 0) || (length > 253)) {
- fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
- return -1;
- }
+ case FR_TYPE_TLV:
+ name = "TLV";
+ break;
- flags.length = length;
- }
+ case FR_TYPE_EVS:
+ name = "EVS";
+ break;
- /*
- * Parse options.
- */
- if (argc >= 4) {
- char *q, *v;
+ case FR_TYPE_VENDOR:
+ name = "VENDOR";
+ break;
- p = argv[3];
- do {
- char key[64], value[256];
+ case FR_TYPE_LONG_EXTENDED:
+ name = "LONG EXTENDED";
+ break;
- q = strchr(p, ',');
- if (!q) q = p + strlen(p);
+ case FR_TYPE_STRUCT:
+ name = "STRUCT";
+ break;
- /*
- * Nothing after the trailing comma
- */
- if (p == q) break;
+ default:
+ name = "ATTRIBUTE";
+ break;
+ }
- if ((size_t)(q - p) > sizeof(key)) {
- fr_strerror_printf("ATTRIBUTE option key too long");
- return -1;
- }
+ printf("%u%.*s%s \"%s\" vendor: %x (%u), num: %x (%u), type: %s, flags: %s\n", da->depth, depth,
+ "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t", name, da->name,
+ fr_dict_vendor_num_by_da(da), fr_dict_vendor_num_by_da(da), da->attr, da->attr,
+ fr_int2str(dict_attr_types, da->type, "?Unknown?"), buff);
- /*
- * Copy key and value
- */
- if (!(v = memchr(p, '=', q - p)) || (v == q)) {
- value[0] = '\0';
- strlcpy(key, p, (q - p) + 1);
- } else {
- strlcpy(key, p, (v - p) + 1);
- strlcpy(value, v + 1, q - v);
- }
+ if (da->children) for (i = 0; i < talloc_array_length(da->children); i++) {
+ if (da->children[i]) {
+ fr_dict_attr_t const *bin;
- /*
- * Boolean flag, means this is a tagged
- * attribute.
- */
- if (strcmp(key, "has_tag") == 0) {
- flags.has_tag = 1;
+ for (bin = da->children[i]; bin; bin = bin->next) fr_dict_print(bin, depth + 1);
+ }
+ }
+}
- /*
- * Encryption method.
- */
- } else if (strcmp(key, "encrypt") == 0) {
- char *qq;
+/** Find a common ancestor that two TLV type attributes share
+ *
+ * @param[in] a first TLV attribute.
+ * @param[in] b second TLV attribute.
+ * @param[in] is_ancestor Enforce a->b relationship (a is parent or ancestor of b).
+ * @return
+ * - Common ancestor if one exists.
+ * - NULL if no common ancestor exists.
+ */
+fr_dict_attr_t const *fr_dict_parent_common(fr_dict_attr_t const *a, fr_dict_attr_t const *b, bool is_ancestor)
+{
+ unsigned int i;
+ fr_dict_attr_t const *p_a, *p_b;
- flags.encrypt = strtol(value, &qq, 0);
- if (*qq) {
- fr_strerror_printf("Invalid encrypt value \"%s\"", value);
- return -1;
- }
+ if (!a || !b) return NULL;
- /*
- * Marks the attribute up as internal.
- * This means it can use numbers outside of the allowed
- * protocol range, and also means it will not be included
- * in replies or proxy requests.
- */
- } else if (strcmp(key, "internal") == 0) {
- flags.internal = 1;
+ if (is_ancestor && (b->depth <= a->depth)) return NULL;
- } else if (strcmp(key, "array") == 0) {
- flags.array = 1;
+ /*
+ * Find a common depth to work back from
+ */
+ if (a->depth > b->depth) {
+ p_b = b;
+ for (p_a = a, i = a->depth - b->depth; p_a && (i > 0); p_a = p_a->parent, i--);
+ } else if (a->depth < b->depth) {
+ p_a = a;
+ for (p_b = b, i = b->depth - a->depth; p_b && (i > 0); p_b = p_b->parent, i--);
+ } else {
+ p_a = a;
+ p_b = b;
+ }
- } else if (strcmp(key, "concat") == 0) {
- flags.concat = 1;
+ while (p_a && p_b) {
+ if (p_a == p_b) return p_a;
- } else if (strcmp(key, "virtual") == 0) {
- flags.virtual = 1;
+ p_a = p_a->parent;
+ p_b = p_b->parent;
+ }
- } else if (strcmp(key, "reference") == 0) {
- ref = dict_resolve_reference(dict, value);
- if (!ref) return -1;
- flags.is_reference = 1;
+ return NULL;
+}
- /*
- * The only thing is the vendor name, and it's a known name:
- * allow it.
- */
- } else if ((argv[3] == p) && (*q == '\0')) {
- if (oid) {
- fr_strerror_printf("ATTRIBUTE cannot use a 'vendor' flag");
- return -1;
- }
-
- if (block_vendor) {
- fr_strerror_printf("Vendor flag inside of 'BEGIN-VENDOR' is not allowed");
- return -1;
- }
+/** Process a single OID component
+ *
+ * @param[out] out Value of component.
+ * @param[in] oid string to parse.
+ * @return
+ * - 0 on success.
+ * - -1 on format error.
+ */
+int fr_dict_oid_component(unsigned int *out, char const **oid)
+{
+ char const *p = *oid;
+ char *q;
+ unsigned long num;
- vendor = fr_dict_vendor_by_name(dict, key);
- if (!vendor) goto unknown;
- break;
+ *out = 0;
- } else {
- unknown:
- fr_strerror_printf("Unknown option '%s'", key);
- return -1;
- }
- p = q;
- } while (*p++);
+ num = strtoul(p, &q, 10);
+ if ((p == q) || (num == ULONG_MAX)) {
+ fr_strerror_printf("Invalid OID component \"%s\" (%lu)", p, num);
+ return -1;
}
-#ifdef WITH_DICTIONARY_WARNINGS
- /*
- * Hack to help us discover which vendors have illegal
- * attributes.
- */
- if (!vendor && (attr < 256) &&
- !strstr(fn, "rfc") && !strstr(fn, "illegal")) {
- fprintf(stderr, "WARNING: Illegal Attribute %s in %s\n",
- argv[0], fn);
- }
-#endif
+ switch (*q) {
+ case '\0':
+ case '.':
+ *oid = q;
+ *out = (unsigned int)num;
- /*
- * Add in a normal attribute
- */
- if (!ref) {
- if (fr_dict_attr_add(dict, parent, argv[0], attr, type, &flags) < 0) return -1;
- /*
- * Add in a special reference attribute
- */
- } else {
- if (dict_attr_ref_add(dict, parent, argv[0], attr, type, &flags, ref) < 0) return -1;
- }
+ return 0;
- return 0;
+ default:
+ fr_strerror_printf("Unexpected text after OID component");
+ *out = 0;
+ return -1;
+ }
}
-/*
- * Process the ATTRIBUTE command, where it only has a name.
+/** Build the tlv_stack for the specified DA and encode the path in OID form
+ *
+ * @param[out] out Where to write the OID.
+ * @param[in] outlen Length of the output buffer.
+ * @param[in] ancestor If not NULL, only print OID portion between
+ * ancestor and da.
+ * @param[in] da to print OID string for.
+ * @return the number of bytes written to the buffer.
*/
-static int dict_read_process_named_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
- char **argv, int argc,
- fr_dict_attr_flags_t const *base_flags)
+size_t fr_dict_print_attr_oid(char *out, size_t outlen,
+ fr_dict_attr_t const *ancestor, fr_dict_attr_t const *da)
{
- int type;
- unsigned int attr;
- uint32_t hash;
- char *p, normalized[512];
+ size_t len;
+ char *p = out, *end = p + outlen;
+ int i;
+ int depth = 0;
+ fr_dict_attr_t const *tlv_stack[FR_DICT_MAX_TLV_STACK + 1];
- if (argc != 2) {
- fr_strerror_printf("Invalid ATTRIBUTE syntax");
- return -1;
- }
+ if (!outlen) return 0;
/*
- * find the type of the attribute.
+ * If the ancestor and the DA match, there's
+ * no OID string to print.
*/
- type = fr_str2int(dict_attr_types, argv[1], -1);
- if (type < 0) {
- fr_strerror_printf("Unknown data type '%s'", argv[1]);
- return -1;
+ if (ancestor == da) {
+ out[0] = '\0';
+ return 0;
}
- strlcpy(normalized, argv[0], sizeof(normalized));
- for (p = normalized; *p != '\0'; p++) {
- if (isupper((int) *p)) {
- *p = tolower((int) *p);
+ fr_proto_tlv_stack_build(tlv_stack, da);
+
+ if (ancestor) {
+ if (tlv_stack[ancestor->depth - 1] != ancestor) {
+ fr_strerror_printf("Attribute \"%s\" is not a descendent of \"%s\"", da->name, ancestor->name);
+ return -1;
}
+ depth = ancestor->depth;
}
- hash = fr_hash_string(normalized);
- attr = hash;
-
/*
- * Add it in.
+ * We don't print the ancestor, we print the OID
+ * between it and the da.
*/
- if (fr_dict_attr_add(dict, parent, argv[0], attr, type, base_flags) < 0) return -1;
+ len = snprintf(p, end - p, "%u", tlv_stack[depth]->attr);
+ if ((p + len) >= end) return p - out;
+ p += len;
- return 0;
+
+ for (i = depth + 1; i < (int)da->depth; i++) {
+ len = snprintf(p, end - p, ".%u", tlv_stack[i]->attr);
+ if ((p + len) >= end) return p - out;
+ p += len;
+ }
+
+ return p - out;
}
-/** Process a value alias
+/** Get the leaf attribute of an OID string
+ *
+ * @note On error, vendor will be set (if present), parent will be the
+ * maximum depth we managed to resolve to, and attr will be the child
+ * we failed to resolve.
*
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[out] attr Number we parsed.
+ * @param[in,out] parent attribute (or root of dictionary).
+ * Will be updated to the parent directly beneath the leaf.
+ * @param[in] oid string to parse.
+ * @return
+ * - > 0 on success (number of bytes parsed).
+ * - <= 0 on parse error (negative offset of parse error).
*/
-static int dict_read_process_value(fr_dict_t *dict, char **argv, int argc)
+ssize_t fr_dict_attr_by_oid(fr_dict_t *dict, fr_dict_attr_t const **parent, unsigned int *attr, char const *oid)
{
- static fr_dict_attr_t const *last_attr = NULL;
- fr_dict_attr_t const *da;
- fr_value_box_t value;
+ char const *p = oid;
+ unsigned int num = 0;
+ ssize_t slen;
- if (argc != 3) {
- fr_strerror_printf("Invalid VALUE syntax");
- return -1;
+ if (!fr_cond_assert(parent)) return 0;
+ INTERNAL_IF_NULL(dict);
+
+ *attr = 0;
+
+ if (fr_dict_oid_component(&num, &p) < 0) return oid - p;
+
+ /*
+ * Record progress even if we error out.
+ *
+ * Don't change this, you will break things.
+ */
+ *attr = num;
+
+ switch ((*parent)->type) {
+ case FR_TYPE_STRUCTURAL:
+ break;
+
+ default:
+ fr_strerror_printf("Attribute %s (%i) is not a TLV, so cannot contain a child attribute. "
+ "Error at sub OID \"%s\"", (*parent)->name, (*parent)->attr, oid);
+ return 0; /* We parsed nothing */
}
/*
- * Most VALUEs are bunched together by ATTRIBUTE. We can
- * save a lot of lookups on dictionary initialization by
- * caching the last attribute.
+ * If it's not a vendor type, it must be between 0..8*type_size
+ *
+ * @fixme: find the TLV parent, and check it's size
*/
- if (last_attr && (strcasecmp(argv[0], last_attr->name) == 0)) {
- da = last_attr;
- } else {
- da = fr_dict_attr_by_name(dict, argv[0]);
- last_attr = da;
+ if (((*parent)->type != FR_TYPE_VENDOR) && ((*parent)->type != FR_TYPE_VSA) && !(*parent)->flags.is_root &&
+ (num > UINT8_MAX)) {
+ fr_strerror_printf("TLV attributes must be between 0..255 inclusive");
+ return 0;
}
+ switch (p[0]) {
/*
- * Remember which attribute is associated with this
- * value. This allows us to define enum
- * values before the attribute exists, and fix them
- * up later.
+ * We've not hit the leaf yet, so the attribute must be
+ * defined already.
*/
- if (!da) {
- dict_enum_fixup_t *fixup;
+ case '.':
+ {
+ fr_dict_attr_t const *child;
+ p++;
- fixup = talloc_zero(dict->pool, dict_enum_fixup_t);
- if (!fixup) {
- oom:
- talloc_free(fixup);
- fr_strerror_printf("Out of memory");
- return -1;
+ child = fr_dict_attr_child_by_num(*parent, num);
+ if (!child) {
+ fr_strerror_printf("Unknown attribute \"%i\" in OID string \"%s\"", num, oid);
+ return 0; /* We parsed nothing */
}
- fixup->attribute = talloc_strdup(fixup, argv[0]);
- if (!fixup->attribute) goto oom;
- fixup->alias = talloc_strdup(fixup, argv[1]);
- if (!fixup->alias) goto oom;
- fixup->value = talloc_strdup(fixup, argv[2]);
- if (!fixup->value) goto oom;
/*
- * Insert to the head of the list.
+ * Record progress even if we error out.
+ *
+ * Don't change this, you will break things.
*/
- fixup->next = dict->enum_fixup;
- dict->enum_fixup = fixup;
+ *parent = child;
- return 0;
+ slen = fr_dict_attr_by_oid(dict, parent, attr, p);
+ if (slen <= 0) return slen - (p - oid);
+ return slen + (p - oid);
}
- {
- fr_type_t type = da->type; /* Might change - Stupid combo IP */
+ /*
+ * Hit the leaf, this is the attribute we need to define.
+ */
+ case '\0':
+ *attr = num;
+ return p - oid;
- if (fr_value_box_from_str(NULL, &value, &type, NULL, argv[2], -1, '\0', false) < 0) {
- fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
- return -1;
- }
- }
-
- if (fr_dict_enum_add_alias(da, argv[1], &value, false, true) < 0) {
- fr_value_box_clear(&value);
- return -1;
+ default:
+ fr_strerror_printf("Malformed OID string, got trailing garbage '%s'", p);
+ return oid - p;
}
- fr_value_box_clear(&value);
-
- return 0;
}
-/*
- * Process the FLAGS command
+/** Return the root attribute of a dictionary
+ *
+ * @param dict to return root for.
+ * @return the root attribute of the dictionary.
*/
-static int dict_read_process_flags(UNUSED fr_dict_t *dict, char **argv, int argc,
- fr_dict_attr_flags_t *base_flags)
+fr_dict_attr_t const *fr_dict_root(fr_dict_t const *dict)
{
- bool sense = true;
-
- if (argc == 1) {
- char *p;
+ return dict->root;
+}
- p = argv[0];
- if (*p == '!') {
- sense = false;
- p++;
- }
+/** Lookup a protocol by its name
+ *
+ * @param[in] name of the protocol to locate.
+ * @return
+ * - Attribute matching name.
+ * - NULL if no matching protocolibute could be found.
+ */
+fr_dict_t *fr_dict_by_protocol_name(char const *name)
+{
+ fr_dict_attr_t root = { .name = name };
+ fr_dict_t find = { .root = &root };
- if (strcmp(p, "internal") == 0) {
- base_flags->internal = sense;
- return 0;
- }
- }
+ if (!protocol_by_name || !name) return NULL;
- fr_strerror_printf("Invalid FLAGS syntax");
- return -1;
+ return fr_hash_table_finddata(protocol_by_name, &find);
}
-static int dict_read_parse_format(char const *format, unsigned int *pvalue, int *ptype, int *plength,
- bool *pcontinuation)
+/** Lookup a protocol by its number.
+ *
+ * Returns the #fr_dict_t belonging to the protocol with the specified number
+ * if any have been registered.
+ *
+ * @param[in] num to search for.
+ * @return dictionary representing the protocol (if it exists).
+ */
+fr_dict_t *fr_dict_by_protocol_num(unsigned int num)
{
- char const *p;
- int type, length;
- bool continuation = false;
+ fr_dict_t find;
+ fr_dict_attr_t root;
- if (strncasecmp(format, "format=", 7) != 0) {
- fr_strerror_printf("Invalid format for VENDOR. Expected 'format=', got '%s'",
- format);
- return -1;
- }
+ if (!protocol_by_num) return NULL;
- p = format + 7;
- if ((strlen(p) < 3) ||
- !isdigit((int)p[0]) ||
- (p[1] != ',') ||
- !isdigit((int)p[2]) ||
- (p[3] && (p[3] != ','))) {
- fr_strerror_printf("Invalid format for VENDOR. Expected text like '1,1', got '%s'",
- p);
- return -1;
- }
+ memset(&find, 0, sizeof(find));
+ memset(&root, 0, sizeof(root));
- type = (int)(p[0] - '0');
- length = (int)(p[2] - '0');
+ find.root = &root;
+ root.attr = num;
- if ((type != 1) && (type != 2) && (type != 4)) {
- fr_strerror_printf("Invalid type value %d for VENDOR", type);
- return -1;
- }
+ return fr_hash_table_finddata(protocol_by_num, &find);
+}
- if ((length != 0) && (length != 1) && (length != 2)) {
- fr_strerror_printf("Ivalid length value %d for VENDOR", length);
- return -1;
- }
+/** Dictionary/attribute ctx struct
+ *
+ */
+typedef struct {
+ fr_dict_t *found_dict; //!< Dictionary attribute found in.
+ fr_dict_attr_t const *found_da; //!< Resolved attribute.
+ fr_dict_attr_t const *find; //!< Attribute to find.
+} dict_attr_search_t;
- if (p[3] == ',') {
- if (!p[4]) {
- fr_strerror_printf("Invalid format for VENDOR. Expected text like '1,1', got '%s'",
- p);
- return -1;
- }
+/** Search for an attribute name in all dictionaries
+ *
+ * @param[in] ctx Attribute to search for.
+ * @param[in] data Dictionary to search in.
+ * @return
+ * - 0 if attribute not found in dictionary.
+ * - 1 if attribute found in dictionary.
+ */
+static int _dict_attr_find_in_dicts(void *ctx, void *data)
+{
+ dict_attr_search_t *search = ctx;
+ fr_dict_t *dict;
- if ((p[4] != 'c') ||
- (p[5] != '\0')) {
- fr_strerror_printf("Invalid format for VENDOR. Expected text like '1,1', got '%s'",
- p);
- return -1;
- }
- continuation = true;
+ if (!data) return 0; /* We get called with NULL data */
- if ((*pvalue != VENDORPEC_WIMAX) ||
- (type != 1) || (length != 1)) {
- fr_strerror_printf("Only WiMAX VSAs can have continuations");
- return -1;
- }
- }
+ dict = talloc_get_type_abort(data, fr_dict_t);
- *ptype = type;
- *plength = length;
- *pcontinuation = continuation;
- return 0;
+ search->found_da = fr_hash_table_finddata(dict->attributes_by_name, search->find);
+ if (!search->found_da) return 0;
+
+ search->found_dict = data;
+
+ return 1;
}
-/** Register the specified dictionary as a protocol dictionary
+/** Attempt to locate the protocol dictionary containing an attribute
*
- * Allows vendor and TLV context to persist across $INCLUDEs
+ * @note Unlike fr_dict_by_attr_name, doesn't search through all the dictionaries,
+ * just uses the fr_dict_attr_t hierarchy and the talloc hierarchy to locate
+ * the dictionary (much much faster and more scalable).
+ *
+ * @param[in] da To get the containing dictionary for.
+ * @return
+ * - The dictionary containing da.
+ * - NULL.
*/
-static int dict_read_process_protocol(char **argv, int argc)
+fr_dict_t *fr_dict_by_da(fr_dict_attr_t const *da)
{
- unsigned int value;
- unsigned int type_size = 1;
- fr_dict_t *dict;
+ fr_dict_attr_t const *da_p = da;
- if ((argc < 2) || (argc > 3)) {
- fr_strerror_printf("Missing arguments after PROTOCOL. Expected PROTOCOL <num> <name>");
- return -1;
+ while (da_p->parent) {
+ da_p = da_p->parent;
+ DA_VERIFY(da_p);
}
- /*
- * Validate all entries
- */
- if (!dict_read_sscanf_i(&value, argv[1])) {
- fr_strerror_printf("Invalid number '%s' following PROTOCOL", argv[1]);
- return -1;
+ if (!da_p->flags.is_root) {
+ fr_strerror_printf("%s: Attribute %s has not been inserted into a dictionary", __FUNCTION__, da->name);
+ return NULL;
}
/*
- * Look for a format statement. This may specify the
- * type length of the protocol's types.
+ * Parent of the root attribute must
+ * be the dictionary.
*/
- if (argc == 3) {
- char const *p;
- char *q;
-
- if (strncasecmp(argv[2], "format=", 7) != 0) {
- fr_strerror_printf("Invalid format for PROTOCOL. Expected 'format=', got '%s'", argv[2]);
- return -1;
- }
- p = argv[2] + 7;
+ return talloc_get_type_abort(talloc_parent(da_p), fr_dict_t);
+}
- type_size = strtoul(p, &q, 10);
- if (q != (p + strlen(p))) {
- fr_strerror_printf("Found trailing garbage '%s' after format specifier", p);
- return -1;
- }
- }
+/** Attempt to locate the protocol dictionary containing an attribute
+ *
+ * @note This is O(n) and will only return the first instance of the dictionary.
+ *
+ * @param[out] found the attribute that was resolved from the name.
+ * @param[in] name the name of the attribute.
+ * @return
+ * - the dictionary the attribute was found in.
+ * - NULL if an attribute with the specified name wasn't found in any dictionary.
+ */
+fr_dict_t *fr_dict_by_attr_name(fr_dict_attr_t const **found, char const *name)
+{
+ fr_dict_attr_t find = {
+ .name = name
+ };
+ dict_attr_search_t search = {
+ .find = &find
+ };
+ int ret;
- dict = fr_dict_by_protocol_num(value);
- if (dict) {
- if (dict->root->flags.type_size != type_size) {
- fr_strerror_printf("Conflicting flags for PROTOCOL \"%s\"", dict->root->name);
- return -1;
- }
- return 0;
- }
+ *found = NULL;
- dict = dict_alloc(NULL);
+ if (!name || !*name) return NULL;
- /*
- * Set the root attribute with the protocol name
- */
- dict_root_set(dict, argv[0], value);
+ ret = fr_hash_table_walk(protocol_by_name, _dict_attr_find_in_dicts, &search);
+ if (ret == 0) return NULL;
- if (dict_protocol_add(dict) < 0) return -1;
+ if (found) *found = search.found_da;
- return 0;
+ return search.found_dict;
}
-/*
- * Process the VENDOR command
+/** Look up a vendor by its name
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] name to search for.
+ * @return
+ * - The vendor.
+ * - NULL if no vendor with that name was regitered for this protocol.
*/
-static int dict_read_process_vendor(fr_dict_t *dict, char **argv, int argc)
+int fr_dict_vendor_by_name(fr_dict_t const *dict, char const *name)
{
- unsigned int value;
- int type, length;
- bool continuation = false;
- fr_dict_vendor_t const *dv;
- fr_dict_vendor_t *mutable;
-
- if ((argc < 2) || (argc > 3)) {
- fr_strerror_printf("Invalid VENDOR syntax");
- return -1;
- }
+ fr_dict_vendor_t find = { .name = name }, *found;
- /*
- * Validate all entries
- */
- if (!dict_read_sscanf_i(&value, argv[1])) {
- fr_strerror_printf("Invalid number in VENDOR");
- return -1;
- }
+ if (!name) return 0;
+ INTERNAL_IF_NULL(dict);
- /* Create a new VENDOR entry for the list */
- if (dict_vendor_add(dict, argv[0], value) < 0) return -1;
+ found = fr_hash_table_finddata(dict->vendors_by_name, &find);
+ if (!found) return 0;
- /*
- * Look for a format statement. Allow it to over-ride the hard-coded formats below.
- */
- if (argc == 3) {
- if (dict_read_parse_format(argv[2], &value, &type, &length, &continuation) < 0) return -1;
+ return found->vendorpec;
+}
- } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */
- type = 4;
- length = 0;
+/** Look up a vendor by its PEN
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] vendorpec to search for.
+ * @return
+ * - The vendor.
+ * - NULL if no vendor with that number was regitered for this protocol.
+ */
+fr_dict_vendor_t const *fr_dict_vendor_by_num(fr_dict_t const *dict, int vendorpec)
+{
+ fr_dict_vendor_t dv;
- } else if (value == VENDORPEC_LUCENT) {
- type = 2;
- length = 1;
+ INTERNAL_IF_NULL(dict);
- } else if (value == VENDORPEC_STARENT) {
- type = 2;
- length = 2;
+ dv.vendorpec = vendorpec;
- } else {
- type = length = 1;
- }
+ return fr_hash_table_finddata(dict->vendors_by_num, &dv);
+}
- dv = fr_dict_vendor_by_num(dict, value);
- if (!dv) {
- fr_strerror_printf("Failed adding format for VENDOR");
- return -1;
- }
+/** Look up a vendor by one of its child attributes
+ *
+ * @param[in] da The vendor attribute.
+ * @return
+ * - The vendor.
+ * - NULL if no vendor with that number was regitered for this protocol.
+ */
+fr_dict_vendor_t const *fr_dict_vendor_by_da(fr_dict_attr_t const *da)
+{
+ fr_dict_t *dict;
+ fr_dict_vendor_t dv;
- memcpy(&mutable, &dv, sizeof(mutable));
+ dv.vendorpec = fr_dict_vendor_num_by_da(da);
+ if (!dv.vendorpec) return NULL;
- mutable->type = type;
- mutable->length = length;
- mutable->flags = continuation;
+ dict = fr_dict_by_da(da);
- return 0;
+ return fr_hash_table_finddata(dict->vendors_by_num, &dv);
}
-/** Parse a dictionary file
+/** Return the vendor that parents this attribute
*
- * @param[in] ctx Contains the current state of the dictionary parser.
- * Used to track what PROTOCOL, VENDOR or TLV block
- * we're in. Block context changes in $INCLUDEs should
- * not affect the context of the including file.
- * @param[in] dir_name Directory containing the dictionary we're loading.
- * @param[in] filename we're parsing.
- * @param[in] src_file The including file.
- * @param[in] src_line Line on which the $INCLUDE or $INCLUDE- statement was found.
+ * @note Uses the dictionary hierachy to determine the parent
+ *
+ * @param[in] da The dictionary attribute to find parent for.
* @return
- * - 0 on success.
- * - -1 on failure.
+ * - NULL if the attribute has no vendor.
+ * - A fr_dict_attr_t representing this attribute's associated vendor.
*/
-static int _dict_from_file(dict_from_file_ctx_t *ctx,
- char const *dir_name, char const *filename,
- char const *src_file, int src_line)
+fr_dict_attr_t const *fr_dict_vendor_attr_by_da(fr_dict_attr_t const *da)
{
- FILE *fp;
- char dir[256], fn[256];
- char buf[256];
- char *p;
- int line = 0;
-
- struct stat statbuf;
- char *argv[MAX_ARGV];
- int argc;
- fr_dict_attr_t const *da;
-
- /*
- * Base flags are only set for the current file
- */
- fr_dict_attr_flags_t base_flags;
+ fr_dict_attr_t const *da_p = da;
- if (!fr_cond_assert(!ctx->dict->root || ctx->parent)) return -1;
+ VERIFY_DA(da);
- if ((strlen(dir_name) + 3 + strlen(filename)) > sizeof(dir)) {
- fr_strerror_printf_push("%s: Filename name too long", "Error reading dictionary");
- return -1;
+ while (da_p->parent) {
+ if (da_p->type == FR_TYPE_VENDOR) break;
+ da_p = da_p->parent;
}
+ if (da_p->type != FR_TYPE_VENDOR) return NULL;
- /*
- * If it's an absolute dir, forget the parent dir,
- * and remember the new one.
- *
- * If it's a relative dir, tack on the current filename
- * to the parent dir. And use that.
- */
- if (!FR_DIR_IS_RELATIVE(filename)) {
- strlcpy(dir, filename, sizeof(dir));
- p = strrchr(dir, FR_DIR_SEP);
- if (p) {
- p[1] = '\0';
- } else {
- strlcat(dir, "/", sizeof(dir));
- }
+ return da_p;
+}
- strlcpy(fn, filename, sizeof(fn));
- } else {
- strlcpy(dir, dir_name, sizeof(dir));
- p = strrchr(dir, FR_DIR_SEP);
- if (p) {
- if (p[1]) strlcat(dir, "/", sizeof(dir));
- } else {
- strlcat(dir, "/", sizeof(dir));
- }
- strlcat(dir, filename, sizeof(dir));
- p = strrchr(dir, FR_DIR_SEP);
- if (p) {
- p[1] = '\0';
- } else {
- strlcat(dir, "/", sizeof(dir));
- }
+/** Return vendor attribute for the specified dictionary and vendorpec
+ *
+ * @param[in] dict to search for the vendor in.
+ * @param[in] vendor_root of the vendor root attribute. Could be 26 (for example) in RADIUS.
+ * @param[in] vendor to find.
+ * @return
+ * - NULL if vendor does not exist.
+ * - A fr_dict_attr_t representing the vendor in the dictionary hierarchy.
+ */
+fr_dict_attr_t const *fr_dict_vendor_attr_by_num(fr_dict_t const *dict, unsigned int vendor_root, unsigned int vendor)
+{
+ fr_dict_attr_t const *da;
- p = strrchr(filename, FR_DIR_SEP);
- if (p) {
- snprintf(fn, sizeof(fn), "%s%s", dir, p);
- } else {
- snprintf(fn, sizeof(fn), "%s%s", dir, filename);
- }
- }
+ if (!dict) return NULL;
- /*
- * Check if we've loaded this file before. If so, ignore it.
- */
- p = strrchr(fn, FR_DIR_SEP);
- if (p) {
- *p = '\0';
- if (dict_stat_check(ctx->dict, fn, p + 1)) {
- *p = FR_DIR_SEP;
- return 0;
- }
- *p = FR_DIR_SEP;
+ da = fr_dict_attr_child_by_num(fr_dict_root(dict), vendor_root);
+ if (!da) {
+ fr_strerror_printf("Vendor root attribute %i not defined in dict %s", vendor_root, dict->root->name);
+ return NULL;
}
- if ((fp = fopen(fn, "r")) == NULL) {
- if (!src_file) {
- fr_strerror_printf_push("%s: Couldn't open dictionary '%s': %s",
- "Error reading dictionary", fn, fr_syserror(errno));
- } else {
- fr_strerror_printf_push("%s: %s[%d]: Couldn't open dictionary '%s': %s",
- "Error reading dictionary", src_file, src_line, fn, fr_syserror(errno));
- }
- return -2;
- }
+ switch (da->type) {
+ case FR_TYPE_VSA: /* Vendor specific attribute */
+ case FR_TYPE_EVS: /* Extended vendor specific attribute */
+ break;
- /*
- * If fopen works, this works.
- */
- if (stat(fn, &statbuf) < 0) {
- fclose(fp);
- return -1;
+ default:
+ fr_strerror_printf("Wrong type for vendor root, expected '%s' or '%s' got '%s'",
+ fr_int2str(dict_attr_types, FR_TYPE_VSA, "<INVALID>"),
+ fr_int2str(dict_attr_types, FR_TYPE_EVS, "<INVALID>"),
+ fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+ return NULL;
}
- if (!S_ISREG(statbuf.st_mode)) {
- fclose(fp);
- fr_strerror_printf_push("%s: Dictionary '%s' is not a regular file", "Error reading dictionary", fn);
- return -1;
+ da = fr_dict_attr_child_by_num(da, vendor);
+ if (!da) {
+ fr_strerror_printf("Vendor %i not defined", vendor);
+ return NULL;
}
- /*
- * Globally writable dictionaries means that users can control
- * the server configuration with little difficulty.
- */
-#ifdef S_IWOTH
- if ((statbuf.st_mode & S_IWOTH) != 0) {
- fclose(fp);
- fr_strerror_printf_push("%s: Dictionary '%s' is globally writable. Refusing to start "
- "due to insecure configuration", "Error reading dictionary", fn);
- return -1;
+ if (da->type != FR_TYPE_VENDOR) {
+ fr_strerror_printf("Wrong type for vendor, expected '%s' got '%s'",
+ fr_int2str(dict_attr_types, da->type, "<INVALID>"),
+ fr_int2str(dict_attr_types, FR_TYPE_VENDOR, "<INVALID>"));
+ return NULL;
}
-#endif
-
- dict_stat_add(ctx->dict, &statbuf);
- /*
- * Seed the random pool with data.
- */
- fr_rand_seed(&statbuf, sizeof(statbuf));
+ return da;
+}
- memset(&base_flags, 0, sizeof(base_flags));
-
- while (fgets(buf, sizeof(buf), fp) != NULL) {
- line++;
-
- switch (buf[0]) {
- case '#':
- case '\0':
- case '\n':
- case '\r':
- continue;
- }
-
- /*
- * Comment characters should NOT be appearing anywhere but
- * as start of a comment;
- */
- p = strchr(buf, '#');
- if (p) *p = '\0';
-
- argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
- if (argc == 0) continue;
+/** Look up a dictionary attribute by a name embedded in another string
+ *
+ * Find the first invalid attribute name char in the string pointed
+ * to by name.
+ *
+ * Copy the characters between the start of the name string and the first
+ * none #fr_dict_attr_allowed_chars char to a buffer and perform a dictionary lookup
+ * using that value.
+ *
+ * If the attribute exists, advance the pointer pointed to by name
+ * to the first none #fr_dict_attr_allowed_chars char, and return the DA.
+ *
+ * If the attribute does not exist, don't advance the pointer and return
+ * NULL.
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in,out] name string start.
+ * @return
+ * - Attribute matching name.
+ * - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_name_substr(fr_dict_t const *dict, char const **name)
+{
+ fr_dict_attr_t find;
+ fr_dict_attr_t const *da;
+ char const *p;
+ size_t len;
- if (argc == 1) {
- fr_strerror_printf("Invalid entry");
+ if (!name || !*name) return NULL;
+ INTERNAL_IF_NULL(dict);
- error:
- fr_strerror_printf_push("Error reading %s[%d]", fn, line);
- fclose(fp);
- return -1;
- }
+ memset(&find, 0, sizeof(find));
- /*
- * Process VALUE lines.
- */
- if (strcasecmp(argv[0], "VALUE") == 0) {
- if (dict_read_process_value(ctx->dict, argv + 1, argc - 1) == -1) goto error;
- continue;
- }
+ /*
+ * Advance p until we get something that's not part of
+ * the dictionary attribute name.
+ */
+ for (p = *name; fr_dict_attr_allowed_chars[(int)*p]; p++);
- /*
- * Perhaps this is an attribute.
- */
- if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
- if (!base_flags.named) {
- if (dict_read_process_attribute(ctx->dict, ctx->parent, ctx->block_vendor,
- argv + 1, argc - 1,
- &base_flags) == -1) goto error;
- } else {
- if (dict_read_process_named_attribute(ctx->dict, ctx->parent,
- argv + 1, argc - 1,
- &base_flags) == -1) goto error;
- }
- continue;
- }
+ len = p - *name;
+ if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
+ fr_strerror_printf("Attribute name too long");
+ return NULL;
+ }
- /*
- * Process VALUE lines.
- */
- if (strcasecmp(argv[0], "FLAGS") == 0) {
- if (dict_read_process_flags(ctx->dict, argv + 1, argc - 1, &base_flags) == -1) goto error;
- continue;
- }
+ find.name = talloc_bstrndup(NULL, *name, len);
+ if (!find.name) {
+ fr_strerror_printf("Out of memory");
+ return NULL;
+ }
+ da = fr_hash_table_finddata(dict->attributes_by_name, &find);
+ talloc_const_free(find.name);
- /*
- * See if we need to import another dictionary.
- */
- if (strcasecmp(argv[0], "$INCLUDE") == 0) {
- dict_from_file_ctx_t nctx = *ctx;
+ if (!da) {
+ fr_strerror_printf("Unknown attribute '%.*s'", (int) len, *name);
+ return NULL;
+ }
+ *name = p;
- /*
- * Included files operate on a copy of the context
- */
- if (_dict_from_file(&nctx, dir, argv[1], fn, line) < 0) {
- fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
- fclose(fp);
- return -1;
- }
- continue;
- } /* $INCLUDE */
+ return da;
+}
- /*
- * Optionally include a dictionary
- */
- if (strcasecmp(argv[0], "$INCLUDE-") == 0) {
- int rcode = _dict_from_file(ctx, dir, argv[1], fn, line);
+/** Locate a #fr_dict_attr_t by its name
+ *
+ * @note Unlike attribute numbers, attribute names are unique to the dictionary.
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] name of the attribute to locate.
+ * @return
+ * - Attribute matching name.
+ * - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_name(fr_dict_t const *dict, char const *name)
+{
+ fr_dict_attr_t find = { .name = name };
- if (rcode == -2) {
- fr_strerror_printf(NULL); /* delete all errors */
- continue;
- }
+ if (!name) return NULL;
+ INTERNAL_IF_NULL(dict);
- if (rcode < 0) {
- fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
- fclose(fp);
- return -1;
- }
- continue;
- } /* $INCLUDE- */
+ return fr_hash_table_finddata(dict->attributes_by_name, &find);
+}
- /*
- * Process VENDOR lines.
- */
- if (strcasecmp(argv[0], "VENDOR") == 0) {
- if (dict_read_process_vendor(ctx->dict, argv + 1, argc - 1) == -1) goto error;
- continue;
- }
+/** Lookup a #fr_dict_attr_t by its vendor and attribute numbers
+ *
+ * @note This is a deprecated function, new code should use #fr_dict_attr_child_by_num.
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] vendor number of the attribute.
+ * @param[in] attr number of the attribute.
+ * @return
+ * - Attribute matching vendor/attr.
+ * - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_num(fr_dict_t *dict, unsigned int vendor, unsigned int attr)
+{
+ fr_dict_attr_t const *parent;
- /*
- * Process PROTOCOL line. Defines a new protocol.
- */
- if (strcasecmp(argv[0], "PROTOCOL") == 0) {
- if (argc < 2) {
- fr_strerror_printf("Invalid PROTOCOL entry");
- goto error;
- }
- if (dict_read_process_protocol(argv + 1, argc - 1) == -1) goto error;
- continue;
- }
+ INTERNAL_IF_NULL(dict);
- /*
- * Switches the current protocol context
- */
- if (strcasecmp(argv[0], "BEGIN-PROTOCOL") == 0) {
- fr_dict_t *found;
+ if (vendor == 0) return fr_dict_attr_child_by_num(dict->root, attr);
- ctx->old_dict = ctx->dict;
+ parent = fr_dict_attr_child_by_num(dict->root, FR_VENDOR_SPECIFIC);
+ if (!parent) return NULL;
- if (argc != 2) {
- fr_strerror_printf("Invalid BEGIN-PROTOCOL entry");
- goto error;
- }
+ parent = fr_dict_attr_child_by_num(parent, vendor);
+ if (!parent) return NULL;
- found = fr_dict_by_protocol_name(argv[1]);
- if (!found) {
- fr_strerror_printf("Unknown protocol '%s'", argv[1]);
- goto error;
- }
+ return fr_dict_attr_child_by_num(parent, attr);
+}
- ctx->dict = found;
+/** Lookup a attribute by its its vendor and attribute numbers and data type
+ *
+ * @note Only works with FR_TYPE_COMBO_IP
+ *
+ * @param[in] da to look for type variant of.
+ * @param[in] type Variant of attribute to lookup.
+ * @return
+ * - Attribute matching parent/attr/type.
+ * - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_type(fr_dict_attr_t const *da, fr_type_t type)
+{
+ fr_dict_t *dict = fr_dict_by_da(da);
+ fr_dict_attr_t find = {
+ .parent = da->parent,
+ .attr = da->attr,
+ .type = type
+ };
- continue;
- }
+ return fr_hash_table_finddata(dict->attributes_combo, &find);
+}
- /*
- * Switches back to the previous protocol context
- */
- if (strcasecmp(argv[0], "END-PROTOCOL") == 0) {
- fr_dict_t const *found;
+/** Check if a child attribute exists in a parent using a pointer (da)
+ *
+ * @param[in] parent to check for child in.
+ * @param[in] child to look for.
+ * @return
+ * - The child attribute on success.
+ * - NULL if the child attribute does not exist.
+ */
+inline fr_dict_attr_t const *fr_dict_attr_child_by_da(fr_dict_attr_t const *parent, fr_dict_attr_t const *child)
+{
+ fr_dict_attr_t const *bin;
- if (argc != 2) {
- fr_strerror_printf("Invalid END-PROTOCOL entry");
- goto error;
- }
+ VERIFY_DA(parent);
- found = fr_dict_by_protocol_name(argv[1]);
- if (!found) {
- fr_strerror_printf("END-PROTOCOL %s does not refer to a valid protocol", argv[1]);
- goto error;
- }
+ if (!parent->children) return NULL;
- if (found != ctx->dict) {
- fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s",
- argv[1], found->root->name);
- goto error;
- }
+ /*
+ * Only some types can have children
+ */
+ switch (parent->type) {
+ default:
+ return NULL;
- ctx->dict = ctx->old_dict; /* Switch back to the old dictionary */
+ case FR_TYPE_STRUCTURAL:
+ break;
+ }
- continue;
- }
+ /*
+ * Child arrays may be trimmed back to save memory.
+ * Check that so we don't SEGV.
+ */
+ if ((child->attr & 0xff) > talloc_array_length(parent->children)) return NULL;
- /*
- * Switches TLV parent context
- */
- if (strcasecmp(argv[0], "BEGIN-TLV") == 0) {
- fr_dict_attr_t const *common;
+ bin = parent->children[child->attr & 0xff];
+ for (;;) {
+ if (!bin) return NULL;
+ if (bin == child) return bin;
+ bin = bin->next;
+ }
- if ((ctx->block_tlv_depth + 1) > FR_DICT_TLV_NEST_MAX) {
- fr_strerror_printf_push("TLVs are nested too deep");
- goto error;
- }
+ return NULL;
+}
- if (argc != 2) {
- fr_strerror_printf_push("Invalid BEGIN-TLV entry");
- goto error;
- }
+/** Check if a child attribute exists in a parent using an attribute number
+ *
+ * @param[in] parent to check for child in.
+ * @param[in] attr number to look for.
+ * @return
+ * - The child attribute on success.
+ * - NULL if the child attribute does not exist.
+ */
+inline fr_dict_attr_t const *fr_dict_attr_child_by_num(fr_dict_attr_t const *parent, unsigned int attr)
+{
+ fr_dict_attr_t const *bin;
- da = fr_dict_attr_by_name(ctx->dict, argv[1]);
- if (!da) {
- fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
- goto error;
- }
+ VERIFY_DA(parent);
- if (da->type != FR_TYPE_TLV) {
- fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'",
- argv[1],
- fr_int2str(dict_attr_types, da->type, "?Unknown?"));
- goto error;
- }
+ if (!parent->children) return NULL;
- common = fr_dict_parent_common(ctx->parent, da, true);
- if (!common ||
- (common->type == FR_TYPE_VSA) ||
- (common->type == FR_TYPE_EVS)) {
- fr_strerror_printf_push("Attribute '%s' should be a child of '%s'",
- argv[1], ctx->parent->name);
- goto error;
- }
+ /*
+ * Only some types can have children
+ */
+ switch (parent->type) {
+ default:
+ return NULL;
- ctx->block_tlv[ctx->block_tlv_depth++] = ctx->parent;
- ctx->parent = da;
+ case FR_TYPE_STRUCTURAL:
+ break;
+ }
- continue;
- } /* BEGIN-TLV */
+ /*
+ * Child arrays may be trimmed back to save memory.
+ * Check that so we don't SEGV.
+ */
+ if ((attr & 0xff) > talloc_array_length(parent->children)) return NULL;
- /*
- * Switches back to previous TLV parent
- */
- if (strcasecmp(argv[0], "END-TLV") == 0) {
- if (--ctx->block_tlv_depth < 0) {
- fr_strerror_printf_push("Too many END-TLV entries. Mismatch at END-TLV %s", argv[1]);
- goto error;
- }
+ bin = parent->children[attr & 0xff];
+ for (;;) {
+ if (!bin) return NULL;
+ if (bin->attr == attr) return bin;
+ bin = bin->next;
+ }
- if (argc != 2) {
- fr_strerror_printf_push("Invalid END-TLV entry");
- goto error;
- }
+ return NULL;
+}
- da = fr_dict_attr_by_name(ctx->dict, argv[1]);
- if (!da) {
- fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
- goto error;
- }
+/** Lookup the structure representing an enum value in a #fr_dict_attr_t
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] da to search in.
+ * @param[in] value to search for.
+ * @return
+ * - Matching #fr_dict_enum_t.
+ * - NULL if no matching #fr_dict_enum_t could be found.
+ */
+fr_dict_enum_t *fr_dict_enum_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
+{
+ fr_dict_enum_t enumv, *dv;
- if (da != ctx->parent) {
- fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[1],
- ctx->parent->name);
- goto error;
- }
- ctx->parent = ctx->block_tlv[ctx->block_tlv_depth];
- continue;
- } /* END-VENDOR */
+ if (!da) return NULL;
- if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) {
- unsigned int vendor;
- fr_dict_attr_flags_t flags;
+ INTERNAL_IF_NULL(dict);
- fr_dict_attr_t const *vsa_da;
- fr_dict_attr_t const *vendor_da;
- fr_dict_attr_t *new;
- fr_dict_attr_t *mutable;
+ /*
+ * Could be NULL or an unknown attribute, in which case
+ * we want to avoid the lookup gracefully...
+ */
+ if (value->type != da->type) return NULL;
- if (argc < 2) {
- fr_strerror_printf_push("Invalid BEGIN-VENDOR entry");
- goto error;
- }
+ /*
+ * First, look up aliases.
+ */
+ enumv.da = da;
+ enumv.alias = "";
- vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
- if (!vendor) {
- fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
- goto error;
- }
+ /*
+ * Look up the attribute alias target, and use
+ * the correct attribute number if found.
+ */
+ dv = fr_hash_table_finddata(dict->values_by_alias, &enumv);
+ if (dv) enumv.da = dv->da;
- /*
- * Check for extended attr VSAs
- *
- * BEGIN-VENDOR foo format=Foo-Encapsulation-Attr
- */
- if (argc > 2) {
- if (strncmp(argv[2], "format=", 7) != 0) {
- fr_strerror_printf_push("Invalid format %s", argv[2]);
- goto error;
- }
+ enumv.value = value;
- p = argv[2] + 7;
- da = fr_dict_attr_by_name(ctx->dict, p);
- if (!da) {
- fr_strerror_printf_push("Invalid format for BEGIN-VENDOR: Unknown "
- "attribute '%s'", p);
- goto error;
- }
+ return fr_hash_table_finddata(dict->values_by_da, &enumv);
+}
- if (da->type != FR_TYPE_EVS) {
- fr_strerror_printf_push("Invalid format for BEGIN-VENDOR. "
- "Attribute '%s' should be 'evs' but is '%s'", p,
- fr_int2str(dict_attr_types, da->type, "?Unknown?"));
- goto error;
- }
+/** Lookup the name of an enum value in a #fr_dict_attr_t
+ *
+ * @param[in] dict of protocol context we're operating in.
+ * If NULL the internal dictionary will be used.
+ * @param[in] da to search in.
+ * @param[in] value number to search for.
+ * @return
+ * - Name of value.
+ * - NULL if no matching value could be found.
+ */
+char const *fr_dict_enum_alias_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
+{
+ fr_dict_enum_t *dv;
- vsa_da = da;
- } else {
- /*
- * Automagically create Attribute 26
- *
- * This should exist, but in case we're starting without
- * the RFC dictionaries we need to add it in the case
- * it doesn't.
- */
- vsa_da = fr_dict_attr_child_by_num(ctx->parent, FR_VENDOR_SPECIFIC);
- if (!vsa_da) {
- memset(&flags, 0, sizeof(flags));
+ if (!da) return NULL;
- memcpy(&mutable, &ctx->parent, sizeof(mutable));
- new = dict_attr_alloc(mutable, fr_dict_root(ctx->dict), "Vendor-Specific",
- FR_VENDOR_SPECIFIC, FR_TYPE_VSA, &flags);
- dict_attr_child_add(mutable, new);
- vsa_da = new;
- }
- }
+ INTERNAL_IF_NULL(dict);
- /*
- * Create a VENDOR attribute on the fly, either in the context
- * of the EVS attribute, or the VSA (26) attribute.
- */
- vendor_da = fr_dict_attr_child_by_num(vsa_da, vendor);
- if (!vendor_da) {
- memset(&flags, 0, sizeof(flags));
+ dv = fr_dict_enum_by_value(dict, da, value);
+ if (!dv) return "";
- if (vsa_da->type == FR_TYPE_VSA) {
- fr_dict_vendor_t const *dv;
+ return dv->alias;
+}
- dv = fr_dict_vendor_by_num(ctx->dict, vendor);
- if (dv) {
- flags.type_size = dv->type;
- flags.length = dv->length;
+/*
+ * Get a value by its name, keyed off of an attribute.
+ */
+fr_dict_enum_t *fr_dict_enum_by_alias(fr_dict_t *dict, fr_dict_attr_t const *da, char const *alias)
+{
+ fr_dict_enum_t find, *found;
- } else { /* unknown vendor, shouldn't happen */
- flags.type_size = 1;
- flags.length = 1;
- }
+ memset(&find, 0, sizeof(find));
- } else { /* EVS are always "format=1,1" */
- flags.type_size = 1;
- flags.length = 1;
- }
+ if (!alias) return NULL;
- memcpy(&mutable, &vsa_da, sizeof(mutable));
- new = dict_attr_alloc(mutable, ctx->parent, argv[1], vendor, FR_TYPE_VENDOR, &flags);
- dict_attr_child_add(mutable, new);
+ INTERNAL_IF_NULL(dict);
- vendor_da = new;
- }
- ctx->parent = vendor_da;
- ctx->block_vendor = vendor;
- continue;
- } /* BEGIN-VENDOR */
+ find.da = da;
+ find.alias = alias;
- if (strcasecmp(argv[0], "END-VENDOR") == 0) {
- unsigned int vendor;
+ /*
+ * Look up the attribute alias target, and use
+ * the correct attribute number if found.
+ */
+ found = fr_hash_table_finddata(dict->values_by_alias, &find);
+ if (found) find.da = found->da;
- if (argc != 2) {
- fr_strerror_printf_push("Invalid END-VENDOR entry");
- goto error;
- }
+ return fr_hash_table_finddata(dict->values_by_alias, &find);
+}
- vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
- if (!vendor) {
- fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
- goto error;
- }
+/*
+ * String split routine. Splits an input string IN PLACE
+ * into pieces, based on spaces.
+ */
+int fr_dict_str_to_argv(char *str, char **argv, int max_argc)
+{
+ int argc = 0;
- if (vendor != ctx->block_vendor) {
- fr_strerror_printf_push("END-VENDOR '%s' does not match any previous BEGIN-VENDOR",
- argv[1]);
- goto error;
- }
- ctx->parent = ctx->dict->root;
- ctx->block_vendor = 0;
- continue;
- } /* END-VENDOR */
+ while (*str) {
+ if (argc >= max_argc) break;
/*
- * Any other string: We don't recognize it.
+ * Chop out comments early.
*/
- fr_strerror_printf_push("Invalid keyword '%s'", argv[0]);
- goto error;
- }
- fclose(fp);
- return 0;
-}
+ if (*str == '#') {
+ *str = '\0';
+ break;
+ }
-static int dict_from_file(fr_dict_t *dict,
- char const *dir_name, char const *filename,
- char const *src_file, int src_line)
-{
- dict_from_file_ctx_t ctx = {
- .dict = dict,
- .parent = dict->root
- };
+ while ((*str == ' ') ||
+ (*str == '\t') ||
+ (*str == '\r') ||
+ (*str == '\n'))
+ *(str++) = '\0';
- return _dict_from_file(&ctx, dir_name, filename, src_file, src_line);
-}
+ if (!*str) break;
-/*
- * External API for testing
- */
-int fr_dict_parse_str(fr_dict_t *dict, char *buf, fr_dict_attr_t const *parent, unsigned int vendor)
-{
- int argc;
- char *argv[MAX_ARGV];
- fr_dict_attr_flags_t base_flags;
+ argv[argc] = str;
+ argc++;
+
+ while (*str &&
+ (*str != ' ') &&
+ (*str != '\t') &&
+ (*str != '\r') &&
+ (*str != '\n'))
+ str++;
+ }
- INTERNAL_IF_NULL(dict);
+ return argc;
+}
- argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
- if (argc == 0) return 0;
+static int dict_read_sscanf_i(unsigned int *pvalue, char const *str)
+{
+ int rcode = 0;
+ int base = 10;
+ static char const *tab = "0123456789";
- if (strcasecmp(argv[0], "VALUE") == 0) {
- return dict_read_process_value(dict, argv + 1, argc - 1);
+ if ((str[0] == '0') &&
+ ((str[1] == 'x') || (str[1] == 'X'))) {
+ tab = "0123456789abcdef";
+ base = 16;
+
+ str += 2;
}
- if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
- if (!parent) parent = fr_dict_root(dict);
+ while (*str) {
+ char const *c;
- memset(&base_flags, 0, sizeof(base_flags));
+ if (*str == '.') break;
- return dict_read_process_attribute(dict, parent, vendor, argv + 1, argc - 1, &base_flags);
- }
+ c = memchr(tab, tolower((int)*str), base);
+ if (!c) return 0;
- if (strcasecmp(argv[0], "VENDOR") == 0) {
- return dict_read_process_vendor(dict, argv + 1, argc - 1);
+ rcode *= base;
+ rcode += (c - tab);
+ str++;
}
- fr_strerror_printf("Invalid input '%s'", argv[0]);
- return -1;
+ *pvalue = rcode;
+ return 1;
}
-/** Return the root attribute of a dictionary
+/** Parser context for dict_from_file
*
- * @param dict to return root for.
- * @return the root attribute of the dictionary.
+ * Allows vendor and TLV context to persist across $INCLUDEs
*/
-fr_dict_attr_t const *fr_dict_root(fr_dict_t const *dict)
+typedef struct {
+ fr_dict_t *dict; //!< Protocol dictionary we're inserting attributes into.
+ fr_dict_t *old_dict; //!< The dictionary before the current BEGIN-PROTOCOL block.
+
+ unsigned int block_vendor; //!< Vendor block we're inserting attributes into.
+ //!< Can be removed once we remove the vendor field from
+ //!< #fr_dict_attr_t.
+
+ fr_dict_attr_t const *block_tlv[FR_DICT_TLV_NEST_MAX]; //!< Nested TLV block's we're
+ //!< inserting attributes into.
+ int block_tlv_depth; //!< Nested TLV block index we're inserting into.
+
+ fr_dict_attr_t const *parent; //!< Current parent attribute (root/vendor/tlv).
+} dict_from_file_ctx_t;
+
+/** Initialise the global protocol hashes
+ *
+ * @note Must be called before any other dictionary functions.
+ *
+ *
+ * @param[in] ctx to allocate the hashes in.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int dict_global_init(TALLOC_CTX *ctx)
{
- return dict->root;
+ if (protocol_by_name && protocol_by_num) return 0;
+
+ protocol_by_name = fr_hash_table_create(ctx, dict_protocol_name_hash, dict_protocol_name_cmp, NULL);
+ if (!protocol_by_name) {
+ fr_strerror_printf("Failed initializing protocol_by_name hash");
+ return -1;
+ }
+ protocol_by_num = fr_hash_table_create(ctx, dict_protocol_num_hash, dict_protocol_num_cmp, NULL);
+ if (!protocol_by_num) {
+ fr_strerror_printf("Failed initializing protocol_by_num hash");
+ return -1;
+ }
+
+ return 0;
}
-/** Copy a known or unknown attribute to produce an unknown attribute
+/** Set a new root dictionary attribute
*
- * Will copy the complete hierarchy down to the first known attribute.
+ * @note Must only be called once per dictionary.
+ *
+ * @param[in] dict to modify.
+ * @param[in] name of dictionary root.
+ * @param[in] protocol_number The artificial (or IANA allocated) number for the protocol.
+ * This is only used for
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
*/
-fr_dict_attr_t *fr_dict_unknown_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
+static int dict_root_set(fr_dict_t *dict, char const *name, unsigned int proto_number)
{
- fr_dict_attr_t *n, *new_parent = NULL;
- fr_dict_attr_t const *parent;
+ fr_dict_attr_flags_t flags = {
+ .is_root = 1,
+ .type_size = 1,
+ .length = 1
+ };
- if (da->parent->flags.is_unknown) {
- new_parent = fr_dict_unknown_acopy(ctx, da->parent);
- parent = new_parent;
- } else {
- parent = da->parent;
+ if (!fr_cond_assert(!dict->root)) {
+ fr_strerror_printf("Dictionary root already set");
+ return -1;
}
- n = dict_attr_alloc(ctx, parent, da->name, da->attr, da->type, &da->flags);
- n->parent = parent;
- n->depth = da->depth;
+ dict->root = dict_attr_alloc_name(dict, name);
+ if (!dict->root) return -1;
- /*
- * Inverted tallloc hierarchy.
- */
- if (new_parent) talloc_steal(n, parent);
+ dict_attr_init(dict->root, NULL, proto_number, FR_TYPE_TLV, &flags);
+ VERIFY_DA(dict->root);
- return n;
+ return 0;
}
-/** Converts an unknown to a known by adding it to the internal dictionaries.
- *
- * Does not free old #fr_dict_attr_t, that is left up to the caller.
+/** Allocate a new dictionary
*
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] old unknown attribute to add.
+ * @param[in] ctx to allocate dictionary in.
* @return
- * - Existing #fr_dict_attr_t if old was found in a dictionary.
- * - A new entry representing old.
+ * - NULL on memory allocation error.
*/
-fr_dict_attr_t const *fr_dict_unknown_add(fr_dict_t *dict, fr_dict_attr_t const *old)
+static fr_dict_t *dict_alloc(TALLOC_CTX *ctx)
{
- fr_dict_attr_t const *da;
- fr_dict_attr_t const *parent;
- fr_dict_attr_flags_t flags;
+ fr_dict_t *dict;
- if (!old) return NULL;
+ dict = talloc_zero(ctx, fr_dict_t);
+ if (!dict) {
+ error:
+ fr_strerror_printf("Failed allocating memory for dictionary");
+ talloc_free(dict);
+ return NULL;
+ }
- da = fr_dict_attr_by_name(dict, old->name);
- if (da) return da;
+ /*
+ * Pre-Allocate 5MB of pool memory for rapid startup
+ */
+ dict->pool = talloc_pool(dict, (1024 * 1024 * 5));
+ if (!dict->pool) goto error;
/*
- * Define the complete unknown hierarchy
+ * Create the table of vendor by name. There MAY NOT
+ * be multiple vendors of the same name.
*/
- if (old->parent && old->parent->flags.is_unknown) {
- parent = fr_dict_unknown_add(dict, old->parent);
- if (!parent) {
- fr_strerror_printf_push("Failed adding parent \"%s\"", old->parent->name);
- return NULL;
- }
- } else {
- parent = old->parent;
- }
+ dict->vendors_by_name = fr_hash_table_create(dict, dict_vendor_name_hash, dict_vendor_name_cmp, hash_pool_free);
+ if (!dict->vendors_by_name) goto error;
- memcpy(&flags, &old->flags, sizeof(flags));
- flags.is_unknown = false;
- flags.is_raw = true;
+ /*
+ * Create the table of vendors by value. There MAY
+ * be vendors of the same value. If there are, we
+ * pick the latest one.
+ */
+ dict->vendors_by_num = fr_hash_table_create(dict, dict_vendor_vendorpec_hash, dict_vendor_vendorpec_cmp, NULL);
+ if (!dict->vendors_by_num) goto error;
/*
- * If this is a vendor, we skip most of the sanity
- * checks and add it to the vendor hash, and add it
- * as a child attribute to the Vendor-Specific
- * container.
+ * Create the table of attributes by name. There MAY NOT
+ * be multiple attributes of the same name.
*/
- if (old->type == FR_TYPE_VENDOR) {
- fr_dict_attr_t *mutable, *n;
+ dict->attributes_by_name = fr_hash_table_create(dict, dict_attr_name_hash, dict_attr_name_cmp, NULL);
+ if (!dict->attributes_by_name) goto error;
- if (dict_vendor_add(dict, old->name, old->attr) < 0) return NULL;
+ /*
+ * Horrible hacks for combo-IP.
+ */
+ dict->attributes_combo = fr_hash_table_create(dict, dict_attr_combo_hash, dict_attr_combo_cmp, hash_pool_free);
+ if (!dict->attributes_combo) goto error;
- n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
+ dict->values_by_alias = fr_hash_table_create(dict, dict_enum_alias_hash, dict_enum_alias_cmp, hash_pool_free);
+ if (!dict->values_by_alias) goto error;
- /*
- * Setup parenting for the attribute
- */
- memcpy(&mutable, &old->parent, sizeof(mutable));
- if (dict_attr_child_add(mutable, n) < 0) return NULL;
+ dict->values_by_da = fr_hash_table_create(dict, dict_enum_value_hash, dict_enum_value_cmp, hash_pool_free);
+ if (!dict->values_by_da) goto error;
- return n;
- }
+ return dict;
+}
+
+/** Lookup a dictionary reference
+ *
+ * Format is [<proto>].[<attr>]
+ *
+ * If protocol is omitted lookup is in the current dictionary.
+ *
+ * FIXME: Probably needs the dictionary equivalent of pass2, to fixup circular dependencies
+ * DHCPv4->RADIUS and RADIUS->DHCPv4 are both valid.
+ *
+ * @param[in] dict The current dictionary we're parsing.
+ * @param[in,out] ref The reference string. Pointer advanced to the end of the string.
+ * @return
+ * - NULL if the reference is invalid.
+ * - A local or foreign attribute representing the target of the reference.
+ */
+static fr_dict_attr_t const *dict_resolve_reference(fr_dict_t *dict, char const *ref)
+{
+ char const *p = ref, *q, *end = p + strlen(ref);
+ fr_dict_t *proto_dict;
+ fr_dict_attr_t const *da;
/*
- * Look up the attribute by number. If it doesn't exist,
- * add it both by name and by number. If it does exist,
- * add it only by name.
+ * If the reference does not begin with .
+ * then it's a reference into a foreign
+ * protocol.
*/
- da = fr_dict_attr_child_by_num(parent, old->attr);
- if (da) {
- fr_dict_attr_t *n;
+ if (*p != '.') {
+ char buffer[FR_DICT_PROTO_MAX_NAME_LEN + 1];
- n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
- if (!n) return NULL;
+ q = strchr(p, '.');
+ if (!q) q = end;
- /*
- * Add the unknown by NAME. e.g. if the admin does "Attr-26", we want
- * to return "Attr-26", and NOT "Vendor-Specific". The rest of the server
- * is responsible for converting "Attr-26 = 0x..." to an actual attribute,
- * if it so desires.
- */
- if (dict_attr_add_by_name(dict, n) < 0) {
- talloc_free(n);
+ if ((size_t)(q - p) > sizeof(buffer)) {
+ fr_strerror_printf("Protocol name too long");
return NULL;
}
- return n;
- }
+ strlcpy(buffer, p, (q - p + 1));
+ p = q;
+
+ dict = fr_dict_by_protocol_name(buffer);
+ if (!dict) {
+ fr_strerror_printf("Referenced protocol \"%s\" not found", buffer);
+ return NULL;
+ }
+ return NULL;
/*
- * Add the attribute by both name and number.
+ * If the reference string begins with .
+ * then the reference is in the current
+ * dictionary.
*/
- if (fr_dict_attr_add(dict, parent, old->name, old->attr, old->type, &flags) < 0) return NULL;
+ } else {
+ proto_dict = dict;
+ }
/*
- * For paranoia, return it by name.
+ * If there's a '.' after the dictionary, then
+ * the reference is to a specific attribute.
*/
- return fr_dict_attr_by_name(dict, old->name);
-}
-
-/** Free dynamically allocated (unknown attributes)
- *
- * If the da was dynamically allocated it will be freed, else the function
- * will return without doing anything.
- *
- * @param[in] da to free.
- */
-void fr_dict_unknown_free(fr_dict_attr_t const **da)
-{
- fr_dict_attr_t **tmp;
-
- if (!da || !*da) return;
+ if (*p == '.') {
+ p++;
- /* Don't free real DAs */
- if (!(*da)->flags.is_unknown) {
- return;
+ da = fr_dict_attr_by_name_substr(proto_dict, &p);
+ if (!da) {
+ fr_strerror_printf("Referenced attribute \"%s\" not found", p);
+ return NULL;
+ }
}
- memcpy(&tmp, &da, sizeof(*tmp));
- talloc_free(*tmp);
+ da = fr_dict_root(proto_dict);
+ if (!da) {
+ fr_strerror_printf("Dictionary missing attribute root");
+ return NULL;
+ }
- *tmp = NULL;
+ return da;
}
-
-/** Build an unknown vendor, parented by a VSA or EVS attribute
- *
- * This allows us to complete the path back to the dictionary root in the case
- * of unknown attributes with unknown vendors.
- *
- * @note Will return known vendors attributes where possible. Do not free directly,
- * use #fr_dict_unknown_free.
- *
- * @param[in] ctx to allocate the vendor attribute in.
- * @param[out] out Where to write point to new unknown dict attr
- * representing the unknown vendor.
- * @param[in] parent of the vendor attribute, either an EVS or VSA attribute.
- * @param[in] vendor id.
- * @return
- * - 0 on success.
- * - -1 on failure.
+/*
+ * Process the ATTRIBUTE command
*/
-int fr_dict_unknown_vendor_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
- fr_dict_attr_t const *parent, unsigned int vendor)
+static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
+ unsigned int block_vendor, char **argv, int argc,
+ fr_dict_attr_flags_t *base_flags)
{
- fr_dict_attr_flags_t flags = {
- .is_unknown = true,
- .is_raw = true,
- .type_size = true,
- .length = true
- };
+ bool oid = false;
- if (!fr_cond_assert(parent)) {
- fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+ unsigned int vendor = 0;
+ unsigned int attr;
+
+ int type;
+ unsigned int length;
+ fr_dict_attr_flags_t flags;
+ fr_dict_attr_t const *ref = NULL;
+ char *p;
+
+ if ((argc < 3) || (argc > 4)) {
+ fr_strerror_printf("Invalid ATTRIBUTE syntax");
return -1;
}
- *out = NULL;
-
/*
- * Vendor attributes can occur under VSA or EVS attributes.
+ * Dictionaries need to have real names, not shitty ones.
*/
- switch (parent->type) {
- case FR_TYPE_VSA:
- case FR_TYPE_EVS:
- if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
-
- *out = dict_attr_alloc(ctx, parent, NULL, vendor, FR_TYPE_VENDOR, &flags);
-
- return 0;
-
- case FR_TYPE_VENDOR:
- if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
- fr_strerror_printf("Unknown vendor cannot be parented by another vendor");
- return -1;
-
- default:
- fr_strerror_printf("Unknown vendors can only be parented by 'vsa' or 'evs' "
- "attributes, not '%s'", fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
+ if (strncmp(argv[0], "Attr-", 5) == 0) {
+ fr_strerror_printf("Invalid ATTRIBUTE name");
return -1;
}
-}
-
-/** Allocates an unknown attribute
- *
- * @copybrief fr_dict_unknown_from_fields
- *
- * @note If vendor != 0, an unknown vendor (may) also be created, parented by
- * the correct EVS or VSA attribute. This is accessible via da->parent,
- * and will be use the unknown da as its talloc parent.
- *
- * @param[in] ctx to allocate DA in.
- * @param[in] parent of the unknown attribute (may also be unknown).
- * @param[in] attr number.
- * @param[in] vendor number.
- * @return 0 on success.
- */
-fr_dict_attr_t const *fr_dict_unknown_afrom_fields(TALLOC_CTX *ctx, fr_dict_attr_t const *parent,
- unsigned int vendor, unsigned int attr)
-{
- fr_dict_attr_t const *da;
- fr_dict_attr_t *n;
- fr_dict_attr_t *new_parent = NULL;
- fr_dict_attr_flags_t flags = {
- .is_unknown = true,
- .is_raw = true,
- };
- if (!fr_cond_assert(parent)) {
- fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
- return NULL;
- }
+ memcpy(&flags, base_flags, sizeof(flags));
/*
- * If there's a vendor specified, we check to see
- * if the parent is a VSA or EVS, and if it is
- * we either lookup the vendor to get the correct
- * attribute, or bridge the gap in the tree, with an
- * unknown vendor.
- *
- * We need to do the check, as the parent could be
- * a TLV, in which case the vendor should be known
- * and we don't need to modify the parent.
+ * Look for OIDs before doing anything else.
*/
- if (vendor && ((parent->type == FR_TYPE_VSA) || (parent->type == FR_TYPE_EVS))) {
- da = fr_dict_attr_child_by_num(parent, vendor);
- if (!da) {
- if (fr_dict_unknown_vendor_afrom_num(ctx, &new_parent, parent, vendor) < 0) return NULL;
- da = new_parent;
+ if (!strchr(argv[1], '.')) {
+ /*
+ * Parse out the attribute number
+ */
+ if (!dict_read_sscanf_i(&attr, argv[1])) {
+ fr_strerror_printf("Invalid ATTRIBUTE number");
+ return -1;
}
- parent = da;
- /*
- * Need to clone the unknown hierachy, as unknown
- * attributes must parent the complete heirachy,
- * and cannot share any parts with any other unknown
- * attributes.
- */
- } else if (parent->flags.is_unknown) {
- new_parent = fr_dict_unknown_acopy(ctx, parent);
- parent = new_parent;
- }
+ /*
+ * Got an OID string. Every attribute should exist other
+ * than the leaf, which is the attribute we're defining.
+ */
+ } else {
+ ssize_t slen;
- n = dict_attr_alloc(ctx, parent, NULL, attr, FR_TYPE_OCTETS, &flags);
+ oid = true;
+
+ slen = fr_dict_attr_by_oid(dict, &parent, &attr, argv[1]);
+ if (slen <= 0) return -1;
+
+ if (!fr_cond_assert(parent)) return -1; /* Should have provided us with a parent */
+ }
/*
- * The config files may reference the unknown by name.
- * If so, use the pre-defined name instead of an unknown
- * one.
- *
- * @fixme: pass the root into this function!
+ * Some types can have fixed length
*/
- da = fr_dict_attr_by_name(NULL, n->name);
- if (da) {
- fr_dict_unknown_free(&parent);
- parent = n;
- fr_dict_unknown_free(&parent);
- return da;
- }
+ p = strchr(argv[2], '[');
+ if (p) *p = '\0';
/*
- * Ensure the parent is freed at the same time as the
- * unknown DA. This should be OK as we never parent
- * multiple unknown attributes off the same parent.
+ * find the type of the attribute.
*/
- if (new_parent && new_parent->flags.is_unknown) talloc_steal(n, new_parent);
+ type = fr_str2int(dict_attr_types, argv[2], -1);
+ if (type < 0) {
+ fr_strerror_printf("Unknown data type '%s'", argv[2]);
+ return -1;
+ }
- return n;
-}
+ if (p) {
+ char *q;
-/** Initialise a fr_dict_attr_t from an ASCII attribute and value
- *
- * Where the attribute name is in the form:
- * - Attr-%d
- * - Attr-%d.%d.%d...
- *
- * @copybrief fr_dict_unknown_from_fields
- *
- * @param[in] ctx to allocate the attribute in.
- * @param[out] out Where to write the new attribute to.
- * @param[in] parent of the unknown attribute (may also be unknown).
- * @param[in] num of the unknown attribute.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int dict_unknown_attr_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
- fr_dict_attr_t const *parent, unsigned long num)
-{
- fr_dict_attr_t *da;
- fr_dict_attr_flags_t flags = {
- .is_unknown = true,
- .is_raw = true,
- };
+ q = strchr(p + 1, ']');
+ if (!q) {
+ fr_strerror_printf("Invalid format for '%s[...]'", argv[2]);
+ return -1;
+ }
- if (!fr_cond_assert(parent)) {
- fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
- return -1;
- }
+ *q = '\0';
- *out = NULL;
+ if (!dict_read_sscanf_i(&length, p + 1)) {
+ fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
+ return -1;
+ }
- da = dict_attr_alloc(ctx, parent, NULL, num, FR_TYPE_OCTETS, &flags);
- if (!da) return -1;
+ if ((length == 0) || (length > 253)) {
+ fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
+ return -1;
+ }
- *out = da;
+ flags.length = length;
+ }
- return 0;
-}
+ /*
+ * Parse options.
+ */
+ if (argc >= 4) {
+ char *q, *v;
-/** Create a fr_dict_attr_t from an ASCII attribute and value
- *
- * Where the attribute name is in the form:
- * - Attr-%d
- * - Attr-%d.%d.%d...
- *
- * @copybrief fr_dict_unknown_from_fields
- *
- * @note If vendor != 0, an unknown vendor (may) also be created, parented by
- * the correct EVS or VSA attribute. This is accessible via vp->parent,
- * and will be use the unknown da as its talloc parent.
- *
- * @param[in] ctx to alloc new attribute in.
- * @param[out] out Where to write the head of the chain unknown
- * dictionary attributes.
- * @param[in] parent Attribute to use as the root for resolving OIDs in.
- * Usually the root of a protocol dictionary.
- * @param[in] oid_str of attribute.
- * @return
- * - The number of bytes parsed on success.
- * - <= 0 on failure. Negative offset indicates parse error position.
- */
-ssize_t fr_dict_unknown_afrom_oid_str(TALLOC_CTX *ctx, fr_dict_attr_t **out,
- fr_dict_attr_t const *parent, char const *oid_str)
-{
- char const *p = oid_str, *end = oid_str + strlen(oid_str);
- fr_dict_attr_t const *our_parent = parent;
- TALLOC_CTX *top_ctx = NULL, *our_ctx = ctx;
+ p = argv[3];
+ do {
+ char key[64], value[256];
- fr_dict_attr_t *n = NULL;
+ q = strchr(p, ',');
+ if (!q) q = p + strlen(p);
- if (!fr_cond_assert(parent)) {
- fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
- return -1;
- }
+ /*
+ * Nothing after the trailing comma
+ */
+ if (p == q) break;
- *out = NULL;
+ if ((size_t)(q - p) > sizeof(key)) {
+ fr_strerror_printf("ATTRIBUTE option key too long");
+ return -1;
+ }
- if (fr_dict_valid_name(oid_str, -1) < 0) return -1;
+ /*
+ * Copy key and value
+ */
+ if (!(v = memchr(p, '=', q - p)) || (v == q)) {
+ value[0] = '\0';
+ strlcpy(key, p, (q - p) + 1);
+ } else {
+ strlcpy(key, p, (v - p) + 1);
+ strlcpy(value, v + 1, q - v);
+ }
- /*
- * All unknown attributes are of the form "Attr-#-#-#-#"
- */
- if (strncasecmp(p, "Attr-", 5) != 0) {
- fr_strerror_printf("Unknown attribute '%s'", oid_str);
- return 0;
- }
- p += 5;
+ /*
+ * Boolean flag, means this is a tagged
+ * attribute.
+ */
+ if (strcmp(key, "has_tag") == 0) {
+ flags.has_tag = 1;
- do {
- unsigned int num;
- fr_dict_attr_t const *da = NULL;
+ /*
+ * Encryption method.
+ */
+ } else if (strcmp(key, "encrypt") == 0) {
+ char *qq;
- if (fr_dict_oid_component(&num, &p) < 0) {
- error:
- talloc_free(top_ctx);
- return -(p - oid_str);
- }
+ flags.encrypt = strtol(value, &qq, 0);
+ if (*qq) {
+ fr_strerror_printf("Invalid encrypt value \"%s\"", value);
+ return -1;
+ }
- switch (*p) {
- /*
- * Structural attribute
- */
- case '.':
- if (!our_parent) goto is_root;
+ /*
+ * Marks the attribute up as internal.
+ * This means it can use numbers outside of the allowed
+ * protocol range, and also means it will not be included
+ * in replies or proxy requests.
+ */
+ } else if (strcmp(key, "internal") == 0) {
+ flags.internal = 1;
- da = fr_dict_attr_child_by_num(our_parent, num);
- if (!da) { /* Unknown component */
- switch (our_parent->type) {
- case FR_TYPE_EVS:
- case FR_TYPE_VSA:
- da = fr_dict_attr_child_by_num(our_parent, num);
- if (!fr_cond_assert(!da || (da->type == FR_TYPE_VENDOR))) goto error;
+ } else if (strcmp(key, "array") == 0) {
+ flags.array = 1;
- if (!da) {
- if (fr_dict_unknown_vendor_afrom_num(our_ctx, &n,
- our_parent, num) < 0) {
- goto error;
- }
- da = n;
- }
- break;
+ } else if (strcmp(key, "concat") == 0) {
+ flags.concat = 1;
- case FR_TYPE_TLV:
- case FR_TYPE_EXTENDED:
- case FR_TYPE_LONG_EXTENDED:
- is_root:
- if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) {
- goto error;
- }
+ } else if (strcmp(key, "virtual") == 0) {
+ flags.virtual = 1;
- da = n;
- break;
+ } else if (strcmp(key, "reference") == 0) {
+ ref = dict_resolve_reference(dict, value);
+ if (!ref) return -1;
+ flags.is_reference = 1;
- /*
- * Can't have a FR_TYPE_STRING inside a
- * FR_TYPE_STRING (for example)
- */
- default:
- fr_strerror_printf("Parent OID component (%s) in \"%.*s\" specified a "
- "non-structural type (%s)", our_parent->name,
- (int)(p - oid_str), oid_str,
- fr_int2str(dict_attr_types, our_parent->type, "<INVALID>"));
- goto error;
+ /*
+ * The only thing is the vendor name, and it's a known name:
+ * allow it.
+ */
+ } else if ((argv[3] == p) && (*q == '\0')) {
+ if (oid) {
+ fr_strerror_printf("ATTRIBUTE cannot use a 'vendor' flag");
+ return -1;
}
- }
- our_parent = da;
- if (n && n->flags.is_unknown) {
- if (top_ctx == NULL) top_ctx = n; /* Track first unknown */
- our_ctx = n;
+ if (block_vendor) {
+ fr_strerror_printf("Vendor flag inside of 'BEGIN-VENDOR' is not allowed");
+ return -1;
+ }
+
+ vendor = fr_dict_vendor_by_name(dict, key);
+ if (!vendor) goto unknown;
+ break;
+
+ } else {
+ unknown:
+ fr_strerror_printf("Unknown option '%s'", key);
+ return -1;
}
+ p = q;
+ } while (*p++);
+ }
+#ifdef WITH_DICTIONARY_WARNINGS
+ /*
+ * Hack to help us discover which vendors have illegal
+ * attributes.
+ */
+ if (!vendor && (attr < 256) &&
+ !strstr(fn, "rfc") && !strstr(fn, "illegal")) {
+ fprintf(stderr, "WARNING: Illegal Attribute %s in %s\n",
+ argv[0], fn);
+ }
+#endif
- break;
+ /*
+ * Add in a normal attribute
+ */
+ if (!ref) {
+ if (fr_dict_attr_add(dict, parent, argv[0], attr, type, &flags) < 0) return -1;
+ /*
+ * Add in a special reference attribute
+ */
+ } else {
+ if (dict_attr_ref_add(dict, parent, argv[0], attr, type, &flags, ref) < 0) return -1;
+ }
- /*
- * Leaf attribute
- */
- case '\0':
- if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) goto error;
- break;
- }
- p++;
- } while (p < end);
+ return 0;
+}
- if (!n) return 0;
+/*
+ * Process the ATTRIBUTE command, where it only has a name.
+ */
+static int dict_read_process_named_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
+ char **argv, int argc,
+ fr_dict_attr_flags_t const *base_flags)
+{
+ int type;
+ unsigned int attr;
+ uint32_t hash;
+ char *p, normalized[512];
+
+ if (argc != 2) {
+ fr_strerror_printf("Invalid ATTRIBUTE syntax");
+ return -1;
+ }
/*
- * Invert the talloc hierarchy, so that if the unknown
- * attribute is freed, any unknown parents are also freed.
+ * find the type of the attribute.
*/
- for (our_parent = n->parent, our_ctx = n;
- our_parent && our_parent->flags.is_unknown;
- our_parent = our_parent->parent) {
- fr_dict_attr_t *tmp;
-
- memcpy(&tmp, &our_parent, sizeof(tmp)); /* const issues *sigh* */
+ type = fr_str2int(dict_attr_types, argv[1], -1);
+ if (type < 0) {
+ fr_strerror_printf("Unknown data type '%s'", argv[1]);
+ return -1;
+ }
- our_ctx = talloc_steal(our_ctx, tmp);
+ strlcpy(normalized, argv[0], sizeof(normalized));
+ for (p = normalized; *p != '\0'; p++) {
+ if (isupper((int) *p)) {
+ *p = tolower((int) *p);
+ }
}
- DA_VERIFY(n);
+ hash = fr_hash_string(normalized);
+ attr = hash;
- *out = n;
+ /*
+ * Add it in.
+ */
+ if (fr_dict_attr_add(dict, parent, argv[0], attr, type, base_flags) < 0) return -1;
- return end - oid_str;
+ return 0;
}
-/** Create a dictionary attribute by name embedded in another string
- *
- * Find the first invalid attribute name char in the string pointed to by name.
- *
- * Copy the characters between the start of the name string and the first none
- * #fr_dict_attr_allowed_chars char to a buffer and initialise da as an unknown
- * attribute.
+/** Process a value alias
*
- * @param[in] ctx To allocate unknown #fr_dict_attr_t in.
- * @param[out] out Where to write the head of the chain unknown
- * dictionary attributes.
- * @param[in] parent Attribute to use as the root for resolving OIDs in.
- * Usually the root of a protocol dictionary.
- * @param[in] name string start.
- * @return
- * - <= 0 on failure.
- * - The number of bytes of name consumed on success.
*/
-ssize_t fr_dict_unknown_afrom_oid_substr(TALLOC_CTX *ctx, fr_dict_attr_t **out,
- fr_dict_attr_t const *parent, char const *name)
+static int dict_read_process_value(fr_dict_t *dict, char **argv, int argc)
{
- char const *p;
- size_t len;
- char buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
- ssize_t slen;
+ static fr_dict_attr_t const *last_attr = NULL;
+ fr_dict_attr_t const *da;
+ fr_value_box_t value;
- if (!name || !*name) return 0;
+ if (argc != 3) {
+ fr_strerror_printf("Invalid VALUE syntax");
+ return -1;
+ }
/*
- * Advance p until we get something that's not part of
- * the dictionary attribute name.
+ * Most VALUEs are bunched together by ATTRIBUTE. We can
+ * save a lot of lookups on dictionary initialization by
+ * caching the last attribute.
*/
- for (p = name; fr_dict_attr_allowed_chars[(int)*p] || (*p == '.') || (*p == '-'); p++);
+ if (last_attr && (strcasecmp(argv[0], last_attr->name) == 0)) {
+ da = last_attr;
+ } else {
+ da = fr_dict_attr_by_name(dict, argv[0]);
+ last_attr = da;
+ }
+
+ /*
+ * Remember which attribute is associated with this
+ * value. This allows us to define enum
+ * values before the attribute exists, and fix them
+ * up later.
+ */
+ if (!da) {
+ dict_enum_fixup_t *fixup;
+
+ fixup = talloc_zero(dict->pool, dict_enum_fixup_t);
+ if (!fixup) {
+ oom:
+ talloc_free(fixup);
+ fr_strerror_printf("Out of memory");
+ return -1;
+ }
+ fixup->attribute = talloc_strdup(fixup, argv[0]);
+ if (!fixup->attribute) goto oom;
+ fixup->alias = talloc_strdup(fixup, argv[1]);
+ if (!fixup->alias) goto oom;
+ fixup->value = talloc_strdup(fixup, argv[2]);
+ if (!fixup->value) goto oom;
+
+ /*
+ * Insert to the head of the list.
+ */
+ fixup->next = dict->enum_fixup;
+ dict->enum_fixup = fixup;
- len = p - name;
- if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
- fr_strerror_printf("Attribute name too long");
return 0;
}
- if (len == 0) {
- fr_strerror_printf("Invalid attribute name");
- return 0;
+
+ {
+ fr_type_t type = da->type; /* Might change - Stupid combo IP */
+
+ if (fr_value_box_from_str(NULL, &value, &type, NULL, argv[2], -1, '\0', false) < 0) {
+ fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
+ return -1;
+ }
}
- strlcpy(buffer, name, len + 1);
- slen = fr_dict_unknown_afrom_oid_str(ctx, out, parent, buffer);
- if (slen <= 0) return slen;
+ if (fr_dict_enum_add_alias(da, argv[1], &value, false, true) < 0) {
+ fr_value_box_clear(&value);
+ return -1;
+ }
+ fr_value_box_clear(&value);
- return p - name;
+ return 0;
}
-
-/** Check to see if we can convert a nested TLV structure to known attributes
- *
- * @param[in] dict to search in.
- * @param[in] da Nested tlv structure to convert.
- * @return
- * - NULL if we can't.
- * - Known attribute if we can.
+/*
+ * Process the FLAGS command
*/
-fr_dict_attr_t const *fr_dict_attr_known(fr_dict_t *dict, fr_dict_attr_t const *da)
+static int dict_read_process_flags(UNUSED fr_dict_t *dict, char **argv, int argc,
+ fr_dict_attr_flags_t *base_flags)
{
- INTERNAL_IF_NULL(dict);
+ bool sense = true;
- if (!da->flags.is_unknown) return da; /* It's known */
+ if (argc == 1) {
+ char *p;
- if (da->parent) {
- fr_dict_attr_t const *parent;
+ p = argv[0];
+ if (*p == '!') {
+ sense = false;
+ p++;
+ }
- parent = fr_dict_attr_known(dict, da->parent);
- if (!parent) return NULL;
+ if (strcmp(p, "internal") == 0) {
+ base_flags->internal = sense;
+ return 0;
+ }
+ }
- return fr_dict_attr_child_by_num(parent, da->attr);
+ fr_strerror_printf("Invalid FLAGS syntax");
+ return -1;
+}
+
+static int dict_read_parse_format(char const *format, unsigned int *pvalue, int *ptype, int *plength,
+ bool *pcontinuation)
+{
+ char const *p;
+ int type, length;
+ bool continuation = false;
+
+ if (strncasecmp(format, "format=", 7) != 0) {
+ fr_strerror_printf("Invalid format for VENDOR. Expected 'format=', got '%s'",
+ format);
+ return -1;
}
- if (dict->root == da) return dict->root;
- return NULL;
+ p = format + 7;
+ if ((strlen(p) < 3) ||
+ !isdigit((int)p[0]) ||
+ (p[1] != ',') ||
+ !isdigit((int)p[2]) ||
+ (p[3] && (p[3] != ','))) {
+ fr_strerror_printf("Invalid format for VENDOR. Expected text like '1,1', got '%s'",
+ p);
+ return -1;
+ }
+
+ type = (int)(p[0] - '0');
+ length = (int)(p[2] - '0');
+
+ if ((type != 1) && (type != 2) && (type != 4)) {
+ fr_strerror_printf("Invalid type value %d for VENDOR", type);
+ return -1;
+ }
+
+ if ((length != 0) && (length != 1) && (length != 2)) {
+ fr_strerror_printf("Ivalid length value %d for VENDOR", length);
+ return -1;
+ }
+
+ if (p[3] == ',') {
+ if (!p[4]) {
+ fr_strerror_printf("Invalid format for VENDOR. Expected text like '1,1', got '%s'",
+ p);
+ return -1;
+ }
+
+ if ((p[4] != 'c') ||
+ (p[5] != '\0')) {
+ fr_strerror_printf("Invalid format for VENDOR. Expected text like '1,1', got '%s'",
+ p);
+ return -1;
+ }
+ continuation = true;
+
+ if ((*pvalue != VENDORPEC_WIMAX) ||
+ (type != 1) || (length != 1)) {
+ fr_strerror_printf("Only WiMAX VSAs can have continuations");
+ return -1;
+ }
+ }
+
+ *ptype = type;
+ *plength = length;
+ *pcontinuation = continuation;
+ return 0;
}
-static void dict_snprint_flags(char *out, size_t outlen, fr_dict_attr_flags_t flags)
+/** Register the specified dictionary as a protocol dictionary
+ *
+ * Allows vendor and TLV context to persist across $INCLUDEs
+ */
+static int dict_read_process_protocol(char **argv, int argc)
{
- char *p = out, *end = p + outlen;
- size_t len;
+ unsigned int value;
+ unsigned int type_size = 1;
+ fr_dict_t *dict;
- out[0] = '\0';
+ if ((argc < 2) || (argc > 3)) {
+ fr_strerror_printf("Missing arguments after PROTOCOL. Expected PROTOCOL <num> <name>");
+ return -1;
+ }
-#define FLAG_SET(_flag) \
-do { \
- if (flags._flag) {\
- p += strlcpy(p, STRINGIFY(_flag)",", end - p);\
- if (p >= end) return;\
- }\
-} while (0)
+ /*
+ * Validate all entries
+ */
+ if (!dict_read_sscanf_i(&value, argv[1])) {
+ fr_strerror_printf("Invalid number '%s' following PROTOCOL", argv[1]);
+ return -1;
+ }
- FLAG_SET(is_root);
- FLAG_SET(is_unknown);
- FLAG_SET(is_raw);
- FLAG_SET(internal);
- FLAG_SET(has_tag);
- FLAG_SET(array);
- FLAG_SET(has_value);
- FLAG_SET(concat);
- FLAG_SET(virtual);
- FLAG_SET(compare);
+ /*
+ * Look for a format statement. This may specify the
+ * type length of the protocol's types.
+ */
+ if (argc == 3) {
+ char const *p;
+ char *q;
- if (flags.encrypt) {
- p += snprintf(p, end - p, "encrypt=%i,", flags.encrypt);
- if (p >= end) return;
+ if (strncasecmp(argv[2], "format=", 7) != 0) {
+ fr_strerror_printf("Invalid format for PROTOCOL. Expected 'format=', got '%s'", argv[2]);
+ return -1;
+ }
+ p = argv[2] + 7;
+
+ type_size = strtoul(p, &q, 10);
+ if (q != (p + strlen(p))) {
+ fr_strerror_printf("Found trailing garbage '%s' after format specifier", p);
+ return -1;
+ }
}
- if (flags.length) {
- p += snprintf(p, end - p, "length=%i,", flags.length);
- if (p >= end) return;
+ dict = fr_dict_by_protocol_num(value);
+ if (dict) {
+ if (dict->root->flags.type_size != type_size) {
+ fr_strerror_printf("Conflicting flags for PROTOCOL \"%s\"", dict->root->name);
+ return -1;
+ }
+ return 0;
}
- if (!out[0]) return;
+ dict = dict_alloc(NULL);
/*
- * Trim the comma
+ * Set the root attribute with the protocol name
*/
- len = strlen(out);
- if (out[len - 1] == ',') out[len - 1] = '\0';
+ dict_root_set(dict, argv[0], value);
+
+ if (dict_protocol_add(dict) < 0) return -1;
+
+ return 0;
}
-void fr_dict_print(fr_dict_attr_t const *da, int depth)
+/*
+ * Process the VENDOR command
+ */
+static int dict_read_process_vendor(fr_dict_t *dict, char **argv, int argc)
{
- char buff[256];
- unsigned int i;
- char const *name;
+ unsigned int value;
+ int type, length;
+ bool continuation = false;
+ fr_dict_vendor_t const *dv;
+ fr_dict_vendor_t *mutable;
- dict_snprint_flags(buff, sizeof(buff), da->flags);
+ if ((argc < 2) || (argc > 3)) {
+ fr_strerror_printf("Invalid VENDOR syntax");
+ return -1;
+ }
- switch (da->type) {
- case FR_TYPE_VSA:
- name = "VSA";
- break;
+ /*
+ * Validate all entries
+ */
+ if (!dict_read_sscanf_i(&value, argv[1])) {
+ fr_strerror_printf("Invalid number in VENDOR");
+ return -1;
+ }
- case FR_TYPE_EXTENDED:
- name = "EXTENDED";
- break;
+ /* Create a new VENDOR entry for the list */
+ if (dict_vendor_add(dict, argv[0], value) < 0) return -1;
- case FR_TYPE_TLV:
- name = "TLV";
- break;
+ /*
+ * Look for a format statement. Allow it to over-ride the hard-coded formats below.
+ */
+ if (argc == 3) {
+ if (dict_read_parse_format(argv[2], &value, &type, &length, &continuation) < 0) return -1;
- case FR_TYPE_EVS:
- name = "EVS";
- break;
+ } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */
+ type = 4;
+ length = 0;
- case FR_TYPE_VENDOR:
- name = "VENDOR";
- break;
+ } else if (value == VENDORPEC_LUCENT) {
+ type = 2;
+ length = 1;
- case FR_TYPE_LONG_EXTENDED:
- name = "LONG EXTENDED";
- break;
+ } else if (value == VENDORPEC_STARENT) {
+ type = 2;
+ length = 2;
- case FR_TYPE_STRUCT:
- name = "STRUCT";
- break;
+ } else {
+ type = length = 1;
+ }
- default:
- name = "ATTRIBUTE";
- break;
+ dv = fr_dict_vendor_by_num(dict, value);
+ if (!dv) {
+ fr_strerror_printf("Failed adding format for VENDOR");
+ return -1;
}
- printf("%u%.*s%s \"%s\" vendor: %x (%u), num: %x (%u), type: %s, flags: %s\n", da->depth, depth,
- "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t", name, da->name,
- fr_dict_vendor_num_by_da(da), fr_dict_vendor_num_by_da(da), da->attr, da->attr,
- fr_int2str(dict_attr_types, da->type, "?Unknown?"), buff);
+ memcpy(&mutable, &dv, sizeof(mutable));
- if (da->children) for (i = 0; i < talloc_array_length(da->children); i++) {
- if (da->children[i]) {
- fr_dict_attr_t const *bin;
+ mutable->type = type;
+ mutable->length = length;
+ mutable->flags = continuation;
- for (bin = da->children[i]; bin; bin = bin->next) fr_dict_print(bin, depth + 1);
- }
- }
+ return 0;
}
-/** Find a common ancestor that two TLV type attributes share
+/** Parse a dictionary file
*
- * @param[in] a first TLV attribute.
- * @param[in] b second TLV attribute.
- * @param[in] is_ancestor Enforce a->b relationship (a is parent or ancestor of b).
+ * @param[in] ctx Contains the current state of the dictionary parser.
+ * Used to track what PROTOCOL, VENDOR or TLV block
+ * we're in. Block context changes in $INCLUDEs should
+ * not affect the context of the including file.
+ * @param[in] dir_name Directory containing the dictionary we're loading.
+ * @param[in] filename we're parsing.
+ * @param[in] src_file The including file.
+ * @param[in] src_line Line on which the $INCLUDE or $INCLUDE- statement was found.
* @return
- * - Common ancestor if one exists.
- * - NULL if no common ancestor exists.
+ * - 0 on success.
+ * - -1 on failure.
*/
-fr_dict_attr_t const *fr_dict_parent_common(fr_dict_attr_t const *a, fr_dict_attr_t const *b, bool is_ancestor)
+static int _dict_from_file(dict_from_file_ctx_t *ctx,
+ char const *dir_name, char const *filename,
+ char const *src_file, int src_line)
{
- unsigned int i;
- fr_dict_attr_t const *p_a, *p_b;
-
- if (!a || !b) return NULL;
+ FILE *fp;
+ char dir[256], fn[256];
+ char buf[256];
+ char *p;
+ int line = 0;
- if (is_ancestor && (b->depth <= a->depth)) return NULL;
+ struct stat statbuf;
+ char *argv[MAX_ARGV];
+ int argc;
+ fr_dict_attr_t const *da;
/*
- * Find a common depth to work back from
+ * Base flags are only set for the current file
*/
- if (a->depth > b->depth) {
- p_b = b;
- for (p_a = a, i = a->depth - b->depth; p_a && (i > 0); p_a = p_a->parent, i--);
- } else if (a->depth < b->depth) {
- p_a = a;
- for (p_b = b, i = b->depth - a->depth; p_b && (i > 0); p_b = p_b->parent, i--);
- } else {
- p_a = a;
- p_b = b;
- }
+ fr_dict_attr_flags_t base_flags;
- while (p_a && p_b) {
- if (p_a == p_b) return p_a;
+ if (!fr_cond_assert(!ctx->dict->root || ctx->parent)) return -1;
- p_a = p_a->parent;
- p_b = p_b->parent;
+ if ((strlen(dir_name) + 3 + strlen(filename)) > sizeof(dir)) {
+ fr_strerror_printf_push("%s: Filename name too long", "Error reading dictionary");
+ return -1;
}
- return NULL;
-}
-
-/** Process a single OID component
- *
- * @param[out] out Value of component.
- * @param[in] oid string to parse.
- * @return
- * - 0 on success.
- * - -1 on format error.
- */
-int fr_dict_oid_component(unsigned int *out, char const **oid)
-{
- char const *p = *oid;
- char *q;
- unsigned long num;
+ /*
+ * If it's an absolute dir, forget the parent dir,
+ * and remember the new one.
+ *
+ * If it's a relative dir, tack on the current filename
+ * to the parent dir. And use that.
+ */
+ if (!FR_DIR_IS_RELATIVE(filename)) {
+ strlcpy(dir, filename, sizeof(dir));
+ p = strrchr(dir, FR_DIR_SEP);
+ if (p) {
+ p[1] = '\0';
+ } else {
+ strlcat(dir, "/", sizeof(dir));
+ }
- *out = 0;
+ strlcpy(fn, filename, sizeof(fn));
+ } else {
+ strlcpy(dir, dir_name, sizeof(dir));
+ p = strrchr(dir, FR_DIR_SEP);
+ if (p) {
+ if (p[1]) strlcat(dir, "/", sizeof(dir));
+ } else {
+ strlcat(dir, "/", sizeof(dir));
+ }
+ strlcat(dir, filename, sizeof(dir));
+ p = strrchr(dir, FR_DIR_SEP);
+ if (p) {
+ p[1] = '\0';
+ } else {
+ strlcat(dir, "/", sizeof(dir));
+ }
- num = strtoul(p, &q, 10);
- if ((p == q) || (num == ULONG_MAX)) {
- fr_strerror_printf("Invalid OID component \"%s\" (%lu)", p, num);
- return -1;
+ p = strrchr(filename, FR_DIR_SEP);
+ if (p) {
+ snprintf(fn, sizeof(fn), "%s%s", dir, p);
+ } else {
+ snprintf(fn, sizeof(fn), "%s%s", dir, filename);
+ }
}
- switch (*q) {
- case '\0':
- case '.':
- *oid = q;
- *out = (unsigned int)num;
+ /*
+ * Check if we've loaded this file before. If so, ignore it.
+ */
+ p = strrchr(fn, FR_DIR_SEP);
+ if (p) {
+ *p = '\0';
+ if (dict_stat_check(ctx->dict, fn, p + 1)) {
+ *p = FR_DIR_SEP;
+ return 0;
+ }
+ *p = FR_DIR_SEP;
+ }
- return 0;
+ if ((fp = fopen(fn, "r")) == NULL) {
+ if (!src_file) {
+ fr_strerror_printf_push("%s: Couldn't open dictionary '%s': %s",
+ "Error reading dictionary", fn, fr_syserror(errno));
+ } else {
+ fr_strerror_printf_push("%s: %s[%d]: Couldn't open dictionary '%s': %s",
+ "Error reading dictionary", src_file, src_line, fn, fr_syserror(errno));
+ }
+ return -2;
+ }
- default:
- fr_strerror_printf("Unexpected text after OID component");
- *out = 0;
+ /*
+ * If fopen works, this works.
+ */
+ if (stat(fn, &statbuf) < 0) {
+ fclose(fp);
return -1;
}
-}
-
-/** Build the tlv_stack for the specified DA and encode the path in OID form
- *
- * @param[out] out Where to write the OID.
- * @param[in] outlen Length of the output buffer.
- * @param[in] ancestor If not NULL, only print OID portion between
- * ancestor and da.
- * @param[in] da to print OID string for.
- * @return the number of bytes written to the buffer.
- */
-size_t fr_dict_print_attr_oid(char *out, size_t outlen,
- fr_dict_attr_t const *ancestor, fr_dict_attr_t const *da)
-{
- size_t len;
- char *p = out, *end = p + outlen;
- int i;
- int depth = 0;
- fr_dict_attr_t const *tlv_stack[FR_DICT_MAX_TLV_STACK + 1];
- if (!outlen) return 0;
+ if (!S_ISREG(statbuf.st_mode)) {
+ fclose(fp);
+ fr_strerror_printf_push("%s: Dictionary '%s' is not a regular file", "Error reading dictionary", fn);
+ return -1;
+ }
/*
- * If the ancestor and the DA match, there's
- * no OID string to print.
+ * Globally writable dictionaries means that users can control
+ * the server configuration with little difficulty.
*/
- if (ancestor == da) {
- out[0] = '\0';
- return 0;
+#ifdef S_IWOTH
+ if ((statbuf.st_mode & S_IWOTH) != 0) {
+ fclose(fp);
+ fr_strerror_printf_push("%s: Dictionary '%s' is globally writable. Refusing to start "
+ "due to insecure configuration", "Error reading dictionary", fn);
+ return -1;
}
+#endif
- fr_proto_tlv_stack_build(tlv_stack, da);
-
- if (ancestor) {
- if (tlv_stack[ancestor->depth - 1] != ancestor) {
- fr_strerror_printf("Attribute \"%s\" is not a descendent of \"%s\"", da->name, ancestor->name);
- return -1;
- }
- depth = ancestor->depth;
- }
+ dict_stat_add(ctx->dict, &statbuf);
/*
- * We don't print the ancestor, we print the OID
- * between it and the da.
+ * Seed the random pool with data.
*/
- len = snprintf(p, end - p, "%u", tlv_stack[depth]->attr);
- if ((p + len) >= end) return p - out;
- p += len;
+ fr_rand_seed(&statbuf, sizeof(statbuf));
+ memset(&base_flags, 0, sizeof(base_flags));
- for (i = depth + 1; i < (int)da->depth; i++) {
- len = snprintf(p, end - p, ".%u", tlv_stack[i]->attr);
- if ((p + len) >= end) return p - out;
- p += len;
- }
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ line++;
- return p - out;
-}
+ switch (buf[0]) {
+ case '#':
+ case '\0':
+ case '\n':
+ case '\r':
+ continue;
+ }
-/** Get the leaf attribute of an OID string
- *
- * @note On error, vendor will be set (if present), parent will be the
- * maximum depth we managed to resolve to, and attr will be the child
- * we failed to resolve.
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[out] attr Number we parsed.
- * @param[in,out] parent attribute (or root of dictionary).
- * Will be updated to the parent directly beneath the leaf.
- * @param[in] oid string to parse.
- * @return
- * - > 0 on success (number of bytes parsed).
- * - <= 0 on parse error (negative offset of parse error).
- */
-ssize_t fr_dict_attr_by_oid(fr_dict_t *dict, fr_dict_attr_t const **parent, unsigned int *attr, char const *oid)
-{
- char const *p = oid;
- unsigned int num = 0;
- ssize_t slen;
+ /*
+ * Comment characters should NOT be appearing anywhere but
+ * as start of a comment;
+ */
+ p = strchr(buf, '#');
+ if (p) *p = '\0';
- if (!fr_cond_assert(parent)) return 0;
- INTERNAL_IF_NULL(dict);
+ argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
+ if (argc == 0) continue;
- *attr = 0;
+ if (argc == 1) {
+ fr_strerror_printf("Invalid entry");
- if (fr_dict_oid_component(&num, &p) < 0) return oid - p;
+ error:
+ fr_strerror_printf_push("Error reading %s[%d]", fn, line);
+ fclose(fp);
+ return -1;
+ }
- /*
- * Record progress even if we error out.
- *
- * Don't change this, you will break things.
- */
- *attr = num;
+ /*
+ * Process VALUE lines.
+ */
+ if (strcasecmp(argv[0], "VALUE") == 0) {
+ if (dict_read_process_value(ctx->dict, argv + 1, argc - 1) == -1) goto error;
+ continue;
+ }
- switch ((*parent)->type) {
- case FR_TYPE_STRUCTURAL:
- break;
+ /*
+ * Perhaps this is an attribute.
+ */
+ if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
+ if (!base_flags.named) {
+ if (dict_read_process_attribute(ctx->dict, ctx->parent, ctx->block_vendor,
+ argv + 1, argc - 1,
+ &base_flags) == -1) goto error;
+ } else {
+ if (dict_read_process_named_attribute(ctx->dict, ctx->parent,
+ argv + 1, argc - 1,
+ &base_flags) == -1) goto error;
+ }
+ continue;
+ }
- default:
- fr_strerror_printf("Attribute %s (%i) is not a TLV, so cannot contain a child attribute. "
- "Error at sub OID \"%s\"", (*parent)->name, (*parent)->attr, oid);
- return 0; /* We parsed nothing */
- }
+ /*
+ * Process VALUE lines.
+ */
+ if (strcasecmp(argv[0], "FLAGS") == 0) {
+ if (dict_read_process_flags(ctx->dict, argv + 1, argc - 1, &base_flags) == -1) goto error;
+ continue;
+ }
- /*
- * If it's not a vendor type, it must be between 0..8*type_size
- *
- * @fixme: find the TLV parent, and check it's size
- */
- if (((*parent)->type != FR_TYPE_VENDOR) && ((*parent)->type != FR_TYPE_VSA) && !(*parent)->flags.is_root &&
- (num > UINT8_MAX)) {
- fr_strerror_printf("TLV attributes must be between 0..255 inclusive");
- return 0;
- }
+ /*
+ * See if we need to import another dictionary.
+ */
+ if (strcasecmp(argv[0], "$INCLUDE") == 0) {
+ dict_from_file_ctx_t nctx = *ctx;
- switch (p[0]) {
- /*
- * We've not hit the leaf yet, so the attribute must be
- * defined already.
- */
- case '.':
- {
- fr_dict_attr_t const *child;
- p++;
+ /*
+ * Included files operate on a copy of the context
+ */
+ if (_dict_from_file(&nctx, dir, argv[1], fn, line) < 0) {
+ fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
+ fclose(fp);
+ return -1;
+ }
+ continue;
+ } /* $INCLUDE */
- child = fr_dict_attr_child_by_num(*parent, num);
- if (!child) {
- fr_strerror_printf("Unknown attribute \"%i\" in OID string \"%s\"", num, oid);
- return 0; /* We parsed nothing */
+ /*
+ * Optionally include a dictionary
+ */
+ if (strcasecmp(argv[0], "$INCLUDE-") == 0) {
+ int rcode = _dict_from_file(ctx, dir, argv[1], fn, line);
+
+ if (rcode == -2) {
+ fr_strerror_printf(NULL); /* delete all errors */
+ continue;
+ }
+
+ if (rcode < 0) {
+ fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
+ fclose(fp);
+ return -1;
+ }
+ continue;
+ } /* $INCLUDE- */
+
+ /*
+ * Process VENDOR lines.
+ */
+ if (strcasecmp(argv[0], "VENDOR") == 0) {
+ if (dict_read_process_vendor(ctx->dict, argv + 1, argc - 1) == -1) goto error;
+ continue;
}
/*
- * Record progress even if we error out.
- *
- * Don't change this, you will break things.
+ * Process PROTOCOL line. Defines a new protocol.
*/
- *parent = child;
-
- slen = fr_dict_attr_by_oid(dict, parent, attr, p);
- if (slen <= 0) return slen - (p - oid);
- return slen + (p - oid);
- }
-
- /*
- * Hit the leaf, this is the attribute we need to define.
- */
- case '\0':
- *attr = num;
- return p - oid;
-
- default:
- fr_strerror_printf("Malformed OID string, got trailing garbage '%s'", p);
- return oid - p;
- }
-}
-
-/** Lookup a protocol by its name
- *
- * @param[in] name of the protocol to locate.
- * @return
- * - Attribute matching name.
- * - NULL if no matching protocolibute could be found.
- */
-fr_dict_t *fr_dict_by_protocol_name(char const *name)
-{
- fr_dict_attr_t root = { .name = name };
- fr_dict_t find = { .root = &root };
-
- if (!protocol_by_name || !name) return NULL;
+ if (strcasecmp(argv[0], "PROTOCOL") == 0) {
+ if (argc < 2) {
+ fr_strerror_printf("Invalid PROTOCOL entry");
+ goto error;
+ }
+ if (dict_read_process_protocol(argv + 1, argc - 1) == -1) goto error;
+ continue;
+ }
- return fr_hash_table_finddata(protocol_by_name, &find);
-}
+ /*
+ * Switches the current protocol context
+ */
+ if (strcasecmp(argv[0], "BEGIN-PROTOCOL") == 0) {
+ fr_dict_t *found;
-/** Lookup a protocol by its number.
- *
- * Returns the #fr_dict_t belonging to the protocol with the specified number
- * if any have been registered.
- *
- * @param[in] num to search for.
- * @return dictionary representing the protocol (if it exists).
- */
-fr_dict_t *fr_dict_by_protocol_num(unsigned int num)
-{
- fr_dict_t find;
- fr_dict_attr_t root;
+ ctx->old_dict = ctx->dict;
- if (!protocol_by_num) return NULL;
+ if (argc != 2) {
+ fr_strerror_printf("Invalid BEGIN-PROTOCOL entry");
+ goto error;
+ }
- memset(&find, 0, sizeof(find));
- memset(&root, 0, sizeof(root));
+ found = fr_dict_by_protocol_name(argv[1]);
+ if (!found) {
+ fr_strerror_printf("Unknown protocol '%s'", argv[1]);
+ goto error;
+ }
- find.root = &root;
- root.attr = num;
+ ctx->dict = found;
- return fr_hash_table_finddata(protocol_by_num, &find);
-}
+ continue;
+ }
-/** Dictionary/attribute ctx struct
- *
- */
-typedef struct {
- fr_dict_t *found_dict; //!< Dictionary attribute found in.
- fr_dict_attr_t const *found_da; //!< Resolved attribute.
- fr_dict_attr_t const *find; //!< Attribute to find.
-} dict_attr_search_t;
+ /*
+ * Switches back to the previous protocol context
+ */
+ if (strcasecmp(argv[0], "END-PROTOCOL") == 0) {
+ fr_dict_t const *found;
-/** Search for an attribute name in all dictionaries
- *
- * @param[in] ctx Attribute to search for.
- * @param[in] data Dictionary to search in.
- * @return
- * - 0 if attribute not found in dictionary.
- * - 1 if attribute found in dictionary.
- */
-static int _dict_attr_find_in_dicts(void *ctx, void *data)
-{
- dict_attr_search_t *search = ctx;
- fr_dict_t *dict;
+ if (argc != 2) {
+ fr_strerror_printf("Invalid END-PROTOCOL entry");
+ goto error;
+ }
- if (!data) return 0; /* We get called with NULL data */
+ found = fr_dict_by_protocol_name(argv[1]);
+ if (!found) {
+ fr_strerror_printf("END-PROTOCOL %s does not refer to a valid protocol", argv[1]);
+ goto error;
+ }
- dict = talloc_get_type_abort(data, fr_dict_t);
+ if (found != ctx->dict) {
+ fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s",
+ argv[1], found->root->name);
+ goto error;
+ }
- search->found_da = fr_hash_table_finddata(dict->attributes_by_name, search->find);
- if (!search->found_da) return 0;
+ ctx->dict = ctx->old_dict; /* Switch back to the old dictionary */
- search->found_dict = data;
+ continue;
+ }
- return 1;
-}
+ /*
+ * Switches TLV parent context
+ */
+ if (strcasecmp(argv[0], "BEGIN-TLV") == 0) {
+ fr_dict_attr_t const *common;
-/** Attempt to locate the protocol dictionary containing an attribute
- *
- * @note Unlike fr_dict_by_attr_name, doesn't search through all the dictionaries,
- * just uses the fr_dict_attr_t hierarchy and the talloc hierarchy to locate
- * the dictionary (much much faster and more scalable).
- *
- * @param[in] da To get the containing dictionary for.
- * @return
- * - The dictionary containing da.
- * - NULL.
- */
-fr_dict_t *fr_dict_by_da(fr_dict_attr_t const *da)
-{
- fr_dict_attr_t const *da_p = da;
+ if ((ctx->block_tlv_depth + 1) > FR_DICT_TLV_NEST_MAX) {
+ fr_strerror_printf_push("TLVs are nested too deep");
+ goto error;
+ }
- while (da_p->parent) {
- da_p = da_p->parent;
- DA_VERIFY(da_p);
- }
+ if (argc != 2) {
+ fr_strerror_printf_push("Invalid BEGIN-TLV entry");
+ goto error;
+ }
- if (!da_p->flags.is_root) {
- fr_strerror_printf("%s: Attribute %s has not been inserted into a dictionary", __FUNCTION__, da->name);
- return NULL;
- }
+ da = fr_dict_attr_by_name(ctx->dict, argv[1]);
+ if (!da) {
+ fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
+ goto error;
+ }
- /*
- * Parent of the root attribute must
- * be the dictionary.
- */
- return talloc_get_type_abort(talloc_parent(da_p), fr_dict_t);
-}
+ if (da->type != FR_TYPE_TLV) {
+ fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'",
+ argv[1],
+ fr_int2str(dict_attr_types, da->type, "?Unknown?"));
+ goto error;
+ }
-/** Attempt to locate the protocol dictionary containing an attribute
- *
- * @note This is O(n) and will only return the first instance of the dictionary.
- *
- * @param[out] found the attribute that was resolved from the name.
- * @param[in] name the name of the attribute.
- * @return
- * - the dictionary the attribute was found in.
- * - NULL if an attribute with the specified name wasn't found in any dictionary.
- */
-fr_dict_t *fr_dict_by_attr_name(fr_dict_attr_t const **found, char const *name)
-{
- fr_dict_attr_t find = {
- .name = name
- };
- dict_attr_search_t search = {
- .find = &find
- };
- int ret;
+ common = fr_dict_parent_common(ctx->parent, da, true);
+ if (!common ||
+ (common->type == FR_TYPE_VSA) ||
+ (common->type == FR_TYPE_EVS)) {
+ fr_strerror_printf_push("Attribute '%s' should be a child of '%s'",
+ argv[1], ctx->parent->name);
+ goto error;
+ }
- *found = NULL;
+ ctx->block_tlv[ctx->block_tlv_depth++] = ctx->parent;
+ ctx->parent = da;
- if (!name || !*name) return NULL;
+ continue;
+ } /* BEGIN-TLV */
- ret = fr_hash_table_walk(protocol_by_name, _dict_attr_find_in_dicts, &search);
- if (ret == 0) return NULL;
+ /*
+ * Switches back to previous TLV parent
+ */
+ if (strcasecmp(argv[0], "END-TLV") == 0) {
+ if (--ctx->block_tlv_depth < 0) {
+ fr_strerror_printf_push("Too many END-TLV entries. Mismatch at END-TLV %s", argv[1]);
+ goto error;
+ }
- if (found) *found = search.found_da;
+ if (argc != 2) {
+ fr_strerror_printf_push("Invalid END-TLV entry");
+ goto error;
+ }
- return search.found_dict;
-}
+ da = fr_dict_attr_by_name(ctx->dict, argv[1]);
+ if (!da) {
+ fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
+ goto error;
+ }
-/** Look up a vendor by its name
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] name to search for.
- * @return
- * - The vendor.
- * - NULL if no vendor with that name was regitered for this protocol.
- */
-int fr_dict_vendor_by_name(fr_dict_t const *dict, char const *name)
-{
- fr_dict_vendor_t find = { .name = name }, *found;
+ if (da != ctx->parent) {
+ fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[1],
+ ctx->parent->name);
+ goto error;
+ }
+ ctx->parent = ctx->block_tlv[ctx->block_tlv_depth];
+ continue;
+ } /* END-VENDOR */
- if (!name) return 0;
- INTERNAL_IF_NULL(dict);
+ if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) {
+ unsigned int vendor;
+ fr_dict_attr_flags_t flags;
- found = fr_hash_table_finddata(dict->vendors_by_name, &find);
- if (!found) return 0;
+ fr_dict_attr_t const *vsa_da;
+ fr_dict_attr_t const *vendor_da;
+ fr_dict_attr_t *new;
+ fr_dict_attr_t *mutable;
- return found->vendorpec;
-}
+ if (argc < 2) {
+ fr_strerror_printf_push("Invalid BEGIN-VENDOR entry");
+ goto error;
+ }
-/** Look up a vendor by its PEN
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] vendorpec to search for.
- * @return
- * - The vendor.
- * - NULL if no vendor with that number was regitered for this protocol.
- */
-fr_dict_vendor_t const *fr_dict_vendor_by_num(fr_dict_t const *dict, int vendorpec)
-{
- fr_dict_vendor_t dv;
+ vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
+ if (!vendor) {
+ fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
+ goto error;
+ }
- INTERNAL_IF_NULL(dict);
+ /*
+ * Check for extended attr VSAs
+ *
+ * BEGIN-VENDOR foo format=Foo-Encapsulation-Attr
+ */
+ if (argc > 2) {
+ if (strncmp(argv[2], "format=", 7) != 0) {
+ fr_strerror_printf_push("Invalid format %s", argv[2]);
+ goto error;
+ }
- dv.vendorpec = vendorpec;
+ p = argv[2] + 7;
+ da = fr_dict_attr_by_name(ctx->dict, p);
+ if (!da) {
+ fr_strerror_printf_push("Invalid format for BEGIN-VENDOR: Unknown "
+ "attribute '%s'", p);
+ goto error;
+ }
- return fr_hash_table_finddata(dict->vendors_by_num, &dv);
-}
+ if (da->type != FR_TYPE_EVS) {
+ fr_strerror_printf_push("Invalid format for BEGIN-VENDOR. "
+ "Attribute '%s' should be 'evs' but is '%s'", p,
+ fr_int2str(dict_attr_types, da->type, "?Unknown?"));
+ goto error;
+ }
-/** Look up a vendor by one of its child attributes
- *
- * @param[in] da The vendor attribute.
- * @return
- * - The vendor.
- * - NULL if no vendor with that number was regitered for this protocol.
- */
-fr_dict_vendor_t const *fr_dict_vendor_by_da(fr_dict_attr_t const *da)
-{
- fr_dict_t *dict;
- fr_dict_vendor_t dv;
+ vsa_da = da;
+ } else {
+ /*
+ * Automagically create Attribute 26
+ *
+ * This should exist, but in case we're starting without
+ * the RFC dictionaries we need to add it in the case
+ * it doesn't.
+ */
+ vsa_da = fr_dict_attr_child_by_num(ctx->parent, FR_VENDOR_SPECIFIC);
+ if (!vsa_da) {
+ memset(&flags, 0, sizeof(flags));
- dv.vendorpec = fr_dict_vendor_num_by_da(da);
- if (!dv.vendorpec) return NULL;
+ memcpy(&mutable, &ctx->parent, sizeof(mutable));
+ new = dict_attr_alloc(mutable, fr_dict_root(ctx->dict), "Vendor-Specific",
+ FR_VENDOR_SPECIFIC, FR_TYPE_VSA, &flags);
+ dict_attr_child_add(mutable, new);
+ vsa_da = new;
+ }
+ }
- dict = fr_dict_by_da(da);
+ /*
+ * Create a VENDOR attribute on the fly, either in the context
+ * of the EVS attribute, or the VSA (26) attribute.
+ */
+ vendor_da = fr_dict_attr_child_by_num(vsa_da, vendor);
+ if (!vendor_da) {
+ memset(&flags, 0, sizeof(flags));
- return fr_hash_table_finddata(dict->vendors_by_num, &dv);
-}
+ if (vsa_da->type == FR_TYPE_VSA) {
+ fr_dict_vendor_t const *dv;
-/** Return the vendor that parents this attribute
- *
- * @note Uses the dictionary hierachy to determine the parent
- *
- * @param[in] da The dictionary attribute to find parent for.
- * @return
- * - NULL if the attribute has no vendor.
- * - A fr_dict_attr_t representing this attribute's associated vendor.
- */
-fr_dict_attr_t const *fr_dict_vendor_attr_by_da(fr_dict_attr_t const *da)
-{
- fr_dict_attr_t const *da_p = da;
+ dv = fr_dict_vendor_by_num(ctx->dict, vendor);
+ if (dv) {
+ flags.type_size = dv->type;
+ flags.length = dv->length;
- VERIFY_DA(da);
+ } else { /* unknown vendor, shouldn't happen */
+ flags.type_size = 1;
+ flags.length = 1;
+ }
- while (da_p->parent) {
- if (da_p->type == FR_TYPE_VENDOR) break;
- da_p = da_p->parent;
- }
- if (da_p->type != FR_TYPE_VENDOR) return NULL;
+ } else { /* EVS are always "format=1,1" */
+ flags.type_size = 1;
+ flags.length = 1;
+ }
- return da_p;
-}
+ memcpy(&mutable, &vsa_da, sizeof(mutable));
+ new = dict_attr_alloc(mutable, ctx->parent, argv[1], vendor, FR_TYPE_VENDOR, &flags);
+ dict_attr_child_add(mutable, new);
-/** Return vendor attribute for the specified dictionary and vendorpec
- *
- * @param[in] dict to search for the vendor in.
- * @param[in] vendor_root of the vendor root attribute. Could be 26 (for example) in RADIUS.
- * @param[in] vendor to find.
- * @return
- * - NULL if vendor does not exist.
- * - A fr_dict_attr_t representing the vendor in the dictionary hierarchy.
- */
-fr_dict_attr_t const *fr_dict_vendor_attr_by_num(fr_dict_t const *dict, unsigned int vendor_root, unsigned int vendor)
-{
- fr_dict_attr_t const *da;
+ vendor_da = new;
+ }
+ ctx->parent = vendor_da;
+ ctx->block_vendor = vendor;
+ continue;
+ } /* BEGIN-VENDOR */
- if (!dict) return NULL;
+ if (strcasecmp(argv[0], "END-VENDOR") == 0) {
+ unsigned int vendor;
- da = fr_dict_attr_child_by_num(fr_dict_root(dict), vendor_root);
- if (!da) {
- fr_strerror_printf("Vendor root attribute %i not defined in dict %s", vendor_root, dict->root->name);
- return NULL;
- }
+ if (argc != 2) {
+ fr_strerror_printf_push("Invalid END-VENDOR entry");
+ goto error;
+ }
- switch (da->type) {
- case FR_TYPE_VSA: /* Vendor specific attribute */
- case FR_TYPE_EVS: /* Extended vendor specific attribute */
- break;
+ vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
+ if (!vendor) {
+ fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
+ goto error;
+ }
- default:
- fr_strerror_printf("Wrong type for vendor root, expected '%s' or '%s' got '%s'",
- fr_int2str(dict_attr_types, FR_TYPE_VSA, "<INVALID>"),
- fr_int2str(dict_attr_types, FR_TYPE_EVS, "<INVALID>"),
- fr_int2str(dict_attr_types, da->type, "<INVALID>"));
- return NULL;
- }
+ if (vendor != ctx->block_vendor) {
+ fr_strerror_printf_push("END-VENDOR '%s' does not match any previous BEGIN-VENDOR",
+ argv[1]);
+ goto error;
+ }
+ ctx->parent = ctx->dict->root;
+ ctx->block_vendor = 0;
+ continue;
+ } /* END-VENDOR */
- da = fr_dict_attr_child_by_num(da, vendor);
- if (!da) {
- fr_strerror_printf("Vendor %i not defined", vendor);
- return NULL;
+ /*
+ * Any other string: We don't recognize it.
+ */
+ fr_strerror_printf_push("Invalid keyword '%s'", argv[0]);
+ goto error;
}
+ fclose(fp);
+ return 0;
+}
- if (da->type != FR_TYPE_VENDOR) {
- fr_strerror_printf("Wrong type for vendor, expected '%s' got '%s'",
- fr_int2str(dict_attr_types, da->type, "<INVALID>"),
- fr_int2str(dict_attr_types, FR_TYPE_VENDOR, "<INVALID>"));
- return NULL;
- }
+static int dict_from_file(fr_dict_t *dict,
+ char const *dir_name, char const *filename,
+ char const *src_file, int src_line)
+{
+ dict_from_file_ctx_t ctx = {
+ .dict = dict,
+ .parent = dict->root
+ };
- return da;
+ return _dict_from_file(&ctx, dir_name, filename, src_file, src_line);
}
-/** Look up a dictionary attribute by a name embedded in another string
- *
- * Find the first invalid attribute name char in the string pointed
- * to by name.
- *
- * Copy the characters between the start of the name string and the first
- * none #fr_dict_attr_allowed_chars char to a buffer and perform a dictionary lookup
- * using that value.
+/** (re)initialize a protocol dictionary
*
- * If the attribute exists, advance the pointer pointed to by name
- * to the first none #fr_dict_attr_allowed_chars char, and return the DA.
+ * Initialize the directory, then fix the attr member of all attributes.
*
- * If the attribute does not exist, don't advance the pointer and return
- * NULL.
+ * First dictionary initialised will be set as the default internal dictionary.
*
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in,out] name string start.
+ * @param[in] ctx to allocate the dictionary from.
+ * @param[out] out Where to write a pointer to the new dictionary.
+ * Will free existing dictionary if files have
+ * changed and *out is not NULL.
+ * @param[in] dir to read dictionary files from.
+ * @param[in] fn file name to read.
+ * @param[in] name to use for the root attributes.
* @return
- * - Attribute matching name.
- * - NULL if no matching attribute could be found.
+ * - 0 on success.
+ * - -1 on failure.
*/
-fr_dict_attr_t const *fr_dict_attr_by_name_substr(fr_dict_t const *dict, char const **name)
+int fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *fn, char const *name)
{
- fr_dict_attr_t find;
- fr_dict_attr_t const *da;
- char const *p;
- size_t len;
+ static bool defined_cast_types;
+ fr_dict_t *dict;
- if (!name || !*name) return NULL;
- INTERNAL_IF_NULL(dict);
+ dict = dict_alloc(ctx);
+ if (!dict) return -1;
- memset(&find, 0, sizeof(find));
+ /*
+ * Free the old dictionaries
+ */
+ if (*out == fr_dict_internal) fr_dict_internal = dict;
+ TALLOC_FREE(*out);
/*
- * Advance p until we get something that's not part of
- * the dictionary attribute name.
+ * Remove this at some point...
*/
- for (p = *name; fr_dict_attr_allowed_chars[(int)*p]; p++);
+ if (!fr_dict_internal) fr_dict_internal = dict;
- len = p - *name;
- if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
- fr_strerror_printf("Attribute name too long");
- return NULL;
- }
+ /*
+ * Magic dictionary root attribute
+ */
+ dict_root_set(dict, name, 0);
- find.name = talloc_bstrndup(NULL, *name, len);
- if (!find.name) {
- fr_strerror_printf("Out of memory");
- return NULL;
- }
- da = fr_hash_table_finddata(dict->attributes_by_name, &find);
- talloc_const_free(find.name);
+ /*
+ * Add cast attributes. We do it this way,
+ * so cast attributes get added automatically for new types.
+ *
+ * We manually add the attributes to the dictionary, and bypass
+ * fr_dict_attr_add(), because we know what we're doing, and
+ * that function does too many checks.
+ */
+ if (!defined_cast_types) {
+ FR_NAME_NUMBER const *p;
+ fr_dict_attr_flags_t flags;
+ char *type_name;
- if (!da) {
- fr_strerror_printf("Unknown attribute '%.*s'", (int) len, *name);
- return NULL;
+ memset(&flags, 0, sizeof(flags));
+
+ flags.internal = 1;
+
+ for (p = dict_attr_types; p->name; p++) {
+ fr_dict_attr_t *n;
+
+ type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+
+ n = dict_attr_alloc(dict->pool, dict->root, type_name,
+ FR_CAST_BASE + p->number, p->number, &flags);
+ if (!n) {
+ error:
+ talloc_free(dict);
+ return -1;
+ }
+
+ if (!fr_hash_table_insert(dict->attributes_by_name, n)) goto error;
+
+ /*
+ * Set up parenting for the attribute.
+ */
+ if (dict_attr_child_add(dict->root, n) < 0) goto error;
+
+ talloc_free(type_name);
+ }
+ defined_cast_types = true;
}
- *name = p;
- return da;
-}
+ if (dict_from_file(dict, dir, fn, NULL, 0) < 0) goto error;
-/** Locate a #fr_dict_attr_t by its name
- *
- * @note Unlike attribute numbers, attribute names are unique to the dictionary.
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] name of the attribute to locate.
- * @return
- * - Attribute matching name.
- * - NULL if no matching attribute could be found.
- */
-fr_dict_attr_t const *fr_dict_attr_by_name(fr_dict_t const *dict, char const *name)
-{
- fr_dict_attr_t find = { .name = name };
+ /*
+ * Resolve any VALUE aliases (enums) that were defined
+ * before the attributes they reference.
+ */
+ if (dict->enum_fixup) {
+ fr_dict_attr_t const *da;
+ dict_enum_fixup_t *this, *next;
- if (!name) return NULL;
- INTERNAL_IF_NULL(dict);
+ for (this = dict->enum_fixup; this != NULL; this = next) {
+ fr_value_box_t value;
+ fr_type_t type;
- return fr_hash_table_finddata(dict->attributes_by_name, &find);
-}
+ next = this->next;
+ da = fr_dict_attr_by_name(dict, this->attribute);
+ if (!da) {
+ fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
+ this->attribute, this->alias);
+ goto error;
+ }
+ type = da->type;
-/** Lookup a #fr_dict_attr_t by its vendor and attribute numbers
- *
- * @note This is a deprecated function, new code should use #fr_dict_attr_child_by_num.
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] vendor number of the attribute.
- * @param[in] attr number of the attribute.
- * @return
- * - Attribute matching vendor/attr.
- * - NULL if no matching attribute could be found.
- */
-fr_dict_attr_t const *fr_dict_attr_by_num(fr_dict_t *dict, unsigned int vendor, unsigned int attr)
-{
- fr_dict_attr_t const *parent;
+ if (fr_value_box_from_str(this, &value, &type, NULL,
+ this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
+ fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
+ goto error;
+ }
- INTERNAL_IF_NULL(dict);
+ if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
- if (vendor == 0) return fr_dict_attr_child_by_num(dict->root, attr);
+ /*
+ * Just so we don't lose track of things.
+ */
+ dict->enum_fixup = next;
+ }
+ }
- parent = fr_dict_attr_child_by_num(dict->root, FR_VENDOR_SPECIFIC);
- if (!parent) return NULL;
+ /*
+ * Walk over all of the hash tables to ensure they're
+ * initialized. We do this because the threads may perform
+ * lookups, and we don't want multi-threaded re-ordering
+ * of the table entries. That would be bad.
+ */
+ fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
+ fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
- parent = fr_dict_attr_child_by_num(parent, vendor);
- if (!parent) return NULL;
+ fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
+ fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
- return fr_dict_attr_child_by_num(parent, attr);
+ *out = dict;
+
+ return 0;
}
-/** Lookup a attribute by its its vendor and attribute numbers and data type
+/** (Re-)Initialize the special internal dictionary
*
- * @note Only works with FR_TYPE_COMBO_IP
+ * This dictionary has additional programatically generated attributes added to it.
*
- * @param[in] da to look for type variant of.
- * @param[in] type Variant of attribute to lookup.
+ * @param[in] ctx to allocate dictionary in.
+ * @param[out] out Where to write pointer to the internal dictionary.
+ * @param[in] dir dictionary is located in.
+ * @param[in] internal_name name of the internal dictionary dir (may be NULL).
* @return
- * - Attribute matching parent/attr/type.
- * - NULL if no matching attribute could be found.
+ * - 0 on success.
+ * - -1 on failure.
*/
-fr_dict_attr_t const *fr_dict_attr_by_type(fr_dict_attr_t const *da, fr_type_t type)
+int fr_dict_internal_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *internal_name)
{
- fr_dict_t *dict = fr_dict_by_da(da);
- fr_dict_attr_t find = {
- .parent = da->parent,
- .attr = da->attr,
- .type = type
- };
-
- return fr_hash_table_finddata(dict->attributes_combo, &find);
-}
+ fr_dict_t *dict = fr_dict_internal;
+ char *dict_dir;
+ char *tmp;
+ FR_NAME_NUMBER const *p;
+ fr_dict_attr_flags_t flags = { .internal = true };
+ char *type_name;
-/** Check if a child attribute exists in a parent using a pointer (da)
- *
- * @param[in] parent to check for child in.
- * @param[in] child to look for.
- * @return
- * - The child attribute on success.
- * - NULL if the child attribute does not exist.
- */
-inline fr_dict_attr_t const *fr_dict_attr_child_by_da(fr_dict_attr_t const *parent, fr_dict_attr_t const *child)
-{
- fr_dict_attr_t const *bin;
+ memcpy(&tmp, &dir, sizeof(tmp));
+ dict_dir = internal_name ? talloc_asprintf(NULL, "%s%c%s", dir, FR_DIR_SEP, internal_name) : tmp;
- VERIFY_DA(parent);
+ if ((!protocol_by_name || !protocol_by_num) && (dict_global_init(ctx) < 0)) return -1;
- if (!parent->children) return NULL;
+ if (!dict) {
+ dict = dict_alloc(ctx);
+ if (!dict) {
+ error:
+ if (!fr_dict_internal) talloc_free(dict);
+ if (internal_name) talloc_free(dict_dir);
+ return -1;
+ }
+ /*
+ * Set the root name of the dictionary
+ */
+ dict_root_set(dict, "internal", 0);
+ } else {
+ if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) {
+ if (internal_name) talloc_free(dict_dir);
+ return 0;
+ }
+ }
/*
- * Only some types can have children
+ * Add cast attributes. We do it this way,
+ * so cast attributes get added automatically for new types.
+ *
+ * We manually add the attributes to the dictionary, and bypass
+ * fr_dict_attr_add(), because we know what we're doing, and
+ * that function does too many checks.
*/
- switch (parent->type) {
- default:
- return NULL;
+ for (p = dict_attr_types; p->name; p++) {
+ fr_dict_attr_t *n;
+
+ type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+
+ n = dict_attr_alloc(dict->pool, dict->root, type_name,
+ FR_CAST_BASE + p->number, p->number, &flags);
+ if (!n) goto error;
+
+ if (!fr_hash_table_insert(dict->attributes_by_name, n)) {
+ fr_strerror_printf("Failed inserting \"%s\" into internal dictionary", type_name);
+ goto error;
+ }
- case FR_TYPE_STRUCTURAL:
- break;
+ /*
+ * Set up parenting for the attribute.
+ */
+ if (dict_attr_child_add(dict->root, n) < 0) goto error;
+
+ talloc_free(type_name);
}
- /*
- * Child arrays may be trimmed back to save memory.
- * Check that so we don't SEGV.
- */
- if ((child->attr & 0xff) > talloc_array_length(parent->children)) return NULL;
+ if (dict_dir && dict_from_file(dict, dict_dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
- bin = parent->children[child->attr & 0xff];
- for (;;) {
- if (!bin) return NULL;
- if (bin == child) return bin;
- bin = bin->next;
- }
+ *out = dict;
+ if (!fr_dict_internal) fr_dict_internal = dict;
- return NULL;
+ return 0;
}
-/** Check if a child attribute exists in a parent using an attribute number
+/** (Re)-initialize a protocol dictionary
*
- * @param[in] parent to check for child in.
- * @param[in] attr number to look for.
+ * Initialize the directory, then fix the attr member of all attributes.
+ *
+ * First dictionary initialised will be set as the default internal dictionary.
+ *
+ * @param[in] ctx to allocate the dictionary from.
+ * @param[out] out Where to write a pointer to the new dictionary. Will free existing
+ * dictionary if files have changed and *out is not NULL.
+ * @param[in] base_dir containing all the protocol directories.
+ * @param[in] proto_name that we're loading the dictionary for.
* @return
- * - The child attribute on success.
- * - NULL if the child attribute does not exist.
+ * - 0 on success.
+ * - -1 on failure.
*/
-inline fr_dict_attr_t const *fr_dict_attr_child_by_num(fr_dict_attr_t const *parent, unsigned int attr)
+int fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
+ char const *base_dir, char const *proto_name)
{
- fr_dict_attr_t const *bin;
-
- VERIFY_DA(parent);
+ fr_dict_t *dict;
+ char *dir;
+ char *proto_dir;
+ char *p;
- if (!parent->children) return NULL;
+ if (!protocol_by_name || !protocol_by_num) {
+ fr_strerror_printf("Dictionary not yet initialized call fr_dict_internal_afrom_file first");
+ return -1;
+ }
/*
- * Only some types can have children
+ * Increment the reference count if the dictionary
+ * has already been loaded.
*/
- switch (parent->type) {
- default:
- return NULL;
-
- case FR_TYPE_STRUCTURAL:
- break;
+ if (!*out) {
+ *out = fr_dict_by_protocol_name(proto_name);
+ if (*out) {
+ talloc_increase_ref_count(*out);
+ return 0;
+ }
}
/*
- * Child arrays may be trimmed back to save memory.
- * Check that so we don't SEGV.
+ * Replace '_' with '/'
*/
- if ((attr & 0xff) > talloc_array_length(parent->children)) return NULL;
+ proto_dir = talloc_strdup(ctx, proto_name);
+ for (p = proto_dir; *p; p++) if (*p == '_') *p = FR_DIR_SEP;
- bin = parent->children[attr & 0xff];
- for (;;) {
- if (!bin) return NULL;
- if (bin->attr == attr) return bin;
- bin = bin->next;
+ dir = talloc_asprintf(proto_dir, "%s%c%s", base_dir, FR_DIR_SEP, proto_dir);
+ if (!*out) {
+ dict = dict_alloc(ctx);
+ if (!dict) {
+ error:
+ talloc_free(proto_dir);
+ return -1;
+ }
+ } else {
+ dict = *out;
+ if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) return 0;
}
- return NULL;
-}
-
-/** Lookup the structure representing an enum value in a #fr_dict_attr_t
- *
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] da to search in.
- * @param[in] value to search for.
- * @return
- * - Matching #fr_dict_enum_t.
- * - NULL if no matching #fr_dict_enum_t could be found.
- */
-fr_dict_enum_t *fr_dict_enum_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
-{
- fr_dict_enum_t enumv, *dv;
+ dict->enum_fixup = NULL; /* just to be safe. */
- if (!da) return NULL;
+ if (dict_from_file(dict, dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
- INTERNAL_IF_NULL(dict);
+ talloc_free(proto_dir);
/*
- * Could be NULL or an unknown attribute, in which case
- * we want to avoid the lookup gracefully...
+ * Resolve any VALUE aliases (enums) that were defined
+ * before the attributes they reference.
*/
- if (value->type != da->type) return NULL;
+ if (dict->enum_fixup) {
+ fr_dict_attr_t const *da;
+ dict_enum_fixup_t *this, *next;
- /*
- * First, look up aliases.
- */
- enumv.da = da;
- enumv.alias = "";
+ for (this = dict->enum_fixup; this != NULL; this = next) {
+ fr_value_box_t value;
+ fr_type_t type;
+
+ next = this->next;
+ da = fr_dict_attr_by_name(dict, this->attribute);
+ if (!da) {
+ fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
+ this->attribute, this->alias);
+ goto error;
+ }
+ type = da->type;
+
+ if (fr_value_box_from_str(this, &value, &type, NULL,
+ this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
+ fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
+ goto error;
+ }
+
+ if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+
+ /*
+ * Just so we don't lose track of things.
+ */
+ dict->enum_fixup = next;
+ }
+ }
/*
- * Look up the attribute alias target, and use
- * the correct attribute number if found.
+ * Walk over all of the hash tables to ensure they're
+ * initialized. We do this because the threads may perform
+ * lookups, and we don't want multi-threaded re-ordering
+ * of the table entries. That would be bad.
*/
- dv = fr_hash_table_finddata(dict->values_by_alias, &enumv);
- if (dv) enumv.da = dv->da;
+ fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
+ fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
- enumv.value = value;
+ fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
+ fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
- return fr_hash_table_finddata(dict->values_by_da, &enumv);
+ *out = dict;
+
+ return 0;
}
-/** Lookup the name of an enum value in a #fr_dict_attr_t
+/** Read supplementary attribute definitions into an existing dictionary
*
- * @param[in] dict of protocol context we're operating in.
- * If NULL the internal dictionary will be used.
- * @param[in] da to search in.
- * @param[in] value number to search for.
- * @return
- * - Name of value.
- * - NULL if no matching value could be found.
+ * @param[in] dict Existing dictionary.
+ * @param[in] dir dictionary is located in.
+ * @param[in] filename of the dictionary.
*/
-char const *fr_dict_enum_alias_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
+int fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename)
{
- fr_dict_enum_t *dv;
+ INTERNAL_IF_NULL(dict);
- if (!da) return NULL;
+ if (!dict->attributes_by_name) {
+ fr_strerror_printf("%s: Must call fr_dict_from_file() before fr_dict_read()", __FUNCTION__);
+ return -1;
+ }
- INTERNAL_IF_NULL(dict);
+ return dict_from_file(dict, dir, filename, NULL, 0);
+}
- dv = fr_dict_enum_by_value(dict, da, value);
- if (!dv) return "";
+static void _fr_dict_dump(fr_dict_attr_t const *da, unsigned int lvl)
+{
+ unsigned int i;
+ size_t len;
+ fr_dict_attr_t const *p;
- return dv->alias;
+ printf("%p - %s (%u) %s\n", da, da->name, da->attr, fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+
+ len = talloc_array_length(da->children);
+ for (i = 0; i < len; i++) {
+ for (p = da->children[i]; p; p = p->next) {
+ _fr_dict_dump(p, lvl + 1);
+ }
+ }
+
+}
+
+void fr_dict_dump(fr_dict_t *dict)
+{
+ _fr_dict_dump(dict->root, 0);
}
/*
- * Get a value by its name, keyed off of an attribute.
+ * External API for testing
*/
-fr_dict_enum_t *fr_dict_enum_by_alias(fr_dict_t *dict, fr_dict_attr_t const *da, char const *alias)
+int fr_dict_parse_str(fr_dict_t *dict, char *buf, fr_dict_attr_t const *parent, unsigned int vendor)
{
- fr_dict_enum_t find, *found;
+ int argc;
+ char *argv[MAX_ARGV];
+ fr_dict_attr_flags_t base_flags;
- memset(&find, 0, sizeof(find));
+ INTERNAL_IF_NULL(dict);
- if (!alias) return NULL;
+ argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
+ if (argc == 0) return 0;
- INTERNAL_IF_NULL(dict);
+ if (strcasecmp(argv[0], "VALUE") == 0) {
+ return dict_read_process_value(dict, argv + 1, argc - 1);
+ }
- find.da = da;
- find.alias = alias;
+ if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
+ if (!parent) parent = fr_dict_root(dict);
- /*
- * Look up the attribute alias target, and use
- * the correct attribute number if found.
- */
- found = fr_hash_table_finddata(dict->values_by_alias, &find);
- if (found) find.da = found->da;
+ memset(&base_flags, 0, sizeof(base_flags));
- return fr_hash_table_finddata(dict->values_by_alias, &find);
+ return dict_read_process_attribute(dict, parent, vendor, argv + 1, argc - 1, &base_flags);
+ }
+
+ if (strcasecmp(argv[0], "VENDOR") == 0) {
+ return dict_read_process_vendor(dict, argv + 1, argc - 1);
+ }
+
+ fr_strerror_printf("Invalid input '%s'", argv[0]);
+ return -1;
}
/*