Add support for Vendor Specific Suboptions (RFC 4243)
[freeradius.git] / src / lib / dict.c
index 20324fd..1738237 100644 (file)
@@ -19,8 +19,6 @@
  *
  * Copyright 2000,2006  The FreeRADIUS server project
  */
-
-#include       <freeradius-devel/ident.h>
 RCSID("$Id$")
 
 #include       <freeradius-devel/libradius.h>
@@ -65,13 +63,9 @@ static DICT_ATTR *dict_base_attrs[256];
  */
 typedef struct dict_stat_t {
        struct dict_stat_t *next;
-       char               *name;
-       time_t             mtime;
+       struct stat stat_buf;
 } dict_stat_t;
 
-static char *stat_root_dir = NULL;
-static char *stat_root_file = NULL;
-
 static dict_stat_t *stat_head = NULL;
 static dict_stat_t *stat_tail = NULL;
 
@@ -90,17 +84,17 @@ static value_fixup_t *value_fixup = NULL;
 const FR_NAME_NUMBER dict_attr_types[] = {
        { "integer",    PW_TYPE_INTEGER },
        { "string",     PW_TYPE_STRING },
-       { "ipaddr",     PW_TYPE_IPADDR },
+       { "ipaddr",     PW_TYPE_IPV4_ADDR },
        { "date",       PW_TYPE_DATE },
        { "abinary",    PW_TYPE_ABINARY },
        { "octets",     PW_TYPE_OCTETS },
        { "ifid",       PW_TYPE_IFID },
-       { "ipv6addr",   PW_TYPE_IPV6ADDR },
-       { "ipv6prefix", PW_TYPE_IPV6PREFIX },
+       { "ipv6addr",   PW_TYPE_IPV6_ADDR },
+       { "ipv6prefix", PW_TYPE_IPV6_PREFIX },
        { "byte",       PW_TYPE_BYTE },
        { "short",      PW_TYPE_SHORT },
        { "ether",      PW_TYPE_ETHERNET },
-       { "combo-ip",   PW_TYPE_COMBO_IP },
+       { "combo-ip",   PW_TYPE_IP_ADDR },
        { "tlv",        PW_TYPE_TLV },
        { "signed",     PW_TYPE_SIGNED },
        { "extended",   PW_TYPE_EXTENDED },
@@ -112,10 +106,39 @@ const FR_NAME_NUMBER dict_attr_types[] = {
        { "int32",      PW_TYPE_SIGNED },
        { "integer64",  PW_TYPE_INTEGER64 },
        { "uint64",     PW_TYPE_INTEGER64 },
-       { "ipv4prefix", PW_TYPE_IPV4PREFIX },
+       { "ipv4prefix", PW_TYPE_IPV4_PREFIX },
+       { "cidr",       PW_TYPE_IPV4_PREFIX },
+       { "vsa",        PW_TYPE_VSA },
        { NULL, 0 }
 };
 
+/*
+ *     Map data types to min / max data sizes.
+ */
+const size_t dict_attr_sizes[PW_TYPE_MAX][2] = {
+       [PW_TYPE_INVALID]       = { ~0, 0 },
+       [PW_TYPE_STRING]        = { 0, ~0 },
+       [PW_TYPE_INTEGER]       = {4, 4 },
+       [PW_TYPE_IPV4_ADDR]     = {4, 4},
+       [PW_TYPE_DATE]          = {4, 4},
+       [PW_TYPE_ABINARY]       = {32, ~0},
+       [PW_TYPE_OCTETS]        = {0, ~0},
+       [PW_TYPE_IFID]          = {8, 8},
+       [PW_TYPE_IPV6_ADDR]     = { 16, 16},
+       [PW_TYPE_IPV6_PREFIX]   = {2, 18},
+       [PW_TYPE_BYTE]          = {1, 1},
+       [PW_TYPE_SHORT]         = {2, 2},
+       [PW_TYPE_ETHERNET]      = {6, 6},
+       [PW_TYPE_SIGNED]        = {4, 4},
+       [PW_TYPE_IP_ADDR]       = {4, 16},
+       [PW_TYPE_TLV]           = {2, ~0},
+       [PW_TYPE_EXTENDED]      = {2, ~0},
+       [PW_TYPE_LONG_EXTENDED] = {3, ~0},
+       [PW_TYPE_EVS]           = {6, ~0},
+       [PW_TYPE_INTEGER64]     = {8, 8},
+       [PW_TYPE_IPV4_PREFIX]   = {6, 6},
+       [PW_TYPE_VSA]           = {4, ~0}
+};
 
 /*
  *     For packing multiple TLV numbers into one 32-bit integer.  The
@@ -128,9 +151,9 @@ const FR_NAME_NUMBER dict_attr_types[] = {
  *     number into the upper 8 bits of the "vendor" field.
  *
  *     e.g.    OID             attribute       vendor
- *             241.1           1               (241 << 8)
- *             241.26.9.1      1               (241 << 8) | (9)
- *             241.1.2         1 | (2 << 8)    (241 << 8)
+ *             241.1           1               (241 << 24)
+ *             241.26.9.1      1               (241 << 24) | (9)
+ *             241.1.2         1 | (2 << 8)    (241 << 24)
  */
 #define MAX_TLV_NEST (4)
 /*
@@ -159,13 +182,13 @@ const int fr_attr_mask[MAX_TLV_NEST + 1] = {
 #define FNV_MAGIC_INIT (0x811c9dc5)
 #define FNV_MAGIC_PRIME (0x01000193)
 
-static uint32_t dict_hashname(const char *name)
+static uint32_t dict_hashname(char const *name)
 {
        uint32_t hash = FNV_MAGIC_INIT;
-       const char *p;
+       char const *p;
 
        for (p = name; *p != '\0'; p++) {
-               int c = *(const unsigned char *) p;
+               int c = *(unsigned char const *) p;
                if (isalpha(c)) c = tolower(c);
 
                hash *= FNV_MAGIC_PRIME;
@@ -179,32 +202,32 @@ static uint32_t dict_hashname(const char *name)
 /*
  *     Hash callback functions.
  */
-static uint32_t dict_attr_name_hash(const void *data)
+static uint32_t dict_attr_name_hash(void const *data)
 {
-       return dict_hashname(((const DICT_ATTR *)data)->name);
+       return dict_hashname(((DICT_ATTR const *)data)->name);
 }
 
-static int dict_attr_name_cmp(const void *one, const void *two)
+static int dict_attr_name_cmp(void const *one, void const *two)
 {
-       const DICT_ATTR *a = one;
-       const DICT_ATTR *b = two;
+       DICT_ATTR const *a = one;
+       DICT_ATTR const *b = two;
 
        return strcasecmp(a->name, b->name);
 }
 
-static uint32_t dict_attr_value_hash(const void *data)
+static uint32_t dict_attr_value_hash(void const *data)
 {
        uint32_t hash;
-       const DICT_ATTR *attr = data;
+       DICT_ATTR const *attr = data;
 
        hash = fr_hash(&attr->vendor, sizeof(attr->vendor));
        return fr_hash_update(&attr->attr, sizeof(attr->attr), hash);
 }
 
-static int dict_attr_value_cmp(const void *one, const void *two)
+static int dict_attr_value_cmp(void const *one, void const *two)
 {
-       const DICT_ATTR *a = one;
-       const DICT_ATTR *b = two;
+       DICT_ATTR const *a = one;
+       DICT_ATTR const *b = two;
 
        if (a->vendor < b->vendor) return -1;
        if (a->vendor > b->vendor) return +1;
@@ -212,20 +235,20 @@ static int dict_attr_value_cmp(const void *one, const void *two)
        return a->attr - b->attr;
 }
 
-static uint32_t dict_attr_combo_hash(const void *data)
+static uint32_t dict_attr_combo_hash(void const *data)
 {
        uint32_t hash;
-       const DICT_ATTR *attr = data;
+       DICT_ATTR const *attr = data;
 
        hash = fr_hash(&attr->vendor, sizeof(attr->vendor));
        hash = fr_hash_update(&attr->type, sizeof(attr->type), hash);
        return fr_hash_update(&attr->attr, sizeof(attr->attr), hash);
 }
 
-static int dict_attr_combo_cmp(const void *one, const void *two)
+static int dict_attr_combo_cmp(void const *one, void const *two)
 {
-       const DICT_ATTR *a = one;
-       const DICT_ATTR *b = two;
+       DICT_ATTR const *a = one;
+       DICT_ATTR const *b = two;
 
        if (a->type < b->type) return -1;
        if (a->type > b->type) return +1;
@@ -236,48 +259,48 @@ static int dict_attr_combo_cmp(const void *one, const void *two)
        return a->attr - b->attr;
 }
 
-static uint32_t dict_vendor_name_hash(const void *data)
+static uint32_t dict_vendor_name_hash(void const *data)
 {
-       return dict_hashname(((const DICT_VENDOR *)data)->name);
+       return dict_hashname(((DICT_VENDOR const *)data)->name);
 }
 
-static int dict_vendor_name_cmp(const void *one, const void *two)
+static int dict_vendor_name_cmp(void const *one, void const *two)
 {
-       const DICT_VENDOR *a = one;
-       const DICT_VENDOR *b = two;
+       DICT_VENDOR const *a = one;
+       DICT_VENDOR const *b = two;
 
        return strcasecmp(a->name, b->name);
 }
 
-static uint32_t dict_vendor_value_hash(const void *data)
+static uint32_t dict_vendor_value_hash(void const *data)
 {
-       return fr_hash(&(((const DICT_VENDOR *)data)->vendorpec),
-                        sizeof(((const DICT_VENDOR *)data)->vendorpec));
+       return fr_hash(&(((DICT_VENDOR const *)data)->vendorpec),
+                        sizeof(((DICT_VENDOR const *)data)->vendorpec));
 }
 
-static int dict_vendor_value_cmp(const void *one, const void *two)
+static int dict_vendor_value_cmp(void const *one, void const *two)
 {
-       const DICT_VENDOR *a = one;
-       const DICT_VENDOR *b = two;
+       DICT_VENDOR const *a = one;
+       DICT_VENDOR const *b = two;
 
        return a->vendorpec - b->vendorpec;
 }
 
-static uint32_t dict_value_name_hash(const void *data)
+static uint32_t dict_value_name_hash(void const *data)
 {
        uint32_t hash;
-       const DICT_VALUE *dval = data;
+       DICT_VALUE const *dval = data;
 
        hash = dict_hashname(dval->name);
        hash = fr_hash_update(&dval->vendor, sizeof(dval->vendor), hash);
        return fr_hash_update(&dval->attr, sizeof(dval->attr), hash);
 }
 
-static int dict_value_name_cmp(const void *one, const void *two)
+static int dict_value_name_cmp(void const *one, void const *two)
 {
        int rcode;
-       const DICT_VALUE *a = one;
-       const DICT_VALUE *b = two;
+       DICT_VALUE const *a = one;
+       DICT_VALUE const *b = two;
 
        rcode = a->attr - b->attr;
        if (rcode != 0) return rcode;
@@ -288,21 +311,21 @@ static int dict_value_name_cmp(const void *one, const void *two)
        return strcasecmp(a->name, b->name);
 }
 
-static uint32_t dict_value_value_hash(const void *data)
+static uint32_t dict_value_value_hash(void const *data)
 {
        uint32_t hash;
-       const DICT_VALUE *dval = data;
+       DICT_VALUE const *dval = data;
 
        hash = fr_hash(&dval->attr, sizeof(dval->attr));
        hash = fr_hash_update(&dval->vendor, sizeof(dval->vendor), hash);
        return fr_hash_update(&dval->value, sizeof(dval->value), hash);
 }
 
-static int dict_value_value_cmp(const void *one, const void *two)
+static int dict_value_value_cmp(void const *one, void const *two)
 {
        int rcode;
-       const DICT_VALUE *a = one;
-       const DICT_VALUE *b = two;
+       DICT_VALUE const *a = one;
+       DICT_VALUE const *b = two;
 
        if (a->vendor < b->vendor) return -1;
        if (a->vendor > b->vendor) return +1;
@@ -321,11 +344,6 @@ static void dict_stat_free(void)
 {
        dict_stat_t *this, *next;
 
-       free(stat_root_dir);
-       stat_root_dir = NULL;
-       free(stat_root_file);
-       stat_root_file = NULL;
-
        if (!stat_head) {
                stat_tail = NULL;
                return;
@@ -333,7 +351,6 @@ static void dict_stat_free(void)
 
        for (this = stat_head; this != NULL; this = next) {
                next = this->next;
-               free(this->name);
                free(this);
        }
 
@@ -344,7 +361,7 @@ static void dict_stat_free(void)
 /*
  *     Add an entry to the list of stat buffers.
  */
-static void dict_stat_add(const char *name, const struct stat *stat_buf)
+static void dict_stat_add(struct stat const *stat_buf)
 {
        dict_stat_t *this;
 
@@ -352,8 +369,7 @@ static void dict_stat_add(const char *name, const struct stat *stat_buf)
        if (!this) return;
        memset(this, 0, sizeof(*this));
 
-       this->name = strdup(name);
-       this->mtime = stat_buf->st_mtime;
+       memcpy(&(this->stat_buf), stat_buf, sizeof(this->stat_buf));
 
        if (!stat_head) {
                stat_head = stat_tail = this;
@@ -368,26 +384,49 @@ static void dict_stat_add(const char *name, const struct stat *stat_buf)
  *     See if any dictionaries have changed.  If not, don't
  *     do anything.
  */
-static int dict_stat_check(const char *root_dir, const char *root_file)
+static int dict_stat_check(char const *dir, char const *file)
 {
-       struct stat buf;
+       struct stat stat_buf;
        dict_stat_t *this;
+       char buffer[2048];
 
-       if (!stat_root_dir) return 0;
-       if (!stat_root_file) return 0;
-
-       if (strcmp(root_dir, stat_root_dir) != 0) return 0;
-       if (strcmp(root_file, stat_root_file) != 0) return 0;
+       /*
+        *      Nothing cached, all files are new.
+        */
+       if (!stat_head) return 0;
 
-       if (!stat_head) return 0; /* changed, reload */
+       /*
+        *      Stat the file.
+        */
+       snprintf(buffer, sizeof(buffer), "%s/%s", dir, file);
+       if (stat(buffer, &stat_buf) < 0) return 0;
 
+       /*
+        *      Find the cache entry.
+        *      FIXME: use a hash table.
+        *      FIXME: check dependencies, via children.
+        *             if A loads B and B changes, we probably want
+        *             to reload B at the minimum.
+        */
        for (this = stat_head; this != NULL; this = this->next) {
-               if (stat(this->name, &buf) < 0) return 0;
+               if (this->stat_buf.st_dev != stat_buf.st_dev) continue;
+               if (this->stat_buf.st_ino != stat_buf.st_ino) continue;
+
+               /*
+                *      The file has changed.  Re-read it.
+                */
+               if (this->stat_buf.st_mtime < stat_buf.st_mtime) return 0;
 
-               if (buf.st_mtime != this->mtime) return 0;
+               /*
+                *      The file is the same.  Ignore it.
+                */
+               return 1;
        }
 
-       return 1;
+       /*
+        *      Not in the cache.
+        */
+       return 0;
 }
 
 typedef struct fr_pool_t {
@@ -504,7 +543,7 @@ void dict_free(void)
 /*
  *     Add vendor to the list.
  */
-int dict_addvendor(const char *name, unsigned int value)
+int dict_addvendor(char const *name, unsigned int value)
 {
        size_t length;
        DICT_VENDOR *dv;
@@ -566,37 +605,51 @@ int dict_addvendor(const char *name, unsigned int value)
        return 0;
 }
 
+
+/*
+ *     [a-zA-Z0-9_-:.]+
+ */
+const int dict_attr_allowed_chars[256] = {
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+       0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+       0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+
 /*
  *     Add an attribute to the dictionary.
  */
-int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
+int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type,
                 ATTR_FLAGS flags)
 {
        size_t namelen;
        static int      max_attr = 0;
-       const char      *p;
-       const DICT_ATTR *da;
+       uint8_t const *p;
+       DICT_ATTR const *da;
        DICT_ATTR *n;
-       
+
        namelen = strlen(name);
        if (namelen >= DICT_ATTR_MAX_NAME_LEN) {
                fr_strerror_printf("dict_addattr: attribute name too long");
                return -1;
        }
 
-       for (p = name; *p != '\0'; p++) {
-               if (*p < ' ') {
-                       fr_strerror_printf("dict_addattr: attribute name cannot contain control characters");
-                       return -1;
-               }
-
-               if ((*p == '"') || (*p == '\\')) {
-                       fr_strerror_printf("dict_addattr: attribute name cannot contain quotation or backslash");
-                       return -1;
-               }
-
-               if ((*p == '<') || (*p == '>') || (*p == '&')) {
-                       fr_strerror_printf("dict_addattr: attribute name cannot contain XML control characters");
+       for (p = (uint8_t const *) name; *p != '\0'; p++) {
+               if (!dict_attr_allowed_chars[*p]) {
+                       fr_strerror_printf("dict_addattr: Invalid character '%c' in attribute name", *p);
                        return -1;
                }
        }
@@ -635,7 +688,7 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
         */
        if (flags.extended || flags.long_extended || flags.evs) {
                if (vendor && (vendor < FR_MAX_VENDOR)) {
-                       fr_strerror_printf("dict_addattr: VSAs cannot use the \"extended\" or \"evs\" attribute formats.");
+                       fr_strerror_printf("dict_addattr: VSAs cannot use the \"extended\" or \"evs\" attribute formats");
                        return -1;
                }
                if (flags.has_tag
@@ -643,7 +696,7 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                    || flags.array
 #endif
                    || (flags.encrypt != FLAG_ENCRYPT_NONE)) {
-                       fr_strerror_printf("dict_addattr: The \"extended\" attributes MUST NOT have any flags set.");
+                       fr_strerror_printf("dict_addattr: The \"extended\" attributes MUST NOT have any flags set");
                        return -1;
                }
        }
@@ -660,7 +713,21 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                        return -1;
                }
        }
-               
+
+       /*
+        *      Allow for generic pointers
+        */
+       switch (type) {
+       default:
+               break;
+
+       case PW_TYPE_STRING:
+       case PW_TYPE_OCTETS:
+       case PW_TYPE_TLV:
+               flags.is_pointer = true;
+               break;
+       }
+
        if (attr < 0) {
                fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (less than zero)");
                return -1;
@@ -671,6 +738,23 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                return -1;
        }
 
+       if (vendor && flags.concat) {
+               fr_strerror_printf("VSAs cannot have the \"concat\" flag set");
+               return -1;
+       }
+
+       if (flags.concat && (type != PW_TYPE_OCTETS)) {
+               fr_strerror_printf("The \"concat\" flag can only be set for attributes of type \"octets\"");
+               return -1;
+       }
+
+       if (flags.concat && (flags.has_tag || flags.array || flags.is_tlv || flags.has_tlv ||
+                            flags.length || flags.evs || flags.extended || flags.long_extended ||
+                            (flags.encrypt != FLAG_ENCRYPT_NONE))) {
+               fr_strerror_printf("The \"concat\" flag cannot be used with any other flag");
+               return -1;
+       }
+
        if ((vendor & (FR_MAX_VENDOR -1)) != 0) {
                DICT_VENDOR *dv;
                static DICT_VENDOR *last_vendor = NULL;
@@ -721,7 +805,7 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                 *      properly.
                 */
                if ((dv->type == 1) && (attr >= 256) && !flags.is_tlv) {
-                       fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (larger than 255).");
+                       fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (larger than 255)");
                        return -1;
                } /* else 256..65535 are allowed */
 
@@ -748,8 +832,8 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                        }
 
                        /*
-                        *      These flags are inhereited inherited
-                        *      from the parent.
+                        *      These flags are inherited from the
+                        *      parent.
                         */
                        flags.extended = da->flags.extended;
                        flags.long_extended = da->flags.long_extended;
@@ -783,7 +867,7 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
         */
        if ((n = fr_pool_alloc(sizeof(*n) + namelen)) == NULL) {
        oom:
-               fr_strerror_printf("dict_adnttr: out of memory");
+               fr_strerror_printf("dict_addattr: out of memory");
                return -1;
        }
 
@@ -807,7 +891,7 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                a = fr_hash_table_finddata(attributes_byname, n);
                if (a && (strcasecmp(a->name, n->name) == 0)) {
                        if (a->attr != n->attr) {
-                               fr_strerror_printf("dict_adnttr: Duplicate attribute name %s", name);
+                               fr_strerror_printf("dict_addattr: Duplicate attribute name %s", name);
                                fr_pool_free(n);
                                return -1;
                        }
@@ -824,7 +908,7 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
                fr_hash_table_delete(attributes_byvalue, a);
 
                if (!fr_hash_table_replace(attributes_byname, n)) {
-                       fr_strerror_printf("dict_adnttr: Internal error storing attribute %s", name);
+                       fr_strerror_printf("dict_addattr: Internal error storing attribute %s", name);
                        fr_pool_free(n);
                        return -1;
                }
@@ -840,41 +924,34 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
         *      by value) we want to use the NEW name.
         */
        if (!fr_hash_table_replace(attributes_byvalue, n)) {
-               fr_strerror_printf("dict_adnttr: Failed inserting attribute name %s", name);
+               fr_strerror_printf("dict_addattr: Failed inserting attribute name %s", name);
                return -1;
        }
 
        /*
         *      Hacks for combo-IP
         */
-       if (n->type == PW_TYPE_COMBO_IP) {
+       if (n->type == PW_TYPE_IP_ADDR) {
                DICT_ATTR *v4, *v6;
 
-               v4 = malloc(sizeof(*v4));
+               v4 = fr_pool_alloc(sizeof(*v4));
                if (!v4) goto oom;
 
-               v6 = malloc(sizeof(*v6));
-               if (!v6) {
-                       free(v4);
-                       goto oom;
-               }
+               v6 = fr_pool_alloc(sizeof(*v6));
+               if (!v6) goto oom;
 
                memcpy(v4, n, sizeof(*v4));
-               v4->type = PW_TYPE_IPADDR;
+               v4->type = PW_TYPE_IPV4_ADDR;
 
                memcpy(v6, n, sizeof(*v6));
-               v6->type = PW_TYPE_IPV6ADDR;
-
-               if (fr_hash_table_insert(attributes_combo, v4)) {
-                       fr_strerror_printf("dict_adnttr: Failed inserting attribute name %s", name);
-                       free(v4);
-                       free(v6);
+               v6->type = PW_TYPE_IPV6_ADDR;
+               if (!fr_hash_table_replace(attributes_combo, v4)) {
+                       fr_strerror_printf("dict_addattr: Failed inserting attribute name %s - IPv4", name);
                        return -1;
                }
 
-               if (fr_hash_table_insert(attributes_combo, v6)) {
-                       fr_strerror_printf("dict_adnttr: Failed inserting attribute name %s", name);
-                       free(v6);
+               if (!fr_hash_table_replace(attributes_combo, v6)) {
+                       fr_strerror_printf("dict_addattr: Failed inserting attribute name %s - IPv6", name);
                        return -1;
                }
        }
@@ -890,13 +967,13 @@ int dict_addattr(const char *name, int attr, unsigned int vendor, int type,
 /*
  *     Add a value for an attribute to the dictionary.
  */
-int dict_addvalue(const char *namestr, const char *attrstr, int value)
+int dict_addvalue(char const *namestr, char const *attrstr, int value)
 {
        size_t          length;
-       const DICT_ATTR *dattr;
+       DICT_ATTR const *da;
        DICT_VALUE      *dval;
 
-       static const DICT_ATTR *last_attr = NULL;
+       static DICT_ATTR const *last_attr = NULL;
 
        if (!*namestr) {
                fr_strerror_printf("dict_addvalue: empty names are not permitted");
@@ -923,31 +1000,31 @@ int dict_addvalue(const char *namestr, const char *attrstr, int value)
         *      caching the last attribute.
         */
        if (last_attr && (strcasecmp(attrstr, last_attr->name) == 0)) {
-               dattr = last_attr;
+               da = last_attr;
        } else {
-               dattr = dict_attrbyname(attrstr);
-               last_attr = dattr;
+               da = dict_attrbyname(attrstr);
+               last_attr = da;
        }
 
        /*
         *      Remember which attribute is associated with this
         *      value, if possible.
         */
-       if (dattr) {
-               if (dattr->flags.has_value_alias) {
+       if (da) {
+               if (da->flags.has_value_alias) {
                        fr_strerror_printf("dict_addvalue: Cannot add VALUE for ATTRIBUTE \"%s\": It already has a VALUE-ALIAS", attrstr);
                        return -1;
                }
 
-               dval->attr = dattr->attr;
-               dval->vendor = dattr->vendor;
+               dval->attr = da->attr;
+               dval->vendor = da->vendor;
 
                /*
                 *      Enforce valid values
                 *
                 *      Don't worry about fixups...
                 */
-               switch (dattr->type) {
+               switch (da->type) {
                        case PW_TYPE_BYTE:
                                if (value > 255) {
                                        fr_pool_free(dval);
@@ -976,7 +1053,7 @@ int dict_addvalue(const char *namestr, const char *attrstr, int value)
                        default:
                                fr_pool_free(dval);
                                fr_strerror_printf("dict_addvalue: VALUEs cannot be defined for attributes of type '%s'",
-                                          fr_int2str(dict_attr_types, dattr->type, "?Unknown?"));
+                                          fr_int2str(dict_attr_types, da->type, "?Unknown?"));
                                return -1;
                }
        } else {
@@ -1008,9 +1085,9 @@ int dict_addvalue(const char *namestr, const char *attrstr, int value)
        {
                DICT_ATTR *tmp;
                memcpy(&tmp, &dval, sizeof(tmp));
-               
+
                if (!fr_hash_table_insert(values_byname, tmp)) {
-                       if (dattr) {
+                       if (da) {
                                DICT_VALUE *old;
 
                                /*
@@ -1018,7 +1095,7 @@ int dict_addvalue(const char *namestr, const char *attrstr, int value)
                                 *      name and value.  There are lots in
                                 *      dictionary.ascend.
                                 */
-                               old = dict_valbyname(dattr->attr, dattr->vendor, namestr);
+                               old = dict_valbyname(da->attr, da->vendor, namestr);
                                if (old && (old->value == dval->value)) {
                                        fr_pool_free(dval);
                                        return 0;
@@ -1044,11 +1121,11 @@ int dict_addvalue(const char *namestr, const char *attrstr, int value)
        return 0;
 }
 
-static int sscanf_i(const char *str, unsigned int *pvalue)
+static int sscanf_i(char const *str, unsigned int *pvalue)
 {
        int rcode = 0;
        int base = 10;
-       static const char *tab = "0123456789";
+       static char const *tab = "0123456789";
 
        if ((str[0] == '0') &&
            ((str[1] == 'x') || (str[1] == 'X'))) {
@@ -1059,7 +1136,7 @@ static int sscanf_i(const char *str, unsigned int *pvalue)
        }
 
        while (*str) {
-               const char *c;
+               char const *c;
 
                if (*str == '.') break;
 
@@ -1090,12 +1167,12 @@ static int sscanf_i(const char *str, unsigned int *pvalue)
  *
  *     <whew>!  Are we crazy, or what?
  */
-int dict_str2oid(const char *ptr, unsigned int *pvalue, unsigned int *pvendor,
+int dict_str2oid(char const *ptr, unsigned int *pvalue, unsigned int *pvendor,
                 int tlv_depth)
 {
-       const char *p;
+       char const *p;
        unsigned int value;
-       const DICT_ATTR *da;
+       DICT_ATTR const *da = NULL;
 
        if (tlv_depth > fr_attr_max_tlv) {
                fr_strerror_printf("Too many sub-attributes");
@@ -1109,10 +1186,10 @@ int dict_str2oid(const char *ptr, unsigned int *pvalue, unsigned int *pvendor,
        if (*pvalue) {
                da = dict_attrbyvalue(*pvalue, *pvendor);
                if (!da) {
-                       fr_strerror_printf("Parent attribute is undefined.");
+                       fr_strerror_printf("Parent attribute is undefined");
                        return -1;
                }
-               
+
                if (!da->flags.has_tlv && !da->flags.extended) {
                        fr_strerror_printf("Parent attribute %s cannot have sub-attributes",
                                           da->name);
@@ -1135,7 +1212,7 @@ int dict_str2oid(const char *ptr, unsigned int *pvalue, unsigned int *pvendor,
         *      If we find it, re-write the parameters, and recurse.
         */
        if (!*pvendor && (tlv_depth == 0) && (*pvalue == PW_VENDOR_SPECIFIC)) {
-               const DICT_VENDOR *dv;
+               DICT_VENDOR const *dv;
 
                if (!p) {
                        fr_strerror_printf("VSA needs to have sub-attribute");
@@ -1149,7 +1226,7 @@ int dict_str2oid(const char *ptr, unsigned int *pvalue, unsigned int *pvendor,
 
                if (*pvendor >= FR_MAX_VENDOR) {
                        fr_strerror_printf("Cannot handle vendor ID larger than 2^24");
-                       
+
                        return -1;
                }
 
@@ -1174,7 +1251,7 @@ int dict_str2oid(const char *ptr, unsigned int *pvalue, unsigned int *pvendor,
                return -1;
        }
 
-       if (!*pvendor && (tlv_depth == 1) &&
+       if (!*pvendor && (tlv_depth == 1) && da &&
            (da->flags.has_tlv || da->flags.extended)) {
 
 
@@ -1205,7 +1282,7 @@ int dict_str2oid(const char *ptr, unsigned int *pvalue, unsigned int *pvendor,
 /*
  *     Bamboo skewers under the fingernails in 5, 4, 3, 2, ...
  */
-static const DICT_ATTR *dict_parent(unsigned int attr, unsigned int vendor)
+static DICT_ATTR const *dict_parent(unsigned int attr, unsigned int vendor)
 {
        if (vendor < FR_MAX_VENDOR) {
                return dict_attrbyvalue(attr & 0xff, vendor);
@@ -1222,9 +1299,9 @@ static const DICT_ATTR *dict_parent(unsigned int attr, unsigned int vendor)
 /*
  *     Process the ATTRIBUTE command
  */
-static int process_attribute(const char* fn, const int line,
+static int process_attribute(char const* fn, int const line,
                             unsigned int block_vendor,
-                            const DICT_ATTR *block_tlv, int tlv_depth,
+                            DICT_ATTR const *block_tlv, int tlv_depth,
                             char **argv, int argc)
 {
        int             oid = 0;
@@ -1267,7 +1344,7 @@ static int process_attribute(const char* fn, const int line,
        }
 
        if (oid) {
-               const DICT_ATTR *da;
+               DICT_ATTR const *da;
 
                vendor = block_vendor;
 
@@ -1278,7 +1355,7 @@ static int process_attribute(const char* fn, const int line,
                        char buffer[256];
 
                        strlcpy(buffer, fr_strerror(), sizeof(buffer));
-                       
+
                        fr_strerror_printf("dict_init: %s[%d]: Invalid attribute identifier: %s", fn, line, buffer);
                        return -1;
                }
@@ -1312,7 +1389,7 @@ static int process_attribute(const char* fn, const int line,
 
        } else {
                type = PW_TYPE_OCTETS;
-               
+
                p = strchr(argv[2] + 7, ']');
                if (!p) {
                        fr_strerror_printf("dict_init: %s[%d]: Invalid format for octets", fn, line);
@@ -1350,7 +1427,7 @@ static int process_attribute(const char* fn, const int line,
                        break;
 
                case PW_TYPE_DATE:
-               case PW_TYPE_IPADDR:
+               case PW_TYPE_IPV4_ADDR:
                case PW_TYPE_INTEGER:
                case PW_TYPE_SIGNED:
                        length = 4;
@@ -1368,7 +1445,7 @@ static int process_attribute(const char* fn, const int line,
                        length = 8;
                        break;
 
-               case PW_TYPE_IPV6ADDR:
+               case PW_TYPE_IPV6_ADDR:
                        length = 16;
                        break;
 
@@ -1377,7 +1454,6 @@ static int process_attribute(const char* fn, const int line,
                                fr_strerror_printf("dict_init: %s[%d]: Attributes of type \"extended\" MUST be RFC attributes with value >= 241.", fn, line);
                                return -1;
                        }
-                       type = PW_TYPE_OCTETS;
                        flags.extended = 1;
                        break;
 
@@ -1386,13 +1462,11 @@ static int process_attribute(const char* fn, const int line,
                                fr_strerror_printf("dict_init: %s[%d]: Attributes of type \"long-extended\" MUST be RFC attributes with value >= 241.", fn, line);
                                return -1;
                        }
-                       type = PW_TYPE_OCTETS;
                        flags.extended = 1;
                        flags.long_extended = 1;
                        break;
 
                case PW_TYPE_EVS:
-                       type = PW_TYPE_OCTETS;
                        flags.extended = 1;
                        flags.evs = 1;
                        if (value != PW_VENDOR_SPECIFIC) {
@@ -1405,7 +1479,7 @@ static int process_attribute(const char* fn, const int line,
                        break;
                }
 
-               flags.length = length;
+               flags.length = length;
 
        } else {                /* argc == 4: we have options */
                char *key, *next, *last;
@@ -1433,7 +1507,7 @@ static int process_attribute(const char* fn, const int line,
                                /* Boolean flag, means this is a
                                   tagged attribute */
                                flags.has_tag = 1;
-                               
+
                        } else if (strncmp(key, "encrypt=", 8) == 0) {
                                /* Encryption method, defaults to 0 (none).
                                   Currently valid is just type 2,
@@ -1452,20 +1526,33 @@ static int process_attribute(const char* fn, const int line,
                                                            fn, line);
                                        return -1;
                                }
-                               
+
                        } else if (strncmp(key, "array", 6) == 0) {
                                flags.array = 1;
-                               
+
                                switch (type) {
-                                       case PW_TYPE_IPADDR:
+                                       case PW_TYPE_IPV4_ADDR:
+                                       case PW_TYPE_IPV6_ADDR:
                                        case PW_TYPE_BYTE:
                                        case PW_TYPE_SHORT:
                                        case PW_TYPE_INTEGER:
                                        case PW_TYPE_DATE:
+                                       case PW_TYPE_STRING:
                                                break;
 
                                        default:
-                                               fr_strerror_printf( "dict_init: %s[%d] Only IP addresses can have the \"array\" flag set.",
+                                               fr_strerror_printf( "dict_init: %s[%d] \"%s\" type cannot have the "
+                                                                  "\"array\" flag set",
+                                                                  fn, line,
+                                                                  fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
+                                               return -1;
+                               }
+
+                       } else if (strncmp(key, "concat", 6) == 0) {
+                               flags.concat = 1;
+
+                               if (type != PW_TYPE_OCTETS) {
+                                               fr_strerror_printf( "dict_init: %s[%d] Only \"octets\" type can have the \"concat\" flag set.",
                                                            fn, line);
                                                return -1;
                                }
@@ -1544,7 +1631,7 @@ static int process_attribute(const char* fn, const int line,
                }
                flags.has_tlv = 1;
        }
-       
+
        if (block_tlv) {
                /*
                 *      TLV's can be only one octet.
@@ -1556,7 +1643,7 @@ static int process_attribute(const char* fn, const int line,
                }
 
                /*
-                *      
+                *
                 */
                value <<= fr_attr_shift[tlv_depth];
                value |= block_tlv->attr;
@@ -1595,7 +1682,7 @@ static int process_attribute(const char* fn, const int line,
 /*
  *     Process the VALUE command
  */
-static int process_value(const char* fn, const int line, char **argv,
+static int process_value(char const* fn, int const line, char **argv,
                         int argc)
 {
        unsigned int    value;
@@ -1640,10 +1727,10 @@ static int process_value(const char* fn, const int line, char **argv,
  *     This allows VALUE mappings to be shared among multiple
  *     attributes.
  */
-static int process_value_alias(const char* fn, const int line, char **argv,
+static int process_value_alias(char const* fn, int const line, char **argv,
                               int argc)
 {
-       const DICT_ATTR *my_da, *da;
+       DICT_ATTR const *my_da, *da;
        DICT_VALUE *dval;
 
        if (argc != 2) {
@@ -1708,12 +1795,12 @@ static int process_value_alias(const char* fn, const int line, char **argv,
 /*
  *     Process the VENDOR command
  */
-static int process_vendor(const char* fn, const int line, char **argv,
+static int process_vendor(char const* fn, int const line, char **argv,
                          int argc)
 {
-       int     value;
-       int     continuation = 0;
-       const   char *format = NULL;
+       int             value;
+       bool            continuation = false;
+       char const      *format = NULL;
 
        if ((argc < 2) || (argc > 3)) {
                fr_strerror_printf( "dict_init: %s[%d] invalid VENDOR entry",
@@ -1761,7 +1848,7 @@ static int process_vendor(const char* fn, const int line, char **argv,
 
        if (format) {
                int type, length;
-               const char *p;
+               char const *p;
                DICT_VENDOR *dv;
 
                if (strncasecmp(format, "format=", 7) != 0) {
@@ -1797,7 +1884,7 @@ static int process_vendor(const char* fn, const int line, char **argv,
                                           fn, line, p);
                                return -1;
                        }
-                       continuation = 1;
+                       continuation = true;
 
                        if ((value != VENDORPEC_WIMAX) ||
                            (type != 1) || (length != 1)) {
@@ -1873,16 +1960,30 @@ int str2argv(char *str, char **argv, int max_argc)
        return argc;
 }
 
+static int my_dict_init(char const *parent, char const *filename,
+                       char const *src_file, int src_line);
+
+int dict_read(char const *dir, char const *filename)
+{
+       if (!attributes_byname) {
+               fr_strerror_printf("Must call dict_init() before dict_read()");
+               return -1;
+       }
+
+       return my_dict_init(dir, filename, NULL, 0);
+}
+
+
 #define MAX_ARGV (16)
 
 /*
  *     Initialize the dictionary.
  */
-static int my_dict_init(const char *dir, const char *fn,
-                       const char *src_file, int src_line)
+static int my_dict_init(char const *parent, char const *filename,
+                       char const *src_file, int src_line)
 {
        FILE    *fp;
-       char    dirtmp[256];
+       char    dir[256], fn[256];
        char    buf[256];
        char    *p;
        int     line = 0;
@@ -1891,7 +1992,7 @@ static int my_dict_init(const char *dir, const char *fn,
        struct stat statbuf;
        char    *argv[MAX_ARGV];
        int     argc;
-       const DICT_ATTR *da, *block_tlv[MAX_TLV_NEST + 1];
+       DICT_ATTR const *da, *block_tlv[MAX_TLV_NEST + 1];
        int     which_block_tlv = 0;
 
        block_tlv[0] = NULL;
@@ -1899,32 +2000,73 @@ static int my_dict_init(const char *dir, const char *fn,
        block_tlv[2] = NULL;
        block_tlv[3] = NULL;
 
-       if (strlen(fn) >= sizeof(dirtmp) / 2 ||
-           strlen(dir) >= sizeof(dirtmp) / 2) {
+       if ((strlen(parent) + 3 + strlen(filename)) > sizeof(dir)) {
                fr_strerror_printf("dict_init: filename name too long");
                return -1;
        }
 
        /*
-        *      First see if fn is relative to dir. If so, create
-        *      new filename. If not, remember the absolute dir.
+        *      If it's an absolute dir, forget the parent dir,
+        *      and remember the new one.
+        *
+        *      If it's a relative dir, tack on the current filename
+        *      to the parent dir.  And use that.
         */
-       if ((p = strrchr(fn, FR_DIR_SEP)) != NULL) {
-               strcpy(dirtmp, fn);
-               dirtmp[p - fn] = 0;
-               dir = dirtmp;
-       } else if (dir && dir[0] && strcmp(dir, ".") != 0) {
-               snprintf(dirtmp, sizeof(dirtmp), "%s/%s", dir, fn);
-               fn = dirtmp;
+       if (!FR_DIR_IS_RELATIVE(filename)) {
+               strlcpy(dir, filename, sizeof(dir));
+               p = strrchr(dir, FR_DIR_SEP);
+               if (p) {
+                       p[1] = '\0';
+               } else {
+                       strlcat(dir, "/", sizeof(dir));
+               }
+
+               strlcpy(fn, filename, sizeof(fn));
+       } else {
+               strlcpy(dir, parent, sizeof(dir));
+               p = strrchr(dir, FR_DIR_SEP);
+               if (p) {
+                       if (p[1]) strlcat(dir, "/", sizeof(dir));
+               } else {
+                       strlcat(dir, "/", sizeof(dir));
+               }
+               strlcat(dir, filename, sizeof(dir));
+               p = strrchr(dir, FR_DIR_SEP);
+               if (p) {
+                       p[1] = '\0';
+               } else {
+                       strlcat(dir, "/", sizeof(dir));
+               }
+
+               p = strrchr(filename, FR_DIR_SEP);
+               if (p) {
+                       snprintf(fn, sizeof(fn), "%s%s", dir, p);
+               } else {
+                       snprintf(fn, sizeof(fn), "%s%s", dir, filename);
+               }
+
+       }
+
+       /*
+        *      Check if we've loaded this file before.  If so, ignore it.
+        */
+       p = strrchr(fn, FR_DIR_SEP);
+       if (p) {
+               *p = '\0';
+               if (dict_stat_check(fn, p + 1)) {
+                       *p = FR_DIR_SEP;
+                       return 0;
+               }
+               *p = FR_DIR_SEP;
        }
 
        if ((fp = fopen(fn, "r")) == NULL) {
                if (!src_file) {
                        fr_strerror_printf("dict_init: Couldn't open dictionary \"%s\": %s",
-                                  fn, strerror(errno));
+                                  fn, fr_syserror(errno));
                } else {
                        fr_strerror_printf("dict_init: %s[%d]: Couldn't open dictionary \"%s\": %s",
-                                  src_file, src_line, fn, strerror(errno));
+                                  src_file, src_line, fn, fr_syserror(errno));
                }
                return -2;
        }
@@ -1950,7 +2092,7 @@ static int my_dict_init(const char *dir, const char *fn,
        }
 #endif
 
-       dict_stat_add(fn, &statbuf);
+       dict_stat_add(&statbuf);
 
        /*
         *      Seed the random pool with data.
@@ -2156,7 +2298,7 @@ static int my_dict_init(const char *dir, const char *fn,
                                        fclose(fp);
                                        return -1;
                                }
-                               
+
                                p = argv[2] + 7;
                                da = dict_attrbyname(p);
                                if (!da) {
@@ -2172,7 +2314,7 @@ static int my_dict_init(const char *dir, const char *fn,
                                        fclose(fp);
                                        return -1;
                                }
-                               
+
                                /*
                                 *      Pack the encapsulating
                                 *      attribute into the upper 8
@@ -2229,11 +2371,8 @@ static int my_dict_init(const char *dir, const char *fn,
 /*
  *     Empty callback for hash table initialization.
  */
-static int null_callback(void *ctx, void *data)
+static int null_callback(UNUSED void *ctx, UNUSED void *data)
 {
-       ctx = ctx;              /* -Wunused */
-       data = data;            /* -Wunused */
-
        return 0;
 }
 
@@ -2242,7 +2381,7 @@ static int null_callback(void *ctx, void *data)
  *     Initialize the directory, then fix the attr member of
  *     all attributes.
  */
-int dict_init(const char *dir, const char *fn)
+int dict_init(char const *dir, char const *fn)
 {
        /*
         *      Check if we need to change anything.  If not, don't do
@@ -2256,8 +2395,6 @@ int dict_init(const char *dir, const char *fn)
         *      Free the dictionaries, and the stat cache.
         */
        dict_free();
-       stat_root_dir = strdup(dir);
-       stat_root_file = strdup(fn);
 
        /*
         *      Create the table of vendor by name.   There MAY NOT
@@ -2339,7 +2476,7 @@ int dict_init(const char *dir, const char *fn)
                return -1;
 
        if (value_fixup) {
-               const DICT_ATTR *a;
+               DICT_ATTR const *a;
                value_fixup_t *this, *next;
 
                for (this = value_fixup; this != NULL; this = next) {
@@ -2441,25 +2578,27 @@ static size_t print_attr_oid(char *buffer, size_t size, unsigned int attr,
 }
 
 /** Free dynamically allocated (unknown attributes)
- * 
+ *
  * If the da was dynamically allocated it will be freed, else the function
  * will return without doing anything.
  *
  * @param da to free.
  */
-void dict_attr_free(DICT_ATTR * const *da)
+void dict_attr_free(DICT_ATTR const **da)
 {
        DICT_ATTR **tmp;
-       
+
+       if (!da || !*da) return;
+
        /* Don't free real DAs */
        if (!(*da)->flags.is_unknown) {
                return;
        }
-       
+
        memcpy(&tmp, &da, sizeof(*tmp));
        free(*tmp);
-       
-       *tmp = NULL;    
+
+       *tmp = NULL;
 }
 
 /** Copies a dictionary attr
@@ -2470,24 +2609,29 @@ void dict_attr_free(DICT_ATTR * const *da)
  * If the attr is known, a pointer to the da will be returned.
  *
  * @param da to copy.
+ * @param vp_free if true, da will be freed at the same time as the
+ *     VALUE_PAIR which contains it.
  * @return return a copy of the da.
  */
-const DICT_ATTR *dict_attr_copy(const DICT_ATTR *da)
+DICT_ATTR const *dict_attr_copy(DICT_ATTR const *da, int vp_free)
 {
        DICT_ATTR *copy;
-       
+
+       if (!da) return NULL;
+
        if (!da->flags.is_unknown) {
                return da;
        }
-       
+
        copy = malloc(DICT_ATTR_SIZE);
        if (!copy) {
                fr_strerror_printf("Out of memory");
                return NULL;
        }
-       
+
        memcpy(copy, da, DICT_ATTR_SIZE);
-       
+       copy->flags.vp_free = (vp_free != 0);
+
        return copy;
 }
 
@@ -2501,30 +2645,41 @@ const DICT_ATTR *dict_attr_copy(const DICT_ATTR *da)
  *
  * @param[in] attr number.
  * @param[in] vendor number.
+ * @param[in] vp_free if > 0 DICT_ATTR will be freed on VALUE_PAIR free.
  * @return new dictionary attribute.
  */
-const DICT_ATTR *dict_attrunknown(unsigned int attr, unsigned int vendor)
+DICT_ATTR const *dict_attrunknown(unsigned int attr, unsigned int vendor,
+                                 int vp_free)
 {
        DICT_ATTR *da;
        char *p;
        int dv_type = 1;
        size_t len = 0;
        size_t bufsize = DICT_ATTR_MAX_NAME_LEN;
-       
+
        da = malloc(DICT_ATTR_SIZE);
        if (!da) {
                fr_strerror_printf("Out of memory");
                return NULL;
        }
        memset(da, 0, DICT_ATTR_SIZE);
-       
+
        da->attr = attr;
        da->vendor = vendor;
        da->type = PW_TYPE_OCTETS;
-       da->flags.is_unknown = TRUE;
-       
+       da->flags.is_unknown = true;
+       da->flags.vp_free = (vp_free != 0);
+
+       /*
+        *      Unknown attributes of the "WiMAX" vendor get marked up
+        *      as being for WiMAX.
+        */
+       if (vendor == VENDORPEC_WIMAX) {
+               da->flags.wimax = 1;
+       }
+
        p = da->name;
-       
+
        len = snprintf(p, bufsize, "Attr-");
        p += len;
        bufsize -= len;
@@ -2549,13 +2704,13 @@ const DICT_ATTR *dict_attrunknown(unsigned int attr, unsigned int vendor)
                        dv_type = dv->type;
                }
                len = snprintf(p, bufsize, "26.%u.", vendor);
-               
+
                p += len;
                bufsize -= len;
        }
 
-       p += print_attr_oid(p, bufsize , attr, dv_type);
-       
+       print_attr_oid(p, bufsize , attr, dv_type);
+
        return da;
 }
 
@@ -2569,19 +2724,20 @@ const DICT_ATTR *dict_attrunknown(unsigned int attr, unsigned int vendor)
  *
  * @todo should check attr/vendor against dictionary and return the real da.
  *
- * @param attribute name to parse.
+ * @param[in] attribute name.
+ * @param[in] vp_free if > 0 DICT_ATTR will be freed on VALUE_PAIR free.
  * @return new da or NULL on error.
  */
-const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
+DICT_ATTR const *dict_attrunknownbyname(char const *attribute, int vp_free)
 {
        unsigned int    attr, vendor = 0;
        unsigned int    dv_type = 1;    /* The type of vendor field */
 
-       const char      *p = attribute;
+       char const      *p = attribute;
        char            *q;
-       
+
        DICT_VENDOR     *dv;
-       const DICT_ATTR *da;
+       DICT_ATTR const *da;
 
        /*
         *      Pull off vendor prefix first.
@@ -2591,7 +2747,7 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                        vendor = (int) strtol(p + 7, &q, 10);
                        if ((vendor == 0) || (vendor > FR_MAX_VENDOR)) {
                                fr_strerror_printf("Invalid vendor value in "
-                                                  "attribute name \"%s\"", 
+                                                  "attribute name \"%s\"",
                                                   attribute);
                                return NULL;
                        }
@@ -2623,8 +2779,7 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
 
                        vendor = dict_vendorbyname(buffer);
                        if (!vendor) {
-                               fr_strerror_printf("Unknown vendor name in "
-                                                  "attribute name \"%s\"",
+                               fr_strerror_printf("Unknown attribute \"%s\"",
                                                   attribute);
                                return NULL;
                        }
@@ -2640,12 +2795,12 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                }
                p++;
        }
-       
+
        /*
         *      Attr-%d
         */
        if (strncasecmp(p, "Attr-", 5) != 0) {
-               fr_strerror_printf("Invalid format in attribute name \"%s\"",
+               fr_strerror_printf("Unknown attribute \"%s\"",
                                   attribute);
                return NULL;
        }
@@ -2662,7 +2817,7 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
        }
 
        p = q;
-       
+
        /*
         *      Vendor-%d-Attr-%d
         *      VendorName-Attr-%d
@@ -2677,7 +2832,7 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                fr_strerror_printf("Invalid OID");
                return NULL;
        }
-       
+
        /*
         *      Look for OIDs.  Require the "Attr-26.Vendor-Id.type"
         *      format, and disallow "Vendor-%d-Attr-%d" and
@@ -2693,8 +2848,8 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                        fr_strerror_printf("Cannot parse attributes without "
                                           "dictionaries");
                        return NULL;
-               }               
-               
+               }
+
                if ((attr != PW_VENDOR_SPECIFIC) &&
                    !(da->flags.extended || da->flags.long_extended)) {
                        fr_strerror_printf("Standard attributes cannot use "
@@ -2730,7 +2885,7 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                        if (dv_type > 3) dv_type = 3; /* hack */
                }
        }
-       
+
        /*
         *      Parse the next number.  It could be a Vendor-Type
         *      of 1..2^24, or it could be a TLV.
@@ -2746,7 +2901,7 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                        if (*q != '.') {
                                goto invalid;
                        }
-                       
+
                        if (dv_type != 1) {
                                goto invalid;
                        }
@@ -2766,22 +2921,22 @@ const DICT_ATTR *dict_attrunknownbyname(const char *attribute)
                }
        }
 
-       return dict_attrunknown(attr, vendor);
+       return dict_attrunknown(attr, vendor, vp_free);
 }
 
 /*
  *     Get an attribute by its numerical value.
  */
-const DICT_ATTR *dict_attrbyvalue(unsigned int attr, unsigned int vendor)
+DICT_ATTR const *dict_attrbyvalue(unsigned int attr, unsigned int vendor)
 {
-       DICT_ATTR dattr;
+       DICT_ATTR da;
 
        if ((attr > 0) && (attr < 256) && !vendor) return dict_base_attrs[attr];
 
-       dattr.attr = attr;
-       dattr.vendor = vendor;
+       da.attr = attr;
+       da.vendor = vendor;
 
-       return fr_hash_table_finddata(attributes_byvalue, &dattr);
+       return fr_hash_table_finddata(attributes_byvalue, &da);
 }
 
 
@@ -2792,38 +2947,53 @@ const DICT_ATTR *dict_attrbyvalue(unsigned int attr, unsigned int vendor)
  *
  * @return The attribute, or NULL if not found
  */
-const DICT_ATTR *dict_attrbytype(unsigned int attr, unsigned int vendor,
-                          PW_TYPE type)
+DICT_ATTR const *dict_attrbytype(unsigned int attr, unsigned int vendor,
+                                PW_TYPE type)
 {
-       DICT_ATTR dattr;
+       DICT_ATTR da;
 
-       dattr.attr = attr;
-       dattr.vendor = vendor;
-       dattr.type = type;
+       da.attr = attr;
+       da.vendor = vendor;
+       da.type = type;
 
-       return fr_hash_table_finddata(attributes_combo, &dattr);
+       return fr_hash_table_finddata(attributes_combo, &da);
 }
 
-
-/*
- *     Get an attribute by it's numerical value, and the parent
+/**
+ * @brief Using a parent and attr/vendor, find a child attr/vendor
  */
-const DICT_ATTR *dict_attrbyparent(const DICT_ATTR *parent, unsigned int attr)
+int dict_attr_child(DICT_ATTR const *parent,
+                   unsigned int *pattr, unsigned int *pvendor)
 {
-       DICT_ATTR dattr;
+       unsigned int attr, vendor;
+       DICT_ATTR da;
+
+       if (!parent || !pattr || !pvendor) return false;
 
-       if (!parent) return NULL;
+       attr = *pattr;
+       vendor = *pvendor;
 
        /*
-        *      Only TLVs can have children
+        *      Only some types can have children
         */
-       if (!parent->flags.has_tlv) return NULL;
+       switch (parent->type) {
+       default: return false;
+
+       case PW_TYPE_VSA:
+       case PW_TYPE_TLV:
+       case PW_TYPE_EVS:
+       case PW_TYPE_EXTENDED:
+       case PW_TYPE_LONG_EXTENDED:
+         break;
+       }
+
+       if ((vendor == 0) && (parent->vendor != 0)) return false;
 
        /*
         *      Bootstrap by starting off with the parents values.
         */
-       dattr.attr = parent->attr;
-       dattr.vendor = parent->vendor;
+       da.attr = parent->attr;
+       da.vendor = parent->vendor;
 
        /*
         *      Do various butchery to insert the "attr" value.
@@ -2835,9 +3005,10 @@ const DICT_ATTR *dict_attrbyparent(const DICT_ATTR *parent, unsigned int attr)
         *      EEVID   000000AA        EVS with vendor VID, attr AAA
         *      EEVID   DDCCBBAA        EVS with TLVs
         */
-       if (!dattr.vendor) {
-               dattr.vendor = parent->attr * FR_MAX_VENDOR;
-               dattr.attr = attr;
+       if (!da.vendor) {
+               da.vendor = parent->attr * FR_MAX_VENDOR;
+               da.vendor |= vendor;
+               da.attr = attr;
 
        } else {
                int i;
@@ -2846,30 +3017,75 @@ const DICT_ATTR *dict_attrbyparent(const DICT_ATTR *parent, unsigned int attr)
                 *      Trying to nest too deep.  It's an error
                 */
                if (parent->attr & (fr_attr_mask[MAX_TLV_NEST] << fr_attr_shift[MAX_TLV_NEST])) {
-                       return NULL;
+                       return false;
                }
 
                for (i = MAX_TLV_NEST - 1; i >= 0; i--) {
                        if ((parent->attr & (fr_attr_mask[i] << fr_attr_shift[i]))) {
-                               dattr.attr |= (attr & fr_attr_mask[i + 1]) << fr_attr_shift[i + 1];
+                               da.attr |= (attr & fr_attr_mask[i + 1]) << fr_attr_shift[i + 1];
                                goto find;
                        }
                }
 
-               return NULL;
+               return false;
        }
 
 find:
-       return fr_hash_table_finddata(attributes_byvalue, &dattr);
+#if 0
+       fprintf(stderr, "LOOKING FOR %08x %08x + %08x %08x --> %08x %08x\n",
+               parent->vendor, parent->attr, attr, vendor,
+               da.vendor, da.attr);
+#endif
+
+       *pattr = da.attr;
+       *pvendor = da.vendor;
+       return true;
+}
+
+/*
+ *     Get an attribute by it's numerical value, and the parent
+ */
+DICT_ATTR const *dict_attrbyparent(DICT_ATTR const *parent, unsigned int attr, unsigned int vendor)
+{
+       unsigned int my_attr, my_vendor;
+       DICT_ATTR da;
+
+       my_attr = attr;
+       my_vendor = vendor;
+
+       if (!dict_attr_child(parent, &my_attr, &my_vendor)) return NULL;
+
+       da.attr = my_attr;
+       da.vendor = my_vendor;
+
+       return fr_hash_table_finddata(attributes_byvalue, &da);
 }
 
 
 /*
  *     Get an attribute by its name.
  */
-const DICT_ATTR *dict_attrbyname(const char *name)
+DICT_ATTR const *dict_attrbyname(char const *name)
+{
+       DICT_ATTR *da;
+       uint32_t buffer[(sizeof(*da) + DICT_ATTR_MAX_NAME_LEN + 3)/4];
+
+       if (!name) return NULL;
+
+       da = (DICT_ATTR *) buffer;
+       strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1);
+
+       return fr_hash_table_finddata(attributes_byname, da);
+}
+
+/*
+ *     Get an attribute by its name, where the name might have a tag
+ *     or something else after it.
+ */
+DICT_ATTR const *dict_attrbytagged_name(char const *name)
 {
        DICT_ATTR *da;
+       char *p;
        uint32_t buffer[(sizeof(*da) + DICT_ATTR_MAX_NAME_LEN + 3)/4];
 
        if (!name) return NULL;
@@ -2877,6 +3093,23 @@ const DICT_ATTR *dict_attrbyname(const char *name)
        da = (DICT_ATTR *) buffer;
        strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1);
 
+       /*
+        *      The name might have a tag or array reference.  That
+        *      isn't properly part of the name, and can be ignored on
+        *      lookup.
+        */
+       for (p = &da->name[0]; *p; p++) {
+               if (*p == ':') {
+                       *p = '\0';
+                       break;
+               }
+
+               if (*p == '[') {
+                       *p = '\0';
+                       break;
+               }
+       }
+
        return fr_hash_table_finddata(attributes_byname, da);
 }
 
@@ -2909,7 +3142,7 @@ DICT_VALUE *dict_valbyattr(unsigned int attr, unsigned int vendor, int value)
 /*
  *     Associate a value with an attribute and return it.
  */
-const char *dict_valnamebyattr(unsigned int attr, unsigned int vendor, int value)
+char const *dict_valnamebyattr(unsigned int attr, unsigned int vendor, int value)
 {
        DICT_VALUE *dv;
 
@@ -2922,7 +3155,7 @@ const char *dict_valnamebyattr(unsigned int attr, unsigned int vendor, int value
 /*
  *     Get a value by its name, keyed off of an attribute.
  */
-DICT_VALUE *dict_valbyname(unsigned int attr, unsigned int vendor, const char *name)
+DICT_VALUE *dict_valbyname(unsigned int attr, unsigned int vendor, char const *name)
 {
        DICT_VALUE *my_dv, *dv;
        uint32_t buffer[(sizeof(*my_dv) + DICT_VALUE_MAX_NAME_LEN + 3)/4];
@@ -2951,10 +3184,10 @@ DICT_VALUE *dict_valbyname(unsigned int attr, unsigned int vendor, const char *n
  *
  *     This is efficient only for small numbers of vendors.
  */
-int dict_vendorbyname(const char *name)
+int dict_vendorbyname(char const *name)
 {
        DICT_VENDOR *dv;
-       uint32_t buffer[(sizeof(*dv) + DICT_VENDOR_MAX_NAME_LEN + 3)/4];
+       size_t buffer[(sizeof(*dv) + DICT_VENDOR_MAX_NAME_LEN + sizeof(size_t) - 1) / sizeof(size_t)];
 
        if (!name) return 0;