From 7ec93ff9e4d979e4bbcf33f9c90c94dc9d3cdba9 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Sun, 13 Nov 2011 16:16:05 +1100 Subject: add new RADIUS client library --- lib/radius/attrs.c | 1411 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1411 insertions(+) create mode 100644 lib/radius/attrs.c (limited to 'lib/radius/attrs.c') diff --git a/lib/radius/attrs.c b/lib/radius/attrs.c new file mode 100644 index 0000000..4fd2bf4 --- /dev/null +++ b/lib/radius/attrs.c @@ -0,0 +1,1411 @@ +/* +Copyright (c) 2011, Network RADIUS SARL +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file attrs.c + * \brief Attribute encoding and decoding routines. + */ + +#include + +/* + * Encodes the data portion of an attribute. + * Returns -1 on error, or the length of the data portion. + */ +static ssize_t vp2data_any(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + int nest, + const VALUE_PAIR **pvp, + uint8_t *start, size_t room) +{ + uint32_t lvalue; + ssize_t len; + const uint8_t *data; + uint8_t *ptr = start; + uint8_t array[4]; + const VALUE_PAIR *vp = *pvp; + +#ifdef NR_TYPE_TLV + /* + * See if we need to encode a TLV. The low portion of + * the attribute has already been placed into the packer. + * If there are still attribute bytes left, then go + * encode them as TLVs. + * + * If we cared about the stack, we could unroll the loop. + */ + if ((nest > 0) && (nest <= nr_attr_max_tlv) && + ((vp->da->attr >> nr_attr_shift[nest]) != 0)) { + return vp2data_tlvs(packet, original, nest, pvp, + start, room); + } +#else + nest = nest; /* -Wunused */ +#endif + + /* + * Set up the default sources for the data. + */ + data = vp->vp_octets; + len = vp->length; + + switch(vp->da->type) { + case NR_TYPE_IPV6PREFIX: + len = sizeof(vp->vp_ipv6prefix); + break; + + case NR_TYPE_STRING: + case NR_TYPE_OCTETS: + case NR_TYPE_IFID: + case NR_TYPE_IPV6ADDR: +#ifdef NR_TYPE_ABINARY + case NR_TYPE_ABINARY: +#endif + /* nothing more to do */ + break; + + case NR_TYPE_BYTE: + len = 1; /* just in case */ + array[0] = vp->vp_integer & 0xff; + data = array; + break; + + case NR_TYPE_SHORT: + len = 2; /* just in case */ + array[0] = (vp->vp_integer >> 8) & 0xff; + array[1] = vp->vp_integer & 0xff; + data = array; + break; + + case NR_TYPE_INTEGER: + len = 4; /* just in case */ + lvalue = htonl(vp->vp_integer); + memcpy(array, &lvalue, sizeof(lvalue)); + data = array; + break; + + case NR_TYPE_IPADDR: + data = (const uint8_t *) &vp->vp_ipaddr; + len = 4; /* just in case */ + break; + + /* + * There are no tagged date attributes. + */ + case NR_TYPE_DATE: + lvalue = htonl(vp->vp_date); + data = (const uint8_t *) &lvalue; + len = 4; /* just in case */ + break; + +#ifdef VENDORPEC_WIMAX + case NR_TYPE_SIGNED: + { + int32_t slvalue; + + len = 4; /* just in case */ + slvalue = htonl(vp->vp_signed); + memcpy(array, &slvalue, sizeof(slvalue)); + break; + } +#endif + +#ifdef NR_TYPE_TLV + case NR_TYPE_TLV: + data = vp->vp_tlv; + if (!data) { + nr_debug_error("ERROR: Cannot encode NULL TLV"); + return -NR_ERR_INVALID_ARG; + } + len = vp->length; + break; +#endif + + default: /* unknown type: ignore it */ + nr_debug_error("ERROR: Unknown attribute type %d", vp->da->type); + return -NR_ERR_ATTR_TYPE_UNKNOWN; + } + + /* + * Bound the data to the calling size + */ + if (len > (ssize_t) room) len = room; + +#ifndef FLAG_ENCRYPT_TUNNEL_PASSWORD + original = original; /* -Wunused */ +#endif + + /* + * Encrypt the various password styles + * + * Attributes with encrypted values MUST be less than + * 128 bytes long. + */ + switch (vp->da->flags.encrypt) { + case FLAG_ENCRYPT_USER_PASSWORD: + len = nr_password_encrypt(ptr, room, data, len, + packet->secret, packet->vector); + break; + +#ifdef FLAG_ENCRYPT_TUNNEL_PASSWORD + case FLAG_ENCRYPT_TUNNEL_PASSWORD: + lvalue = 0; + if (vp->da->flags.has_tag) lvalue = 1; + + /* + * Check if there's enough room. If there isn't, + * we discard the attribute. + * + * This is ONLY a problem if we have multiple VSA's + * in one Vendor-Specific, though. + */ + if (room < (18 + lvalue)) { + *pvp = vp->next; + return 0; + } + + switch (packet->code) { + case PW_ACCESS_ACCEPT: + case PW_ACCESS_REJECT: + case PW_ACCESS_CHALLENGE: + default: + if (!original) { + nr_debug_error("ERROR: No request packet, cannot encrypt %s attribute in the vp.", vp->da->name); + return -NR_ERR_REQUEST_REQUIRED; + } + + if (lvalue) ptr[0] = vp->tag; + len = nr_tunnelpw_encrypt(ptr + lvalue, + room - lvalue, data, len, + packet->secret, + original->vector); + if (len < 0) return len; + break; + case PW_ACCOUNTING_REQUEST: + case PW_DISCONNECT_REQUEST: + case PW_COA_REQUEST: + ptr[0] = vp->tag; + len = nr_tunnelpw_encrypt(ptr + 1, room, data, len - 1, + packet->secret, + packet->vector); + if (len < 0) return len; + break; + } + break; +#endif + + /* + * The code above ensures that this attribute + * always fits. + */ +#ifdef FLAG_ENCRYPT_ASCEND_SECRET + case FLAG_ENCRYPT_ASCEND_SECRET: + make_secret(ptr, packet->vector, packet->secret, data); + len = AUTH_VECTOR_LEN; + break; +#endif + + default: + if (vp->da->flags.has_tag && TAG_VALID(vp->tag)) { + if (vp->da->type == NR_TYPE_STRING) { + if (len > ((ssize_t) (room - 1))) len = room - 1; + ptr[0] = vp->tag; + ptr++; + } else if (vp->da->type == NR_TYPE_INTEGER) { + array[0] = vp->tag; + } /* else it can't be any other type */ + } + memcpy(ptr, data, len); + break; + } /* switch over encryption flags */ + + *(pvp) = vp->next; + return len + (ptr - start);; +} + + +/* + * Encode an RFC format TLV. This could be a standard attribute, + * or a TLV data type. If it's a standard attribute, then + * vp->da->attr == attribute. Otherwise, attribute may be + * something else. + */ +static ssize_t vp2attr_rfc(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const VALUE_PAIR **pvp, + unsigned int attribute, uint8_t *ptr, size_t room) +{ + ssize_t len; + + if (room < 2) { + *pvp = (*pvp)->next; + return 0; + } + + ptr[0] = attribute & 0xff; + ptr[1] = 2; + + if (room > ((unsigned) 255 - ptr[1])) room = 255 - ptr[1]; + + len = vp2data_any(packet, original, 0, pvp, ptr + ptr[1], room); + if (len < 0) return len; + + ptr[1] += len; + + return ptr[1]; +} + + +#ifndef WITHOUT_VSAS +/* + * Encode a VSA which is a TLV. If it's in the RFC format, call + * vp2attr_rfc. Otherwise, encode it here. + */ +static ssize_t vp2attr_vsa(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const VALUE_PAIR **pvp, + unsigned int attribute, unsigned int vendor, + uint8_t *ptr, size_t room) +{ + ssize_t len; + const DICT_VENDOR *dv; + + /* + * Unknown vendor: RFC format. + * Known vendor and RFC format: go do that. + */ + dv = nr_dict_vendor_byvalue(vendor); + if (!dv || + ( +#ifdef NR_TYPE_TLV + !(*pvp)->flags.is_tlv && +#endif + (dv->type == 1) && (dv->length == 1))) { + return vp2attr_rfc(packet, original, pvp, + attribute, ptr, room); + } + +#ifdef NR_TYPE_TLV + if ((*pvp)->flags.is_tlv) { + return data2vp_tlvs(packet, original, 0, pvp, + ptr, room); + } +#endif + + switch (dv->type) { + default: + nr_debug_error("vp2attr_vsa: Internal sanity check failed," + " type %u", (unsigned) dv->type); + return -NR_ERR_INTERNAL_FAILURE; + + case 4: + ptr[0] = 0; /* attr must be 24-bit */ + ptr[1] = (attribute >> 16) & 0xff; + ptr[2] = (attribute >> 8) & 0xff; + ptr[3] = attribute & 0xff; + break; + + case 2: + ptr[0] = (attribute >> 8) & 0xff; + ptr[1] = attribute & 0xff; + break; + + case 1: + ptr[0] = attribute & 0xff; + break; + } + + switch (dv->length) { + default: + nr_debug_error("vp2attr_vsa: Internal sanity check failed," + " length %u", (unsigned) dv->length); + return -NR_ERR_INTERNAL_FAILURE; + + case 0: + break; + + case 2: + ptr[dv->type] = 0; + /* FALL-THROUGH */ + + case 1: + ptr[dv->type + dv->length - 1] = dv->type + dv->length; + break; + + } + + if (room > ((unsigned) 255 - (dv->type + dv->length))) { + room = 255 - (dv->type + dv->length); + } + + len = vp2data_any(packet, original, 0, pvp, + ptr + dv->type + dv->length, room); + if (len < 0) return len; + + if (dv->length) ptr[dv->type + dv->length - 1] += len; + + return dv->type + dv->length + len; +} + + +/* + * Encode a Vendor-Specific attribute. + */ +ssize_t nr_vp2vsa(const RADIUS_PACKET *packet, const RADIUS_PACKET *original, + const VALUE_PAIR **pvp, uint8_t *ptr, + size_t room) +{ + ssize_t len; + uint32_t lvalue; + const VALUE_PAIR *vp = *pvp; + +#ifdef VENDORPEC_WIMAX + /* + * Double-check for WiMAX + */ + if (vp->da->vendor == VENDORPEC_WIMAX) { + return nr_vp2wimax(packet, original, pvp, + ptr, room); + } +#endif + + if (vp->da->vendor > NR_MAX_VENDOR) { + nr_debug_error("nr_vp2vsa: Invalid arguments"); + return -NR_ERR_INVALID_ARG; + } + + /* + * Not enough room for: + * attr, len, vendor-id + */ + if (room < 6) { + *pvp = vp->next; + return 0; + } + + /* + * Build the Vendor-Specific header + */ + ptr[0] = PW_VENDOR_SPECIFIC; + ptr[1] = 6; + lvalue = htonl(vp->da->vendor); + memcpy(ptr + 2, &lvalue, 4); + + if (room > ((unsigned) 255 - ptr[1])) room = 255 - ptr[1]; + + len = vp2attr_vsa(packet, original, pvp, + vp->da->attr, vp->da->vendor, + ptr + ptr[1], room); + if (len < 0) return len; + + ptr[1] += len; + + return ptr[1]; +} +#endif + + +/* + * Encode an RFC standard attribute 1..255 + */ +ssize_t nr_vp2rfc(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const VALUE_PAIR **pvp, + uint8_t *ptr, size_t room) +{ + const VALUE_PAIR *vp = *pvp; + + if (vp->da->vendor != 0) { + nr_debug_error("nr_vp2rfc called with VSA"); + return -NR_ERR_INVALID_ARG; + } + + if ((vp->da->attr == 0) || (vp->da->attr > 255)) { + nr_debug_error("nr_vp2rfc called with non-standard attribute %u", vp->da->attr); + return -NR_ERR_INVALID_ARG; + } + +#ifdef PW_CHARGEABLE_USER_IDENTITY + if ((vp->length == 0) && + (vp->da != NR_DA_CHARGEABLE_USER_IDENTITY)) { + *pvp = vp->next; + return 0; + } +#endif + + return vp2attr_rfc(packet, original, pvp, vp->da->attr, + ptr, room); +} + +#ifdef PW_CHAP_PASSWORD +/* + * Encode an RFC standard attribute 1..255 + */ +static ssize_t nr_chap2rfc(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const VALUE_PAIR **pvp, + uint8_t *ptr, size_t room) +{ + ssize_t rcode; + const VALUE_PAIR *vp = *pvp; + NR_MD5_CTX ctx; + uint8_t buffer[MAX_STRING_LEN*2 + 1], *p; + VALUE_PAIR chap = { + NR_DA_CHAP_PASSWORD, + 17, + 0, + NULL, + { + .octets = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + }, + }; + + if ((vp->da->vendor != 0) || (vp->da != NR_DA_CHAP_PASSWORD)) { + nr_debug_error("nr_chap2rfc called with non-CHAP"); + return -NR_ERR_INVALID_ARG; + } + + p = buffer; + *(p++) = nr_rand() & 0xff; /* id */ + + memcpy(p, vp->vp_strvalue, strlen(vp->vp_strvalue)); + p += strlen(vp->vp_strvalue); + + vp = nr_vps_find(packet->vps, PW_CHAP_CHALLENGE, 0); + if (vp) { + memcpy(p, vp->vp_octets, vp->length); + p += vp->length; + } else { + memcpy(p, packet->vector, sizeof(packet->vector)); + p += sizeof(packet->vector); + } + + nr_MD5Init(&ctx); + nr_MD5Update(&ctx, buffer, p - buffer); + nr_MD5Final(&chap.vp_octets[1], &ctx); + + chap.vp_octets[0] = buffer[0]; + vp = &chap; + + rcode = vp2attr_rfc(packet, original, &vp, chap.da->attr, + ptr, room); + if (rcode < 0) return rcode; + + *pvp = (*pvp)->next; + return rcode; +} +#endif /* PW_CHAP_PASSWORD */ + +#ifdef PW_MESSAGE_AUTHENTICATOR +/** Fake Message-Authenticator. + * + * This structure is used to replace a Message-Authenticator in the + * input list of VALUE_PAIRs when encoding a packet. If the caller + * asks us to encode a Message-Authenticator, we ignore the one given + * to us by the caller (which may have the wrong length, etc.), and + * instead use this one, which has the correct length and data. + */ +static const VALUE_PAIR fake_ma = { + NR_DA_MESSAGE_AUTHENTICATOR, + 16, + 0, + NULL, + { + .octets = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + } +}; +#endif /* PW_MESSAGE_AUTHENTICATOR */ + +/* + * Parse a data structure into a RADIUS attribute. + */ +ssize_t nr_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original, + const VALUE_PAIR **pvp, uint8_t *start, + size_t room) +{ + const VALUE_PAIR *vp = *pvp; + + /* + * RFC format attributes take the fast path. + */ + if (vp->da->vendor != 0) { +#ifdef VENDORPEC_EXTENDED + if (vp->da->vendor > NR_MAX_VENDOR) { + return nr_vp2attr_extended(packet, original, + pvp, start, room); + + } +#endif + +#ifdef VENDORPEC_WIMAX + if (vp->da->vendor == VENDORPEC_WIMAX) { + return nr_vp2attr_wimax(packet, original, + pvp, start, room); + } +#endif + +#ifndef WITHOUT_VSAS + return nr_vp2vsa(packet, original, pvp, start, room); +#else + nr_debug_error("VSAs are not supported"); + return -NR_ERR_UNSUPPORTED; +#endif + } + + /* + * Ignore non-protocol attributes. + */ + if (vp->da->attr > 255) { + *pvp = vp->next; + return 0; + } + +#ifdef PW_MESSAGE_AUTHENTICATOR + /* + * The caller wants a Message-Authenticator, but doesn't + * know how to calculate it, or what the correct values + * are. So... create one for him. + */ + if (vp->da == NR_DA_MESSAGE_AUTHENTICATOR) { + ssize_t rcode; + + vp = &fake_ma; + rcode = nr_vp2rfc(packet, original, &vp, start, room); + if (rcode <= 0) return rcode; + *pvp = (*pvp)->next; + return rcode; + } +#endif + +#ifdef PW_CHAP_PASSWORD + /* + * The caller wants a CHAP-Password, but doesn't know how + * to calculate it, or what the correct values are. To + * help, we calculate it for him. + */ + if (vp->da == NR_DA_CHAP_PASSWORD) { + int encoded = 0; + + /* + * CHAP is ID + MD5(...). If it's length is NOT + * 17, then the caller has passed us a password, + * and wants us to encode it. If the length IS + * 17, then we need to double-check if the caller + * has already encoded it. + */ + if (vp->length == 17) { + int i; + + /* + * ASCII and UTF-8 disallow values 0..31. + * If they appear, then the CHAP-Password + * has already been encoded by the + * caller. The probability of a + * CHAP-Password being all 32..256 is + * (1-32/256)^17 =~ .10 + * + * This check isn't perfect, but it + * should be pretty rare for people to + * have 17-character passwords *and* have + * them all 32..256. + */ + for (i = 0; i < 17; i++) { + if (vp->vp_octets[i] < 32) { + encoded = 1; + break; + } + } + } + + if (!encoded) { + return nr_chap2rfc(packet, original, pvp, start, room); + } + } +#endif + + return nr_vp2rfc(packet, original, pvp, + start, room); +} + + +/* + * Ignore unknown attributes, but "decoding" them into nothing. + */ +static ssize_t data2vp_raw(UNUSED const RADIUS_PACKET *packet, + UNUSED const RADIUS_PACKET *original, + unsigned int attribute, + unsigned int vendor, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ + VALUE_PAIR *vp; + + if (length > sizeof(vp->vp_octets)) return -NR_ERR_ATTR_OVERFLOW; + + vp = nr_vp_alloc_raw(attribute, vendor); + if (!vp) return -NR_ERR_NO_MEM; + + memcpy(vp->vp_octets, data, length); + vp->length = length; + + *pvp = vp; + return length; +} + +ssize_t nr_attr2vp_raw(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ + + if (length < 2) return -NR_ERR_PACKET_TOO_SMALL; + if (data[1] < 2) return -NR_ERR_ATTR_TOO_SMALL; + if (data[1] > length) return -NR_ERR_ATTR_OVERFLOW; + + return data2vp_raw(packet, original, data[0], 0, + data + 2, data[1] - 2, pvp); +} + +/* + * Create any kind of VP from the attribute contents. + * + * Will return -1 on error, or "length". + */ +static ssize_t data2vp_any(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + int nest, + unsigned int attribute, unsigned int vendor, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ +#ifdef FLAG_ENCRYPT_TUNNEL_PASSWORD + ssize_t rcode; +#endif + int data_offset = 0; + const DICT_ATTR *da; + VALUE_PAIR *vp = NULL; + + if (length == 0) { + /* + * Hacks for CUI. The WiMAX spec says that it + * can be zero length, even though this is + * forbidden by the RADIUS specs. So... we make + * a special case for it. + */ + if ((vendor == 0) && + (attribute == PW_CHARGEABLE_USER_IDENTITY)) { + data = (const uint8_t *) ""; + length = 1; + } else { + *pvp = NULL; + return 0; + } + } + + da = nr_dict_attr_byvalue(attribute, vendor); + + /* + * Unknown attribute. Create it as a "raw" attribute. + */ + if (!da) { + raw: + if (vp) nr_vp_free(&vp); + return data2vp_raw(packet, original, + attribute, vendor, data, length, pvp); + } + +#ifdef NR_TYPE_TLV + /* + * TLVs are handled first. They can't be tagged, and + * they can't be encrypted. + */ + if (da->da->type == NR_TYPE_TLV) { + return data2vp_tlvs(packet, original, + attribute, vendor, nest, + data, length, pvp); + } +#else + nest = nest; /* -Wunused */ +#endif + + /* + * The attribute is known, and well formed. We can now + * create it. The main failure from here on in is being + * out of memory. + */ + vp = nr_vp_alloc(da); + if (!vp) return -NR_ERR_NO_MEM; + + /* + * Handle tags. + */ + if (vp->da->flags.has_tag) { + if (TAG_VALID(data[0]) +#ifdef FLAG_ENCRYPT_TUNNEL_PASSWORD + || (vp->da->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD) +#endif + ) { + /* + * Tunnel passwords REQUIRE a tag, even + * if don't have a valid tag. + */ + vp->tag = data[0]; + + if ((vp->da->type == NR_TYPE_STRING) || + (vp->da->type == NR_TYPE_OCTETS)) { + if (length == 0) goto raw; + data_offset = 1; + } + } + } + + /* + * Copy the data to be decrypted + */ + vp->length = length - data_offset; + memcpy(&vp->vp_octets[0], data + data_offset, vp->length); + + /* + * Decrypt the attribute. + */ + switch (vp->da->flags.encrypt) { + /* + * User-Password + */ + case FLAG_ENCRYPT_USER_PASSWORD: + if (original) { + rcode = nr_password_encrypt(vp->vp_octets, + sizeof(vp->vp_strvalue), + data + data_offset, vp->length, + packet->secret, + original->vector); + } else { + rcode = nr_password_encrypt(vp->vp_octets, + sizeof(vp->vp_strvalue), + data + data_offset, vp->length, + packet->secret, + packet->vector); + } + if (rcode < 0) goto raw; + vp->vp_strvalue[128] = '\0'; + vp->length = strlen(vp->vp_strvalue); + break; + + /* + * Tunnel-Password's may go ONLY + * in response packets. + */ +#ifdef FLAG_ENCRYPT_TUNNEL_PASSWORD + case FLAG_ENCRYPT_TUNNEL_PASSWORD: + if (!original) goto raw; + + rcode = nr_tunnelpw_decrypt(vp->vp_octets, + sizeof(vp->vp_octets), + data + data_offset, vp->length, + packet->secret, original->vector); + if (rcode < 0) goto raw; + vp->length = rcode; + break; +#endif + + +#ifdef FLAG_ENCRYPT_ASCEND_SECRET: + /* + * Ascend-Send-Secret + * Ascend-Receive-Secret + */ + case FLAG_ENCRYPT_ASCEND_SECRET: + if (!original) { + goto raw; + } else { + uint8_t my_digest[AUTH_VECTOR_LEN]; + make_secret(my_digest, + original->vector, + packet->secret, data); + memcpy(vp->vp_strvalue, my_digest, + AUTH_VECTOR_LEN ); + vp->vp_strvalue[AUTH_VECTOR_LEN] = '\0'; + vp->length = strlen(vp->vp_strvalue); + } + break; +#endif + + default: + break; + } /* switch over encryption flags */ + + /* + * Expected a certain length, but got something else. + */ + if ((vp->da->flags.length != 0) && + (vp->length != vp->da->flags.length)) { + goto raw; + } + + switch (vp->da->type) { + case NR_TYPE_STRING: + case NR_TYPE_OCTETS: +#ifdef NR_TYPE_ABINARY + case NR_TYPE_ABINARY: +#endif + /* nothing more to do */ + break; + + case NR_TYPE_BYTE: + vp->vp_integer = vp->vp_octets[0]; + break; + + + case NR_TYPE_SHORT: + vp->vp_integer = (vp->vp_octets[0] << 8) | vp->vp_octets[1]; + break; + + case NR_TYPE_INTEGER: + memcpy(&vp->vp_integer, vp->vp_octets, 4); + vp->vp_integer = ntohl(vp->vp_integer); + + if (vp->da->flags.has_tag) vp->vp_integer &= 0x00ffffff; + break; + + case NR_TYPE_DATE: + memcpy(&vp->vp_date, vp->vp_octets, 4); + vp->vp_date = ntohl(vp->vp_date); + break; + + + case NR_TYPE_IPADDR: + memcpy(&vp->vp_ipaddr, vp->vp_octets, 4); + break; + + /* + * IPv6 interface ID is 8 octets long. + */ + case NR_TYPE_IFID: + /* vp->vp_ifid == vp->vp_octets */ + break; + + /* + * IPv6 addresses are 16 octets long + */ + case NR_TYPE_IPV6ADDR: + /* vp->vp_ipv6addr == vp->vp_octets */ + break; + + /* + * IPv6 prefixes are 2 to 18 octets long. + * + * RFC 3162: The first octet is unused. + * The second is the length of the prefix + * the rest are the prefix data. + * + * The prefix length can have value 0 to 128. + */ + case NR_TYPE_IPV6PREFIX: + if (vp->length < 2 || vp->length > 18) goto raw; + if (vp->vp_octets[1] > 128) goto raw; + + /* + * FIXME: double-check that + * (vp->vp_octets[1] >> 3) matches vp->length + 2 + */ + if (vp->length < 18) { + memset(vp->vp_octets + vp->length, 0, + 18 - vp->length); + } + break; + +#ifdef VENDORPEC_WIMAX + case NR_TYPE_SIGNED: + if (vp->length != 4) goto raw; + + /* + * Overload vp_integer for ntohl, which takes + * uint32_t, not int32_t + */ + memcpy(&vp->vp_integer, vp->vp_octets, 4); + vp->vp_integer = ntohl(vp->vp_integer); + memcpy(&vp->vp_signed, &vp->vp_integer, 4); + break; +#endif + +#ifdef NR_TYPE_TLV + case NR_TYPE_TLV: + nr_vp_free(&vp); + nr_debug_error("data2vp_any: Internal sanity check failed"); + return -NR_ERR_ATTR_TYPE_UNKNOWN; +#endif + +#ifdef VENDORPEC_WIMAX + case NR_TYPE_COMBO_IP: + if (vp->length == 4) { + vp->da->type = NR_TYPE_IPADDR; + memcpy(&vp->vp_ipaddr, vp->vp_octets, 4); + break; + + } else if (vp->length == 16) { + vp->da->type = NR_TYPE_IPV6ADDR; + /* vp->vp_ipv6addr == vp->vp_octets */ + break; + + } + /* FALL-THROUGH */ +#endif + + default: + goto raw; + } + + *pvp = vp; + + return length; +} + + +/* + * Create a "standard" RFC VALUE_PAIR from the given data. + */ +ssize_t nr_attr2vp_rfc(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ + ssize_t rcode; + + if (length < 2) return -NR_ERR_PACKET_TOO_SMALL; + if (data[1] < 2) return -NR_ERR_ATTR_TOO_SMALL; + if (data[1] > length) return -NR_ERR_ATTR_OVERFLOW; + + rcode = data2vp_any(packet, original, 0, + data[0], 0, data + 2, data[1] - 2, pvp); + if (rcode < 0) return rcode; + + return data[1]; +} + +#ifndef WITHOUT_VSAS +/* + * Check if a set of RADIUS formatted TLVs are OK. + */ +int nr_tlv_ok(const uint8_t *data, size_t length, + size_t dv_type, size_t dv_length) +{ + const uint8_t *end = data + length; + + if ((dv_length > 2) || (dv_type == 0) || (dv_type > 4)) { + nr_debug_error("nr_tlv_ok: Invalid arguments"); + return -NR_ERR_INVALID_ARG; + } + + while (data < end) { + size_t attrlen; + + if ((data + dv_type + dv_length) > end) { + nr_debug_error("Attribute header overflow"); + return -NR_ERR_ATTR_TOO_SMALL; + } + + switch (dv_type) { + case 4: + if ((data[0] == 0) && (data[1] == 0) && + (data[2] == 0) && (data[3] == 0)) { + zero: + nr_debug_error("Invalid attribute 0"); + return -NR_ERR_ATTR_INVALID; + } + + if (data[0] != 0) { + nr_debug_error("Invalid attribute > 2^24"); + return -NR_ERR_ATTR_INVALID; + } + break; + + case 2: + if ((data[1] == 0) && (data[1] == 0)) goto zero; + break; + + case 1: + if (data[0] == 0) goto zero; + break; + + default: + nr_debug_error("Internal sanity check failed"); + return -NR_ERR_INTERNAL_FAILURE; + } + + switch (dv_length) { + case 0: + return 0; + + case 2: + if (data[dv_type + 1] != 0) { + nr_debug_error("Attribute is longer than 256 octets"); + return -NR_ERR_ATTR_TOO_LARGE; + } + /* FALL-THROUGH */ + case 1: + attrlen = data[dv_type + dv_length - 1]; + break; + + + default: + nr_debug_error("Internal sanity check failed"); + return -NR_ERR_INTERNAL_FAILURE; + } + + if (attrlen < (dv_type + dv_length)) { + nr_debug_error("Attribute header has invalid length"); + return -NR_ERR_PACKET_TOO_SMALL; + } + + if (attrlen > length) { + nr_debug_error("Attribute overflows container"); + return -NR_ERR_ATTR_OVERFLOW; + } + + data += attrlen; + length -= attrlen; + } + + return 0; +} + + +/* + * Convert a top-level VSA to a VP. + */ +static ssize_t attr2vp_vsa(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + unsigned int vendor, + size_t dv_type, size_t dv_length, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ + unsigned int attribute; + ssize_t attrlen, my_len; + +#ifndef NDEBUG + if (length <= (dv_type + dv_length)) { + nr_debug_error("attr2vp_vsa: Failure to call nr_tlv_ok"); + return -NR_ERR_PACKET_TOO_SMALL; + } +#endif + + switch (dv_type) { + case 4: + /* data[0] must be zero */ + attribute = data[1] << 16; + attribute |= data[2] << 8; + attribute |= data[3]; + break; + + case 2: + attribute = data[0] << 8; + attribute |= data[1]; + break; + + case 1: + attribute = data[0]; + break; + + default: + nr_debug_error("attr2vp_vsa: Internal sanity check failed"); + return -NR_ERR_INTERNAL_FAILURE; + } + + switch (dv_length) { + case 2: + /* data[dv_type] must be zero */ + attrlen = data[dv_type + 1]; + break; + + case 1: + attrlen = data[dv_type]; + break; + + case 0: + attrlen = length; + break; + + default: + nr_debug_error("attr2vp_vsa: Internal sanity check failed"); + return -NR_ERR_INTERNAL_FAILURE; + } + +#ifndef NDEBUG + if (attrlen <= (ssize_t) (dv_type + dv_length)) { + nr_debug_error("attr2vp_vsa: Failure to call nr_tlv_ok"); + return -NR_ERR_PACKET_TOO_SMALL; + } +#endif + + attrlen -= (dv_type + dv_length); + + my_len = data2vp_any(packet, original, 0, + attribute, vendor, + data + dv_type + dv_length, attrlen, pvp); + if (my_len < 0) return my_len; + +#ifndef NDEBUG + if (my_len != attrlen) { + nr_vp_free(pvp); + nr_debug_error("attr2vp_vsa: Incomplete decode %d != %d", + (int) my_len, (int) attrlen); + return -NR_ERR_INTERNAL_FAILURE; + } +#endif + + return dv_type + dv_length + attrlen; +} + + +/* + * Create Vendor-Specifc VALUE_PAIRs from a RADIUS attribute. + */ +ssize_t nr_attr2vp_vsa(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ + size_t dv_type, dv_length; + ssize_t my_len; + uint32_t lvalue; + const DICT_VENDOR *dv; + + if (length < 2) return -NR_ERR_PACKET_TOO_SMALL; + if (data[1] < 2) return -NR_ERR_ATTR_TOO_SMALL; + if (data[1] > length) return -NR_ERR_ATTR_OVERFLOW; + + if (data[0] != PW_VENDOR_SPECIFIC) { + nr_debug_error("nr_attr2vp_vsa: Invalid attribute"); + return -NR_ERR_INVALID_ARG; + } + + /* + * Not enough room for a Vendor-Id. + * Or the high octet of the Vendor-Id is set. + */ + if ((data[1] < 6) || (data[2] != 0)) { + return nr_attr2vp_raw(packet, original, + data, length, pvp); + } + + memcpy(&lvalue, data + 2, 4); + lvalue = ntohl(lvalue); + +#ifdef VENDORPEC_WIMAX + /* + * WiMAX gets its own set of magic. + */ + if (lvalue == VENDORPEC_WIMAX) { + return nr_attr2vp_wimax(packet, original, + data, length, pvp); + } +#endif + + dv_type = dv_length = 1; + dv = nr_dict_vendor_byvalue(lvalue); + if (!dv) { + return nr_attr2vp_rfc(packet, original, + data, length, pvp); + } + + dv_type = dv->type; + dv_length = dv->length; + + /* + * Attribute is not in the correct form. + */ + if (nr_tlv_ok(data + 6, data[1] - 6, dv_type, dv_length) < 0) { + return nr_attr2vp_raw(packet, original, + data, length, pvp); + } + + my_len = attr2vp_vsa(packet, original, + lvalue, dv_type, dv_length, + data + 6, data[1] - 6, pvp); + if (my_len < 0) return my_len; + +#ifndef NDEBUG + if (my_len != (data[1] - 6)) { + nr_vp_free(pvp); + nr_debug_error("nr_attr2vp_vsa: Incomplete decode"); + return -NR_ERR_INTERNAL_FAILURE; + } +#endif + + return data[1]; +} +#endif /* WITHOUT_VSAS */ + + +/* + * Create a "normal" VALUE_PAIR from the given data. + */ +ssize_t nr_attr2vp(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, + const uint8_t *data, size_t length, + VALUE_PAIR **pvp) +{ + if (length < 2) return -NR_ERR_PACKET_TOO_SMALL; + if (data[1] < 2) return -NR_ERR_ATTR_TOO_SMALL; + if (data[1] > length) return -NR_ERR_ATTR_OVERFLOW; + +#ifndef WITHOUT_VSAS + /* + * VSAs get their own handler. + */ + if (data[0] == PW_VENDOR_SPECIFIC) { + return nr_attr2vp_vsa(packet, original, + data, length, pvp); + } +#endif + +#ifdef VENDORPEC_EXTENDED + /* + * Extended attribute format gets their own handler. + */ + if (nr_dict_attr_byvalue(data[0], VENDORPEC_EXTENDED) != NULL) { + return nr_attr2vp_extended(packet, original, + data, length, pvp); + } +#endif + + return nr_attr2vp_rfc(packet, original, data, length, pvp); +} + +ssize_t nr_attr2data(const RADIUS_PACKET *packet, ssize_t start, + unsigned int attribute, unsigned int vendor, + const uint8_t **pdata, size_t *plength) +{ + uint8_t *data, *attr; + const uint8_t *end; + + if (!packet || !pdata || !plength) return -NR_ERR_INVALID_ARG; + + if (!packet->data) return -NR_ERR_INVALID_ARG; + if (packet->length < 20) return -NR_ERR_INVALID_ARG; + + /* + * Too long or short, not good. + */ + if ((start < 0) || + ((start > 0) && (start < 20))) return -NR_ERR_INVALID_ARG; + + if ((size_t) start >= (packet->length - 2)) return -NR_ERR_INVALID_ARG; + + end = packet->data + packet->length; + + /* + * Loop over the packet, converting attrs to VPs. + */ + if (start == 0) { + data = packet->data + 20; + } else { + data = packet->data + start; + data += data[1]; + if (data >= end) return 0; + } + + for (attr = data; attr < end; attr += attr[1]) { + const DICT_VENDOR *dv = NULL; + +#ifndef NEBUG + /* + * This code is copied from packet_ok(). + * It could be put into a separate function. + */ + if ((attr + 2) > end) { + nr_debug_error("Attribute overflows packet"); + return -NR_ERR_ATTR_OVERFLOW; + } + + if (attr[1] < 2) { + nr_debug_error("Attribute length is too small"); + return -NR_ERR_ATTR_TOO_SMALL; + } + + if ((attr + attr[1]) > end) { + nr_debug_error("Attribute length is too large"); + return -NR_ERR_ATTR_TOO_LARGE; + } +#endif + + if ((vendor == 0) && (attr[0] == attribute)) { + *pdata = attr + 2; + *plength = attr[1] - 2; + return attr - packet->data; + } + +#ifndef WITHOUT_VSAS + if (vendor != 0) { + uint32_t vendorpec; + + if (attr[0] != PW_VENDOR_SPECIFIC) continue; + + if (attr[1] < 6) continue; + + memcpy(&vendorpec, attr + 2, 4); + vendorpec = ntohl(vendorpec); + if (vendor != vendorpec) continue; + + if (!dv) { + dv = nr_dict_vendor_byvalue(vendor); + if (dv && + ((dv->type != 1) || (dv->length != 1))) { + return -NR_ERR_VENDOR_UNKNOWN; + } + } + + /* + * No data. + */ + if (attr[1] < 9) continue; + + /* + * Malformed, or more than one VSA in + * the Vendor-Specific + */ + if (attr[7] + 6 != attr[1]) continue; + + /* + * Not the right VSA. + */ + if (attr[6] != attribute) continue; + + *pdata = attr + 8; + *plength = attr[1] - 8; + return attr - packet->data; + } +#endif + } + + return 0; /* nothing more: stop */ +} + -- cgit v1.1