summaryrefslogtreecommitdiff
path: root/radius/attrs.c
diff options
context:
space:
mode:
Diffstat (limited to 'radius/attrs.c')
-rw-r--r--radius/attrs.c1411
1 files changed, 1411 insertions, 0 deletions
diff --git a/radius/attrs.c b/radius/attrs.c
new file mode 100644
index 0000000..21cd3f0
--- /dev/null
+++ b/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 <organization> 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 <COPYRIGHT HOLDER> 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 "client.h"
+
+/*
+ * 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 RS_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 RS_TYPE_IPV6PREFIX:
+ len = sizeof(vp->vp_ipv6prefix);
+ break;
+
+ case RS_TYPE_STRING:
+ case RS_TYPE_OCTETS:
+ case RS_TYPE_IFID:
+ case RS_TYPE_IPV6ADDR:
+#ifdef RS_TYPE_ABINARY
+ case RS_TYPE_ABINARY:
+#endif
+ /* nothing more to do */
+ break;
+
+ case RS_TYPE_BYTE:
+ len = 1; /* just in case */
+ array[0] = vp->vp_integer & 0xff;
+ data = array;
+ break;
+
+ case RS_TYPE_SHORT:
+ len = 2; /* just in case */
+ array[0] = (vp->vp_integer >> 8) & 0xff;
+ array[1] = vp->vp_integer & 0xff;
+ data = array;
+ break;
+
+ case RS_TYPE_INTEGER:
+ len = 4; /* just in case */
+ lvalue = htonl(vp->vp_integer);
+ memcpy(array, &lvalue, sizeof(lvalue));
+ data = array;
+ break;
+
+ case RS_TYPE_IPADDR:
+ data = (const uint8_t *) &vp->vp_ipaddr;
+ len = 4; /* just in case */
+ break;
+
+ /*
+ * There are no tagged date attributes.
+ */
+ case RS_TYPE_DATE:
+ lvalue = htonl(vp->vp_date);
+ data = (const uint8_t *) &lvalue;
+ len = 4; /* just in case */
+ break;
+
+#ifdef VENDORPEC_WIMAX
+ case RS_TYPE_SIGNED:
+ {
+ int32_t slvalue;
+
+ len = 4; /* just in case */
+ slvalue = htonl(vp->vp_signed);
+ memcpy(array, &slvalue, sizeof(slvalue));
+ break;
+ }
+#endif
+
+#ifdef RS_TYPE_TLV
+ case RS_TYPE_TLV:
+ data = vp->vp_tlv;
+ if (!data) {
+ nr_debug_error("ERROR: Cannot encode NULL TLV");
+ return -RSE_INVAL;
+ }
+ len = vp->length;
+ break;
+#endif
+
+ default: /* unknown type: ignore it */
+ nr_debug_error("ERROR: Unknown attribute type %d", vp->da->type);
+ return -RSE_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 -RSE_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 == RS_TYPE_STRING) {
+ if (len > ((ssize_t) (room - 1))) len = room - 1;
+ ptr[0] = vp->tag;
+ ptr++;
+ } else if (vp->da->type == RS_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 RS_TYPE_TLV
+ !(*pvp)->flags.is_tlv &&
+#endif
+ (dv->type == 1) && (dv->length == 1))) {
+ return vp2attr_rfc(packet, original, pvp,
+ attribute, ptr, room);
+ }
+
+#ifdef RS_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 -RSE_INTERNAL;
+
+ 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 -RSE_INTERNAL;
+
+ 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 > RS_MAX_VENDOR) {
+ nr_debug_error("nr_vp2vsa: Invalid arguments");
+ return -RSE_INVAL;
+ }
+
+ /*
+ * 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 -RSE_INVAL;
+ }
+
+ if ((vp->da->attr == 0) || (vp->da->attr > 255)) {
+ nr_debug_error("nr_vp2rfc called with non-standard attribute %u", vp->da->attr);
+ return -RSE_INVAL;
+ }
+
+#ifdef PW_CHARGEABLE_USER_IDENTITY
+ if ((vp->length == 0) &&
+ (vp->da != RS_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;
+ RS_MD5_CTX ctx;
+ uint8_t buffer[RS_MAX_STRING_LEN*2 + 1], *p;
+ VALUE_PAIR chap = {
+ RS_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 != RS_DA_CHAP_PASSWORD)) {
+ nr_debug_error("nr_chap2rfc called with non-CHAP");
+ return -RSE_INVAL;
+ }
+
+ 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);
+ }
+
+ RS_MD5Init(&ctx);
+ RS_MD5Update(&ctx, buffer, p - buffer);
+ RS_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 = {
+ RS_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 > RS_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 -RSE_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 == RS_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 == RS_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 -RSE_ATTR_OVERFLOW;
+
+ vp = nr_vp_alloc_raw(attribute, vendor);
+ if (!vp) return -RSE_NOMEM;
+
+ 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 -RSE_PACKET_TOO_SMALL;
+ if (data[1] < 2) return -RSE_ATTR_TOO_SMALL;
+ if (data[1] > length) return -RSE_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 RS_TYPE_TLV
+ /*
+ * TLVs are handled first. They can't be tagged, and
+ * they can't be encrypted.
+ */
+ if (da->da->type == RS_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 -RSE_NOMEM;
+
+ /*
+ * 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 == RS_TYPE_STRING) ||
+ (vp->da->type == RS_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 RS_TYPE_STRING:
+ case RS_TYPE_OCTETS:
+#ifdef RS_TYPE_ABINARY
+ case RS_TYPE_ABINARY:
+#endif
+ /* nothing more to do */
+ break;
+
+ case RS_TYPE_BYTE:
+ vp->vp_integer = vp->vp_octets[0];
+ break;
+
+
+ case RS_TYPE_SHORT:
+ vp->vp_integer = (vp->vp_octets[0] << 8) | vp->vp_octets[1];
+ break;
+
+ case RS_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 RS_TYPE_DATE:
+ memcpy(&vp->vp_date, vp->vp_octets, 4);
+ vp->vp_date = ntohl(vp->vp_date);
+ break;
+
+
+ case RS_TYPE_IPADDR:
+ memcpy(&vp->vp_ipaddr, vp->vp_octets, 4);
+ break;
+
+ /*
+ * IPv6 interface ID is 8 octets long.
+ */
+ case RS_TYPE_IFID:
+ /* vp->vp_ifid == vp->vp_octets */
+ break;
+
+ /*
+ * IPv6 addresses are 16 octets long
+ */
+ case RS_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 RS_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 RS_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 RS_TYPE_TLV
+ case RS_TYPE_TLV:
+ nr_vp_free(&vp);
+ nr_debug_error("data2vp_any: Internal sanity check failed");
+ return -RSE_ATTR_TYPE_UNKNOWN;
+#endif
+
+#ifdef VENDORPEC_WIMAX
+ case RS_TYPE_COMBO_IP:
+ if (vp->length == 4) {
+ vp->da->type = RS_TYPE_IPADDR;
+ memcpy(&vp->vp_ipaddr, vp->vp_octets, 4);
+ break;
+
+ } else if (vp->length == 16) {
+ vp->da->type = RS_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 -RSE_PACKET_TOO_SMALL;
+ if (data[1] < 2) return -RSE_ATTR_TOO_SMALL;
+ if (data[1] > length) return -RSE_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 -RSE_INVAL;
+ }
+
+ while (data < end) {
+ size_t attrlen;
+
+ if ((data + dv_type + dv_length) > end) {
+ nr_debug_error("Attribute header overflow");
+ return -RSE_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 -RSE_ATTR_INVALID;
+ }
+
+ if (data[0] != 0) {
+ nr_debug_error("Invalid attribute > 2^24");
+ return -RSE_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 -RSE_INTERNAL;
+ }
+
+ 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 -RSE_ATTR_TOO_LARGE;
+ }
+ /* FALL-THROUGH */
+ case 1:
+ attrlen = data[dv_type + dv_length - 1];
+ break;
+
+
+ default:
+ nr_debug_error("Internal sanity check failed");
+ return -RSE_INTERNAL;
+ }
+
+ if (attrlen < (dv_type + dv_length)) {
+ nr_debug_error("Attribute header has invalid length");
+ return -RSE_PACKET_TOO_SMALL;
+ }
+
+ if (attrlen > length) {
+ nr_debug_error("Attribute overflows container");
+ return -RSE_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 -RSE_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 -RSE_INTERNAL;
+ }
+
+ 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 -RSE_INTERNAL;
+ }
+
+#ifndef NDEBUG
+ if (attrlen <= (ssize_t) (dv_type + dv_length)) {
+ nr_debug_error("attr2vp_vsa: Failure to call nr_tlv_ok");
+ return -RSE_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 -RSE_INTERNAL;
+ }
+#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 -RSE_PACKET_TOO_SMALL;
+ if (data[1] < 2) return -RSE_ATTR_TOO_SMALL;
+ if (data[1] > length) return -RSE_ATTR_OVERFLOW;
+
+ if (data[0] != PW_VENDOR_SPECIFIC) {
+ nr_debug_error("nr_attr2vp_vsa: Invalid attribute");
+ return -RSE_INVAL;
+ }
+
+ /*
+ * 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 -RSE_INTERNAL;
+ }
+#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 -RSE_PACKET_TOO_SMALL;
+ if (data[1] < 2) return -RSE_ATTR_TOO_SMALL;
+ if (data[1] > length) return -RSE_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 -RSE_INVAL;
+
+ if (!packet->data) return -RSE_INVAL;
+ if (packet->length < 20) return -RSE_INVAL;
+
+ /*
+ * Too long or short, not good.
+ */
+ if ((start < 0) ||
+ ((start > 0) && (start < 20))) return -RSE_INVAL;
+
+ if ((size_t) start >= (packet->length - 2)) return -RSE_INVAL;
+
+ 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 -RSE_ATTR_OVERFLOW;
+ }
+
+ if (attr[1] < 2) {
+ nr_debug_error("Attribute length is too small");
+ return -RSE_ATTR_TOO_SMALL;
+ }
+
+ if ((attr + attr[1]) > end) {
+ nr_debug_error("Attribute length is too large");
+ return -RSE_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 -RSE_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 */
+}
+