Fix logic for using udpfromto
[freeradius.git] / src / lib / radius.c
index 22c4dde..37757a8 100644 (file)
@@ -68,9 +68,7 @@ static fr_randctx fr_rand_pool;       /* across multiple calls */
 static int fr_rand_initialized = 0;
 static unsigned int salt_offset = 0;
 
-
-#define MAX_PACKET_CODE (52)
-static const char *packet_codes[] = {
+const char *fr_packet_codes[FR_MAX_PACKET_CODE] = {
   "",
   "Access-Request",
   "Access-Accept",
@@ -142,6 +140,68 @@ void fr_printf_log(const char *fmt, ...)
        return;
 }
 
+static void print_hex(RADIUS_PACKET *packet)
+{
+       int i;
+
+       if (!packet->data) 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) |
+                                  (packet->data[3])));
+       printf("  Vector:\t");
+       for (i = 4; i < 20; i++) {
+               printf("%02x", packet->data[i]);
+       }
+       printf("\n");
+
+       if (packet->data_len > 20) {
+               int total;
+               const uint8_t *ptr;
+               printf("  Data:");
+
+               total = packet->data_len - 20;
+               ptr = packet->data + 20;
+
+               while (total > 0) {
+                       int attrlen;
+
+                       printf("\t\t");
+                       if (total < 2) { /* too short */
+                               printf("%02x\n", *ptr);
+                               break;
+                       }
+
+                       if (ptr[1] > total) { /* too long */
+                               for (i = 0; i < total; i++) {
+                                       printf("%02x ", ptr[i]);
+                               }
+                               break;
+                       }
+
+                       printf("%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 ((attrlen & 0x0f) != 0x00) printf("\n");
+
+                       ptr += attrlen;
+                       total -= attrlen;
+               }
+       }
+       fflush(stdout);
+}
+
+
 /*
  *     Wrapper for sendto which handles sendfromto, IPv6, and all
  *     possible combinations.
@@ -173,7 +233,7 @@ static int rad_sendto(int sockfd, void *data, size_t data_len, int flags,
         *      And if they don't specify a source IP address, don't
         *      use udpfromto.
         */
-       if ((dst_ipaddr->af == AF_INET) ||
+       if ((dst_ipaddr->af == AF_INET) &&
            (src_ipaddr->af != AF_UNSPEC)) {
                return sendfromto(sockfd, data, data_len, flags,
                                  (struct sockaddr *)&src, sizeof_src,
@@ -426,8 +486,8 @@ static void make_secret(uint8_t *digest, const uint8_t *vector,
 }
 
 #define MAX_PASS_LEN (128)
-static void make_passwd(uint8_t *output, int *outlen,
-                       const uint8_t *input, int inlen,
+static void make_passwd(uint8_t *output, size_t *outlen,
+                       const uint8_t *input, size_t inlen,
                        const char *secret, const uint8_t *vector)
 {
        FR_MD5_CTX context, old;
@@ -440,10 +500,15 @@ static void make_passwd(uint8_t *output, int *outlen,
         *      If the length is zero, round it up.
         */
        len = inlen;
+
+       if (len > MAX_PASS_LEN) len = MAX_PASS_LEN;
+
+       memcpy(passwd, input, len);
+       memset(passwd + len, 0, sizeof(passwd) - len);
+
        if (len == 0) {
                len = AUTH_PASS_LEN;
        }
-       else if (len > MAX_PASS_LEN) len = MAX_PASS_LEN;
 
        else if ((len & 0x0f) != 0) {
                len += 0x0f;
@@ -451,9 +516,6 @@ static void make_passwd(uint8_t *output, int *outlen,
        }
        *outlen = len;
 
-       memcpy(passwd, input, len);
-       memset(passwd + len, 0, sizeof(passwd) - len);
-
        fr_MD5Init(&context);
        fr_MD5Update(&context, (const uint8_t *) secret, strlen(secret));
        old = context;
@@ -480,8 +542,8 @@ static void make_passwd(uint8_t *output, int *outlen,
        memcpy(output, passwd, len);
 }
 
-static void make_tunnel_passwd(uint8_t *output, int *outlen,
-                              const uint8_t *input, int inlen, int room,
+static void make_tunnel_passwd(uint8_t *output, size_t *outlen,
+                              const uint8_t *input, size_t inlen, size_t room,
                               const char *secret, const uint8_t *vector)
 {
        FR_MD5_CTX context, old;
@@ -564,6 +626,7 @@ static void make_tunnel_passwd(uint8_t *output, int *outlen,
                }
 
                fr_MD5Final(digest, &context);
+
                for (i = 0; i < AUTH_PASS_LEN; i++) {
                        passwd[i + 2 + n] ^= digest[i];
                }
@@ -571,13 +634,17 @@ static void make_tunnel_passwd(uint8_t *output, int *outlen,
        memcpy(output, passwd, len + 2);
 }
 
+/*
+ *     Returns the end of the data.
+ */
 static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-                  const char *secret, const VALUE_PAIR *vp, uint8_t *ptr,
-                  int offset, int room)
+                  const char *secret, const VALUE_PAIR *vp, uint8_t *start,
+                  size_t room)
 {
        uint32_t lvalue;
        size_t len;
        const uint8_t *data;
+       uint8_t *ptr = start;
        uint8_t array[4];
 
        /*
@@ -600,7 +667,6 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                len = 1;        /* just in case */
                array[0] = vp->vp_integer & 0xff;
                data = array;
-               offset = 0;
                break;
 
        case PW_TYPE_SHORT:
@@ -608,19 +674,13 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                array[0] = (vp->vp_integer >> 8) & 0xff;
                array[1] = vp->vp_integer & 0xff;
                data = array;
-               offset = 0;
                break;
 
        case PW_TYPE_INTEGER:
                len = 4;        /* just in case */
                lvalue = htonl(vp->vp_integer);
                memcpy(array, &lvalue, sizeof(lvalue));
-
-               /*
-                *      Perhaps discard the first octet.
-                */
-               data = &array[offset];
-               len -= offset;
+               data = array;
                break;
 
        case PW_TYPE_IPADDR:
@@ -653,6 +713,7 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                        fr_strerror_printf("ERROR: Cannot encode NULL TLV");
                        return -1;
                }
+               if (vp->length > room) return 0; /* can't chop TLVs to fit */
                break;
 
        default:                /* unknown type: ignore it */
@@ -661,11 +722,9 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
        }
 
        /*
-        *      Bound the data to 255 bytes.
+        *      Bound the data to the calling size
         */
-       if (len + offset > room) {
-               len = room - offset;
-       }
+       if (len > room) len = room;
 
        /*
         *      Encrypt the various password styles
@@ -675,22 +734,22 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
         */
        switch (vp->flags.encrypt) {
        case FLAG_ENCRYPT_USER_PASSWORD:
-               make_passwd(ptr + offset, &len,
-                           data, len,
+               make_passwd(ptr, &len, data, len,
                            secret, packet->vector);
                break;
 
        case FLAG_ENCRYPT_TUNNEL_PASSWORD:
+               lvalue = 0;
+               if (vp->flags.has_tag) lvalue = 1;
+
                /*
-                *      Check if 255 - offset - total_length is less
-                *      than 18.  If so, we can't fit the data into
-                *      the available space, and we discard the
-                *      attribute.
+                *      Check if there's enough room.  If there isn't,
+                *      we discard the attribute.
                 *
                 *      This is ONLY a problem if we have multiple VSA's
                 *      in one Vendor-Specific, though.
                 */
-               if ((room - offset) < 18) return 0;
+               if (room < (18 + lvalue)) return 0;
 
                switch (packet->code) {
                case PW_AUTHENTICATION_ACK:
@@ -701,15 +760,17 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                                fr_strerror_printf("ERROR: No request packet, cannot encrypt %s attribute in the vp.", vp->name);
                                return -1;
                        }
-                       make_tunnel_passwd(ptr + offset, &len,
-                                          data, len, room - offset,
+
+                       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 + offset, &len,
-                                          data, len, room - offset,
+                       ptr[0] = vp->flags.tag;
+                       make_tunnel_passwd(ptr + 1, &len, data, len - 1, room,
                                           secret, packet->vector);
                        break;
                }
@@ -720,228 +781,255 @@ static int vp2data(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                 *      always fits.
                 */
        case FLAG_ENCRYPT_ASCEND_SECRET:
-               make_secret(ptr + offset, packet->vector,
-                           secret, data);
+               make_secret(ptr, packet->vector, secret, data);
                len = AUTH_VECTOR_LEN;
                break;
 
 
        default:
-               /*
-                *      Just copy the data over
-                */
-               memcpy(ptr + offset, data, len);
+               if (vp->flags.has_tag && TAG_VALID(vp->flags.tag)) {
+                       if (vp->type == PW_TYPE_STRING) {
+                               if (len > (room - 1)) len = room - 1;
+                               ptr[0] = vp->flags.tag;
+                               ptr++;
+                       } else if (vp->type == PW_TYPE_INTEGER) {
+                               array[0] = vp->flags.tag;
+                       } /* else it can't be any other type */
+               }
+               memcpy(ptr, data, len);
                break;
        } /* switch over encryption flags */
 
-       return len;
+       return len + (ptr - start);;
 }
 
 
-static VALUE_PAIR *rad_vp2tlv(VALUE_PAIR *vps)
+static int rad_vp2rfc(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     unsigned int attribute, uint8_t *ptr, size_t room)
 {
-       int maxattr = 0;
-       int length, attribute;
-       uint8_t *ptr;
-       VALUE_PAIR *vp, *tlv;
+       int len;
 
-       attribute = vps->attribute & 0xffff00ff;
-       maxattr = vps->attribute & 0x0ff;
+       if (room < 2) return 0;
 
-       tlv = paircreate(attribute, PW_TYPE_TLV);
-       if (!tlv) return NULL;
+       ptr[0] = attribute & 0xff; /* NOT vp->attribute */
+       ptr[1] = 2;
 
-       tlv->length = 0;
-       for (vp = vps; vp != NULL; vp = vp->next) {
-               /*
-                *      Group the attributes ONLY until we see a
-                *      non-TLV attribute.
-                */
-               if (!vp->flags.is_tlv ||
-                   vp->flags.encoded ||
-                   (vp->flags.encrypt != FLAG_ENCRYPT_NONE) ||
-                   ((vp->attribute & 0xffff00ff) != attribute) ||
-                   ((vp->attribute & 0x0000ff00) <= maxattr)) {
-                       break;
-               }
+       if (room > (255 - ptr[1])) room = 255 - ptr[1];
+       len = vp2data(packet, original, secret, vp, ptr + 2, room);
+       if (len < 0) return len;
 
-               maxattr = vp->attribute & 0xff00;
-               tlv->length += vp->length + 2;
-       }
+       ptr[1] += len;
 
-       if (!tlv->length) {
-               pairfree(&tlv);
-               return NULL;
-       }
+       return ptr[1];
+}
 
-       tlv->vp_tlv = malloc(tlv->length);
-       if (!tlv->vp_tlv) {
-               pairfree(&tlv);
-               return NULL;
-       }
+extern int fr_wimax_max_tlv;
+extern int fr_wimax_shift[];
+extern int fr_wimax_mask[];
 
-       ptr = tlv->vp_tlv;
-       maxattr = vps->attribute & 0x0ff;
-       for (vp = vps; vp != NULL; vp = vp->next) {
-               if (!vp->flags.is_tlv ||
-                   vp->flags.encoded ||
-                   (vp->flags.encrypt != FLAG_ENCRYPT_NONE) ||
-                   ((vp->attribute & 0xffff00ff) != attribute) ||
-                   ((vp->attribute & 0x0000ff00) <= maxattr)) {
-                       break;
-               }
+static int tlv2data(const RADIUS_PACKET *packet,
+                   const RADIUS_PACKET *original,
+                   const char *secret, const VALUE_PAIR *vp,
+                   uint8_t *ptr, size_t room, int nest)
+{
+       int len;
 
-               maxattr = vp->attribute & 0xff00;
-               length = vp2data(NULL, NULL, NULL, vp, ptr + 2, 0,
-                                tlv->vp_tlv + tlv->length - ptr);
-               if (length < 0) {
-                       vp->length = ptr - vp->vp_tlv;
-                       return tlv; /* should be a more serious error... */
-               }
+       if (nest > fr_wimax_max_tlv) return -1;
 
-               /*
-                *      Pack the attribute.
-                */
-               ptr[0] = (vp->attribute & 0xff00) >> 8;
-               ptr[1] = (length & 0xff) + 2;
+       if (room < 2) return 0;
+       room -= 2;
 
-               ptr += vp->length + 2;
-               vp->flags.encoded = 1;
+       ptr[0] = (vp->attribute >> fr_wimax_shift[nest]) & fr_wimax_mask[nest];
+       ptr[1] = 2;
+
+       /*
+        *      No more nested TLVs: pack the data.
+        */
+       if ((nest == fr_wimax_max_tlv) ||
+           ((vp->attribute >> fr_wimax_shift[nest + 1]) == 0)) {
+               len = vp2data(packet, original, secret, vp, ptr + 2, room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr + 2, room,
+                              nest + 1);
        }
+       if (len <= 0) return len;
+
+       ptr[1] += len;
 
-       return tlv;
+       return ptr[1];
 }
 
-/*
- *     Pack data without any encryption.
- *     start == start of RADIUS attribute
- *     ptr   == continuation byte (i.e. one after length)
- */
-static int rad_vp2continuation(const VALUE_PAIR *vp, uint8_t *start,
-                              uint8_t *ptr)
+static int wimax2data(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     uint8_t *start, size_t room, uint8_t *ptr)
 {
-       size_t left, piece;
-       size_t hsize = (ptr - start);
-       uint8_t *this = start;
-       const uint8_t *data;
-       uint8_t header[16];
-       
+       int len;
+
        /*
-        *      If it's too long and marked as encrypted, ignore it.
+        *      Offsets to Vendor-Specific length, and to length of
+        *      WiMAX attribute.
         */
-       if (vp->flags.encrypt != FLAG_ENCRYPT_NONE) {
-               return 0;
-       }
-       
-       memcpy(header, start, hsize);
+#define VS_OFF (1)
+#define WM_OFF (7)
 
-       left = vp->length;
-       
-       switch (vp->type) {
-       case PW_TYPE_TLV:
-               data = vp->vp_tlv;
-               break;
+       if (room < 1) return 0;
+       room--;
 
-       case PW_TYPE_OCTETS:
-       case PW_TYPE_STRING:
-               data = vp->vp_octets;
-               break;
+       /*
+        *      Account for continuation bytes.  The caller has
+        *      already accounting for the continuation byte in the
+        *      Vendor-Specific "length" field.
+        */
+       start[WM_OFF]++;
+       *(ptr++) = 0;
 
-               /*
-                *      This is invalid.
-                */
-       default:
+       /*
+        *      Chop everything to fit in one attribute.
+        */
+       if (room > (255 - 9)) room = (255 - 9);
+
+       /*
+        *      The attribute contains TLVs that we have NOT decoded
+        *      properly, OR it contains TLV that the user has encoded
+        *      manually.  If it has no data, OR it's too long,
+        *      discard it.  We're not going to walk through its
+        *      contents trying to figure out how to chop it across
+        *      multiple continuations.
+        */
+       if (vp->flags.has_tlv && (!vp->vp_tlv || (vp->length > room))) {
                return 0;
        }
-       
-       while (left > 0) {
-               memcpy(this, header, hsize);
-               ptr = this + hsize;
-               
-               /*
-                *      254 to account for
-                *      continuation flag.
-                */
-               if (left > (254 - hsize)) {
-                       piece = 254 - hsize;
-                       *(ptr++) = 0x80;
-               } else {
-                       piece = left;
-                       *(ptr++) = 0x00;
-               }
-               
-               memcpy(ptr, data, piece);
-               this[1] = hsize + piece + 1;
 
-               /*
-                *      
-                */
-               this[hsize - 1] = hsize - 6 + 1 + piece;
-               data += piece;
-               ptr += piece;
-               left -= piece;
-               this = ptr;
-       }
-       
-       return (ptr - start);
+       /*
+        *      The attribute is a top-level integer, ipaddr, etc.
+        *      Encode it.
+        */
+       if (!vp->flags.is_tlv) {
+               len = vp2data(packet, original, secret, vp, ptr, room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr, room, 1);
+       }
+
+       if (len <= 0) return len;
+
+       start[VS_OFF] += len;
+       start[WM_OFF] += len;
+
+       return start[VS_OFF];
 }
 
 
+static int rad_vp2extended(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     unsigned int attribute, uint8_t *ptr, size_t room)
+{
+       int len = 2;
+
+       if (room < 3) return 0;
+
+       ptr[0] = attribute & 0xff; /* NOT vp->attribute */
+       ptr[1] = 3;
+
+       if (vp->flags.extended) {
+               ptr[2] = (attribute & 0xff00) >> 8;
+               len++;
+
+       } else if (vp->flags.extended_flags) {
+               if (room < 4) return 0;
+
+               ptr[1] = 4;
+               ptr[2] = (attribute & 0xff00) >> 8;
+               ptr[3] = 0;
+
+               len += 2;
+       }
+
+       /*
+        *      For now, no extended attribute can be longer than the
+        *      encapsulating attribute.  Once we add support for the
+        *      "M" bit, this restriction will be relaxed.
+        */
+       if (room > (255 - ptr[1])) room = 255 - ptr[1];
+
+       if (!vp->flags.is_tlv) {
+               len = vp2data(packet, original, secret, vp, ptr + ptr[1], room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr + ptr[1], room, 2);
+       }
+
+       if (len < 0) return len;
+
+       ptr[1] += len;
+
+       return ptr[1];
+}
+
 /*
  *     Parse a data structure into a RADIUS attribute.
  */
 int rad_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-               const char *secret, const VALUE_PAIR *vp, uint8_t *start)
+               const char *secret, const VALUE_PAIR *vp, uint8_t *start,
+               size_t room)
 {
-       int             vendorcode;
-       int             offset, len, total_length;
-       uint32_t        lvalue;
-       uint8_t         *ptr, *length_ptr, *vsa_length_ptr, *tlv_length_ptr;
+       int len;
+       uint32_t lvalue;
+       uint8_t *ptr;
+       DICT_VENDOR *dv;
 
-       ptr = start;
-       vendorcode = total_length = 0;
-       length_ptr = vsa_length_ptr = tlv_length_ptr = NULL;
+       /*
+        *      RFC format attributes take the fast path.
+        */
+       if (vp->vendor == 0) {
+               return rad_vp2rfc(packet, original, secret, vp,
+                                 vp->attribute, start, room);
+       }
+
+       if (vp->vendor == VENDORPEC_EXTENDED) {
+               return rad_vp2extended(packet, original, secret, vp,
+                                      vp->attribute, start, room);
+       }
 
        /*
-        *      For interoperability, always put vendor attributes
-        *      into their own VSA.
+        *      Not enough room for:
+        *              attr, len, vendor-id, vsa, vsalen
         */
-       if ((vendorcode = VENDOR(vp->attribute)) == 0) {
-               *(ptr++) = vp->attribute & 0xFF;
-               length_ptr = ptr;
-               *(ptr++) = 2;
-               total_length += 2;
+       if (room < 8) return 0;
 
-       } else {
-               int vsa_tlen = 1;
-               int vsa_llen = 1;
-               int vsa_offset = 0;
-               DICT_VENDOR *dv = dict_vendorbyvalue(vendorcode);
+       /*
+        *      Build the Vendor-Specific header
+        */
+       ptr = start;
+       *ptr++ = PW_VENDOR_SPECIFIC;
+       *ptr++ = 6;
+       room -= 6;
+       lvalue = htonl(vp->vendor);
+       memcpy(ptr, &lvalue, 4);
+       ptr += 4;
 
-               /*
-                *      This must be an RFC-format attribute.  If it
-                *      wasn't, then the "decode" function would have
-                *      made a Vendor-Specific attribute (i.e. type
-                *      26), and we would have "vendorcode == 0" here.
-                */
-               if (dv) {
-                       vsa_tlen = dv->type;
-                       vsa_llen = dv->length;
-                       if (dv->flags) vsa_offset = 1;
-               }
+       /*
+        *      Unknown vendors, and type=1,length=1,no-continuation
+        *      are RFC format attributes.
+        */
+       dv = dict_vendorbyvalue(vp->vendor);
+       if (!dv ||
+           ((dv->type == 1) && (dv->length = 1) && !dv->flags)) {
+               len = rad_vp2rfc(packet, original, secret, vp,
+                                vp->attribute, ptr, room);
+               if (len <= 0) return len;
 
-               /*
-                *      Build a VSA header.
-                */
-               *ptr++ = PW_VENDOR_SPECIFIC;
-               vsa_length_ptr = ptr;
-               *ptr++ = 6;
-               lvalue = htonl(vendorcode);
-               memcpy(ptr, &lvalue, 4);
-               ptr += 4;
-               total_length += 6;
+               start[1] += len;
+               return start[1];
+       }
 
-               switch (vsa_tlen) {
+       if (room < (dv->type + dv->length + dv->flags)) return 0;
+       room -= (dv->type + dv->length + dv->flags);
+       start[1] += (dv->type + dv->length + dv->flags);
+
+       switch (dv->type) {
                case 1:
                        ptr[0] = (vp->attribute & 0xFF);
                        break;
@@ -953,115 +1041,138 @@ int rad_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
 
                case 4:
                        ptr[0] = 0;
-                       ptr[1] = 0;
+                       ptr[1] = ((vp->attribute >> 16) & 0xFF);
                        ptr[2] = ((vp->attribute >> 8) & 0xFF);
                        ptr[3] = (vp->attribute & 0xFF);
                        break;
 
                default:
                        return 0; /* silently discard it */
-               }
-               ptr += vsa_tlen;
+       }
+       ptr += dv->type;
 
-               switch (vsa_llen) {
-               case 0:
-                       length_ptr = vsa_length_ptr;
-                       vsa_length_ptr = NULL;
-                       break;
-               case 1:
-                       ptr[0] = 0;
-                       length_ptr = ptr;
-                       break;
-               case 2:
-                       ptr[0] = 0;
-                       ptr[1] = 0;
-                       length_ptr = ptr + 1;
-                       break;
+       switch (dv->length) {
+       case 0:
+               break;
+       case 1:
+               ptr[0] = dv->type + 1;
+               break;
+       case 2:
+               ptr[0] = 0;
+               ptr[1] = dv->type + 2;
+               break;
 
-               default:
-                       return 0; /* silently discard it */
-               }
-               ptr += vsa_llen;
+       default:
+               return 0; /* silently discard it */
+       }
+       ptr += dv->length;
 
-               /*
-                *      Allow for some continuation.
-                */
-               if (vsa_offset) {
-                       /*
-                        *      Allow TLV's to be encoded, if someone
-                        *      manages to somehow encode the sub-tlv's.
-                        *
-                        *      FIXME: Keep track of room in the packet!
-                        */
-                       if (vp->length > (254 - (ptr - start))) {
-                               return rad_vp2continuation(vp, start, ptr);
-                       }
+       /*
+        *      WiMAX attributes take their own path through the
+        *      system.
+        */
+       if (dv->flags) return wimax2data(packet, original, secret, vp,
+                                        start, room, ptr);
 
-                       ptr[0] = 0x00;
-                       ptr++;
+       len = vp2data(packet, original, secret, vp, ptr, room);
+       if (len <= 0) return len;
 
-                       /*
-                        *      sub-TLV's can only be in one format.
-                        */
-                       if (vp->flags.is_tlv) {
-                               *(ptr++) = (vp->attribute & 0xff00) >> 8;
-                               tlv_length_ptr = ptr;
-                               *(ptr++) = 2;
-                               vsa_offset += 2;
-                       }
-               }
+       if (dv->length != 0) ptr[-1] += len;
 
-               total_length += vsa_tlen + vsa_llen + vsa_offset;
-               if (vsa_length_ptr) *vsa_length_ptr += vsa_tlen + vsa_llen + vsa_offset;
-               *length_ptr += vsa_tlen + vsa_llen + vsa_offset;
-       }
+       start[1] += len;
 
-       offset = 0;
-       if (vp->flags.has_tag) {
-               if (TAG_VALID(vp->flags.tag)) {
-                       ptr[0] = vp->flags.tag & 0xff;
-                       offset = 1;
+       return start[1];
+}
 
-               } else if (vp->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD) {
-                       /*
-                        *      Tunnel passwords REQUIRE a tag, even
-                        *      if don't have a valid tag.
-                        */
-                       ptr[0] = 0;
-                       offset = 1;
-               } /* else don't write a tag */
-       } /* else the attribute doesn't have a tag */
+/*
+ *  Swap 123a -> 0321
+ */
+#define REORDER(x) ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000 >> 24))
 
-       len = vp2data(packet, original, secret, vp, ptr, offset,
-                     255 - total_length);
-       if (len < 0) return -1;
+
+/*
+ *     Encode a WiMAX sub-TLV.  It must NOT be called for WiMAX
+ *     attributes that are of type integer, string, etc.
+ */
+static int rad_encode_wimax(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret, VALUE_PAIR *reply,
+                           uint8_t *start, size_t room)
+{
+       int len, redo;
+       uint32_t lvalue;
+       uint8_t *ptr = start, *vsa = start;
+       uint32_t maxattr;
+       VALUE_PAIR *vp = reply;
 
        /*
-        *      Account for the tag (if any).
+        *      Swap the order of the WiMAX hacks, to make later
+        *      comparisons easier.
         */
-       len += offset;
+       maxattr = REORDER(vp->attribute);
 
        /*
-        *      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 ((len == 0) &&
-           (vp->attribute != PW_CHARGEABLE_USER_IDENTITY)) return 0;
+       ptr = start;
+       redo = 0;
+
+redo_vsa:
+       vsa = ptr;
+
+       if (room < 9) return 0;
+       *ptr++ = PW_VENDOR_SPECIFIC;
+       *ptr++ = 9;
+       room -= 9;
+       lvalue = htonl(vp->vendor);
+       memcpy(ptr, &lvalue, 4);
+       ptr += 4;
+       *(ptr++) = vp->attribute & 0xff;
+       *(ptr++) = 3;
+       *(ptr++) = 0;           /* continuation */
+       room -= 9;
+
+redo_tlv:
+       len = tlv2data(packet, original, secret, vp, ptr, room, 1);
+       if (len < 0) return len;
 
        /*
-        *      Update the various lengths.
+        *      Not enough room.  Do a continuation.
         */
-       *length_ptr += len;
-       if (vsa_length_ptr) *vsa_length_ptr += len;
-       if (tlv_length_ptr) *tlv_length_ptr += len;
+       if ((len == 0) || ((vsa[VS_OFF] + len) > 255)) {
+               if (redo) return (start - vsa);
+
+               vsa[8] = 0x80;
+               redo = 1;
+               goto redo_vsa;
+       }
+       redo = 0;
+
        ptr += len;
-       total_length += len;
+       vsa[VS_OFF] += len;
+       vsa[WM_OFF] += len;
+
+       vp->flags.encoded = 1;
+       vp = vp->next;
+
+       /*
+        *      Look at the NEXT tlv.  Ensure that we encode
+        *      attributes into a common VSA *only* if they are for
+        *      the same WiMAX VSA, AND if the TLVs are in numerically
+        *      increasing order.
+        */
+       if (vp && vp->flags.is_tlv && (reply->vendor == vp->vendor) &&
+           ((reply->attribute & 0xff) == (vp->attribute & 0xff))) {
+               uint32_t attr;
+
+               attr = REORDER(vp->attribute);
+               if (attr >= maxattr) {
+                       maxattr = attr;
+                       goto redo_tlv;
+               }
+       }
 
-       return total_length;    /* of attribute */
+       return ptr - start;
 }
 
 
@@ -1080,16 +1191,12 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
        char            ip_buffer[128];
 
        /*
-        *      For simplicity in the following logic, we allow
-        *      the attributes to "overflow" the 4k maximum
-        *      RADIUS packet size, by one attribute.
-        *
-        *      It's uint32_t, for alignment purposes.
+        *      A 4K packet, aligned on 64-bits.
         */
-       uint32_t        data[(MAX_PACKET_LEN + 256) / 4];
+       uint64_t        data[MAX_PACKET_LEN / sizeof(uint64_t)];
 
-       if ((packet->code > 0) && (packet->code < MAX_PACKET_CODE)) {
-               what = packet_codes[packet->code];
+       if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
+               what = fr_packet_codes[packet->code];
        } else {
                what = "Reply";
        }
@@ -1163,10 +1270,12 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
         */
        for (reply = packet->vps; reply; reply = reply->next) {
                /*
-                *      Ignore non-wire attributes
+                *      Ignore non-wire attributes, but allow extended
+                *      attributes.
                 */
-               if ((VENDOR(reply->attribute) == 0) &&
-                   ((reply->attribute & 0xFFFF) > 0xff)) {
+               if ((reply->vendor == 0) &&
+                   ((reply->attribute & 0xFFFF) >= 256) &&
+                   !reply->flags.extended && !reply->flags.extended_flags) {
 #ifndef NDEBUG
                        /*
                         *      Permit the admin to send BADLY formatted
@@ -1204,38 +1313,29 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                debug_pair(reply);
 
                /*
-                *      Print them in order, even if they were encoded
-                *      already.
+                *      Skip attributes that are encoded.
                 */
-               len = 0;
-               if (reply->flags.encoded) goto next;
-
-               if (reply->flags.is_tlv) {
-                       VALUE_PAIR *tlv = rad_vp2tlv(reply);
-                       if (tlv) {
-                               tlv->next = reply->next;
-                               reply->next = tlv;
-                       }
+               if (reply->flags.encoded) continue;
 
-                       /*
-                        *      The encoded flag MUST be set in reply!
-                        */
-                       reply = reply->next;
-               }
+               if ((reply->vendor == VENDORPEC_WIMAX) && reply->flags.is_tlv) {
+                       len = rad_encode_wimax(packet, original, secret,
+                                              reply, ptr,
+                                              ((uint8_t *) data) + sizeof(data) - ptr);
+               } else {
 
-               len = rad_vp2attr(packet, original, secret, reply, ptr);
+                       len = rad_vp2attr(packet, original, secret, reply, ptr,
+                                         ((uint8_t *) data) + sizeof(data) - ptr);
+               }
 
                if (len < 0) return -1;
 
                /*
-                *      Check that the packet is no more than 4k in
-                *      size, AFTER writing the attribute past the 4k
-                *      boundary, but BEFORE deciding to increase the
-                *      size of the packet. Note that the 'data'
-                *      buffer, above, is one attribute longer than
-                *      necessary, in order to permit this overflow.
+                *      Failed to encode the attribute, likely because
+                *      the packet is full.
                 */
-               if ((total_length + len) > MAX_PACKET_LEN) {
+               if ((len == 0) &&
+                   (total_length > (sizeof(data) - 2 - reply->length))) {
+                       DEBUG("WARNING: Attributes are too long for packet.  Discarding data past %d bytes", total_length);
                        break;
                }
 
@@ -1400,8 +1500,8 @@ int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                return 0;
        }
 
-       if ((packet->code > 0) && (packet->code < MAX_PACKET_CODE)) {
-               what = packet_codes[packet->code];
+       if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
+               what = fr_packet_codes[packet->code];
        } else {
                what = "Reply";
        }
@@ -1437,7 +1537,7 @@ int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                      packet->dst_port);
 
                for (reply = packet->vps; reply; reply = reply->next) {
-                       if ((VENDOR(reply->attribute) == 0) &&
+                       if ((reply->vendor == 0) &&
                            ((reply->attribute & 0xFFFF) > 0xff)) continue;
                        debug_pair(reply);
                }
@@ -1451,6 +1551,26 @@ 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
+ *     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 result = 0;
+       size_t i;
+
+       for (i = 0; i < length; i++) {
+               result |= a[i] ^ b[i];
+       }
+
+       return result;          /* 0 is OK, !0 is !OK, just like memcmp */
+}
+
 
 /*
  *     Validates the requesting client NAS.  Calculates the
@@ -1480,7 +1600,7 @@ static int calc_acctdigest(RADIUS_PACKET *packet, const char *secret)
        /*
         *      Return 0 if OK, 2 if not OK.
         */
-       if (memcmp(digest, packet->vector, AUTH_VECTOR_LEN) != 0) return 2;
+       if (digest_cmp(digest, packet->vector, AUTH_VECTOR_LEN) != 0) return 2;
        return 0;
 }
 
@@ -1523,7 +1643,7 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original,
        /*
         *      Return 0 if OK, 2 if not OK.
         */
-       if (memcmp(packet->vector, calc_digest, AUTH_VECTOR_LEN) != 0) return 2;
+       if (digest_cmp(packet->vector, calc_digest, AUTH_VECTOR_LEN) != 0) return 2;
        return 0;
 }
 
@@ -1557,7 +1677,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
                                     host_ipaddr, sizeof(host_ipaddr)),
-                          packet->data_len, AUTH_HDR_LEN);
+                                  (int) packet->data_len, AUTH_HDR_LEN);
                return 0;
        }
 
@@ -1571,7 +1691,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
                                     host_ipaddr, sizeof(host_ipaddr)),
-                          packet->data_len, MAX_PACKET_LEN);
+                                  (int) packet->data_len, MAX_PACKET_LEN);
                return 0;
        }
 
@@ -1588,7 +1708,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
         *      Code of 16 or greate is not understood.
         */
        if ((hdr->code == 0) ||
-           (hdr->code >= MAX_PACKET_CODE)) {
+           (hdr->code >= FR_MAX_PACKET_CODE)) {
                fr_strerror_printf("WARNING: Bad RADIUS packet from host %s: unknown packet code%d ",
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
@@ -1657,7 +1777,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                           inet_ntop(packet->src_ipaddr.af,
                                     &packet->src_ipaddr.ipaddr,
                                     host_ipaddr, sizeof(host_ipaddr)),
-                          packet->data_len, totallen);
+                                  (int) packet->data_len, totallen);
                return 0;
        }
 
@@ -1694,6 +1814,18 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
 
        while (count > 0) {
                /*
+                *      We need at least 2 bytes to check the
+                *      attribute header.
+                */
+               if (count < 2) {
+                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute header overflows the packet",
+                                  inet_ntop(packet->src_ipaddr.af,
+                                            &packet->src_ipaddr.ipaddr,
+                                            host_ipaddr, sizeof(host_ipaddr)));
+                       return 0;
+               }
+
+               /*
                 *      Attribute number zero is NOT defined.
                 */
                if (attr[0] == 0) {
@@ -1709,7 +1841,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                 *      fields.  Anything shorter is an invalid attribute.
                 */
                        if (attr[1] < 2) {
-                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %d too short",
+                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %u too short",
                                   inet_ntop(packet->src_ipaddr.af,
                                             &packet->src_ipaddr.ipaddr,
                                             host_ipaddr, sizeof(host_ipaddr)),
@@ -1718,6 +1850,19 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
                }
 
                /*
+                *      If there are fewer bytes in the packet than in the
+                *      attribute, it's a bad packet.
+                */
+               if (count < attr[1]) {
+                       fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %u data overflows the packet",
+                                  inet_ntop(packet->src_ipaddr.af,
+                                            &packet->src_ipaddr.ipaddr,
+                                            host_ipaddr, sizeof(host_ipaddr)),
+                                          attr[0]);
+                       return 0;
+               }
+
+               /*
                 *      Sanity check the attributes for length.
                 */
                switch (attr[0]) {
@@ -1820,6 +1965,7 @@ int rad_packet_ok(RADIUS_PACKET *packet, int flags)
  */
 RADIUS_PACKET *rad_recv(int fd, int flags)
 {
+       int sock_flags = 0;
        RADIUS_PACKET           *packet;
 
        /*
@@ -1831,7 +1977,12 @@ RADIUS_PACKET *rad_recv(int fd, int flags)
        }
        memset(packet, 0, sizeof(*packet));
 
-       packet->data_len = rad_recvfrom(fd, &packet->data, 0,
+       if (flags & 0x02) {
+               sock_flags = MSG_PEEK;
+               flags &= ~0x02;
+       }
+
+       packet->data_len = rad_recvfrom(fd, &packet->data, sock_flags,
                                        &packet->src_ipaddr, &packet->src_port,
                                        &packet->dst_ipaddr, &packet->dst_port);
 
@@ -1896,9 +2047,9 @@ RADIUS_PACKET *rad_recv(int fd, int flags)
        if (fr_debug_flag) {
                char host_ipaddr[128];
 
-               if ((packet->code > 0) && (packet->code < MAX_PACKET_CODE)) {
+               if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
                        DEBUG("rad_recv: %s packet from host %s port %d",
-                             packet_codes[packet->code],
+                             fr_packet_codes[packet->code],
                              inet_ntop(packet->src_ipaddr.af,
                                        &packet->src_ipaddr.ipaddr,
                                        host_ipaddr, sizeof(host_ipaddr)),
@@ -1911,7 +2062,8 @@ RADIUS_PACKET *rad_recv(int fd, int flags)
                              packet->src_port,
                              packet->code);
                }
-               DEBUG(", id=%d, length=%d\n", packet->id, packet->data_len);
+               DEBUG(", id=%d, length=%d\n",
+                     packet->id, (int) packet->data_len);
        }
 
        return packet;
@@ -1983,7 +2135,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 (memcmp(calc_auth_vector, msg_auth_vector,
+                       if (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.)",
@@ -2010,7 +2162,7 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
         *      It looks like a RADIUS packet, but we can't validate
         *      the signature.
         */
-       if ((packet->code == 0) || (packet->code >= MAX_PACKET_CODE)) {
+       if ((packet->code == 0) || (packet->code >= FR_MAX_PACKET_CODE)) {
                char buffer[32];
                fr_strerror_printf("Received Unknown packet code %d "
                           "from client %s port %d: Cannot validate signature.",
@@ -2042,8 +2194,8 @@ 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.)",
-                                          packet_codes[packet->code],
+                                          "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,
                                                     buffer, sizeof(buffer)));
@@ -2063,13 +2215,12 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        rcode = calc_replydigest(packet, original, secret);
                        if (rcode > 1) {
                                fr_strerror_printf("Received %s packet "
-                                          "from client %s port %d with invalid signature (err=%d)!  (Shared secret is incorrect.)",
-                                          packet_codes[packet->code],
+                                          "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;
@@ -2091,7 +2242,7 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 
 static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                           const RADIUS_PACKET *original,
-                          const char *secret, int attribute, int length,
+                          const char *secret, size_t length,
                           const uint8_t *data, VALUE_PAIR *vp)
 {
        int offset = 0;
@@ -2107,6 +2258,15 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
        vp->next = NULL;
 
        /*
+        *      It's supposed to be a fixed length, but we found
+        *      a different length instead.  Make it type "octets",
+        *      and do no more processing on it.
+        */
+       if ((vp->flags.length > 0) && (vp->flags.length != length)) {
+               goto raw;
+       }
+
+       /*
         *      Handle tags.
         */
        if (vp->flags.has_tag) {
@@ -2119,7 +2279,10 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                        vp->flags.tag = data[0];
 
                        if ((vp->type == PW_TYPE_STRING) ||
-                           (vp->type == PW_TYPE_OCTETS)) offset = 1;
+                           (vp->type == PW_TYPE_OCTETS)) {
+                               if (length == 0) goto raw;
+                               offset = 1;
+                       }
                }
        }
 
@@ -2221,7 +2384,7 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
                 */
                {
                        DICT_VALUE *dval;
-                       dval = dict_valbyattr(vp->attribute,
+                       dval = dict_valbyattr(vp->attribute, vp->vendor,
                                              vp->vp_integer);
                        if (dval) {
                                strlcpy(vp->vp_strvalue,
@@ -2323,16 +2486,35 @@ static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
 
        default:
        raw:
-               vp->type = PW_TYPE_OCTETS;
-               vp->length = length;
-               memcpy(vp->vp_octets, data, length);
-
-
                /*
-                *      Ensure there's no encryption or tag stuff,
-                *      we just pass the attribute as-is.
+                *      Change the name to show the user that the
+                *      attribute is not of the correct format.
                 */
-               memset(&vp->flags, 0, sizeof(vp->flags));
+               {
+                       int attr = vp->attribute;
+                       int vendor = vp->vendor;
+                       VALUE_PAIR *vp2;
+
+                       vp2 = pairalloc(NULL);
+                       if (!vp2) {
+                               pairfree(&vp);
+                               return NULL;
+                       }
+                       pairfree(&vp);
+                       vp = vp2;
+
+                       /*
+                        *      This sets "vp->flags" appropriately,
+                        *      and vp->type.
+                        */
+                       if (!paircreate_raw(attr, vendor, PW_TYPE_OCTETS, vp)) {
+                               return NULL;
+                       }
+
+                       vp->length = length;
+                       memcpy(vp->vp_octets, data, length);
+               }
+               break;
        }
 
        return vp;
@@ -2379,7 +2561,8 @@ static void rad_sortvp(VALUE_PAIR **head)
  *     Sane clients should put the fragments next to each other, in
  *     which case this is O(N), in the number of fragments.
  */
-static uint8_t *rad_coalesce(int attribute, size_t length, uint8_t *data,
+static uint8_t *rad_coalesce(unsigned int attribute, int vendor,
+                            size_t length, uint8_t *data,
                             size_t packet_length, size_t *ptlv_length)
                             
 {
@@ -2390,9 +2573,12 @@ static uint8_t *rad_coalesce(int attribute, size_t length, uint8_t *data,
        for (ptr = data + length;
             ptr != (data + packet_length);
             ptr += ptr[1]) {
+               /* FIXME: Check that there are 6 bytes of data here... */
                if ((ptr[0] != PW_VENDOR_SPECIFIC) ||
                    (ptr[1] < (2 + 4 + 3)) || /* WiMAX VSA with continuation */
-                   (ptr[2] != 0) || (ptr[3] != 0)) { /* our requirement */
+                   (ptr[2] != 0) || (ptr[3] != 0) ||  /* our requirement */
+                   (ptr[4] != ((vendor >> 8) & 0xff)) ||
+                   (ptr[5] != (vendor & 0xff))) {
                        continue;
                }
 
@@ -2457,6 +2643,74 @@ static uint8_t *rad_coalesce(int attribute, size_t length, uint8_t *data,
        return tlv_data;
 }
 
+
+/*
+ *     Walk over Evil WIMAX TLVs, creating attributes.
+ */
+static VALUE_PAIR *tlv2wimax(const RADIUS_PACKET *packet,
+                            const RADIUS_PACKET *original,
+                            const char *secret,
+                            int attribute, int vendor,
+                            uint8_t *ptr, size_t len, int nest)
+{
+       VALUE_PAIR *head = NULL;
+       VALUE_PAIR **tail = &head;
+       VALUE_PAIR *vp;
+       uint8_t *y;             /* why do I need to do this? */
+
+       if (nest > fr_wimax_max_tlv) return NULL;
+
+       /*
+        *      Sanity check the attribute.
+        */
+       for (y = ptr; y < (ptr + len); y += y[1]) {
+               if ((y[0] == 0) || ((y + 2) > (ptr + len)) ||
+                   (y[1] < 2) || ((y + y[1]) > (ptr + len))) {
+                       return NULL;
+               }
+
+               /*
+                *      Attribute number is too large for us to
+                *      represent it in our horrible internal
+                *      representation.
+                */
+               if ((ptr[0] & ~fr_wimax_mask[nest]) != 0) {
+                       return NULL;
+               }
+       }
+
+       for (y = ptr; y < (ptr + len); y += y[1]) {
+               DICT_ATTR *da;
+
+               da = dict_attrbyvalue(attribute | (ptr[0] << fr_wimax_shift[nest]), vendor);
+               if (da && (da->type == PW_TYPE_TLV)) {
+                       vp = tlv2wimax(packet, original, secret,
+                                      attribute | (ptr[0] << fr_wimax_shift[nest]),
+                                      vendor, ptr + 2, ptr[1] - 2,
+                                      nest + 1);
+                       if (!vp) goto error;
+               } else {
+                       vp = paircreate(attribute | (ptr[0] << fr_wimax_shift[nest]), vendor,
+                                       PW_TYPE_OCTETS);
+                       if (!vp) {
+                       error:
+                               pairfree(&head);
+                               return NULL;
+                       }
+
+                       if (!data2vp(packet, original, secret,
+                                    y[1] - 2, y + 2, vp)) {
+                               goto error;
+                       }
+               }
+
+               *tail = vp;
+               while (*tail) tail = &((*tail)->next);
+       }
+
+       return head;
+}
+
 /*
  *     Start at the *data* portion of a continued attribute.  search
  *     through the rest of the attributes to find a matching one, and
@@ -2465,7 +2719,8 @@ static uint8_t *rad_coalesce(int attribute, size_t length, uint8_t *data,
 static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
                                       const RADIUS_PACKET *original,
                                       const char *secret, int attribute,
-                                      int length,
+                                      int vendor,
+                                      int length, /* CANNOT be zero */
                                       uint8_t *data, size_t packet_length,
                                       int flag, DICT_ATTR *da)
 {
@@ -2473,13 +2728,14 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
        uint8_t *ptr;
        uint8_t *tlv_data;
        VALUE_PAIR *vp, *head, **tail;
+       DICT_ATTR *tlv_da;
 
        /*
         *      Ensure we have data that hasn't been split across
         *      multiple attributes.
         */
        if (flag) {
-               tlv_data = rad_coalesce(attribute, length,
+               tlv_data = rad_coalesce(attribute, vendor, length,
                                        data, packet_length, &tlv_length);
                if (!tlv_data) return NULL;
        } else {
@@ -2498,7 +2754,7 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
         *      Note that we don't check "flag" here.  The calling
         *      code ensures that 
         */
-       if (da->type != PW_TYPE_TLV) {
+       if (!da || (da->type != PW_TYPE_TLV)) {
        not_well_formed:
                if (tlv_data == data) { /* true if we had 'goto' */
                        tlv_data = malloc(tlv_length);
@@ -2506,7 +2762,7 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
                        memcpy(tlv_data, data, tlv_length);
                }
                
-               vp = paircreate(attribute, PW_TYPE_OCTETS);
+               vp = paircreate(attribute, vendor, PW_TYPE_OCTETS);
                if (!vp) return NULL;
                        
                vp->type = PW_TYPE_TLV;
@@ -2531,6 +2787,7 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
                    (ptr[1] > left)) {
                        goto not_well_formed;
                }
+
                left -= ptr[1];
        }
 
@@ -2543,20 +2800,32 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
        for (ptr = tlv_data;
             ptr != (tlv_data + tlv_length);
             ptr += ptr[1]) {
-               vp = paircreate(attribute | (ptr[0] << 8), PW_TYPE_OCTETS);
-               if (!vp) {
-                       pairfree(&head);
-                       goto not_well_formed;
-               }
 
-               if (!data2vp(packet, original, secret,
-                            ptr[0], ptr[1] - 2, ptr + 2, vp)) {
-                       pairfree(&head);
-                       goto not_well_formed;
+               tlv_da = dict_attrbyvalue(attribute | (ptr[0] << fr_wimax_shift[1]), vendor);
+               if (tlv_da && (tlv_da->type == PW_TYPE_TLV)) {
+                       vp = tlv2wimax(packet, original, secret,
+                                      attribute | (ptr[0] << 8),
+                                      vendor, ptr + 2, ptr[1] - 2, 2);
+
+                       if (!vp) goto error;
+               } else {
+                       vp = paircreate(attribute | (ptr[0] << fr_wimax_shift[1]), vendor,
+                                       PW_TYPE_OCTETS);
+                       if (!vp) {
+                       error:
+                               pairfree(&head);
+                               goto not_well_formed;
+                       }
+
+                       if (!data2vp(packet, original, secret,
+                                    ptr[1] - 2, ptr + 2, vp)) {
+                               goto error;
+                       }
                }
 
                *tail = vp;
-               tail = &(vp->next);
+
+               while (*tail) tail = &((*tail)->next);
        }
 
        /*
@@ -2571,18 +2840,88 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
 
 
 /*
+ *     Extended attribute TLV to VP.
+ */
+static VALUE_PAIR *tlv2vp(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret, int attribute,
+                           int length, const uint8_t *data)
+{
+       VALUE_PAIR *vp;
+
+       if ((length < 2) || (data[1] < 2)) return NULL;
+
+       /*
+        *      For now, only one TLV is allowed.
+        */
+       if (data[1] != length) return NULL;
+
+       attribute |= (data[0] << fr_wimax_shift[2]);
+
+       vp = paircreate(attribute, VENDORPEC_EXTENDED, PW_TYPE_OCTETS);
+       if (!vp) return NULL;
+
+       return data2vp(packet, original, secret, length - 2, data + 2, vp);
+}
+
+/*
  *     Parse a RADIUS attribute into a data structure.
  */
-VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
-                       const char *secret, int attribute, int length,
-                       const uint8_t *data)
+VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet,
+                       const RADIUS_PACKET *original,
+                       const char *secret, int attribute, int vendor,
+                       int length, const uint8_t *data)
 {
        VALUE_PAIR *vp;
 
-       vp = paircreate(attribute, PW_TYPE_OCTETS);
+       /*
+        *      Hard-coded values are bad...
+        */
+       if ((vendor == 0) && (attribute >= 241) && (attribute <= 246)) {
+               DICT_ATTR *da;
+
+               da = dict_attrbyvalue(attribute, VENDORPEC_EXTENDED);
+               if (da && (da->flags.extended || da->flags.extended_flags)) {
+
+                       if (length == 0) return NULL;
+
+                       attribute |= (data[0] << fr_wimax_shift[1]);
+                       vendor = VENDORPEC_EXTENDED;
+
+                       data++;
+                       length--;
+
+                       /*
+                        *      There may be a flag octet.
+                        */
+                       if (da->flags.extended_flags) {
+                               if (length == 0) return NULL;
+
+                               /*
+                                *      If there's a flag, we can't
+                                *      handle it.
+                                */
+                               if (data[0] != 0) return NULL;
+                               data++;
+                               length--;
+                       }
+
+                       /*
+                        *      Now look up the extended attribute, to
+                        *      see if it's a TLV carrying more data.
+                        *      
+                        */
+                       da = dict_attrbyvalue(attribute, VENDORPEC_EXTENDED);
+                       if (da && da->flags.has_tlv) {
+                               return tlv2vp(packet, original, secret,
+                                             attribute, length, data);
+                       }
+               }
+       }
+       vp = paircreate(attribute, vendor, PW_TYPE_OCTETS);
        if (!vp) return NULL;
 
-       return data2vp(packet, original, secret, attribute, length, data, vp);
+       return data2vp(packet, original, secret, length, data, vp);
 }
 
 
@@ -2688,13 +3027,10 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                        if (myvendor == 0) goto create_pair;
 
                        /*
-                        *      This is an implementation issue.
-                        *      We currently pack vendor into the upper
-                        *      16 bits of a 32-bit attribute number,
-                        *      so we can't handle vendor numbers larger
-                        *      than 16 bits.
+                        *      Allow vendors up to 2^24.  Past that,
+                        *      get confused.
                         */
-                       if (myvendor > 65535) goto create_pair;
+                       if (myvendor > FR_MAX_VENDOR) goto create_pair;
 
                        vsa_tlen = vsa_llen = 1;
                        vsa_offset = 0;
@@ -2756,7 +3092,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 
                                switch (vsa_llen) {
                                case 0:
-                                       attribute = (myvendor << 16) | myattr;
+                                       attribute = myattr;
                                        ptr += 4 + vsa_tlen;
                                        attrlen -= (4 + vsa_tlen);
                                        packet_length -= 4 + vsa_tlen;
@@ -2825,7 +3161,6 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                default:        /* can't hit this. */
                        return -1;
                }
-               attribute |= (vendorcode << 16);
                vsa_ptr = ptr;
                ptr += vsa_tlen;
 
@@ -2844,20 +3179,26 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 
                ptr += vsa_llen + vsa_offset;
                vendorlen -= vsa_tlen + vsa_llen + vsa_offset + attrlen;
-               if (vendorlen == 0) vendorcode = 0;
                packet_length -= (vsa_tlen + vsa_llen + vsa_offset);
 
                /*
+                *      Ignore VSAs that have no data.
+                */
+               if (attrlen == 0) goto next;
+
+               /*
                 *      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 ((vendorcode == VENDORPEC_WIMAX) && (attribute == 0)) {
+                       goto next;
+               }
 
                if (vsa_offset) {
                        DICT_ATTR *da;
 
-                       da = dict_attrbyvalue(attribute);
+                       da = dict_attrbyvalue(attribute, vendorcode);
 
                        /*
                         *      If it's NOT continued, AND we know
@@ -2872,7 +3213,8 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                         *      Go do a lot of work to find the stuff.
                         */
                        pair = rad_continuation2vp(packet, original, secret,
-                                                  attribute, attrlen, ptr,
+                                                  attribute, vendorcode,
+                                                  attrlen, ptr,
                                                   packet_length,
                                                   ((vsa_ptr[2] & 0x80) != 0),
                                                   da);
@@ -2894,7 +3236,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                    (attribute != PW_CHARGEABLE_USER_IDENTITY)) goto next;
 
                pair = rad_attr2vp(packet, original, secret,
-                                  attribute, attrlen, ptr);
+                                  attribute, vendorcode, attrlen, ptr);
                if (!pair) {
                        pairfree(&packet->vps);
                        fr_strerror_printf("out of memory");
@@ -2930,6 +3272,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                }
 
        next:
+               if (vendorlen == 0) vendorcode = 0;
                ptr += attrlen;
                packet_length -= attrlen;
        }
@@ -3316,7 +3659,7 @@ int rad_chap_encode(RADIUS_PACKET *packet, uint8_t *output, int id,
         *      Use Chap-Challenge pair if present,
         *      Request-Authenticator otherwise.
         */
-       challenge = pairfind(packet->vps, PW_CHAP_CHALLENGE);
+       challenge = pairfind(packet->vps, PW_CHAP_CHALLENGE, 0);
        if (challenge) {
                memcpy(ptr, challenge->vp_strvalue, challenge->length);
                i += challenge->length;
@@ -3444,6 +3787,35 @@ RADIUS_PACKET *rad_alloc(int newvector)
        return rp;
 }
 
+RADIUS_PACKET *rad_alloc_reply(RADIUS_PACKET *packet)
+{
+       RADIUS_PACKET *reply;
+
+       if (!packet) return NULL;
+
+       reply = rad_alloc(0);
+       if (!reply) return NULL;
+
+       /*
+        *      Initialize the fields from the request.
+        */
+       reply->sockfd = packet->sockfd;
+       reply->dst_ipaddr = packet->src_ipaddr;
+       reply->src_ipaddr = packet->dst_ipaddr;
+       reply->dst_port = packet->src_port;
+       reply->src_port = packet->dst_port;
+       reply->id = packet->id;
+       reply->code = 0; /* UNKNOWN code */
+       memcpy(reply->vector, packet->vector,
+              sizeof(reply->vector));
+       reply->vps = NULL;
+       reply->data = NULL;
+       reply->data_len = 0;
+
+       return reply;
+}
+
+
 /*
  *     Free a RADIUS_PACKET
  */