/* * 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 #include /** 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, "")); 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, ""), fr_int2str(dict_attr_types, dst_type, "")); 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, ""), fr_int2str(dict_attr_types, dst_type, "")); 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, ""), fr_int2str(dict_attr_types, dst_type, "")); 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, ""), fr_int2str(dict_attr_types, dst_type, "")); 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, ""); 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, ""), 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) */ }