diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/lib/value.c | |
parent | Initial commit. (diff) | |
download | freeradius-upstream.tar.xz freeradius-upstream.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/value.c')
-rw-r--r-- | src/lib/value.c | 2013 |
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..aa49897 --- /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((uint8_t) cp[0]), 16); + cp += 2; + } else if ((cp[1] != '\0') && ((cp[2] == ':') || (cp[2] == '\0'))) { + c1 = memchr(hextab, tolower((uint8_t) cp[0]), 16); + c2 = memchr(hextab, tolower((uint8_t) 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) */ +} + |