Fix logic for using udpfromto
[freeradius.git] / src / lib / radius.c
index 5333303..37757a8 100644 (file)
@@ -233,7 +233,7 @@ static int rad_sendto(int sockfd, void *data, size_t data_len, int flags,
         *      And if they don't specify a source IP address, don't
         *      use udpfromto.
         */
-       if ((dst_ipaddr->af == AF_INET) ||
+       if ((dst_ipaddr->af == AF_INET) &&
            (src_ipaddr->af != AF_UNSPEC)) {
                return sendfromto(sockfd, data, data_len, flags,
                                  (struct sockaddr *)&src, sizeof_src,
@@ -626,6 +626,7 @@ static void make_tunnel_passwd(uint8_t *output, size_t *outlen,
                }
 
                fr_MD5Final(digest, &context);
+
                for (i = 0; i < AUTH_PASS_LEN; i++) {
                        passwd[i + 2 + n] ^= digest[i];
                }
@@ -636,14 +637,14 @@ static void make_tunnel_passwd(uint8_t *output, size_t *outlen,
 /*
  *     Returns the end of the data.
  */
-static uint8_t *vp2data(const RADIUS_PACKET *packet,
-                       const RADIUS_PACKET *original,
-                       const char *secret, const VALUE_PAIR *vp, uint8_t *ptr,
-                       size_t room)
+static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
+                  const char *secret, const VALUE_PAIR *vp, uint8_t *start,
+                  size_t room)
 {
        uint32_t lvalue;
        size_t len;
        const uint8_t *data;
+       uint8_t *ptr = start;
        uint8_t array[4];
 
        /*
@@ -710,13 +711,14 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
                data = vp->vp_tlv;
                if (!data) {
                        fr_strerror_printf("ERROR: Cannot encode NULL TLV");
-                       return NULL;
+                       return -1;
                }
+               if (vp->length > room) return 0; /* can't chop TLVs to fit */
                break;
 
        default:                /* unknown type: ignore it */
                fr_strerror_printf("ERROR: Unknown attribute type %d", vp->type);
-               return NULL;
+               return -1;
        }
 
        /*
@@ -737,6 +739,9 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
                break;
 
        case FLAG_ENCRYPT_TUNNEL_PASSWORD:
+               lvalue = 0;
+               if (vp->flags.has_tag) lvalue = 1;
+
                /*
                 *      Check if there's enough room.  If there isn't,
                 *      we discard the attribute.
@@ -744,7 +749,7 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
                 *      This is ONLY a problem if we have multiple VSA's
                 *      in one Vendor-Specific, though.
                 */
-               if (room < 18) return ptr;
+               if (room < (18 + lvalue)) return 0;
 
                switch (packet->code) {
                case PW_AUTHENTICATION_ACK:
@@ -753,15 +758,19 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
                default:
                        if (!original) {
                                fr_strerror_printf("ERROR: No request packet, cannot encrypt %s attribute in the vp.", vp->name);
-                               return NULL;
+                               return -1;
                        }
-                       make_tunnel_passwd(ptr, &len, data, len, room,
+
+                       if (lvalue) ptr[0] = vp->flags.tag;
+                       make_tunnel_passwd(ptr + lvalue, &len, data, len,
+                                          room - lvalue,
                                           secret, original->vector);
                        break;
                case PW_ACCOUNTING_REQUEST:
                case PW_DISCONNECT_REQUEST:
                case PW_COA_REQUEST:
-                       make_tunnel_passwd(ptr, &len, data, len, room,
+                       ptr[0] = vp->flags.tag;
+                       make_tunnel_passwd(ptr + 1, &len, data, len - 1, room,
                                           secret, packet->vector);
                        break;
                }
@@ -778,227 +787,249 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
 
 
        default:
-               /*
-                *      Just copy the data over
-                */
+               if (vp->flags.has_tag && TAG_VALID(vp->flags.tag)) {
+                       if (vp->type == PW_TYPE_STRING) {
+                               if (len > (room - 1)) len = room - 1;
+                               ptr[0] = vp->flags.tag;
+                               ptr++;
+                       } else if (vp->type == PW_TYPE_INTEGER) {
+                               array[0] = vp->flags.tag;
+                       } /* else it can't be any other type */
+               }
                memcpy(ptr, data, len);
                break;
        } /* switch over encryption flags */
 
-       return ptr + len;
+       return len + (ptr - start);;
 }
 
 
-static VALUE_PAIR *rad_vp2tlv(VALUE_PAIR *vps)
+static int rad_vp2rfc(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     unsigned int attribute, uint8_t *ptr, size_t room)
 {
-       int maxattr = 0;
-       int length;
-       unsigned int attribute;
-       uint8_t *ptr, *end;
-       VALUE_PAIR *vp, *tlv;
+       int len;
 
-       attribute = vps->attribute & 0xffff00ff;
-       maxattr = vps->attribute & 0x0ff;
+       if (room < 2) return 0;
 
-       tlv = paircreate(attribute, PW_TYPE_TLV);
-       if (!tlv) return NULL;
+       ptr[0] = attribute & 0xff; /* NOT vp->attribute */
+       ptr[1] = 2;
 
-       tlv->length = 0;
-       for (vp = vps; vp != NULL; vp = vp->next) {
-               /*
-                *      Group the attributes ONLY until we see a
-                *      non-TLV attribute.
-                */
-               if (!vp->flags.is_tlv ||
-                   vp->flags.encoded ||
-                   (vp->flags.encrypt != FLAG_ENCRYPT_NONE) ||
-                   ((vp->attribute & 0xffff00ff) != attribute) ||
-                   ((vp->attribute & 0x0000ff00) <= maxattr)) {
-                       break;
-               }
+       if (room > (255 - ptr[1])) room = 255 - ptr[1];
+       len = vp2data(packet, original, secret, vp, ptr + 2, room);
+       if (len < 0) return len;
 
-               maxattr = vp->attribute & 0xff00;
-               tlv->length += vp->length + 2;
-       }
+       ptr[1] += len;
 
-       if (!tlv->length) {
-               pairfree(&tlv);
-               return NULL;
-       }
+       return ptr[1];
+}
 
-       tlv->vp_tlv = malloc(tlv->length);
-       if (!tlv->vp_tlv) {
-               pairfree(&tlv);
-               return NULL;
-       }
+extern int fr_wimax_max_tlv;
+extern int fr_wimax_shift[];
+extern int fr_wimax_mask[];
 
-       ptr = tlv->vp_tlv;
-       maxattr = vps->attribute & 0x0ff;
-       for (vp = vps; vp != NULL; vp = vp->next) {
-               if (!vp->flags.is_tlv ||
-                   vp->flags.encoded ||
-                   (vp->flags.encrypt != FLAG_ENCRYPT_NONE) ||
-                   ((vp->attribute & 0xffff00ff) != attribute) ||
-                   ((vp->attribute & 0x0000ff00) <= maxattr)) {
-                       break;
-               }
+static int tlv2data(const RADIUS_PACKET *packet,
+                   const RADIUS_PACKET *original,
+                   const char *secret, const VALUE_PAIR *vp,
+                   uint8_t *ptr, size_t room, int nest)
+{
+       int len;
 
-               maxattr = vp->attribute & 0xff00;
-               end = vp2data(NULL, NULL, NULL, vp, ptr + 2,
-                             tlv->vp_tlv + tlv->length - ptr);
-               if (!end) {
-                       vp->length = ptr - vp->vp_tlv;
-                       return tlv; /* should be a more serious error... */
-               }
+       if (nest > fr_wimax_max_tlv) return -1;
 
-               length = (end - ptr);
-               if (length > 255) return NULL;
+       if (room < 2) return 0;
+       room -= 2;
 
-               /*
-                *      Pack the attribute.
-                */
-               ptr[0] = (vp->attribute & 0xff00) >> 8;
-               ptr[1] = length;
+       ptr[0] = (vp->attribute >> fr_wimax_shift[nest]) & fr_wimax_mask[nest];
+       ptr[1] = 2;
 
-               ptr += ptr[1];
-               vp->flags.encoded = 1;
+       /*
+        *      No more nested TLVs: pack the data.
+        */
+       if ((nest == fr_wimax_max_tlv) ||
+           ((vp->attribute >> fr_wimax_shift[nest + 1]) == 0)) {
+               len = vp2data(packet, original, secret, vp, ptr + 2, room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr + 2, room,
+                              nest + 1);
        }
+       if (len <= 0) return len;
+
+       ptr[1] += len;
 
-       return tlv;
+       return ptr[1];
 }
 
-/*
- *     Pack data without any encryption.
- *     start == start of RADIUS attribute
- *     ptr   == continuation byte (i.e. one after length)
- */
-static int rad_vp2continuation(const VALUE_PAIR *vp, uint8_t *start,
-                              uint8_t *ptr)
+static int wimax2data(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     uint8_t *start, size_t room, uint8_t *ptr)
 {
-       size_t left, piece;
-       size_t hsize = (ptr - start);
-       uint8_t *this = start;
-       const uint8_t *data;
-       uint8_t header[16];
-       
+       int len;
+
        /*
-        *      If it's too long and marked as encrypted, ignore it.
+        *      Offsets to Vendor-Specific length, and to length of
+        *      WiMAX attribute.
         */
-       if (vp->flags.encrypt != FLAG_ENCRYPT_NONE) {
-               return 0;
-       }
-       
-       memcpy(header, start, hsize);
+#define VS_OFF (1)
+#define WM_OFF (7)
 
-       left = vp->length;
-       
-       switch (vp->type) {
-       case PW_TYPE_TLV:
-               data = vp->vp_tlv;
-               break;
+       if (room < 1) return 0;
+       room--;
 
-       case PW_TYPE_OCTETS:
-       case PW_TYPE_STRING:
-               data = vp->vp_octets;
-               break;
+       /*
+        *      Account for continuation bytes.  The caller has
+        *      already accounting for the continuation byte in the
+        *      Vendor-Specific "length" field.
+        */
+       start[WM_OFF]++;
+       *(ptr++) = 0;
 
-               /*
-                *      This is invalid.
-                */
-       default:
+       /*
+        *      Chop everything to fit in one attribute.
+        */
+       if (room > (255 - 9)) room = (255 - 9);
+
+       /*
+        *      The attribute contains TLVs that we have NOT decoded
+        *      properly, OR it contains TLV that the user has encoded
+        *      manually.  If it has no data, OR it's too long,
+        *      discard it.  We're not going to walk through its
+        *      contents trying to figure out how to chop it across
+        *      multiple continuations.
+        */
+       if (vp->flags.has_tlv && (!vp->vp_tlv || (vp->length > room))) {
                return 0;
        }
-       
-       while (left > 0) {
-               memcpy(this, header, hsize);
-               ptr = this + hsize;
-               
-               /*
-                *      254 to account for
-                *      continuation flag.
-                */
-               if (left > (254 - hsize)) {
-                       piece = 254 - hsize;
-                       *(ptr++) = 0x80;
-               } else {
-                       piece = left;
-                       *(ptr++) = 0x00;
-               }
-               
-               memcpy(ptr, data, piece);
-               this[1] = hsize + piece + 1;
 
-               /*
-                *      
-                */
-               this[hsize - 1] = hsize - 6 + 1 + piece;
-               data += piece;
-               ptr += piece;
-               left -= piece;
-               this = ptr;
-       }
-       
-       return (ptr - start);
+       /*
+        *      The attribute is a top-level integer, ipaddr, etc.
+        *      Encode it.
+        */
+       if (!vp->flags.is_tlv) {
+               len = vp2data(packet, original, secret, vp, ptr, room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr, room, 1);
+       }
+
+       if (len <= 0) return len;
+
+       start[VS_OFF] += len;
+       start[WM_OFF] += len;
+
+       return start[VS_OFF];
 }
 
 
+static int rad_vp2extended(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     unsigned int attribute, uint8_t *ptr, size_t room)
+{
+       int len = 2;
+
+       if (room < 3) return 0;
+
+       ptr[0] = attribute & 0xff; /* NOT vp->attribute */
+       ptr[1] = 3;
+
+       if (vp->flags.extended) {
+               ptr[2] = (attribute & 0xff00) >> 8;
+               len++;
+
+       } else if (vp->flags.extended_flags) {
+               if (room < 4) return 0;
+
+               ptr[1] = 4;
+               ptr[2] = (attribute & 0xff00) >> 8;
+               ptr[3] = 0;
+
+               len += 2;
+       }
+
+       /*
+        *      For now, no extended attribute can be longer than the
+        *      encapsulating attribute.  Once we add support for the
+        *      "M" bit, this restriction will be relaxed.
+        */
+       if (room > (255 - ptr[1])) room = 255 - ptr[1];
+
+       if (!vp->flags.is_tlv) {
+               len = vp2data(packet, original, secret, vp, ptr + ptr[1], room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr + ptr[1], room, 2);
+       }
+
+       if (len < 0) return len;
+
+       ptr[1] += len;
+
+       return ptr[1];
+}
+
 /*
  *     Parse a data structure into a RADIUS attribute.
  */
 int rad_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-               const char *secret, const VALUE_PAIR *vp, uint8_t *start)
+               const char *secret, const VALUE_PAIR *vp, uint8_t *start,
+               size_t room)
 {
-       int             vendorcode;
-       int             len, total_length;
-       uint32_t        lvalue;
-       uint8_t         *ptr, *length_ptr, *vsa_length_ptr, *tlv_length_ptr;
-       uint8_t         *end;
+       int len;
+       uint32_t lvalue;
+       uint8_t *ptr;
+       DICT_VENDOR *dv;
 
-       ptr = start;
-       end = ptr + 255;
-       vendorcode = total_length = 0;
-       length_ptr = vsa_length_ptr = tlv_length_ptr = NULL;
+       /*
+        *      RFC format attributes take the fast path.
+        */
+       if (vp->vendor == 0) {
+               return rad_vp2rfc(packet, original, secret, vp,
+                                 vp->attribute, start, room);
+       }
+
+       if (vp->vendor == VENDORPEC_EXTENDED) {
+               return rad_vp2extended(packet, original, secret, vp,
+                                      vp->attribute, start, room);
+       }
 
        /*
-        *      For interoperability, always put vendor attributes
-        *      into their own VSA.
+        *      Not enough room for:
+        *              attr, len, vendor-id, vsa, vsalen
         */
-       if ((vendorcode = VENDOR(vp->attribute)) == 0) {
-               *(ptr++) = vp->attribute & 0xFF;
-               length_ptr = ptr;
-               *(ptr++) = 2;
-               total_length += 2;
+       if (room < 8) return 0;
 
-       } else {
-               int vsa_tlen = 1;
-               int vsa_llen = 1;
-               int vsa_offset = 0;
-               DICT_VENDOR *dv = dict_vendorbyvalue(vendorcode);
+       /*
+        *      Build the Vendor-Specific header
+        */
+       ptr = start;
+       *ptr++ = PW_VENDOR_SPECIFIC;
+       *ptr++ = 6;
+       room -= 6;
+       lvalue = htonl(vp->vendor);
+       memcpy(ptr, &lvalue, 4);
+       ptr += 4;
 
-               /*
-                *      This must be an RFC-format attribute.  If it
-                *      wasn't, then the "decode" function would have
-                *      made a Vendor-Specific attribute (i.e. type
-                *      26), and we would have "vendorcode == 0" here.
-                */
-               if (dv) {
-                       vsa_tlen = dv->type;
-                       vsa_llen = dv->length;
-                       if (dv->flags) vsa_offset = 1;
-               }
+       /*
+        *      Unknown vendors, and type=1,length=1,no-continuation
+        *      are RFC format attributes.
+        */
+       dv = dict_vendorbyvalue(vp->vendor);
+       if (!dv ||
+           ((dv->type == 1) && (dv->length = 1) && !dv->flags)) {
+               len = rad_vp2rfc(packet, original, secret, vp,
+                                vp->attribute, ptr, room);
+               if (len <= 0) return len;
 
-               /*
-                *      Build a VSA header.
-                */
-               *ptr++ = PW_VENDOR_SPECIFIC;
-               vsa_length_ptr = ptr;
-               *ptr++ = 6;
-               lvalue = htonl(vendorcode);
-               memcpy(ptr, &lvalue, 4);
-               ptr += 4;
-               total_length += 6;
+               start[1] += len;
+               return start[1];
+       }
 
-               switch (vsa_tlen) {
+       if (room < (dv->type + dv->length + dv->flags)) return 0;
+       room -= (dv->type + dv->length + dv->flags);
+       start[1] += (dv->type + dv->length + dv->flags);
+
+       switch (dv->type) {
                case 1:
                        ptr[0] = (vp->attribute & 0xFF);
                        break;
@@ -1010,116 +1041,138 @@ int rad_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
 
                case 4:
                        ptr[0] = 0;
-                       ptr[1] = 0;
+                       ptr[1] = ((vp->attribute >> 16) & 0xFF);
                        ptr[2] = ((vp->attribute >> 8) & 0xFF);
                        ptr[3] = (vp->attribute & 0xFF);
                        break;
 
                default:
                        return 0; /* silently discard it */
-               }
-               ptr += vsa_tlen;
+       }
+       ptr += dv->type;
 
-               switch (vsa_llen) {
-               case 0:
-                       length_ptr = vsa_length_ptr;
-                       vsa_length_ptr = NULL;
-                       break;
-               case 1:
-                       ptr[0] = 0;
-                       length_ptr = ptr;
-                       break;
-               case 2:
-                       ptr[0] = 0;
-                       ptr[1] = 0;
-                       length_ptr = ptr + 1;
-                       break;
+       switch (dv->length) {
+       case 0:
+               break;
+       case 1:
+               ptr[0] = dv->type + 1;
+               break;
+       case 2:
+               ptr[0] = 0;
+               ptr[1] = dv->type + 2;
+               break;
 
-               default:
-                       return 0; /* silently discard it */
-               }
-               ptr += vsa_llen;
+       default:
+               return 0; /* silently discard it */
+       }
+       ptr += dv->length;
 
-               /*
-                *      Allow for some continuation.
-                */
-               if (vsa_offset) {
-                       /*
-                        *      Allow TLV's to be encoded, if someone
-                        *      manages to somehow encode the sub-tlv's.
-                        *
-                        *      FIXME: Keep track of room in the packet!
-                        */
-                       if (vp->length > (((size_t) 254) - (ptr - start))) {
-                               return rad_vp2continuation(vp, start, ptr);
-                       }
+       /*
+        *      WiMAX attributes take their own path through the
+        *      system.
+        */
+       if (dv->flags) return wimax2data(packet, original, secret, vp,
+                                        start, room, ptr);
 
-                       ptr[0] = 0x00;
-                       ptr++;
+       len = vp2data(packet, original, secret, vp, ptr, room);
+       if (len <= 0) return len;
 
-                       /*
-                        *      sub-TLV's can only be in one format.
-                        */
-                       if (vp->flags.is_tlv) {
-                               *(ptr++) = (vp->attribute & 0xff00) >> 8;
-                               tlv_length_ptr = ptr;
-                               *(ptr++) = 2;
-                               vsa_offset += 2;
-                       }
-               }
+       if (dv->length != 0) ptr[-1] += len;
 
-               total_length += vsa_tlen + vsa_llen + vsa_offset;
-               if (vsa_length_ptr) *vsa_length_ptr += vsa_tlen + vsa_llen + vsa_offset;
-               *length_ptr += vsa_tlen + vsa_llen + vsa_offset;
-       }
+       start[1] += len;
+
+       return start[1];
+}
+
+/*
+ *  Swap 123a -> 0321
+ */
+#define REORDER(x) ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000 >> 24))
+
+
+/*
+ *     Encode a WiMAX sub-TLV.  It must NOT be called for WiMAX
+ *     attributes that are of type integer, string, etc.
+ */
+static int rad_encode_wimax(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret, VALUE_PAIR *reply,
+                           uint8_t *start, size_t room)
+{
+       int len, redo;
+       uint32_t lvalue;
+       uint8_t *ptr = start, *vsa = start;
+       uint32_t maxattr;
+       VALUE_PAIR *vp = reply;
 
        /*
-        *      Insert tags for string attributes.  They go BEFORE
-        *      the string.
+        *      Swap the order of the WiMAX hacks, to make later
+        *      comparisons easier.
         */
-       if (vp->flags.has_tag && (vp->type == PW_TYPE_STRING) &&
-           (TAG_VALID(vp->flags.tag) ||
-            (vp->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD))) {
-               ptr[0] = vp->flags.tag;
-               end = vp2data(packet, original, secret, vp, ptr + 1,
-                             (end - ptr) - 1);
-       } else {
-               end = vp2data(packet, original, secret, vp, ptr,
-                             (end - ptr));
-       }
-       if (!end) return -1;
+       maxattr = REORDER(vp->attribute);
 
        /*
-        *      Insert tags for integer attributes.  They go at the START
-        *      of the integer, and over-write the first byte.
+        *      Build the Vendor-Specific header
         */
-       if (vp->flags.has_tag && (vp->type == PW_TYPE_INTEGER)) {
-               ptr[0] = vp->flags.tag;
-       }
+       ptr = start;
+       redo = 0;
+
+redo_vsa:
+       vsa = ptr;
+
+       if (room < 9) return 0;
+       *ptr++ = PW_VENDOR_SPECIFIC;
+       *ptr++ = 9;
+       room -= 9;
+       lvalue = htonl(vp->vendor);
+       memcpy(ptr, &lvalue, 4);
+       ptr += 4;
+       *(ptr++) = vp->attribute & 0xff;
+       *(ptr++) = 3;
+       *(ptr++) = 0;           /* continuation */
+       room -= 9;
+
+redo_tlv:
+       len = tlv2data(packet, original, secret, vp, ptr, room, 1);
+       if (len < 0) return len;
 
        /*
-        *      RFC 2865 section 5 says that zero-length attributes
-        *      MUST NOT be sent.
-        *
-        *      ... and the WiMAX forum ignores this... because of
-        *      one vendor.  Don't they have anything better to do
-        *      with their time?
+        *      Not enough room.  Do a continuation.
         */
-       if ((end == ptr) &&
-           (vp->attribute != PW_CHARGEABLE_USER_IDENTITY)) return 0;
+       if ((len == 0) || ((vsa[VS_OFF] + len) > 255)) {
+               if (redo) return (start - vsa);
+
+               vsa[8] = 0x80;
+               redo = 1;
+               goto redo_vsa;
+       }
+       redo = 0;
 
-       len = (end - ptr);
+       ptr += len;
+       vsa[VS_OFF] += len;
+       vsa[WM_OFF] += len;
+
+       vp->flags.encoded = 1;
+       vp = vp->next;
 
        /*
-        *      Update the various lengths.
+        *      Look at the NEXT tlv.  Ensure that we encode
+        *      attributes into a common VSA *only* if they are for
+        *      the same WiMAX VSA, AND if the TLVs are in numerically
+        *      increasing order.
         */
-       *length_ptr += len;
-       if (vsa_length_ptr) *vsa_length_ptr += len;
-       if (tlv_length_ptr) *tlv_length_ptr += len;
-       ptr += len;
-       total_length += len;
+       if (vp && vp->flags.is_tlv && (reply->vendor == vp->vendor) &&
+           ((reply->attribute & 0xff) == (vp->attribute & 0xff))) {
+               uint32_t attr;
+
+               attr = REORDER(vp->attribute);
+               if (attr >= maxattr) {
+                       maxattr = attr;
+                       goto redo_tlv;
+               }
+       }
 
-       return total_length;    /* of attribute */
+       return ptr - start;
 }
 
 
@@ -1138,13 +1191,9 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
        char            ip_buffer[128];
 
        /*
-        *      For simplicity in the following logic, we allow
-        *      the attributes to "overflow" the 4k maximum
-        *      RADIUS packet size, by one attribute.
-        *
-        *      It's uint32_t, for alignment purposes.
+        *      A 4K packet, aligned on 64-bits.
         */
-       uint32_t        data[(MAX_PACKET_LEN + 256) / 4];
+       uint64_t        data[MAX_PACKET_LEN / sizeof(uint64_t)];
 
        if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
                what = fr_packet_codes[packet->code];
@@ -1221,10 +1270,12 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
         */
        for (reply = packet->vps; reply; reply = reply->next) {
                /*
-                *      Ignore non-wire attributes
+                *      Ignore non-wire attributes, but allow extended
+                *      attributes.
                 */
-               if ((VENDOR(reply->attribute) == 0) &&
-                   ((reply->attribute & 0xFFFF) > 0xff)) {
+               if ((reply->vendor == 0) &&
+                   ((reply->attribute & 0xFFFF) >= 256) &&
+                   !reply->flags.extended && !reply->flags.extended_flags) {
 #ifndef NDEBUG
                        /*
                         *      Permit the admin to send BADLY formatted
@@ -1262,38 +1313,29 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                debug_pair(reply);
 
                /*
-                *      Print them in order, even if they were encoded
-                *      already.
+                *      Skip attributes that are encoded.
                 */
-               len = 0;
-               if (reply->flags.encoded) goto next;
-
-               if (reply->flags.is_tlv) {
-                       VALUE_PAIR *tlv = rad_vp2tlv(reply);
-                       if (tlv) {
-                               tlv->next = reply->next;
-                               reply->next = tlv;
-                       }
+               if (reply->flags.encoded) continue;
 
-                       /*
-                        *      The encoded flag MUST be set in reply!
-                        */
-                       reply = reply->next;
-               }
+               if ((reply->vendor == VENDORPEC_WIMAX) && reply->flags.is_tlv) {
+                       len = rad_encode_wimax(packet, original, secret,
+                                              reply, ptr,
+                                              ((uint8_t *) data) + sizeof(data) - ptr);
+               } else {
 
-               len = rad_vp2attr(packet, original, secret, reply, ptr);
+                       len = rad_vp2attr(packet, original, secret, reply, ptr,
+                                         ((uint8_t *) data) + sizeof(data) - ptr);
+               }
 
                if (len < 0) return -1;
 
                /*
-                *      Check that the packet is no more than 4k in
-                *      size, AFTER writing the attribute past the 4k
-                *      boundary, but BEFORE deciding to increase the
-                *      size of the packet. Note that the 'data'
-                *      buffer, above, is one attribute longer than
-                *      necessary, in order to permit this overflow.
+                *      Failed to encode the attribute, likely because
+                *      the packet is full.
                 */
-               if ((total_length + len) > MAX_PACKET_LEN) {
+               if ((len == 0) &&
+                   (total_length > (sizeof(data) - 2 - reply->length))) {
+                       DEBUG("WARNING: Attributes are too long for packet.  Discarding data past %d bytes", total_length);
                        break;
                }
 
@@ -1495,7 +1537,7 @@ int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                      packet->dst_port);
 
                for (reply = packet->vps; reply; reply = reply->next) {
-                       if ((VENDOR(reply->attribute) == 0) &&
+                       if ((reply->vendor == 0) &&
                            ((reply->attribute & 0xFFFF) > 0xff)) continue;
                        debug_pair(reply);
                }
@@ -1635,7 +1677,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
                                     host_ipaddr, sizeof(host_ipaddr)),
-                          packet->data_len, AUTH_HDR_LEN);
+                                  (int) packet->data_len, AUTH_HDR_LEN);
                return 0;
        }
 
@@ -1649,7 +1691,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
                                     host_ipaddr, sizeof(host_ipaddr)),
-                          packet->data_len, MAX_PACKET_LEN);
+                                  (int) packet->data_len, MAX_PACKET_LEN);
                return 0;
        }
 
@@ -1735,7 +1777,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
                                     host_ipaddr, sizeof(host_ipaddr)),
-                          packet->data_len, totallen);
+                                  (int) packet->data_len, totallen);
                return 0;
        }
 
@@ -1772,6 +1814,18 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
 
        while (count > 0) {
                /*
+                *      We need at least 2 bytes to check the
+                *      attribute header.
+                */
+               if (count < 2) {
+                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute header overflows the packet",
+                                  inet_ntop(packet->src_ipaddr.af,
+                                            &packet->src_ipaddr.ipaddr,
+                                            host_ipaddr, sizeof(host_ipaddr)));
+                       return 0;
+               }
+
+               /*
                 *      Attribute number zero is NOT defined.
                 */
                if (attr[0] == 0) {
@@ -1787,7 +1841,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                 *      fields.  Anything shorter is an invalid attribute.
                 */
                        if (attr[1] < 2) {
-                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %d too short",
+                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %u too short",
                                   inet_ntop(packet->src_ipaddr.af,
                                             &packet->src_ipaddr.ipaddr,
                                             host_ipaddr, sizeof(host_ipaddr)),
@@ -1796,6 +1850,19 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                }
 
                /*
+                *      If there are fewer bytes in the packet than in the
+                *      attribute, it's a bad packet.
+                */
+               if (count < attr[1]) {
+                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %u data overflows the packet",
+                                  inet_ntop(packet->src_ipaddr.af,
+                                            &packet->src_ipaddr.ipaddr,
+                                            host_ipaddr, sizeof(host_ipaddr)),
+                                          attr[0]);
+                       return 0;
+               }
+
+               /*
                 *      Sanity check the attributes for length.
                 */
                switch (attr[0]) {
@@ -1995,7 +2062,8 @@ RADIUS_PACKET *rad_recv(int fd, int flags)
                              packet->src_port,
                              packet->code);
                }
-               DEBUG(", id=%d, length=%d\n", packet->id, packet->data_len);
+               DEBUG(", id=%d, length=%d\n",
+                     packet->id, (int) packet->data_len);
        }
 
        return packet;
@@ -2126,7 +2194,7 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                case PW_ACCOUNTING_REQUEST:
                        if (calc_acctdigest(packet, secret) > 1) {
                                fr_strerror_printf("Received %s packet "
-                                          "from %s with invalid signature!  (Shared secret is incorrect.)",
+                                          "from client %s with invalid signature!  (Shared secret is incorrect.)",
                                           fr_packet_codes[packet->code],
                                           inet_ntop(packet->src_ipaddr.af,
                                                     &packet->src_ipaddr.ipaddr,
@@ -2147,13 +2215,12 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        rcode = calc_replydigest(packet, original, secret);
                        if (rcode > 1) {
                                fr_strerror_printf("Received %s packet "
-                                          "from client %s port %d with invalid signature (err=%d)!  (Shared secret is incorrect.)",
+                                          "from home server %s port %d with invalid signature!  (Shared secret is incorrect.)",
                                           fr_packet_codes[packet->code],
                                           inet_ntop(packet->src_ipaddr.af,
                                                     &packet->src_ipaddr.ipaddr,
                                                     buffer, sizeof(buffer)),
-                                          packet->src_port,
-                                          rcode);
+                                          packet->src_port);
                                return -1;
                        }
                        break;
@@ -2175,8 +2242,7 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 
 static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                           const RADIUS_PACKET *original,
-                          const char *secret,
-                          UNUSED unsigned int attribute, size_t length,
+                          const char *secret, size_t length,
                           const uint8_t *data, VALUE_PAIR *vp)
 {
        int offset = 0;
@@ -2192,6 +2258,15 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
        vp->next = NULL;
 
        /*
+        *      It's supposed to be a fixed length, but we found
+        *      a different length instead.  Make it type "octets",
+        *      and do no more processing on it.
+        */
+       if ((vp->flags.length > 0) && (vp->flags.length != length)) {
+               goto raw;
+       }
+
+       /*
         *      Handle tags.
         */
        if (vp->flags.has_tag) {
@@ -2204,7 +2279,10 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                        vp->flags.tag = data[0];
 
                        if ((vp->type == PW_TYPE_STRING) ||
-                           (vp->type == PW_TYPE_OCTETS)) offset = 1;
+                           (vp->type == PW_TYPE_OCTETS)) {
+                               if (length == 0) goto raw;
+                               offset = 1;
+                       }
                }
        }
 
@@ -2306,7 +2384,7 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                 */
                {
                        DICT_VALUE *dval;
-                       dval = dict_valbyattr(vp->attribute,
+                       dval = dict_valbyattr(vp->attribute, vp->vendor,
                                              vp->vp_integer);
                        if (dval) {
                                strlcpy(vp->vp_strvalue,
@@ -2408,16 +2486,35 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
 
        default:
        raw:
-               vp->type = PW_TYPE_OCTETS;
-               vp->length = length;
-               memcpy(vp->vp_octets, data, length);
-
-
                /*
-                *      Ensure there's no encryption or tag stuff,
-                *      we just pass the attribute as-is.
+                *      Change the name to show the user that the
+                *      attribute is not of the correct format.
                 */
-               memset(&vp->flags, 0, sizeof(vp->flags));
+               {
+                       int attr = vp->attribute;
+                       int vendor = vp->vendor;
+                       VALUE_PAIR *vp2;
+
+                       vp2 = pairalloc(NULL);
+                       if (!vp2) {
+                               pairfree(&vp);
+                               return NULL;
+                       }
+                       pairfree(&vp);
+                       vp = vp2;
+
+                       /*
+                        *      This sets "vp->flags" appropriately,
+                        *      and vp->type.
+                        */
+                       if (!paircreate_raw(attr, vendor, PW_TYPE_OCTETS, vp)) {
+                               return NULL;
+                       }
+
+                       vp->length = length;
+                       memcpy(vp->vp_octets, data, length);
+               }
+               break;
        }
 
        return vp;
@@ -2464,8 +2561,8 @@ static void rad_sortvp(VALUE_PAIR **head)
  *     Sane clients should put the fragments next to each other, in
  *     which case this is O(N), in the number of fragments.
  */
-static uint8_t *rad_coalesce(unsigned int attribute, size_t length,
-                            uint8_t *data,
+static uint8_t *rad_coalesce(unsigned int attribute, int vendor,
+                            size_t length, uint8_t *data,
                             size_t packet_length, size_t *ptlv_length)
                             
 {
@@ -2476,9 +2573,12 @@ static uint8_t *rad_coalesce(unsigned int attribute, size_t length,
        for (ptr = data + length;
             ptr != (data + packet_length);
             ptr += ptr[1]) {
+               /* FIXME: Check that there are 6 bytes of data here... */
                if ((ptr[0] != PW_VENDOR_SPECIFIC) ||
                    (ptr[1] < (2 + 4 + 3)) || /* WiMAX VSA with continuation */
-                   (ptr[2] != 0) || (ptr[3] != 0)) { /* our requirement */
+                   (ptr[2] != 0) || (ptr[3] != 0) ||  /* our requirement */
+                   (ptr[4] != ((vendor >> 8) & 0xff)) ||
+                   (ptr[5] != (vendor & 0xff))) {
                        continue;
                }
 
@@ -2543,6 +2643,74 @@ static uint8_t *rad_coalesce(unsigned int attribute, size_t length,
        return tlv_data;
 }
 
+
+/*
+ *     Walk over Evil WIMAX TLVs, creating attributes.
+ */
+static VALUE_PAIR *tlv2wimax(const RADIUS_PACKET *packet,
+                            const RADIUS_PACKET *original,
+                            const char *secret,
+                            int attribute, int vendor,
+                            uint8_t *ptr, size_t len, int nest)
+{
+       VALUE_PAIR *head = NULL;
+       VALUE_PAIR **tail = &head;
+       VALUE_PAIR *vp;
+       uint8_t *y;             /* why do I need to do this? */
+
+       if (nest > fr_wimax_max_tlv) return NULL;
+
+       /*
+        *      Sanity check the attribute.
+        */
+       for (y = ptr; y < (ptr + len); y += y[1]) {
+               if ((y[0] == 0) || ((y + 2) > (ptr + len)) ||
+                   (y[1] < 2) || ((y + y[1]) > (ptr + len))) {
+                       return NULL;
+               }
+
+               /*
+                *      Attribute number is too large for us to
+                *      represent it in our horrible internal
+                *      representation.
+                */
+               if ((ptr[0] & ~fr_wimax_mask[nest]) != 0) {
+                       return NULL;
+               }
+       }
+
+       for (y = ptr; y < (ptr + len); y += y[1]) {
+               DICT_ATTR *da;
+
+               da = dict_attrbyvalue(attribute | (ptr[0] << fr_wimax_shift[nest]), vendor);
+               if (da && (da->type == PW_TYPE_TLV)) {
+                       vp = tlv2wimax(packet, original, secret,
+                                      attribute | (ptr[0] << fr_wimax_shift[nest]),
+                                      vendor, ptr + 2, ptr[1] - 2,
+                                      nest + 1);
+                       if (!vp) goto error;
+               } else {
+                       vp = paircreate(attribute | (ptr[0] << fr_wimax_shift[nest]), vendor,
+                                       PW_TYPE_OCTETS);
+                       if (!vp) {
+                       error:
+                               pairfree(&head);
+                               return NULL;
+                       }
+
+                       if (!data2vp(packet, original, secret,
+                                    y[1] - 2, y + 2, vp)) {
+                               goto error;
+                       }
+               }
+
+               *tail = vp;
+               while (*tail) tail = &((*tail)->next);
+       }
+
+       return head;
+}
+
 /*
  *     Start at the *data* portion of a continued attribute.  search
  *     through the rest of the attributes to find a matching one, and
@@ -2551,6 +2719,7 @@ static uint8_t *rad_coalesce(unsigned int attribute, size_t length,
 static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
                                       const RADIUS_PACKET *original,
                                       const char *secret, int attribute,
+                                      int vendor,
                                       int length, /* CANNOT be zero */
                                       uint8_t *data, size_t packet_length,
                                       int flag, DICT_ATTR *da)
@@ -2559,13 +2728,14 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
        uint8_t *ptr;
        uint8_t *tlv_data;
        VALUE_PAIR *vp, *head, **tail;
+       DICT_ATTR *tlv_da;
 
        /*
         *      Ensure we have data that hasn't been split across
         *      multiple attributes.
         */
        if (flag) {
-               tlv_data = rad_coalesce(attribute, length,
+               tlv_data = rad_coalesce(attribute, vendor, length,
                                        data, packet_length, &tlv_length);
                if (!tlv_data) return NULL;
        } else {
@@ -2592,7 +2762,7 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
                        memcpy(tlv_data, data, tlv_length);
                }
                
-               vp = paircreate(attribute, PW_TYPE_OCTETS);
+               vp = paircreate(attribute, vendor, PW_TYPE_OCTETS);
                if (!vp) return NULL;
                        
                vp->type = PW_TYPE_TLV;
@@ -2617,6 +2787,7 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
                    (ptr[1] > left)) {
                        goto not_well_formed;
                }
+
                left -= ptr[1];
        }
 
@@ -2629,20 +2800,32 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
        for (ptr = tlv_data;
             ptr != (tlv_data + tlv_length);
             ptr += ptr[1]) {
-               vp = paircreate(attribute | (ptr[0] << 8), PW_TYPE_OCTETS);
-               if (!vp) {
-                       pairfree(&head);
-                       goto not_well_formed;
-               }
 
-               if (!data2vp(packet, original, secret,
-                            ptr[0], ptr[1] - 2, ptr + 2, vp)) {
-                       pairfree(&head);
-                       goto not_well_formed;
+               tlv_da = dict_attrbyvalue(attribute | (ptr[0] << fr_wimax_shift[1]), vendor);
+               if (tlv_da && (tlv_da->type == PW_TYPE_TLV)) {
+                       vp = tlv2wimax(packet, original, secret,
+                                      attribute | (ptr[0] << 8),
+                                      vendor, ptr + 2, ptr[1] - 2, 2);
+
+                       if (!vp) goto error;
+               } else {
+                       vp = paircreate(attribute | (ptr[0] << fr_wimax_shift[1]), vendor,
+                                       PW_TYPE_OCTETS);
+                       if (!vp) {
+                       error:
+                               pairfree(&head);
+                               goto not_well_formed;
+                       }
+
+                       if (!data2vp(packet, original, secret,
+                                    ptr[1] - 2, ptr + 2, vp)) {
+                               goto error;
+                       }
                }
 
                *tail = vp;
-               tail = &(vp->next);
+
+               while (*tail) tail = &((*tail)->next);
        }
 
        /*
@@ -2657,18 +2840,88 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
 
 
 /*
+ *     Extended attribute TLV to VP.
+ */
+static VALUE_PAIR *tlv2vp(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret, int attribute,
+                           int length, const uint8_t *data)
+{
+       VALUE_PAIR *vp;
+
+       if ((length < 2) || (data[1] < 2)) return NULL;
+
+       /*
+        *      For now, only one TLV is allowed.
+        */
+       if (data[1] != length) return NULL;
+
+       attribute |= (data[0] << fr_wimax_shift[2]);
+
+       vp = paircreate(attribute, VENDORPEC_EXTENDED, PW_TYPE_OCTETS);
+       if (!vp) return NULL;
+
+       return data2vp(packet, original, secret, length - 2, data + 2, vp);
+}
+
+/*
  *     Parse a RADIUS attribute into a data structure.
  */
-VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-                       const char *secret, int attribute, int length,
-                       const uint8_t *data)
+VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet,
+                       const RADIUS_PACKET *original,
+                       const char *secret, int attribute, int vendor,
+                       int length, const uint8_t *data)
 {
        VALUE_PAIR *vp;
 
-       vp = paircreate(attribute, PW_TYPE_OCTETS);
+       /*
+        *      Hard-coded values are bad...
+        */
+       if ((vendor == 0) && (attribute >= 241) && (attribute <= 246)) {
+               DICT_ATTR *da;
+
+               da = dict_attrbyvalue(attribute, VENDORPEC_EXTENDED);
+               if (da && (da->flags.extended || da->flags.extended_flags)) {
+
+                       if (length == 0) return NULL;
+
+                       attribute |= (data[0] << fr_wimax_shift[1]);
+                       vendor = VENDORPEC_EXTENDED;
+
+                       data++;
+                       length--;
+
+                       /*
+                        *      There may be a flag octet.
+                        */
+                       if (da->flags.extended_flags) {
+                               if (length == 0) return NULL;
+
+                               /*
+                                *      If there's a flag, we can't
+                                *      handle it.
+                                */
+                               if (data[0] != 0) return NULL;
+                               data++;
+                               length--;
+                       }
+
+                       /*
+                        *      Now look up the extended attribute, to
+                        *      see if it's a TLV carrying more data.
+                        *      
+                        */
+                       da = dict_attrbyvalue(attribute, VENDORPEC_EXTENDED);
+                       if (da && da->flags.has_tlv) {
+                               return tlv2vp(packet, original, secret,
+                                             attribute, length, data);
+                       }
+               }
+       }
+       vp = paircreate(attribute, vendor, PW_TYPE_OCTETS);
        if (!vp) return NULL;
 
-       return data2vp(packet, original, secret, attribute, length, data, vp);
+       return data2vp(packet, original, secret, length, data, vp);
 }
 
 
@@ -2774,13 +3027,10 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        if (myvendor == 0) goto create_pair;
 
                        /*
-                        *      This is an implementation issue.
-                        *      We currently pack vendor into the upper
-                        *      16 bits of a 32-bit attribute number,
-                        *      so we can't handle vendor numbers larger
-                        *      than 16 bits.
+                        *      Allow vendors up to 2^24.  Past that,
+                        *      get confused.
                         */
-                       if (myvendor > 65535) goto create_pair;
+                       if (myvendor > FR_MAX_VENDOR) goto create_pair;
 
                        vsa_tlen = vsa_llen = 1;
                        vsa_offset = 0;
@@ -2842,7 +3092,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 
                                switch (vsa_llen) {
                                case 0:
-                                       attribute = (myvendor << 16) | myattr;
+                                       attribute = myattr;
                                        ptr += 4 + vsa_tlen;
                                        attrlen -= (4 + vsa_tlen);
                                        packet_length -= 4 + vsa_tlen;
@@ -2911,7 +3161,6 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                default:        /* can't hit this. */
                        return -1;
                }
-               attribute |= (vendorcode << 16);
                vsa_ptr = ptr;
                ptr += vsa_tlen;
 
@@ -2930,7 +3179,6 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 
                ptr += vsa_llen + vsa_offset;
                vendorlen -= vsa_tlen + vsa_llen + vsa_offset + attrlen;
-               if (vendorlen == 0) vendorcode = 0;
                packet_length -= (vsa_tlen + vsa_llen + vsa_offset);
 
                /*
@@ -2943,12 +3191,14 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                 *      are a secret flag to us that the attribute has
                 *      already been dealt with.
                 */
-               if (attribute == 0x60b50000) goto next;
+               if ((vendorcode == VENDORPEC_WIMAX) && (attribute == 0)) {
+                       goto next;
+               }
 
                if (vsa_offset) {
                        DICT_ATTR *da;
 
-                       da = dict_attrbyvalue(attribute);
+                       da = dict_attrbyvalue(attribute, vendorcode);
 
                        /*
                         *      If it's NOT continued, AND we know
@@ -2963,7 +3213,8 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                         *      Go do a lot of work to find the stuff.
                         */
                        pair = rad_continuation2vp(packet, original, secret,
-                                                  attribute, attrlen, ptr,
+                                                  attribute, vendorcode,
+                                                  attrlen, ptr,
                                                   packet_length,
                                                   ((vsa_ptr[2] & 0x80) != 0),
                                                   da);
@@ -2985,7 +3236,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                    (attribute != PW_CHARGEABLE_USER_IDENTITY)) goto next;
 
                pair = rad_attr2vp(packet, original, secret,
-                                  attribute, attrlen, ptr);
+                                  attribute, vendorcode, attrlen, ptr);
                if (!pair) {
                        pairfree(&packet->vps);
                        fr_strerror_printf("out of memory");
@@ -3021,6 +3272,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                }
 
        next:
+               if (vendorlen == 0) vendorcode = 0;
                ptr += attrlen;
                packet_length -= attrlen;
        }
@@ -3407,7 +3659,7 @@ int rad_chap_encode(RADIUS_PACKET *packet, uint8_t *output, int id,
         *      Use Chap-Challenge pair if present,
         *      Request-Authenticator otherwise.
         */
-       challenge = pairfind(packet->vps, PW_CHAP_CHALLENGE);
+       challenge = pairfind(packet->vps, PW_CHAP_CHALLENGE, 0);
        if (challenge) {
                memcpy(ptr, challenge->vp_strvalue, challenge->length);
                i += challenge->length;