]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Reorganise attribute adding/validation functions and add support for internal diction...
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 19 Mar 2018 10:52:33 +0000 (10:52 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 19 Mar 2018 10:52:33 +0000 (10:52 +0000)
References allow one attribute to internally reference another TLV or a dictionary root.

This allows us to create "group" attributes which allow any attribute to be encoded within the group and foreign protocol attributes which allow RADIUS to be encoded within DHCP (for example).

Attribute references are also needed to mix protocols in the same virtual server, as the template validation functions can retrieve the foreign dictionary context from the talloc hierarchy.

src/include/dict.h
src/lib/util/dict.c
src/main/pair.c
src/main/tmpl.c
src/modules/rlm_ldap/rlm_ldap.c
src/modules/rlm_test/rlm_test.c
src/tests/unit/dict.txt

index 9b2ab345a46991df8c7acae0df69cddb2d990b54..1336583096eed0bd3e8df7910be9fdf53b6466d0 100644 (file)
@@ -54,7 +54,8 @@ typedef struct {
        unsigned int            is_root : 1;                    //!< Is root of a dictionary.
        unsigned int            is_unknown : 1;                 //!< Attribute number or vendor is unknown.
        unsigned int            is_raw : 1;                     //!< raw attribute, unknown or malformed
-
+       unsigned int            is_reference : 1;               //!< Is reference to another point in the attribute
+                                                               ///< tree.
        unsigned int            internal : 1;                   //!< Internal attribute, should not be received
                                                                ///< in protocol packets, should not be encoded.
        unsigned int            has_tag : 1;                    //!< Tagged attribute.
@@ -100,6 +101,14 @@ struct dict_attr {
        char const              *name;                          //!< Attribute name.
 };
 
+/** Dictionary reference
+ */
+typedef struct {
+       fr_dict_attr_t          tlv;                            //!< Describes how to encode the local TLV.
+       fr_dict_t const         *dict;                          //!< Cached dictionary pointer for "to".
+       fr_dict_attr_t const    *to;                            //!< Pointed to attribute.
+} fr_dict_attr_ref_t;
+
 /** Value of an enumerated attribute
  *
  * Maps one of more string values to integers and vice versa.
@@ -128,6 +137,7 @@ typedef struct {
 /*
  *     Dictionary constants
  */
+#define FR_DICT_PROTO_MAX_NAME_LEN     (128)                   //!< Maximum length of a protocol name.
 #define FR_DICT_ENUM_MAX_NAME_LEN      (128)                   //!< Maximum length of a enum value.
 #define FR_DICT_VENDOR_MAX_NAME_LEN    (128)                   //!< Maximum length of a vendor name.
 #define FR_DICT_ATTR_MAX_NAME_LEN      (128)                   //!< Maximum length of a attribute name.
@@ -183,10 +193,8 @@ int                        fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
 
 int                    fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename);
 
-int                    fr_dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int value);
-
 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 flags);
+                                        fr_type_t type, fr_dict_attr_flags_t const *flags);
 
 int                    fr_dict_enum_add_alias(fr_dict_attr_t const *da, char const *alias,
                                               fr_value_box_t const *value, bool coerce, bool replace);
index 7e9d475543b22407b5049bdc4df331b558312f6b..8d46db844ae96847809b5a07b2b4147ada911822 100644 (file)
@@ -257,6 +257,7 @@ bool const fr_dict_non_data_types[FR_TYPE_MAX + 1] = {
 #  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);
@@ -592,771 +593,719 @@ static int dict_global_init(TALLOC_CTX *ctx)
        return 0;
 }
 
-/** Allocate a dictionary attribute as a pool, and assign a name buffer
- *
- * @param[in] ctx              to allocate attribute in.
- * @param[in] name             to set.
- * @return
- *     - 0 on success.
- *     - -1 on failure (memory allocation error).
- */
-static fr_dict_attr_t *dict_attr_alloc_name(TALLOC_CTX *ctx, char const *name)
-{
-       fr_dict_attr_t *da;
-
-       if (!name) {
-               fr_strerror_printf("No attribute name provided");
-               return NULL;
-       }
-
-#ifdef HAVE_TALLOC_POOLED_OBJECT
-       da = talloc_pooled_object(ctx, fr_dict_attr_t, 1, strlen(name) + 1);
-       memset(da, 0, sizeof(*da));
-#else
-       da = talloc_zero(ctx, fr_dict_attr_t);
-#endif
-
-       da->name = talloc_typed_strdup(da, name);
-       if (!da->name) {
-               talloc_free(da);
-               fr_strerror_printf("Out of memory");
-               return NULL;
-       }
-
-       return da;
-}
-
-/** Copy a an existing attribute
+/** Validate a new attribute definition
  *
- * @param[in] ctx              to allocate new attribute in.
- * @param[in] in               attribute to copy.
- * @return
- *     - A copy of the input fr_dict_attr_t on success.
- *     - NULL on failure.
- */
-static fr_dict_attr_t *dict_attr_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *in)
-{
-       fr_dict_attr_t *n;
-
-       n = dict_attr_alloc_name(ctx, in->name);
-       if (!n) return NULL;
-
-       n->attr = in->attr;
-       n->type = in->type;
-       n->flags = in->flags;
-       n->parent = in->parent;
-       n->depth = in->depth;
-
-       return n;
-}
-
-/** Allocate a dictionary attribute on the heap
+ * @todo we need to check length of none vendor attributes.
  *
- * @param[in] ctx              to allocate the attribute in.
- * @param[in] parent           of the attribute, if none, should be
- *                             the dictionary root.
- * @param[in] name             of the attribute.  If NULL an OID string
- *                             will be created and set as the name.
+ * @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 the attribute.
- * @param[in] flags            to assign.
+ * @param[in] type             of attribute.
+ * @param[in] flags            to set in the attribute.
  * @return
- *     - A new fr_dict_attr_t on success.
- *     - NULL on failure.
+ *     - true if attribute definition is valid.
+ *     - false if attribute definition is not valid.
  */
-static fr_dict_attr_t *dict_attr_alloc(TALLOC_CTX *ctx,
-                                      fr_dict_attr_t const *parent,
-                                      char const *name, int attr,
-                                      fr_type_t type, fr_dict_attr_flags_t const *flags)
+static bool dict_attr_fields_valid(fr_dict_t *dict, fr_dict_attr_t const *parent,
+                                  char const *name, int *attr, fr_type_t type, fr_dict_attr_flags_t *flags)
 {
-       fr_dict_attr_t  *da;
-       fr_dict_attr_t  new = {
-               .attr   = attr,
-               .type   = type,
-               .flags  = *flags,
-               .parent = parent,
-               .depth  = parent->depth + 1,
-       };
+       size_t                  name_len;
+       fr_dict_attr_t const    *v;
 
        if (!fr_cond_assert(parent)) return NULL;
 
-       if (!name) {
-               char    buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
-               char    *p = buffer;
-               size_t  len;
+       name_len = strlen(name);
+       if (name_len >= FR_DICT_ATTR_MAX_NAME_LEN) {
+               fr_strerror_printf("Attribute name too long");
+       error:
+               fr_strerror_printf_push("Definition for '%s' is invalid", name);
+               return false;
+       }
 
-               len = snprintf(p, sizeof(buffer), "Attr-");
-               p += len;
+       if (fr_dict_valid_name(name, -1) < 0) return NULL;
 
-               len = fr_dict_print_attr_oid(p, sizeof(buffer) - (p - buffer), NULL, &new);
-               if (is_truncated(len, sizeof(buffer) - (p - buffer))) {
-                       fr_strerror_printf("OID string too long for unknown attribute");
-                       return NULL;
+       /*
+        *      type_size is used to limit the maximum attribute number, so it's checked first.
+        */
+       if (flags->type_size) {
+               if ((type != FR_TYPE_TLV) && (type != FR_TYPE_VENDOR)) {
+                       fr_strerror_printf("The 'format=' flag can only be used with attributes of type 'tlv'");
+                       goto error;
                }
 
-               da = dict_attr_alloc_name(ctx, buffer);
-       } else {
-               da = dict_attr_alloc_name(ctx, name);
+               if ((flags->type_size != 1) &&
+                   (flags->type_size != 2) &&
+                   (flags->type_size != 4)) {
+                       fr_strerror_printf("The 'format=' flag can only be used with attributes of type size 1,2 or 4");
+                       goto error;
+               }
        }
 
-       new.name = da->name;
-       memcpy(da, &new, sizeof(*da));
-
-       return da;
-}
-
-/** Set a new root dictionary 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.
- */
-static int dict_root_set(fr_dict_t *dict, char const *name, unsigned int proto_number)
-{
-       if (!fr_cond_assert(!dict->root)) {
-               fr_strerror_printf("Dictionary root already set");
-               return -1;
-       }
+       /******************** sanity check attribute number ********************/
 
-       dict->root = dict_attr_alloc_name(dict, name);
-       if (!dict->root) return -1;
+       if (parent->flags.is_root) {
+               static unsigned int max_attr = UINT8_MAX + 1;
 
-       dict->root->attr = proto_number;
-       dict->root->flags.is_root = 1;
-       dict->root->type = FR_TYPE_TLV;
-       dict->root->flags.type_size = 1;
-       dict->root->flags.length = 1;
-       VERIFY_DA(dict->root);
+               if (*attr == -1) {
+                       if (fr_dict_attr_by_name(dict, name)) return 0; /* exists, don't add it again */
+                       *attr = ++max_attr;
+                       flags->internal = 1;
 
-       return 0;
-}
+               } else if (*attr <= 0) {
+                       fr_strerror_printf("ATTRIBUTE number %i is invalid, must be greater than zero", *attr);
+                       goto error;
 
-/** Allocate a new dictionary
- *
- * @param[in] ctx to allocate dictionary in.
- * @return
- *     - NULL on memory allocation error.
- */
-static fr_dict_t *dict_alloc(TALLOC_CTX *ctx)
-{
-       fr_dict_t *dict;
+               } else if ((unsigned int) *attr > max_attr) {
+                       max_attr = *attr;
+               }
 
-       dict = talloc_zero(ctx, fr_dict_t);
-       if (!dict) {
-       error:
-               fr_strerror_printf("Failed allocating memory for dictionary");
-               talloc_free(dict);
-               return NULL;
+               /*
+                *      Auto-set internal flags for raddb/dictionary.
+                *      So that the end user doesn't have to know
+                *      about internal implementation of the server.
+                */
+               if ((parent->flags.type_size == 1) &&
+                   (*attr >= 3000) && (*attr < 4000)) {
+                       flags->internal = true;
+               }
        }
 
        /*
-        *      Pre-Allocate 5MB of pool memory for rapid startup
+        *      Any other negative attribute number is wrong.
         */
-       dict->pool = talloc_pool(dict, (1024 * 1024 * 5));
-       if (!dict->pool) goto error;
+       if (*attr < 0) {
+               fr_strerror_printf("ATTRIBUTE number %i is invalid, must be greater than zero", *attr);
+               goto error;
+       }
 
        /*
-        *      Create the table of vendor by name.   There MAY NOT
-        *      be multiple vendors of the same name.
+        *      If attributes have number greater than 255, do sanity checks.
+        *
+        *      We assume that the root attribute is of type TLV, with
+        *      the appropriate flags set for attributes in this
+        *      space.
         */
-       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;
+       if ((*attr > UINT8_MAX) && !flags->internal &&
+           !((strncmp("VMPS", name, 4) == 0) || (strncmp("VQP", name, 3) == 0))) {     /* Fixme */
+               for (v = parent; v != NULL; v = v->parent) {
+                       if ((v->type == FR_TYPE_TLV) || (v->type == FR_TYPE_VENDOR)) {
+                               if ((v->flags.type_size < 4) &&
+                                   (*attr >= (1 << (8 * v->flags.type_size)))) {
+                                       fr_strerror_printf("Attributes must have value between 1..%u",
+                                                          (1 << (8 * v->flags.type_size)) - 1);
+                                       goto error;
+                               }
+                               break;
+                       }
+               }
+       }
 
-       /*
-        *      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;
+       /******************** sanity check flags ********************/
 
        /*
-        *      Create the table of attributes by name.   There MAY NOT
-        *      be multiple attributes of the same name.
+        *      virtual attributes are special.
         */
-       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 (flags->virtual) {
+               if (!parent->flags.is_root) {
+                       fr_strerror_printf("The 'virtual' flag can only be used for normal attributes");
+                       goto error;
+               }
+
+               if (*attr <= (1 << (8 * parent->flags.type_size))) {
+                       fr_strerror_printf("The 'virtual' flag can only be used for non-protocol attributes");
+                       goto error;
+               }
+       }
 
        /*
-        *      Horrible hacks for combo-IP.
+        *      Tags can only be used in a few limited situations.
         */
-       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;
-
-       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;
+       if (flags->has_tag) {
+               if ((type != FR_TYPE_UINT32) && (type != FR_TYPE_STRING)) {
+                       fr_strerror_printf("The 'has_tag' flag can only be used for attributes of type 'integer' "
+                                          "or 'string'");
+                       goto error;
+               }
 
-       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;
+               if (!(parent->flags.is_root ||
+                     ((parent->type == FR_TYPE_VENDOR) &&
+                      (parent->parent && parent->parent->type == FR_TYPE_VSA)))) {
+                       fr_strerror_printf("The 'has_tag' flag can only be used with RFC and VSA attributes");
+                       goto error;
+               }
 
-       return dict;
-}
+               if (flags->array || flags->has_value || flags->concat || flags->virtual || flags->length) {
+                       fr_strerror_printf("The 'has_tag' flag cannot be used with any other flag");
+                       goto error;
+               }
 
-/** 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;
+               if (flags->encrypt && (flags->encrypt != FLAG_ENCRYPT_TUNNEL_PASSWORD)) {
+                       fr_strerror_printf("The 'has_tag' flag can only be used with 'encrypt=2'");
+                       goto error;
+               }
+       }
 
        /*
-        *      Setup fields in the child
+        *      'concat' can only be used in a few limited situations.
         */
-       child->parent = parent;
-       child->depth = parent->depth + 1;
+       if (flags->concat) {
+               if (type != FR_TYPE_OCTETS) {
+                       fr_strerror_printf("The 'concat' flag can only be used for attributes of type 'octets'");
+                       goto error;
+               }
 
-       VERIFY_DA(child);
+               if (!parent->flags.is_root) {
+                       fr_strerror_printf("The 'concat' flag can only be used with RFC attributes");
+                       goto error;
+               }
 
-       /*
-        *      We only allocate the pointer array *if* the parent has children.
-        */
-       if (!parent->children) parent->children = talloc_zero_array(parent, fr_dict_attr_t const *, UINT8_MAX + 1);
-       if (!parent->children) return -1;
+               if (flags->array || flags->internal || flags->has_value || flags->virtual ||
+                   flags->encrypt || flags->length) {
+                       fr_strerror_printf("The 'concat' flag cannot be used any other flag");
+                       goto error;
+               }
+       }
 
        /*
-        *      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.
+        *      'octets[n]' can only be used in a few limited situations.
         */
-       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;
+       if (flags->length) {
+               if (flags->has_value || flags->virtual) {
+                       fr_strerror_printf("The 'octets[...]' syntax cannot be used any other flag");
+                       goto error;
+               }
 
-               default:
-                       break;
+               if (flags->length > 253) {
+                       fr_strerror_printf("Invalid length %d", flags->length);
+                       return NULL;
                }
 
-               switch ((*bin)->type) {
-               case FR_TYPE_STRUCTURAL:
-                       bin_is_struct = true;
-                       break;
+               if ((type == FR_TYPE_TLV) || (type == FR_TYPE_VENDOR)) {
+                       if ((flags->length != 1) &&
+                           (flags->length != 2) &&
+                           (flags->length != 4)) {
+                               fr_strerror_printf("The 'length' flag can only be used with attributes of TLV lengths of 1,2 or 4");
+                               goto error;
+                       }
 
-               default:
-                       break;
+               } else if ((type != FR_TYPE_OCTETS) &&
+                          (type != FR_TYPE_STRUCT)) {
+                       fr_strerror_printf("The 'length' flag can only be set for attributes of type 'octets' or 'struct'");
+                       goto error;
                }
 
-               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;
+               if (type == FR_TYPE_STRUCT) {
+                       if (flags->type_size != 0) {
+                               fr_strerror_printf("Invalid initializer for type_size");
+                               goto error;
+                       }
 
-               bin = &(*bin)->next;
+                       /*
+                        *      Set maximum length for the struct, and
+                        *      initialize the current length to be zero.
+                        */
+                       flags->type_size = flags->length;
+                       flags->length = 0;
+               }
        }
 
-       memcpy(&this, &bin, sizeof(this));
-       child->next = *this;
-       *this = child;
-
-       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.
- *
- * @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
- *     - 0 on success.
- *     - -1 on failure.
- */
-int fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *fn, char const *name)
-{
-       static bool     defined_cast_types;
-       fr_dict_t       *dict;
-
-       dict = dict_alloc(ctx);
-       if (!dict) return -1;
-
-       /*
-        *      Free the old dictionaries
-        */
-       if (*out == fr_dict_internal) fr_dict_internal = dict;
-       TALLOC_FREE(*out);
-
-       /*
-        *      Remove this at some point...
-        */
-       if (!fr_dict_internal) fr_dict_internal = dict;
-
        /*
-        *      Magic dictionary root attribute
-        */
-       dict_root_set(dict, name, 0);
-
-       /*
-        *      Add cast attributes.  We do it this way,
-        *      so cast attributes get added automatically for new types.
+        *      DHCP options allow for packing multiple values into one option.
         *
-        *      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.
+        *      We allow it for DHCP and FreeDHCP dictionaries.  Not anywhere else.
         */
-       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);
+       if (flags->array) {
+               for (v = parent; v != NULL; v = v->parent) {
+                       if (v->type != FR_TYPE_VENDOR) continue;
 
-                       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 ((v->attr != 34673) && /* freedhcp */
+                           (v->attr != DHCP_MAGIC_VENDOR)) {
+                               fr_strerror_printf("The 'array' flag can only be used with DHCP options");
+                               goto error;
                        }
+                       break;
+               }
 
-                       if (!fr_hash_table_insert(dict->attributes_by_name, n)) goto error;
+               switch (type) {
+               default:
+                       fr_strerror_printf("The 'array' flag cannot be used with attributes of type '%s'",
+                                          fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
+                       goto error;
 
-                       /*
-                        *      Set up parenting for the attribute.
-                        */
-                       if (dict_attr_child_add(dict->root, n) < 0) goto error;
+               case FR_TYPE_IPV4_ADDR:
+               case FR_TYPE_IPV6_ADDR:
+               case FR_TYPE_UINT8:
+               case FR_TYPE_UINT16:
+               case FR_TYPE_UINT32:
+               case FR_TYPE_DATE:
+               case FR_TYPE_STRING:
+               case FR_TYPE_OCTETS:
+                       break;
+               }
 
-                       talloc_free(type_name);
+               if (flags->internal || flags->has_value || flags->encrypt || flags->virtual) {
+                       fr_strerror_printf("The 'array' flag cannot be used any other flag");
+                       goto error;
                }
-               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.
+        *      'has_value' should only be set internally.  If the
+        *      caller sets it, we still sanity check it.
         */
-       if (dict->enum_fixup) {
-               fr_dict_attr_t const *da;
-               dict_enum_fixup_t *this, *next;
+       if (flags->has_value) {
+               if (type != FR_TYPE_UINT32) {
+                       fr_strerror_printf("The 'has_value' flag can only be used with attributes "
+                                          "of type 'integer'");
+                       goto error;
+               }
 
-               for (this = dict->enum_fixup; this != NULL; this = next) {
-                       fr_value_box_t  value;
-                       fr_type_t       type;
+               if (flags->encrypt || flags->virtual) {
+                       fr_strerror_printf("The 'has_value' flag cannot be used with any other flag");
+                       goto error;
+               }
+       }
 
-                       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);
+       if (flags->encrypt) {
+               /*
+                *      Stupid hacks for MS-CHAP-MPPE-Keys.  The User-Password
+                *      encryption method has no provisions for encoding the
+                *      length of the data.  For User-Password, the data is
+                *      (presumably) all printable non-zero data.  For
+                *      MS-CHAP-MPPE-Keys, the data is binary crap.  So... we
+                *      MUST specify a length in the dictionary.
+                */
+               if ((flags->encrypt == FLAG_ENCRYPT_USER_PASSWORD) && (type != FR_TYPE_STRING)) {
+                       if (type != FR_TYPE_OCTETS) {
+                               fr_strerror_printf("The 'encrypt=1' flag can only be used with "
+                                                  "attributes of type 'string'");
                                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);
+                       if (flags->length == 0) {
+                               fr_strerror_printf("The 'encrypt=1' flag MUST be used with an explicit length for "
+                                                  "'octets' data types");
                                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;
                }
-       }
 
-       /*
-        *      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 (flags->encrypt > FLAG_ENCRYPT_OTHER) {
+                       fr_strerror_printf("The 'encrypt' flag can only be 0..4");
+                       goto error;
+               }
 
-       fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
-       fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
+               /*
+                *      The Tunnel-Password encryption method can be used anywhere.
+                *
+                *      We forbid User-Password and Ascend-Send-Secret
+                *      methods in the extended space.
+                */
+               if ((flags->encrypt != FLAG_ENCRYPT_TUNNEL_PASSWORD) && !flags->internal && !parent->flags.internal) {
+                       for (v = parent; v != NULL; v = v->parent) {
+                               switch (v->type) {
+                               case FR_TYPE_EXTENDED:
+                               case FR_TYPE_LONG_EXTENDED:
+                               case FR_TYPE_EVS:
+                                       fr_strerror_printf("The 'encrypt=%d' flag cannot be used with attributes "
+                                                          "of type '%s'", flags->encrypt,
+                                                          fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
+                                       goto error;
 
-       *out = dict;
+                               default:
+                                       break;
+                               }
 
-       return 0;
-}
+                       }
+               }
 
-/** (Re-)Initialize the special internal dictionary
- *
- * This dictionary has additional programatically generated attributes added to it.
- *
- * @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
- *     - 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)
-{
-       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;
+               switch (type) {
+               case FR_TYPE_TLV:
+                       if (flags->internal || parent->flags.internal) break;
+                       /* FALL-THROUGH */
 
-       memcpy(&tmp, &dir, sizeof(tmp));
-       dict_dir = internal_name ? talloc_asprintf(NULL, "%s%c%s", dir, FR_DIR_SEP, internal_name) : tmp;
+               default:
+               encrypt_fail:
+                       fr_strerror_printf("The 'encrypt' flag cannot be used with attributes of type '%s'",
+                                          fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
+                       goto error;
 
-       if ((!protocol_by_name || !protocol_by_num) && (dict_global_init(ctx) < 0)) return -1;
+               case FR_TYPE_IPV4_ADDR:
+               case FR_TYPE_UINT32:
+               case FR_TYPE_OCTETS:
+                       if (flags->encrypt == FLAG_ENCRYPT_ASCEND_SECRET) goto encrypt_fail;
 
-       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;
+               case FR_TYPE_STRING:
+                       break;
                }
+       }
 
-               /*
-                *      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;
+       /******************** sanity check data types and parents ********************/
+
+       /*
+        *      Enforce restrictions on which data types can appear where.
+        */
+       switch (type) {
+       /*
+        *      These types may only be parented from the root of the dictionary
+        */
+       case FR_TYPE_EXTENDED:
+       case FR_TYPE_LONG_EXTENDED:
+//     case FR_TYPE_VSA:
+               if (!parent->flags.is_root) {
+                       fr_strerror_printf("Attributes of type '%s' can only be used in the RFC space",
+                                          fr_int2str(dict_attr_types, type, "?Unknown?"));
+                       goto error;
                }
-       }
+               break;
+
        /*
-        *      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.
+        *      EVS may only occur under extended and long extended.
         */
-       for (p = dict_attr_types; p->name; p++) {
-               fr_dict_attr_t *n;
+       case FR_TYPE_EVS:
+               if ((parent->type != FR_TYPE_EXTENDED) && (parent->type != FR_TYPE_LONG_EXTENDED)) {
+                       fr_strerror_printf("Attributes of type 'evs' MUST have a parent of type 'extended', "
+                                          "instead of '%s'", fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
+                       goto error;
+               }
+               break;
 
-               type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+       case FR_TYPE_VENDOR:
+               if ((parent->type != FR_TYPE_VSA) && (parent->type != FR_TYPE_EVS)) {
+                       fr_strerror_printf("Attributes of type 'vendor' MUST have a parent of type 'vsa' or "
+                                          "'evs', instead of '%s'",
+                                          fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
+                       goto error;
+               }
 
-               n = dict_attr_alloc(dict->pool, dict->root, type_name,
-                                      FR_CAST_BASE + p->number, p->number, &flags);
-               if (!n) goto error;
+               if (parent->type == FR_TYPE_VSA) {
+                       fr_dict_vendor_t const *dv;
 
-               if (!fr_hash_table_insert(dict->attributes_by_name, n)) {
-                       fr_strerror_printf("Failed inserting \"%s\" into internal dictionary", type_name);
-                       goto error;
+                       dv = fr_dict_vendor_by_num(dict, *attr);
+                       if (dv) {
+                               flags->type_size = dv->type;
+                               flags->length = dv->length;
+                       } else {
+                               flags->type_size = 1;
+                               flags->length = 1;
+                       }
+               } else {
+                       flags->type_size = 1;
+                       flags->length = 1;
                }
+               break;
 
+       case FR_TYPE_TLV:
                /*
-                *      Set up parenting for the attribute.
+                *      Ensure that type_size and length are set.
                 */
-               if (dict_attr_child_add(dict->root, n) < 0) goto error;
+               for (v = parent; v != NULL; v = v->parent) {
+                       if ((v->type == FR_TYPE_TLV) || (v->type == FR_TYPE_VENDOR)) {
+                               break;
+                       }
+               }
 
-               talloc_free(type_name);
-       }
+               /*
+                *      root is always FR_TYPE_TLV, so we're OK.
+                */
+               if (!v) {
+                       fr_strerror_printf("Attributes of type '%s' require a parent attribute",
+                                          fr_int2str(dict_attr_types, type, "?Unknown?"));
+                       goto error;
+               }
 
-       if (dict_dir && dict_from_file(dict, dict_dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
+               /*
+                *      Over-ride whatever was there before, so we
+                *      don't have multiple formats of VSAs.
+                */
+               flags->type_size = v->flags.type_size;
+               flags->length = v->flags.length;
+               break;
 
-       *out = dict;
-       if (!fr_dict_internal) fr_dict_internal = dict;
+       case FR_TYPE_COMBO_IP_ADDR:
+               /*
+                *      RFC 6929 says that this is a terrible idea.
+                */
+               for (v = parent; v != NULL; v = v->parent) {
+                       if (v->type == FR_TYPE_VSA) {
+                               break;
+                       }
+               }
 
-       return 0;
-}
+               if (!v) {
+                       fr_strerror_printf("Attributes of type '%s' can only be used in VSA dictionaries",
+                                          fr_int2str(dict_attr_types, type, "?Unknown?"));
+                       goto error;
+               }
+               break;
 
-/** (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.
- *
- * @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
- *     - 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)
-{
-       fr_dict_t       *dict;
-       char            *dir;
-       char            *proto_dir;
-       char            *p;
+       case FR_TYPE_INVALID:
+       case FR_TYPE_TIMEVAL:
+       case FR_TYPE_FLOAT64:
+       case FR_TYPE_COMBO_IP_PREFIX:
+               fr_strerror_printf("Attributes of type '%s' cannot be used in dictionaries",
+                                  fr_int2str(dict_attr_types, type, "?Unknown?"));
+               goto error;
 
-       if (!protocol_by_name || !protocol_by_num) {
-               fr_strerror_printf("Dictionary not yet initialized call fr_dict_internal_afrom_file first");
-               return -1;
+       default:
+               break;
        }
 
        /*
-        *      Increment the reference count if the dictionary
-        *      has already been loaded.
+        *      Force "length" for data types of fixed length;
         */
-       if (!*out) {
-               *out = fr_dict_by_protocol_name(proto_name);
-               if (*out) {
-                        talloc_increase_ref_count(*out);
-                        return 0;
-               }
-       }
+       switch (type) {
+       case FR_TYPE_UINT8:
+       case FR_TYPE_BOOL:
+               flags->length = 1;
+               break;
 
-       /*
-        *      Replace '_' with '/'
-        */
-       proto_dir = talloc_strdup(ctx, proto_name);
-       for (p = proto_dir; *p; p++) if (*p == '_') *p = FR_DIR_SEP;
+       case FR_TYPE_UINT16:
+               flags->length = 2;
+               break;
 
-       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;
-       }
+       case FR_TYPE_DATE:
+       case FR_TYPE_IPV4_ADDR:
+       case FR_TYPE_UINT32:
+       case FR_TYPE_INT32:
+               flags->length = 4;
+               break;
 
-       dict->enum_fixup = NULL;        /* just to be safe. */
+       case FR_TYPE_UINT64:
+               flags->length = 8;
+               break;
 
-       if (dict_from_file(dict, dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
+       case FR_TYPE_SIZE:
+               flags->length = sizeof(size_t);
+               break;
 
-       talloc_free(proto_dir);
+       case FR_TYPE_ETHERNET:
+               flags->length = 6;
+               break;
 
-       /*
-        *      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;
+       case FR_TYPE_IFID:
+               flags->length = 8;
+               break;
 
-               for (this = dict->enum_fixup; this != NULL; this = next) {
-                       fr_value_box_t  value;
-                       fr_type_t       type;
+       case FR_TYPE_IPV6_ADDR:
+               flags->length = 16;
+               break;
 
-                       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;
+       case FR_TYPE_EXTENDED:
+               if (!parent->flags.is_root || (*attr < 241)) {
+                       fr_strerror_printf("Attributes of type 'extended' MUST be "
+                                          "RFC attributes with value >= 241.");
+                       goto error;
+               }
+               flags->length = 0;
+               break;
 
-                       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;
-                       }
+       case FR_TYPE_LONG_EXTENDED:
+               if (!parent->flags.is_root || (*attr < 241)) {
+                       fr_strerror_printf("Attributes of type 'long-extended' MUST "
+                                          "be RFC attributes with value >= 241.");
+                       goto error;
+               }
 
-                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+               flags->length = 0;
+               break;
 
-                       /*
-                        *      Just so we don't lose track of things.
-                        */
-                       dict->enum_fixup = next;
+       case FR_TYPE_EVS:
+               if (*attr != FR_VENDOR_SPECIFIC) {
+                       fr_strerror_printf("Attributes of type 'evs' MUST have attribute code 26, got %i", *attr);
+                       goto error;
                }
+
+               flags->length = 0;
+               break;
+
+               /*
+                *      The length is calculated from th children, not
+                *      input as the flags.
+                */
+       case FR_TYPE_STRUCT:
+               flags->length = 0;
+               break;
+
+       case FR_TYPE_STRING:
+       case FR_TYPE_OCTETS:
+       case FR_TYPE_TLV:
+               break;
+
+       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.
+        *      Validate attribute based on parent.
         */
-       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);
+       if (parent->type == FR_TYPE_STRUCT) {
+               fr_dict_attr_t *mutable;
 
-       *out = dict;
+               /*
+                *      STRUCTs will have their length filled in later.
+                */
+               if ((type != FR_TYPE_STRUCT) && (flags->length == 0)) {
+                       fr_strerror_printf("Children of 'struct' type attributes MUST have fixed length.");
+                       goto error;
+               }
 
-       return 0;
-}
+               if ((*attr > 1) && !parent->flags.length) {
+                       fr_strerror_printf("Children of 'struct' type attributes MUST start with sub-attribute 1.");
+                       goto error;
+               }
 
-int fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename)
-{
-       INTERNAL_IF_NULL(dict);
+               /*
+                *      Sneak in the length of the children.
+                */
+               memcpy(&mutable, &parent, sizeof(mutable));
+               mutable->flags.length += flags->length;
 
-       if (!dict->attributes_by_name) {
-               fr_strerror_printf("%s: Must call fr_dict_from_file() before fr_dict_read()", "Error reading dictionary");
-               return -1;
+               /*
+                *      The struct has a maximum size.  Complain if we exceed it.
+                */
+               if (mutable->flags.type_size && (mutable->flags.length > mutable->flags.type_size)) {
+                       fr_strerror_printf("Child attribute causes struct to overflow maximum size of %d octets",
+                                          mutable->flags.type_size);
+                       goto error;
+               }
        }
 
-       return dict_from_file(dict, dir, filename, NULL, 0);
+       return true;
 }
 
-/** Add a vendor to the dictionary
- *
- * Inserts a vendor entry into the vendor hash table.  This must be done before adding
- * attributes under a VSA.
+/** Allocate a dictionary attribute as a pool, and assign a name buffer
  *
- * @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.
+ * @param[in] ctx              to allocate attribute in.
+ * @param[in] name             to set.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - 0 on success.
+ *     - -1 on failure (memory allocation error).
  */
-int fr_dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int num)
+static fr_dict_attr_t *dict_attr_alloc_name(TALLOC_CTX *ctx, char const *name)
 {
-       INTERNAL_IF_NULL(dict);
-       size_t                  len;
-       fr_dict_vendor_t        *vendor;
+       fr_dict_attr_t *da;
 
-       len = strlen(name);
-       if (len >= FR_DICT_VENDOR_MAX_NAME_LEN) {
-               fr_strerror_printf("%s: Vendor name too long", __FUNCTION__);
-               return -1;
+       if (!name) {
+               fr_strerror_printf("No attribute name provided");
+               return NULL;
        }
 
 #ifdef HAVE_TALLOC_POOLED_OBJECT
-       vendor = talloc_pooled_object(dict, fr_dict_vendor_t, 1, strlen(name) + 1);
-       memset(vendor, 0, sizeof(*vendor));
+       da = talloc_pooled_object(ctx, fr_dict_attr_t, 1, strlen(name) + 1);
+       memset(da, 0, sizeof(*da));
 #else
-       vendor = talloc_zero(dict, fr_dict_vendor_t);
+       da = talloc_zero(ctx, fr_dict_attr_t);
 #endif
 
-       vendor->name = talloc_typed_strdup(vendor, name);
-       if (!vendor->name) {
-               talloc_free(vendor);
+       da->name = talloc_typed_strdup(da, name);
+       if (!da->name) {
+               talloc_free(da);
                fr_strerror_printf("Out of memory");
-               return -1;
+               return NULL;
        }
-       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;
+       return 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);
-                       return -1;
-               }
+/** Initialise fields in a dictionary attribute structure
+ *
+ * @param[in] da               to initialise.
+ * @param[in] parent           of the attribute, if none, should be
+ *                             the dictionary root.
+ * @param[in] attr             number.
+ * @param[in] type             of the attribute.
+ * @param[in] flags            to assign.
+ */
+static inline void dict_attr_init(fr_dict_attr_t *da,
+                                 fr_dict_attr_t const *parent, int attr,
+                                 fr_type_t type, fr_dict_attr_flags_t const *flags)
+{
+       da->attr = attr;
+       da->type = type;
+       da->flags = *flags;
+       da->parent = parent;
+       da->depth = parent ? parent->depth + 1 : 0;
+}
 
-               /*
-                *      Already inserted.  Discard the duplicate entry.
-                */
-               talloc_free(vendor);
+/** Allocate a dictionary attribute on the heap
+ *
+ * @param[in] ctx              to allocate the attribute in.
+ * @param[in] parent           of the attribute, if none, should be
+ *                             the dictionary root.
+ * @param[in] name             of the attribute.  If NULL an OID string
+ *                             will be created and set as the name.
+ * @param[in] attr             number.
+ * @param[in] type             of the attribute.
+ * @param[in] flags            to assign.
+ * @return
+ *     - A new fr_dict_attr_t on success.
+ *     - NULL on failure.
+ */
+static fr_dict_attr_t *dict_attr_alloc(TALLOC_CTX *ctx,
+                                      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  *n;
 
-               return 0;
-       }
+       if (!fr_cond_assert(parent)) return NULL;
 
        /*
-        *      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.
+        *      Allocate a new attribute
         */
-       if (!fr_hash_table_replace(dict->vendors_by_num, vendor)) {
-               fr_strerror_printf("%s: Failed inserting vendor %s", __FUNCTION__, name);
-               return -1;
+       if (!name) {
+               char            buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
+               char            *p = buffer;
+               size_t          len;
+               fr_dict_attr_t  tmp;
+
+               memset(&tmp, 0, sizeof(tmp));
+               dict_attr_init(&tmp, parent, attr, type, flags);
+
+               len = snprintf(p, sizeof(buffer), "Attr-");
+               p += len;
+
+               len = fr_dict_print_attr_oid(p, sizeof(buffer) - (p - buffer), NULL, &tmp);
+               if (is_truncated(len, sizeof(buffer) - (p - buffer))) {
+                       fr_strerror_printf("OID string too long for unknown attribute");
+                       return NULL;
+               }
+
+               n = dict_attr_alloc_name(ctx, buffer);
+       } else {
+               n = dict_attr_alloc_name(ctx, name);
        }
 
-       return 0;
+       dict_attr_init(n, parent, attr, type, flags);
+       VERIFY_DA(n);
+
+       return n;
 }
 
-/** 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.
+/** Copy a an existing attribute
  *
- * @param[in] dict of protocol we're inserting.
+ * @param[in] ctx              to allocate new attribute in.
+ * @param[in] in               attribute to copy.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - A copy of the input fr_dict_attr_t on success.
+ *     - NULL on failure.
  */
-static int dict_protocol_add(fr_dict_t *dict)
+static fr_dict_attr_t *dict_attr_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *in)
 {
-       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;
-               }
-
-               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;
-               }
+       fr_dict_attr_t *n;
 
-               return 0;
-       }
+       n = dict_attr_alloc_name(ctx, in->name);
+       if (!n) return NULL;
 
-       if (!fr_hash_table_insert(protocol_by_num, dict)) {
-               fr_strerror_printf("%s: Duplicate protocol number %i", __FUNCTION__, dict->root->attr);
-               return -1;
-       }
+       dict_attr_init(n, in->parent, in->attr, in->type, &in->flags);
+       VERIFY_DA(n);
 
-       return 0;
+       return n;
 }
 
-/** Add an attribute to the name table for the dictionary.
- *
- * @todo we need to check length of none vendor attributes.
+/** Allocate a special "reference" attribute
  *
  * @param[in] dict             of protocol context we're operating in.
  *                             If NULL the internal dictionary will be used.
@@ -1366,589 +1315,717 @@ static int dict_protocol_add(fr_dict_t *dict)
  * @param[in] type             of attribute.
  * @param[in] flags            to set in the attribute.
  * @return
- *     - fr_dict_attr_t on success
- *     - NULL on failure
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-static fr_dict_attr_t *dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t const *parent,
-                                            char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t flags)
+static fr_dict_attr_t *dict_attr_ref_alloc(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)
 {
-       size_t                  namelen;
-       fr_dict_attr_t          *n;
-       fr_dict_attr_t const    *v;
-
-       INTERNAL_IF_NULL(dict);
+       fr_dict_attr_ref_t *ref_n;
 
-       VERIFY_DA(parent);
+       if (!name) {
+               fr_strerror_printf("No attribute name provided");
+               return NULL;
+       }
 
-       if (!fr_cond_assert(parent)) return NULL;
+#ifdef HAVE_TALLOC_POOLED_OBJECT
+       ref_n = talloc_pooled_object(dict->pool, fr_dict_attr_ref_t, 1, strlen(name) + 1);
+       memset(ref_n, 0, sizeof(*ref_n));
+#else
+       ref_n = talloc_zero(dict->pool, fr_dict_attr_ref_t);
+#endif
 
-       namelen = strlen(name);
-       if (namelen >= FR_DICT_ATTR_MAX_NAME_LEN) {
-               fr_strerror_printf("Attribute name too long");
-       error:
-               fr_strerror_printf_push("fr_dict_attr_add: Failed adding '%s'", name);
+       ref_n->tlv.name = talloc_typed_strdup(ref, name);
+       if (!ref_n->tlv.name) {
+               talloc_free(ref_n);
+               fr_strerror_printf("Out of memory");
                return NULL;
        }
 
-       if (fr_dict_valid_name(name, -1) < 0) return NULL;
+       dict_attr_init(&ref_n->tlv, parent, attr, type, flags);
+       ref_n->dict = fr_dict_by_da(ref);       /* Cache the dictionary */
+       ref_n->to = ref;
 
-       /*
-        *      type_size is used to limit the maximum attribute number, so it's checked first.
-        */
-       if (flags.type_size) {
-               if ((type != FR_TYPE_TLV) && (type != FR_TYPE_VENDOR)) {
-                       fr_strerror_printf("The 'format=' flag can only be used with attributes of type 'tlv'");
-                       goto error;
-               }
+       return (fr_dict_attr_t *)ref_n;
+}
 
-               if ((flags.type_size != 1) &&
-                   (flags.type_size != 2) &&
-                   (flags.type_size != 4)) {
-                       fr_strerror_printf("The 'format=' flag can only be used with attributes of type size 1,2 or 4");
-                       goto error;
-               }
-       }
+/** Set a new root dictionary 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.
+ */
+static int dict_root_set(fr_dict_t *dict, char const *name, unsigned int proto_number)
+{
+       fr_dict_attr_flags_t flags = {
+               .is_root = 1,
+               .type_size = 1,
+               .length = 1
+       };
 
-       /******************** sanity check attribute number ********************/
+       if (!fr_cond_assert(!dict->root)) {
+               fr_strerror_printf("Dictionary root already set");
+               return -1;
+       }
 
-       if (parent->flags.is_root) {
-               static unsigned int max_attr = UINT8_MAX + 1;
+       dict->root = dict_attr_alloc_name(dict, name);
+       if (!dict->root) return -1;
 
-               if (attr == -1) {
-                       if (fr_dict_attr_by_name(dict, name)) return 0; /* exists, don't add it again */
-                       attr = ++max_attr;
-                       flags.internal = 1;
+       dict_attr_init(dict->root, NULL, proto_number, FR_TYPE_TLV, &flags);
+       VERIFY_DA(dict->root);
 
-               } else if (attr <= 0) {
-                       fr_strerror_printf("ATTRIBUTE number %i is invalid, must be greater than zero", attr);
-                       goto error;
+       return 0;
+}
 
-               } else if ((unsigned int) attr > max_attr) {
-                       max_attr = attr;
-               }
+/** Allocate a new dictionary
+ *
+ * @param[in] ctx to allocate dictionary in.
+ * @return
+ *     - NULL on memory allocation error.
+ */
+static fr_dict_t *dict_alloc(TALLOC_CTX *ctx)
+{
+       fr_dict_t *dict;
 
-               /*
-                *      Auto-set internal flags for raddb/dictionary.
-                *      So that the end user doesn't have to know
-                *      about internal implementation of the server.
-                */
-               if ((parent->flags.type_size == 1) &&
-                   (attr >= 3000) && (attr < 4000)) {
-                       flags.internal = true;
-               }
+       dict = talloc_zero(ctx, fr_dict_t);
+       if (!dict) {
+       error:
+               fr_strerror_printf("Failed allocating memory for dictionary");
+               talloc_free(dict);
+               return NULL;
        }
 
        /*
-        *      Any other negative attribute number is wrong.
+        *      Pre-Allocate 5MB of pool memory for rapid startup
         */
-       if (attr < 0) {
-               fr_strerror_printf("ATTRIBUTE number %i is invalid, must be greater than zero", attr);
-               goto error;
-       }
+       dict->pool = talloc_pool(dict, (1024 * 1024 * 5));
+       if (!dict->pool) goto error;
 
        /*
-        *      If attributes have number greater than 255, do sanity checks.
-        *
-        *      We assume that the root attribute is of type TLV, with
-        *      the appropriate flags set for attributes in this
-        *      space.
+        *      Create the table of vendor by name.   There MAY NOT
+        *      be multiple vendors of the same name.
         */
-       if ((attr > UINT8_MAX) && !flags.internal &&
-           !((strncmp("VMPS", name, 4) == 0) || (strncmp("VQP", name, 3) == 0))) {     /* Fixme */
-               for (v = parent; v != NULL; v = v->parent) {
-                       if ((v->type == FR_TYPE_TLV) || (v->type == FR_TYPE_VENDOR)) {
-                               if ((v->flags.type_size < 4) &&
-                                   (attr >= (1 << (8 * v->flags.type_size)))) {
-                                       fr_strerror_printf("Attributes must have value between 1..%u",
-                                                          (1 << (8 * v->flags.type_size)) - 1);
-                                       goto error;
-                               }
-                               break;
-                       }
-               }
-       }
-
-       /******************** sanity check flags ********************/
+       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;
 
        /*
-        *      virtual attributes are special.
+        *      Create the table of vendors by value.  There MAY
+        *      be vendors of the same value.  If there are, we
+        *      pick the latest one.
         */
-       if (flags.virtual) {
-               if (!parent->flags.is_root) {
-                       fr_strerror_printf("The 'virtual' flag can only be used for normal attributes");
-                       goto error;
-               }
+       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 (attr <= (1 << (8 * parent->flags.type_size))) {
-                       fr_strerror_printf("The 'virtual' flag can only be used for non-protocol attributes");
-                       goto error;
-               }
-       }
+       /*
+        *      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;
 
        /*
-        *      Tags can only be used in a few limited situations.
+        *      Horrible hacks for combo-IP.
         */
-       if (flags.has_tag) {
-               if ((type != FR_TYPE_UINT32) && (type != FR_TYPE_STRING)) {
-                       fr_strerror_printf("The 'has_tag' flag can only be used for attributes of type 'integer' "
-                                          "or 'string'");
-                       goto error;
-               }
+       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;
 
-               if (!(parent->flags.is_root ||
-                     ((parent->type == FR_TYPE_VENDOR) &&
-                      (parent->parent && parent->parent->type == FR_TYPE_VSA)))) {
-                       fr_strerror_printf("The 'has_tag' flag can only be used with RFC and VSA attributes");
-                       goto error;
-               }
+       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;
 
-               if (flags.array || flags.has_value || flags.concat || flags.virtual ||
-                   flags.length) {
-                       fr_strerror_printf("The 'has_tag' flag cannot be used with any other flag");
-                       goto error;
-               }
+       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;
 
-               if (flags.encrypt && (flags.encrypt != FLAG_ENCRYPT_TUNNEL_PASSWORD)) {
-                       fr_strerror_printf("The 'has_tag' flag can only be used with 'encrypt=2'");
-                       goto error;
-               }
-       }
+       return dict;
+}
+
+/** (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.
+ *
+ * @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
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *fn, char const *name)
+{
+       static bool     defined_cast_types;
+       fr_dict_t       *dict;
+
+       dict = dict_alloc(ctx);
+       if (!dict) return -1;
 
        /*
-        *      'concat' can only be used in a few limited situations.
+        *      Free the old dictionaries
         */
-       if (flags.concat) {
-               if (type != FR_TYPE_OCTETS) {
-                       fr_strerror_printf("The 'concat' flag can only be used for attributes of type 'octets'");
-                       goto error;
-               }
+       if (*out == fr_dict_internal) fr_dict_internal = dict;
+       TALLOC_FREE(*out);
 
-               if (!parent->flags.is_root) {
-                       fr_strerror_printf("The 'concat' flag can only be used with RFC attributes");
-                       goto error;
-               }
+       /*
+        *      Remove this at some point...
+        */
+       if (!fr_dict_internal) fr_dict_internal = dict;
 
-               if (flags.array || flags.internal || flags.has_value || flags.virtual ||
-                   flags.encrypt || flags.length) {
-                       fr_strerror_printf("The 'concat' flag cannot be used any other flag");
-                       goto error;
-               }
-       }
+       /*
+        *      Magic dictionary root attribute
+        */
+       dict_root_set(dict, name, 0);
 
        /*
-        *      'octets[n]' can only be used in a few limited situations.
+        *      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 (flags.length) {
-               if (flags.has_value || flags.virtual) {
-                       fr_strerror_printf("The 'octets[...]' syntax cannot be used any other flag");
-                       goto error;
-               }
+       if (!defined_cast_types) {
+               FR_NAME_NUMBER const    *p;
+               fr_dict_attr_flags_t    flags;
+               char                    *type_name;
 
-               if (flags.length > 253) {
-                       fr_strerror_printf("Invalid length %d", flags.length);
-                       return NULL;
-               }
+               memset(&flags, 0, sizeof(flags));
 
-               if ((type == FR_TYPE_TLV) || (type == FR_TYPE_VENDOR)) {
-                       if ((flags.length != 1) &&
-                           (flags.length != 2) &&
-                           (flags.length != 4)) {
-                               fr_strerror_printf("The 'length' flag can only be used with attributes of TLV lengths of 1,2 or 4");
-                               goto error;
-                       }
+               flags.internal = 1;
 
-               } else if ((type != FR_TYPE_OCTETS) &&
-                          (type != FR_TYPE_STRUCT)) {
-                       fr_strerror_printf("The 'length' flag can only be set for attributes of type 'octets' or 'struct'");
-                       goto error;
-               }
+               for (p = dict_attr_types; p->name; p++) {
+                       fr_dict_attr_t *n;
 
-               if (type == FR_TYPE_STRUCT) {
-                       if (flags.type_size != 0) {
-                               fr_strerror_printf("Invalid initializer for type_size");
-                               goto error;
+                       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 maximum length for the struct, and
-                        *      initialize the current length to be zero.
+                        *      Set up parenting for the attribute.
                         */
-                       flags.type_size = flags.length;
-                       flags.length = 0;
+                       if (dict_attr_child_add(dict->root, n) < 0) goto error;
+
+                       talloc_free(type_name);
                }
+               defined_cast_types = true;
        }
 
+       if (dict_from_file(dict, dir, fn, NULL, 0) < 0) goto error;
+
        /*
-        *      DHCP options allow for packing multiple values into one option.
-        *
-        *      We allow it for DHCP and FreeDHCP dictionaries.  Not anywhere else.
+        *      Resolve any VALUE aliases (enums) that were defined
+        *      before the attributes they reference.
         */
-       if (flags.array) {
-               for (v = parent; v != NULL; v = v->parent) {
-                       if (v->type != FR_TYPE_VENDOR) continue;
+       if (dict->enum_fixup) {
+               fr_dict_attr_t const *da;
+               dict_enum_fixup_t *this, *next;
 
-                       if ((v->attr != 34673) && /* freedhcp */
-                           (v->attr != DHCP_MAGIC_VENDOR)) {
-                               fr_strerror_printf("The 'array' flag can only be used with DHCP options");
+               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;
                        }
-                       break;
-               }
+                       type = da->type;
 
-               switch (type) {
-               default:
-                       fr_strerror_printf("The 'array' flag cannot be used with attributes of type '%s'",
-                                          fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
-                       goto error;
+                       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;
+                       }
 
-               case FR_TYPE_IPV4_ADDR:
-               case FR_TYPE_IPV6_ADDR:
-               case FR_TYPE_UINT8:
-               case FR_TYPE_UINT16:
-               case FR_TYPE_UINT32:
-               case FR_TYPE_DATE:
-               case FR_TYPE_STRING:
-               case FR_TYPE_OCTETS:
-                       break;
-               }
+                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
 
-               if (flags.internal || flags.has_value || flags.encrypt || flags.virtual) {
-                       fr_strerror_printf("The 'array' flag cannot be used any other flag");
-                       goto error;
+                       /*
+                        *      Just so we don't lose track of things.
+                        */
+                       dict->enum_fixup = next;
                }
        }
 
        /*
-        *      'has_value' should only be set internally.  If the
-        *      caller sets it, we still sanity check it.
+        *      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.
         */
-       if (flags.has_value) {
-               if (type != FR_TYPE_UINT32) {
-                       fr_strerror_printf("The 'has_value' flag can only be used with attributes "
-                                          "of type 'integer'");
-                       goto error;
-               }
+       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 (flags.encrypt || flags.virtual) {
-                       fr_strerror_printf("The 'has_value' flag cannot be used with any other flag");
-                       goto error;
+       fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
+       fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
+
+       *out = dict;
+
+       return 0;
+}
+
+/** (Re-)Initialize the special internal dictionary
+ *
+ * This dictionary has additional programatically generated attributes added to it.
+ *
+ * @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
+ *     - 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)
+{
+       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;
+
+       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 (flags.encrypt) {
                /*
-                *      Stupid hacks for MS-CHAP-MPPE-Keys.  The User-Password
-                *      encryption method has no provisions for encoding the
-                *      length of the data.  For User-Password, the data is
-                *      (presumably) all printable non-zero data.  For
-                *      MS-CHAP-MPPE-Keys, the data is binary crap.  So... we
-                *      MUST specify a length in the dictionary.
+                *      Set the root name of the dictionary
                 */
-               if ((flags.encrypt == FLAG_ENCRYPT_USER_PASSWORD) && (type != FR_TYPE_STRING)) {
-                       if (type != FR_TYPE_OCTETS) {
-                               fr_strerror_printf("The 'encrypt=1' flag can only be used with "
-                                                  "attributes of type 'string'");
-                               goto error;
-                       }
-
-                       if (flags.length == 0) {
-                               fr_strerror_printf("The 'encrypt=1' flag MUST be used with an explicit length for "
-                                                  "'octets' data types");
-                               goto error;
-                       }
+               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;
                }
+       }
+       /*
+        *      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.
+        */
+       for (p = dict_attr_types; p->name; p++) {
+               fr_dict_attr_t *n;
 
-               if (flags.encrypt > FLAG_ENCRYPT_OTHER) {
-                       fr_strerror_printf("The 'encrypt' flag can only be 0..4");
+               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;
                }
 
                /*
-                *      The Tunnel-Password encryption method can be used anywhere.
-                *
-                *      We forbid User-Password and Ascend-Send-Secret
-                *      methods in the extended space.
+                *      Set up parenting for the attribute.
                 */
-               if ((flags.encrypt != FLAG_ENCRYPT_TUNNEL_PASSWORD) && !flags.internal && !parent->flags.internal) {
-                       for (v = parent; v != NULL; v = v->parent) {
-                               switch (v->type) {
-                               case FR_TYPE_EXTENDED:
-                               case FR_TYPE_LONG_EXTENDED:
-                               case FR_TYPE_EVS:
-                                       fr_strerror_printf("The 'encrypt=%d' flag cannot be used with attributes "
-                                                          "of type '%s'", flags.encrypt,
-                                                          fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
-                                       goto error;
+               if (dict_attr_child_add(dict->root, n) < 0) goto error;
 
-                               default:
-                                       break;
-                               }
+               talloc_free(type_name);
+       }
 
-                       }
-               }
+       if (dict_dir && dict_from_file(dict, dict_dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
 
-               switch (type) {
-               case FR_TYPE_TLV:
-                       if (flags.internal || parent->flags.internal) break;
-                       /* FALL-THROUGH */
+       *out = dict;
+       if (!fr_dict_internal) fr_dict_internal = dict;
 
-               default:
-               encrypt_fail:
-                       fr_strerror_printf("The 'encrypt' flag cannot be used with attributes of type '%s'",
-                                          fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
-                       goto error;
+       return 0;
+}
 
-               case FR_TYPE_IPV4_ADDR:
-               case FR_TYPE_UINT32:
-               case FR_TYPE_OCTETS:
-                       if (flags.encrypt == FLAG_ENCRYPT_ASCEND_SECRET) goto encrypt_fail;
+/** (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.
+ *
+ * @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
+ *     - 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)
+{
+       fr_dict_t       *dict;
+       char            *dir;
+       char            *proto_dir;
+       char            *p;
 
-               case FR_TYPE_STRING:
-                       break;
-               }
+       if (!protocol_by_name || !protocol_by_num) {
+               fr_strerror_printf("Dictionary not yet initialized call fr_dict_internal_afrom_file first");
+               return -1;
        }
 
-       /******************** sanity check data types and parents ********************/
-
-       /*
-        *      Enforce restrictions on which data types can appear where.
-        */
-       switch (type) {
        /*
-        *      These types may only be parented from the root of the dictionary
+        *      Increment the reference count if the dictionary
+        *      has already been loaded.
         */
-       case FR_TYPE_EXTENDED:
-       case FR_TYPE_LONG_EXTENDED:
-//     case FR_TYPE_VSA:
-               if (!parent->flags.is_root) {
-                       fr_strerror_printf("Attributes of type '%s' can only be used in the RFC space",
-                                          fr_int2str(dict_attr_types, type, "?Unknown?"));
-                       goto error;
+       if (!*out) {
+               *out = fr_dict_by_protocol_name(proto_name);
+               if (*out) {
+                        talloc_increase_ref_count(*out);
+                        return 0;
                }
-               break;
+       }
 
        /*
-        *      EVS may only occur under extended and long extended.
+        *      Replace '_' with '/'
         */
-       case FR_TYPE_EVS:
-               if ((parent->type != FR_TYPE_EXTENDED) && (parent->type != FR_TYPE_LONG_EXTENDED)) {
-                       fr_strerror_printf("Attributes of type 'evs' MUST have a parent of type 'extended', "
-                                          "instead of '%s'", fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
-                       goto error;
-               }
-               break;
+       proto_dir = talloc_strdup(ctx, proto_name);
+       for (p = proto_dir; *p; p++) if (*p == '_') *p = FR_DIR_SEP;
 
-       case FR_TYPE_VENDOR:
-               if ((parent->type != FR_TYPE_VSA) && (parent->type != FR_TYPE_EVS)) {
-                       fr_strerror_printf("Attributes of type 'vendor' MUST have a parent of type 'vsa' or "
-                                          "'evs', instead of '%s'",
-                                          fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
-                       goto error;
+       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;
+       }
 
-               if (parent->type == FR_TYPE_VSA) {
-                       fr_dict_vendor_t const *dv;
+       dict->enum_fixup = NULL;        /* just to be safe. */
 
-                       dv = fr_dict_vendor_by_num(dict, attr);
-                       if (dv) {
-                               flags.type_size = dv->type;
-                               flags.length = dv->length;
-                       } else {
-                               flags.type_size = 1;
-                               flags.length = 1;
-                       }
-               } else {
-                       flags.type_size = 1;
-                       flags.length = 1;
-               }
-               break;
+       if (dict_from_file(dict, dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
 
-       case FR_TYPE_TLV:
-               /*
-                *      Ensure that type_size and length are set.
-                */
-               for (v = parent; v != NULL; v = v->parent) {
-                       if ((v->type == FR_TYPE_TLV) || (v->type == FR_TYPE_VENDOR)) {
-                               break;
-                       }
-               }
+       talloc_free(proto_dir);
 
-               /*
-                *      root is always FR_TYPE_TLV, so we're OK.
-                */
-               if (!v) {
-                       fr_strerror_printf("Attributes of type '%s' require a parent attribute",
-                                          fr_int2str(dict_attr_types, type, "?Unknown?"));
-                       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;
 
-               /*
-                *      Over-ride whatever was there before, so we
-                *      don't have multiple formats of VSAs.
-                */
-               flags.type_size = v->flags.type_size;
-               flags.length = v->flags.length;
-               break;
+               for (this = dict->enum_fixup; this != NULL; this = next) {
+                       fr_value_box_t  value;
+                       fr_type_t       type;
 
-       case FR_TYPE_COMBO_IP_ADDR:
-               /*
-                *      RFC 6929 says that this is a terrible idea.
-                */
-               for (v = parent; v != NULL; v = v->parent) {
-                       if (v->type == FR_TYPE_VSA) {
-                               break;
+                       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 (!v) {
-                       fr_strerror_printf("Attributes of type '%s' can only be used in VSA dictionaries",
-                                          fr_int2str(dict_attr_types, type, "?Unknown?"));
-                       goto error;
-               }
-               break;
+                       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;
+                       }
 
-       case FR_TYPE_INVALID:
-       case FR_TYPE_TIMEVAL:
-       case FR_TYPE_FLOAT64:
-       case FR_TYPE_COMBO_IP_PREFIX:
-               fr_strerror_printf("Attributes of type '%s' cannot be used in dictionaries",
-                                  fr_int2str(dict_attr_types, type, "?Unknown?"));
-               goto error;
+                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
 
-       default:
-               break;
+                       /*
+                        *      Just so we don't lose track of things.
+                        */
+                       dict->enum_fixup = next;
+               }
        }
 
        /*
-        *      Force "length" for data types of fixed length;
+        *      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.
         */
-       switch (type) {
-       case FR_TYPE_UINT8:
-       case FR_TYPE_BOOL:
-               flags.length = 1;
-               break;
+       fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
+       fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
 
-       case FR_TYPE_UINT16:
-               flags.length = 2;
-               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);
 
-       case FR_TYPE_DATE:
-       case FR_TYPE_IPV4_ADDR:
-       case FR_TYPE_UINT32:
-       case FR_TYPE_INT32:
-               flags.length = 4;
-               break;
+       *out = dict;
 
-       case FR_TYPE_UINT64:
-               flags.length = 8;
-               break;
+       return 0;
+}
 
-       case FR_TYPE_SIZE:
-               flags.length = sizeof(size_t);
-               break;
+/** 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);
 
-       case FR_TYPE_ETHERNET:
-               flags.length = 6;
-               break;
+       if (!dict->attributes_by_name) {
+               fr_strerror_printf("%s: Must call fr_dict_from_file() before fr_dict_read()", __FUNCTION__);
+               return -1;
+       }
 
-       case FR_TYPE_IFID:
-               flags.length = 8;
-               break;
+       return dict_from_file(dict, dir, filename, NULL, 0);
+}
 
-       case FR_TYPE_IPV6_ADDR:
-               flags.length = 16;
-               break;
+/** 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.
+ *
+ * @param[in] dict of protocol we're inserting.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int dict_protocol_add(fr_dict_t *dict)
+{
+       if (!dict->root) return -1;     /* Should always have root */
 
-       case FR_TYPE_EXTENDED:
-               if (!parent->flags.is_root || (attr < 241)) {
-                       fr_strerror_printf("Attributes of type 'extended' MUST be "
-                                          "RFC attributes with value >= 241.");
-                       goto error;
+       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;
                }
-               flags.length = 0;
-               break;
 
-       case FR_TYPE_LONG_EXTENDED:
-               if (!parent->flags.is_root || (attr < 241)) {
-                       fr_strerror_printf("Attributes of type 'long-extended' MUST "
-                                          "be RFC attributes with value >= 241.");
-                       goto error;
+               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;
                }
 
-               flags.length = 0;
-               break;
+               return 0;
+       }
 
-       case FR_TYPE_EVS:
-               if (attr != FR_VENDOR_SPECIFIC) {
-                       fr_strerror_printf("Attributes of type 'evs' MUST have attribute code 26, got %i", attr);
-                       goto error;
-               }
+       if (!fr_hash_table_insert(protocol_by_num, dict)) {
+               fr_strerror_printf("%s: Duplicate protocol number %i", __FUNCTION__, dict->root->attr);
+               return -1;
+       }
 
-               flags.length = 0;
-               break;
+       return 0;
+}
+
+/** Add a vendor to the dictionary
+ *
+ * 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
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int num)
+{
+       INTERNAL_IF_NULL(dict);
+       size_t                  len;
+       fr_dict_vendor_t        *vendor;
+
+       len = strlen(name);
+       if (len >= FR_DICT_VENDOR_MAX_NAME_LEN) {
+               fr_strerror_printf("%s: Vendor name too long", __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
+
+       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 */
+
+       if (!fr_hash_table_insert(dict->vendors_by_name, vendor)) {
+               fr_dict_vendor_t *old_vendor;
+
+               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;
+               }
 
                /*
-                *      The length is calculated from th children, not
-                *      input as the flags.
+                *      Already inserted.  Discard the duplicate entry.
                 */
-       case FR_TYPE_STRUCT:
-               flags.length = 0;
-               break;
+               talloc_free(vendor);
 
-       case FR_TYPE_STRING:
-       case FR_TYPE_OCTETS:
-       case FR_TYPE_TLV:
-               break;
+               return 0;
+       }
 
-       default:
-               break;
+       /*
+        *      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 0;
+}
+
+/** 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;
+
        /*
-        *      Validate attribute based on parent.
+        *      Setup fields in the child
+        */
+       child->parent = parent;
+       child->depth = parent->depth + 1;
+
+       VERIFY_DA(child);
+
+       /*
+        *      We only allocate the pointer array *if* the parent has children.
+        */
+       if (!parent->children) parent->children = talloc_zero_array(parent, fr_dict_attr_t const *, UINT8_MAX + 1);
+       if (!parent->children) return -1;
+
+       /*
+        *      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 (parent->type == FR_TYPE_STRUCT) {
-               fr_dict_attr_t *mutable;
+       bin = &parent->children[child->attr & 0xff];
+       for (;;) {
+               bool child_is_struct = false;
+               bool bin_is_struct = false;
+
+               if (!*bin) break;
 
                /*
-                *      STRUCTs will have their length filled in later.
+                *      Workaround for vendors that overload the RFC space.
+                *      Structural attributes always take priority.
                 */
-               if ((type != FR_TYPE_STRUCT) && (flags.length == 0)) {
-                       fr_strerror_printf("Children of 'struct' type attributes MUST have fixed length.");
-                       goto error;
-               }
+               switch (child->type) {
+               case FR_TYPE_STRUCTURAL:
+                       child_is_struct = true;
+                       break;
 
-               if ((attr > 1) && !parent->flags.length) {
-                       fr_strerror_printf("Children of 'struct' type attributes MUST start with sub-attribute 1.");
-                       goto error;
+               default:
+                       break;
                }
 
-               /*
-                *      Sneak in the length of the children.
-                */
-               memcpy(&mutable, &parent, sizeof(mutable));
-               mutable->flags.length += flags.length;
+               switch ((*bin)->type) {
+               case FR_TYPE_STRUCTURAL:
+                       bin_is_struct = true;
+                       break;
 
-               /*
-                *      The struct has a maximum size.  Complain if we exceed it.
-                */
-               if (mutable->flags.type_size && (mutable->flags.length > mutable->flags.type_size)) {
-                       fr_strerror_printf("Child attribute causes struct to overflow maximum size of %d octets",
-                                          mutable->flags.type_size);
-                       goto error;
+               default:
+                       break;
                }
-       }
 
-       n = dict_attr_alloc(dict->pool, parent, name, attr, type, &flags);
-       if (!n) {
-               fr_strerror_printf("Out of memory");
-               goto error;
+               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;
+
+               bin = &(*bin)->next;
        }
 
+       memcpy(&this, &bin, sizeof(this));
+       child->next = *this;
+       *this = child;
+
+       return 0;
+}
+
+/** Add an attribute to the name table for 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.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t *da)
+{
        /*
         *      Insert the attribute, only if it's not a duplicate.
         */
-       if (!fr_hash_table_insert(dict->attributes_by_name, n)) {
+       if (!fr_hash_table_insert(dict->attributes_by_name, da)) {
                fr_dict_attr_t *a;
 
                /*
@@ -1956,12 +2033,12 @@ static fr_dict_attr_t *dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t con
                 *      error out.  We don't allow duplicate attribute
                 *      definitions.
                 */
-               a = fr_hash_table_finddata(dict->attributes_by_name, n);
-               if (a && (strcasecmp(a->name, n->name) == 0)) {
-                       if ((a->attr != n->attr) || (a->parent != n->parent)) {
+               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");
-                               talloc_free(n);
-                               goto error;
+                       error:
+                               return -1;
                        }
                }
 
@@ -1973,31 +2050,55 @@ static fr_dict_attr_t *dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t con
                 *      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, n)) {
+               if (!fr_hash_table_replace(dict->attributes_by_name, da)) {
                        fr_strerror_printf("Internal error storing attribute");
-                       talloc_free(n);
                        goto error;
                }
        }
 
        /*
-        *      Hacks for combo-IP
+        *      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.
         */
-       if (n->type == FR_TYPE_COMBO_IP_ADDR) {
+       switch (da->type) {
+       case FR_TYPE_COMBO_IP_ADDR:
+       {
                fr_dict_attr_t *v4, *v6;
 
-               v4 = dict_attr_acopy(dict->pool, n);
-               if (!v4) {
-                       talloc_free(n);
+               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;
                }
 
-               v6 = dict_attr_acopy(dict->pool, n);
-               if (!v6) {
-                       talloc_free(n);
+               if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
+                       fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
                        goto error;
                }
-               v4->type = FR_TYPE_IPV4_ADDR;
+       }
+
+       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;
 
                if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
                        fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
@@ -2009,13 +2110,86 @@ static fr_dict_attr_t *dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t con
                        goto error;
                }
        }
+       default:
+               break;
+       }
 
-       return n;
+       return 0;
 }
 
-/** Add an attribute to the dictionary
+
+/** Add an reference to the dictionary
  *
- * @todo we need to check length of none vendor 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.
+ * @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;
+
+       /*
+        *      Check it's valid
+        */
+       if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
+
+       /*
+        *      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);
+
+               if (to_ref->to == ref) {
+                       fr_strerror_printf("Circular reference between \"%s\" and \"%s\"", name, ref->name);
+                       return -1;
+               }
+       }
+
+       /*
+        *      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;
+       }
+
+       /*
+        *      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 an attribute to the dictionary
  *
  * @param[in] dict             of protocol context we're operating in.
  *                             If NULL the internal dictionary will be used.
@@ -2029,20 +2203,35 @@ static fr_dict_attr_t *dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t con
  *     - -1 on failure.
  */
 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 flags)
+                    char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags)
 {
-       fr_dict_attr_t *n;
-       fr_dict_attr_t *mutable;
+       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;
 
-       n = dict_attr_add_by_name(dict, parent, name, attr, type, flags);
+       n = dict_attr_alloc(dict->pool, parent, name, attr, type, &our_flags);
        if (!n) return -1;
 
+       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));
 
-       if (dict_attr_child_add(mutable, n) < 0) return -1;
+       /*
+        *      Add in by number
+        */
+       if (dict_attr_child_add(mutable, n) < 0) goto error;
 
        return 0;
 }
@@ -2281,6 +2470,85 @@ typedef struct {
        fr_dict_attr_t const    *parent;                //!< Current parent attribute (root/vendor/tlv).
 } dict_from_file_ctx_t;
 
+/** 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 the reference does not begin with .
+        *      then it's a reference into a foreign
+        *      protocol.
+        */
+       if (*p != '.') {
+               char buffer[FR_DICT_PROTO_MAX_NAME_LEN + 1];
+
+               q = strchr(p, '.');
+               if (!q) q = end;
+
+               if ((size_t)(q - p) > sizeof(buffer)) {
+                       fr_strerror_printf("Protocol name too long");
+                       return NULL;
+               }
+
+               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;
+       /*
+        *      If the reference string begins with .
+        *      then the reference is in the current
+        *      dictionary.
+        */
+       } else {
+               proto_dict = dict;
+       }
+
+       /*
+        *      If there's a '.' after the dictionary, then
+        *      the reference is to a specific attribute.
+        */
+       if (*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;
+               }
+       }
+
+       da = fr_dict_root(proto_dict);
+       if (!da) {
+               fr_strerror_printf("Dictionary missing attribute root");
+               return NULL;
+       }
+
+       return da;
+}
+
 /*
  *     Process the ATTRIBUTE command
  */
@@ -2296,6 +2564,7 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
        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)) {
@@ -2335,9 +2604,7 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
                oid = true;
 
                slen = fr_dict_attr_by_oid(dict, &parent, &attr, argv[1]);
-               if (slen <= 0) {
-                       return -1;
-               }
+               if (slen <= 0) return -1;
 
                if (!fr_cond_assert(parent)) return -1; /* Should have provided us with a parent */
        }
@@ -2366,7 +2633,7 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
                        return -1;
                }
 
-               *q = 0;
+               *q = '\0';
 
                if (!dict_read_sscanf_i(&length, p + 1)) {
                        fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
@@ -2385,53 +2652,83 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
         *      Parse options.
         */
        if (argc >= 4) {
-               char *key, *next, *last;
+               char *q, *v;
 
-               key = argv[3];
+               p = argv[3];
                do {
-                       next = strchr(key, ',');
-                       if (next) *(next++) = '\0';
+                       char key[64], value[256];
+
+                       q = strchr(p, ',');
+                       if (!q) q = p + strlen(p);
+
+                       /*
+                        *      Nothing after the trailing comma
+                        */
+                       if (p == q) break;
+
+                       if ((size_t)(q - p) > sizeof(key)) {
+                               fr_strerror_printf("ATTRIBUTE option key too long");
+                               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);
+                       }
 
                        /*
                         *      Boolean flag, means this is a tagged
                         *      attribute.
                         */
-                       if ((strcmp(key, "has_tag") == 0) || (strcmp(key, "has_tag=1") == 0)) {
+                       if (strcmp(key, "has_tag") == 0) {
                                flags.has_tag = 1;
 
                        /*
                         *      Encryption method.
                         */
-                       } else if (strncmp(key, "encrypt=", 8) == 0) {
-                               flags.encrypt = strtol(key + 8, &last, 0);
-                               if (*last) {
-                                       fr_strerror_printf("Invalid option %s", key);
+                       } else if (strcmp(key, "encrypt") == 0) {
+                               char *qq;
+
+                               flags.encrypt = strtol(value, &qq, 0);
+                               if (*qq) {
+                                       fr_strerror_printf("Invalid encrypt value \"%s\"", value);
                                        return -1;
                                }
 
-                               /*
-                                *      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 (strncmp(key, "internal", 9) == 0) {
+                       /*
+                        *      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;
 
-                       } else if (strncmp(key, "array", 6) == 0) {
+                       } else if (strcmp(key, "array") == 0) {
                                flags.array = 1;
 
-                       } else if (strncmp(key, "concat", 7) == 0) {
+                       } else if (strcmp(key, "concat") == 0) {
                                flags.concat = 1;
 
-                       } else if (strncmp(key, "virtual", 8) == 0) {
+                       } else if (strcmp(key, "virtual") == 0) {
                                flags.virtual = 1;
 
+                       } else if (strcmp(key, "reference") == 0) {
+                               ref = dict_resolve_reference(dict, value);
+                               if (!ref) return -1;
+                               flags.is_reference = 1;
+
                        /*
-                        *      The only thing is the vendor name,
-                        *      and it's a known name: allow it.
+                        *      The only thing is the vendor name, and it's a known name:
+                        *      allow it.
                         */
-                       } else if ((key == argv[3]) && !next) {
+                       } else if ((argv[3] == p) && (*q == '\0')) {
                                if (oid) {
                                        fr_strerror_printf("ATTRIBUTE cannot use a 'vendor' flag");
                                        return -1;
@@ -2451,10 +2748,8 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
                                fr_strerror_printf("Unknown option '%s'", key);
                                return -1;
                        }
-
-                       key = next;
-                       if (key && !*key) break;
-               } while (key);
+                       p = q;
+               } while (*p++);
        }
 
 #ifdef WITH_DICTIONARY_WARNINGS
@@ -2470,9 +2765,16 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
 #endif
 
        /*
-        *      Add it in.
+        *      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
         */
-       if (fr_dict_attr_add(dict, parent, argv[0], attr, type, flags) < 0) return -1;
+       } else {
+               if (dict_attr_ref_add(dict, parent, argv[0], attr, type, &flags, ref) < 0) return -1;
+       }
 
        return 0;
 }
@@ -2482,7 +2784,7 @@ static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *pa
  */
 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 *base_flags)
+                                            fr_dict_attr_flags_t const *base_flags)
 {
        int type;
        unsigned int attr;
@@ -2516,9 +2818,7 @@ static int dict_read_process_named_attribute(fr_dict_t *dict, fr_dict_attr_t con
        /*
         *      Add it in.
         */
-       if (fr_dict_attr_add(dict, parent, argv[0], attr, type, *base_flags) < 0) {
-               return -1;
-       }
+       if (fr_dict_attr_add(dict, parent, argv[0], attr, type, base_flags) < 0) return -1;
 
        return 0;
 }
@@ -2781,17 +3081,13 @@ static int dict_read_process_vendor(fr_dict_t *dict, char **argv, int argc)
        }
 
        /* Create a new VENDOR entry for the list */
-       if (fr_dict_vendor_add(dict, argv[0], value) < 0) {
-               return -1;
-       }
+       if (dict_vendor_add(dict, argv[0], value) < 0) return -1;
 
        /*
         *      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;
-               }
+               if (dict_read_parse_format(argv[2], &value, &type, &length, &continuation) < 0) return -1;
 
        } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */
                type = 4;
@@ -3479,7 +3775,7 @@ fr_dict_attr_t const *fr_dict_unknown_add(fr_dict_t *dict, fr_dict_attr_t const
        if (old->type == FR_TYPE_VENDOR) {
                fr_dict_attr_t *mutable, *n;
 
-               if (fr_dict_vendor_add(dict, old->name, old->attr) < 0) return NULL;
+               if (dict_vendor_add(dict, old->name, old->attr) < 0) return NULL;
 
                n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
 
@@ -3499,19 +3795,29 @@ fr_dict_attr_t const *fr_dict_unknown_add(fr_dict_t *dict, fr_dict_attr_t const
         */
        da = fr_dict_attr_child_by_num(parent, old->attr);
        if (da) {
+               fr_dict_attr_t *n;
+
+               n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
+               if (!n) return NULL;
+
                /*
                 *      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.
                 */
-               return dict_attr_add_by_name(dict, parent, old->name, old->attr, old->type, flags);
+               if (dict_attr_add_by_name(dict, n) < 0) {
+                       talloc_free(n);
+                       return NULL;
+               }
+
+               return n;
        }
 
        /*
         *      Add the attribute by both name and number.
         */
-       if (fr_dict_attr_add(dict, parent, old->name, old->attr, old->type, flags) < 0) return NULL;
+       if (fr_dict_attr_add(dict, parent, old->name, old->attr, old->type, &flags) < 0) return NULL;
 
        /*
         *      For paranoia, return it by name.
@@ -4891,7 +5197,7 @@ ssize_t fr_dict_valid_name(char const *name, ssize_t len)
        end = p + len;
 
        do {
-               if (!fr_dict_attr_allowed_chars[*p]) {
+               if (!fr_dict_attr_allowed_chars[(int)*p]) {
                        char buff[5];
 
                        fr_snprint(buff, sizeof(buff), (char const *)p, 1, '\'');
index 9f5f4316251afe8f84da3964c4ade4016af665e0..0250206ff9427de3d0a7745254854978cd148d07 100644 (file)
@@ -352,25 +352,26 @@ static bool otherattr(fr_dict_attr_t const *attribute, fr_dict_attr_t const **fr
 int paircompare_register_byname(char const *name, fr_dict_attr_t const *from,
                                bool first_only, RAD_COMPARE_FUNC func, void *instance)
 {
-       fr_dict_attr_flags_t flags;
-       fr_dict_attr_t const *da;
+       fr_dict_attr_flags_t    flags = {
+                                       .compare = 1
+                               };
 
-       memset(&flags, 0, sizeof(flags));
-       flags.compare = 1;
+       fr_dict_attr_t const    *da;
 
-       da = fr_dict_attr_by_name(NULL, name);
+       da = fr_dict_attr_by_name(fr_dict_internal, name);
        if (da) {
                if (!da->flags.compare) {
                        fr_strerror_printf("Attribute '%s' already exists", name);
                        return -1;
                }
        } else if (from) {
-               if (fr_dict_attr_add(NULL, fr_dict_root(fr_dict_internal), name, -1, from->type, flags) < 0) {
+               if (fr_dict_attr_add(fr_dict_internal, fr_dict_root(fr_dict_internal),
+                                    name, -1, from->type, &flags) < 0) {
                        fr_strerror_printf_push("Failed creating attribute '%s'", name);
                        return -1;
                }
 
-               da = fr_dict_attr_by_name(NULL, name);
+               da = fr_dict_attr_by_name(fr_dict_internal, name);
                if (!da) {
                        fr_strerror_printf("Failed finding attribute '%s'", name);
                        return -1;
index 961af0cebef27c0537867a17d10444bd4f54a92f..b6ed07c40c87ef5e55cfbae86bb61653b69e3947 100644 (file)
@@ -1303,7 +1303,7 @@ int tmpl_define_undefined_attr(vp_tmpl_t *vpt, fr_type_t type, fr_dict_attr_flag
 
        if (vpt->type != TMPL_TYPE_ATTR_UNDEFINED) return 1;
 
-       if (fr_dict_attr_add(NULL, fr_dict_root(fr_dict_internal), vpt->tmpl_unknown_name, -1, type, *flags) < 0) {
+       if (fr_dict_attr_add(NULL, fr_dict_root(fr_dict_internal), vpt->tmpl_unknown_name, -1, type, flags) < 0) {
                return -1;
        }
        da = fr_dict_attr_by_name(NULL, vpt->tmpl_unknown_name);
index 8c60a17b86edba7b285fb5ea0004079a5bea5fe3..1fb07ff657688aae197895b096638fade85fbb1f 100644 (file)
@@ -1456,7 +1456,7 @@ static int mod_bootstrap(void *instance, CONF_SECTION *conf)
 
                memset(&flags, 0, sizeof(flags));
                if (fr_dict_attr_add(NULL, fr_dict_root(fr_dict_internal), inst->cache_attribute, -1, FR_TYPE_STRING,
-                                    flags) < 0) {
+                                    &flags) < 0) {
                        PERROR("Error creating cache attribute");
                error:
                        return -1;
index c579fdbbc11890947f923441f6f4b8feb190708e..612abe9fcc55523277cea039ae3a743223ca657d 100644 (file)
@@ -213,8 +213,11 @@ static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf)
 {
        rlm_test_t *inst = instance;
 
-       paircompare_register_byname("test-Paircmp", fr_dict_attr_by_num(NULL, 0, FR_USER_NAME), false,
-                                   rlm_test_cmp, inst);
+       if (paircompare_register_byname("test-Paircmp", fr_dict_attr_by_num(NULL, 0, FR_USER_NAME), false,
+                                       rlm_test_cmp, inst) < 0) {
+               PERROR("Failed registering \"test-Paircmp\"");
+               return -1;
+       }
 
        /*
         *      Log some messages
index a2000dca8514e2d9172c46ff42ffe4189fc32369..bd8654f6c51ec85bdeb38704e46d32b9deab1b5d 100644 (file)
@@ -55,7 +55,7 @@ dictionary ATTRIBUTE  Unit-Array      255             octets[123458]
 data Invalid length for 'octets[...]'
 
 dictionary ATTRIBUTE   Unit-Integer    255             integer[1]
-data fr_dict_attr_add: Failed adding 'Unit-Integer': The 'length' flag can only be set for attributes of type 'octets' or 'struct'
+data Definition for 'Unit-Integer' is invalid: The 'length' flag can only be set for attributes of type 'octets' or 'struct'
 
 dictionary ATTRIBUTE   Unit-$bad$stuff 255             integer
 data Invalid character '$' in attribute name
@@ -67,13 +67,13 @@ dictionary ATTRIBUTE        Unit-Array      241.254         octets[1234]
 data Invalid length for 'octets[...]'
 
 dictionary ATTRIBUTE   Unit-Tag        241.255         integer has_tag
-data fr_dict_attr_add: Failed adding 'Unit-Tag': The 'has_tag' flag can only be used with RFC and VSA attributes
+data Definition for 'Unit-Tag' is invalid: The 'has_tag' flag can only be used with RFC and VSA attributes
 
 dictionary ATTRIBUTE   Unit-Tag        255             date    has_tag
-data fr_dict_attr_add: Failed adding 'Unit-Tag': The 'has_tag' flag can only be used for attributes of type 'integer' or 'string'
+data Definition for 'Unit-Tag' is invalid: The 'has_tag' flag can only be used for attributes of type 'integer' or 'string'
 
 dictionary ATTRIBUTE   Unit-Tag        255             tlv     encrypt=1
-data fr_dict_attr_add: Failed adding 'Unit-Tag': The 'encrypt=1' flag can only be used with attributes of type 'string'
+data Definition for 'Unit-Tag' is invalid: The 'encrypt=1' flag can only be used with attributes of type 'string'
 
 #
 #  structs