fix printing of long hex values
[freeradius.git] / src / lib / value.c
index 9697d78..ca18327 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * valuepair.c Functions to handle value_data_t
+ * value.c     Functions to handle value_data_t
  *
  * Version:    $Id$
  *
@@ -56,7 +56,7 @@ int value_data_cmp(PW_TYPE a_type, value_data_t const *a, size_t a_len,
        {
                size_t length;
 
-               if (a_len > b_len) {
+               if (a_len < b_len) {
                        length = a_len;
                } else {
                        length = b_len;
@@ -110,7 +110,7 @@ int value_data_cmp(PW_TYPE a_type, value_data_t const *a, size_t a_len,
                break;
 
        case PW_TYPE_ETHERNET:
-               compare = memcmp(&a->ether, &b->ether, sizeof(a->ether));
+               compare = memcmp(a->ether, b->ether, sizeof(a->ether));
                break;
 
        case PW_TYPE_IPV4_ADDR: {
@@ -131,15 +131,15 @@ int value_data_cmp(PW_TYPE a_type, value_data_t const *a, size_t a_len,
                break;
 
        case PW_TYPE_IPV6_PREFIX:
-               compare = memcmp(&a->ipv6prefix, &b->ipv6prefix, sizeof(a->ipv6prefix));
+               compare = memcmp(a->ipv6prefix, b->ipv6prefix, sizeof(a->ipv6prefix));
                break;
 
        case PW_TYPE_IPV4_PREFIX:
-               compare = memcmp(&a->ipv4prefix, &b->ipv4prefix, sizeof(a->ipv4prefix));
+               compare = memcmp(a->ipv4prefix, b->ipv4prefix, sizeof(a->ipv4prefix));
                break;
 
        case PW_TYPE_IFID:
-               compare = memcmp(&a->ifid, &b->ifid, sizeof(a->ifid));
+               compare = memcmp(a->ifid, b->ifid, sizeof(a->ifid));
                break;
 
        /*
@@ -312,31 +312,29 @@ int value_data_cmp_op(FR_TOKEN op,
 
                case PW_TYPE_IPV4_PREFIX:       /* IPv4 and IPv4 Prefix */
                        return value_data_cidr_cmp_op(op, 4, 32, (uint8_t const *) &a->ipaddr,
-                                                   b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix + 2);
+                                                     b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv4 with IPv6 address");
                        return -1;
                }
-               break;
 
        case PW_TYPE_IPV4_PREFIX:               /* IPv4 and IPv4 Prefix */
                switch (b_type) {
                case PW_TYPE_IPV4_ADDR:
                        return value_data_cidr_cmp_op(op, 4, a->ipv4prefix[1],
-                                                   (uint8_t const *) &a->ipv4prefix + 2,
+                                                   (uint8_t const *) &a->ipv4prefix[2],
                                                    32, (uint8_t const *) &b->ipaddr);
 
                case PW_TYPE_IPV4_PREFIX:       /* IPv4 Prefix and IPv4 Prefix */
                        return value_data_cidr_cmp_op(op, 4, a->ipv4prefix[1],
-                                                   (uint8_t const *) &a->ipv4prefix + 2,
-                                                   b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix + 2);
+                                                   (uint8_t const *) &a->ipv4prefix[2],
+                                                   b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv4 with IPv6 address");
                        return -1;
                }
-               break;
 
        case PW_TYPE_IPV6_ADDR:
                switch (b_type) {
@@ -345,32 +343,29 @@ int value_data_cmp_op(FR_TOKEN op,
 
                case PW_TYPE_IPV6_PREFIX:       /* IPv6 and IPv6 Preifx */
                        return value_data_cidr_cmp_op(op, 16, 128, (uint8_t const *) &a->ipv6addr,
-                                                   b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix + 2);
-                       break;
+                                                     b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv6 with IPv4 address");
                        return -1;
                }
-               break;
 
        case PW_TYPE_IPV6_PREFIX:
                switch (b_type) {
                case PW_TYPE_IPV6_ADDR:         /* IPv6 Prefix and IPv6 */
                        return value_data_cidr_cmp_op(op, 16, a->ipv6prefix[1],
-                                                   (uint8_t const *) &a->ipv6prefix + 2,
-                                                   128, (uint8_t const *) &b->ipv6addr);
+                                                     (uint8_t const *) &a->ipv6prefix[2],
+                                                     128, (uint8_t const *) &b->ipv6addr);
 
                case PW_TYPE_IPV6_PREFIX:       /* IPv6 Prefix and IPv6 */
                        return value_data_cidr_cmp_op(op, 16, a->ipv6prefix[1],
-                                                   (uint8_t const *) &a->ipv6prefix + 2,
-                                                   b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix + 2);
+                                                     (uint8_t const *) &a->ipv6prefix[2],
+                                                     b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv6 with IPv4 address");
                        return -1;
                }
-               break;
 
        default:
        cmp:
@@ -447,19 +442,18 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
        switch (*src_type) {
        case PW_TYPE_STRING:
        {
-               char            *p;
+               char            *p, *buff;
                char const      *q;
                int             x;
 
-               dst->strvalue = p = talloc_memdup(ctx, src, len + 1);
-               p[len] = '\0';
-               talloc_set_type(p, char);
+               buff = p = talloc_bstrndup(ctx, src, len);
 
                /*
                 *      No de-quoting.  Just copy the string.
                 */
                if (!quote) {
                        ret = len;
+                       dst->strvalue = buff;
                        goto finish;
                }
 
@@ -471,14 +465,23 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                if (quote == '\'') {
                        q = p;
 
-                       /*
-                        *      Escape ONLY the quotation character.
-                        *      Everything else is left as-is.
-                        */
-                       while (q < (dst->strvalue + len)) {
+                       while (q < (buff + len)) {
+                               /*
+                                *      The quotation character is escaped.
+                                */
+                               if ((q[0] == '\\') &&
+                                   (q[1] == quote)) {
+                                       *(p++) = quote;
+                                       q += 2;
+                                       continue;
+                               }
+
+                               /*
+                                *      Two backslashes get mangled to one.
+                                */
                                if ((q[0] == '\\') &&
-                                   (q[1] == '\'')) {
-                                       *(p++) = '\'';
+                                   (q[1] == '\\')) {
+                                       *(p++) = '\\';
                                        q += 2;
                                        continue;
                                }
@@ -490,8 +493,10 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                        }
 
                        *p = '\0';
-                       ret = p - dst->strvalue;
-                       dst->ptr = talloc_realloc(ctx, dst->ptr, char, ret + 1);
+                       ret = p - buff;
+
+                       /* Shrink the buffer to the correct size */
+                       dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1);
                        goto finish;
                }
 
@@ -500,11 +505,12 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                 *      escaping.
                 */
                q = p;
-               while (q < (dst->strvalue + len)) {
+               while (q < (buff + len)) {
                        char c = *q++;
 
-                       if ((c == '\\') && (q >= (dst->strvalue + len))) {
+                       if ((c == '\\') && (q >= (buff + len))) {
                                fr_strerror_printf("Invalid escape at end of string");
+                               talloc_free(buff);
                                return -1;
                        }
 
@@ -570,18 +576,19 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                }
 
                *p = '\0';
-               ret = p - dst->strvalue;
-               dst->ptr = talloc_realloc(ctx, dst->ptr, char, ret + 1);
+               ret = p - buff;
+               dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1);
        }
                goto finish;
 
-       /* raw octets: 0x01020304... */
        case PW_TYPE_VSA:
-               if (strcmp(src, "ANY") == 0) {
-                       ret = 0;
-                       goto finish;
-               } /* else it's hex */
+               fr_strerror_printf("Must use 'Attr-26 = ...' instead of 'Vendor-Specific = ...'");
+               return -1;
 
+       /* raw octets: 0x01020304... */
+#ifndef WITH_ASCEND_BINARY
+       do_octets:
+#endif
        case PW_TYPE_OCTETS:
        {
                uint8_t *p;
@@ -596,14 +603,13 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                        goto finish;
                }
 
-       do_octets:
                len -= 2;
 
                /*
                 *      Invalid.
                 */
                if ((len & 0x01) != 0) {
-                       fr_strerror_printf("Length of Hex String is not even, got %zu bytes", ret);
+                       fr_strerror_printf("Length of Hex String is not even, got %zu bytes", len);
                        return -1;
                }
 
@@ -621,12 +627,25 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
 
        case PW_TYPE_ABINARY:
 #ifdef WITH_ASCEND_BINARY
-               if ((len > 1) && (strncasecmp(src, "0x", 2) == 0)) goto do_octets;
+               if ((len > 1) && (strncasecmp(src, "0x", 2) == 0)) {
+                       ssize_t bin;
 
-               if (ascend_parse_filter(dst, src, len) < 0 ) {
-                       /* Allow ascend_parse_filter's strerror to bubble up */
-                       return -1;
+                       if (len > ((sizeof(dst->filter) + 1) * 2)) {
+                               fr_strerror_printf("Hex data is too large for ascend filter");
+                               return -1;
+                       }
+
+                       bin = fr_hex2bin((uint8_t *) &dst->filter, ret, src + 2, len - 2);
+                       if (bin < ret) {
+                               memset(((uint8_t *) &dst->filter) + bin, 0, ret - bin);
+                       }
+               } else {
+                       if (ascend_parse_filter(dst, src, len) < 0 ) {
+                               /* Allow ascend_parse_filter's strerror to bubble up */
+                               return -1;
+                       }
                }
+
                ret = sizeof(dst->filter);
                goto finish;
 #else
@@ -640,29 +659,8 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
 
        /* don't use this! */
        case PW_TYPE_TLV:
-       {
-               uint8_t *p;
-
-               if ((len < 2) || (len & 0x01) || (strncasecmp(src, "0x", 2) != 0)) {
-                       fr_strerror_printf("Invalid TLV specification");
-                       return -1;
-               }
-               len -= 2;
-
-               ret = len >> 1;
-               p = talloc_array(ctx, uint8_t, ret);
-               if (!p) {
-                       fr_strerror_printf("No memory");
-                       return -1;
-               }
-               if (fr_hex2bin(p, ret, src + 2, len) != (size_t)ret) {
-                       fr_strerror_printf("Invalid hex data in TLV");
-                       return -1;
-               }
-
-               dst->tlv = p;
-       }
-               goto finish;
+               fr_strerror_printf("Cannot parse TLV");
+               return -1;
 
        case PW_TYPE_IPV4_ADDR:
        {
@@ -691,7 +689,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                if (fr_pton4(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
 
                dst->ipv4prefix[1] = addr.prefix;
-               memcpy(dst->ipv4prefix + 2, &addr.ipaddr.ip4addr.s_addr, sizeof(dst->ipv4prefix) - 2);
+               memcpy(&dst->ipv4prefix[2], &addr.ipaddr.ip4addr.s_addr, sizeof(dst->ipv4prefix) - 2);
        }
                goto finish;
 
@@ -711,7 +709,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                        return -1;
                }
 
-               memcpy(&dst->ipv6addr, &addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6addr));
+               memcpy(&dst->ipv6addr, addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6addr));
        }
                goto finish;
 
@@ -722,7 +720,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                if (fr_pton6(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
 
                dst->ipv6prefix[1] = addr.prefix;
-               memcpy(dst->ipv6prefix + 2, &addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6prefix) - 2);
+               memcpy(&dst->ipv6prefix[2], addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6prefix) - 2);
        }
                goto finish;
 
@@ -762,7 +760,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                 */
                if (src_enumv && *p && !is_whitespace(p)) {
                        if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) {
-                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute '%s'",
+                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s",
                                                   src, src_enumv->name);
                                return -1;
                        }
@@ -795,7 +793,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                 */
                if (src_enumv && *p && !is_whitespace(p)) {
                        if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) {
-                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute '%s'",
+                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s",
                                                   src, src_enumv->name);
                                return -1;
                        }
@@ -828,7 +826,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                 */
                if (src_enumv && *p && !is_whitespace(p)) {
                        if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) {
-                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute \"%s\"",
+                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s",
                                                   src, src_enumv->name);
                                return -1;
                        }
@@ -877,7 +875,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                break;
 
        case PW_TYPE_IFID:
-               if (ifid_aton(src, (void *) &dst->ifid) == NULL) {
+               if (ifid_aton(src, (void *) dst->ifid) == NULL) {
                        fr_strerror_printf("Failed to parse interface-id string \"%s\"", src);
                        return -1;
                }
@@ -897,7 +895,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                if (is_integer(src)) {
                        uint64_t integer = htonll(atoll(src));
 
-                       memcpy(&dst->ether, &integer, sizeof(dst->ether));
+                       memcpy(dst->ether, &integer, sizeof(dst->ether));
                        break;
                }
 
@@ -959,9 +957,9 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
                dst->sinteger = (int32_t)strtol(src, NULL, 10);
                break;
 
-               /*
-                *  Anything else.
-                */
+       /*
+        *  Anything else.
+        */
        default:
                fr_strerror_printf("Unknown attribute type %d", *src_type);
                return -1;
@@ -997,6 +995,7 @@ static void value_data_hton(value_data_t *dst, PW_TYPE type, void const *src, si
        case PW_TYPE_OCTETS:
        case PW_TYPE_STRING:
                fr_assert(0);
+               return;         /* shouldn't happen */
 
        default:
                memcpy(dst, src, src_len);
@@ -1015,7 +1014,7 @@ static void value_data_hton(value_data_t *dst, PW_TYPE type, void const *src, si
  * @param src_enumv Enumerated values used to convert integers to strings.
  * @param src Input data.
  * @param src_len Input data len.
- * @return the length of data in the dst.
+ * @return the length of data in the dst or -1 on error.
  */
 ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
                        PW_TYPE dst_type, DICT_ATTR const *dst_enumv,
@@ -1035,12 +1034,8 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
         *      Converts the src data to octets with no processing.
         */
        if (dst_type == PW_TYPE_OCTETS) {
-               if (src_type == PW_TYPE_STRING) {
-                       dst->octets = talloc_memdup(ctx, src->strvalue, src_len);
-               } else {
-                       value_data_hton(dst, src_type, src, src_len);
-                       dst->octets = talloc_memdup(ctx, dst, src_len);
-               }
+               value_data_hton(dst, src_type, src, src_len);
+               dst->octets = talloc_memdup(ctx, dst, src_len);
                talloc_set_type(dst->octets, uint8_t);
                return talloc_array_length(dst->strvalue);
        }
@@ -1049,13 +1044,13 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
         *      Serialise a value_data_t
         */
        if (dst_type == PW_TYPE_STRING) {
-               dst->strvalue = vp_data_aprints_value(ctx, src_type, src_enumv, src, src_len, '\0');
+               dst->strvalue = value_data_aprints(ctx, src_type, src_enumv, src, src_len, '\0');
                return talloc_array_length(dst->strvalue) - 1;
        }
 
        if ((src_type == PW_TYPE_IFID) &&
            (dst_type == PW_TYPE_INTEGER64)) {
-               memcpy(&dst->integer64, &src->ifid, sizeof(src->ifid));
+               memcpy(&dst->integer64, src->ifid, sizeof(src->ifid));
                dst->integer64 = htonll(dst->integer64);
        fixed_length:
                return dict_attr_sizes[dst_type][0];
@@ -1074,7 +1069,7 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
                 */
                if ((array[0] != 0) || (array[1] != 0)) return -1;
 
-               memcpy(&dst->ether, &array[2], 6);
+               memcpy(dst->ether, &array[2], 6);
                goto fixed_length;
        }
 
@@ -1096,6 +1091,10 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
                        dst->integer64 = src->integer;
                        break;
 
+               case PW_TYPE_DATE:
+                       dst->integer64 = src->date;
+                       break;
+
                case PW_TYPE_OCTETS:
                        goto do_octets;
 
@@ -1149,6 +1148,221 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
        }
 
        /*
+        *      We can cast integers less that < INT_MAX to signed
+        */
+       if (dst_type == PW_TYPE_SIGNED) {
+               switch (src_type) {
+               case PW_TYPE_BYTE:
+                       dst->sinteger = src->byte;
+                       break;
+
+               case PW_TYPE_SHORT:
+                       dst->sinteger = src->ushort;
+                       break;
+
+               case PW_TYPE_INTEGER:
+                       if (src->integer > INT_MAX) {
+                               fr_strerror_printf("Invalid cast: From integer to signed.  integer value %u is larger "
+                                                  "than max signed int and would overflow", src->integer);
+                               return -1;
+                       }
+                       dst->sinteger = (int)src->integer;
+                       break;
+
+               case PW_TYPE_INTEGER64:
+                       if (src->integer > INT_MAX) {
+                               fr_strerror_printf("Invalid cast: From integer64 to signed.  integer64 value %" PRIu64
+                                                  " is larger than max signed int and would overflow", src->integer64);
+                               return -1;
+                       }
+                       dst->sinteger = (int)src->integer64;
+                       break;
+
+               case PW_TYPE_OCTETS:
+                       goto do_octets;
+
+               default:
+                       goto invalid_cast;
+               }
+               goto fixed_length;
+       }
+       /*
+        *      Conversions between IPv4 addresses, IPv6 addresses, IPv4 prefixes and IPv6 prefixes
+        *
+        *      For prefix to ipaddress conversions, we assume that the host portion has already
+        *      been zeroed out.
+        *
+        *      We allow casts from v6 to v4 if the v6 address has the correct mapping prefix.
+        *
+        *      We only allow casts from prefixes to addresses if the prefix is the the length of
+        *      the address, e.g. 32 for ipv4 128 for ipv6.
+        */
+       {
+               /*
+                *      10 bytes of 0x00 2 bytes of 0xff
+                */
+               static uint8_t const v4_v6_map[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                                    0x00, 0x00, 0x00, 0x00, 0xff, 0xff };
+
+               switch (dst_type) {
+               case PW_TYPE_IPV4_ADDR:
+                       switch (src_type) {
+                       case PW_TYPE_IPV6_ADDR:
+                               if (memcmp(src->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                               bad_v6_prefix_map:
+                                       fr_strerror_printf("Invalid cast from %s to %s.  No IPv4-IPv6 mapping prefix",
+                                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                                       return -1;
+                               }
+
+                               memcpy(&dst->ipaddr, &src->ipv6addr.s6_addr[sizeof(v4_v6_map)],
+                                      sizeof(dst->ipaddr));
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV4_PREFIX:
+                               if (src->ipv4prefix[1] != 32) {
+                               bad_v4_prefix_len:
+                                       fr_strerror_printf("Invalid cast from %s to %s.  Only /32 prefixes may be "
+                                                          "cast to IP address types",
+                                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                                       return -1;
+                               }
+
+                               memcpy(&dst->ipaddr, &src->ipv4prefix[2], sizeof(dst->ipaddr));
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_PREFIX:
+                               if (src->ipv6prefix[1] != 128) {
+                               bad_v6_prefix_len:
+                                       fr_strerror_printf("Invalid cast from %s to %s.  Only /128 prefixes may be "
+                                                          "cast to IP address types",
+                                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                                       return -1;
+                               }
+                               if (memcmp(&src->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                                       goto bad_v6_prefix_map;
+                               }
+                               memcpy(&dst->ipaddr, &src->ipv6prefix[2 + sizeof(v4_v6_map)],
+                                      sizeof(dst->ipaddr));
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               case PW_TYPE_IPV6_ADDR:
+                       switch (src_type) {
+                       case PW_TYPE_IPV4_ADDR:
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(dst->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6addr.s6_addr[sizeof(v4_v6_map)], &src->ipaddr,
+                                      sizeof(dst->ipv6addr.s6_addr) - sizeof(v4_v6_map));
+
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV4_PREFIX:
+                               if (src->ipv4prefix[1] != 32) goto bad_v4_prefix_len;
+
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(dst->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6addr.s6_addr[sizeof(v4_v6_map)], &src->ipv4prefix[2],
+                                      sizeof(dst->ipv6addr.s6_addr) - sizeof(v4_v6_map));
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_PREFIX:
+                               if (src->ipv4prefix[1] != 128) goto bad_v6_prefix_len;
+
+                               memcpy(dst->ipv6addr.s6_addr, &src->ipv6prefix[2], sizeof(dst->ipv6addr.s6_addr));
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               case PW_TYPE_IPV4_PREFIX:
+                       switch (src_type) {
+                       case PW_TYPE_IPV4_ADDR:
+                               memcpy(&dst->ipv4prefix[2], &src->ipaddr, sizeof(dst->ipv4prefix) - 2);
+                               dst->ipv4prefix[0] = 0;
+                               dst->ipv4prefix[1] = 32;
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_ADDR:
+                               if (memcmp(src->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                                       goto bad_v6_prefix_map;
+                               }
+                               memcpy(&dst->ipv4prefix[2], &src->ipv6addr.s6_addr[sizeof(v4_v6_map)],
+                                      sizeof(dst->ipv4prefix) - 2);
+                               dst->ipv4prefix[0] = 0;
+                               dst->ipv4prefix[1] = 32;
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_PREFIX:
+                               if (memcmp(&src->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                                       goto bad_v6_prefix_map;
+                               }
+
+                               /*
+                                *      Prefix must be >= 96 bits. If it's < 96 bytes and the
+                                *      above check passed, the v6 address wasn't masked
+                                *      correctly when it was packet into a value_data_t.
+                                */
+                               if (!fr_assert(src->ipv6prefix[1] >= (sizeof(v4_v6_map) * 8))) return -1;
+
+                               memcpy(&dst->ipv4prefix[2], &src->ipv6prefix[2 + sizeof(v4_v6_map)],
+                                      sizeof(dst->ipv4prefix) - 2);
+                               dst->ipv4prefix[0] = 0;
+                               dst->ipv4prefix[1] = src->ipv6prefix[1] - (sizeof(v4_v6_map) * 8);
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               case PW_TYPE_IPV6_PREFIX:
+                       switch (src_type) {
+                       case PW_TYPE_IPV4_ADDR:
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(&dst->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6prefix[2 + sizeof(v4_v6_map)], &src->ipaddr,
+                                      (sizeof(dst->ipv6prefix) - 2) - sizeof(v4_v6_map));
+                               dst->ipv6prefix[0] = 0;
+                               dst->ipv6prefix[1] = 128;
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV4_PREFIX:
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(&dst->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6prefix[2 + sizeof(v4_v6_map)], &src->ipv4prefix[2],
+                                      (sizeof(dst->ipv6prefix) - 2) - sizeof(v4_v6_map));
+                               dst->ipv6prefix[0] = 0;
+                               dst->ipv6prefix[1] = (sizeof(v4_v6_map) * 8) + src->ipv4prefix[1];
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_ADDR:
+                               memcpy(&dst->ipv6prefix[2], &src->ipv6addr, sizeof(dst->ipv6prefix) - 2);
+                               dst->ipv6prefix[0] = 0;
+                               dst->ipv6prefix[1] = 128;
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       /*
         *      The attribute we've found has to have a size which is
         *      compatible with the type of the destination cast.
         */
@@ -1193,18 +1407,25 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
        return src_len;
 }
 
-ssize_t value_data_copy(TALLOC_CTX *ctx, value_data_t *dst, PW_TYPE type,
+/** Copy value data verbatim duplicating any buffers
+ *
+ * @param ctx To allocate buffers in.
+ * @param dst Where to copy value_data to.
+ * @param src_type Type of src.
+ * @param src Where to copy value_data from.
+ * @param src_len Where
+ */
+ssize_t value_data_copy(TALLOC_CTX *ctx, value_data_t *dst, PW_TYPE src_type,
                        const value_data_t *src, size_t src_len)
 {
-       switch (type) {
+       switch (src_type) {
        default:
                memcpy(dst, src, sizeof(*src));
                break;
 
        case PW_TYPE_STRING:
-               dst->strvalue = talloc_memdup(ctx, src->strvalue, src_len + 1);
+               dst->strvalue = talloc_bstrndup(ctx, src->strvalue, src_len);
                if (!dst->strvalue) return -1;
-               talloc_set_type(dst->strvalue, char);
                break;
 
        case PW_TYPE_OCTETS:
@@ -1216,3 +1437,445 @@ ssize_t value_data_copy(TALLOC_CTX *ctx, value_data_t *dst, PW_TYPE type,
 
        return src_len;
 }
+
+
+
+/** Print one attribute value to a string
+ *
+ */
+char *value_data_aprints(TALLOC_CTX *ctx,
+                        PW_TYPE type, DICT_ATTR const *enumv, value_data_t const *data,
+                        size_t inlen, char quote)
+{
+       char *p = NULL;
+       unsigned int i;
+
+       switch (type) {
+       case PW_TYPE_STRING:
+       {
+               size_t len, ret;
+
+               if (!quote) {
+                       p = talloc_bstrndup(ctx, data->strvalue, inlen);
+                       if (!p) return NULL;
+                       talloc_set_type(p, char);
+                       return p;
+               }
+
+               /* Gets us the size of the buffer we need to alloc */
+               len = fr_prints_len(data->strvalue, inlen, quote);
+               p = talloc_array(ctx, char, len);
+               if (!p) return NULL;
+
+               ret = fr_prints(p, len, data->strvalue, inlen, quote);
+               if (!fr_assert(ret == (len - 1))) {
+                       talloc_free(p);
+                       return NULL;
+               }
+               break;
+       }
+
+       case PW_TYPE_INTEGER:
+               i = data->integer;
+               goto print_int;
+
+       case PW_TYPE_SHORT:
+               i = data->ushort;
+               goto print_int;
+
+       case PW_TYPE_BYTE:
+               i = data->byte;
+
+       print_int:
+       {
+               DICT_VALUE const *dv;
+
+               if (enumv && (dv = dict_valbyattr(enumv->attr, enumv->vendor, i))) {
+                       p = talloc_typed_strdup(ctx, dv->name);
+               } else {
+                       p = talloc_typed_asprintf(ctx, "%u", i);
+               }
+       }
+               break;
+
+       case PW_TYPE_SIGNED:
+               p = talloc_typed_asprintf(ctx, "%d", data->sinteger);
+               break;
+
+       case PW_TYPE_INTEGER64:
+               p = talloc_typed_asprintf(ctx, "%" PRIu64 , data->integer64);
+               break;
+
+       case PW_TYPE_ETHERNET:
+               p = talloc_typed_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x",
+                                         data->ether[0], data->ether[1],
+                                         data->ether[2], data->ether[3],
+                                         data->ether[4], data->ether[5]);
+               break;
+
+       case PW_TYPE_ABINARY:
+#ifdef WITH_ASCEND_BINARY
+               p = talloc_array(ctx, char, 128);
+               if (!p) return NULL;
+               print_abinary(p, 128, (uint8_t const *) &data->filter, inlen, 0);
+               break;
+#else
+                 /* FALL THROUGH */
+#endif
+
+       case PW_TYPE_OCTETS:
+               p = talloc_array(ctx, char, 2 + 1 + inlen * 2);
+               if (!p) return NULL;
+               p[0] = '0';
+               p[1] = 'x';
+
+               fr_bin2hex(p + 2, data->octets, inlen);
+               p[2 + (inlen * 2)] = '\0';
+               break;
+
+       case PW_TYPE_DATE:
+       {
+               time_t t;
+               struct tm s_tm;
+
+               t = data->date;
+
+               p = talloc_array(ctx, char, 64);
+               strftime(p, 64, "%b %e %Y %H:%M:%S %Z",
+                        localtime_r(&t, &s_tm));
+               break;
+       }
+
+       /*
+        *      We need to use the proper inet_ntop functions for IP
+        *      addresses, else the output might not match output of
+        *      other functions, which makes testing difficult.
+        *
+        *      An example is tunnelled ipv4 in ipv6 addresses.
+        */
+       case PW_TYPE_IPV4_ADDR:
+       case PW_TYPE_IPV4_PREFIX:
+       {
+               char buff[INET_ADDRSTRLEN  + 4]; // + /prefix
+
+               buff[0] = '\0';
+               value_data_prints(buff, sizeof(buff), type, enumv, data, inlen, '\0');
+
+               p = talloc_typed_strdup(ctx, buff);
+       }
+       break;
+
+       case PW_TYPE_IPV6_ADDR:
+       case PW_TYPE_IPV6_PREFIX:
+       {
+               char buff[INET6_ADDRSTRLEN + 4]; // + /prefix
+
+               buff[0] = '\0';
+               value_data_prints(buff, sizeof(buff), type, enumv, data, inlen, '\0');
+
+               p = talloc_typed_strdup(ctx, buff);
+       }
+       break;
+
+       case PW_TYPE_IFID:
+               p = talloc_typed_asprintf(ctx, "%x:%x:%x:%x",
+                                         (data->ifid[0] << 8) | data->ifid[1],
+                                         (data->ifid[2] << 8) | data->ifid[3],
+                                         (data->ifid[4] << 8) | data->ifid[5],
+                                         (data->ifid[6] << 8) | data->ifid[7]);
+               break;
+
+       case PW_TYPE_BOOLEAN:
+               p = talloc_typed_strdup(ctx, data->byte ? "yes" : "no");
+               break;
+
+       /*
+        *      Don't add default here
+        */
+       case PW_TYPE_INVALID:
+       case PW_TYPE_COMBO_IP_ADDR:
+       case PW_TYPE_COMBO_IP_PREFIX:
+       case PW_TYPE_TLV:
+       case PW_TYPE_EXTENDED:
+       case PW_TYPE_LONG_EXTENDED:
+       case PW_TYPE_EVS:
+       case PW_TYPE_VSA:
+       case PW_TYPE_TIMEVAL:
+       case PW_TYPE_MAX:
+               fr_assert(0);
+               return NULL;
+       }
+
+       return p;
+}
+
+
+/** Print the value of an attribute to a string
+ *
+ * @note return value should be checked with is_truncated.
+ * @note Will always \0 terminate unless outlen == 0.
+ *
+ * @param out Where to write the printed version of the attribute value.
+ * @param outlen Length of the output buffer.
+ * @param type of data being printed.
+ * @param enumv Enumerated string values for integer types.
+ * @param data to print.
+ * @param inlen Length of data.
+ * @param quote char to escape in string output.
+ * @return  the number of bytes written to the out buffer, or a number >= outlen if truncation has occurred.
+ */
+size_t value_data_prints(char *out, size_t outlen,
+                        PW_TYPE type, DICT_ATTR const *enumv, value_data_t const *data,
+                        ssize_t inlen, char quote)
+{
+       DICT_VALUE      *v;
+       char            buf[1024];      /* Interim buffer to use with poorly behaved printing functions */
+       char const      *a = NULL;
+       char            *p = out;
+       time_t          t;
+       struct tm       s_tm;
+       unsigned int    i;
+
+       size_t          len = 0, freespace = outlen;
+
+       if (!data) return 0;
+       if (outlen == 0) return inlen;
+
+       *out = '\0';
+
+       p = out;
+
+       switch (type) {
+       case PW_TYPE_STRING:
+
+               /*
+                *      Ensure that WE add the quotation marks around the string.
+                */
+               if (quote) {
+                       if (freespace < 3) return inlen + 2;
+
+                       *p++ = quote;
+                       freespace--;
+
+                       len = fr_prints(p, freespace, data->strvalue, inlen, quote);
+                       /* always terminate the quoted string with another quote */
+                       if (len >= (freespace - 1)) {
+                               /* Use out not p as we're operating on the entire buffer */
+                               out[outlen - 2] = (char) quote;
+                               out[outlen - 1] = '\0';
+                               return len + 2;
+                       }
+                       p += len;
+                       freespace -= len;
+
+                       *p++ = (char) quote;
+                       freespace--;
+                       *p = '\0';
+
+                       return len + 2;
+               }
+
+               return fr_prints(out, outlen, data->strvalue, inlen, quote);
+
+       case PW_TYPE_INTEGER:
+               i = data->integer;
+               goto print_int;
+
+       case PW_TYPE_SHORT:
+               i = data->ushort;
+               goto print_int;
+
+       case PW_TYPE_BYTE:
+               i = data->byte;
+
+print_int:
+               /* Normal, non-tagged attribute */
+               if (enumv && (v = dict_valbyattr(enumv->attr, enumv->vendor, i)) != NULL) {
+                       a = v->name;
+                       len = strlen(a);
+               } else {
+                       /* should never be truncated */
+                       len = snprintf(buf, sizeof(buf), "%u", i);
+                       a = buf;
+               }
+               break;
+
+       case PW_TYPE_INTEGER64:
+               return snprintf(out, outlen, "%" PRIu64, data->integer64);
+
+       case PW_TYPE_DATE:
+               t = data->date;
+               if (quote > 0) {
+                       len = strftime(buf, sizeof(buf) - 1, "%%%b %e %Y %H:%M:%S %Z%%", localtime_r(&t, &s_tm));
+                       buf[0] = (char) quote;
+                       buf[len - 1] = (char) quote;
+                       buf[len] = '\0';
+               } else {
+                       len = strftime(buf, sizeof(buf), "%b %e %Y %H:%M:%S %Z", localtime_r(&t, &s_tm));
+               }
+               a = buf;
+               break;
+
+       case PW_TYPE_SIGNED: /* Damned code for 1 WiMAX attribute */
+               len = snprintf(buf, sizeof(buf), "%d", data->sinteger);
+               a = buf;
+               break;
+
+       case PW_TYPE_IPV4_ADDR:
+               a = inet_ntop(AF_INET, &(data->ipaddr), buf, sizeof(buf));
+               len = strlen(buf);
+               break;
+
+       case PW_TYPE_ABINARY:
+#ifdef WITH_ASCEND_BINARY
+               print_abinary(buf, sizeof(buf), (uint8_t const *) data->filter, inlen, quote);
+               a = buf;
+               len = strlen(buf);
+               break;
+#else
+       /* FALL THROUGH */
+#endif
+       case PW_TYPE_OCTETS:
+       case PW_TYPE_TLV:
+       {
+               size_t binlen;
+               size_t hexlen;
+
+               binlen = inlen;
+               hexlen = (binlen * 2) + 2; /* NOT accounting for trailing NUL */
+
+               /*
+                *      If the buffer is too small, put something into
+                *      it, and return how much we should have written
+                *
+                *      0 + x + H + H + NUL = 5
+                */
+               if (freespace < 5) {
+                       switch (freespace) {
+                       case '4':
+                       case '3':
+                               out[0] = '0';
+                               out[1] = 'x';
+                               out[2] = '\0';
+                               return hexlen;
+
+                       case 2:
+                               *out = '0';
+                               out++;
+                               /* FALL-THROUGH */
+
+                       case 1:
+                               *out = '\0';
+                               break;
+
+                       case 0:
+                               break;
+                       }
+
+                       return hexlen;
+               }
+
+               /*
+                *      The output buffer is at least 5 bytes, we haev
+                *      room for '0xHH' plus a trailing NUL byte.
+                */
+               out[0] = '0';
+               out[1] = 'x';
+
+               /*
+                *      Get maximum number of bytes we can encode
+                *      given freespace, ensuring we account for '0',
+                *      'x', and the trailing NUL in the buffer.
+                *
+                *      Note that we can't have "freespace = 0" after
+                *      this, as 'freespace' has to be at least 5.
+                */
+               freespace -= 3;
+               freespace /= 2;
+               if (binlen > freespace) {
+                       binlen = freespace;
+               }
+
+               fr_bin2hex(out + 2, data->octets, binlen);
+               return hexlen;
+       }
+
+       case PW_TYPE_IFID:
+               a = ifid_ntoa(buf, sizeof(buf), data->ifid);
+               len = strlen(buf);
+               break;
+
+       case PW_TYPE_IPV6_ADDR:
+               a = inet_ntop(AF_INET6, &data->ipv6addr, buf, sizeof(buf));
+               len = strlen(buf);
+               break;
+
+       case PW_TYPE_IPV6_PREFIX:
+       {
+               struct in6_addr addr;
+
+               /*
+                *      Alignment issues.
+                */
+               memcpy(&addr, &(data->ipv6prefix[2]), sizeof(addr));
+
+               a = inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
+               if (a) {
+                       p = buf;
+
+                       len = strlen(buf);
+                       p += len;
+                       len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) data->ipv6prefix[1]);
+               }
+       }
+               break;
+
+       case PW_TYPE_IPV4_PREFIX:
+       {
+               struct in_addr addr;
+
+               /*
+                *      Alignment issues.
+                */
+               memcpy(&addr, &(data->ipv4prefix[2]), sizeof(addr));
+
+               a = inet_ntop(AF_INET, &addr, buf, sizeof(buf));
+               if (a) {
+                       p = buf;
+
+                       len = strlen(buf);
+                       p += len;
+                       len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) (data->ipv4prefix[1] & 0x3f));
+               }
+       }
+               break;
+
+       case PW_TYPE_ETHERNET:
+               return snprintf(out, outlen, "%02x:%02x:%02x:%02x:%02x:%02x",
+                               data->ether[0], data->ether[1],
+                               data->ether[2], data->ether[3],
+                               data->ether[4], data->ether[5]);
+
+       /*
+        *      Don't add default here
+        */
+       case PW_TYPE_INVALID:
+       case PW_TYPE_COMBO_IP_ADDR:
+       case PW_TYPE_COMBO_IP_PREFIX:
+       case PW_TYPE_EXTENDED:
+       case PW_TYPE_LONG_EXTENDED:
+       case PW_TYPE_EVS:
+       case PW_TYPE_VSA:
+       case PW_TYPE_TIMEVAL:
+       case PW_TYPE_BOOLEAN:
+       case PW_TYPE_MAX:
+               fr_assert(0);
+               *out = '\0';
+               return 0;
+       }
+
+       if (a) strlcpy(out, a, outlen);
+
+       return len;     /* Return the number of bytes we would of written (for truncation detection) */
+}
+