Check packet pointer in data2vp_any() before using, to allow the
[freeradius.git] / src / lib / radius.c
index 5333303..7529189 100644 (file)
@@ -1,5 +1,6 @@
-/*
- * radius.c    Functions to send/receive radius packets.
+/**
+ * @file radius.c
+ * @brief Functions to send/receive radius packets.
  *
  * Version:    $Id$
  *
@@ -37,6 +38,13 @@ RCSID("$Id$")
 #include       <malloc.h>
 #endif
 
+#if 0
+#define VP_TRACE if (fr_debug_flag) printf
+#else
+#define VP_TRACE(_x, ...)
+#endif
+
+
 /*
  *  The RFC says 4096 octets max, and most packets are less than 256.
  */
@@ -140,59 +148,81 @@ void fr_printf_log(const char *fmt, ...)
        return;
 }
 
-static void print_hex(RADIUS_PACKET *packet)
+static const char *tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+
+static void print_hex_data(const uint8_t *ptr, int attrlen, int depth)
+{
+       int i;
+
+       for (i = 0; i < attrlen; i++) {
+               if ((i > 0) && ((i & 0x0f) == 0x00))
+                       fprintf(fr_log_fp, "%.*s", depth, tabs);
+               fprintf(fr_log_fp, "%02x ", ptr[i]);
+               if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n");
+       }
+       if ((i & 0x0f) != 0) fprintf(fr_log_fp, "\n");
+}
+
+
+void rad_print_hex(RADIUS_PACKET *packet)
 {
        int i;
 
-       if (!packet->data) return;
+       if (!packet->data || !fr_log_fp) return;
 
-       printf("  Code:\t\t%u\n", packet->data[0]);
-       printf("  Id:\t\t%u\n", packet->data[1]);
-       printf("  Length:\t%u\n", ((packet->data[2] << 8) |
+       fprintf(fr_log_fp, "  Code:\t\t%u\n", packet->data[0]);
+       fprintf(fr_log_fp, "  Id:\t\t%u\n", packet->data[1]);
+       fprintf(fr_log_fp, "  Length:\t%u\n", ((packet->data[2] << 8) |
                                   (packet->data[3])));
-       printf("  Vector:\t");
+       fprintf(fr_log_fp, "  Vector:\t");
        for (i = 4; i < 20; i++) {
-               printf("%02x", packet->data[i]);
+               fprintf(fr_log_fp, "%02x", packet->data[i]);
        }
-       printf("\n");
+       fprintf(fr_log_fp, "\n");
 
        if (packet->data_len > 20) {
                int total;
                const uint8_t *ptr;
-               printf("  Data:");
+               fprintf(fr_log_fp, "  Data:");
 
                total = packet->data_len - 20;
                ptr = packet->data + 20;
 
                while (total > 0) {
                        int attrlen;
+                       unsigned int vendor = 0;
 
-                       printf("\t\t");
+                       fprintf(fr_log_fp, "\t\t");
                        if (total < 2) { /* too short */
-                               printf("%02x\n", *ptr);
+                               fprintf(fr_log_fp, "%02x\n", *ptr);
                                break;
                        }
 
                        if (ptr[1] > total) { /* too long */
                                for (i = 0; i < total; i++) {
-                                       printf("%02x ", ptr[i]);
+                                       fprintf(fr_log_fp, "%02x ", ptr[i]);
                                }
                                break;
                        }
 
-                       printf("%02x  %02x  ", ptr[0], ptr[1]);
+                       fprintf(fr_log_fp, "%02x  %02x  ", ptr[0], ptr[1]);
                        attrlen = ptr[1] - 2;
-                       ptr += 2;
-                       total -= 2;
-
-                       for (i = 0; i < attrlen; i++) {
-                               if ((i > 0) && ((i & 0x0f) == 0x00))
-                                       printf("\t\t\t");
-                               printf("%02x ", ptr[i]);
-                               if ((i & 0x0f) == 0x0f) printf("\n");
+
+                       if ((ptr[0] == PW_VENDOR_SPECIFIC) &&
+                           (attrlen > 4)) {
+                               vendor = (ptr[3] << 16) | (ptr[4] << 8) | ptr[5];
+                               fprintf(fr_log_fp, "%02x%02x%02x%02x (%u)  ",
+                                      ptr[2], ptr[3], ptr[4], ptr[5], vendor);
+                               attrlen -= 4;
+                               ptr += 6;
+                               total -= 6;
+
+                       } else {
+                               ptr += 2;
+                               total -= 2;
                        }
 
-                       if ((attrlen & 0x0f) != 0x00) printf("\n");
+                       print_hex_data(ptr, attrlen, 3);
 
                        ptr += attrlen;
                        total -= attrlen;
@@ -201,15 +231,15 @@ static void print_hex(RADIUS_PACKET *packet)
        fflush(stdout);
 }
 
-
-/*
- *     Wrapper for sendto which handles sendfromto, IPv6, and all
+/**
+ * @brief Wrapper for sendto which handles sendfromto, IPv6, and all
  *     possible combinations.
  */
 static int rad_sendto(int sockfd, void *data, size_t data_len, int flags,
                      fr_ipaddr_t *src_ipaddr, int src_port,
                      fr_ipaddr_t *dst_ipaddr, int dst_port)
 {
+       int rcode;
        struct sockaddr_storage dst;
        socklen_t               sizeof_dst;
 
@@ -228,26 +258,34 @@ static int rad_sendto(int sockfd, void *data, size_t data_len, int flags,
 
 #ifdef WITH_UDPFROMTO
        /*
-        *      Only IPv4 is supported for udpfromto.
-        *
         *      And if they don't specify a source IP address, don't
         *      use udpfromto.
         */
-       if ((dst_ipaddr->af == AF_INET) ||
-           (src_ipaddr->af != AF_UNSPEC)) {
-               return sendfromto(sockfd, data, data_len, flags,
-                                 (struct sockaddr *)&src, sizeof_src,
-                                 (struct sockaddr *)&dst, sizeof_dst);
+       if (((dst_ipaddr->af == AF_INET) || (dst_ipaddr->af == AF_INET6)) &&
+           (src_ipaddr->af != AF_UNSPEC) &&
+           !fr_inaddr_any(src_ipaddr)) {
+               rcode = sendfromto(sockfd, data, data_len, flags,
+                                  (struct sockaddr *)&src, sizeof_src,
+                                  (struct sockaddr *)&dst, sizeof_dst);
+               goto done;
        }
 #else
        src_ipaddr = src_ipaddr; /* -Wunused */
 #endif
 
        /*
-        *      No udpfromto, OR an IPv6 socket, fail gracefully.
+        *      No udpfromto, fail gracefully.
         */
-       return sendto(sockfd, data, data_len, flags,
-                     (struct sockaddr *) &dst, sizeof_dst);
+       rcode = sendto(sockfd, data, data_len, flags,
+                      (struct sockaddr *) &dst, sizeof_dst);
+#ifdef WITH_UDPFROMTO
+done:
+#endif
+       if (rcode < 0) {
+               DEBUG("rad_send() failed: %s\n", strerror(errno));
+       }
+
+       return rcode;
 }
 
 
@@ -330,8 +368,8 @@ ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, int *src_port,
 }
 
 
-/*
- *     wrapper for recvfrom, which handles recvfromto, IPv6, and all
+/**
+ * @brief wrapper for recvfrom, which handles recvfromto, IPv6, and all
  *     possible combinations.
  */
 static ssize_t rad_recvfrom(int sockfd, uint8_t **pbuf, int flags,
@@ -415,14 +453,14 @@ static ssize_t rad_recvfrom(int sockfd, uint8_t **pbuf, int flags,
         *      packet after "len" bytes.
         */
 #ifdef WITH_UDPFROMTO
-       if (dst.ss_family == AF_INET) {
+       if ((dst.ss_family == AF_INET) || (dst.ss_family == AF_INET6)) {
                data_len = recvfromto(sockfd, buf, len, flags,
                                      (struct sockaddr *)&src, &sizeof_src,
                                      (struct sockaddr *)&dst, &sizeof_dst);
        } else
 #endif
                /*
-                *      No udpfromto, OR an IPv6 socket.  Fail gracefully.
+                *      No udpfromto, fail gracefully.
                 */
                data_len = recvfrom(sockfd, buf, len, flags,
                                    (struct sockaddr *)&src, &sizeof_src);
@@ -458,17 +496,15 @@ static ssize_t rad_recvfrom(int sockfd, uint8_t **pbuf, int flags,
 
 
 #define AUTH_PASS_LEN (AUTH_VECTOR_LEN)
-/*************************************************************************
- *
- *      Function: make_secret
- *
- *      Purpose: Build an encrypted secret value to return in a reply
- *               packet.  The secret is hidden by xoring with a MD5 digest
+/**
+ * @brief Build an encrypted secret value to return in a reply packet
+ * 
+ *               The secret is hidden by xoring with a MD5 digest
  *               created from the shared secret and the authentication
  *               vector.  We put them into MD5 in the reverse order from
  *               that used when encrypting passwords to RADIUS.
  *
- *************************************************************************/
+ */
 static void make_secret(uint8_t *digest, const uint8_t *vector,
                        const char *secret, const uint8_t *value)
 {
@@ -486,7 +522,7 @@ static void make_secret(uint8_t *digest, const uint8_t *vector,
 }
 
 #define MAX_PASS_LEN (128)
-static void make_passwd(uint8_t *output, size_t *outlen,
+static void make_passwd(uint8_t *output, ssize_t *outlen,
                        const uint8_t *input, size_t inlen,
                        const char *secret, const uint8_t *vector)
 {
@@ -542,7 +578,7 @@ static void make_passwd(uint8_t *output, size_t *outlen,
        memcpy(output, passwd, len);
 }
 
-static void make_tunnel_passwd(uint8_t *output, size_t *outlen,
+static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen,
                               const uint8_t *input, size_t inlen, size_t room,
                               const char *secret, const uint8_t *vector)
 {
@@ -626,6 +662,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];
                }
@@ -633,18 +670,165 @@ static void make_tunnel_passwd(uint8_t *output, size_t *outlen,
        memcpy(output, passwd, len + 2);
 }
 
-/*
- *     Returns the end of the data.
+extern int fr_attr_max_tlv;
+extern int fr_attr_shift[];
+extern int fr_attr_mask[];
+
+static int do_next_tlv(const VALUE_PAIR *vp, const VALUE_PAIR *next, int nest)
+{
+       unsigned int tlv1, tlv2;
+
+       if (nest > fr_attr_max_tlv) return 0;
+
+       if (!vp) return 0;
+
+       /*
+        *      Keep encoding TLVs which have the same scope.
+        *      e.g. two attributes of:
+        *              ATTR.TLV1.TLV2.TLV3 = data1
+        *              ATTR.TLV1.TLV2.TLV4 = data2
+        *      both get put into a container of "ATTR.TLV1.TLV2"
+        */
+
+       /*
+        *      Nothing to follow, we're done.
+        */
+       if (!next) return 0;
+
+       /*
+        *      Not from the same vendor, skip it.
+        */
+       if (vp->vendor != next->vendor) return 0;
+
+       /*
+        *      In a different TLV space, skip it.
+        */
+       tlv1 = vp->attribute;
+       tlv2 = next->attribute;
+       
+       tlv1 &= ((1 << fr_attr_shift[nest]) - 1);
+       tlv2 &= ((1 << fr_attr_shift[nest]) - 1);
+       
+       if (tlv1 != tlv2) return 0;
+
+       return 1;
+}
+
+
+static ssize_t vp2data_any(const RADIUS_PACKET *packet,
+                          const RADIUS_PACKET *original,
+                          const char *secret, int nest,
+                          const VALUE_PAIR **pvp,
+                          uint8_t *start, size_t room);
+
+static ssize_t vp2attr_rfc(const RADIUS_PACKET *packet,
+                          const RADIUS_PACKET *original,
+                          const char *secret, const VALUE_PAIR **pvp,
+                          unsigned int attribute, uint8_t *ptr, size_t room);
+
+/**
+ * @brief This is really a sub-function of vp2data_any().  It encodes
+ *     the *data* portion of the TLV, and assumes that the encapsulating
+ *     attribute has already been encoded.
  */
-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 ssize_t vp2data_tlvs(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret, int nest,
+                           const VALUE_PAIR **pvp,
+                           uint8_t *start, size_t room)
+{
+       ssize_t len;
+       size_t my_room;
+       uint8_t *ptr = start;
+       const VALUE_PAIR *vp = *pvp;
+       const VALUE_PAIR *svp = vp;
+
+       if (!svp) return 0;
+
+#ifndef NDEBUG
+       if (nest > fr_attr_max_tlv) {
+               fr_strerror_printf("vp2data_tlvs: attribute nesting overflow");
+               return -1;
+       }
+#endif
+
+       while (vp) {
+               if (room < 2) return ptr - start;
+               
+               ptr[0] = (vp->attribute >> fr_attr_shift[nest]) & fr_attr_mask[nest];
+               ptr[1] = 2;
+               
+               my_room = room;
+               if (room > 255) my_room = 255;
+
+               len = vp2data_any(packet, original, secret, nest,
+                                 &vp, ptr + 2, my_room - 2);
+               if (len < 0) return len;
+               if (len == 0) return ptr - start;
+               /* len can NEVER be more than 253 */
+
+               ptr[1] += len;
+
+#ifndef NDEBUG
+               if ((fr_debug_flag > 3) && fr_log_fp) {
+                       fprintf(fr_log_fp, "\t\t%02x %02x  ", ptr[0], ptr[1]);
+                       print_hex_data(ptr + 2, len, 3);
+               }
+#endif
+
+               room -= ptr[1];
+               ptr += ptr[1];
+               *pvp = vp;
+               
+               if (!do_next_tlv(svp, vp, nest)) break;
+       }
+
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) {
+               DICT_ATTR *da;
+               
+               da = dict_attrbyvalue(svp->attribute & ((1 << fr_attr_shift[nest ]) - 1), svp->vendor);
+               if (da) fprintf(fr_log_fp, "\t%s = ...\n", da->name);
+       }
+#endif
+
+       return ptr - start;
+}
+
+
+/**
+ * @brief Encodes the data portion of an attribute.
+ * @return -1 on error, or the length of the data portion.
+ */
+static ssize_t vp2data_any(const RADIUS_PACKET *packet,
+                          const RADIUS_PACKET *original,
+                          const char *secret, int nest,
+                          const VALUE_PAIR **pvp,
+                          uint8_t *start, size_t room)
 {
        uint32_t lvalue;
-       size_t len;
+       ssize_t len;
        const uint8_t *data;
+       uint8_t *ptr = start;
        uint8_t array[4];
+       uint64_t lvalue64;
+       const VALUE_PAIR *vp = *pvp;
+
+       /*
+        *      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 (vp->flags.is_tlv && (nest < fr_attr_max_tlv) &&
+           ((vp->attribute >> fr_attr_shift[nest + 1]) != 0)) {
+               return vp2data_tlvs(packet, original, secret, nest + 1, pvp,
+                                   start, room);
+       }
+
+       debug_pair(vp);
 
        /*
         *      Set up the default sources for the data.
@@ -652,6 +836,12 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
        data = vp->vp_octets;
        len = vp->length;
 
+       /*
+        *      Short-circuit it for long attributes.  They can't be
+        *      encrypted, tagged, etc.
+        */
+       if ((vp->type & PW_FLAG_LONG) != 0) goto do_tlv;
+
        switch(vp->type) {
        case PW_TYPE_STRING:
        case PW_TYPE_OCTETS:
@@ -682,6 +872,12 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
                data = array;
                break;
 
+       case PW_TYPE_INTEGER64:
+               len = 8;        /* just in case */
+               lvalue64 = htonll(vp->vp_integer64);
+               data = (uint8_t *) &lvalue64;
+               break;
+
        case PW_TYPE_IPADDR:
                data = (const uint8_t *) &vp->vp_ipaddr;
                len = 4;        /* just in case */
@@ -707,22 +903,31 @@ static uint8_t *vp2data(const RADIUS_PACKET *packet,
        }
 
        case PW_TYPE_TLV:
+       do_tlv:
                data = vp->vp_tlv;
                if (!data) {
                        fr_strerror_printf("ERROR: Cannot encode NULL TLV");
-                       return NULL;
+                       return -1;
                }
                break;
 
        default:                /* unknown type: ignore it */
                fr_strerror_printf("ERROR: Unknown attribute type %d", vp->type);
-               return NULL;
+               return -1;
+       }
+
+       /*
+        *      No data: skip it.
+        */
+       if (len == 0) {
+               *pvp = vp->next;
+               return 0;
        }
 
        /*
         *      Bound the data to the calling size
         */
-       if (len > room) len = room;
+       if (len > (ssize_t) room) len = room;
 
        /*
         *      Encrypt the various password styles
@@ -737,6 +942,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 +952,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 +961,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,373 +990,617 @@ 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 > ((ssize_t) (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;
+       *pvp = vp->next;
+       return len + (ptr - start);
 }
 
-
-static VALUE_PAIR *rad_vp2tlv(VALUE_PAIR *vps)
+static ssize_t attr_shift(const uint8_t *start, const uint8_t *end,
+                         uint8_t *ptr, int hdr_len, ssize_t len,
+                         int flag_offset, int vsa_offset)
 {
-       int maxattr = 0;
-       int length;
-       unsigned int attribute;
-       uint8_t *ptr, *end;
-       VALUE_PAIR *vp, *tlv;
-
-       attribute = vps->attribute & 0xffff00ff;
-       maxattr = vps->attribute & 0x0ff;
-
-       tlv = paircreate(attribute, PW_TYPE_TLV);
-       if (!tlv) return NULL;
+       int check_len = len - ptr[1];
+       int total = len + hdr_len;
+       
+       /*
+        *      Pass 1: Check if the addition of the headers
+        *      overflows the available room.  If so, return
+        *      what we were capable of encoding.
+        */
+       
+       while (check_len > (255 - hdr_len)) {
+               total += hdr_len;
+               check_len -= (255 - hdr_len);
+       }
 
-       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)) {
+       /*
+        *      Note that this results in a number of attributes maybe
+        *      being marked as "encoded", but which aren't in the
+        *      packet.  Oh well.  The solution is to fix the
+        *      "vp2data_any" function to take into account the header
+        *      lengths.
+        */
+       if ((ptr + ptr[1] + total) > end) {
+               return (ptr + ptr[1]) - start;
+       }
+       
+       /*
+        *      Pass 2: Now that we know there's enough room,
+        *      re-arrange the data to form a set of valid
+        *      RADIUS attributes.
+        */
+       while (1) {
+               int sublen = 255 - ptr[1];
+               
+               if (len <= sublen) {
                        break;
                }
-
-               maxattr = vp->attribute & 0xff00;
-               tlv->length += vp->length + 2;
+               
+               len -= sublen;
+               memmove(ptr + 255 + hdr_len, ptr + 255, sublen);
+               memcpy(ptr + 255, ptr, hdr_len);
+               ptr[1] += sublen;
+               if (vsa_offset) ptr[vsa_offset] += sublen;
+               ptr[flag_offset] |= 0x80;
+               
+               ptr += 255;
+               ptr[1] = hdr_len;
+               if (vsa_offset) ptr[vsa_offset] = 3;
        }
 
-       if (!tlv->length) {
-               pairfree(&tlv);
-               return NULL;
-       }
+       ptr[1] += len;
+       if (vsa_offset) ptr[vsa_offset] += len;
 
-       tlv->vp_tlv = malloc(tlv->length);
-       if (!tlv->vp_tlv) {
-               pairfree(&tlv);
-               return NULL;
+       return (ptr + ptr[1]) - start;
+}
+
+
+/**
+ * @brief Encode an "extended" attribute.
+ */
+int rad_vp2extended(const RADIUS_PACKET *packet,
+                   const RADIUS_PACKET *original,
+                   const char *secret, const VALUE_PAIR **pvp,
+                   uint8_t *ptr, size_t room)
+{
+       int len;
+       int hdr_len;
+       int nest = 1;
+       uint8_t *start = ptr;
+       const VALUE_PAIR *vp = *pvp;
+
+       if (vp->vendor < VENDORPEC_EXTENDED) {
+               fr_strerror_printf("rad_vp2extended called for non-extended attribute");
+               return -1;
        }
 
-       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;
-               }
+       if (room < 3) return 0;
 
-               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... */
-               }
+       ptr[0] = vp->attribute & 0xff;
+       ptr[1] = 3;
 
-               length = (end - ptr);
-               if (length > 255) return NULL;
+       if (vp->flags.extended) {
+               ptr[2] = (vp->attribute & 0xff00) >> 8;
 
-               /*
-                *      Pack the attribute.
-                */
-               ptr[0] = (vp->attribute & 0xff00) >> 8;
-               ptr[1] = length;
+       } else if (vp->flags.extended_flags) {
+               if (room < 4) return 0;
 
-               ptr += ptr[1];
-               vp->flags.encoded = 1;
+               ptr[1] = 4;
+               ptr[2] = (vp->attribute & 0xff00) >> 8;
+               ptr[3] = 0;
        }
 
-       return tlv;
-}
-
-/*
- *     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)
-{
-       size_t left, piece;
-       size_t hsize = (ptr - start);
-       uint8_t *this = start;
-       const uint8_t *data;
-       uint8_t header[16];
-       
        /*
-        *      If it's too long and marked as encrypted, ignore it.
+        *      Only "flagged" attributes can be longer than one
+        *      attribute.
         */
-       if (vp->flags.encrypt != FLAG_ENCRYPT_NONE) {
-               return 0;
+       if (!vp->flags.extended_flags && (room > 255)) {
+               room = 255;
        }
-       
-       memcpy(header, start, hsize);
-
-       left = vp->length;
-       
-       switch (vp->type) {
-       case PW_TYPE_TLV:
-               data = vp->vp_tlv;
-               break;
 
-       case PW_TYPE_OCTETS:
-       case PW_TYPE_STRING:
-               data = vp->vp_octets;
-               break;
+       /*
+        *      Handle EVS VSAs.
+        */
+       if (vp->flags.evs) {
+               uint8_t *evs = ptr + ptr[1];
 
-               /*
-                *      This is invalid.
-                */
-       default:
-               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;
+               if (room < (size_t) (ptr[1] + 5)) return 0;
 
                /*
-                *      
+                *      RADIUS Attribute Type is packed into the high byte
+                *      of the Vendor Id.  So over-write it in the packet.
+                *
+                *      And hard-code Extended-Type to Vendor-Specific.
                 */
-               this[hsize - 1] = hsize - 6 + 1 + piece;
-               data += piece;
-               ptr += piece;
-               left -= piece;
-               this = ptr;
-       }
-       
-       return (ptr - start);
-}
+               ptr[0] = (vp->vendor >> 24) & 0xff;
+               ptr[2] = 26;
 
+               evs[0] = 0;     /* always zero */
+               evs[1] = (vp->vendor >> 16) & 0xff;
+               evs[2] = (vp->vendor >> 8) & 0xff;
+               evs[3] = vp->vendor & 0xff;
+               evs[4] = vp->attribute & 0xff;          
 
-/*
- *     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)
-{
-       int             vendorcode;
-       int             len, total_length;
-       uint32_t        lvalue;
-       uint8_t         *ptr, *length_ptr, *vsa_length_ptr, *tlv_length_ptr;
-       uint8_t         *end;
+               ptr[1] += 5;
+               nest = 0;
+       }
+       hdr_len = ptr[1];
 
-       ptr = start;
-       end = ptr + 255;
-       vendorcode = total_length = 0;
-       length_ptr = vsa_length_ptr = tlv_length_ptr = NULL;
+       len = vp2data_any(packet, original, secret, nest,
+                         pvp, ptr + ptr[1], room - hdr_len);
+       if (len <= 0) return len;
 
        /*
-        *      For interoperability, always put vendor attributes
-        *      into their own VSA.
+        *      There may be more than 252 octets of data encoded in
+        *      the attribute.  If so, move the data up in the packet,
+        *      and copy the existing header over.  Set the "M" flag ONLY
+        *      after copying the rest of the data.
         */
-       if ((vendorcode = VENDOR(vp->attribute)) == 0) {
-               *(ptr++) = vp->attribute & 0xFF;
-               length_ptr = ptr;
-               *(ptr++) = 2;
-               total_length += 2;
-
-       } else {
-               int vsa_tlen = 1;
-               int vsa_llen = 1;
-               int vsa_offset = 0;
-               DICT_VENDOR *dv = dict_vendorbyvalue(vendorcode);
-
-               /*
-                *      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;
-               }
-
-               /*
-                *      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;
-
-               switch (vsa_tlen) {
-               case 1:
-                       ptr[0] = (vp->attribute & 0xFF);
-                       break;
-
-               case 2:
-                       ptr[0] = ((vp->attribute >> 8) & 0xFF);
-                       ptr[1] = (vp->attribute & 0xFF);
-                       break;
+       if (vp->flags.extended_flags && (len > (255 - ptr[1]))) {
+               return attr_shift(start, start + room, ptr, 4, len, 3, 0);
+       }
 
-               case 4:
-                       ptr[0] = 0;
-                       ptr[1] = 0;
-                       ptr[2] = ((vp->attribute >> 8) & 0xFF);
-                       ptr[3] = (vp->attribute & 0xFF);
-                       break;
+       ptr[1] += len;
+       
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) {
+               int jump = 3;
 
-               default:
-                       return 0; /* silently discard it */
+               fprintf(fr_log_fp, "\t\t%02x %02x  ", ptr[0], ptr[1]);
+               if (!vp->flags.extended_flags) {
+                       fprintf(fr_log_fp, "%02x  ", ptr[2]);
+                       
+               } else {
+                       fprintf(fr_log_fp, "%02x %02x  ", ptr[2], ptr[3]);
+                       jump = 4;
                }
-               ptr += vsa_tlen;
-
-               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;
 
-               default:
-                       return 0; /* silently discard it */
+               if (vp->flags.evs) {
+                       fprintf(fr_log_fp, "%02x%02x%02x%02x (%u)  %02x  ",
+                               ptr[jump], ptr[jump + 1],
+                               ptr[jump + 2], ptr[jump + 3],
+                               ((ptr[jump + 1] << 16) |
+                                (ptr[jump + 2] << 8) |
+                                ptr[jump + 3]),
+                               ptr[jump + 4]);
+                       jump += 5;
                }
-               ptr += vsa_llen;
 
-               /*
-                *      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);
-                       }
+               print_hex_data(ptr + jump, len, 3);
+       }
+#endif
 
-                       ptr[0] = 0x00;
-                       ptr++;
+       return (ptr + ptr[1]) - start;
+}
 
-                       /*
-                        *      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;
-                       }
-               }
 
-               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;
-       }
+/**
+ * @brief Encode a WiMAX attribute.
+ */
+int rad_vp2wimax(const RADIUS_PACKET *packet,
+                const RADIUS_PACKET *original,
+                const char *secret, const VALUE_PAIR **pvp,
+                uint8_t *ptr, size_t room)
+{
+       int len;
+       uint32_t lvalue;
+       int hdr_len;
+       uint8_t *start = ptr;
+       const VALUE_PAIR *vp = *pvp;
 
        /*
-        *      Insert tags for string attributes.  They go BEFORE
-        *      the string.
+        *      Double-check for WiMAX format.
         */
-       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 (!vp->flags.wimax) {
+               fr_strerror_printf("rad_vp2wimax called for non-WIMAX VSA");
+               return -1;
        }
-       if (!end) return -1;
 
        /*
-        *      Insert tags for integer attributes.  They go at the START
-        *      of the integer, and over-write the first byte.
+        *      Not enough room for:
+        *              attr, len, vendor-id, vsa, vsalen, continuation
         */
-       if (vp->flags.has_tag && (vp->type == PW_TYPE_INTEGER)) {
-               ptr[0] = vp->flags.tag;
-       }
+       if (room < 9) return 0;
 
        /*
-        *      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?
+        *      Build the Vendor-Specific header
         */
-       if ((end == ptr) &&
-           (vp->attribute != PW_CHARGEABLE_USER_IDENTITY)) return 0;
+       ptr = start;
+       ptr[0] = PW_VENDOR_SPECIFIC;
+       ptr[1] = 9;
+       lvalue = htonl(vp->vendor);
+       memcpy(ptr + 2, &lvalue, 4);
+       ptr[6] = (vp->attribute & fr_attr_mask[1]);
+       ptr[7] = 3;
+       ptr[8] = 0;             /* continuation byte */
 
-       len = (end - ptr);
+       hdr_len = 9;
+
+       len = vp2data_any(packet, original, secret, 0, pvp, ptr + ptr[1],
+                         room - hdr_len);
+       if (len <= 0) return len;
 
        /*
-        *      Update the various lengths.
+        *      There may be more than 252 octets of data encoded in
+        *      the attribute.  If so, move the data up in the packet,
+        *      and copy the existing header over.  Set the "C" flag
+        *      ONLY after copying the rest of the data.
         */
-       *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 (len > (255 - ptr[1])) {
+               return attr_shift(start, start + room, ptr, hdr_len, len, 8, 7);
+       }
 
-       return total_length;    /* of attribute */
-}
+       ptr[1] += len;
+       ptr[7] += len;
+
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) {
+               fprintf(fr_log_fp, "\t\t%02x %02x  %02x%02x%02x%02x (%u)  %02x %02x %02x   ",
+                      ptr[0], ptr[1],
+                      ptr[2], ptr[3], ptr[4], ptr[5],
+                      (ptr[3] << 16) | (ptr[4] << 8) | ptr[5],
+                      ptr[6], ptr[7], ptr[8]);
+               print_hex_data(ptr + 9, len, 3);
+       }
+#endif
 
+       return (ptr + ptr[1]) - start;
+}
 
-/*
- *     Encode a packet.
+/**
+ * @brief Encode an RFC format TLV.
+ *
+ *     This could be a standard attribute,
+ *     or a TLV data type.  If it's a standard attribute, then
+ *     vp->attribute == attribute.  Otherwise, attribute may be
+ *     something else.
  */
-int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-              const char *secret)
+static ssize_t vp2attr_rfc(const RADIUS_PACKET *packet,
+                          const RADIUS_PACKET *original,
+                          const char *secret, const VALUE_PAIR **pvp,
+                          unsigned int attribute, uint8_t *ptr, size_t room)
+{
+       ssize_t len;
+
+       if (room < 2) return 0;
+
+       ptr[0] = attribute & 0xff;
+       ptr[1] = 2;
+
+       if (room > ((unsigned) 255 - ptr[1])) room = 255 - ptr[1];
+
+       len = vp2data_any(packet, original, secret, 0, pvp, ptr + ptr[1], room);
+       if (len <= 0) return len;
+
+       ptr[1] += len;
+
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) {
+               fprintf(fr_log_fp, "\t\t%02x %02x  ", ptr[0], ptr[1]);
+               print_hex_data(ptr + 2, len, 3);
+       }
+#endif
+
+       return ptr[1];
+}
+
+
+/**
+ * @brief 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 char *secret, const VALUE_PAIR **pvp,
+                          unsigned int attribute, unsigned int vendor,
+                          uint8_t *ptr, size_t room)
+{
+       ssize_t len;
+       DICT_VENDOR *dv;
+       const VALUE_PAIR *vp = *pvp;
+
+       /*
+        *      Unknown vendor: RFC format.
+        *      Known vendor and RFC format: go do that.
+        */
+       dv = dict_vendorbyvalue(vendor);
+       if (!dv ||
+           (!vp->flags.is_tlv && (dv->type == 1) && (dv->length == 1))) {
+               return vp2attr_rfc(packet, original, secret, pvp,
+                                  attribute, ptr, room);
+       }
+
+       switch (dv->type) {
+       default:
+               fr_strerror_printf("vp2attr_vsa: Internal sanity check failed,"
+                                  " type %u", (unsigned) dv->type);
+               return -1;
+
+       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:
+               fr_strerror_printf("vp2attr_vsa: Internal sanity check failed,"
+                                  " length %u", (unsigned) dv->length);
+               return -1;
+
+       case 0:
+               break;
+
+       case 2:
+               ptr[dv->type] = 0;
+               ptr[dv->type + 1] = dv->type + 2;
+               break;
+
+       case 1:
+               ptr[dv->type] = dv->type + 1;
+               break;
+
+       }
+
+       if (room > ((unsigned) 255 - (dv->type + dv->length))) {
+               room = 255 - (dv->type + dv->length);
+       }
+
+       len = vp2data_any(packet, original, secret, 0, pvp,
+                         ptr + dv->type + dv->length, room);
+       if (len <= 0) return len;
+
+       if (dv->length) ptr[dv->type + dv->length - 1] += len;
+
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) {
+               switch (dv->type) {
+               default:
+                       break;
+
+               case 4:
+                       if ((fr_debug_flag > 3) && fr_log_fp)
+                               fprintf(fr_log_fp, "\t\t%02x%02x%02x%02x ",
+                                       ptr[0], ptr[1], ptr[2], ptr[3]);
+                       break;
+                       
+               case 2:
+                       if ((fr_debug_flag > 3) && fr_log_fp)
+                               fprintf(fr_log_fp, "\t\t%02x%02x ",
+                                       ptr[0], ptr[1]);
+               break;
+               
+               case 1:
+                       if ((fr_debug_flag > 3) && fr_log_fp)
+                               fprintf(fr_log_fp, "\t\t%02x ", ptr[0]);
+                       break;
+               }
+               
+               switch (dv->length) {
+               default:
+                       break;
+
+               case 0:
+                       fprintf(fr_log_fp, "  ");
+                       break;
+
+               case 1:
+                       fprintf(fr_log_fp, "%02x  ",
+                               ptr[dv->type]);
+                       break;
+
+               case 2:
+                       fprintf(fr_log_fp, "%02x%02x  ",
+                               ptr[dv->type], ptr[dv->type] + 1);
+                       break;
+               }
+
+               print_hex_data(ptr + dv->type + dv->length, len, 3);
+       }
+#endif
+
+       return dv->type + dv->length + len;
+}
+
+
+/**
+ * @brief Encode a Vendor-Specific attribute.
+ */
+int rad_vp2vsa(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
+               const char *secret, const VALUE_PAIR **pvp, uint8_t *ptr,
+               size_t room)
+{
+       ssize_t len;
+       uint32_t lvalue;
+       const VALUE_PAIR *vp = *pvp;
+
+       /*
+        *      Double-check for WiMAX format.
+        */
+       if (vp->flags.wimax) {
+               return rad_vp2wimax(packet, original, secret, pvp,
+                                   ptr, room);
+       }
+
+       if (vp->vendor > FR_MAX_VENDOR) {
+               fr_strerror_printf("rad_vp2vsa: Invalid arguments");
+               return -1;
+       }
+
+       /*
+        *      Not enough room for:
+        *              attr, len, vendor-id
+        */
+       if (room < 6) return 0;
+
+       /*
+        *      Build the Vendor-Specific header
+        */
+       ptr[0] = PW_VENDOR_SPECIFIC;
+       ptr[1] = 6;
+       lvalue = htonl(vp->vendor);
+       memcpy(ptr + 2, &lvalue, 4);
+
+       if (room > ((unsigned) 255 - ptr[1])) room = 255 - ptr[1];
+
+       len = vp2attr_vsa(packet, original, secret, pvp,
+                         vp->attribute, vp->vendor,
+                         ptr + ptr[1], room);
+       if (len < 0) return len;
+
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) {
+               fprintf(fr_log_fp, "\t\t%02x %02x  %02x%02x%02x%02x (%u)  ",
+                      ptr[0], ptr[1],
+                      ptr[2], ptr[3], ptr[4], ptr[5],
+                      (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]);
+               print_hex_data(ptr + 6, len, 3);
+       }
+#endif
+
+       ptr[1] += len;
+
+       return ptr[1];
+}
+
+
+/**
+ * @brief Encode an RFC standard attribute 1..255
+ */
+int rad_vp2rfc(const RADIUS_PACKET *packet,
+              const RADIUS_PACKET *original,
+              const char *secret, const VALUE_PAIR **pvp,
+              uint8_t *ptr, size_t room)
+{
+       const VALUE_PAIR *vp = *pvp;
+
+       if (vp->vendor != 0) {
+               fr_strerror_printf("rad_vp2rfc called with VSA");
+               return -1;
+       }
+
+       if ((vp->attribute == 0) || (vp->attribute > 255)) {
+               fr_strerror_printf("rad_vp2rfc called with non-standard attribute %u", vp->attribute);
+               return -1;
+       }
+
+       /*
+        *      Only CUI is allowed to have zero length.
+        *      Thank you, WiMAX!
+        */
+       if ((vp->length == 0) &&
+           (vp->attribute == PW_CHARGEABLE_USER_IDENTITY)) {
+               ptr[0] = PW_CHARGEABLE_USER_IDENTITY;
+               ptr[1] = 2;
+
+               *pvp = vp->next;
+               return 2;
+       }
+
+       return vp2attr_rfc(packet, original, secret, pvp, vp->attribute,
+                          ptr, room);
+}
+
+
+/**
+ * @brief 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 **pvp, uint8_t *start,
+               size_t room)
+{
+       const VALUE_PAIR *vp;
+
+       if (!pvp || !*pvp || !start || (room <= 2)) return -1;
+
+       vp = *pvp;
+
+       /*
+        *      RFC format attributes take the fast path.
+        */
+       if (vp->vendor == 0) {
+               if (vp->attribute > 255) return 0;
+
+               /*
+                *      Message-Authenticator is hard-coded.
+                */
+               if (vp->attribute == PW_MESSAGE_AUTHENTICATOR) {
+                       if (room < 18) return -1;
+                       
+                       debug_pair(vp);
+                       start[0] = PW_MESSAGE_AUTHENTICATOR;
+                       start[1] = 18;
+                       memset(start + 2, 0, 16);
+#ifndef NDEBUG
+                       if ((fr_debug_flag > 3) && fr_log_fp) {
+                               fprintf(fr_log_fp, "\t\t50 12 ...\n");
+                       }
+#endif
+
+                       *pvp = (*pvp)->next;
+                       return 18;
+               }
+
+               return rad_vp2rfc(packet, original, secret, pvp,
+                                 start, room);
+       }
+
+       if (vp->vendor > FR_MAX_VENDOR) {
+               return rad_vp2extended(packet, original, secret, pvp,
+                                      start, room);
+       }
+
+       if (vp->flags.wimax) {
+               return rad_vp2wimax(packet, original, secret, pvp,
+                                   start, room);
+       }
+
+       return rad_vp2vsa(packet, original, secret, pvp,
+                         start, room);
+}
+
+
+/**
+ * @brief Encode a packet.
+ */
+int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
+              const char *secret)
 {
        radius_packet_t *hdr;
        uint8_t         *ptr;
        uint16_t        total_length;
        int             len;
-       VALUE_PAIR      *reply;
+       const VALUE_PAIR        *reply;
        const char      *what;
        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];
@@ -1219,12 +1675,18 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
        /*
         *      Loop over the reply attributes for the packet.
         */
-       for (reply = packet->vps; reply; reply = reply->next) {
+       reply = packet->vps;
+       while (reply) {
+               size_t last_len;
+               const char *last_name = NULL;
+
                /*
-                *      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
@@ -1233,9 +1695,11 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                        if (reply->attribute == PW_RAW_ATTRIBUTE) {
                                memcpy(ptr, reply->vp_octets, reply->length);
                                len = reply->length;
+                               reply = reply->next;
                                goto next;
                        }
 #endif
+                       reply = reply->next;
                        continue;
                }
 
@@ -1244,60 +1708,36 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                 *      length and initial value.
                 */
                if (reply->attribute == PW_MESSAGE_AUTHENTICATOR) {
-                       reply->length = AUTH_VECTOR_LEN;
-                       memset(reply->vp_strvalue, 0, AUTH_VECTOR_LEN);
-
                        /*
                         *      Cache the offset to the
                         *      Message-Authenticator
                         */
                        packet->offset = total_length;
+                       last_len = 16;
+               } else {
+                       last_len = reply->length;
                }
+               last_name = reply->name;
 
-               /*
-                *      Print out ONLY the attributes which
-                *      we're sending over the wire, and print
-                *      them out BEFORE they're encrypted.
-                */
-               debug_pair(reply);
-
-               /*
-                *      Print them in order, even if they were encoded
-                *      already.
-                */
-               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;
-                       }
-
-                       /*
-                        *      The encoded flag MUST be set in reply!
-                        */
-                       reply = reply->next;
-               }
-
-               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) {
-                       break;
+               if (len == 0) {
+                       if (last_len != 0) {
+                               DEBUG("WARNING: Failed encoding attribute %s\n", last_name);
+                       } else {
+                               DEBUG("WARNING: Skipping zero-length attribute %s\n", last_name);
+                       }
                }
 
-       next:
+#ifndef NDEBUG
+       next:                   /* Used only for Raw-Attribute */
+#endif
                ptr += len;
                total_length += len;
        } /* done looping over all attributes */
@@ -1327,8 +1767,8 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
 }
 
 
-/*
- *     Sign a previously encoded packet.
+/**
+ * @brief Sign a previously encoded packet.
  */
 int rad_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
             const char *secret)
@@ -1357,8 +1797,12 @@ int rad_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                uint8_t calc_auth_vector[AUTH_VECTOR_LEN];
 
                switch (packet->code) {
-               case PW_ACCOUNTING_REQUEST:
                case PW_ACCOUNTING_RESPONSE:
+                       if (original && original->code == PW_STATUS_SERVER) {
+                               goto do_ack;
+                       }
+
+               case PW_ACCOUNTING_REQUEST:
                case PW_DISCONNECT_REQUEST:
                case PW_DISCONNECT_ACK:
                case PW_DISCONNECT_NAK:
@@ -1368,6 +1812,7 @@ int rad_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                        memset(hdr->vector, 0, AUTH_VECTOR_LEN);
                        break;
 
+               do_ack:
                case PW_AUTHENTICATION_ACK:
                case PW_AUTHENTICATION_REJECT:
                case PW_ACCESS_CHALLENGE:
@@ -1440,8 +1885,8 @@ int rad_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
        return 0;
 }
 
-/*
- *     Reply to the request.  Also attach
+/**
+ * @brief Reply to the request.  Also attach
  *     reply attribute value pairs and any user message provided.
  */
 int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
@@ -1495,12 +1940,16 @@ 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);
                }
        }
 
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) rad_print_hex(packet);
+#endif
+
        /*
         *      And send it on it's way.
         */
@@ -1509,15 +1958,17 @@ int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                          &packet->dst_ipaddr, packet->dst_port);
 }
 
-/*
- *     Do a comparison of two authentication digests by comparing
- *     the FULL digest.  Otehrwise, the server can be subject to
+/**
+ * @brief Do a comparison of two authentication digests by comparing
+ *     the FULL digest.
+ *
+ *     Otherwise, the server can be subject to
  *     timing attacks that allow attackers find a valid message
  *     authenticator.
  *
  *     http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf
  */
-static int digest_cmp(const uint8_t *a, const uint8_t *b, size_t length)
+int rad_digest_cmp(const uint8_t *a, const uint8_t *b, size_t length)
 {
        int result = 0;
        size_t i;
@@ -1530,8 +1981,8 @@ static int digest_cmp(const uint8_t *a, const uint8_t *b, size_t length)
 }
 
 
-/*
- *     Validates the requesting client NAS.  Calculates the
+/**
+ * @brief Validates the requesting client NAS.  Calculates the
  *     signature based on the clients private key.
  */
 static int calc_acctdigest(RADIUS_PACKET *packet, const char *secret)
@@ -1558,13 +2009,13 @@ static int calc_acctdigest(RADIUS_PACKET *packet, const char *secret)
        /*
         *      Return 0 if OK, 2 if not OK.
         */
-       if (digest_cmp(digest, packet->vector, AUTH_VECTOR_LEN) != 0) return 2;
+       if (rad_digest_cmp(digest, packet->vector, AUTH_VECTOR_LEN) != 0) return 2;
        return 0;
 }
 
 
-/*
- *     Validates the requesting client NAS.  Calculates the
+/**
+ * @brief Validates the requesting client NAS.  Calculates the
  *     signature based on the clients private key.
  */
 static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original,
@@ -1601,13 +2052,100 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original,
        /*
         *      Return 0 if OK, 2 if not OK.
         */
-       if (digest_cmp(packet->vector, calc_digest, AUTH_VECTOR_LEN) != 0) return 2;
+       if (rad_digest_cmp(packet->vector, calc_digest, AUTH_VECTOR_LEN) != 0) return 2;
        return 0;
 }
 
 
-/*
- *     See if the data pointed to by PTR is a valid RADIUS packet.
+/**
+ * @brief Check if a set of RADIUS formatted TLVs are OK.
+ */
+int rad_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)) {
+               fr_strerror_printf("rad_tlv_ok: Invalid arguments");
+               return -1;
+       }
+
+       while (data < end) {
+               size_t attrlen;
+
+               if ((data + dv_type + dv_length) > end) {
+                       fr_strerror_printf("Attribute header overflow");
+                       return -1;
+               }
+
+               switch (dv_type) {
+               case 4:
+                       if ((data[0] == 0) && (data[1] == 0) &&
+                           (data[2] == 0) && (data[3] == 0)) {
+                       zero:
+                               fr_strerror_printf("Invalid attribute 0");
+                               return -1;
+                       }
+
+                       if (data[0] != 0) {
+                               fr_strerror_printf("Invalid attribute > 2^24");
+                               return -1;
+                       }
+                       break;
+
+               case 2:
+                       if ((data[1] == 0) && (data[1] == 0)) goto zero;
+                       break;
+
+               case 1:
+                       if (data[0] == 0) goto zero;
+                       break;
+
+               default:
+                       fr_strerror_printf("Internal sanity check failed");
+                       return -1;
+               }
+
+               switch (dv_length) {
+               case 0:
+                       return 0;
+
+               case 2:
+                       if (data[dv_type + 1] != 0) {
+                               fr_strerror_printf("Attribute is longer than 256 octets");
+                               return -1;
+                       }
+                       /* FALL-THROUGH */
+               case 1:
+                       attrlen = data[dv_type + dv_length - 1];
+                       break;
+
+
+               default:
+                       fr_strerror_printf("Internal sanity check failed");
+                       return -1;
+               }
+
+               if (attrlen < (dv_type + dv_length)) {
+                       fr_strerror_printf("Attribute header has invalid length");
+                       return -1;
+               }
+
+               if (attrlen > length) {
+                       fr_strerror_printf("Attribute overflows container");
+                       return -1;
+               }
+
+               data += attrlen;
+               length -= attrlen;
+       }
+
+       return 0;
+}
+
+
+/**
+ * @brief See if the data pointed to by PTR is a valid RADIUS packet.
  *
  *     packet is not 'const * const' because we may update data_len,
  *     if there's more data in the UDP packet than in the RADIUS packet.
@@ -1635,7 +2173,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 +2187,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 +2273,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 +2310,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 +2337,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 +2346,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]) {
@@ -1892,8 +2455,8 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
 }
 
 
-/*
- *     Receive UDP client requests, and fill in
+/**
+ * @brief Receive UDP client requests, and fill in
  *     the basics of a RADIUS_PACKET structure.
  */
 RADIUS_PACKET *rad_recv(int fd, int flags)
@@ -1995,15 +2558,20 @@ 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);
        }
 
+#ifndef NDEBUG
+       if ((fr_debug_flag > 3) && fr_log_fp) rad_print_hex(packet);
+#endif
+
        return packet;
 }
 
 
-/*
- *     Verify the signature of a packet.
+/**
+ * @brief Verify the signature of a packet.
  */
 int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
               const char *secret)
@@ -2042,8 +2610,13 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        default:
                                break;
 
-                       case PW_ACCOUNTING_REQUEST:
                        case PW_ACCOUNTING_RESPONSE:
+                               if (original &&
+                                   (original->code == PW_STATUS_SERVER)) {
+                                       goto do_ack;
+                               }
+
+                       case PW_ACCOUNTING_REQUEST:
                        case PW_DISCONNECT_REQUEST:
                        case PW_DISCONNECT_ACK:
                        case PW_DISCONNECT_NAK:
@@ -2053,6 +2626,7 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                                memset(packet->data + 4, 0, AUTH_VECTOR_LEN);
                                break;
 
+                       do_ack:
                        case PW_AUTHENTICATION_ACK:
                        case PW_AUTHENTICATION_REJECT:
                        case PW_ACCESS_CHALLENGE:
@@ -2067,7 +2641,7 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        fr_hmac_md5(packet->data, packet->data_len,
                                    (const uint8_t *) secret, strlen(secret),
                                    calc_auth_vector);
-                       if (digest_cmp(calc_auth_vector, msg_auth_vector,
+                       if (rad_digest_cmp(calc_auth_vector, msg_auth_vector,
                                   sizeof(calc_auth_vector)) != 0) {
                                char buffer[32];
                                fr_strerror_printf("Received packet from %s with invalid Message-Authenticator!  (Shared secret is incorrect.)",
@@ -2126,7 +2700,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 +2721,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;
@@ -2173,23 +2746,149 @@ 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 uint8_t *data, VALUE_PAIR *vp)
+/**
+ * @brief Create a "raw" attribute from the attribute contents.
+ */
+static ssize_t data2vp_raw(UNUSED const RADIUS_PACKET *packet,
+                          UNUSED const RADIUS_PACKET *original,
+                          UNUSED const char *secret,
+                          unsigned int attribute, unsigned int vendor,
+                          const uint8_t *data, size_t length,
+                          VALUE_PAIR **pvp)
 {
-       int offset = 0;
+       VALUE_PAIR *vp;
 
        /*
-        *      If length is greater than 253, something is SERIOUSLY
-        *      wrong.
+        *      Keep the next function happy.
         */
-       if (length > 253) length = 253; /* paranoia (pair-anoia?) */
+       vp = pairalloc(NULL);
+       vp = paircreate_raw(attribute, vendor, PW_TYPE_OCTETS, vp);
+       if (!vp) {
+               fr_strerror_printf("data2vp_raw: Failed creating attribute");
+               return -1;
+       }
 
        vp->length = length;
-       vp->operator = T_OP_EQ;
-       vp->next = NULL;
+
+       /*
+        *      If it's short, put it into the array.  If it's too
+        *      long, flag it as such, and put it somewhere else;
+        */
+       if (length <= sizeof(vp->vp_octets)) {
+               memcpy(vp->vp_octets, data, length);
+       } else {
+               vp->type |= PW_FLAG_LONG;
+               vp->vp_tlv = malloc(length);
+               if (!vp->vp_tlv) {
+                       pairfree(&vp);
+                       return -1;
+               }
+               memcpy(vp->vp_tlv, data, length);
+       }
+
+       *pvp = vp;
+
+       return length;
+}
+
+
+static ssize_t data2vp_tlvs(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret,
+                           unsigned int attribute, unsigned int vendor,
+                           int nest,
+                           const uint8_t *start, size_t length,
+                           VALUE_PAIR **pvp);
+
+/**
+ * @brief Create any kind of VP from the attribute contents.
+ * @return -1 on error, or "length".
+ */
+static ssize_t data2vp_any(const RADIUS_PACKET *packet,
+                          const RADIUS_PACKET *original,
+                          const char *secret, int nest,
+                          unsigned int attribute, unsigned int vendor,
+                          const uint8_t *data, size_t length,
+                          VALUE_PAIR **pvp)
+{
+       int data_offset = 0;
+       DICT_ATTR *da;
+       VALUE_PAIR *vp = NULL;
+       uint8_t buffer[256];
+
+       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 = dict_attrbyvalue(attribute, vendor);
+
+       /*
+        *      Unknown attribute.  Create it as a "raw" attribute.
+        */
+       if (!da) {
+               VP_TRACE("Not found %u.%u\n", vendor, attribute);
+       raw:
+               if (vp) pairfree(&vp);
+               return data2vp_raw(packet, original, secret,
+                                  attribute, vendor, data, length, pvp);
+       }
+
+       /*
+        *      TLVs are handled first.  They can't be tagged, and
+        *      they can't be encrypted.
+        */
+       if (da->type == PW_TYPE_TLV) {
+               VP_TRACE("Found TLV %u.%u\n", vendor, attribute);
+               return data2vp_tlvs(packet, original, secret,
+                                   attribute, vendor, nest,
+                                   data, length, pvp);
+       }
+
+       /*
+        *      The data is very long.
+        */
+       if (length > sizeof(vp->vp_octets)) {
+               /*
+                *      Long encrypted attributes are forbidden.
+                */
+               if (da->flags.encrypt != FLAG_ENCRYPT_NONE) goto raw;
+
+#ifndef NDEBUG
+               /*
+                *      Catch programming errors.
+                */
+               if ((da->type != PW_TYPE_STRING) &&
+                   (da->type != PW_TYPE_OCTETS)) goto raw;
+
+#endif
+
+               /*
+                *      FIXME: Figure out how to deal with long
+                *      strings and binary data!
+                */
+               goto raw;
+       }
+
+       /*
+        *      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 = pairalloc(da);
+       if (!vp) return -1;
 
        /*
         *      Handle tags.
@@ -2204,35 +2903,39 @@ 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;
+                               data_offset = 1;
+                       }
                }
        }
 
        /*
         *      Copy the data to be decrypted
         */
-       memcpy(&vp->vp_octets[0], data + offset, length - offset);
-       vp->length -= offset;
+       vp->length = length - data_offset;      
+       memcpy(buffer, data + data_offset, vp->length);
 
        /*
         *      Decrypt the attribute.
         */
-       switch (vp->flags.encrypt) {
+       if (secret && packet) switch (vp->flags.encrypt) {
                /*
                 *  User-Password
                 */
        case FLAG_ENCRYPT_USER_PASSWORD:
                if (original) {
-                       rad_pwdecode((char *)vp->vp_strvalue,
+                       rad_pwdecode(buffer,
                                     vp->length, secret,
                                     original->vector);
                } else {
-                       rad_pwdecode((char *)vp->vp_strvalue,
+                       rad_pwdecode(buffer,
                                     vp->length, secret,
                                     packet->vector);
                }
+               buffer[253] = '\0';
                if (vp->attribute == PW_USER_PASSWORD) {
-                       vp->length = strlen(vp->vp_strvalue);
+                       vp->length = strlen(buffer);
                }
                break;
 
@@ -2243,7 +2946,7 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
        case FLAG_ENCRYPT_TUNNEL_PASSWORD:
                if (!original) goto raw;
 
-               if (rad_tunnel_pwdecode(vp->vp_octets, &vp->length,
+               if (rad_tunnel_pwdecode(buffer, &vp->length,
                                        secret, original->vector) < 0) {
                        goto raw;
                }
@@ -2261,10 +2964,10 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                        make_secret(my_digest,
                                    original->vector,
                                    secret, data);
-                       memcpy(vp->vp_strvalue, my_digest,
+                       memcpy(buffer, my_digest,
                               AUTH_VECTOR_LEN );
-                       vp->vp_strvalue[AUTH_VECTOR_LEN] = '\0';
-                       vp->length = strlen(vp->vp_strvalue);
+                       buffer[AUTH_VECTOR_LEN] = '\0';
+                       vp->length = strlen(buffer);
                }
                break;
 
@@ -2275,51 +2978,49 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
 
        switch (vp->type) {
        case PW_TYPE_STRING:
+               memcpy(vp->vp_strvalue, buffer, vp->length);
+               vp->vp_strvalue[vp->length] = '\0';
+               break;
+
        case PW_TYPE_OCTETS:
        case PW_TYPE_ABINARY:
-               /* nothing more to do */
+               memcpy(vp->vp_octets, buffer, vp->length);
                break;
 
        case PW_TYPE_BYTE:
                if (vp->length != 1) goto raw;
 
-               vp->vp_integer = vp->vp_octets[0];
+               vp->vp_integer = buffer[0];
                break;
 
 
        case PW_TYPE_SHORT:
                if (vp->length != 2) goto raw;
 
-               vp->vp_integer = (vp->vp_octets[0] << 8) | vp->vp_octets[1];
+               vp->vp_integer = (buffer[0] << 8) | buffer[1];
                break;
 
        case PW_TYPE_INTEGER:
                if (vp->length != 4) goto raw;
 
-               memcpy(&vp->vp_integer, vp->vp_octets, 4);
+               memcpy(&vp->vp_integer, buffer, 4);
                vp->vp_integer = ntohl(vp->vp_integer);
 
                if (vp->flags.has_tag) vp->vp_integer &= 0x00ffffff;
+               break;
 
-               /*
-                *      Try to get named VALUEs
-                */
-               {
-                       DICT_VALUE *dval;
-                       dval = dict_valbyattr(vp->attribute,
-                                             vp->vp_integer);
-                       if (dval) {
-                               strlcpy(vp->vp_strvalue,
-                                       dval->name,
-                                       sizeof(vp->vp_strvalue));
-                       }
-               }
+       case PW_TYPE_INTEGER64:
+               if (vp->length != 8) goto raw;
+
+               /* vp_integer64 is a union with vp_octets */
+               memcpy(&vp->vp_integer64, buffer, 8);
+               vp->vp_integer64 = ntohll(vp->vp_integer64);
                break;
 
        case PW_TYPE_DATE:
                if (vp->length != 4) goto raw;
 
-               memcpy(&vp->vp_date, vp->vp_octets, 4);
+               memcpy(&vp->vp_date, buffer, 4);
                vp->vp_date = ntohl(vp->vp_date);
                break;
 
@@ -2327,7 +3028,7 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
        case PW_TYPE_IPADDR:
                if (vp->length != 4) goto raw;
 
-               memcpy(&vp->vp_ipaddr, vp->vp_octets, 4);
+               memcpy(&vp->vp_ipaddr, buffer, 4);
                break;
 
                /*
@@ -2335,7 +3036,7 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                 */
        case PW_TYPE_IFID:
                if (vp->length != 8) goto raw;
-               /* vp->vp_ifid == vp->vp_octets */
+               memcpy(&vp->vp_ifid, buffer, 8);
                break;
 
                /*
@@ -2343,7 +3044,7 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                 */
        case PW_TYPE_IPV6ADDR:
                if (vp->length != 16) goto raw;
-               /* vp->vp_ipv6addr == vp->vp_octets */
+               memcpy(&vp->vp_ipv6addr, buffer, 16);
                break;
 
                /*
@@ -2357,14 +3058,15 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                 */
        case PW_TYPE_IPV6PREFIX:
                if (vp->length < 2 || vp->length > 18) goto raw;
-               if (vp->vp_octets[1] > 128) goto raw;
+               if (buffer[1] > 128) goto raw;
 
                /*
                 *      FIXME: double-check that
                 *      (vp->vp_octets[1] >> 3) matches vp->length + 2
                 */
+               memcpy(&vp->vp_ipv6prefix, buffer, vp->length);
                if (vp->length < 18) {
-                       memset(vp->vp_octets + vp->length, 0,
+                       memset(((uint8_t *)vp->vp_ipv6prefix) + vp->length, 0,
                               18 - vp->length);
                }
                break;
@@ -2376,629 +3078,821 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                 *      Overload vp_integer for ntohl, which takes
                 *      uint32_t, not int32_t
                 */
-               memcpy(&vp->vp_integer, vp->vp_octets, 4);
+               memcpy(&vp->vp_integer, buffer, 4);
                vp->vp_integer = ntohl(vp->vp_integer);
-               memcpy(&vp->vp_signed, &vp->vp_integer, 4);
                break;
 
        case PW_TYPE_TLV:
-               vp->length = length;
-               vp->vp_tlv = malloc(length);
-               if (!vp->vp_tlv) {
-                       pairfree(&vp);
-                       fr_strerror_printf("No memory");
-                       return NULL;
+               pairfree(&vp);
+               fr_strerror_printf("data2vp_any: Internal sanity check failed");
+               return -1;
+
+       case PW_TYPE_COMBO_IP:
+               if (vp->length == 4) {
+                       vp->type = PW_TYPE_IPADDR;
+                       memcpy(&vp->vp_ipaddr, buffer, 4);
+                       break;
+
+               } else if (vp->length == 16) {
+                       vp->type = PW_TYPE_IPV6ADDR;
+                       memcpy(&vp->vp_ipv6addr, buffer, 16);
+                       break;
+
                }
-               memcpy(vp->vp_tlv, data, length);
+               /* FALL-THROUGH */
+
+       default:
+               goto raw;
+       }
+
+       *pvp = vp;
+
+       return length;
+}
+
+
+/**
+ * @brief Convert a top-level VSA to a VP.
+ */
+static ssize_t attr2vp_vsa(const RADIUS_PACKET *packet,
+                          const RADIUS_PACKET *original,
+                          const char *secret, 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)) {
+               fr_strerror_printf("attr2vp_vsa: Failure to call rad_tlv_ok");
+               return -1;
+       }
+#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:
+               fr_strerror_printf("attr2vp_vsa: Internal sanity check failed");
+               return -1;
+       }
+
+       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;
 
-       case PW_TYPE_COMBO_IP:
-               if (vp->length == 4) {
-                       vp->type = PW_TYPE_IPADDR;
-                       memcpy(&vp->vp_ipaddr, vp->vp_octets, 4);
-                       break;
+       default:
+               fr_strerror_printf("attr2vp_vsa: Internal sanity check failed");
+               return -1;
+       }
+
+#ifndef NDEBUG
+       if (attrlen <= (ssize_t) (dv_type + dv_length)) {
+               fr_strerror_printf("attr2vp_vsa: Failure to call rad_tlv_ok");
+               return -1;
+       }
+#endif
+
+       attrlen -= (dv_type + dv_length);
+       
+       my_len = data2vp_any(packet, original, secret, 0,
+                            attribute, vendor,
+                            data + dv_type + dv_length, attrlen, pvp);
+       if (my_len < 0) return my_len;
+
+#ifndef NDEBUG
+       if (my_len != attrlen) {
+               pairfree(pvp);
+               fr_strerror_printf("attr2vp_vsa: Incomplete decode %d != %d",
+                                  (int) my_len, (int) attrlen);
+               return -1;
+       }
+#endif
+
+       return dv_type + dv_length + attrlen;
+}
+
+/**
+ * @brief Convert one or more TLVs to VALUE_PAIRs.  This function can
+ *     be called recursively...
+ */
+static ssize_t data2vp_tlvs(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret,
+                           unsigned int attribute, unsigned int vendor,
+                           int nest,
+                           const uint8_t *start, size_t length,
+                           VALUE_PAIR **pvp)
+{
+       size_t dv_type, dv_length;
+       const uint8_t *data, *end;
+       VALUE_PAIR *head, **last, *vp;
+
+       data = start;
+
+       /*
+        *      The default format for a VSA is the RFC recommended
+        *      format.
+        */
+       dv_type = 1;
+       dv_length = 1;
+
+       /*
+        *      Top-level TLVs can be of a weird format.  TLVs
+        *      encapsulated in a TLV can only be in the RFC format.
+        */
+       if (nest == 1) {
+               DICT_VENDOR *dv;
+               dv = dict_vendorbyvalue(vendor);        
+               if (dv) {
+                       dv_type = dv->type;
+                       dv_length = dv->length;
+                       /* dict.c enforces sane values on the above fields */
+               }
+       }
+
+       if (nest >= fr_attr_max_tlv) {
+               fr_strerror_printf("data2vp_tlvs: Internal sanity check failed in recursion");
+               return -1;
+       }
+
+       /*
+        *      The VSAs do not exactly fill the data,
+        *      The *entire* TLV is malformed.
+        */
+       if (rad_tlv_ok(data, length, dv_type, dv_length) < 0) {
+               VP_TRACE("TLV malformed %u.%u\n", vendor, attribute);
+               return data2vp_raw(packet, original, secret,
+                                  attribute, vendor, data, length, pvp);
+       }
+
+       end = data + length;
+       head = NULL;
+       last = &head;
+
+       while (data < end) {
+               unsigned int my_attr;
+               unsigned int my_len;
+
+#ifndef NDEBUG
+               if ((data + dv_type + dv_length) > end) {
+                       fr_strerror_printf("data2vp_tlvs: Internal sanity check failed in tlvs: Insufficient data");
+                       pairfree(&head);
+                       return -1;
+               }
+#endif
+
+               switch (dv_type) {
+               case 1:
+                       my_attr = attribute;
+                       my_attr |= ((data[0] & fr_attr_mask[nest + 1])
+                                   << fr_attr_shift[nest + 1]);
+                       break;
+               case 2:
+                       my_attr = (data[0] << 8) | data[1];
+                       break;
+
+               case 4:
+                       my_attr = (data[1] << 16) | (data[1] << 8) | data[3];
+                       break;
+
+               default:
+                       fr_strerror_printf("data2vp_tlvs: Internal sanity check failed");
+                       return -1;
+               }
+
+               switch (dv_length) {
+               case 0:
+                       my_len = length;
+                       break;
+
+               case 1:
+               case 2:
+                       my_len = data[dv_type + dv_length - 1];
+                       break;
+
+               default:
+                       fr_strerror_printf("data2vp_tlvs: Internal sanity check failed");
+                       return -1;
+               }
+               
+#ifndef NDEBUG
+               if (my_len < (dv_type + dv_length)) {
+                       fr_strerror_printf("data2vp_tlvs: Internal sanity check failed in tlvs: underflow");
+                       pairfree(&head);
+                       return -1;
+               }
+
+               if ((data + my_len) > end) {
+                       fr_strerror_printf("data2vp_tlvs: Internal sanity check failed in tlvs: overflow");
+                       pairfree(&head);
+                       return -1;
+               }
+#endif
+
+               my_len -= dv_type + dv_length;
+
+               /*
+                *      If this returns > 0, it returns "my_len"
+                */
+               if (data2vp_any(packet, original, secret, nest + 1,
+                               my_attr, vendor,
+                               data + dv_type + dv_length, my_len, &vp) < 0) {
+                       pairfree(&head);
+                       return -1;
+               }
+
+               data += my_len + dv_type + dv_length;
+               *last = vp;
+
+               while (vp) {
+                       last = &(vp->next);
+                       vp = vp->next;
+               }
+       }
+
+       *pvp = head;
+       return data - start;
+}
+
+
+/**
+ * @brief Group "continued" attributes together, and create VPs from them.
+ *
+ *     The caller ensures that the RADIUS packet is OK, and that the
+ *     continuations have all been checked.
+ */
+static ssize_t data2vp_continued(const RADIUS_PACKET *packet,
+                                const RADIUS_PACKET *original,
+                                const char *secret,
+                                const uint8_t *start, size_t length,
+                                VALUE_PAIR **pvp, int nest,
+                                unsigned int attribute, unsigned int vendor,
+                                int first_offset, int later_offset,
+                                ssize_t attrlen)
+{
+       ssize_t left;
+       uint8_t *attr, *ptr;
+       const uint8_t *data;
+
+       attr = malloc(attrlen);
+       if (!attr) {
+               fr_strerror_printf("Out of memory");
+               return -1;
+       }
+
+       left = attrlen;
+       ptr = attr;
+       data = start;
+
+       /*
+        *      Do the first one.
+        */
+       memcpy(ptr, data + first_offset, data[1] - first_offset);
+       ptr += data[1] - first_offset;
+       left -= data[1] - first_offset;
+       data += data[1];
+
+       while (left > 0) {
+#ifndef NDEBUG
+               if (data >= (start + length)) {
+                       free(attr);
+                       fr_strerror_printf("data2vp_continued: Internal sanity check failed");
+                       return -1;
+               }
+#endif
+               memcpy(ptr, data + later_offset, data[1] - later_offset);
+               ptr += data[1] - later_offset;
+               left -= data[1] - later_offset;
+               data += data[1];
+       }
+
+       left = data2vp_any(packet, original, secret, nest,
+                          attribute, vendor,
+                          attr, attrlen, pvp);
+       free(attr);
+       if (left < 0) return left;
+
+       return data - start;
+}
+
+
+/**
+ * @brief Create a "raw" VALUE_PAIR from a RADIUS attribute.
+ */
+ssize_t rad_attr2vp_raw(const RADIUS_PACKET *packet,
+                       const RADIUS_PACKET *original,
+                       const char *secret,
+                       const uint8_t *data, size_t length,
+                       VALUE_PAIR **pvp)
+{
+       ssize_t my_len;
+
+       if ((length < 2) || (data[1] < 2) || (data[1] > length)) {
+               fr_strerror_printf("rad_attr2vp_raw: Invalid length");
+               return -1;
+       }
 
-               } else if (vp->length == 16) {
-                       vp->type = PW_TYPE_IPV6ADDR;
-                       /* vp->vp_ipv6addr == vp->vp_octets */
-                       break;
+       my_len = data2vp_raw(packet, original, secret, data[0], 0,
+                            data + 2, data[1] - 2, pvp);
+       if (my_len < 0) return my_len;
+       
+       return data[1];
+}
 
-               }
-               /* FALL-THROUGH */
 
-       default:
-       raw:
-               vp->type = PW_TYPE_OCTETS;
-               vp->length = length;
-               memcpy(vp->vp_octets, data, length);
+/**
+ * @brief Get the length of the data portion of all of the contiguous
+ *     continued attributes.
+ * @return
+ *     0 for "no continuation"
+ *     -1 on malformed packets (continuation followed by non-wimax, etc.)
+ */
+static ssize_t wimax_attrlen(uint32_t vendor,
+                            const uint8_t *start, const uint8_t *end)
+{
+       ssize_t total;
+       const uint8_t *data = start;
 
+       if ((data[8] & 0x80) == 0) return 0;
+       total = data[7] - 3;
+       data += data[1];
 
-               /*
-                *      Ensure there's no encryption or tag stuff,
-                *      we just pass the attribute as-is.
-                */
-               memset(&vp->flags, 0, sizeof(vp->flags));
+       while (data < end) {
+               
+               if ((data + 9) > end) return -1;
+
+               if ((data[0] != PW_VENDOR_SPECIFIC) ||
+                   (data[1] < 9) ||
+                   (memcmp(data + 2, &vendor, 4) != 0) ||
+                   (data[6] != start[6]) ||
+                   ((data[7] + 6) != data[1])) return -1;
+
+               total += data[7] - 3;
+               if ((data[8] & 0x80) == 0) break;
+               data += data[1];
        }
 
-       return vp;
+       return total;
 }
 
-static void rad_sortvp(VALUE_PAIR **head)
+
+/**
+ * @brief Get the length of the data portion of all of the contiguous
+ *     continued attributes.
+ *
+ * @return
+ *     0 for "no continuation"
+ *     -1 on malformed packets (continuation followed by non-wimax, etc.)
+ */
+static ssize_t extended_attrlen(const uint8_t *start, const uint8_t *end)
 {
-       int swapped;
-       VALUE_PAIR *vp, **tail;
+       ssize_t total;
+       const uint8_t *data = start;
 
-       /*
-        *      Walk over the VP's, sorting them in order.  Did I
-        *      mention that I hate WiMAX continuations?
-        *
-        *      And bubble sort!  WTF is up with that?
-        */
-       do {
-               swapped = 0;
-               tail = head;
-               while (*tail) {
-                       vp = *tail;
-                       if (!vp->next) break;
-
-                       if (vp->attribute > vp->next->attribute) {
-                               *tail = vp->next;
-                               vp->next = (*tail)->next;
-                               (*tail)->next = vp;
-                               swapped = 1;
-                       }
-                       tail = &(vp->next);
-               }
-       } while (swapped);
+       if ((data[3] & 0x80) == 0) return 0;
+       total = data[1] - 4;
+       data += data[1];
+       
+       while (data < end) {
+               if ((data + 4) > end) return -1;
+
+               if ((data[0] != start[0]) ||
+                   (data[1] < 4) ||
+                   (data[2] != start[2])) return -1;
+
+               total += data[1] - 4;
+               if ((data[3] & 0x80) == 0) break;
+               data += data[1];
+       }
+
+       return total;
 }
 
 
-/*
- *     Walk the packet, looking for continuations of this attribute.
- *
- *     This is (worst-case) O(N^2) in the number of RADIUS
- *     attributes.  That happens only when perverse clients create
- *     continued attributes, AND separate the fragmented portions
- *     with a lot of other attributes.
- *
- *     Sane clients should put the fragments next to each other, in
- *     which case this is O(N), in the number of fragments.
+/**
+ * @brief Create WiMAX VALUE_PAIRs from a RADIUS attribute.
  */
-static uint8_t *rad_coalesce(unsigned int attribute, size_t length,
-                            uint8_t *data,
-                            size_t packet_length, size_t *ptlv_length)
-                            
+ssize_t rad_attr2vp_wimax(const RADIUS_PACKET *packet,
+                         const RADIUS_PACKET *original,
+                         const char *secret,
+                         const uint8_t *data,  size_t length,
+                         VALUE_PAIR **pvp)
 {
+       ssize_t my_len;
+       unsigned int attribute;
        uint32_t lvalue;
-       size_t tlv_length = length;
-       uint8_t *ptr, *tlv, *tlv_data;
-
-       for (ptr = data + length;
-            ptr != (data + packet_length);
-            ptr += ptr[1]) {
-               if ((ptr[0] != PW_VENDOR_SPECIFIC) ||
-                   (ptr[1] < (2 + 4 + 3)) || /* WiMAX VSA with continuation */
-                   (ptr[2] != 0) || (ptr[3] != 0)) { /* our requirement */
-                       continue;
-               }
-
-               memcpy(&lvalue, ptr + 2, 4); /* Vendor Id */
-               lvalue = ntohl(lvalue);
-               lvalue <<= 16;
-               lvalue |= ptr[2 + 4]; /* add in VSA number */
-               if (lvalue != attribute) continue;
 
-               /*
-                *      If the vendor-length is too small, it's badly
-                *      formed, so we stop.
-                */
-               if ((ptr[2 + 4 + 1]) < 3) break;
+       if ((length < 2) || (data[1] < 2) || (data[1] > length)) {
+               fr_strerror_printf("rad_attr2vp_wimax: Invalid length");
+               return -1;
+       }
 
-               tlv_length += ptr[2 + 4 + 1] - 3;
-               if ((ptr[2 + 4 + 1 + 1] & 0x80) == 0) break;
+       if (data[0] != PW_VENDOR_SPECIFIC) {
+               fr_strerror_printf("rad_attr2vp_wimax: Invalid attribute");
+               return -1;
        }
 
-       tlv = tlv_data = malloc(tlv_length);
-       if (!tlv_data) return NULL;
+       /*
+        *      Not enough room for a Vendor-Id. + WiMAX header
+        */
+       if (data[1] < 9) {
+               return rad_attr2vp_raw(packet, original, secret,
+                                      data, length, pvp);
+       }
 
-       memcpy(tlv, data, length);
-       tlv += length;
+       memcpy(&lvalue, data + 2, 4);
+       lvalue = ntohl(lvalue);
 
        /*
-        *      Now we walk the list again, copying the data over to
-        *      our newly created memory.
+        *      Not WiMAX format.
         */
-       for (ptr = data + length;
-            ptr != (data + packet_length);
-            ptr += ptr[1]) {
-               int this_length;
+       if (lvalue != VENDORPEC_WIMAX) {
+               DICT_VENDOR *dv;
 
-               if ((ptr[0] != PW_VENDOR_SPECIFIC) ||
-                   (ptr[1] < (2 + 4 + 3)) || /* WiMAX VSA with continuation */
-                   (ptr[2] != 0) || (ptr[3] != 0)) { /* our requirement */
-                       continue;
+               dv = dict_vendorbyvalue(lvalue);
+               if (!dv || !dv->flags) {
+                       fr_strerror_printf("rad_attr2vp_wimax: Not a WiMAX attribute");
+                       return -1;
                }
+       }
 
-               memcpy(&lvalue, ptr + 2, 4);
-               lvalue = ntohl(lvalue);
-               lvalue <<= 16;
-               lvalue |= ptr[2 + 4];
-               if (lvalue != attribute) continue;
+       /*
+        *      The WiMAX attribute is encapsulated in a VSA.  If the
+        *      WiMAX length disagrees with the VSA length, it's malformed.
+        */
+       if ((data[7] + 6) != data[1]) {
+               return rad_attr2vp_raw(packet, original, secret,
+                                      data, length, pvp);
+       }
 
-               /*
-                *      If the vendor-length is too small, it's badly
-                *      formed, so we stop.
-                */
-               if ((ptr[2 + 4 + 1]) < 3) break;
+       attribute = data[6];
 
-               this_length = ptr[2 + 4 + 1] - 3;
-               memcpy(tlv, ptr + 2 + 4 + 3, this_length);
-               tlv += this_length;
+       /*
+        *      Attribute is continued.  Do some more work.
+        */
+       if (data[8] != 0) {
+               my_len = wimax_attrlen(htonl(lvalue), data, data + length);
+               if (my_len < 0) {
+                       return rad_attr2vp_raw(packet, original, secret,
+                                              data, length, pvp);
+               }
 
-               ptr[2 + 4] = 0; /* What a hack! */
-               if ((ptr[2 + 4 + 1 + 1] & 0x80) == 0) break;
+               return data2vp_continued(packet, original, secret,
+                                        data, length, pvp, 0,
+                                        data[6], lvalue,
+                                        9, 9, my_len);
        }
 
-       *ptlv_length = tlv_length;
-       return tlv_data;
+       my_len = data2vp_any(packet, original, secret, 0, attribute, lvalue,
+                            data + 9, data[1] - 9, pvp);
+       if (my_len < 0) return my_len;
+
+       return data[1];
 }
 
-/*
- *     Start at the *data* portion of a continued attribute.  search
- *     through the rest of the attributes to find a matching one, and
- *     add it's contents to our contents.
+/**
+ * @brief Create Vendor-Specifc VALUE_PAIRs from a RADIUS attribute.
  */
-static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
-                                      const RADIUS_PACKET *original,
-                                      const char *secret, int attribute,
-                                      int length, /* CANNOT be zero */
-                                      uint8_t *data, size_t packet_length,
-                                      int flag, DICT_ATTR *da)
+ssize_t rad_attr2vp_vsa(const RADIUS_PACKET *packet,
+                       const RADIUS_PACKET *original,
+                       const char *secret,
+                       const uint8_t *data, size_t length,
+                       VALUE_PAIR **pvp)
 {
-       size_t tlv_length, left;
-       uint8_t *ptr;
-       uint8_t *tlv_data;
-       VALUE_PAIR *vp, *head, **tail;
+       size_t dv_type, dv_length;
+       ssize_t my_len;
+       uint32_t lvalue;
+       DICT_VENDOR *dv;
 
-       /*
-        *      Ensure we have data that hasn't been split across
-        *      multiple attributes.
-        */
-       if (flag) {
-               tlv_data = rad_coalesce(attribute, length,
-                                       data, packet_length, &tlv_length);
-               if (!tlv_data) return NULL;
-       } else {
-               tlv_data = data;
-               tlv_length = length;
+       if ((length < 2) || (data[1] < 2) || (data[1] > length)) {
+               fr_strerror_printf("rad_attr2vp_vsa: Invalid length");
+               return -1;
        }
 
-       /*
-        *      Non-TLV types cannot be continued across multiple
-        *      attributes.  This is true even of keys that are
-        *      encrypted with the tunnel-password method.  The spec
-        *      says that they can be continued... but also that the
-        *      keys are 160 bits, which means that they CANNOT be
-        *      continued.  <sigh>
-        *
-        *      Note that we don't check "flag" here.  The calling
-        *      code ensures that 
-        */
-       if (!da || (da->type != PW_TYPE_TLV)) {
-       not_well_formed:
-               if (tlv_data == data) { /* true if we had 'goto' */
-                       tlv_data = malloc(tlv_length);
-                       if (!tlv_data) return NULL;
-                       memcpy(tlv_data, data, tlv_length);
-               }
-               
-               vp = paircreate(attribute, PW_TYPE_OCTETS);
-               if (!vp) return NULL;
-                       
-               vp->type = PW_TYPE_TLV;
-               vp->flags.encrypt = FLAG_ENCRYPT_NONE;
-               vp->flags.has_tag = 0;
-               vp->flags.is_tlv = 0;
-               vp->vp_tlv = tlv_data;
-               vp->length = tlv_length;
-               return vp;
-       } /* else it WAS a TLV, go decode the sub-tlv's */
+       if (data[0] != PW_VENDOR_SPECIFIC) {
+               fr_strerror_printf("rad_attr2vp_vsa: Invalid attribute");
+               return -1;
+       }
 
        /*
-        *      Now (sigh) we walk over the TLV, seeing if it is
-        *      well-formed.
+        *      Not enough room for a Vendor-Id.
+        *      Or the high octet of the Vendor-Id is set.
         */
-       left = tlv_length;
-       for (ptr = tlv_data;
-            ptr != (tlv_data + tlv_length);
-            ptr += ptr[1]) {
-               if ((left < 2) ||
-                   (ptr[1] < 2) ||
-                   (ptr[1] > left)) {
-                       goto not_well_formed;
-               }
-               left -= ptr[1];
+       if ((data[1] < 6) || (data[2] != 0)) {
+               return rad_attr2vp_raw(packet, original, secret,
+                                      data, length, pvp);
        }
 
+       memcpy(&lvalue, data + 2, 4);
+       lvalue = ntohl(lvalue);
+
        /*
-        *      Now we walk over the TLV *again*, creating sub-tlv's.
+        *      WiMAX gets its own set of magic.
         */
-       head = NULL;
-       tail = &head;
-
-       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 (lvalue == VENDORPEC_WIMAX) {
+       wimax:
+               return rad_attr2vp_wimax(packet, original, secret,
+                                        data, length, pvp);
+       }
 
-               if (!data2vp(packet, original, secret,
-                            ptr[0], ptr[1] - 2, ptr + 2, vp)) {
-                       pairfree(&head);
-                       goto not_well_formed;
-               }
+       dv_type = dv_length = 1;
+       dv = dict_vendorbyvalue(lvalue);
+       if (dv) {
+               dv_type = dv->type;
+               dv_length = dv->length;
 
-               *tail = vp;
-               tail = &(vp->next);
+               if (dv->flags) goto wimax;
        }
 
        /*
-        *      TLV's MAY be continued, but sometimes they're not.
+        *      Attribute is not in the correct form.
         */
-       if (tlv_data != data) free(tlv_data);
+       if (rad_tlv_ok(data + 6, data[1] - 6, dv_type, dv_length) < 0) {
+               return rad_attr2vp_raw(packet, original, secret,
+                                      data, length, pvp);
+       }
 
-       if (head->next) rad_sortvp(&head);
+       my_len = attr2vp_vsa(packet, original, secret,
+                            lvalue, dv_type, dv_length,
+                            data + 6, data[1] - 6, pvp);
+       if (my_len < 0) return my_len;
 
-       return head;
-}
+       /*
+        *      Incomplete decode means that something is wrong
+        *      with the attribute.  Back up, and make it "raw".
+        */
+       if (my_len != (data[1] - 6)) {
+               pairfree(pvp);
+               return rad_attr2vp_raw(packet, original, secret,
+                                      data, length, pvp);
+       }
 
+       return data[1];
+}
 
-/*
- *     Parse a RADIUS attribute into a data structure.
+/**
+ * @brief Create an "extended" VALUE_PAIR from a RADIUS attribute.
  */
-VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-                       const char *secret, int attribute, int length,
-                       const uint8_t *data)
+ssize_t rad_attr2vp_extended(const RADIUS_PACKET *packet,
+                            const RADIUS_PACKET *original,
+                            const char *secret,
+                            const uint8_t *start, size_t length,
+                            VALUE_PAIR **pvp)
 {
-       VALUE_PAIR *vp;
-
-       vp = paircreate(attribute, PW_TYPE_OCTETS);
-       if (!vp) return NULL;
+       unsigned int attribute;
+       int shift = 1;
+       int continued = 0;
+       unsigned int vendor = VENDORPEC_EXTENDED;
+       size_t data_len = length;
+       const uint8_t *data;
+       DICT_ATTR *da;
 
-       return data2vp(packet, original, secret, attribute, length, data, vp);
-}
+       data = start;
 
+       if ((length < 2) || (data[1] < 2) || (data[1] > length)) {
+               fr_strerror_printf("rad_attr2vp_extended: Invalid length");
+               return -1;
+       }
 
-/*
- *     Calculate/check digest, and decode radius attributes.
- *     Returns:
- *     -1 on decoding error
- *     0 on success
- */
-int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
-              const char *secret)
-{
-       uint32_t                lvalue;
-       uint32_t                vendorcode;
-       VALUE_PAIR              **tail;
-       VALUE_PAIR              *pair;
-       uint8_t                 *ptr, *vsa_ptr;
-       int                     packet_length;
-       int                     attribute;
-       int                     attrlen;
-       int                     vendorlen;
-       radius_packet_t         *hdr;
-       int                     vsa_tlen, vsa_llen, vsa_offset;
-       DICT_VENDOR             *dv = NULL;
-       int                     num_attributes = 0;
+       da = dict_attrbyvalue(data[0], vendor);
+       if (!da ||
+           (!da->flags.extended && !da->flags.extended_flags)) {
+               fr_strerror_printf("rad_attr2vp_extended: Attribute is not extended format");
+               return -1;
+       }
 
-       /*
-        *      Extract attribute-value pairs
-        */
-       hdr = (radius_packet_t *)packet->data;
-       ptr = hdr->data;
-       packet_length = packet->data_len - AUTH_HDR_LEN;
+       data = start;
 
        /*
-        *      There may be VP's already in the packet.  Don't
-        *      destroy them.
+        *      No Extended-Type.  It's a raw attribute.
+        *      Also, if there's no data following the Extended-Type,
+        *      it's a raw attribute.
         */
-       for (tail = &packet->vps; *tail != NULL; tail = &((*tail)->next)) {
-               /* nothing */
+       if (data[1] <= 3) {
+       raw:
+               return rad_attr2vp_raw(packet, original, secret, start,
+                                      length, pvp);
        }
 
-       vendorcode = 0;
-       vendorlen  = 0;
-       vsa_tlen = vsa_llen = 1;
-       vsa_offset = 0;
-
        /*
-        *      We have to read at least two bytes.
-        *
-        *      rad_recv() above ensures that this is OK.
+        *      The attribute is "241.1", for example.  Go look that
+        *      up to see what type it is.
         */
-       while (packet_length > 0) {
-               attribute = -1;
-               attrlen = -1;
-
-               /*
-                *      Normal attribute, handle it like normal.
-                */
-               if (vendorcode == 0) {
-                       /*
-                        *      No room to read attr/length,
-                        *      or bad attribute, or attribute is
-                        *      too short, or attribute is too long,
-                        *      stop processing the packet.
-                        */
-                       if ((packet_length < 2) ||
-                           (ptr[0] == 0) ||  (ptr[1] < 2) ||
-                           (ptr[1] > packet_length)) break;
+       attribute = data[0];
+       attribute |= (data[2] << fr_attr_shift[1]);
 
-                       attribute = *ptr++;
-                       attrlen   = *ptr++;
+       da = dict_attrbyvalue(attribute, vendor);
+       if (!da) goto raw;
 
-                       attrlen -= 2;
-                       packet_length  -= 2;
+       vendor = VENDORPEC_EXTENDED;
 
-                       if (attribute != PW_VENDOR_SPECIFIC) goto create_pair;
+       data_len = length;
+       if (data[1] < length) data_len = data[1];
 
-                       /*
-                        *      No vendor code, or ONLY vendor code.
-                        */
-                       if (attrlen <= 4) goto create_pair;
+       data += 3;
+       data_len -= 3;
 
-                       vendorlen = 0;
-               }
+       /*
+        *      If there's supposed to be a flag octet.  If not, it's
+        *      a raw attribute.  If the flag is set, it's supposed to
+        *      be continued.
+        */
+       if (da->flags.extended_flags) {
+               if (data_len == 0) goto raw;
 
+               continued = ((data[0] & 0x80) != 0);
+               data++;
+               data_len--;
+       }
+       
+       /*
+        *      Extended VSAs have 4 octets of
+        *      Vendor-Id followed by one octet of
+        *      Vendor-Type.
+        */
+       if (da->flags.evs) {
+               if (data_len < 5) goto raw;
+               
                /*
-                *      Handle Vendor-Specific
+                *      Vendor Ids can only be 24-bit.
                 */
-               if (vendorlen == 0) {
-                       uint8_t *subptr;
-                       int sublen;
-                       int myvendor;
-
-                       /*
-                        *      attrlen was checked above.
-                        */
-                       memcpy(&lvalue, ptr, 4);
-                       myvendor = ntohl(lvalue);
-
-                       /*
-                        *      Zero isn't allowed.
-                        */
-                       if (myvendor == 0) goto create_pair;
+               if (data[0] != 0) goto raw;
+               
+               vendor = ((data[1] << 16) |
+                         (data[2] << 8) |
+                         data[3]);
+               
+               /*
+                *      Pack the *encapsulating* attribute number into
+                *      the vendor id.  This number should be >= 241.
+                */
+               vendor |= start[0] * FR_MAX_VENDOR;
+               shift = 0;
+               
+               /*
+                *      Over-write the attribute with the
+                *      VSA.
+                */
+               attribute = data[4];
+               data += 5;
+               data_len -= 5;
+       }
 
-                       /*
-                        *      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.
-                        */
-                       if (myvendor > 65535) goto create_pair;
-
-                       vsa_tlen = vsa_llen = 1;
-                       vsa_offset = 0;
-                       dv = dict_vendorbyvalue(myvendor);
-                       if (dv) {
-                               vsa_tlen = dv->type;
-                               vsa_llen = dv->length;
-                               if (dv->flags) vsa_offset = 1;
-                       }
+       if (continued) {
+               int first_offset = 4;
+               ssize_t my_len;
 
-                       /*
-                        *      Sweep through the list of VSA's,
-                        *      seeing if they exactly fill the
-                        *      outer Vendor-Specific attribute.
-                        *
-                        *      If not, create a raw Vendor-Specific.
-                        */
-                       subptr = ptr + 4;
-                       sublen = attrlen - 4;
+               if (vendor != VENDORPEC_EXTENDED) first_offset += 5;
 
-                       /*
-                        *      See if we can parse it.
-                        */
-                       do {
-                               int myattr = 0;
-
-                               /*
-                                *      Not enough room for one more
-                                *      attribute.  Die!
-                                */
-                               if (sublen < (vsa_tlen + vsa_llen + vsa_offset)) goto create_pair;
-
-                               /*
-                                *      Ensure that the attribute number
-                                *      is OK.
-                                */
-                               switch (vsa_tlen) {
-                               case 1:
-                                       myattr = subptr[0];
-                                       break;
-
-                               case 2:
-                                       myattr = (subptr[0] << 8) | subptr[1];
-                                       break;
-
-                               case 4:
-                                       if ((subptr[0] != 0) ||
-                                           (subptr[1] != 0)) goto create_pair;
-
-                                       myattr = (subptr[2] << 8) | subptr[3];
-                                       break;
-
-                                       /*
-                                        *      Our dictionary is broken.
-                                        */
-                               default:
-                                       goto create_pair;
-                               }
+               my_len = extended_attrlen(start, start + length);
+               if (my_len < 0) goto raw;
 
-                               switch (vsa_llen) {
-                               case 0:
-                                       attribute = (myvendor << 16) | myattr;
-                                       ptr += 4 + vsa_tlen;
-                                       attrlen -= (4 + vsa_tlen);
-                                       packet_length -= 4 + vsa_tlen;
-                                       goto create_pair;
-
-                               case 1:
-                                       if (subptr[vsa_tlen] < (vsa_tlen + vsa_llen + vsa_offset))
-                                               goto create_pair;
-
-                                       if (subptr[vsa_tlen] > sublen)
-                                               goto create_pair;
-
-                                       /*
-                                        *      WiMAX: 0bCrrrrrrr
-                                        *      Reserved bits MUST be
-                                        *      zero.
-                                        */
-                                       if (vsa_offset &&
-                                           ((subptr[vsa_tlen + vsa_llen] & 0x7f) != 0))
-                                               goto create_pair;
-
-                                       sublen -= subptr[vsa_tlen];
-                                       subptr += subptr[vsa_tlen];
-                                       break;
-
-                               case 2:
-                                       if (subptr[vsa_tlen] != 0) goto create_pair;
-                                       if (subptr[vsa_tlen + 1] < (vsa_tlen + vsa_llen))
-                                               goto create_pair;
-                                       if (subptr[vsa_tlen + 1] > sublen)
-                                               goto create_pair;
-                                       sublen -= subptr[vsa_tlen + 1];
-                                       subptr += subptr[vsa_tlen + 1];
-                                       break;
-
-                                       /*
-                                        *      Our dictionaries are
-                                        *      broken.
-                                        */
-                               default:
-                                       goto create_pair;
-                               }
-                       } while (sublen > 0);
+               if (vendor != VENDORPEC_EXTENDED) my_len -= 5;
 
-                       vendorcode = myvendor;
-                       vendorlen = attrlen - 4;
-                       packet_length -= 4;
+               return data2vp_continued(packet, original, secret,
+                                        start, length, pvp, shift,
+                                        attribute, vendor,
+                                        first_offset, 4, my_len);
+       }
 
-                       ptr += 4;
-               }
+       if (data2vp_any(packet, original, secret, shift,
+                       attribute, vendor, data, data_len, pvp) < 0) {
+               return -1;
+       }
 
-               /*
-                *      attrlen is the length of this attribute.
-                *      total_len is the length of the encompassing
-                *      attribute.
-                */
-               switch (vsa_tlen) {
-               case 1:
-                       attribute = ptr[0];
-                       break;
+       return (data + data_len) - start;
+}
 
-               case 2:
-                       attribute = (ptr[0] << 8) | ptr[1];
-                       break;
 
-               default:        /* can't hit this. */
-                       return -1;
-               }
-               attribute |= (vendorcode << 16);
-               vsa_ptr = ptr;
-               ptr += vsa_tlen;
+/**
+ * @brief Create a "standard" RFC VALUE_PAIR from the given data.
+ */
+ssize_t rad_attr2vp_rfc(const RADIUS_PACKET *packet,
+                       const RADIUS_PACKET *original,
+                       const char *secret,
+                       const uint8_t *data, size_t length,
+                       VALUE_PAIR **pvp)
+{
+       if ((length < 2) || (data[1] < 2) || (data[1] > length)) {
+               fr_strerror_printf("rad_attr2vp_rfc: Insufficient data");
+               return -1;
+       }
+       
+       if (data2vp_any(packet, original, secret, 0,
+                       data[0], 0, data + 2, data[1] - 2, pvp) < 0) {
+               return -1;
+       }
 
-               switch (vsa_llen) {
-               case 1:
-                       attrlen = ptr[0] - (vsa_tlen + vsa_llen + vsa_offset);
-                       break;
+       return data[1];
+}      
 
-               case 2:
-                       attrlen = ptr[1] - (vsa_tlen + vsa_llen);
-                       break;
+/**
+ * @brief Create a "normal" VALUE_PAIR from the given data.
+ */
+ssize_t rad_attr2vp(const RADIUS_PACKET *packet,
+                   const RADIUS_PACKET *original,
+                   const char *secret,
+                   const uint8_t *data, size_t length,
+                   VALUE_PAIR **pvp)
+{
+       if ((length < 2) || (data[1] < 2) || (data[1] > length)) {
+               fr_strerror_printf("rad_attr2vp: Insufficient data");
+               return -1;
+       }
 
-               default:        /* can't hit this. */
-                       return -1;
-               }
+       /*
+        *      VSAs get their own handler.
+        */
+       if (data[0] == PW_VENDOR_SPECIFIC) {
+               return rad_attr2vp_vsa(packet, original, secret,
+                                      data, length, pvp);
+       }
 
-               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);
+       /*
+        *      Extended attribute format gets their own handler.
+        */
+       if (dict_attrbyvalue(data[0], VENDORPEC_EXTENDED) != NULL) {
+               return rad_attr2vp_extended(packet, original, secret,
+                                           data, length, pvp);
+       }
 
-               /*
-                *      Ignore VSAs that have no data.
-                */
-               if (attrlen == 0) goto next;
+       return rad_attr2vp_rfc(packet, original, secret, data, length, pvp);
+}
 
-               /*
-                *      WiMAX attributes of type 0 are ignored.  They
-                *      are a secret flag to us that the attribute has
-                *      already been dealt with.
-                */
-               if (attribute == 0x60b50000) goto next;
 
-               if (vsa_offset) {
-                       DICT_ATTR *da;
+/**
+ * @brief Calculate/check digest, and decode radius attributes.
+ * @return -1 on decoding error, 0 on success
+ */
+int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
+              const char *secret)
+{
+       int                     packet_length;
+       int                     num_attributes;
+       uint8_t                 *ptr;
+       radius_packet_t         *hdr;
+       VALUE_PAIR *head, **tail, *vp;
 
-                       da = dict_attrbyvalue(attribute);
+       /*
+        *      Extract attribute-value pairs
+        */
+       hdr = (radius_packet_t *)packet->data;
+       ptr = hdr->data;
+       packet_length = packet->data_len - AUTH_HDR_LEN;
 
-                       /*
-                        *      If it's NOT continued, AND we know
-                        *      about it, AND it's not a TLV, we can
-                        *      create a normal pair.
-                        */
-                       if (((vsa_ptr[2] & 0x80) == 0) &&
-                           da && (da->type != PW_TYPE_TLV)) goto create_pair;
+       head = NULL;
+       tail = &head;
+       num_attributes = 0;
 
-                       /*
-                        *      Else it IS continued, or it's a TLV.
-                        *      Go do a lot of work to find the stuff.
-                        */
-                       pair = rad_continuation2vp(packet, original, secret,
-                                                  attribute, attrlen, ptr,
-                                                  packet_length,
-                                                  ((vsa_ptr[2] & 0x80) != 0),
-                                                  da);
-                       goto created_pair;
-               }
+       /*
+        *      Loop over the attributes, decoding them into VPs.
+        */
+       while (packet_length > 0) {
+               ssize_t my_len;
 
                /*
-                *      Create the attribute, setting the default type
-                *      to 'octets'.  If the type in the dictionary
-                *      is different, then the dictionary type will
-                *      over-ride this one.
-                *
-                *      If the attribute has no data, then discard it.
-                *
-                *      Unless it's CUI.  Damn you, CUI!
+                *      This may return many VPs
                 */
-       create_pair:
-               if (!attrlen &&
-                   (attribute != PW_CHARGEABLE_USER_IDENTITY)) goto next;
-
-               pair = rad_attr2vp(packet, original, secret,
-                                  attribute, attrlen, ptr);
-               if (!pair) {
-                       pairfree(&packet->vps);
-                       fr_strerror_printf("out of memory");
+               my_len = rad_attr2vp(packet, original, secret,
+                                    ptr, packet_length, &vp);
+               if (my_len < 0) {
+                       pairfree(&head);
                        return -1;
                }
 
-       created_pair:
-               *tail = pair;
-               while (pair) {
+               *tail = vp;
+               while (vp) {
                        num_attributes++;
-                       debug_pair(pair);
-                       tail = &pair->next;
-                       pair = pair->next;
+                       debug_pair(vp);
+                       tail = &(vp->next);
+                       vp = vp->next;
                }
 
                /*
@@ -3011,7 +3905,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                    (num_attributes > fr_max_attributes)) {
                        char host_ipaddr[128];
 
-                       pairfree(&packet->vps);
+                       pairfree(&head);
                        fr_strerror_printf("WARNING: Possible DoS attack from host %s: Too many attributes in request (received %d, max %d are allowed).",
                                   inet_ntop(packet->src_ipaddr.af,
                                             &packet->src_ipaddr.ipaddr,
@@ -3020,9 +3914,8 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        return -1;
                }
 
-       next:
-               ptr += attrlen;
-               packet_length -= attrlen;
+               ptr += my_len;
+               packet_length -= my_len;
        }
 
        /*
@@ -3030,13 +3923,23 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
         *      random pool.
         */
        fr_rand_seed(packet->data, AUTH_HDR_LEN);
+       
+       /*
+        *      There may be VP's already in the packet.  Don't
+        *      destroy them.  Instead, add the decoded attributes to
+        *      the tail of the list.
+        */
+       for (tail = &packet->vps; *tail != NULL; tail = &((*tail)->next)) {
+               /* nothing */
+       }
+       *tail = head;
 
        return 0;
 }
 
 
-/*
- *     Encode password.
+/**
+ * @brief Encode password.
  *
  *     We assume that the passwd buffer passed is big enough.
  *     RFC2138 says the password is max 128 chars, so the size
@@ -3108,8 +4011,8 @@ int rad_pwencode(char *passwd, size_t *pwlen, const char *secret,
        return 0;
 }
 
-/*
- *     Decode password.
+/**
+ * @brief Decode password.
  */
 int rad_pwdecode(char *passwd, size_t pwlen, const char *secret,
                 const uint8_t *vector)
@@ -3174,8 +4077,8 @@ int rad_pwdecode(char *passwd, size_t pwlen, const char *secret,
 }
 
 
-/*
- *     Encode Tunnel-Password attributes when sending them out on the wire.
+/**
+ * @brief Encode Tunnel-Password attributes when sending them out on the wire.
  *
  *     int *pwlen is updated to the new length of the encrypted
  *     password - a multiple of 16 bytes.
@@ -3259,8 +4162,8 @@ int rad_tunnel_pwencode(char *passwd, size_t *pwlen, const char *secret,
        return 0;
 }
 
-/*
- *     Decode Tunnel-Password encrypted attributes.
+/**
+ * @brief Decode Tunnel-Password encrypted attributes.
  *
  *      Defined in RFC-2868, this uses a two char SALT along with the
  *      initial intermediate value, to differentiate it from the
@@ -3365,10 +4268,10 @@ int rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, const char *secret,
        return reallen;
 }
 
-/*
- *     Encode a CHAP password
+/**
+ * @brief Encode a CHAP password
  *
- *     FIXME: might not work with Ascend because
+ *     @bug FIXME: might not work with Ascend because
  *     we use vp->length, and Ascend gear likes
  *     to send an extra '\0' in the string!
  */
@@ -3407,7 +4310,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;
@@ -3423,8 +4326,8 @@ int rad_chap_encode(RADIUS_PACKET *packet, uint8_t *output, int id,
 }
 
 
-/*
- *     Seed the random number generator.
+/**
+ * @brief Seed the random number generator.
  *
  *     May be called any number of times.
  */
@@ -3445,7 +4348,7 @@ void fr_rand_seed(const void *data, size_t size)
                        size_t total;
                        ssize_t this;
 
-                       total = this = 0;
+                       total = 0;
                        while (total < sizeof(fr_rand_pool.randrsl)) {
                                this = read(fd, fr_rand_pool.randrsl,
                                            sizeof(fr_rand_pool.randrsl) - total);
@@ -3477,8 +4380,8 @@ void fr_rand_seed(const void *data, size_t size)
 }
 
 
-/*
- *     Return a 32-bit random number.
+/**
+ * @brief Return a 32-bit random number.
  */
 uint32_t fr_rand(void)
 {
@@ -3501,8 +4404,8 @@ uint32_t fr_rand(void)
 }
 
 
-/*
- *     Allocate a new RADIUS_PACKET
+/**
+ * @brief Allocate a new RADIUS_PACKET
  */
 RADIUS_PACKET *rad_alloc(int newvector)
 {
@@ -3564,8 +4467,8 @@ RADIUS_PACKET *rad_alloc_reply(RADIUS_PACKET *packet)
 }
 
 
-/*
- *     Free a RADIUS_PACKET
+/**
+ * @brief Free a RADIUS_PACKET
  */
 void rad_free(RADIUS_PACKET **radius_packet_ptr)
 {