summaryrefslogtreecommitdiffstats
path: root/src/lib/value.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/value.c')
-rw-r--r--src/lib/value.c2013
1 files changed, 2013 insertions, 0 deletions
diff --git a/src/lib/value.c b/src/lib/value.c
new file mode 100644
index 0000000..05d42e2
--- /dev/null
+++ b/src/lib/value.c
@@ -0,0 +1,2013 @@
+/*
+ * value.c Functions to handle value_data_t
+ *
+ * Version: $Id$
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2014 The FreeRADIUS server project
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/libradius.h>
+#include <ctype.h>
+
+/** Compare two values
+ *
+ * @param[in] a_type of data to compare.
+ * @param[in] a_len of data to compare.
+ * @param[in] a Value to compare.
+ * @param[in] b_type of data to compare.
+ * @param[in] b_len of data to compare.
+ * @param[in] b Value to compare.
+ * @return -1 if a is less than b, 0 if both are equal, 1 if a is more than b, < -1 on error.
+ */
+int value_data_cmp(PW_TYPE a_type, value_data_t const *a, size_t a_len,
+ PW_TYPE b_type, value_data_t const *b, size_t b_len)
+{
+ int compare = 0;
+
+ if (a_type != b_type) {
+ fr_strerror_printf("Can't compare values of different types");
+ return -2;
+ }
+
+ /*
+ * After doing the previous check for special comparisons,
+ * do the per-type comparison here.
+ */
+ switch (a_type) {
+ case PW_TYPE_ABINARY:
+ case PW_TYPE_OCTETS:
+ case PW_TYPE_STRING: /* We use memcmp to be \0 safe */
+ {
+ size_t length;
+
+ if (a_len < b_len) {
+ length = a_len;
+ } else {
+ length = b_len;
+ }
+
+ if (length) {
+ compare = memcmp(a->octets, b->octets, length);
+ if (compare != 0) break;
+ }
+
+ /*
+ * Contents are the same. The return code
+ * is therefore the difference in lengths.
+ *
+ * i.e. "0x00" is smaller than "0x0000"
+ */
+ compare = a_len - b_len;
+ }
+ break;
+
+ /*
+ * Short-hand for simplicity.
+ */
+#define CHECK(_type) if (a->_type < b->_type) { compare = -1; \
+ } else if (a->_type > b->_type) { compare = +1; }
+
+ case PW_TYPE_BOOLEAN: /* this isn't a RADIUS type, and shouldn't really ever be used */
+ case PW_TYPE_BYTE:
+ CHECK(byte);
+ break;
+
+
+ case PW_TYPE_SHORT:
+ CHECK(ushort);
+ break;
+
+ case PW_TYPE_DATE:
+ CHECK(date);
+ break;
+
+ case PW_TYPE_INTEGER:
+ CHECK(integer);
+ break;
+
+ case PW_TYPE_SIGNED:
+ CHECK(sinteger);
+ break;
+
+ case PW_TYPE_INTEGER64:
+ CHECK(integer64);
+ break;
+
+ case PW_TYPE_ETHERNET:
+ compare = memcmp(a->ether, b->ether, sizeof(a->ether));
+ break;
+
+ case PW_TYPE_IPV4_ADDR: {
+ uint32_t a_int, b_int;
+
+ a_int = ntohl(a->ipaddr.s_addr);
+ b_int = ntohl(b->ipaddr.s_addr);
+ if (a_int < b_int) {
+ compare = -1;
+ } else if (a_int > b_int) {
+ compare = +1;
+ }
+ }
+ break;
+
+ case PW_TYPE_IPV6_ADDR:
+ compare = memcmp(&a->ipv6addr, &b->ipv6addr, sizeof(a->ipv6addr));
+ break;
+
+ case PW_TYPE_IPV6_PREFIX:
+ compare = memcmp(a->ipv6prefix, b->ipv6prefix, sizeof(a->ipv6prefix));
+ break;
+
+ case PW_TYPE_IPV4_PREFIX:
+ compare = memcmp(a->ipv4prefix, b->ipv4prefix, sizeof(a->ipv4prefix));
+ break;
+
+ case PW_TYPE_IFID:
+ compare = memcmp(a->ifid, b->ifid, sizeof(a->ifid));
+ break;
+
+ /*
+ * Na of the types below should be in the REQUEST
+ */
+ case PW_TYPE_INVALID: /* We should never see these */
+ case PW_TYPE_COMBO_IP_ADDR: /* This should have been converted into IPADDR/IPV6ADDR */
+ case PW_TYPE_COMBO_IP_PREFIX: /* This should have been converted into IPADDR/IPV6ADDR */
+ 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); /* unknown type */
+ return -2;
+
+ /*
+ * Do NOT add a default here, as new types are added
+ * static analysis will warn us they're not handled
+ */
+ }
+
+ if (compare > 0) {
+ return 1;
+ } else if (compare < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * We leverage the fact that IPv4 and IPv6 prefixes both
+ * have the same format:
+ *
+ * reserved, prefix-len, data...
+ */
+static int value_data_cidr_cmp_op(FR_TOKEN op, int bytes,
+ uint8_t a_net, uint8_t const *a,
+ uint8_t b_net, uint8_t const *b)
+{
+ int i, common;
+ uint32_t mask;
+
+ /*
+ * Handle the case of netmasks being identical.
+ */
+ if (a_net == b_net) {
+ int compare;
+
+ compare = memcmp(a, b, bytes);
+
+ /*
+ * If they're identical return true for
+ * identical.
+ */
+ if ((compare == 0) &&
+ ((op == T_OP_CMP_EQ) ||
+ (op == T_OP_LE) ||
+ (op == T_OP_GE))) {
+ return true;
+ }
+
+ /*
+ * Everything else returns false.
+ *
+ * 10/8 == 24/8 --> false
+ * 10/8 <= 24/8 --> false
+ * 10/8 >= 24/8 --> false
+ */
+ return false;
+ }
+
+ /*
+ * Netmasks are different. That limits the
+ * possible results, based on the operator.
+ */
+ switch (op) {
+ case T_OP_CMP_EQ:
+ return false;
+
+ case T_OP_NE:
+ return true;
+
+ case T_OP_LE:
+ case T_OP_LT: /* 192/8 < 192.168/16 --> false */
+ if (a_net < b_net) {
+ return false;
+ }
+ break;
+
+ case T_OP_GE:
+ case T_OP_GT: /* 192/16 > 192.168/8 --> false */
+ if (a_net > b_net) {
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+
+ if (a_net < b_net) {
+ common = a_net;
+ } else {
+ common = b_net;
+ }
+
+ /*
+ * Do the check byte by byte. If the bytes are
+ * identical, it MAY be a match. If they're different,
+ * it is NOT a match.
+ */
+ i = 0;
+ while (i < bytes) {
+ /*
+ * All leading bytes are identical.
+ */
+ if (common == 0) return true;
+
+ /*
+ * Doing bitmasks takes more work.
+ */
+ if (common < 8) break;
+
+ if (a[i] != b[i]) return false;
+
+ common -= 8;
+ i++;
+ continue;
+ }
+
+ mask = 1;
+ mask <<= (8 - common);
+ mask--;
+ mask = ~mask;
+
+ if ((a[i] & mask) == ((b[i] & mask))) {
+ return true;
+ }
+
+ return false;
+}
+
+/** Compare two attributes using an operator
+ *
+ * @param[in] op to use in comparison.
+ * @param[in] a_type of data to compare.
+ * @param[in] a_len of data to compare.
+ * @param[in] a Value to compare.
+ * @param[in] b_type of data to compare.
+ * @param[in] b_len of data to compare.
+ * @param[in] b Value to compare.
+ * @return 1 if true, 0 if false, -1 on error.
+ */
+int value_data_cmp_op(FR_TOKEN op,
+ PW_TYPE a_type, value_data_t const *a, size_t a_len,
+ PW_TYPE b_type, value_data_t const *b, size_t b_len)
+{
+ int compare = 0;
+
+ if (!a || !b) return -1;
+
+ switch (a_type) {
+ case PW_TYPE_IPV4_ADDR:
+ switch (b_type) {
+ case PW_TYPE_IPV4_ADDR: /* IPv4 and IPv4 */
+ goto cmp;
+
+ 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]);
+
+ default:
+ fr_strerror_printf("Cannot compare IPv4 with IPv6 address");
+ return -1;
+ }
+
+ 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],
+ 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]);
+
+ default:
+ fr_strerror_printf("Cannot compare IPv4 with IPv6 address");
+ return -1;
+ }
+
+ case PW_TYPE_IPV6_ADDR:
+ switch (b_type) {
+ case PW_TYPE_IPV6_ADDR: /* IPv6 and IPv6 */
+ goto cmp;
+
+ 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]);
+
+ default:
+ fr_strerror_printf("Cannot compare IPv6 with IPv4 address");
+ return -1;
+ }
+
+ 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);
+
+ 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]);
+
+ default:
+ fr_strerror_printf("Cannot compare IPv6 with IPv4 address");
+ return -1;
+ }
+
+ default:
+ cmp:
+ compare = value_data_cmp(a_type, a, a_len,
+ b_type, b, b_len);
+ if (compare < -1) { /* comparison error */
+ return -1;
+ }
+ }
+
+ /*
+ * Now do the operator comparison.
+ */
+ switch (op) {
+ case T_OP_CMP_EQ:
+ return (compare == 0);
+
+ case T_OP_NE:
+ return (compare != 0);
+
+ case T_OP_LT:
+ return (compare < 0);
+
+ case T_OP_GT:
+ return (compare > 0);
+
+ case T_OP_LE:
+ return (compare <= 0);
+
+ case T_OP_GE:
+ return (compare >= 0);
+
+ default:
+ return 0;
+ }
+}
+
+static char const hextab[] = "0123456789abcdef";
+
+/** Convert string value to a value_data_t type
+ *
+ * @param[in] ctx to alloc strings in.
+ * @param[out] dst where to write parsed value.
+ * @param[in,out] src_type of value data to create/type of value created.
+ * @param[in] src_enumv DICT_ATTR with string aliases for integer values.
+ * @param[in] src String to convert. Binary safe for variable length values if len is provided.
+ * @param[in] src_len may be < 0 in which case strlen(len) is used to determine length, else src_len
+ * should be the length of the string or sub string to parse.
+ * @param[in] quote quotation character used to drive de-escaping
+ * @return length of data written to out or -1 on parse error.
+ */
+ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
+ PW_TYPE *src_type, DICT_ATTR const *src_enumv,
+ char const *src, ssize_t src_len, char quote)
+{
+ DICT_VALUE *dval;
+ size_t len;
+ ssize_t ret;
+ char buffer[256];
+
+ if (!src) return -1;
+
+ len = (src_len < 0) ? strlen(src) : (size_t)src_len;
+
+ /*
+ * Set size for all fixed length attributes.
+ */
+ ret = dict_attr_sizes[*src_type][1]; /* Max length */
+
+ /*
+ * It's a variable ret src_type so we just alloc a new buffer
+ * of size len and copy.
+ */
+ switch (*src_type) {
+ case PW_TYPE_STRING:
+ {
+ char *p, *buff;
+ char const *q;
+ int x;
+
+ buff = p = talloc_bstrndup(ctx, src, len);
+
+ /*
+ * No de-quoting. Just copy the string.
+ */
+ if (!quote) {
+ ret = len;
+ dst->strvalue = buff;
+ goto finish;
+ }
+
+ /*
+ * Do escaping for single quoted strings. Only
+ * single quotes get escaped. Everything else is
+ * left as-is.
+ */
+ if (quote == '\'') {
+ q = p;
+
+ 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 += 2;
+ continue;
+ }
+
+ /*
+ * Not escaped, just copy it over.
+ */
+ *(p++) = *(q++);
+ }
+
+ *p = '\0';
+ ret = p - buff;
+
+ /* Shrink the buffer to the correct size */
+ dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1);
+ goto finish;
+ }
+
+ /*
+ * It's "string" or `string`, do all standard
+ * escaping.
+ */
+ q = p;
+ while (q < (buff + len)) {
+ char c = *q++;
+
+ if ((c == '\\') && (q >= (buff + len))) {
+ fr_strerror_printf("Invalid escape at end of string");
+ talloc_free(buff);
+ return -1;
+ }
+
+ /*
+ * Fix up \X -> ... the binary form of it.
+ */
+ if (c == '\\') {
+ switch (*q) {
+ case 'r':
+ c = '\r';
+ q++;
+ break;
+
+ case 'n':
+ c = '\n';
+ q++;
+ break;
+
+ case 't':
+ c = '\t';
+ q++;
+ break;
+
+ case '\\':
+ c = '\\';
+ q++;
+ break;
+
+ default:
+ /*
+ * \" --> ", but only inside of double quoted strings, etc.
+ */
+ if (*q == quote) {
+ c = quote;
+ q++;
+ break;
+ }
+
+ /*
+ * \000 --> binary zero character
+ */
+ if ((q[0] >= '0') &&
+ (q[0] <= '9') &&
+ (q[1] >= '0') &&
+ (q[1] <= '9') &&
+ (q[2] >= '0') &&
+ (q[2] <= '9') &&
+ (sscanf(q, "%3o", &x) == 1)) {
+ c = x;
+ q += 3;
+ }
+
+ /*
+ * Else It's not a recognised escape sequence DON'T
+ * consume the backslash. This is identical
+ * behaviour to bash and most other things that
+ * use backslash escaping.
+ */
+ }
+ }
+
+ *p++ = c;
+ }
+
+ *p = '\0';
+ ret = p - buff;
+ dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1);
+ }
+ goto finish;
+
+ case PW_TYPE_VSA:
+ 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;
+
+ /*
+ * No 0x prefix, just copy verbatim.
+ */
+ if ((len < 2) || (strncasecmp(src, "0x", 2) != 0)) {
+ dst->octets = talloc_memdup(ctx, (uint8_t const *)src, len);
+ talloc_set_type(dst->octets, uint8_t);
+ ret = len;
+ goto finish;
+ }
+
+ len -= 2;
+
+ /*
+ * Invalid.
+ */
+ if ((len & 0x01) != 0) {
+ fr_strerror_printf("Length of Hex String is not even, got %zu bytes", len);
+ return -1;
+ }
+
+ ret = len >> 1;
+ p = talloc_array(ctx, uint8_t, ret);
+ if (fr_hex2bin(p, ret, src + 2, len) != (size_t)ret) {
+ talloc_free(p);
+ fr_strerror_printf("Invalid hex data");
+ return -1;
+ }
+
+ dst->octets = p;
+ }
+ goto finish;
+
+ case PW_TYPE_ABINARY:
+#ifdef WITH_ASCEND_BINARY
+ if ((len > 1) && (strncasecmp(src, "0x", 2) == 0)) {
+ ssize_t bin;
+
+ 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
+ /*
+ * If Ascend binary is NOT defined,
+ * then fall through to raw octets, so that
+ * the user can at least make them by hand...
+ */
+ goto do_octets;
+#endif
+
+ /* don't use this! */
+ case PW_TYPE_TLV:
+ fr_strerror_printf("Cannot parse TLV");
+ return -1;
+
+ case PW_TYPE_IPV4_ADDR:
+ {
+ fr_ipaddr_t addr;
+
+ if (fr_pton4(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
+
+ /*
+ * We allow v4 addresses to have a /32 suffix as some databases (PostgreSQL)
+ * print them this way.
+ */
+ if (addr.prefix != 32) {
+ fr_strerror_printf("Invalid IPv4 mask length \"/%i\". Only \"/32\" permitted "
+ "for non-prefix types", addr.prefix);
+ return -1;
+ }
+
+ dst->ipaddr.s_addr = addr.ipaddr.ip4addr.s_addr;
+ }
+ goto finish;
+
+ case PW_TYPE_IPV4_PREFIX:
+ {
+ fr_ipaddr_t addr;
+
+ 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);
+ }
+ goto finish;
+
+ case PW_TYPE_IPV6_ADDR:
+ {
+ fr_ipaddr_t addr;
+
+ if (fr_pton6(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
+
+ /*
+ * We allow v6 addresses to have a /128 suffix as some databases (PostgreSQL)
+ * print them this way.
+ */
+ if (addr.prefix != 128) {
+ fr_strerror_printf("Invalid IPv6 mask length \"/%i\". Only \"/128\" permitted "
+ "for non-prefix types", addr.prefix);
+ return -1;
+ }
+
+ memcpy(&dst->ipv6addr, addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6addr));
+ }
+ goto finish;
+
+ case PW_TYPE_IPV6_PREFIX:
+ {
+ fr_ipaddr_t addr;
+
+ 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);
+ }
+ goto finish;
+
+ default:
+ break;
+ }
+
+ /*
+ * It's a fixed size src_type, copy to a temporary buffer and
+ * \0 terminate if insize >= 0.
+ */
+ if (src_len > 0) {
+ if (len >= sizeof(buffer)) {
+ fr_strerror_printf("Temporary buffer too small");
+ return -1;
+ }
+
+ memcpy(buffer, src, src_len);
+ buffer[src_len] = '\0';
+ src = buffer;
+ }
+
+ switch (*src_type) {
+ case PW_TYPE_BYTE:
+ {
+ char *p;
+ unsigned int i;
+
+ /*
+ * Note that ALL integers are unsigned!
+ */
+ i = fr_strtoul(src, &p);
+
+ /*
+ * Look for the named src for the given
+ * attribute.
+ */
+ 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",
+ src, src_enumv->name);
+ return -1;
+ }
+
+ dst->byte = dval->value;
+ } else {
+ if (i > 255) {
+ fr_strerror_printf("Byte value \"%s\" is larger than 255", src);
+ return -1;
+ }
+
+ dst->byte = i;
+ }
+ break;
+ }
+
+ case PW_TYPE_SHORT:
+ {
+ char *p;
+ unsigned int i;
+
+ /*
+ * Note that ALL integers are unsigned!
+ */
+ i = fr_strtoul(src, &p);
+
+ /*
+ * Look for the named src for the given
+ * attribute.
+ */
+ 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",
+ src, src_enumv->name);
+ return -1;
+ }
+
+ dst->ushort = dval->value;
+ } else {
+ if (i > 65535) {
+ fr_strerror_printf("Short value \"%s\" is larger than 65535", src);
+ return -1;
+ }
+
+ dst->ushort = i;
+ }
+ break;
+ }
+
+ case PW_TYPE_INTEGER:
+ {
+ char *p;
+ unsigned int i;
+
+ /*
+ * Note that ALL integers are unsigned!
+ */
+ i = fr_strtoul(src, &p);
+
+ /*
+ * Look for the named src for the given
+ * attribute.
+ */
+ 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",
+ src, src_enumv->name);
+ return -1;
+ }
+
+ dst->integer = dval->value;
+ } else {
+ /*
+ * Value is always within the limits
+ */
+ dst->integer = i;
+ }
+ }
+ break;
+
+ case PW_TYPE_INTEGER64:
+ {
+ uint64_t i;
+
+ /*
+ * Note that ALL integers are unsigned!
+ */
+ if (sscanf(src, "%" PRIu64, &i) != 1) {
+ fr_strerror_printf("Failed parsing \"%s\" as unsigned 64bit integer", src);
+ return -1;
+ }
+ dst->integer64 = i;
+ }
+ break;
+
+ case PW_TYPE_DATE:
+ {
+ /*
+ * time_t may be 64 bits, whule vp_date MUST be 32-bits. We need an
+ * intermediary variable to handle the conversions.
+ */
+ time_t date;
+ struct tm tm = { 0 };
+ char *end;
+
+ /*
+ * Try to parse dates via locale-specific names,
+ * using the same format string as strftime(),
+ * below.
+ *
+ * If that fails (e.g. unix dates as integer),
+ * then we fall back to our parsing routine,
+ * which is much more forgiving.
+ */
+ end = strptime(src, "%b %e %Y %H:%M:%S %Z", &tm);
+ if (end && (*end == '\0')) {
+ date = mktime(&tm);
+
+ } else if (fr_get_time(src, &date) < 0) {
+ fr_strerror_printf("failed to parse time string \"%s\"", src);
+ return -1;
+ }
+
+ dst->date = date;
+ }
+
+ break;
+
+ case PW_TYPE_IFID:
+ if (ifid_aton(src, (void *) dst->ifid) == NULL) {
+ fr_strerror_printf("Failed to parse interface-id string \"%s\"", src);
+ return -1;
+ }
+ break;
+
+ case PW_TYPE_ETHERNET:
+ {
+ char const *c1, *c2, *cp;
+ size_t p_len = 0;
+
+ /*
+ * Convert things which are obviously integers to Ethernet addresses
+ *
+ * We assume the number is the bigendian representation of the
+ * ethernet address.
+ */
+ if (is_integer(src)) {
+ uint64_t integer = htonll(atoll(src));
+
+ memcpy(dst->ether, &integer, sizeof(dst->ether));
+ break;
+ }
+
+ cp = src;
+ while (*cp) {
+ if (cp[1] == ':') {
+ c1 = hextab;
+ c2 = memchr(hextab, tolower((int) cp[0]), 16);
+ cp += 2;
+ } else if ((cp[1] != '\0') && ((cp[2] == ':') || (cp[2] == '\0'))) {
+ c1 = memchr(hextab, tolower((int) cp[0]), 16);
+ c2 = memchr(hextab, tolower((int) cp[1]), 16);
+ cp += 2;
+ if (*cp == ':') cp++;
+ } else {
+ c1 = c2 = NULL;
+ }
+ if (!c1 || !c2 || (p_len >= sizeof(dst->ether))) {
+ fr_strerror_printf("failed to parse Ethernet address \"%s\"", src);
+ return -1;
+ }
+ dst->ether[p_len] = ((c1-hextab)<<4) + (c2-hextab);
+ p_len++;
+ }
+ }
+ break;
+
+ /*
+ * Crazy polymorphic (IPv4/IPv6) attribute src_type for WiMAX.
+ *
+ * We try and make is saner by replacing the original
+ * da, with either an IPv4 or IPv6 da src_type.
+ *
+ * These are not dynamic da, and will have the same vendor
+ * and attribute as the original.
+ */
+ case PW_TYPE_COMBO_IP_ADDR:
+ {
+ if (inet_pton(AF_INET6, src, &dst->ipv6addr) > 0) {
+ *src_type = PW_TYPE_IPV6_ADDR;
+ ret = dict_attr_sizes[PW_TYPE_COMBO_IP_ADDR][1]; /* size of IPv6 address */
+ } else {
+ fr_ipaddr_t ipaddr;
+
+ if (ip_hton(&ipaddr, AF_INET, src, false) < 0) {
+ fr_strerror_printf("Failed to find IPv4 address for %s", src);
+ return -1;
+ }
+
+ *src_type = PW_TYPE_IPV4_ADDR;
+ dst->ipaddr.s_addr = ipaddr.ipaddr.ip4addr.s_addr;
+ ret = dict_attr_sizes[PW_TYPE_COMBO_IP_ADDR][0]; /* size of IPv4 address */
+ }
+ }
+ break;
+
+ case PW_TYPE_SIGNED:
+ /* Damned code for 1 WiMAX attribute */
+ dst->sinteger = (int32_t)strtol(src, NULL, 10);
+ break;
+
+ /*
+ * Anything else.
+ */
+ default:
+ fr_strerror_printf("Unknown attribute type %d", *src_type);
+ return -1;
+ }
+
+finish:
+ return ret;
+}
+
+/** Performs byte order reversal for types that need it
+ *
+ */
+static ssize_t value_data_hton(value_data_t *dst, PW_TYPE dst_type, void const *src, size_t src_len)
+{
+ size_t dst_len;
+ uint8_t *dst_ptr;
+
+ /* 8 byte integers */
+ switch (dst_type) {
+ case PW_TYPE_INTEGER64:
+ dst_len = sizeof(dst->integer64);
+
+ if (src_len < dst_len) {
+ too_small:
+ fr_strerror_printf("Source is too small to cast to destination type");
+ return -1;
+ }
+
+ dst->integer64 = htonll(*(uint64_t const *)src);
+ break;
+
+ /* 4 byte integers */
+ case PW_TYPE_INTEGER:
+ case PW_TYPE_DATE:
+ case PW_TYPE_SIGNED:
+ dst_len = sizeof(dst->integer);
+
+ if (src_len < dst_len) goto too_small;
+
+ dst->integer = htonl(*(uint32_t const *)src);
+ break;
+
+ /* 2 byte integers */
+ case PW_TYPE_SHORT:
+ dst_len = sizeof(dst->ushort);
+
+ if (src_len < dst_len) goto too_small;
+
+ dst->ushort = htons(*(uint16_t const *)src);
+ break;
+
+ /* 1 byte integer */
+ case PW_TYPE_BYTE:
+ dst_len = sizeof(dst->byte);
+
+ if (src_len < dst_len) goto too_small;
+
+ dst->byte = *(uint8_t const *)src;
+ break;
+
+ case PW_TYPE_IPV4_ADDR:
+ dst_len = 4;
+ dst_ptr = (uint8_t *) &dst->ipaddr.s_addr;
+
+ copy:
+ /*
+ * Not enough information, die.
+ */
+ if (src_len < dst_len) goto too_small;
+
+ /*
+ * Copy only as much as we need from the source.
+ */
+ memcpy(dst_ptr, src, dst_len);
+ break;
+
+ case PW_TYPE_ABINARY:
+ dst_len = sizeof(dst->filter);
+ dst_ptr = (uint8_t *) dst->filter;
+
+ /*
+ * Too little data is OK here.
+ */
+ if (src_len < dst_len) {
+ memcpy(dst_ptr, src, src_len);
+ memset(dst_ptr + src_len, 0, dst_len - src_len);
+ break;
+ }
+ goto copy;
+
+ case PW_TYPE_IFID:
+ dst_len = sizeof(dst->ifid);
+ dst_ptr = (uint8_t *) dst->ifid;
+ goto copy;
+
+ case PW_TYPE_IPV6_ADDR:
+ dst_len = sizeof(dst->ipv6addr);
+ dst_ptr = (uint8_t *) dst->ipv6addr.s6_addr;
+ goto copy;
+
+ case PW_TYPE_IPV4_PREFIX:
+ dst_len = sizeof(dst->ipv4prefix);
+ dst_ptr = (uint8_t *) dst->ipv4prefix;
+
+ if (src_len < dst_len) goto too_small;
+ if ((((uint8_t const *)src)[1] & 0x3f) > 32) return -1;
+ goto copy;
+
+ case PW_TYPE_IPV6_PREFIX:
+ dst_len = sizeof(dst->ipv6prefix);
+ dst_ptr = (uint8_t *) dst->ipv6prefix;
+
+ /*
+ * Smaller IPv6 prefixes are OK, too, so long as
+ * they're not too short.
+ */
+ if (src_len < 2) goto too_small;
+
+ /*
+ * Prefix is too long.
+ */
+ if (((uint8_t const *)src)[1] > 128) return -1;
+
+ if (src_len < dst_len) {
+ memcpy(dst_ptr, src, src_len);
+ memset(dst_ptr + src_len, 0, dst_len - src_len);
+ break;
+ }
+
+ goto copy;
+
+ case PW_TYPE_ETHERNET:
+ dst_len = sizeof(dst->ether);
+ dst_ptr = (uint8_t *) dst->ether;
+ goto copy;
+
+ default:
+ fr_strerror_printf("Invalid cast to %s",
+ fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+ return -1; /* can't do it */
+ }
+
+ return dst_len;
+}
+
+/** Convert one type of value_data_t to another
+ *
+ * @note This should be the canonical function used to convert between data types.
+ *
+ * @param ctx to allocate buffers in (usually the same as dst)
+ * @param dst Where to write result of casting.
+ * @param dst_type to cast to.
+ * @param dst_enumv Enumerated values used to converts strings to integers.
+ * @param src_type to cast from.
+ * @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 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,
+ PW_TYPE src_type, DICT_ATTR const *src_enumv,
+ value_data_t const *src, size_t src_len)
+{
+ ssize_t dst_len;
+
+ if (!fr_assert(dst_type != src_type)) {
+ fr_strerror_printf("Types do not match");
+ return -1;
+ }
+
+ /*
+ * Deserialise a value_data_t
+ */
+ if (src_type == PW_TYPE_STRING) {
+ return value_data_from_str(ctx, dst, &dst_type, dst_enumv, src->strvalue, src_len, '\0');
+ }
+
+ /*
+ * Converts the src data to octets with no processing.
+ */
+ if (dst_type == PW_TYPE_OCTETS) {
+ dst_len = value_data_hton(dst, src_type, src, src_len);
+ if (dst_len < 0) return -1;
+
+ dst->octets = talloc_memdup(ctx, dst, dst_len);
+ talloc_set_type(dst->octets, uint8_t);
+ return dst_len;
+ }
+
+ /*
+ * Serialise a value_data_t
+ */
+ if (dst_type == PW_TYPE_STRING) {
+ 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));
+ dst->integer64 = htonll(dst->integer64);
+ fixed_length:
+ return dict_attr_sizes[dst_type][0];
+ }
+
+ if ((src_type == PW_TYPE_INTEGER64) &&
+ (dst_type == PW_TYPE_ETHERNET)) {
+ uint8_t array[8];
+ uint64_t i;
+
+ i = htonll(src->integer64);
+ memcpy(array, &i, 8);
+
+ /*
+ * For OUIs in the DB.
+ */
+ if ((array[0] != 0) || (array[1] != 0)) return -1;
+
+ memcpy(dst->ether, &array[2], 6);
+ goto fixed_length;
+ }
+
+ /*
+ * For integers, we allow the casting of a SMALL type to
+ * a larger type, but not vice-versa.
+ */
+ if (dst_type == PW_TYPE_INTEGER64) {
+ switch (src_type) {
+ case PW_TYPE_BYTE:
+ dst->integer64 = src->byte;
+ break;
+
+ case PW_TYPE_SHORT:
+ dst->integer64 = src->ushort;
+ break;
+
+ case PW_TYPE_INTEGER:
+ dst->integer64 = src->integer;
+ break;
+
+ case PW_TYPE_DATE:
+ dst->integer64 = src->date;
+ break;
+
+ case PW_TYPE_OCTETS:
+ goto do_octets;
+
+ default:
+ invalid_cast:
+ fr_strerror_printf("Invalid cast from %s to %s",
+ fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+ fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+ return -1;
+
+ }
+ goto fixed_length;
+ }
+
+ /*
+ * We can cast LONG integers to SHORTER ones, so long
+ * as the long one is on the LHS.
+ */
+ if (dst_type == PW_TYPE_INTEGER) {
+ switch (src_type) {
+ case PW_TYPE_BYTE:
+ dst->integer = src->byte;
+ break;
+
+ case PW_TYPE_SHORT:
+ dst->integer = src->ushort;
+ break;
+
+ case PW_TYPE_DATE:
+ dst->integer = src->date;
+ break;
+
+ case PW_TYPE_IPV4_ADDR:
+ dst->integer = ntohl(src->ipaddr.s_addr);
+ break;
+
+ case PW_TYPE_OCTETS:
+ goto do_octets;
+
+ default:
+ goto invalid_cast;
+ }
+ goto fixed_length;
+ }
+
+ if (dst_type == PW_TYPE_SHORT) {
+ switch (src_type) {
+ case PW_TYPE_BYTE:
+ dst->ushort = src->byte;
+ break;
+
+ case PW_TYPE_OCTETS:
+ goto do_octets;
+
+ default:
+ goto invalid_cast;
+ }
+ goto fixed_length;
+ }
+
+ /*
+ * 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.
+ */
+ if ((src_len < dict_attr_sizes[dst_type][0]) ||
+ (src_len > dict_attr_sizes[dst_type][1])) {
+ char const *src_type_name;
+
+ src_type_name = fr_int2str(dict_attr_types, src_type, "<INVALID>");
+ fr_strerror_printf("Invalid cast from %s to %s. Length should be between %zu and %zu but is %zu",
+ src_type_name,
+ fr_int2str(dict_attr_types, dst_type, "<INVALID>"),
+ dict_attr_sizes[dst_type][0], dict_attr_sizes[dst_type][1],
+ src_len);
+ return -1;
+ }
+
+ if (src_type == PW_TYPE_OCTETS) {
+ do_octets:
+ return value_data_hton(dst, dst_type, src->octets, src_len);
+ }
+
+ /*
+ * Convert host order to network byte order.
+ */
+ if ((dst_type == PW_TYPE_IPV4_ADDR) &&
+ ((src_type == PW_TYPE_INTEGER) ||
+ (src_type == PW_TYPE_DATE) ||
+ (src_type == PW_TYPE_SIGNED))) {
+ dst->ipaddr.s_addr = htonl(src->integer);
+
+ } else if ((src_type == PW_TYPE_IPV4_ADDR) &&
+ ((dst_type == PW_TYPE_INTEGER) ||
+ (dst_type == PW_TYPE_DATE) ||
+ (dst_type == PW_TYPE_SIGNED))) {
+ dst->integer = htonl(src->ipaddr.s_addr);
+
+ } else { /* they're of the same byte order */
+ memcpy(&dst, &src, src_len);
+ }
+
+ return src_len;
+}
+
+/** 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 (src_type) {
+ default:
+ memcpy(dst, src, sizeof(*src));
+ break;
+
+ case PW_TYPE_STRING:
+ dst->strvalue = talloc_bstrndup(ctx, src->strvalue, src_len);
+ if (!dst->strvalue) return -1;
+ break;
+
+ case PW_TYPE_OCTETS:
+ dst->octets = talloc_memdup(ctx, src->octets, src_len);
+ talloc_set_type(dst->strvalue, uint8_t);
+ if (!dst->octets) return -1;
+ break;
+ }
+
+ 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_zero_array(ctx, char, 64);
+ strftime(p, 63, "%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) */
+}
+