From 24763e3cd0a564eb71f3c501bbb4fbb0d7070762 Mon Sep 17 00:00:00 2001
From: Jouni Malinen <j@w1.fi>
Date: Fri, 15 Apr 2022 17:31:48 +0300
Subject: [PATCH] RADIUS: Attributes with Extended Types (RFC 6929)

Supported extended types for RADIUS attributes for the cases defined in
RFC 6929.

Signed-off-by: Jouni Malinen <j@w1.fi>
---
 src/radius/radius.c | 195 ++++++++++++++++++++++++++++++++++++++------
 src/radius/radius.h |  26 +++++-
 2 files changed, 193 insertions(+), 28 deletions(-)

diff --git a/src/radius/radius.c b/src/radius/radius.c
index be16e27b9..a64228067 100644
--- a/src/radius/radius.c
+++ b/src/radius/radius.c
@@ -1,6 +1,6 @@
 /*
  * RADIUS message processing
- * Copyright (c) 2002-2009, 2011-2015, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2009, 2011-2022, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -159,7 +159,8 @@ static const char *radius_code_string(u8 code)
 
 
 struct radius_attr_type {
-	u8 type;
+	u16 type; /* 0..255 for basic types;
+		   * (241 << 8) | <ext-type> for extended types */
 	char *name;
 	enum {
 		RADIUS_ATTR_UNDIST, RADIUS_ATTR_TEXT, RADIUS_ATTR_IP,
@@ -260,11 +261,31 @@ static const struct radius_attr_type radius_attrs[] =
 	  RADIUS_ATTR_HEXDUMP },
 	{ RADIUS_ATTR_WLAN_GROUP_MGMT_CIPHER, "WLAN-Group-Mgmt-Pairwise-Cipher",
 	  RADIUS_ATTR_HEXDUMP },
+	{ RADIUS_ATTR_EXT_TYPE_1, "Extended-Type-1", RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_TYPE_2, "Extended-Type-2", RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_TYPE_3, "Extended-Type-3", RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_TYPE_4, "Extended-Type-4", RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_LONG_EXT_TYPE_1, "Long-Extended-Type-1",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_LONG_EXT_TYPE_2, "Long-Extended-Type-2",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_VENDOR_SPECIFIC_1, "Extended-Vendor-Specific-1",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_VENDOR_SPECIFIC_2, "Extended-Vendor-Specific-2",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_VENDOR_SPECIFIC_3, "Extended-Vendor-Specific-3",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_VENDOR_SPECIFIC_4, "Extended-Vendor-Specific-4",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_VENDOR_SPECIFIC_5, "Extended-Vendor-Specific-5",
+	  RADIUS_ATTR_UNDIST },
+	{ RADIUS_ATTR_EXT_VENDOR_SPECIFIC_6, "Extended-Vendor-Specific-6",
+	  RADIUS_ATTR_UNDIST },
 };
 #define RADIUS_ATTRS ARRAY_SIZE(radius_attrs)
 
 
-static const struct radius_attr_type *radius_get_attr_type(u8 type)
+static const struct radius_attr_type * radius_get_attr_type(u16 type)
 {
 	size_t i;
 
@@ -277,23 +298,60 @@ static const struct radius_attr_type *radius_get_attr_type(u8 type)
 }
 
 
+static bool radius_is_long_ext_type(u8 type)
+{
+	return type == RADIUS_ATTR_LONG_EXT_TYPE_1 ||
+		type == RADIUS_ATTR_LONG_EXT_TYPE_2;
+}
+
+
+static bool radius_is_ext_type(u8 type)
+{
+	return type >= RADIUS_ATTR_EXT_TYPE_1 &&
+		type <= RADIUS_ATTR_LONG_EXT_TYPE_2;
+}
+
+
 static void radius_msg_dump_attr(struct radius_attr_hdr *hdr)
 {
+	struct radius_attr_hdr_ext *ext = NULL;
 	const struct radius_attr_type *attr;
 	int len;
 	unsigned char *pos;
 	char buf[1000];
 
-	attr = radius_get_attr_type(hdr->type);
+	if (hdr->length < sizeof(struct radius_attr_hdr))
+		return;
 
-	wpa_printf(MSG_INFO, "   Attribute %d (%s) length=%d",
-		   hdr->type, attr ? attr->name : "?Unknown?", hdr->length);
+	if (radius_is_ext_type(hdr->type)) {
+		if (hdr->length < 4) {
+			wpa_printf(MSG_INFO,
+				   "   Invalid attribute %d (too short for extended type)",
+				hdr->type);
+			return;
+		}
 
-	if (attr == NULL || hdr->length < sizeof(struct radius_attr_hdr))
-		return;
+		ext = (struct radius_attr_hdr_ext *) hdr;
+	}
+
+	if (ext) {
+		attr = radius_get_attr_type((ext->type << 8) | ext->ext_type);
+		wpa_printf(MSG_INFO, "   Attribute %d.%d (%s) length=%d",
+			   ext->type, ext->ext_type,
+			   attr ? attr->name : "?Unknown?", ext->length);
+		pos = (unsigned char *) (ext + 1);
+		len = ext->length - sizeof(struct radius_attr_hdr_ext);
+	} else {
+		attr = radius_get_attr_type(hdr->type);
+		wpa_printf(MSG_INFO, "   Attribute %d (%s) length=%d",
+			   hdr->type, attr ? attr->name : "?Unknown?",
+			   hdr->length);
+		pos = (unsigned char *) (hdr + 1);
+		len = hdr->length - sizeof(struct radius_attr_hdr);
+	}
 
-	len = hdr->length - sizeof(struct radius_attr_hdr);
-	pos = (unsigned char *) (hdr + 1);
+	if (!attr)
+		return;
 
 	switch (attr->data_type) {
 	case RADIUS_ATTR_TEXT:
@@ -627,22 +685,54 @@ static int radius_msg_add_attr_to_array(struct radius_msg *msg,
 }
 
 
-struct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type,
-					    const u8 *data, size_t data_len)
+struct radius_attr_hdr * radius_msg_add_attr(struct radius_msg *msg, u16 type,
+					     const u8 *data, size_t data_len)
 {
-	size_t buf_needed;
-	struct radius_attr_hdr *attr;
+	size_t buf_needed, max_len;
+	struct radius_attr_hdr *attr = NULL;
+	struct radius_attr_hdr_ext *ext;
+	u8 ext_type = 0;
 
 	if (TEST_FAIL())
 		return NULL;
 
-	if (data_len > RADIUS_MAX_ATTR_LEN) {
-		wpa_printf(MSG_ERROR, "radius_msg_add_attr: too long attribute (%lu bytes)",
-		       (unsigned long) data_len);
-		return NULL;
+	if (type > 255) {
+		if (!radius_is_ext_type(type >> 8)) {
+			wpa_printf(MSG_ERROR,
+				   "%s: Undefined extended type %d.%d",
+				   __func__, type >> 8, type & 0xff);
+			return NULL;
+		}
+		ext_type = type & 0xff;
+		type >>= 8;
+	} else if (radius_is_ext_type(type)) {
+		wpa_printf(MSG_ERROR, "%s: Unexpected extended type use for %d",
+			   __func__, type);
 	}
 
-	buf_needed = sizeof(*attr) + data_len;
+	if (radius_is_long_ext_type(type)) {
+		size_t hdr_len = sizeof(struct radius_attr_hdr_ext) + 1;
+		size_t plen = 255 - hdr_len;
+		size_t num;
+
+		max_len = 4096;
+		num = (data_len + plen - 1) / plen;
+		if (num == 0)
+			num = 1;
+		buf_needed = num * hdr_len + data_len;
+	} else if (radius_is_ext_type(type)) {
+		max_len = RADIUS_MAX_EXT_ATTR_LEN;
+		buf_needed = sizeof(struct radius_attr_hdr_ext) + data_len;
+	} else {
+		max_len = RADIUS_MAX_ATTR_LEN;
+		buf_needed = sizeof(*attr) + data_len;
+	}
+	if (data_len > max_len) {
+		wpa_printf(MSG_ERROR,
+			   "%s: too long attribute (%zu > %zu bytes)",
+			   __func__, data_len, max_len);
+		return NULL;
+	}
 
 	if (wpabuf_tailroom(msg->buf) < buf_needed) {
 		/* allocate more space for message buffer */
@@ -651,13 +741,44 @@ struct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type,
 		msg->hdr = wpabuf_mhead(msg->buf);
 	}
 
-	attr = wpabuf_put(msg->buf, sizeof(struct radius_attr_hdr));
-	attr->type = type;
-	attr->length = sizeof(*attr) + data_len;
-	wpabuf_put_data(msg->buf, data, data_len);
-
-	if (radius_msg_add_attr_to_array(msg, attr))
-		return NULL;
+	if (radius_is_long_ext_type(type)) {
+		size_t plen = 255 - sizeof(struct radius_attr_hdr_ext) - 1;
+		size_t alen;
+
+		do {
+			alen = data_len > plen ? plen : data_len;
+			ext = wpabuf_put(msg->buf,
+					 sizeof(struct radius_attr_hdr_ext));
+			if (!attr)
+				attr = (struct radius_attr_hdr *) ext;
+			ext->type = type;
+			ext->length = sizeof(*ext) + 1 + alen;
+			ext->ext_type = ext_type;
+			wpabuf_put_u8(msg->buf, data_len > alen ? 0x80 : 0);
+			wpabuf_put_data(msg->buf, data, data_len);
+			data += alen;
+			data_len -= alen;
+			if (radius_msg_add_attr_to_array(
+				    msg, (struct radius_attr_hdr *) ext))
+				return NULL;
+		} while (data_len > 0);
+	} else if (radius_is_ext_type(type)) {
+		ext = wpabuf_put(msg->buf, sizeof(struct radius_attr_hdr_ext));
+		attr = (struct radius_attr_hdr *) ext;
+		ext->type = type;
+		ext->length = sizeof(*ext) + data_len;
+		ext->ext_type = ext_type;
+		wpabuf_put_data(msg->buf, data, data_len);
+		if (radius_msg_add_attr_to_array(msg, attr))
+			return NULL;
+	} else {
+		attr = wpabuf_put(msg->buf, sizeof(struct radius_attr_hdr));
+		attr->type = type;
+		attr->length = sizeof(*attr) + data_len;
+		wpabuf_put_data(msg->buf, data, data_len);
+		if (radius_msg_add_attr_to_array(msg, attr))
+			return NULL;
+	}
 
 	return attr;
 }
@@ -1285,6 +1406,28 @@ int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data,
 }
 
 
+int radius_msg_add_ext_vs(struct radius_msg *msg, u16 type, u32 vendor_id,
+			  u8 vendor_type, const u8 *data, size_t len)
+{
+	struct radius_attr_hdr *attr;
+	u8 *buf, *pos;
+	size_t alen;
+
+	alen = 4 + 1 + len;
+	buf = os_malloc(alen);
+	if (!buf)
+		return 0;
+	pos = buf;
+	WPA_PUT_BE32(pos, vendor_id);
+	pos += 4;
+	*pos++ = vendor_type;
+	os_memcpy(pos, data, len);
+	attr = radius_msg_add_attr(msg, type, buf, alen);
+	os_free(buf);
+	return attr != NULL;
+}
+
+
 int radius_user_password_hide(struct radius_msg *msg,
 			      const u8 *data, size_t data_len,
 			      const u8 *secret, size_t secret_len,
diff --git a/src/radius/radius.h b/src/radius/radius.h
index fb8148180..490c8d1f6 100644
--- a/src/radius/radius.h
+++ b/src/radius/radius.h
@@ -1,6 +1,6 @@
 /*
  * RADIUS message processing
- * Copyright (c) 2002-2009, 2012, 2014-2015, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2009, 2012, 2014-2022, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -46,7 +46,15 @@ struct radius_attr_hdr {
 	/* followed by length-2 octets of attribute value */
 } STRUCT_PACKED;
 
+struct radius_attr_hdr_ext {
+	u8 type;
+	u8 length; /* including this header */
+	u8 ext_type;
+	/* followed by length-3 octets of attribute value */
+} STRUCT_PACKED;
+
 #define RADIUS_MAX_ATTR_LEN (255 - sizeof(struct radius_attr_hdr))
+#define RADIUS_MAX_EXT_ATTR_LEN (255 - sizeof(struct radius_attr_hdr_ext))
 
 enum { RADIUS_ATTR_USER_NAME = 1,
        RADIUS_ATTR_USER_PASSWORD = 2,
@@ -113,6 +121,18 @@ enum { RADIUS_ATTR_USER_NAME = 1,
        RADIUS_ATTR_WLAN_GROUP_CIPHER = 187,
        RADIUS_ATTR_WLAN_AKM_SUITE = 188,
        RADIUS_ATTR_WLAN_GROUP_MGMT_CIPHER = 189,
+       RADIUS_ATTR_EXT_TYPE_1 = 241,
+       RADIUS_ATTR_EXT_TYPE_2 = 242,
+       RADIUS_ATTR_EXT_TYPE_3 = 243,
+       RADIUS_ATTR_EXT_TYPE_4 = 244,
+       RADIUS_ATTR_LONG_EXT_TYPE_1 = 245,
+       RADIUS_ATTR_LONG_EXT_TYPE_2 = 246,
+       RADIUS_ATTR_EXT_VENDOR_SPECIFIC_1 = (241 << 8) | 26,
+       RADIUS_ATTR_EXT_VENDOR_SPECIFIC_2 = (242 << 8) | 26,
+       RADIUS_ATTR_EXT_VENDOR_SPECIFIC_3 = (243 << 8) | 26,
+       RADIUS_ATTR_EXT_VENDOR_SPECIFIC_4 = (244 << 8) | 26,
+       RADIUS_ATTR_EXT_VENDOR_SPECIFIC_5 = (245 << 8) | 26,
+       RADIUS_ATTR_EXT_VENDOR_SPECIFIC_6 = (246 << 8) | 26,
 };
 
 
@@ -257,7 +277,7 @@ int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret,
 int radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret,
 			      size_t secret_len,
 			      int require_message_authenticator);
-struct radius_attr_hdr * radius_msg_add_attr(struct radius_msg *msg, u8 type,
+struct radius_attr_hdr * radius_msg_add_attr(struct radius_msg *msg, u16 type,
 					     const u8 *data, size_t data_len);
 struct radius_msg * radius_msg_parse(const u8 *data, size_t len);
 int radius_msg_add_eap(struct radius_msg *msg, const u8 *data,
@@ -284,6 +304,8 @@ int radius_msg_add_mppe_keys(struct radius_msg *msg,
 			     const u8 *recv_key, size_t recv_key_len);
 int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data,
 		       size_t len);
+int radius_msg_add_ext_vs(struct radius_msg *msg, u16 type, u32 vendor_id,
+			  u8 vendor_type, const u8 *data, size_t len);
 int radius_user_password_hide(struct radius_msg *msg,
 			      const u8 *data, size_t data_len,
 			      const u8 *secret, size_t secret_len,
-- 
2.25.1

