1189251Ssam/*
2214734Srpaulo * RADIUS message processing
3252726Srpaulo * Copyright (c) 2002-2009, 2011-2012, Jouni Malinen <j@w1.fi>
4189251Ssam *
5252726Srpaulo * This software may be distributed under the terms of the BSD license.
6252726Srpaulo * See README for more details.
7189251Ssam */
8189251Ssam
9214734Srpaulo#include "utils/includes.h"
10189251Ssam
11214734Srpaulo#include "utils/common.h"
12214734Srpaulo#include "utils/wpabuf.h"
13214734Srpaulo#include "crypto/md5.h"
14214734Srpaulo#include "crypto/crypto.h"
15189251Ssam#include "radius.h"
16189251Ssam
17189251Ssam
18214734Srpaulo/**
19214734Srpaulo * struct radius_msg - RADIUS message structure for new and parsed messages
20214734Srpaulo */
21214734Srpaulostruct radius_msg {
22214734Srpaulo	/**
23214734Srpaulo	 * buf - Allocated buffer for RADIUS message
24214734Srpaulo	 */
25214734Srpaulo	struct wpabuf *buf;
26214734Srpaulo
27214734Srpaulo	/**
28214734Srpaulo	 * hdr - Pointer to the RADIUS header in buf
29214734Srpaulo	 */
30214734Srpaulo	struct radius_hdr *hdr;
31214734Srpaulo
32214734Srpaulo	/**
33214734Srpaulo	 * attr_pos - Array of indexes to attributes
34214734Srpaulo	 *
35214734Srpaulo	 * The values are number of bytes from buf to the beginning of
36214734Srpaulo	 * struct radius_attr_hdr.
37214734Srpaulo	 */
38214734Srpaulo	size_t *attr_pos;
39214734Srpaulo
40214734Srpaulo	/**
41214734Srpaulo	 * attr_size - Total size of the attribute pointer array
42214734Srpaulo	 */
43214734Srpaulo	size_t attr_size;
44214734Srpaulo
45214734Srpaulo	/**
46214734Srpaulo	 * attr_used - Total number of attributes in the array
47214734Srpaulo	 */
48214734Srpaulo	size_t attr_used;
49214734Srpaulo};
50214734Srpaulo
51214734Srpaulo
52214734Srpaulostruct radius_hdr * radius_msg_get_hdr(struct radius_msg *msg)
53189251Ssam{
54214734Srpaulo	return msg->hdr;
55189251Ssam}
56189251Ssam
57189251Ssam
58214734Srpaulostruct wpabuf * radius_msg_get_buf(struct radius_msg *msg)
59189251Ssam{
60214734Srpaulo	return msg->buf;
61214734Srpaulo}
62189251Ssam
63189251Ssam
64214734Srpaulostatic struct radius_attr_hdr *
65214734Srpauloradius_get_attr_hdr(struct radius_msg *msg, int idx)
66214734Srpaulo{
67214734Srpaulo	return (struct radius_attr_hdr *)
68214734Srpaulo		(wpabuf_mhead_u8(msg->buf) + msg->attr_pos[idx]);
69214734Srpaulo}
70189251Ssam
71189251Ssam
72214734Srpaulostatic void radius_msg_set_hdr(struct radius_msg *msg, u8 code, u8 identifier)
73214734Srpaulo{
74214734Srpaulo	msg->hdr->code = code;
75214734Srpaulo	msg->hdr->identifier = identifier;
76189251Ssam}
77189251Ssam
78189251Ssam
79214734Srpaulostatic int radius_msg_initialize(struct radius_msg *msg)
80189251Ssam{
81252726Srpaulo	msg->attr_pos = os_calloc(RADIUS_DEFAULT_ATTR_COUNT,
82252726Srpaulo				  sizeof(*msg->attr_pos));
83214734Srpaulo	if (msg->attr_pos == NULL)
84189251Ssam		return -1;
85189251Ssam
86189251Ssam	msg->attr_size = RADIUS_DEFAULT_ATTR_COUNT;
87189251Ssam	msg->attr_used = 0;
88189251Ssam
89189251Ssam	return 0;
90189251Ssam}
91189251Ssam
92189251Ssam
93214734Srpaulo/**
94214734Srpaulo * radius_msg_new - Create a new RADIUS message
95214734Srpaulo * @code: Code for RADIUS header
96214734Srpaulo * @identifier: Identifier for RADIUS header
97214734Srpaulo * Returns: Context for RADIUS message or %NULL on failure
98214734Srpaulo *
99214734Srpaulo * The caller is responsible for freeing the returned data with
100214734Srpaulo * radius_msg_free().
101214734Srpaulo */
102214734Srpaulostruct radius_msg * radius_msg_new(u8 code, u8 identifier)
103189251Ssam{
104214734Srpaulo	struct radius_msg *msg;
105214734Srpaulo
106214734Srpaulo	msg = os_zalloc(sizeof(*msg));
107214734Srpaulo	if (msg == NULL)
108214734Srpaulo		return NULL;
109214734Srpaulo
110214734Srpaulo	msg->buf = wpabuf_alloc(RADIUS_DEFAULT_MSG_SIZE);
111214734Srpaulo	if (msg->buf == NULL || radius_msg_initialize(msg)) {
112214734Srpaulo		radius_msg_free(msg);
113214734Srpaulo		return NULL;
114214734Srpaulo	}
115214734Srpaulo	msg->hdr = wpabuf_put(msg->buf, sizeof(struct radius_hdr));
116214734Srpaulo
117214734Srpaulo	radius_msg_set_hdr(msg, code, identifier);
118214734Srpaulo
119214734Srpaulo	return msg;
120189251Ssam}
121189251Ssam
122189251Ssam
123214734Srpaulo/**
124214734Srpaulo * radius_msg_free - Free a RADIUS message
125214734Srpaulo * @msg: RADIUS message from radius_msg_new() or radius_msg_parse()
126214734Srpaulo */
127189251Ssamvoid radius_msg_free(struct radius_msg *msg)
128189251Ssam{
129214734Srpaulo	if (msg == NULL)
130214734Srpaulo		return;
131189251Ssam
132214734Srpaulo	wpabuf_free(msg->buf);
133189251Ssam	os_free(msg->attr_pos);
134214734Srpaulo	os_free(msg);
135189251Ssam}
136189251Ssam
137189251Ssam
138189251Ssamstatic const char *radius_code_string(u8 code)
139189251Ssam{
140189251Ssam	switch (code) {
141189251Ssam	case RADIUS_CODE_ACCESS_REQUEST: return "Access-Request";
142189251Ssam	case RADIUS_CODE_ACCESS_ACCEPT: return "Access-Accept";
143189251Ssam	case RADIUS_CODE_ACCESS_REJECT: return "Access-Reject";
144189251Ssam	case RADIUS_CODE_ACCOUNTING_REQUEST: return "Accounting-Request";
145189251Ssam	case RADIUS_CODE_ACCOUNTING_RESPONSE: return "Accounting-Response";
146189251Ssam	case RADIUS_CODE_ACCESS_CHALLENGE: return "Access-Challenge";
147189251Ssam	case RADIUS_CODE_STATUS_SERVER: return "Status-Server";
148189251Ssam	case RADIUS_CODE_STATUS_CLIENT: return "Status-Client";
149189251Ssam	case RADIUS_CODE_RESERVED: return "Reserved";
150252726Srpaulo	case RADIUS_CODE_DISCONNECT_REQUEST: return "Disconnect-Request";
151252726Srpaulo	case RADIUS_CODE_DISCONNECT_ACK: return "Disconnect-ACK";
152252726Srpaulo	case RADIUS_CODE_DISCONNECT_NAK: return "Disconnect-NAK";
153252726Srpaulo	case RADIUS_CODE_COA_REQUEST: return "CoA-Request";
154252726Srpaulo	case RADIUS_CODE_COA_ACK: return "CoA-ACK";
155252726Srpaulo	case RADIUS_CODE_COA_NAK: return "CoA-NAK";
156189251Ssam	default: return "?Unknown?";
157189251Ssam	}
158189251Ssam}
159189251Ssam
160189251Ssam
161189251Ssamstruct radius_attr_type {
162189251Ssam	u8 type;
163189251Ssam	char *name;
164189251Ssam	enum {
165189251Ssam		RADIUS_ATTR_UNDIST, RADIUS_ATTR_TEXT, RADIUS_ATTR_IP,
166189251Ssam		RADIUS_ATTR_HEXDUMP, RADIUS_ATTR_INT32, RADIUS_ATTR_IPV6
167189251Ssam	} data_type;
168189251Ssam};
169189251Ssam
170189251Ssamstatic struct radius_attr_type radius_attrs[] =
171189251Ssam{
172189251Ssam	{ RADIUS_ATTR_USER_NAME, "User-Name", RADIUS_ATTR_TEXT },
173189251Ssam	{ RADIUS_ATTR_USER_PASSWORD, "User-Password", RADIUS_ATTR_UNDIST },
174189251Ssam	{ RADIUS_ATTR_NAS_IP_ADDRESS, "NAS-IP-Address", RADIUS_ATTR_IP },
175189251Ssam	{ RADIUS_ATTR_NAS_PORT, "NAS-Port", RADIUS_ATTR_INT32 },
176189251Ssam	{ RADIUS_ATTR_FRAMED_MTU, "Framed-MTU", RADIUS_ATTR_INT32 },
177189251Ssam	{ RADIUS_ATTR_REPLY_MESSAGE, "Reply-Message", RADIUS_ATTR_TEXT },
178189251Ssam	{ RADIUS_ATTR_STATE, "State", RADIUS_ATTR_UNDIST },
179189251Ssam	{ RADIUS_ATTR_CLASS, "Class", RADIUS_ATTR_UNDIST },
180189251Ssam	{ RADIUS_ATTR_VENDOR_SPECIFIC, "Vendor-Specific", RADIUS_ATTR_UNDIST },
181189251Ssam	{ RADIUS_ATTR_SESSION_TIMEOUT, "Session-Timeout", RADIUS_ATTR_INT32 },
182189251Ssam	{ RADIUS_ATTR_IDLE_TIMEOUT, "Idle-Timeout", RADIUS_ATTR_INT32 },
183189251Ssam	{ RADIUS_ATTR_TERMINATION_ACTION, "Termination-Action",
184189251Ssam	  RADIUS_ATTR_INT32 },
185189251Ssam	{ RADIUS_ATTR_CALLED_STATION_ID, "Called-Station-Id",
186189251Ssam	  RADIUS_ATTR_TEXT },
187189251Ssam	{ RADIUS_ATTR_CALLING_STATION_ID, "Calling-Station-Id",
188189251Ssam	  RADIUS_ATTR_TEXT },
189189251Ssam	{ RADIUS_ATTR_NAS_IDENTIFIER, "NAS-Identifier", RADIUS_ATTR_TEXT },
190189251Ssam	{ RADIUS_ATTR_PROXY_STATE, "Proxy-State", RADIUS_ATTR_UNDIST },
191189251Ssam	{ RADIUS_ATTR_ACCT_STATUS_TYPE, "Acct-Status-Type",
192189251Ssam	  RADIUS_ATTR_INT32 },
193189251Ssam	{ RADIUS_ATTR_ACCT_DELAY_TIME, "Acct-Delay-Time", RADIUS_ATTR_INT32 },
194189251Ssam	{ RADIUS_ATTR_ACCT_INPUT_OCTETS, "Acct-Input-Octets",
195189251Ssam	  RADIUS_ATTR_INT32 },
196189251Ssam	{ RADIUS_ATTR_ACCT_OUTPUT_OCTETS, "Acct-Output-Octets",
197189251Ssam	  RADIUS_ATTR_INT32 },
198189251Ssam	{ RADIUS_ATTR_ACCT_SESSION_ID, "Acct-Session-Id", RADIUS_ATTR_TEXT },
199189251Ssam	{ RADIUS_ATTR_ACCT_AUTHENTIC, "Acct-Authentic", RADIUS_ATTR_INT32 },
200189251Ssam	{ RADIUS_ATTR_ACCT_SESSION_TIME, "Acct-Session-Time",
201189251Ssam	  RADIUS_ATTR_INT32 },
202189251Ssam	{ RADIUS_ATTR_ACCT_INPUT_PACKETS, "Acct-Input-Packets",
203189251Ssam	  RADIUS_ATTR_INT32 },
204189251Ssam	{ RADIUS_ATTR_ACCT_OUTPUT_PACKETS, "Acct-Output-Packets",
205189251Ssam	  RADIUS_ATTR_INT32 },
206189251Ssam	{ RADIUS_ATTR_ACCT_TERMINATE_CAUSE, "Acct-Terminate-Cause",
207189251Ssam	  RADIUS_ATTR_INT32 },
208189251Ssam	{ RADIUS_ATTR_ACCT_MULTI_SESSION_ID, "Acct-Multi-Session-Id",
209189251Ssam	  RADIUS_ATTR_TEXT },
210189251Ssam	{ RADIUS_ATTR_ACCT_LINK_COUNT, "Acct-Link-Count", RADIUS_ATTR_INT32 },
211189251Ssam	{ RADIUS_ATTR_ACCT_INPUT_GIGAWORDS, "Acct-Input-Gigawords",
212189251Ssam	  RADIUS_ATTR_INT32 },
213189251Ssam	{ RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS, "Acct-Output-Gigawords",
214189251Ssam	  RADIUS_ATTR_INT32 },
215189251Ssam	{ RADIUS_ATTR_EVENT_TIMESTAMP, "Event-Timestamp",
216189251Ssam	  RADIUS_ATTR_INT32 },
217189251Ssam	{ RADIUS_ATTR_NAS_PORT_TYPE, "NAS-Port-Type", RADIUS_ATTR_INT32 },
218189251Ssam	{ RADIUS_ATTR_TUNNEL_TYPE, "Tunnel-Type", RADIUS_ATTR_HEXDUMP },
219189251Ssam	{ RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, "Tunnel-Medium-Type",
220189251Ssam	  RADIUS_ATTR_HEXDUMP },
221252726Srpaulo	{ RADIUS_ATTR_TUNNEL_PASSWORD, "Tunnel-Password",
222252726Srpaulo	  RADIUS_ATTR_UNDIST },
223189251Ssam	{ RADIUS_ATTR_CONNECT_INFO, "Connect-Info", RADIUS_ATTR_TEXT },
224189251Ssam	{ RADIUS_ATTR_EAP_MESSAGE, "EAP-Message", RADIUS_ATTR_UNDIST },
225189251Ssam	{ RADIUS_ATTR_MESSAGE_AUTHENTICATOR, "Message-Authenticator",
226189251Ssam	  RADIUS_ATTR_UNDIST },
227189251Ssam	{ RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, "Tunnel-Private-Group-Id",
228189251Ssam	  RADIUS_ATTR_HEXDUMP },
229189251Ssam	{ RADIUS_ATTR_ACCT_INTERIM_INTERVAL, "Acct-Interim-Interval",
230189251Ssam	  RADIUS_ATTR_INT32 },
231252726Srpaulo	{ RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargeable-User-Identity",
232189251Ssam	  RADIUS_ATTR_TEXT },
233189251Ssam	{ RADIUS_ATTR_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", RADIUS_ATTR_IPV6 },
234252726Srpaulo	{ RADIUS_ATTR_ERROR_CAUSE, "Error-Cause", RADIUS_ATTR_INT32 }
235189251Ssam};
236189251Ssam#define RADIUS_ATTRS (sizeof(radius_attrs) / sizeof(radius_attrs[0]))
237189251Ssam
238189251Ssam
239189251Ssamstatic struct radius_attr_type *radius_get_attr_type(u8 type)
240189251Ssam{
241189251Ssam	size_t i;
242189251Ssam
243189251Ssam	for (i = 0; i < RADIUS_ATTRS; i++) {
244189251Ssam		if (type == radius_attrs[i].type)
245189251Ssam			return &radius_attrs[i];
246189251Ssam	}
247189251Ssam
248189251Ssam	return NULL;
249189251Ssam}
250189251Ssam
251189251Ssam
252189251Ssamstatic void print_char(char c)
253189251Ssam{
254189251Ssam	if (c >= 32 && c < 127)
255189251Ssam		printf("%c", c);
256189251Ssam	else
257189251Ssam		printf("<%02x>", c);
258189251Ssam}
259189251Ssam
260189251Ssam
261189251Ssamstatic void radius_msg_dump_attr(struct radius_attr_hdr *hdr)
262189251Ssam{
263189251Ssam	struct radius_attr_type *attr;
264189251Ssam	int i, len;
265189251Ssam	unsigned char *pos;
266189251Ssam
267189251Ssam	attr = radius_get_attr_type(hdr->type);
268189251Ssam
269189251Ssam	printf("   Attribute %d (%s) length=%d\n",
270189251Ssam	       hdr->type, attr ? attr->name : "?Unknown?", hdr->length);
271189251Ssam
272252726Srpaulo	if (attr == NULL || hdr->length < sizeof(struct radius_attr_hdr))
273189251Ssam		return;
274189251Ssam
275189251Ssam	len = hdr->length - sizeof(struct radius_attr_hdr);
276189251Ssam	pos = (unsigned char *) (hdr + 1);
277189251Ssam
278189251Ssam	switch (attr->data_type) {
279189251Ssam	case RADIUS_ATTR_TEXT:
280189251Ssam		printf("      Value: '");
281189251Ssam		for (i = 0; i < len; i++)
282189251Ssam			print_char(pos[i]);
283189251Ssam		printf("'\n");
284189251Ssam		break;
285189251Ssam
286189251Ssam	case RADIUS_ATTR_IP:
287189251Ssam		if (len == 4) {
288189251Ssam			struct in_addr addr;
289189251Ssam			os_memcpy(&addr, pos, 4);
290189251Ssam			printf("      Value: %s\n", inet_ntoa(addr));
291189251Ssam		} else
292189251Ssam			printf("      Invalid IP address length %d\n", len);
293189251Ssam		break;
294189251Ssam
295189251Ssam#ifdef CONFIG_IPV6
296189251Ssam	case RADIUS_ATTR_IPV6:
297189251Ssam		if (len == 16) {
298189251Ssam			char buf[128];
299189251Ssam			const char *atxt;
300189251Ssam			struct in6_addr *addr = (struct in6_addr *) pos;
301189251Ssam			atxt = inet_ntop(AF_INET6, addr, buf, sizeof(buf));
302189251Ssam			printf("      Value: %s\n", atxt ? atxt : "?");
303189251Ssam		} else
304189251Ssam			printf("      Invalid IPv6 address length %d\n", len);
305189251Ssam		break;
306189251Ssam#endif /* CONFIG_IPV6 */
307189251Ssam
308189251Ssam	case RADIUS_ATTR_HEXDUMP:
309189251Ssam	case RADIUS_ATTR_UNDIST:
310189251Ssam		printf("      Value:");
311189251Ssam		for (i = 0; i < len; i++)
312189251Ssam			printf(" %02x", pos[i]);
313189251Ssam		printf("\n");
314189251Ssam		break;
315189251Ssam
316189251Ssam	case RADIUS_ATTR_INT32:
317189251Ssam		if (len == 4)
318189251Ssam			printf("      Value: %u\n", WPA_GET_BE32(pos));
319189251Ssam		else
320189251Ssam			printf("      Invalid INT32 length %d\n", len);
321189251Ssam		break;
322189251Ssam
323189251Ssam	default:
324189251Ssam		break;
325189251Ssam	}
326189251Ssam}
327189251Ssam
328189251Ssam
329189251Ssamvoid radius_msg_dump(struct radius_msg *msg)
330189251Ssam{
331189251Ssam	size_t i;
332189251Ssam
333189251Ssam	printf("RADIUS message: code=%d (%s) identifier=%d length=%d\n",
334189251Ssam	       msg->hdr->code, radius_code_string(msg->hdr->code),
335252726Srpaulo	       msg->hdr->identifier, be_to_host16(msg->hdr->length));
336189251Ssam
337189251Ssam	for (i = 0; i < msg->attr_used; i++) {
338189251Ssam		struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
339189251Ssam		radius_msg_dump_attr(attr);
340189251Ssam	}
341189251Ssam}
342189251Ssam
343189251Ssam
344189251Ssamint radius_msg_finish(struct radius_msg *msg, const u8 *secret,
345189251Ssam		      size_t secret_len)
346189251Ssam{
347189251Ssam	if (secret) {
348189251Ssam		u8 auth[MD5_MAC_LEN];
349189251Ssam		struct radius_attr_hdr *attr;
350189251Ssam
351189251Ssam		os_memset(auth, 0, MD5_MAC_LEN);
352189251Ssam		attr = radius_msg_add_attr(msg,
353189251Ssam					   RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
354189251Ssam					   auth, MD5_MAC_LEN);
355189251Ssam		if (attr == NULL) {
356214734Srpaulo			wpa_printf(MSG_WARNING, "RADIUS: Could not add "
357214734Srpaulo				   "Message-Authenticator");
358189251Ssam			return -1;
359189251Ssam		}
360252726Srpaulo		msg->hdr->length = host_to_be16(wpabuf_len(msg->buf));
361214734Srpaulo		hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
362214734Srpaulo			 wpabuf_len(msg->buf), (u8 *) (attr + 1));
363189251Ssam	} else
364252726Srpaulo		msg->hdr->length = host_to_be16(wpabuf_len(msg->buf));
365189251Ssam
366214734Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
367214734Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)",
368214734Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
369189251Ssam		return -1;
370189251Ssam	}
371189251Ssam	return 0;
372189251Ssam}
373189251Ssam
374189251Ssam
375189251Ssamint radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret,
376189251Ssam			  size_t secret_len, const u8 *req_authenticator)
377189251Ssam{
378189251Ssam	u8 auth[MD5_MAC_LEN];
379189251Ssam	struct radius_attr_hdr *attr;
380189251Ssam	const u8 *addr[4];
381189251Ssam	size_t len[4];
382189251Ssam
383189251Ssam	os_memset(auth, 0, MD5_MAC_LEN);
384189251Ssam	attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
385189251Ssam				   auth, MD5_MAC_LEN);
386189251Ssam	if (attr == NULL) {
387189251Ssam		printf("WARNING: Could not add Message-Authenticator\n");
388189251Ssam		return -1;
389189251Ssam	}
390252726Srpaulo	msg->hdr->length = host_to_be16(wpabuf_len(msg->buf));
391189251Ssam	os_memcpy(msg->hdr->authenticator, req_authenticator,
392189251Ssam		  sizeof(msg->hdr->authenticator));
393214734Srpaulo	hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
394214734Srpaulo		 wpabuf_len(msg->buf), (u8 *) (attr + 1));
395189251Ssam
396189251Ssam	/* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
397189251Ssam	addr[0] = (u8 *) msg->hdr;
398189251Ssam	len[0] = 1 + 1 + 2;
399189251Ssam	addr[1] = req_authenticator;
400189251Ssam	len[1] = MD5_MAC_LEN;
401214734Srpaulo	addr[2] = wpabuf_head_u8(msg->buf) + sizeof(struct radius_hdr);
402214734Srpaulo	len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
403189251Ssam	addr[3] = secret;
404189251Ssam	len[3] = secret_len;
405189251Ssam	md5_vector(4, addr, len, msg->hdr->authenticator);
406189251Ssam
407214734Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
408214734Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)",
409214734Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
410189251Ssam		return -1;
411189251Ssam	}
412189251Ssam	return 0;
413189251Ssam}
414189251Ssam
415189251Ssam
416252726Srpauloint radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret,
417252726Srpaulo			       size_t secret_len,
418252726Srpaulo			       const struct radius_hdr *req_hdr)
419252726Srpaulo{
420252726Srpaulo	const u8 *addr[2];
421252726Srpaulo	size_t len[2];
422252726Srpaulo	u8 auth[MD5_MAC_LEN];
423252726Srpaulo	struct radius_attr_hdr *attr;
424252726Srpaulo
425252726Srpaulo	os_memset(auth, 0, MD5_MAC_LEN);
426252726Srpaulo	attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
427252726Srpaulo				   auth, MD5_MAC_LEN);
428252726Srpaulo	if (attr == NULL) {
429252726Srpaulo		wpa_printf(MSG_WARNING, "Could not add Message-Authenticator");
430252726Srpaulo		return -1;
431252726Srpaulo	}
432252726Srpaulo
433252726Srpaulo	msg->hdr->length = host_to_be16(wpabuf_len(msg->buf));
434252726Srpaulo	os_memcpy(msg->hdr->authenticator, req_hdr->authenticator, 16);
435252726Srpaulo	hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
436252726Srpaulo		 wpabuf_len(msg->buf), (u8 *) (attr + 1));
437252726Srpaulo
438252726Srpaulo	/* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
439252726Srpaulo	addr[0] = wpabuf_head_u8(msg->buf);
440252726Srpaulo	len[0] = wpabuf_len(msg->buf);
441252726Srpaulo	addr[1] = secret;
442252726Srpaulo	len[1] = secret_len;
443252726Srpaulo	if (md5_vector(2, addr, len, msg->hdr->authenticator) < 0)
444252726Srpaulo		return -1;
445252726Srpaulo
446252726Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
447252726Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)",
448252726Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
449252726Srpaulo		return -1;
450252726Srpaulo	}
451252726Srpaulo	return 0;
452252726Srpaulo}
453252726Srpaulo
454252726Srpaulo
455189251Ssamvoid radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
456189251Ssam			    size_t secret_len)
457189251Ssam{
458189251Ssam	const u8 *addr[2];
459189251Ssam	size_t len[2];
460189251Ssam
461252726Srpaulo	msg->hdr->length = host_to_be16(wpabuf_len(msg->buf));
462189251Ssam	os_memset(msg->hdr->authenticator, 0, MD5_MAC_LEN);
463214734Srpaulo	addr[0] = wpabuf_head(msg->buf);
464214734Srpaulo	len[0] = wpabuf_len(msg->buf);
465189251Ssam	addr[1] = secret;
466189251Ssam	len[1] = secret_len;
467189251Ssam	md5_vector(2, addr, len, msg->hdr->authenticator);
468189251Ssam
469214734Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
470214734Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long messages (%lu)",
471214734Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
472189251Ssam	}
473189251Ssam}
474189251Ssam
475189251Ssam
476252726Srpauloint radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret,
477252726Srpaulo			       size_t secret_len)
478252726Srpaulo{
479252726Srpaulo	const u8 *addr[4];
480252726Srpaulo	size_t len[4];
481252726Srpaulo	u8 zero[MD5_MAC_LEN];
482252726Srpaulo	u8 hash[MD5_MAC_LEN];
483252726Srpaulo
484252726Srpaulo	os_memset(zero, 0, sizeof(zero));
485252726Srpaulo	addr[0] = (u8 *) msg->hdr;
486252726Srpaulo	len[0] = sizeof(struct radius_hdr) - MD5_MAC_LEN;
487252726Srpaulo	addr[1] = zero;
488252726Srpaulo	len[1] = MD5_MAC_LEN;
489252726Srpaulo	addr[2] = (u8 *) (msg->hdr + 1);
490252726Srpaulo	len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
491252726Srpaulo	addr[3] = secret;
492252726Srpaulo	len[3] = secret_len;
493252726Srpaulo	md5_vector(4, addr, len, hash);
494252726Srpaulo	return os_memcmp(msg->hdr->authenticator, hash, MD5_MAC_LEN) != 0;
495252726Srpaulo}
496252726Srpaulo
497252726Srpaulo
498252726Srpauloint radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret,
499252726Srpaulo			      size_t secret_len)
500252726Srpaulo{
501252726Srpaulo	const u8 *addr[4];
502252726Srpaulo	size_t len[4];
503252726Srpaulo	u8 zero[MD5_MAC_LEN];
504252726Srpaulo	u8 hash[MD5_MAC_LEN];
505252726Srpaulo	u8 auth[MD5_MAC_LEN], orig[MD5_MAC_LEN];
506252726Srpaulo	u8 orig_authenticator[16];
507252726Srpaulo
508252726Srpaulo	struct radius_attr_hdr *attr = NULL, *tmp;
509252726Srpaulo	size_t i;
510252726Srpaulo
511252726Srpaulo	os_memset(zero, 0, sizeof(zero));
512252726Srpaulo	addr[0] = (u8 *) msg->hdr;
513252726Srpaulo	len[0] = sizeof(struct radius_hdr) - MD5_MAC_LEN;
514252726Srpaulo	addr[1] = zero;
515252726Srpaulo	len[1] = MD5_MAC_LEN;
516252726Srpaulo	addr[2] = (u8 *) (msg->hdr + 1);
517252726Srpaulo	len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
518252726Srpaulo	addr[3] = secret;
519252726Srpaulo	len[3] = secret_len;
520252726Srpaulo	md5_vector(4, addr, len, hash);
521252726Srpaulo	if (os_memcmp(msg->hdr->authenticator, hash, MD5_MAC_LEN) != 0)
522252726Srpaulo		return 1;
523252726Srpaulo
524252726Srpaulo	for (i = 0; i < msg->attr_used; i++) {
525252726Srpaulo		tmp = radius_get_attr_hdr(msg, i);
526252726Srpaulo		if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) {
527252726Srpaulo			if (attr != NULL) {
528252726Srpaulo				wpa_printf(MSG_WARNING, "Multiple "
529252726Srpaulo					   "Message-Authenticator attributes "
530252726Srpaulo					   "in RADIUS message");
531252726Srpaulo				return 1;
532252726Srpaulo			}
533252726Srpaulo			attr = tmp;
534252726Srpaulo		}
535252726Srpaulo	}
536252726Srpaulo
537252726Srpaulo	if (attr == NULL) {
538252726Srpaulo		/* Message-Authenticator is MAY; not required */
539252726Srpaulo		return 0;
540252726Srpaulo	}
541252726Srpaulo
542252726Srpaulo	os_memcpy(orig, attr + 1, MD5_MAC_LEN);
543252726Srpaulo	os_memset(attr + 1, 0, MD5_MAC_LEN);
544252726Srpaulo	os_memcpy(orig_authenticator, msg->hdr->authenticator,
545252726Srpaulo		  sizeof(orig_authenticator));
546252726Srpaulo	os_memset(msg->hdr->authenticator, 0,
547252726Srpaulo		  sizeof(msg->hdr->authenticator));
548252726Srpaulo	hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
549252726Srpaulo		 wpabuf_len(msg->buf), auth);
550252726Srpaulo	os_memcpy(attr + 1, orig, MD5_MAC_LEN);
551252726Srpaulo	os_memcpy(msg->hdr->authenticator, orig_authenticator,
552252726Srpaulo		  sizeof(orig_authenticator));
553252726Srpaulo
554252726Srpaulo	return os_memcmp(orig, auth, MD5_MAC_LEN) != 0;
555252726Srpaulo}
556252726Srpaulo
557252726Srpaulo
558189251Ssamstatic int radius_msg_add_attr_to_array(struct radius_msg *msg,
559189251Ssam					struct radius_attr_hdr *attr)
560189251Ssam{
561189251Ssam	if (msg->attr_used >= msg->attr_size) {
562189251Ssam		size_t *nattr_pos;
563189251Ssam		int nlen = msg->attr_size * 2;
564189251Ssam
565252726Srpaulo		nattr_pos = os_realloc_array(msg->attr_pos, nlen,
566252726Srpaulo					     sizeof(*msg->attr_pos));
567189251Ssam		if (nattr_pos == NULL)
568189251Ssam			return -1;
569189251Ssam
570189251Ssam		msg->attr_pos = nattr_pos;
571189251Ssam		msg->attr_size = nlen;
572189251Ssam	}
573189251Ssam
574214734Srpaulo	msg->attr_pos[msg->attr_used++] =
575214734Srpaulo		(unsigned char *) attr - wpabuf_head_u8(msg->buf);
576189251Ssam
577189251Ssam	return 0;
578189251Ssam}
579189251Ssam
580189251Ssam
581189251Ssamstruct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type,
582189251Ssam					    const u8 *data, size_t data_len)
583189251Ssam{
584189251Ssam	size_t buf_needed;
585189251Ssam	struct radius_attr_hdr *attr;
586189251Ssam
587189251Ssam	if (data_len > RADIUS_MAX_ATTR_LEN) {
588189251Ssam		printf("radius_msg_add_attr: too long attribute (%lu bytes)\n",
589189251Ssam		       (unsigned long) data_len);
590189251Ssam		return NULL;
591189251Ssam	}
592189251Ssam
593214734Srpaulo	buf_needed = sizeof(*attr) + data_len;
594189251Ssam
595214734Srpaulo	if (wpabuf_tailroom(msg->buf) < buf_needed) {
596189251Ssam		/* allocate more space for message buffer */
597214734Srpaulo		if (wpabuf_resize(&msg->buf, buf_needed) < 0)
598189251Ssam			return NULL;
599214734Srpaulo		msg->hdr = wpabuf_mhead(msg->buf);
600189251Ssam	}
601189251Ssam
602214734Srpaulo	attr = wpabuf_put(msg->buf, sizeof(struct radius_attr_hdr));
603189251Ssam	attr->type = type;
604189251Ssam	attr->length = sizeof(*attr) + data_len;
605214734Srpaulo	wpabuf_put_data(msg->buf, data, data_len);
606189251Ssam
607189251Ssam	if (radius_msg_add_attr_to_array(msg, attr))
608189251Ssam		return NULL;
609189251Ssam
610189251Ssam	return attr;
611189251Ssam}
612189251Ssam
613189251Ssam
614214734Srpaulo/**
615214734Srpaulo * radius_msg_parse - Parse a RADIUS message
616214734Srpaulo * @data: RADIUS message to be parsed
617214734Srpaulo * @len: Length of data buffer in octets
618214734Srpaulo * Returns: Parsed RADIUS message or %NULL on failure
619214734Srpaulo *
620214734Srpaulo * This parses a RADIUS message and makes a copy of its data. The caller is
621214734Srpaulo * responsible for freeing the returned data with radius_msg_free().
622214734Srpaulo */
623214734Srpaulostruct radius_msg * radius_msg_parse(const u8 *data, size_t len)
624189251Ssam{
625189251Ssam	struct radius_msg *msg;
626189251Ssam	struct radius_hdr *hdr;
627189251Ssam	struct radius_attr_hdr *attr;
628189251Ssam	size_t msg_len;
629189251Ssam	unsigned char *pos, *end;
630189251Ssam
631189251Ssam	if (data == NULL || len < sizeof(*hdr))
632189251Ssam		return NULL;
633189251Ssam
634189251Ssam	hdr = (struct radius_hdr *) data;
635189251Ssam
636252726Srpaulo	msg_len = be_to_host16(hdr->length);
637189251Ssam	if (msg_len < sizeof(*hdr) || msg_len > len) {
638214734Srpaulo		wpa_printf(MSG_INFO, "RADIUS: Invalid message length");
639189251Ssam		return NULL;
640189251Ssam	}
641189251Ssam
642189251Ssam	if (msg_len < len) {
643214734Srpaulo		wpa_printf(MSG_DEBUG, "RADIUS: Ignored %lu extra bytes after "
644214734Srpaulo			   "RADIUS message", (unsigned long) len - msg_len);
645189251Ssam	}
646189251Ssam
647214734Srpaulo	msg = os_zalloc(sizeof(*msg));
648189251Ssam	if (msg == NULL)
649189251Ssam		return NULL;
650189251Ssam
651214734Srpaulo	msg->buf = wpabuf_alloc_copy(data, msg_len);
652214734Srpaulo	if (msg->buf == NULL || radius_msg_initialize(msg)) {
653214734Srpaulo		radius_msg_free(msg);
654189251Ssam		return NULL;
655189251Ssam	}
656214734Srpaulo	msg->hdr = wpabuf_mhead(msg->buf);
657189251Ssam
658189251Ssam	/* parse attributes */
659214734Srpaulo	pos = wpabuf_mhead_u8(msg->buf) + sizeof(struct radius_hdr);
660214734Srpaulo	end = wpabuf_mhead_u8(msg->buf) + wpabuf_len(msg->buf);
661189251Ssam	while (pos < end) {
662189251Ssam		if ((size_t) (end - pos) < sizeof(*attr))
663189251Ssam			goto fail;
664189251Ssam
665189251Ssam		attr = (struct radius_attr_hdr *) pos;
666189251Ssam
667189251Ssam		if (pos + attr->length > end || attr->length < sizeof(*attr))
668189251Ssam			goto fail;
669189251Ssam
670189251Ssam		/* TODO: check that attr->length is suitable for attr->type */
671189251Ssam
672189251Ssam		if (radius_msg_add_attr_to_array(msg, attr))
673189251Ssam			goto fail;
674189251Ssam
675189251Ssam		pos += attr->length;
676189251Ssam	}
677189251Ssam
678189251Ssam	return msg;
679189251Ssam
680189251Ssam fail:
681189251Ssam	radius_msg_free(msg);
682189251Ssam	return NULL;
683189251Ssam}
684189251Ssam
685189251Ssam
686189251Ssamint radius_msg_add_eap(struct radius_msg *msg, const u8 *data, size_t data_len)
687189251Ssam{
688189251Ssam	const u8 *pos = data;
689189251Ssam	size_t left = data_len;
690189251Ssam
691189251Ssam	while (left > 0) {
692189251Ssam		int len;
693189251Ssam		if (left > RADIUS_MAX_ATTR_LEN)
694189251Ssam			len = RADIUS_MAX_ATTR_LEN;
695189251Ssam		else
696189251Ssam			len = left;
697189251Ssam
698189251Ssam		if (!radius_msg_add_attr(msg, RADIUS_ATTR_EAP_MESSAGE,
699189251Ssam					 pos, len))
700189251Ssam			return 0;
701189251Ssam
702189251Ssam		pos += len;
703189251Ssam		left -= len;
704189251Ssam	}
705189251Ssam
706189251Ssam	return 1;
707189251Ssam}
708189251Ssam
709189251Ssam
710252726Srpaulostruct wpabuf * radius_msg_get_eap(struct radius_msg *msg)
711189251Ssam{
712252726Srpaulo	struct wpabuf *eap;
713189251Ssam	size_t len, i;
714189251Ssam	struct radius_attr_hdr *attr;
715189251Ssam
716189251Ssam	if (msg == NULL)
717189251Ssam		return NULL;
718189251Ssam
719189251Ssam	len = 0;
720189251Ssam	for (i = 0; i < msg->attr_used; i++) {
721189251Ssam		attr = radius_get_attr_hdr(msg, i);
722252726Srpaulo		if (attr->type == RADIUS_ATTR_EAP_MESSAGE &&
723252726Srpaulo		    attr->length > sizeof(struct radius_attr_hdr))
724189251Ssam			len += attr->length - sizeof(struct radius_attr_hdr);
725189251Ssam	}
726189251Ssam
727189251Ssam	if (len == 0)
728189251Ssam		return NULL;
729189251Ssam
730252726Srpaulo	eap = wpabuf_alloc(len);
731189251Ssam	if (eap == NULL)
732189251Ssam		return NULL;
733189251Ssam
734189251Ssam	for (i = 0; i < msg->attr_used; i++) {
735189251Ssam		attr = radius_get_attr_hdr(msg, i);
736252726Srpaulo		if (attr->type == RADIUS_ATTR_EAP_MESSAGE &&
737252726Srpaulo		    attr->length > sizeof(struct radius_attr_hdr)) {
738189251Ssam			int flen = attr->length - sizeof(*attr);
739252726Srpaulo			wpabuf_put_data(eap, attr + 1, flen);
740189251Ssam		}
741189251Ssam	}
742189251Ssam
743189251Ssam	return eap;
744189251Ssam}
745189251Ssam
746189251Ssam
747189251Ssamint radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret,
748189251Ssam			       size_t secret_len, const u8 *req_auth)
749189251Ssam{
750189251Ssam	u8 auth[MD5_MAC_LEN], orig[MD5_MAC_LEN];
751189251Ssam	u8 orig_authenticator[16];
752189251Ssam	struct radius_attr_hdr *attr = NULL, *tmp;
753189251Ssam	size_t i;
754189251Ssam
755189251Ssam	for (i = 0; i < msg->attr_used; i++) {
756189251Ssam		tmp = radius_get_attr_hdr(msg, i);
757189251Ssam		if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) {
758189251Ssam			if (attr != NULL) {
759189251Ssam				printf("Multiple Message-Authenticator "
760189251Ssam				       "attributes in RADIUS message\n");
761189251Ssam				return 1;
762189251Ssam			}
763189251Ssam			attr = tmp;
764189251Ssam		}
765189251Ssam	}
766189251Ssam
767189251Ssam	if (attr == NULL) {
768189251Ssam		printf("No Message-Authenticator attribute found\n");
769189251Ssam		return 1;
770189251Ssam	}
771189251Ssam
772189251Ssam	os_memcpy(orig, attr + 1, MD5_MAC_LEN);
773189251Ssam	os_memset(attr + 1, 0, MD5_MAC_LEN);
774189251Ssam	if (req_auth) {
775189251Ssam		os_memcpy(orig_authenticator, msg->hdr->authenticator,
776189251Ssam			  sizeof(orig_authenticator));
777189251Ssam		os_memcpy(msg->hdr->authenticator, req_auth,
778189251Ssam			  sizeof(msg->hdr->authenticator));
779189251Ssam	}
780214734Srpaulo	hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
781214734Srpaulo		 wpabuf_len(msg->buf), auth);
782189251Ssam	os_memcpy(attr + 1, orig, MD5_MAC_LEN);
783189251Ssam	if (req_auth) {
784189251Ssam		os_memcpy(msg->hdr->authenticator, orig_authenticator,
785189251Ssam			  sizeof(orig_authenticator));
786189251Ssam	}
787189251Ssam
788189251Ssam	if (os_memcmp(orig, auth, MD5_MAC_LEN) != 0) {
789189251Ssam		printf("Invalid Message-Authenticator!\n");
790189251Ssam		return 1;
791189251Ssam	}
792189251Ssam
793189251Ssam	return 0;
794189251Ssam}
795189251Ssam
796189251Ssam
797189251Ssamint radius_msg_verify(struct radius_msg *msg, const u8 *secret,
798189251Ssam		      size_t secret_len, struct radius_msg *sent_msg, int auth)
799189251Ssam{
800189251Ssam	const u8 *addr[4];
801189251Ssam	size_t len[4];
802189251Ssam	u8 hash[MD5_MAC_LEN];
803189251Ssam
804189251Ssam	if (sent_msg == NULL) {
805189251Ssam		printf("No matching Access-Request message found\n");
806189251Ssam		return 1;
807189251Ssam	}
808189251Ssam
809189251Ssam	if (auth &&
810189251Ssam	    radius_msg_verify_msg_auth(msg, secret, secret_len,
811189251Ssam				       sent_msg->hdr->authenticator)) {
812189251Ssam		return 1;
813189251Ssam	}
814189251Ssam
815189251Ssam	/* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
816189251Ssam	addr[0] = (u8 *) msg->hdr;
817189251Ssam	len[0] = 1 + 1 + 2;
818189251Ssam	addr[1] = sent_msg->hdr->authenticator;
819189251Ssam	len[1] = MD5_MAC_LEN;
820214734Srpaulo	addr[2] = wpabuf_head_u8(msg->buf) + sizeof(struct radius_hdr);
821214734Srpaulo	len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
822189251Ssam	addr[3] = secret;
823189251Ssam	len[3] = secret_len;
824189251Ssam	md5_vector(4, addr, len, hash);
825189251Ssam	if (os_memcmp(hash, msg->hdr->authenticator, MD5_MAC_LEN) != 0) {
826189251Ssam		printf("Response Authenticator invalid!\n");
827189251Ssam		return 1;
828189251Ssam	}
829189251Ssam
830189251Ssam	return 0;
831189251Ssam}
832189251Ssam
833189251Ssam
834189251Ssamint radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src,
835189251Ssam			 u8 type)
836189251Ssam{
837189251Ssam	struct radius_attr_hdr *attr;
838189251Ssam	size_t i;
839189251Ssam	int count = 0;
840189251Ssam
841189251Ssam	for (i = 0; i < src->attr_used; i++) {
842189251Ssam		attr = radius_get_attr_hdr(src, i);
843252726Srpaulo		if (attr->type == type && attr->length >= sizeof(*attr)) {
844189251Ssam			if (!radius_msg_add_attr(dst, type, (u8 *) (attr + 1),
845189251Ssam						 attr->length - sizeof(*attr)))
846189251Ssam				return -1;
847189251Ssam			count++;
848189251Ssam		}
849189251Ssam	}
850189251Ssam
851189251Ssam	return count;
852189251Ssam}
853189251Ssam
854189251Ssam
855189251Ssam/* Create Request Authenticator. The value should be unique over the lifetime
856189251Ssam * of the shared secret between authenticator and authentication server.
857189251Ssam * Use one-way MD5 hash calculated from current timestamp and some data given
858189251Ssam * by the caller. */
859189251Ssamvoid radius_msg_make_authenticator(struct radius_msg *msg,
860189251Ssam				   const u8 *data, size_t len)
861189251Ssam{
862189251Ssam	struct os_time tv;
863189251Ssam	long int l;
864189251Ssam	const u8 *addr[3];
865189251Ssam	size_t elen[3];
866189251Ssam
867189251Ssam	os_get_time(&tv);
868189251Ssam	l = os_random();
869189251Ssam	addr[0] = (u8 *) &tv;
870189251Ssam	elen[0] = sizeof(tv);
871189251Ssam	addr[1] = data;
872189251Ssam	elen[1] = len;
873189251Ssam	addr[2] = (u8 *) &l;
874189251Ssam	elen[2] = sizeof(l);
875189251Ssam	md5_vector(3, addr, elen, msg->hdr->authenticator);
876189251Ssam}
877189251Ssam
878189251Ssam
879189251Ssam/* Get Vendor-specific RADIUS Attribute from a parsed RADIUS message.
880189251Ssam * Returns the Attribute payload and sets alen to indicate the length of the
881189251Ssam * payload if a vendor attribute with subtype is found, otherwise returns NULL.
882189251Ssam * The returned payload is allocated with os_malloc() and caller must free it
883189251Ssam * by calling os_free().
884189251Ssam */
885189251Ssamstatic u8 *radius_msg_get_vendor_attr(struct radius_msg *msg, u32 vendor,
886189251Ssam				      u8 subtype, size_t *alen)
887189251Ssam{
888189251Ssam	u8 *data, *pos;
889189251Ssam	size_t i, len;
890189251Ssam
891189251Ssam	if (msg == NULL)
892189251Ssam		return NULL;
893189251Ssam
894189251Ssam	for (i = 0; i < msg->attr_used; i++) {
895189251Ssam		struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
896189251Ssam		size_t left;
897189251Ssam		u32 vendor_id;
898189251Ssam		struct radius_attr_vendor *vhdr;
899189251Ssam
900252726Srpaulo		if (attr->type != RADIUS_ATTR_VENDOR_SPECIFIC ||
901252726Srpaulo		    attr->length < sizeof(*attr))
902189251Ssam			continue;
903189251Ssam
904189251Ssam		left = attr->length - sizeof(*attr);
905189251Ssam		if (left < 4)
906189251Ssam			continue;
907189251Ssam
908189251Ssam		pos = (u8 *) (attr + 1);
909189251Ssam
910189251Ssam		os_memcpy(&vendor_id, pos, 4);
911189251Ssam		pos += 4;
912189251Ssam		left -= 4;
913189251Ssam
914189251Ssam		if (ntohl(vendor_id) != vendor)
915189251Ssam			continue;
916189251Ssam
917189251Ssam		while (left >= sizeof(*vhdr)) {
918189251Ssam			vhdr = (struct radius_attr_vendor *) pos;
919189251Ssam			if (vhdr->vendor_length > left ||
920189251Ssam			    vhdr->vendor_length < sizeof(*vhdr)) {
921189251Ssam				left = 0;
922189251Ssam				break;
923189251Ssam			}
924189251Ssam			if (vhdr->vendor_type != subtype) {
925189251Ssam				pos += vhdr->vendor_length;
926189251Ssam				left -= vhdr->vendor_length;
927189251Ssam				continue;
928189251Ssam			}
929189251Ssam
930189251Ssam			len = vhdr->vendor_length - sizeof(*vhdr);
931189251Ssam			data = os_malloc(len);
932189251Ssam			if (data == NULL)
933189251Ssam				return NULL;
934189251Ssam			os_memcpy(data, pos + sizeof(*vhdr), len);
935189251Ssam			if (alen)
936189251Ssam				*alen = len;
937189251Ssam			return data;
938189251Ssam		}
939189251Ssam	}
940189251Ssam
941189251Ssam	return NULL;
942189251Ssam}
943189251Ssam
944189251Ssam
945189251Ssamstatic u8 * decrypt_ms_key(const u8 *key, size_t len,
946189251Ssam			   const u8 *req_authenticator,
947189251Ssam			   const u8 *secret, size_t secret_len, size_t *reslen)
948189251Ssam{
949189251Ssam	u8 *plain, *ppos, *res;
950189251Ssam	const u8 *pos;
951189251Ssam	size_t left, plen;
952189251Ssam	u8 hash[MD5_MAC_LEN];
953189251Ssam	int i, first = 1;
954189251Ssam	const u8 *addr[3];
955189251Ssam	size_t elen[3];
956189251Ssam
957189251Ssam	/* key: 16-bit salt followed by encrypted key info */
958189251Ssam
959189251Ssam	if (len < 2 + 16)
960189251Ssam		return NULL;
961189251Ssam
962189251Ssam	pos = key + 2;
963189251Ssam	left = len - 2;
964189251Ssam	if (left % 16) {
965189251Ssam		printf("Invalid ms key len %lu\n", (unsigned long) left);
966189251Ssam		return NULL;
967189251Ssam	}
968189251Ssam
969189251Ssam	plen = left;
970189251Ssam	ppos = plain = os_malloc(plen);
971189251Ssam	if (plain == NULL)
972189251Ssam		return NULL;
973189251Ssam	plain[0] = 0;
974189251Ssam
975189251Ssam	while (left > 0) {
976189251Ssam		/* b(1) = MD5(Secret + Request-Authenticator + Salt)
977189251Ssam		 * b(i) = MD5(Secret + c(i - 1)) for i > 1 */
978189251Ssam
979189251Ssam		addr[0] = secret;
980189251Ssam		elen[0] = secret_len;
981189251Ssam		if (first) {
982189251Ssam			addr[1] = req_authenticator;
983189251Ssam			elen[1] = MD5_MAC_LEN;
984189251Ssam			addr[2] = key;
985189251Ssam			elen[2] = 2; /* Salt */
986189251Ssam		} else {
987189251Ssam			addr[1] = pos - MD5_MAC_LEN;
988189251Ssam			elen[1] = MD5_MAC_LEN;
989189251Ssam		}
990189251Ssam		md5_vector(first ? 3 : 2, addr, elen, hash);
991189251Ssam		first = 0;
992189251Ssam
993189251Ssam		for (i = 0; i < MD5_MAC_LEN; i++)
994189251Ssam			*ppos++ = *pos++ ^ hash[i];
995189251Ssam		left -= MD5_MAC_LEN;
996189251Ssam	}
997189251Ssam
998189251Ssam	if (plain[0] == 0 || plain[0] > plen - 1) {
999189251Ssam		printf("Failed to decrypt MPPE key\n");
1000189251Ssam		os_free(plain);
1001189251Ssam		return NULL;
1002189251Ssam	}
1003189251Ssam
1004189251Ssam	res = os_malloc(plain[0]);
1005189251Ssam	if (res == NULL) {
1006189251Ssam		os_free(plain);
1007189251Ssam		return NULL;
1008189251Ssam	}
1009189251Ssam	os_memcpy(res, plain + 1, plain[0]);
1010189251Ssam	if (reslen)
1011189251Ssam		*reslen = plain[0];
1012189251Ssam	os_free(plain);
1013189251Ssam	return res;
1014189251Ssam}
1015189251Ssam
1016189251Ssam
1017189251Ssamstatic void encrypt_ms_key(const u8 *key, size_t key_len, u16 salt,
1018189251Ssam			   const u8 *req_authenticator,
1019189251Ssam			   const u8 *secret, size_t secret_len,
1020189251Ssam			   u8 *ebuf, size_t *elen)
1021189251Ssam{
1022189251Ssam	int i, len, first = 1;
1023189251Ssam	u8 hash[MD5_MAC_LEN], saltbuf[2], *pos;
1024189251Ssam	const u8 *addr[3];
1025189251Ssam	size_t _len[3];
1026189251Ssam
1027189251Ssam	WPA_PUT_BE16(saltbuf, salt);
1028189251Ssam
1029189251Ssam	len = 1 + key_len;
1030189251Ssam	if (len & 0x0f) {
1031189251Ssam		len = (len & 0xf0) + 16;
1032189251Ssam	}
1033189251Ssam	os_memset(ebuf, 0, len);
1034189251Ssam	ebuf[0] = key_len;
1035189251Ssam	os_memcpy(ebuf + 1, key, key_len);
1036189251Ssam
1037189251Ssam	*elen = len;
1038189251Ssam
1039189251Ssam	pos = ebuf;
1040189251Ssam	while (len > 0) {
1041189251Ssam		/* b(1) = MD5(Secret + Request-Authenticator + Salt)
1042189251Ssam		 * b(i) = MD5(Secret + c(i - 1)) for i > 1 */
1043189251Ssam		addr[0] = secret;
1044189251Ssam		_len[0] = secret_len;
1045189251Ssam		if (first) {
1046189251Ssam			addr[1] = req_authenticator;
1047189251Ssam			_len[1] = MD5_MAC_LEN;
1048189251Ssam			addr[2] = saltbuf;
1049189251Ssam			_len[2] = sizeof(saltbuf);
1050189251Ssam		} else {
1051189251Ssam			addr[1] = pos - MD5_MAC_LEN;
1052189251Ssam			_len[1] = MD5_MAC_LEN;
1053189251Ssam		}
1054189251Ssam		md5_vector(first ? 3 : 2, addr, _len, hash);
1055189251Ssam		first = 0;
1056189251Ssam
1057189251Ssam		for (i = 0; i < MD5_MAC_LEN; i++)
1058189251Ssam			*pos++ ^= hash[i];
1059189251Ssam
1060189251Ssam		len -= MD5_MAC_LEN;
1061189251Ssam	}
1062189251Ssam}
1063189251Ssam
1064189251Ssam
1065189251Ssamstruct radius_ms_mppe_keys *
1066189251Ssamradius_msg_get_ms_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
1067189251Ssam		       const u8 *secret, size_t secret_len)
1068189251Ssam{
1069189251Ssam	u8 *key;
1070189251Ssam	size_t keylen;
1071189251Ssam	struct radius_ms_mppe_keys *keys;
1072189251Ssam
1073189251Ssam	if (msg == NULL || sent_msg == NULL)
1074189251Ssam		return NULL;
1075189251Ssam
1076189251Ssam	keys = os_zalloc(sizeof(*keys));
1077189251Ssam	if (keys == NULL)
1078189251Ssam		return NULL;
1079189251Ssam
1080189251Ssam	key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT,
1081189251Ssam					 RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY,
1082189251Ssam					 &keylen);
1083189251Ssam	if (key) {
1084189251Ssam		keys->send = decrypt_ms_key(key, keylen,
1085189251Ssam					    sent_msg->hdr->authenticator,
1086189251Ssam					    secret, secret_len,
1087189251Ssam					    &keys->send_len);
1088189251Ssam		os_free(key);
1089189251Ssam	}
1090189251Ssam
1091189251Ssam	key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT,
1092189251Ssam					 RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY,
1093189251Ssam					 &keylen);
1094189251Ssam	if (key) {
1095189251Ssam		keys->recv = decrypt_ms_key(key, keylen,
1096189251Ssam					    sent_msg->hdr->authenticator,
1097189251Ssam					    secret, secret_len,
1098189251Ssam					    &keys->recv_len);
1099189251Ssam		os_free(key);
1100189251Ssam	}
1101189251Ssam
1102189251Ssam	return keys;
1103189251Ssam}
1104189251Ssam
1105189251Ssam
1106189251Ssamstruct radius_ms_mppe_keys *
1107189251Ssamradius_msg_get_cisco_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
1108189251Ssam			  const u8 *secret, size_t secret_len)
1109189251Ssam{
1110189251Ssam	u8 *key;
1111189251Ssam	size_t keylen;
1112189251Ssam	struct radius_ms_mppe_keys *keys;
1113189251Ssam
1114189251Ssam	if (msg == NULL || sent_msg == NULL)
1115189251Ssam		return NULL;
1116189251Ssam
1117189251Ssam	keys = os_zalloc(sizeof(*keys));
1118189251Ssam	if (keys == NULL)
1119189251Ssam		return NULL;
1120189251Ssam
1121189251Ssam	key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_CISCO,
1122189251Ssam					 RADIUS_CISCO_AV_PAIR, &keylen);
1123189251Ssam	if (key && keylen == 51 &&
1124189251Ssam	    os_memcmp(key, "leap:session-key=", 17) == 0) {
1125189251Ssam		keys->recv = decrypt_ms_key(key + 17, keylen - 17,
1126189251Ssam					    sent_msg->hdr->authenticator,
1127189251Ssam					    secret, secret_len,
1128189251Ssam					    &keys->recv_len);
1129189251Ssam	}
1130189251Ssam	os_free(key);
1131189251Ssam
1132189251Ssam	return keys;
1133189251Ssam}
1134189251Ssam
1135189251Ssam
1136189251Ssamint radius_msg_add_mppe_keys(struct radius_msg *msg,
1137189251Ssam			     const u8 *req_authenticator,
1138189251Ssam			     const u8 *secret, size_t secret_len,
1139189251Ssam			     const u8 *send_key, size_t send_key_len,
1140189251Ssam			     const u8 *recv_key, size_t recv_key_len)
1141189251Ssam{
1142189251Ssam	struct radius_attr_hdr *attr;
1143189251Ssam	u32 vendor_id = htonl(RADIUS_VENDOR_ID_MICROSOFT);
1144189251Ssam	u8 *buf;
1145189251Ssam	struct radius_attr_vendor *vhdr;
1146189251Ssam	u8 *pos;
1147189251Ssam	size_t elen;
1148189251Ssam	int hlen;
1149189251Ssam	u16 salt;
1150189251Ssam
1151189251Ssam	hlen = sizeof(vendor_id) + sizeof(*vhdr) + 2;
1152189251Ssam
1153189251Ssam	/* MS-MPPE-Send-Key */
1154189251Ssam	buf = os_malloc(hlen + send_key_len + 16);
1155189251Ssam	if (buf == NULL) {
1156189251Ssam		return 0;
1157189251Ssam	}
1158189251Ssam	pos = buf;
1159189251Ssam	os_memcpy(pos, &vendor_id, sizeof(vendor_id));
1160189251Ssam	pos += sizeof(vendor_id);
1161189251Ssam	vhdr = (struct radius_attr_vendor *) pos;
1162189251Ssam	vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY;
1163189251Ssam	pos = (u8 *) (vhdr + 1);
1164189251Ssam	salt = os_random() | 0x8000;
1165189251Ssam	WPA_PUT_BE16(pos, salt);
1166189251Ssam	pos += 2;
1167189251Ssam	encrypt_ms_key(send_key, send_key_len, salt, req_authenticator, secret,
1168189251Ssam		       secret_len, pos, &elen);
1169189251Ssam	vhdr->vendor_length = hlen + elen - sizeof(vendor_id);
1170189251Ssam
1171189251Ssam	attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
1172189251Ssam				   buf, hlen + elen);
1173189251Ssam	os_free(buf);
1174189251Ssam	if (attr == NULL) {
1175189251Ssam		return 0;
1176189251Ssam	}
1177189251Ssam
1178189251Ssam	/* MS-MPPE-Recv-Key */
1179189251Ssam	buf = os_malloc(hlen + send_key_len + 16);
1180189251Ssam	if (buf == NULL) {
1181189251Ssam		return 0;
1182189251Ssam	}
1183189251Ssam	pos = buf;
1184189251Ssam	os_memcpy(pos, &vendor_id, sizeof(vendor_id));
1185189251Ssam	pos += sizeof(vendor_id);
1186189251Ssam	vhdr = (struct radius_attr_vendor *) pos;
1187189251Ssam	vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY;
1188189251Ssam	pos = (u8 *) (vhdr + 1);
1189189251Ssam	salt ^= 1;
1190189251Ssam	WPA_PUT_BE16(pos, salt);
1191189251Ssam	pos += 2;
1192189251Ssam	encrypt_ms_key(recv_key, recv_key_len, salt, req_authenticator, secret,
1193189251Ssam		       secret_len, pos, &elen);
1194189251Ssam	vhdr->vendor_length = hlen + elen - sizeof(vendor_id);
1195189251Ssam
1196189251Ssam	attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
1197189251Ssam				   buf, hlen + elen);
1198189251Ssam	os_free(buf);
1199189251Ssam	if (attr == NULL) {
1200189251Ssam		return 0;
1201189251Ssam	}
1202189251Ssam
1203189251Ssam	return 1;
1204189251Ssam}
1205189251Ssam
1206189251Ssam
1207189251Ssam/* Add User-Password attribute to a RADIUS message and encrypt it as specified
1208189251Ssam * in RFC 2865, Chap. 5.2 */
1209189251Ssamstruct radius_attr_hdr *
1210189251Ssamradius_msg_add_attr_user_password(struct radius_msg *msg,
1211189251Ssam				  const u8 *data, size_t data_len,
1212189251Ssam				  const u8 *secret, size_t secret_len)
1213189251Ssam{
1214189251Ssam	u8 buf[128];
1215252726Srpaulo	size_t padlen, i, buf_len, pos;
1216189251Ssam	const u8 *addr[2];
1217189251Ssam	size_t len[2];
1218189251Ssam	u8 hash[16];
1219189251Ssam
1220189251Ssam	if (data_len > 128)
1221189251Ssam		return NULL;
1222189251Ssam
1223189251Ssam	os_memcpy(buf, data, data_len);
1224189251Ssam	buf_len = data_len;
1225189251Ssam
1226189251Ssam	padlen = data_len % 16;
1227252726Srpaulo	if (padlen && data_len < sizeof(buf)) {
1228189251Ssam		padlen = 16 - padlen;
1229189251Ssam		os_memset(buf + data_len, 0, padlen);
1230189251Ssam		buf_len += padlen;
1231189251Ssam	}
1232189251Ssam
1233189251Ssam	addr[0] = secret;
1234189251Ssam	len[0] = secret_len;
1235189251Ssam	addr[1] = msg->hdr->authenticator;
1236189251Ssam	len[1] = 16;
1237189251Ssam	md5_vector(2, addr, len, hash);
1238189251Ssam
1239189251Ssam	for (i = 0; i < 16; i++)
1240189251Ssam		buf[i] ^= hash[i];
1241189251Ssam	pos = 16;
1242189251Ssam
1243189251Ssam	while (pos < buf_len) {
1244189251Ssam		addr[0] = secret;
1245189251Ssam		len[0] = secret_len;
1246189251Ssam		addr[1] = &buf[pos - 16];
1247189251Ssam		len[1] = 16;
1248189251Ssam		md5_vector(2, addr, len, hash);
1249189251Ssam
1250189251Ssam		for (i = 0; i < 16; i++)
1251189251Ssam			buf[pos + i] ^= hash[i];
1252189251Ssam
1253189251Ssam		pos += 16;
1254189251Ssam	}
1255189251Ssam
1256189251Ssam	return radius_msg_add_attr(msg, RADIUS_ATTR_USER_PASSWORD,
1257189251Ssam				   buf, buf_len);
1258189251Ssam}
1259189251Ssam
1260189251Ssam
1261189251Ssamint radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len)
1262189251Ssam{
1263189251Ssam	struct radius_attr_hdr *attr = NULL, *tmp;
1264189251Ssam	size_t i, dlen;
1265189251Ssam
1266189251Ssam	for (i = 0; i < msg->attr_used; i++) {
1267189251Ssam		tmp = radius_get_attr_hdr(msg, i);
1268189251Ssam		if (tmp->type == type) {
1269189251Ssam			attr = tmp;
1270189251Ssam			break;
1271189251Ssam		}
1272189251Ssam	}
1273189251Ssam
1274252726Srpaulo	if (!attr || attr->length < sizeof(*attr))
1275189251Ssam		return -1;
1276189251Ssam
1277189251Ssam	dlen = attr->length - sizeof(*attr);
1278189251Ssam	if (buf)
1279189251Ssam		os_memcpy(buf, (attr + 1), dlen > len ? len : dlen);
1280189251Ssam	return dlen;
1281189251Ssam}
1282189251Ssam
1283189251Ssam
1284189251Ssamint radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf,
1285189251Ssam			    size_t *len, const u8 *start)
1286189251Ssam{
1287189251Ssam	size_t i;
1288189251Ssam	struct radius_attr_hdr *attr = NULL, *tmp;
1289189251Ssam
1290189251Ssam	for (i = 0; i < msg->attr_used; i++) {
1291189251Ssam		tmp = radius_get_attr_hdr(msg, i);
1292189251Ssam		if (tmp->type == type &&
1293189251Ssam		    (start == NULL || (u8 *) tmp > start)) {
1294189251Ssam			attr = tmp;
1295189251Ssam			break;
1296189251Ssam		}
1297189251Ssam	}
1298189251Ssam
1299252726Srpaulo	if (!attr || attr->length < sizeof(*attr))
1300189251Ssam		return -1;
1301189251Ssam
1302189251Ssam	*buf = (u8 *) (attr + 1);
1303189251Ssam	*len = attr->length - sizeof(*attr);
1304189251Ssam	return 0;
1305189251Ssam}
1306189251Ssam
1307189251Ssam
1308189251Ssamint radius_msg_count_attr(struct radius_msg *msg, u8 type, int min_len)
1309189251Ssam{
1310189251Ssam	size_t i;
1311189251Ssam	int count;
1312189251Ssam
1313189251Ssam	for (count = 0, i = 0; i < msg->attr_used; i++) {
1314189251Ssam		struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
1315189251Ssam		if (attr->type == type &&
1316189251Ssam		    attr->length >= sizeof(struct radius_attr_hdr) + min_len)
1317189251Ssam			count++;
1318189251Ssam	}
1319189251Ssam
1320189251Ssam	return count;
1321189251Ssam}
1322189251Ssam
1323189251Ssam
1324189251Ssamstruct radius_tunnel_attrs {
1325189251Ssam	int tag_used;
1326189251Ssam	int type; /* Tunnel-Type */
1327189251Ssam	int medium_type; /* Tunnel-Medium-Type */
1328189251Ssam	int vlanid;
1329189251Ssam};
1330189251Ssam
1331189251Ssam
1332189251Ssam/**
1333189251Ssam * radius_msg_get_vlanid - Parse RADIUS attributes for VLAN tunnel information
1334189251Ssam * @msg: RADIUS message
1335189251Ssam * Returns: VLAN ID for the first tunnel configuration of -1 if none is found
1336189251Ssam */
1337189251Ssamint radius_msg_get_vlanid(struct radius_msg *msg)
1338189251Ssam{
1339189251Ssam	struct radius_tunnel_attrs tunnel[RADIUS_TUNNEL_TAGS], *tun;
1340189251Ssam	size_t i;
1341189251Ssam	struct radius_attr_hdr *attr = NULL;
1342189251Ssam	const u8 *data;
1343189251Ssam	char buf[10];
1344189251Ssam	size_t dlen;
1345189251Ssam
1346189251Ssam	os_memset(&tunnel, 0, sizeof(tunnel));
1347189251Ssam
1348189251Ssam	for (i = 0; i < msg->attr_used; i++) {
1349189251Ssam		attr = radius_get_attr_hdr(msg, i);
1350252726Srpaulo		if (attr->length < sizeof(*attr))
1351252726Srpaulo			return -1;
1352189251Ssam		data = (const u8 *) (attr + 1);
1353189251Ssam		dlen = attr->length - sizeof(*attr);
1354189251Ssam		if (attr->length < 3)
1355189251Ssam			continue;
1356189251Ssam		if (data[0] >= RADIUS_TUNNEL_TAGS)
1357189251Ssam			tun = &tunnel[0];
1358189251Ssam		else
1359189251Ssam			tun = &tunnel[data[0]];
1360189251Ssam
1361189251Ssam		switch (attr->type) {
1362189251Ssam		case RADIUS_ATTR_TUNNEL_TYPE:
1363189251Ssam			if (attr->length != 6)
1364189251Ssam				break;
1365189251Ssam			tun->tag_used++;
1366189251Ssam			tun->type = WPA_GET_BE24(data + 1);
1367189251Ssam			break;
1368189251Ssam		case RADIUS_ATTR_TUNNEL_MEDIUM_TYPE:
1369189251Ssam			if (attr->length != 6)
1370189251Ssam				break;
1371189251Ssam			tun->tag_used++;
1372189251Ssam			tun->medium_type = WPA_GET_BE24(data + 1);
1373189251Ssam			break;
1374189251Ssam		case RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID:
1375189251Ssam			if (data[0] < RADIUS_TUNNEL_TAGS) {
1376189251Ssam				data++;
1377189251Ssam				dlen--;
1378189251Ssam			}
1379189251Ssam			if (dlen >= sizeof(buf))
1380189251Ssam				break;
1381189251Ssam			os_memcpy(buf, data, dlen);
1382189251Ssam			buf[dlen] = '\0';
1383189251Ssam			tun->tag_used++;
1384189251Ssam			tun->vlanid = atoi(buf);
1385189251Ssam			break;
1386189251Ssam		}
1387189251Ssam	}
1388189251Ssam
1389189251Ssam	for (i = 0; i < RADIUS_TUNNEL_TAGS; i++) {
1390189251Ssam		tun = &tunnel[i];
1391189251Ssam		if (tun->tag_used &&
1392189251Ssam		    tun->type == RADIUS_TUNNEL_TYPE_VLAN &&
1393189251Ssam		    tun->medium_type == RADIUS_TUNNEL_MEDIUM_TYPE_802 &&
1394189251Ssam		    tun->vlanid > 0)
1395189251Ssam			return tun->vlanid;
1396189251Ssam	}
1397189251Ssam
1398189251Ssam	return -1;
1399189251Ssam}
1400214734Srpaulo
1401214734Srpaulo
1402252726Srpaulo/**
1403252726Srpaulo * radius_msg_get_tunnel_password - Parse RADIUS attribute Tunnel-Password
1404252726Srpaulo * @msg: Received RADIUS message
1405252726Srpaulo * @keylen: Length of returned password
1406252726Srpaulo * @secret: RADIUS shared secret
1407252726Srpaulo * @secret_len: Length of secret
1408252726Srpaulo * @sent_msg: Sent RADIUS message
1409252726Srpaulo * @n: Number of password attribute to return (starting with 0)
1410252726Srpaulo * Returns: Pointer to n-th password (free with os_free) or %NULL
1411252726Srpaulo */
1412252726Srpaulochar * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen,
1413252726Srpaulo				      const u8 *secret, size_t secret_len,
1414252726Srpaulo				      struct radius_msg *sent_msg, size_t n)
1415252726Srpaulo{
1416252726Srpaulo	u8 *buf = NULL;
1417252726Srpaulo	size_t buflen;
1418252726Srpaulo	const u8 *salt;
1419252726Srpaulo	u8 *str;
1420252726Srpaulo	const u8 *addr[3];
1421252726Srpaulo	size_t len[3];
1422252726Srpaulo	u8 hash[16];
1423252726Srpaulo	u8 *pos;
1424252726Srpaulo	size_t i, j = 0;
1425252726Srpaulo	struct radius_attr_hdr *attr;
1426252726Srpaulo	const u8 *data;
1427252726Srpaulo	size_t dlen;
1428252726Srpaulo	const u8 *fdata = NULL; /* points to found item */
1429252726Srpaulo	size_t fdlen = -1;
1430252726Srpaulo	char *ret = NULL;
1431252726Srpaulo
1432252726Srpaulo	/* find n-th valid Tunnel-Password attribute */
1433252726Srpaulo	for (i = 0; i < msg->attr_used; i++) {
1434252726Srpaulo		attr = radius_get_attr_hdr(msg, i);
1435252726Srpaulo		if (attr == NULL ||
1436252726Srpaulo		    attr->type != RADIUS_ATTR_TUNNEL_PASSWORD) {
1437252726Srpaulo			continue;
1438252726Srpaulo		}
1439252726Srpaulo		if (attr->length <= 5)
1440252726Srpaulo			continue;
1441252726Srpaulo		data = (const u8 *) (attr + 1);
1442252726Srpaulo		dlen = attr->length - sizeof(*attr);
1443252726Srpaulo		if (dlen <= 3 || dlen % 16 != 3)
1444252726Srpaulo			continue;
1445252726Srpaulo		j++;
1446252726Srpaulo		if (j <= n)
1447252726Srpaulo			continue;
1448252726Srpaulo
1449252726Srpaulo		fdata = data;
1450252726Srpaulo		fdlen = dlen;
1451252726Srpaulo		break;
1452252726Srpaulo	}
1453252726Srpaulo	if (fdata == NULL)
1454252726Srpaulo		goto out;
1455252726Srpaulo
1456252726Srpaulo	/* alloc writable memory for decryption */
1457252726Srpaulo	buf = os_malloc(fdlen);
1458252726Srpaulo	if (buf == NULL)
1459252726Srpaulo		goto out;
1460252726Srpaulo	os_memcpy(buf, fdata, fdlen);
1461252726Srpaulo	buflen = fdlen;
1462252726Srpaulo
1463252726Srpaulo	/* init pointers */
1464252726Srpaulo	salt = buf + 1;
1465252726Srpaulo	str = buf + 3;
1466252726Srpaulo
1467252726Srpaulo	/* decrypt blocks */
1468252726Srpaulo	pos = buf + buflen - 16; /* last block */
1469252726Srpaulo	while (pos >= str + 16) { /* all but the first block */
1470252726Srpaulo		addr[0] = secret;
1471252726Srpaulo		len[0] = secret_len;
1472252726Srpaulo		addr[1] = pos - 16;
1473252726Srpaulo		len[1] = 16;
1474252726Srpaulo		md5_vector(2, addr, len, hash);
1475252726Srpaulo
1476252726Srpaulo		for (i = 0; i < 16; i++)
1477252726Srpaulo			pos[i] ^= hash[i];
1478252726Srpaulo
1479252726Srpaulo		pos -= 16;
1480252726Srpaulo	}
1481252726Srpaulo
1482252726Srpaulo	/* decrypt first block */
1483252726Srpaulo	if (str != pos)
1484252726Srpaulo		goto out;
1485252726Srpaulo	addr[0] = secret;
1486252726Srpaulo	len[0] = secret_len;
1487252726Srpaulo	addr[1] = sent_msg->hdr->authenticator;
1488252726Srpaulo	len[1] = 16;
1489252726Srpaulo	addr[2] = salt;
1490252726Srpaulo	len[2] = 2;
1491252726Srpaulo	md5_vector(3, addr, len, hash);
1492252726Srpaulo
1493252726Srpaulo	for (i = 0; i < 16; i++)
1494252726Srpaulo		pos[i] ^= hash[i];
1495252726Srpaulo
1496252726Srpaulo	/* derive plaintext length from first subfield */
1497252726Srpaulo	*keylen = (unsigned char) str[0];
1498252726Srpaulo	if ((u8 *) (str + *keylen) >= (u8 *) (buf + buflen)) {
1499252726Srpaulo		/* decryption error - invalid key length */
1500252726Srpaulo		goto out;
1501252726Srpaulo	}
1502252726Srpaulo	if (*keylen == 0) {
1503252726Srpaulo		/* empty password */
1504252726Srpaulo		goto out;
1505252726Srpaulo	}
1506252726Srpaulo
1507252726Srpaulo	/* copy passphrase into new buffer */
1508252726Srpaulo	ret = os_malloc(*keylen);
1509252726Srpaulo	if (ret)
1510252726Srpaulo		os_memcpy(ret, str + 1, *keylen);
1511252726Srpaulo
1512252726Srpauloout:
1513252726Srpaulo	/* return new buffer */
1514252726Srpaulo	os_free(buf);
1515252726Srpaulo	return ret;
1516252726Srpaulo}
1517252726Srpaulo
1518252726Srpaulo
1519214734Srpaulovoid radius_free_class(struct radius_class_data *c)
1520214734Srpaulo{
1521214734Srpaulo	size_t i;
1522214734Srpaulo	if (c == NULL)
1523214734Srpaulo		return;
1524214734Srpaulo	for (i = 0; i < c->count; i++)
1525214734Srpaulo		os_free(c->attr[i].data);
1526214734Srpaulo	os_free(c->attr);
1527214734Srpaulo	c->attr = NULL;
1528214734Srpaulo	c->count = 0;
1529214734Srpaulo}
1530214734Srpaulo
1531214734Srpaulo
1532214734Srpauloint radius_copy_class(struct radius_class_data *dst,
1533214734Srpaulo		      const struct radius_class_data *src)
1534214734Srpaulo{
1535214734Srpaulo	size_t i;
1536214734Srpaulo
1537214734Srpaulo	if (src->attr == NULL)
1538214734Srpaulo		return 0;
1539214734Srpaulo
1540252726Srpaulo	dst->attr = os_calloc(src->count, sizeof(struct radius_attr_data));
1541214734Srpaulo	if (dst->attr == NULL)
1542214734Srpaulo		return -1;
1543214734Srpaulo
1544214734Srpaulo	dst->count = 0;
1545214734Srpaulo
1546214734Srpaulo	for (i = 0; i < src->count; i++) {
1547214734Srpaulo		dst->attr[i].data = os_malloc(src->attr[i].len);
1548214734Srpaulo		if (dst->attr[i].data == NULL)
1549214734Srpaulo			break;
1550214734Srpaulo		dst->count++;
1551214734Srpaulo		os_memcpy(dst->attr[i].data, src->attr[i].data,
1552214734Srpaulo			  src->attr[i].len);
1553214734Srpaulo		dst->attr[i].len = src->attr[i].len;
1554214734Srpaulo	}
1555214734Srpaulo
1556214734Srpaulo	return 0;
1557214734Srpaulo}
1558252726Srpaulo
1559252726Srpaulo
1560252726Srpaulou8 radius_msg_find_unlisted_attr(struct radius_msg *msg, u8 *attrs)
1561252726Srpaulo{
1562252726Srpaulo	size_t i, j;
1563252726Srpaulo	struct radius_attr_hdr *attr;
1564252726Srpaulo
1565252726Srpaulo	for (i = 0; i < msg->attr_used; i++) {
1566252726Srpaulo		attr = radius_get_attr_hdr(msg, i);
1567252726Srpaulo
1568252726Srpaulo		for (j = 0; attrs[j]; j++) {
1569252726Srpaulo			if (attr->type == attrs[j])
1570252726Srpaulo				break;
1571252726Srpaulo		}
1572252726Srpaulo
1573252726Srpaulo		if (attrs[j] == 0)
1574252726Srpaulo			return attr->type; /* unlisted attr */
1575252726Srpaulo	}
1576252726Srpaulo
1577252726Srpaulo	return 0;
1578252726Srpaulo}
1579