Merge libradsec-new-client.
[libradsec.git] / lib / radius / radpkt.c
diff --git a/lib/radius/radpkt.c b/lib/radius/radpkt.c
new file mode 100644 (file)
index 0000000..bb8f75e
--- /dev/null
@@ -0,0 +1,916 @@
+/*
+Copyright (c) 2011, Network RADIUS SARL
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** \file packet.c
+ *  \brief Encoding and decoding packets
+ */
+
+#include       "client.h"
+
+#if RS_MAX_PACKET_LEN < 64
+#error RS_MAX_PACKET_LEN is too small.  It should be at least 64.
+#endif
+
+#if RS_MAX_PACKET_LEN > 16384
+#error RS_MAX_PACKET_LEN is too large.  It should be smaller than 16K.
+#endif
+
+const char *nr_packet_codes[RS_MAX_PACKET_CODE + 1] = {
+  NULL,
+  "Access-Request",
+  "Access-Accept",
+  "Access-Reject",
+  "Accounting-Request",
+  "Accounting-Response",
+  NULL, NULL, NULL, NULL, NULL,
+  "Access-Challenge",
+  "Status-Server",             /* 12 */
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 19 */
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 20..29 */
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 30..39 */
+  "Disconnect-Request",
+  "Disconnect-ACK",
+  "Disconnect-NAK",
+  "CoA-Request",
+  "CoA-ACK",
+  "CoA-NAK"
+};
+
+
+static uint64_t allowed_responses[RS_MAX_PACKET_CODE + 1] = {
+       0,
+       (1 << PW_ACCESS_ACCEPT) | (1 << PW_ACCESS_REJECT) | (1 << PW_ACCESS_CHALLENGE),
+       0, 0,
+       1 << PW_ACCOUNTING_RESPONSE,
+       0,
+       0, 0, 0, 0, 0,
+       0,
+       (1 << PW_ACCESS_ACCEPT) | (1 << PW_ACCESS_REJECT) | (1 << PW_ACCESS_CHALLENGE) | (1 << PW_ACCOUNTING_RESPONSE),
+       0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..29 */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..39 */
+       (((uint64_t) 1) << PW_DISCONNECT_ACK) | (((uint64_t) 1) << PW_DISCONNECT_NAK),
+       0,
+       0,
+       (((uint64_t) 1) << PW_COA_ACK) | (((uint64_t) 1) << PW_COA_NAK),
+       0,
+       0
+};
+
+
+int nr_packet_ok_raw(const uint8_t *data, size_t sizeof_data)
+{
+       size_t packet_len;
+       const uint8_t *attr, *end;
+
+       if (!data || (sizeof_data < 20)) {
+               nr_debug_error("Invalid argument");
+               return -RSE_INVAL;
+       }
+
+       packet_len = (data[2] << 8) | data[3];
+       if (packet_len < 20) {
+               nr_debug_error("Packet length is too small");
+               return -RSE_PACKET_TOO_SMALL;
+       }
+
+       if (packet_len > sizeof_data) {
+               nr_debug_error("Packet length overflows received data");
+               return -RSE_PACKET_TOO_LARGE;
+       }
+
+       /*
+        *      If we receive 100 bytes, and the header says it's 20 bytes,
+        *      then it's 20 bytes.
+        */
+       end = data + packet_len;
+
+       for (attr = data + 20; attr < end; attr += attr[1]) {
+               if ((attr + 2) > end) {
+                       nr_debug_error("Attribute overflows packet");
+                       return -RSE_ATTR_OVERFLOW;
+               }
+
+               if (attr[1] < 2) {
+                       nr_debug_error("Attribute length is too small");
+                       return -RSE_ATTR_TOO_SMALL;
+               }
+
+               if ((attr + attr[1]) > end) {
+                       nr_debug_error("Attribute length is too large");
+                       return -RSE_ATTR_TOO_LARGE;
+               }
+       }
+
+       return 0;
+}
+
+int nr_packet_ok(RADIUS_PACKET *packet)
+{
+       int rcode;
+
+       if (!packet) return -RSE_INVAL;
+
+       if ((packet->flags & RS_PACKET_OK) != 0) return 0;
+
+       rcode = nr_packet_ok_raw(packet->data, packet->length);
+       if (rcode < 0) return rcode;
+
+       packet->flags |= RS_PACKET_OK;
+       return 0;
+}
+
+
+/*
+ *     Comparison function that is time-independent.  Using "memcmp"
+ *     would satisfy the "comparison" part.  However, it would also
+ *     leak information about *which* bytes are wrong.  Attackers
+ *     could use that leak to create a "correct" RADIUS packet which
+ *     will be accepted by the client and/or server.
+ */
+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;
+}
+
+
+#ifdef PW_MESSAGE_AUTHENTICATOR
+static int msg_auth_ok(const RADIUS_PACKET *original,
+                      uint8_t *ma,
+                      uint8_t *data, size_t length)
+{
+       uint8_t packet_vector[sizeof(original->vector)];
+       uint8_t msg_auth_vector[sizeof(original->vector)];
+       uint8_t calc_auth_vector[sizeof(original->vector)];
+       
+       if (ma[1] != 18) {
+               nr_debug_error("Message-Authenticator has invalid length");
+               return -RSE_MSG_AUTH_LEN;
+       }
+
+       memcpy(packet_vector, data + 4, sizeof(packet_vector));
+       memcpy(msg_auth_vector, ma + 2, sizeof(msg_auth_vector));
+       memset(ma + 2, 0, sizeof(msg_auth_vector));
+
+       switch (data[0]) {
+       default:
+               break;
+               
+       case PW_ACCOUNTING_REQUEST:
+       case PW_ACCOUNTING_RESPONSE:
+       case PW_DISCONNECT_REQUEST:
+       case PW_DISCONNECT_ACK:
+       case PW_DISCONNECT_NAK:
+       case PW_COA_REQUEST:
+       case PW_COA_ACK:
+       case PW_COA_NAK:
+               memset(data + 4, 0, sizeof(packet_vector));
+               break;
+               
+       case PW_ACCESS_ACCEPT:
+       case PW_ACCESS_REJECT:
+       case PW_ACCESS_CHALLENGE:
+               if (!original) {
+                       nr_debug_error("Cannot validate response without request");
+                       return -RSE_REQUEST_REQUIRED;
+               }
+               memcpy(data + 4, original->vector, sizeof(original->vector));
+               break;
+       }
+       
+       nr_hmac_md5(data, length,
+                   (const uint8_t *) original->secret, original->sizeof_secret,
+                   calc_auth_vector);
+
+       memcpy(ma + 2, msg_auth_vector, sizeof(msg_auth_vector));
+       memcpy(data + 4, packet_vector, sizeof(packet_vector));
+
+       if (digest_cmp(calc_auth_vector, msg_auth_vector,
+                      sizeof(calc_auth_vector)) != 0) {
+               nr_debug_error("Invalid Message-Authenticator");
+               return -RSE_MSG_AUTH_WRONG;
+       }
+
+       return 1;
+}
+#endif
+
+/*
+ *     The caller ensures that the packet codes are as expected.
+ */
+static int packet_auth_ok(const RADIUS_PACKET *original,
+                         uint8_t *data, size_t length)
+{
+       uint8_t packet_vector[sizeof(original->vector)];
+       uint8_t calc_digest[sizeof(original->vector)];
+       RS_MD5_CTX ctx;
+
+       if ((data[0] == PW_ACCESS_REQUEST) ||
+           (data[0] == PW_STATUS_SERVER)) return 1;
+
+       memcpy(packet_vector, data + 4, sizeof(packet_vector));
+
+       if (!original) {
+               memset(data + 4, 0, sizeof(packet_vector));
+       } else {
+               memcpy(data + 4, original->vector, sizeof(original->vector));
+       }
+
+       RS_MD5Init(&ctx);
+       RS_MD5Update(&ctx, data, length);
+       RS_MD5Update(&ctx, (const unsigned char *)original->secret, original->sizeof_secret);
+       RS_MD5Final(calc_digest, &ctx);
+
+       memcpy(data + 4, packet_vector, sizeof(packet_vector));
+
+       if (digest_cmp(calc_digest, packet_vector,
+                      sizeof(packet_vector)) != 0) {
+               nr_debug_error("Invalid authentication vector");
+               return -RSE_AUTH_VECTOR_WRONG;
+       }
+
+       return 0;
+}
+
+
+int nr_packet_verify(RADIUS_PACKET *packet, const RADIUS_PACKET *original)
+{
+       int rcode;
+       uint8_t *attr;
+#ifdef PW_MESSAGE_AUTHENTICATOR
+       const uint8_t *end;
+#endif
+
+       if (!packet || !packet->data || !packet->secret) {
+               nr_debug_error("Invalid argument");
+               return -RSE_INVAL;
+       }
+
+       if ((packet->flags & RS_PACKET_VERIFIED) != 0) return 0;
+
+       /*
+        *      Packet isn't well formed.  Ignore it.
+        */
+       rcode = nr_packet_ok(packet);
+       if (rcode < 0) return rcode;
+
+       /*
+        *      Get rid of improper packets as early as possible.
+        */
+       if (original) {
+               uint64_t mask;
+
+               if (original->code > RS_MAX_PACKET_CODE) {
+                       nr_debug_error("Invalid original code %u",
+                                          original->code);
+                       return -RSE_INVALID_REQUEST_CODE;
+               }
+
+               if (packet->data[1] != original->id) {
+                       nr_debug_error("Ignoring response with wrong ID %u",
+                                          packet->data[1]);
+                       return -RSE_INVALID_RESPONSE_CODE;
+               }
+
+               mask = 1;
+               mask <<= packet->data[0];
+
+               if ((allowed_responses[original->code] & mask) == 0) {
+                       nr_debug_error("Ignoring response with wrong code %u",
+                                          packet->data[0]);
+                       return -RSE_INVALID_RESPONSE_CODE;
+               }
+
+               if ((memcmp(&packet->src, &original->dst, sizeof(packet->src)) != 0) &&
+                   (evutil_sockaddr_cmp((struct sockaddr *)&packet->src, (struct sockaddr *)&original->dst, 1) != 0)) {
+                       nr_debug_error("Ignoring response from wrong IP/port");
+                       return -RSE_INVALID_RESPONSE_SRC;
+               }
+
+       } else if (allowed_responses[packet->data[0]] != 0) {
+               nr_debug_error("Ignoring response without original");
+               return -RSE_INVALID_RESPONSE_CODE;
+       }
+
+#ifdef PW_MESSAGE_AUTHENTICATOR
+       end = packet->data + packet->length;
+
+       /*
+        *      Note that the packet MUST be well-formed here.
+        */
+       for (attr = packet->data + 20; attr < end; attr += attr[1]) {
+               if (attr[0] == PW_MESSAGE_AUTHENTICATOR) {
+                       rcode = msg_auth_ok(original, attr,
+                                           packet->data, packet->length);
+                       if (rcode < 0) return rcode;
+               }
+       }
+#endif
+
+       /*
+        *      Verify the packet authenticator.
+        */
+       rcode = packet_auth_ok(original, packet->data, packet->length);
+       if (rcode < 0) return rcode;
+
+       packet->flags |= RS_PACKET_VERIFIED;
+
+       return 0;
+}
+
+
+int nr_packet_decode(RADIUS_PACKET *packet, const RADIUS_PACKET *original)
+{
+       int             rcode, num_attributes;
+       uint8_t         *data, *attr;
+       const uint8_t   *end;
+       VALUE_PAIR      **tail, *vp;
+
+       if (!packet) return -RSE_INVAL;
+
+       if ((packet->flags & RS_PACKET_DECODED) != 0) return 0;
+      
+       rcode = nr_packet_ok(packet);
+       if (rcode < 0) return rcode;
+
+       data = packet->data;
+       end = data + packet->length;
+       tail = &packet->vps;
+       num_attributes = 0;
+
+       /*
+        *      Loop over the packet, converting attrs to VPs.
+        */
+       for (attr = data + 20; attr < end; attr += attr[1]) {
+               rcode = nr_attr2vp(packet, original,
+                                   attr, end - attr, &vp);
+               if (rcode < 0) {
+                       nr_vp_free(&packet->vps);
+                       return -rcode;
+               }
+
+               *tail = vp;
+               while (vp) {
+                       num_attributes++;
+                       tail = &(vp->next);
+                       vp = vp->next;
+               }
+
+               if (num_attributes > RS_MAX_ATTRIBUTES) {
+                       nr_debug_error("Too many attributes");
+                       nr_vp_free(&packet->vps);
+                       return -RSE_TOO_MANY_ATTRS;
+               }
+       }
+
+       packet->code = data[0];
+       packet->id = data[1];
+       memcpy(packet->vector, data + 4, sizeof(packet->vector));
+
+       packet->flags |= RS_PACKET_DECODED;
+
+       return 0;
+}
+
+
+int nr_packet_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original)
+{
+#ifdef PW_MESSAGE_AUTHENTICATOR
+       size_t ma = 0;
+       const uint8_t *attr, *end;
+#endif
+
+       if ((packet->flags & RS_PACKET_SIGNED) != 0) return 0;
+
+       if ((packet->flags & RS_PACKET_ENCODED) == 0) {
+               int rcode;
+
+               rcode = nr_packet_encode(packet, original);
+               if (rcode < 0) return rcode;
+       }
+
+       if ((packet->code == PW_ACCESS_ACCEPT) ||
+           (packet->code == PW_ACCESS_CHALLENGE) ||
+           (packet->code == PW_ACCESS_REJECT)) {
+#ifdef PW_MESSAGE_AUTHENTICATOR
+               if (!original) {
+                       nr_debug_error("Original packet is required to create the  Message-Authenticator");
+                       return -RSE_REQUEST_REQUIRED;
+               }
+#endif
+               
+               memcpy(packet->data + 4, original->vector,
+                      sizeof(original->vector));
+       } else {
+               memcpy(packet->data + 4, packet->vector,
+                      sizeof(packet->vector));
+       }
+
+#ifdef PW_MESSAGE_AUTHENTICATOR
+       end = packet->data + packet->length;
+
+       for (attr = packet->data + 20; attr < end; attr += attr[1]) {
+               if (attr[0] == PW_MESSAGE_AUTHENTICATOR) {
+                       ma = (attr - packet->data);
+                       break;
+               }
+       }
+
+       /*
+        *      Force all Access-Request packets to have a
+        *      Message-Authenticator.
+        */
+       if (!ma && ((packet->length + 18) <= packet->sizeof_data) &&
+           ((packet->code == PW_ACCESS_REQUEST) ||
+            (packet->code == PW_STATUS_SERVER))) {
+               ma = packet->length;
+
+               packet->data[ma]= PW_MESSAGE_AUTHENTICATOR;
+               packet->data[ma + 1] = 18;
+               memset(&packet->data[ma + 2], 0, 16);
+               packet->length += 18;
+       }
+
+       /*
+        *      Reset the length.
+        */
+       packet->data[2] = (packet->length >> 8) & 0xff;
+       packet->data[3] = packet->length & 0xff;
+
+       /*
+        *      Sign the Message-Authenticator && packet.
+        */
+       if (ma) {
+               nr_hmac_md5(packet->data, packet->length,
+                           (const uint8_t *) packet->secret, packet->sizeof_secret,
+                           packet->data + ma + 2);
+       }
+#endif
+
+       /*
+        *      Calculate the signature.
+        */
+       if (!((packet->code == PW_ACCESS_REQUEST) ||
+             (packet->code == PW_STATUS_SERVER))) {
+               RS_MD5_CTX      ctx;
+
+               RS_MD5Init(&ctx);
+               RS_MD5Update(&ctx, packet->data, packet->length);
+               RS_MD5Update(&ctx, (const unsigned char *)packet->secret, packet->sizeof_secret);
+               RS_MD5Final(packet->vector, &ctx);
+       }
+
+       memcpy(packet->data + 4, packet->vector, sizeof(packet->vector));
+
+       packet->attempts = 0;
+       packet->flags |= RS_PACKET_SIGNED;
+
+       return 0;
+}
+
+
+static int can_encode_packet(RADIUS_PACKET *packet,
+                            const RADIUS_PACKET *original)
+{
+       if ((packet->code == 0) ||
+           (packet->code > RS_MAX_PACKET_CODE) ||
+           (original && (original->code > RS_MAX_PACKET_CODE))) {
+               nr_debug_error("Cannot send unknown packet code");
+               return -RSE_INVALID_REQUEST_CODE;
+       }
+
+       if (!nr_packet_codes[packet->code]) {
+               nr_debug_error("Cannot handle packet code %u",
+                                  packet->code);
+               return -RSE_INVALID_REQUEST_CODE;
+       }
+
+#ifdef NR_NO_MALLOC
+       if (!packet->data) {
+               nr_debug_error("No place to put packet");
+               return -RSE_NO_PACKET_DATA;
+       }
+#endif
+
+       if (packet->sizeof_data < 20) {
+               nr_debug_error("The buffer is too small to encode the packet");
+               return -RSE_PACKET_TOO_SMALL;
+       }
+
+       /*
+        *      Enforce request / response correlation.
+        */
+       if (original) {
+               uint64_t mask;
+
+               mask = 1;
+               mask <<= packet->code;
+
+               if ((allowed_responses[original->code] & mask) == 0) {
+                       nr_debug_error("Cannot encode response %u to packet %u",
+                                          packet->code, original->code);
+                       return -RSE_INVALID_RESPONSE_CODE;
+               }
+               packet->id = original->id;
+
+       } else if (allowed_responses[packet->code] == 0) {
+               nr_debug_error("Cannot encode response %u without original",
+                                  packet->code);
+               return -RSE_REQUEST_REQUIRED;
+       }
+
+       return 0;
+}
+
+static void encode_header(RADIUS_PACKET *packet)
+{
+       if ((packet->flags & RS_PACKET_HEADER) != 0) return;
+
+       memset(packet->data, 0, 20);
+       packet->data[0] = packet->code;
+       packet->data[1] = packet->id;
+       packet->data[2] = 0;
+       packet->data[3] = 20;
+       packet->length = 20;
+
+       /*
+        *      Calculate a random authentication vector.
+        */
+       if ((packet->code == PW_ACCESS_REQUEST) ||
+           (packet->code == PW_STATUS_SERVER)) {
+               nr_rand_bytes(packet->vector, sizeof(packet->vector));
+       } else {
+               memset(packet->vector, 0, sizeof(packet->vector));
+       }
+
+       memcpy(packet->data + 4, packet->vector, sizeof(packet->vector));
+
+       packet->flags |= RS_PACKET_HEADER;
+}
+
+int nr_packet_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original)
+{
+#ifdef PW_MESSAGE_AUTHENTICATOR
+       size_t ma = 0;
+#endif
+       int rcode;
+       ssize_t len;
+       const VALUE_PAIR *vp;
+       uint8_t *data, *end;
+
+       if ((packet->flags & RS_PACKET_ENCODED) != 0) return 0;
+
+       rcode = can_encode_packet(packet, original);
+       if (rcode < 0) return rcode;
+
+       data = packet->data;
+       end = data + packet->sizeof_data;
+
+       encode_header(packet);
+       data += 20;
+
+       /*
+        *      Encode each VALUE_PAIR
+        */
+       vp = packet->vps;
+       while (vp) {
+#ifdef PW_MESSAGE_AUTHENTICATOR
+               if (vp->da->attr == PW_MESSAGE_AUTHENTICATOR) {
+                       ma = (data - packet->data);
+               }
+#endif
+               len = nr_vp2attr(packet, original, &vp,
+                                 data, end - data);
+               if (len < 0) return len;
+
+               if (len == 0) break; /* insufficient room to encode it */
+
+               data += data[1];
+       }
+
+#ifdef PW_MESSAGE_AUTHENTICATOR
+       /*
+        *      Always send a Message-Authenticator.
+        *
+        *      We do *not* recommend removing this code.
+        */
+       if (((packet->code == PW_ACCESS_REQUEST) ||
+            (packet->code == PW_STATUS_SERVER)) &&
+           !ma &&
+           ((data + 18) <= end)) {
+               ma = (data - packet->data);
+               data[0] = PW_MESSAGE_AUTHENTICATOR;
+               data[1] = 18;
+               memset(data + 2, 0, 16);
+               data += data[1];
+       }
+#endif
+
+       packet->length = data - packet->data;
+
+       packet->data[2] = (packet->length >> 8) & 0xff;
+       packet->data[3] = packet->length & 0xff;
+
+       packet->flags |= RS_PACKET_ENCODED;
+
+       return packet->length;
+}
+
+
+/*
+ *     Ensure that the nr_data2attr_t structure is filled in
+ *     appropriately.  This includes filling in a fake DICT_ATTR
+ *     structure, if necessary.
+ */
+static int do_callback(void *ctx, nr_packet_walk_func_t callback,
+                      int attr, int vendor,
+                      const uint8_t *data, size_t sizeof_data)
+                      
+{
+       int rcode;
+       const DICT_ATTR *da;
+       DICT_ATTR myda;
+       char buffer[64];
+
+       da = nr_dict_attr_byvalue(attr, vendor);
+
+       /*
+        *      The attribute is supposed to have a particular length,
+        *      but does not.  It is therefore malformed.
+        */
+       if (da && (da->flags.length != 0) &&
+           da->flags.length != sizeof_data) {
+               da = NULL;
+       }
+
+       if (!da) {
+               rcode = nr_dict_attr_2struct(&myda, attr, vendor,
+                                            buffer, sizeof(buffer));
+               
+               if (rcode < 0) return rcode;
+               da = &myda;
+       }
+       
+       rcode = callback(ctx, da, data, sizeof_data);
+       if (rcode < 0) return rcode;
+
+       return 0;
+}
+
+
+int nr_packet_walk(RADIUS_PACKET *packet, void *ctx,
+                  nr_packet_walk_func_t callback)
+{
+       int rcode;
+       uint8_t *attr;
+       const uint8_t *end;
+
+       if (!packet || !callback) return -RSE_INVAL;
+
+       rcode = nr_packet_ok(packet);
+       if (rcode < 0) return rcode;
+
+       end = packet->data + packet->length;
+
+       for (attr = packet->data + 20; attr < end; attr += attr[1]) {
+               int length, value;
+               int dv_type, dv_length;
+               uint32_t vendorpec;
+               const uint8_t *vsa;
+               const DICT_VENDOR *dv = NULL;
+
+               vendorpec = 0;
+               value = attr[0];
+
+               if (value != PW_VENDOR_SPECIFIC) {
+               raw:
+                       rcode = do_callback(ctx, callback,
+                                           attr[0], 0,
+                                           attr + 2, attr[1] - 2);
+                       if (rcode < 0) return rcode;
+                       continue;
+               }
+
+               if (attr[1] < 6) goto raw;
+               memcpy(&vendorpec, attr + 2, 4);
+               vendorpec = ntohl(vendorpec);
+
+               if (dv && (dv->vendor != vendorpec)) dv = NULL;
+
+               if (!dv) dv = nr_dict_vendor_byvalue(vendorpec);
+
+               if (dv) {
+                       dv_type = dv->type;
+                       dv_length = dv->length;
+               } else {
+                       dv_type = 1;
+                       dv_length = 1;
+               }
+
+               /*
+                *      Malformed: it's a raw attribute.
+                */
+               if (nr_tlv_ok(attr + 6, attr[1] - 6, dv_type, dv_length) < 0) {
+                       goto raw;
+               }
+
+               for (vsa = attr + 6; vsa < attr + attr[1]; vsa += length) {
+                       switch (dv_type) {
+                       case 4:
+                               value = (vsa[2] << 8) | vsa[3];
+                               break;
+
+                       case 2:
+                               value = (vsa[0] << 8) | vsa[1];
+                               break;
+
+                       case 1:
+                               value = vsa[0];
+                               break;
+
+                       default:
+                               return -RSE_INTERNAL;
+                       }
+
+                       switch (dv_length) {
+                       case 0:
+                               length = attr[1] - 6 - dv_type;
+                               break;
+
+                       case 2:
+                       case 1:
+                               length = vsa[dv_type + dv_length - 1];
+                               break;
+
+                       default:
+                               return -RSE_INTERNAL;
+                       }
+
+                       rcode = do_callback(ctx, callback,
+                                           value, vendorpec,
+                                           vsa + dv_type + dv_length,
+                                           length - dv_type - dv_length);
+                       if (rcode < 0) return rcode;
+               }
+       }
+
+       return 0;
+}
+
+int nr_packet_init(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
+                  const char *secret, int code,
+                  void *data, size_t sizeof_data)
+{
+       int rcode;
+
+       if ((code < 0) || (code > RS_MAX_PACKET_CODE)) {
+               return -RSE_INVALID_REQUEST_CODE;
+       }
+
+       if (!data || (sizeof_data < 20)) return -RSE_INVAL;
+
+       memset(packet, 0, sizeof(*packet));
+       packet->secret = secret;
+       packet->sizeof_secret = secret ? strlen(secret) : 0;
+       packet->code = code;
+       packet->id = 0;
+       packet->data = data;
+       packet->sizeof_data = sizeof_data;
+
+       rcode = can_encode_packet(packet, original);
+       if (rcode < 0) return rcode;
+
+       encode_header(packet);
+
+       return 0;
+}
+
+
+static int pack_eap(RADIUS_PACKET *packet,
+                   const void *data, size_t data_len)
+{
+       uint8_t *attr, *end;
+       const uint8_t *eap;
+       size_t left;
+               
+       eap = data;
+       left = data_len;
+       attr = packet->data + packet->length;
+       end = attr + packet->sizeof_data;
+       
+       while (left > 253) {
+               if ((attr + 255) > end) return -RSE_ATTR_OVERFLOW;
+               
+               attr[0] = PW_EAP_MESSAGE;
+               attr[1] = 255;
+               memcpy(attr + 2, eap, 253);
+               attr += attr[1];
+               eap += 253;
+               left -= 253;
+       }
+       
+       if ((attr + (2 + left)) > end) return -RSE_ATTR_OVERFLOW;
+       
+       attr[0] = PW_EAP_MESSAGE;
+       attr[1] = 2 + left;
+       memcpy(attr + 2, eap, left);
+       attr += attr[1];
+       packet->length = attr - packet->data;
+
+       return 0;
+}
+
+ssize_t nr_packet_attr_append(RADIUS_PACKET *packet,
+                             const RADIUS_PACKET *original,
+                             const DICT_ATTR *da,
+                             const void *data, size_t data_len)
+{
+       ssize_t rcode;
+       uint8_t *attr, *end;
+       VALUE_PAIR my_vp;
+       const VALUE_PAIR *vp;
+
+       if (!packet || !da || !data) {
+               return -RSE_INVAL;
+       }
+
+       if (data_len == 0) {
+               if (da->type != RS_TYPE_STRING) return -RSE_ATTR_TOO_SMALL;
+
+               data_len = strlen(data);
+       }
+
+       packet->flags |= RS_PACKET_ENCODED; /* ignore any VPs */
+
+       attr = packet->data + packet->length;
+       end = attr + packet->sizeof_data;
+
+       if ((attr + 2 + data_len) > end) {
+               return -RSE_ATTR_OVERFLOW;
+       }
+
+       if ((da->flags.length != 0) &&
+           (data_len != da->flags.length)) {
+               return -RSE_ATTR_VALUE_MALFORMED;
+       }
+
+#ifdef PW_EAP_MESSAGE
+       /*
+        *      automatically split EAP-Message into multiple
+        *      attributes.
+        */
+       if (!da->vendor && (da->attr == PW_EAP_MESSAGE) && (data_len > 253)) {
+               return pack_eap(packet, data, data_len);
+       }
+#endif
+
+       if (data_len > 253) return -RSE_ATTR_TOO_LARGE;
+
+       vp = nr_vp_init(&my_vp, da);
+       rcode = nr_vp_set_data(&my_vp, data, data_len);
+       if (rcode < 0) return rcode;
+
+       /*
+        *      Note that this function packs VSAs each into their own
+        *      Vendor-Specific attribute.  If this isn't what you
+        *      want, use the version of the library with full support
+        *      for TLVs, WiMAX, and extended attributes.
+        */
+       rcode = nr_vp2attr(packet, original, &vp, attr, end - attr);
+       if (rcode <= 0) return rcode;
+
+       packet->length += rcode;
+
+       return rcode;
+}