1189251Ssam/*
2214734Srpaulo * RADIUS message processing
3214734Srpaulo * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
4189251Ssam *
5189251Ssam * This program is free software; you can redistribute it and/or modify
6189251Ssam * it under the terms of the GNU General Public License version 2 as
7189251Ssam * published by the Free Software Foundation.
8189251Ssam *
9189251Ssam * Alternatively, this software may be distributed under the terms of BSD
10189251Ssam * license.
11189251Ssam *
12189251Ssam * See README and COPYING for more details.
13189251Ssam */
14189251Ssam
15214734Srpaulo#include "utils/includes.h"
16189251Ssam
17214734Srpaulo#include "utils/common.h"
18214734Srpaulo#include "utils/wpabuf.h"
19214734Srpaulo#include "crypto/md5.h"
20214734Srpaulo#include "crypto/crypto.h"
21189251Ssam#include "radius.h"
22189251Ssam
23189251Ssam
24214734Srpaulo/**
25214734Srpaulo * struct radius_msg - RADIUS message structure for new and parsed messages
26214734Srpaulo */
27214734Srpaulostruct radius_msg {
28214734Srpaulo	/**
29214734Srpaulo	 * buf - Allocated buffer for RADIUS message
30214734Srpaulo	 */
31214734Srpaulo	struct wpabuf *buf;
32214734Srpaulo
33214734Srpaulo	/**
34214734Srpaulo	 * hdr - Pointer to the RADIUS header in buf
35214734Srpaulo	 */
36214734Srpaulo	struct radius_hdr *hdr;
37214734Srpaulo
38214734Srpaulo	/**
39214734Srpaulo	 * attr_pos - Array of indexes to attributes
40214734Srpaulo	 *
41214734Srpaulo	 * The values are number of bytes from buf to the beginning of
42214734Srpaulo	 * struct radius_attr_hdr.
43214734Srpaulo	 */
44214734Srpaulo	size_t *attr_pos;
45214734Srpaulo
46214734Srpaulo	/**
47214734Srpaulo	 * attr_size - Total size of the attribute pointer array
48214734Srpaulo	 */
49214734Srpaulo	size_t attr_size;
50214734Srpaulo
51214734Srpaulo	/**
52214734Srpaulo	 * attr_used - Total number of attributes in the array
53214734Srpaulo	 */
54214734Srpaulo	size_t attr_used;
55214734Srpaulo};
56214734Srpaulo
57214734Srpaulo
58214734Srpaulostruct radius_hdr * radius_msg_get_hdr(struct radius_msg *msg)
59189251Ssam{
60214734Srpaulo	return msg->hdr;
61189251Ssam}
62189251Ssam
63189251Ssam
64214734Srpaulostruct wpabuf * radius_msg_get_buf(struct radius_msg *msg)
65189251Ssam{
66214734Srpaulo	return msg->buf;
67214734Srpaulo}
68189251Ssam
69189251Ssam
70214734Srpaulostatic struct radius_attr_hdr *
71214734Srpauloradius_get_attr_hdr(struct radius_msg *msg, int idx)
72214734Srpaulo{
73214734Srpaulo	return (struct radius_attr_hdr *)
74214734Srpaulo		(wpabuf_mhead_u8(msg->buf) + msg->attr_pos[idx]);
75214734Srpaulo}
76189251Ssam
77189251Ssam
78214734Srpaulostatic void radius_msg_set_hdr(struct radius_msg *msg, u8 code, u8 identifier)
79214734Srpaulo{
80214734Srpaulo	msg->hdr->code = code;
81214734Srpaulo	msg->hdr->identifier = identifier;
82189251Ssam}
83189251Ssam
84189251Ssam
85214734Srpaulostatic int radius_msg_initialize(struct radius_msg *msg)
86189251Ssam{
87189251Ssam	msg->attr_pos =
88189251Ssam		os_zalloc(RADIUS_DEFAULT_ATTR_COUNT * sizeof(*msg->attr_pos));
89214734Srpaulo	if (msg->attr_pos == NULL)
90189251Ssam		return -1;
91189251Ssam
92189251Ssam	msg->attr_size = RADIUS_DEFAULT_ATTR_COUNT;
93189251Ssam	msg->attr_used = 0;
94189251Ssam
95189251Ssam	return 0;
96189251Ssam}
97189251Ssam
98189251Ssam
99214734Srpaulo/**
100214734Srpaulo * radius_msg_new - Create a new RADIUS message
101214734Srpaulo * @code: Code for RADIUS header
102214734Srpaulo * @identifier: Identifier for RADIUS header
103214734Srpaulo * Returns: Context for RADIUS message or %NULL on failure
104214734Srpaulo *
105214734Srpaulo * The caller is responsible for freeing the returned data with
106214734Srpaulo * radius_msg_free().
107214734Srpaulo */
108214734Srpaulostruct radius_msg * radius_msg_new(u8 code, u8 identifier)
109189251Ssam{
110214734Srpaulo	struct radius_msg *msg;
111214734Srpaulo
112214734Srpaulo	msg = os_zalloc(sizeof(*msg));
113214734Srpaulo	if (msg == NULL)
114214734Srpaulo		return NULL;
115214734Srpaulo
116214734Srpaulo	msg->buf = wpabuf_alloc(RADIUS_DEFAULT_MSG_SIZE);
117214734Srpaulo	if (msg->buf == NULL || radius_msg_initialize(msg)) {
118214734Srpaulo		radius_msg_free(msg);
119214734Srpaulo		return NULL;
120214734Srpaulo	}
121214734Srpaulo	msg->hdr = wpabuf_put(msg->buf, sizeof(struct radius_hdr));
122214734Srpaulo
123214734Srpaulo	radius_msg_set_hdr(msg, code, identifier);
124214734Srpaulo
125214734Srpaulo	return msg;
126189251Ssam}
127189251Ssam
128189251Ssam
129214734Srpaulo/**
130214734Srpaulo * radius_msg_free - Free a RADIUS message
131214734Srpaulo * @msg: RADIUS message from radius_msg_new() or radius_msg_parse()
132214734Srpaulo */
133189251Ssamvoid radius_msg_free(struct radius_msg *msg)
134189251Ssam{
135214734Srpaulo	if (msg == NULL)
136214734Srpaulo		return;
137189251Ssam
138214734Srpaulo	wpabuf_free(msg->buf);
139189251Ssam	os_free(msg->attr_pos);
140214734Srpaulo	os_free(msg);
141189251Ssam}
142189251Ssam
143189251Ssam
144189251Ssamstatic const char *radius_code_string(u8 code)
145189251Ssam{
146189251Ssam	switch (code) {
147189251Ssam	case RADIUS_CODE_ACCESS_REQUEST: return "Access-Request";
148189251Ssam	case RADIUS_CODE_ACCESS_ACCEPT: return "Access-Accept";
149189251Ssam	case RADIUS_CODE_ACCESS_REJECT: return "Access-Reject";
150189251Ssam	case RADIUS_CODE_ACCOUNTING_REQUEST: return "Accounting-Request";
151189251Ssam	case RADIUS_CODE_ACCOUNTING_RESPONSE: return "Accounting-Response";
152189251Ssam	case RADIUS_CODE_ACCESS_CHALLENGE: return "Access-Challenge";
153189251Ssam	case RADIUS_CODE_STATUS_SERVER: return "Status-Server";
154189251Ssam	case RADIUS_CODE_STATUS_CLIENT: return "Status-Client";
155189251Ssam	case RADIUS_CODE_RESERVED: return "Reserved";
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 },
221189251Ssam	{ RADIUS_ATTR_CONNECT_INFO, "Connect-Info", RADIUS_ATTR_TEXT },
222189251Ssam	{ RADIUS_ATTR_EAP_MESSAGE, "EAP-Message", RADIUS_ATTR_UNDIST },
223189251Ssam	{ RADIUS_ATTR_MESSAGE_AUTHENTICATOR, "Message-Authenticator",
224189251Ssam	  RADIUS_ATTR_UNDIST },
225189251Ssam	{ RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, "Tunnel-Private-Group-Id",
226189251Ssam	  RADIUS_ATTR_HEXDUMP },
227189251Ssam	{ RADIUS_ATTR_ACCT_INTERIM_INTERVAL, "Acct-Interim-Interval",
228189251Ssam	  RADIUS_ATTR_INT32 },
229189251Ssam	{ RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargable-User-Identity",
230189251Ssam	  RADIUS_ATTR_TEXT },
231189251Ssam	{ RADIUS_ATTR_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", RADIUS_ATTR_IPV6 },
232189251Ssam};
233189251Ssam#define RADIUS_ATTRS (sizeof(radius_attrs) / sizeof(radius_attrs[0]))
234189251Ssam
235189251Ssam
236189251Ssamstatic struct radius_attr_type *radius_get_attr_type(u8 type)
237189251Ssam{
238189251Ssam	size_t i;
239189251Ssam
240189251Ssam	for (i = 0; i < RADIUS_ATTRS; i++) {
241189251Ssam		if (type == radius_attrs[i].type)
242189251Ssam			return &radius_attrs[i];
243189251Ssam	}
244189251Ssam
245189251Ssam	return NULL;
246189251Ssam}
247189251Ssam
248189251Ssam
249189251Ssamstatic void print_char(char c)
250189251Ssam{
251189251Ssam	if (c >= 32 && c < 127)
252189251Ssam		printf("%c", c);
253189251Ssam	else
254189251Ssam		printf("<%02x>", c);
255189251Ssam}
256189251Ssam
257189251Ssam
258189251Ssamstatic void radius_msg_dump_attr(struct radius_attr_hdr *hdr)
259189251Ssam{
260189251Ssam	struct radius_attr_type *attr;
261189251Ssam	int i, len;
262189251Ssam	unsigned char *pos;
263189251Ssam
264189251Ssam	attr = radius_get_attr_type(hdr->type);
265189251Ssam
266189251Ssam	printf("   Attribute %d (%s) length=%d\n",
267189251Ssam	       hdr->type, attr ? attr->name : "?Unknown?", hdr->length);
268189251Ssam
269189251Ssam	if (attr == NULL)
270189251Ssam		return;
271189251Ssam
272189251Ssam	len = hdr->length - sizeof(struct radius_attr_hdr);
273189251Ssam	pos = (unsigned char *) (hdr + 1);
274189251Ssam
275189251Ssam	switch (attr->data_type) {
276189251Ssam	case RADIUS_ATTR_TEXT:
277189251Ssam		printf("      Value: '");
278189251Ssam		for (i = 0; i < len; i++)
279189251Ssam			print_char(pos[i]);
280189251Ssam		printf("'\n");
281189251Ssam		break;
282189251Ssam
283189251Ssam	case RADIUS_ATTR_IP:
284189251Ssam		if (len == 4) {
285189251Ssam			struct in_addr addr;
286189251Ssam			os_memcpy(&addr, pos, 4);
287189251Ssam			printf("      Value: %s\n", inet_ntoa(addr));
288189251Ssam		} else
289189251Ssam			printf("      Invalid IP address length %d\n", len);
290189251Ssam		break;
291189251Ssam
292189251Ssam#ifdef CONFIG_IPV6
293189251Ssam	case RADIUS_ATTR_IPV6:
294189251Ssam		if (len == 16) {
295189251Ssam			char buf[128];
296189251Ssam			const char *atxt;
297189251Ssam			struct in6_addr *addr = (struct in6_addr *) pos;
298189251Ssam			atxt = inet_ntop(AF_INET6, addr, buf, sizeof(buf));
299189251Ssam			printf("      Value: %s\n", atxt ? atxt : "?");
300189251Ssam		} else
301189251Ssam			printf("      Invalid IPv6 address length %d\n", len);
302189251Ssam		break;
303189251Ssam#endif /* CONFIG_IPV6 */
304189251Ssam
305189251Ssam	case RADIUS_ATTR_HEXDUMP:
306189251Ssam	case RADIUS_ATTR_UNDIST:
307189251Ssam		printf("      Value:");
308189251Ssam		for (i = 0; i < len; i++)
309189251Ssam			printf(" %02x", pos[i]);
310189251Ssam		printf("\n");
311189251Ssam		break;
312189251Ssam
313189251Ssam	case RADIUS_ATTR_INT32:
314189251Ssam		if (len == 4)
315189251Ssam			printf("      Value: %u\n", WPA_GET_BE32(pos));
316189251Ssam		else
317189251Ssam			printf("      Invalid INT32 length %d\n", len);
318189251Ssam		break;
319189251Ssam
320189251Ssam	default:
321189251Ssam		break;
322189251Ssam	}
323189251Ssam}
324189251Ssam
325189251Ssam
326189251Ssamvoid radius_msg_dump(struct radius_msg *msg)
327189251Ssam{
328189251Ssam	size_t i;
329189251Ssam
330189251Ssam	printf("RADIUS message: code=%d (%s) identifier=%d length=%d\n",
331189251Ssam	       msg->hdr->code, radius_code_string(msg->hdr->code),
332189251Ssam	       msg->hdr->identifier, ntohs(msg->hdr->length));
333189251Ssam
334189251Ssam	for (i = 0; i < msg->attr_used; i++) {
335189251Ssam		struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
336189251Ssam		radius_msg_dump_attr(attr);
337189251Ssam	}
338189251Ssam}
339189251Ssam
340189251Ssam
341189251Ssamint radius_msg_finish(struct radius_msg *msg, const u8 *secret,
342189251Ssam		      size_t secret_len)
343189251Ssam{
344189251Ssam	if (secret) {
345189251Ssam		u8 auth[MD5_MAC_LEN];
346189251Ssam		struct radius_attr_hdr *attr;
347189251Ssam
348189251Ssam		os_memset(auth, 0, MD5_MAC_LEN);
349189251Ssam		attr = radius_msg_add_attr(msg,
350189251Ssam					   RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
351189251Ssam					   auth, MD5_MAC_LEN);
352189251Ssam		if (attr == NULL) {
353214734Srpaulo			wpa_printf(MSG_WARNING, "RADIUS: Could not add "
354214734Srpaulo				   "Message-Authenticator");
355189251Ssam			return -1;
356189251Ssam		}
357214734Srpaulo		msg->hdr->length = htons(wpabuf_len(msg->buf));
358214734Srpaulo		hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
359214734Srpaulo			 wpabuf_len(msg->buf), (u8 *) (attr + 1));
360189251Ssam	} else
361214734Srpaulo		msg->hdr->length = htons(wpabuf_len(msg->buf));
362189251Ssam
363214734Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
364214734Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)",
365214734Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
366189251Ssam		return -1;
367189251Ssam	}
368189251Ssam	return 0;
369189251Ssam}
370189251Ssam
371189251Ssam
372189251Ssamint radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret,
373189251Ssam			  size_t secret_len, const u8 *req_authenticator)
374189251Ssam{
375189251Ssam	u8 auth[MD5_MAC_LEN];
376189251Ssam	struct radius_attr_hdr *attr;
377189251Ssam	const u8 *addr[4];
378189251Ssam	size_t len[4];
379189251Ssam
380189251Ssam	os_memset(auth, 0, MD5_MAC_LEN);
381189251Ssam	attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
382189251Ssam				   auth, MD5_MAC_LEN);
383189251Ssam	if (attr == NULL) {
384189251Ssam		printf("WARNING: Could not add Message-Authenticator\n");
385189251Ssam		return -1;
386189251Ssam	}
387214734Srpaulo	msg->hdr->length = htons(wpabuf_len(msg->buf));
388189251Ssam	os_memcpy(msg->hdr->authenticator, req_authenticator,
389189251Ssam		  sizeof(msg->hdr->authenticator));
390214734Srpaulo	hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
391214734Srpaulo		 wpabuf_len(msg->buf), (u8 *) (attr + 1));
392189251Ssam
393189251Ssam	/* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
394189251Ssam	addr[0] = (u8 *) msg->hdr;
395189251Ssam	len[0] = 1 + 1 + 2;
396189251Ssam	addr[1] = req_authenticator;
397189251Ssam	len[1] = MD5_MAC_LEN;
398214734Srpaulo	addr[2] = wpabuf_head_u8(msg->buf) + sizeof(struct radius_hdr);
399214734Srpaulo	len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
400189251Ssam	addr[3] = secret;
401189251Ssam	len[3] = secret_len;
402189251Ssam	md5_vector(4, addr, len, msg->hdr->authenticator);
403189251Ssam
404214734Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
405214734Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)",
406214734Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
407189251Ssam		return -1;
408189251Ssam	}
409189251Ssam	return 0;
410189251Ssam}
411189251Ssam
412189251Ssam
413189251Ssamvoid radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
414189251Ssam			    size_t secret_len)
415189251Ssam{
416189251Ssam	const u8 *addr[2];
417189251Ssam	size_t len[2];
418189251Ssam
419214734Srpaulo	msg->hdr->length = htons(wpabuf_len(msg->buf));
420189251Ssam	os_memset(msg->hdr->authenticator, 0, MD5_MAC_LEN);
421214734Srpaulo	addr[0] = wpabuf_head(msg->buf);
422214734Srpaulo	len[0] = wpabuf_len(msg->buf);
423189251Ssam	addr[1] = secret;
424189251Ssam	len[1] = secret_len;
425189251Ssam	md5_vector(2, addr, len, msg->hdr->authenticator);
426189251Ssam
427214734Srpaulo	if (wpabuf_len(msg->buf) > 0xffff) {
428214734Srpaulo		wpa_printf(MSG_WARNING, "RADIUS: Too long messages (%lu)",
429214734Srpaulo			   (unsigned long) wpabuf_len(msg->buf));
430189251Ssam	}
431189251Ssam}
432189251Ssam
433189251Ssam
434189251Ssamstatic int radius_msg_add_attr_to_array(struct radius_msg *msg,
435189251Ssam					struct radius_attr_hdr *attr)
436189251Ssam{
437189251Ssam	if (msg->attr_used >= msg->attr_size) {
438189251Ssam		size_t *nattr_pos;
439189251Ssam		int nlen = msg->attr_size * 2;
440189251Ssam
441189251Ssam		nattr_pos = os_realloc(msg->attr_pos,
442189251Ssam				       nlen * sizeof(*msg->attr_pos));
443189251Ssam		if (nattr_pos == NULL)
444189251Ssam			return -1;
445189251Ssam
446189251Ssam		msg->attr_pos = nattr_pos;
447189251Ssam		msg->attr_size = nlen;
448189251Ssam	}
449189251Ssam
450214734Srpaulo	msg->attr_pos[msg->attr_used++] =
451214734Srpaulo		(unsigned char *) attr - wpabuf_head_u8(msg->buf);
452189251Ssam
453189251Ssam	return 0;
454189251Ssam}
455189251Ssam
456189251Ssam
457189251Ssamstruct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type,
458189251Ssam					    const u8 *data, size_t data_len)
459189251Ssam{
460189251Ssam	size_t buf_needed;
461189251Ssam	struct radius_attr_hdr *attr;
462189251Ssam
463189251Ssam	if (data_len > RADIUS_MAX_ATTR_LEN) {
464189251Ssam		printf("radius_msg_add_attr: too long attribute (%lu bytes)\n",
465189251Ssam		       (unsigned long) data_len);
466189251Ssam		return NULL;
467189251Ssam	}
468189251Ssam
469214734Srpaulo	buf_needed = sizeof(*attr) + data_len;
470189251Ssam
471214734Srpaulo	if (wpabuf_tailroom(msg->buf) < buf_needed) {
472189251Ssam		/* allocate more space for message buffer */
473214734Srpaulo		if (wpabuf_resize(&msg->buf, buf_needed) < 0)
474189251Ssam			return NULL;
475214734Srpaulo		msg->hdr = wpabuf_mhead(msg->buf);
476189251Ssam	}
477189251Ssam
478214734Srpaulo	attr = wpabuf_put(msg->buf, sizeof(struct radius_attr_hdr));
479189251Ssam	attr->type = type;
480189251Ssam	attr->length = sizeof(*attr) + data_len;
481214734Srpaulo	wpabuf_put_data(msg->buf, data, data_len);
482189251Ssam
483189251Ssam	if (radius_msg_add_attr_to_array(msg, attr))
484189251Ssam		return NULL;
485189251Ssam
486189251Ssam	return attr;
487189251Ssam}
488189251Ssam
489189251Ssam
490214734Srpaulo/**
491214734Srpaulo * radius_msg_parse - Parse a RADIUS message
492214734Srpaulo * @data: RADIUS message to be parsed
493214734Srpaulo * @len: Length of data buffer in octets
494214734Srpaulo * Returns: Parsed RADIUS message or %NULL on failure
495214734Srpaulo *
496214734Srpaulo * This parses a RADIUS message and makes a copy of its data. The caller is
497214734Srpaulo * responsible for freeing the returned data with radius_msg_free().
498214734Srpaulo */
499214734Srpaulostruct radius_msg * radius_msg_parse(const u8 *data, size_t len)
500189251Ssam{
501189251Ssam	struct radius_msg *msg;
502189251Ssam	struct radius_hdr *hdr;
503189251Ssam	struct radius_attr_hdr *attr;
504189251Ssam	size_t msg_len;
505189251Ssam	unsigned char *pos, *end;
506189251Ssam
507189251Ssam	if (data == NULL || len < sizeof(*hdr))
508189251Ssam		return NULL;
509189251Ssam
510189251Ssam	hdr = (struct radius_hdr *) data;
511189251Ssam
512189251Ssam	msg_len = ntohs(hdr->length);
513189251Ssam	if (msg_len < sizeof(*hdr) || msg_len > len) {
514214734Srpaulo		wpa_printf(MSG_INFO, "RADIUS: Invalid message length");
515189251Ssam		return NULL;
516189251Ssam	}
517189251Ssam
518189251Ssam	if (msg_len < len) {
519214734Srpaulo		wpa_printf(MSG_DEBUG, "RADIUS: Ignored %lu extra bytes after "
520214734Srpaulo			   "RADIUS message", (unsigned long) len - msg_len);
521189251Ssam	}
522189251Ssam
523214734Srpaulo	msg = os_zalloc(sizeof(*msg));
524189251Ssam	if (msg == NULL)
525189251Ssam		return NULL;
526189251Ssam
527214734Srpaulo	msg->buf = wpabuf_alloc_copy(data, msg_len);
528214734Srpaulo	if (msg->buf == NULL || radius_msg_initialize(msg)) {
529214734Srpaulo		radius_msg_free(msg);
530189251Ssam		return NULL;
531189251Ssam	}
532214734Srpaulo	msg->hdr = wpabuf_mhead(msg->buf);
533189251Ssam
534189251Ssam	/* parse attributes */
535214734Srpaulo	pos = wpabuf_mhead_u8(msg->buf) + sizeof(struct radius_hdr);
536214734Srpaulo	end = wpabuf_mhead_u8(msg->buf) + wpabuf_len(msg->buf);
537189251Ssam	while (pos < end) {
538189251Ssam		if ((size_t) (end - pos) < sizeof(*attr))
539189251Ssam			goto fail;
540189251Ssam
541189251Ssam		attr = (struct radius_attr_hdr *) pos;
542189251Ssam
543189251Ssam		if (pos + attr->length > end || attr->length < sizeof(*attr))
544189251Ssam			goto fail;
545189251Ssam
546189251Ssam		/* TODO: check that attr->length is suitable for attr->type */
547189251Ssam
548189251Ssam		if (radius_msg_add_attr_to_array(msg, attr))
549189251Ssam			goto fail;
550189251Ssam
551189251Ssam		pos += attr->length;
552189251Ssam	}
553189251Ssam
554189251Ssam	return msg;
555189251Ssam
556189251Ssam fail:
557189251Ssam	radius_msg_free(msg);
558189251Ssam	return NULL;
559189251Ssam}
560189251Ssam
561189251Ssam
562189251Ssamint radius_msg_add_eap(struct radius_msg *msg, const u8 *data, size_t data_len)
563189251Ssam{
564189251Ssam	const u8 *pos = data;
565189251Ssam	size_t left = data_len;
566189251Ssam
567189251Ssam	while (left > 0) {
568189251Ssam		int len;
569189251Ssam		if (left > RADIUS_MAX_ATTR_LEN)
570189251Ssam			len = RADIUS_MAX_ATTR_LEN;
571189251Ssam		else
572189251Ssam			len = left;
573189251Ssam
574189251Ssam		if (!radius_msg_add_attr(msg, RADIUS_ATTR_EAP_MESSAGE,
575189251Ssam					 pos, len))
576189251Ssam			return 0;
577189251Ssam
578189251Ssam		pos += len;
579189251Ssam		left -= len;
580189251Ssam	}
581189251Ssam
582189251Ssam	return 1;
583189251Ssam}
584189251Ssam
585189251Ssam
586189251Ssamu8 *radius_msg_get_eap(struct radius_msg *msg, size_t *eap_len)
587189251Ssam{
588189251Ssam	u8 *eap, *pos;
589189251Ssam	size_t len, i;
590189251Ssam	struct radius_attr_hdr *attr;
591189251Ssam
592189251Ssam	if (msg == NULL)
593189251Ssam		return NULL;
594189251Ssam
595189251Ssam	len = 0;
596189251Ssam	for (i = 0; i < msg->attr_used; i++) {
597189251Ssam		attr = radius_get_attr_hdr(msg, i);
598189251Ssam		if (attr->type == RADIUS_ATTR_EAP_MESSAGE)
599189251Ssam			len += attr->length - sizeof(struct radius_attr_hdr);
600189251Ssam	}
601189251Ssam
602189251Ssam	if (len == 0)
603189251Ssam		return NULL;
604189251Ssam
605189251Ssam	eap = os_malloc(len);
606189251Ssam	if (eap == NULL)
607189251Ssam		return NULL;
608189251Ssam
609189251Ssam	pos = eap;
610189251Ssam	for (i = 0; i < msg->attr_used; i++) {
611189251Ssam		attr = radius_get_attr_hdr(msg, i);
612189251Ssam		if (attr->type == RADIUS_ATTR_EAP_MESSAGE) {
613189251Ssam			int flen = attr->length - sizeof(*attr);
614189251Ssam			os_memcpy(pos, attr + 1, flen);
615189251Ssam			pos += flen;
616189251Ssam		}
617189251Ssam	}
618189251Ssam
619189251Ssam	if (eap_len)
620189251Ssam		*eap_len = len;
621189251Ssam
622189251Ssam	return eap;
623189251Ssam}
624189251Ssam
625189251Ssam
626189251Ssamint radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret,
627189251Ssam			       size_t secret_len, const u8 *req_auth)
628189251Ssam{
629189251Ssam	u8 auth[MD5_MAC_LEN], orig[MD5_MAC_LEN];
630189251Ssam	u8 orig_authenticator[16];
631189251Ssam	struct radius_attr_hdr *attr = NULL, *tmp;
632189251Ssam	size_t i;
633189251Ssam
634189251Ssam	for (i = 0; i < msg->attr_used; i++) {
635189251Ssam		tmp = radius_get_attr_hdr(msg, i);
636189251Ssam		if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) {
637189251Ssam			if (attr != NULL) {
638189251Ssam				printf("Multiple Message-Authenticator "
639189251Ssam				       "attributes in RADIUS message\n");
640189251Ssam				return 1;
641189251Ssam			}
642189251Ssam			attr = tmp;
643189251Ssam		}
644189251Ssam	}
645189251Ssam
646189251Ssam	if (attr == NULL) {
647189251Ssam		printf("No Message-Authenticator attribute found\n");
648189251Ssam		return 1;
649189251Ssam	}
650189251Ssam
651189251Ssam	os_memcpy(orig, attr + 1, MD5_MAC_LEN);
652189251Ssam	os_memset(attr + 1, 0, MD5_MAC_LEN);
653189251Ssam	if (req_auth) {
654189251Ssam		os_memcpy(orig_authenticator, msg->hdr->authenticator,
655189251Ssam			  sizeof(orig_authenticator));
656189251Ssam		os_memcpy(msg->hdr->authenticator, req_auth,
657189251Ssam			  sizeof(msg->hdr->authenticator));
658189251Ssam	}
659214734Srpaulo	hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
660214734Srpaulo		 wpabuf_len(msg->buf), auth);
661189251Ssam	os_memcpy(attr + 1, orig, MD5_MAC_LEN);
662189251Ssam	if (req_auth) {
663189251Ssam		os_memcpy(msg->hdr->authenticator, orig_authenticator,
664189251Ssam			  sizeof(orig_authenticator));
665189251Ssam	}
666189251Ssam
667189251Ssam	if (os_memcmp(orig, auth, MD5_MAC_LEN) != 0) {
668189251Ssam		printf("Invalid Message-Authenticator!\n");
669189251Ssam		return 1;
670189251Ssam	}
671189251Ssam
672189251Ssam	return 0;
673189251Ssam}
674189251Ssam
675189251Ssam
676189251Ssamint radius_msg_verify(struct radius_msg *msg, const u8 *secret,
677189251Ssam		      size_t secret_len, struct radius_msg *sent_msg, int auth)
678189251Ssam{
679189251Ssam	const u8 *addr[4];
680189251Ssam	size_t len[4];
681189251Ssam	u8 hash[MD5_MAC_LEN];
682189251Ssam
683189251Ssam	if (sent_msg == NULL) {
684189251Ssam		printf("No matching Access-Request message found\n");
685189251Ssam		return 1;
686189251Ssam	}
687189251Ssam
688189251Ssam	if (auth &&
689189251Ssam	    radius_msg_verify_msg_auth(msg, secret, secret_len,
690189251Ssam				       sent_msg->hdr->authenticator)) {
691189251Ssam		return 1;
692189251Ssam	}
693189251Ssam
694189251Ssam	/* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
695189251Ssam	addr[0] = (u8 *) msg->hdr;
696189251Ssam	len[0] = 1 + 1 + 2;
697189251Ssam	addr[1] = sent_msg->hdr->authenticator;
698189251Ssam	len[1] = MD5_MAC_LEN;
699214734Srpaulo	addr[2] = wpabuf_head_u8(msg->buf) + sizeof(struct radius_hdr);
700214734Srpaulo	len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
701189251Ssam	addr[3] = secret;
702189251Ssam	len[3] = secret_len;
703189251Ssam	md5_vector(4, addr, len, hash);
704189251Ssam	if (os_memcmp(hash, msg->hdr->authenticator, MD5_MAC_LEN) != 0) {
705189251Ssam		printf("Response Authenticator invalid!\n");
706189251Ssam		return 1;
707189251Ssam	}
708189251Ssam
709189251Ssam	return 0;
710189251Ssam}
711189251Ssam
712189251Ssam
713189251Ssamint radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src,
714189251Ssam			 u8 type)
715189251Ssam{
716189251Ssam	struct radius_attr_hdr *attr;
717189251Ssam	size_t i;
718189251Ssam	int count = 0;
719189251Ssam
720189251Ssam	for (i = 0; i < src->attr_used; i++) {
721189251Ssam		attr = radius_get_attr_hdr(src, i);
722189251Ssam		if (attr->type == type) {
723189251Ssam			if (!radius_msg_add_attr(dst, type, (u8 *) (attr + 1),
724189251Ssam						 attr->length - sizeof(*attr)))
725189251Ssam				return -1;
726189251Ssam			count++;
727189251Ssam		}
728189251Ssam	}
729189251Ssam
730189251Ssam	return count;
731189251Ssam}
732189251Ssam
733189251Ssam
734189251Ssam/* Create Request Authenticator. The value should be unique over the lifetime
735189251Ssam * of the shared secret between authenticator and authentication server.
736189251Ssam * Use one-way MD5 hash calculated from current timestamp and some data given
737189251Ssam * by the caller. */
738189251Ssamvoid radius_msg_make_authenticator(struct radius_msg *msg,
739189251Ssam				   const u8 *data, size_t len)
740189251Ssam{
741189251Ssam	struct os_time tv;
742189251Ssam	long int l;
743189251Ssam	const u8 *addr[3];
744189251Ssam	size_t elen[3];
745189251Ssam
746189251Ssam	os_get_time(&tv);
747189251Ssam	l = os_random();
748189251Ssam	addr[0] = (u8 *) &tv;
749189251Ssam	elen[0] = sizeof(tv);
750189251Ssam	addr[1] = data;
751189251Ssam	elen[1] = len;
752189251Ssam	addr[2] = (u8 *) &l;
753189251Ssam	elen[2] = sizeof(l);
754189251Ssam	md5_vector(3, addr, elen, msg->hdr->authenticator);
755189251Ssam}
756189251Ssam
757189251Ssam
758189251Ssam/* Get Vendor-specific RADIUS Attribute from a parsed RADIUS message.
759189251Ssam * Returns the Attribute payload and sets alen to indicate the length of the
760189251Ssam * payload if a vendor attribute with subtype is found, otherwise returns NULL.
761189251Ssam * The returned payload is allocated with os_malloc() and caller must free it
762189251Ssam * by calling os_free().
763189251Ssam */
764189251Ssamstatic u8 *radius_msg_get_vendor_attr(struct radius_msg *msg, u32 vendor,
765189251Ssam				      u8 subtype, size_t *alen)
766189251Ssam{
767189251Ssam	u8 *data, *pos;
768189251Ssam	size_t i, len;
769189251Ssam
770189251Ssam	if (msg == NULL)
771189251Ssam		return NULL;
772189251Ssam
773189251Ssam	for (i = 0; i < msg->attr_used; i++) {
774189251Ssam		struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
775189251Ssam		size_t left;
776189251Ssam		u32 vendor_id;
777189251Ssam		struct radius_attr_vendor *vhdr;
778189251Ssam
779189251Ssam		if (attr->type != RADIUS_ATTR_VENDOR_SPECIFIC)
780189251Ssam			continue;
781189251Ssam
782189251Ssam		left = attr->length - sizeof(*attr);
783189251Ssam		if (left < 4)
784189251Ssam			continue;
785189251Ssam
786189251Ssam		pos = (u8 *) (attr + 1);
787189251Ssam
788189251Ssam		os_memcpy(&vendor_id, pos, 4);
789189251Ssam		pos += 4;
790189251Ssam		left -= 4;
791189251Ssam
792189251Ssam		if (ntohl(vendor_id) != vendor)
793189251Ssam			continue;
794189251Ssam
795189251Ssam		while (left >= sizeof(*vhdr)) {
796189251Ssam			vhdr = (struct radius_attr_vendor *) pos;
797189251Ssam			if (vhdr->vendor_length > left ||
798189251Ssam			    vhdr->vendor_length < sizeof(*vhdr)) {
799189251Ssam				left = 0;
800189251Ssam				break;
801189251Ssam			}
802189251Ssam			if (vhdr->vendor_type != subtype) {
803189251Ssam				pos += vhdr->vendor_length;
804189251Ssam				left -= vhdr->vendor_length;
805189251Ssam				continue;
806189251Ssam			}
807189251Ssam
808189251Ssam			len = vhdr->vendor_length - sizeof(*vhdr);
809189251Ssam			data = os_malloc(len);
810189251Ssam			if (data == NULL)
811189251Ssam				return NULL;
812189251Ssam			os_memcpy(data, pos + sizeof(*vhdr), len);
813189251Ssam			if (alen)
814189251Ssam				*alen = len;
815189251Ssam			return data;
816189251Ssam		}
817189251Ssam	}
818189251Ssam
819189251Ssam	return NULL;
820189251Ssam}
821189251Ssam
822189251Ssam
823189251Ssamstatic u8 * decrypt_ms_key(const u8 *key, size_t len,
824189251Ssam			   const u8 *req_authenticator,
825189251Ssam			   const u8 *secret, size_t secret_len, size_t *reslen)
826189251Ssam{
827189251Ssam	u8 *plain, *ppos, *res;
828189251Ssam	const u8 *pos;
829189251Ssam	size_t left, plen;
830189251Ssam	u8 hash[MD5_MAC_LEN];
831189251Ssam	int i, first = 1;
832189251Ssam	const u8 *addr[3];
833189251Ssam	size_t elen[3];
834189251Ssam
835189251Ssam	/* key: 16-bit salt followed by encrypted key info */
836189251Ssam
837189251Ssam	if (len < 2 + 16)
838189251Ssam		return NULL;
839189251Ssam
840189251Ssam	pos = key + 2;
841189251Ssam	left = len - 2;
842189251Ssam	if (left % 16) {
843189251Ssam		printf("Invalid ms key len %lu\n", (unsigned long) left);
844189251Ssam		return NULL;
845189251Ssam	}
846189251Ssam
847189251Ssam	plen = left;
848189251Ssam	ppos = plain = os_malloc(plen);
849189251Ssam	if (plain == NULL)
850189251Ssam		return NULL;
851189251Ssam	plain[0] = 0;
852189251Ssam
853189251Ssam	while (left > 0) {
854189251Ssam		/* b(1) = MD5(Secret + Request-Authenticator + Salt)
855189251Ssam		 * b(i) = MD5(Secret + c(i - 1)) for i > 1 */
856189251Ssam
857189251Ssam		addr[0] = secret;
858189251Ssam		elen[0] = secret_len;
859189251Ssam		if (first) {
860189251Ssam			addr[1] = req_authenticator;
861189251Ssam			elen[1] = MD5_MAC_LEN;
862189251Ssam			addr[2] = key;
863189251Ssam			elen[2] = 2; /* Salt */
864189251Ssam		} else {
865189251Ssam			addr[1] = pos - MD5_MAC_LEN;
866189251Ssam			elen[1] = MD5_MAC_LEN;
867189251Ssam		}
868189251Ssam		md5_vector(first ? 3 : 2, addr, elen, hash);
869189251Ssam		first = 0;
870189251Ssam
871189251Ssam		for (i = 0; i < MD5_MAC_LEN; i++)
872189251Ssam			*ppos++ = *pos++ ^ hash[i];
873189251Ssam		left -= MD5_MAC_LEN;
874189251Ssam	}
875189251Ssam
876189251Ssam	if (plain[0] == 0 || plain[0] > plen - 1) {
877189251Ssam		printf("Failed to decrypt MPPE key\n");
878189251Ssam		os_free(plain);
879189251Ssam		return NULL;
880189251Ssam	}
881189251Ssam
882189251Ssam	res = os_malloc(plain[0]);
883189251Ssam	if (res == NULL) {
884189251Ssam		os_free(plain);
885189251Ssam		return NULL;
886189251Ssam	}
887189251Ssam	os_memcpy(res, plain + 1, plain[0]);
888189251Ssam	if (reslen)
889189251Ssam		*reslen = plain[0];
890189251Ssam	os_free(plain);
891189251Ssam	return res;
892189251Ssam}
893189251Ssam
894189251Ssam
895189251Ssamstatic void encrypt_ms_key(const u8 *key, size_t key_len, u16 salt,
896189251Ssam			   const u8 *req_authenticator,
897189251Ssam			   const u8 *secret, size_t secret_len,
898189251Ssam			   u8 *ebuf, size_t *elen)
899189251Ssam{
900189251Ssam	int i, len, first = 1;
901189251Ssam	u8 hash[MD5_MAC_LEN], saltbuf[2], *pos;
902189251Ssam	const u8 *addr[3];
903189251Ssam	size_t _len[3];
904189251Ssam
905189251Ssam	WPA_PUT_BE16(saltbuf, salt);
906189251Ssam
907189251Ssam	len = 1 + key_len;
908189251Ssam	if (len & 0x0f) {
909189251Ssam		len = (len & 0xf0) + 16;
910189251Ssam	}
911189251Ssam	os_memset(ebuf, 0, len);
912189251Ssam	ebuf[0] = key_len;
913189251Ssam	os_memcpy(ebuf + 1, key, key_len);
914189251Ssam
915189251Ssam	*elen = len;
916189251Ssam
917189251Ssam	pos = ebuf;
918189251Ssam	while (len > 0) {
919189251Ssam		/* b(1) = MD5(Secret + Request-Authenticator + Salt)
920189251Ssam		 * b(i) = MD5(Secret + c(i - 1)) for i > 1 */
921189251Ssam		addr[0] = secret;
922189251Ssam		_len[0] = secret_len;
923189251Ssam		if (first) {
924189251Ssam			addr[1] = req_authenticator;
925189251Ssam			_len[1] = MD5_MAC_LEN;
926189251Ssam			addr[2] = saltbuf;
927189251Ssam			_len[2] = sizeof(saltbuf);
928189251Ssam		} else {
929189251Ssam			addr[1] = pos - MD5_MAC_LEN;
930189251Ssam			_len[1] = MD5_MAC_LEN;
931189251Ssam		}
932189251Ssam		md5_vector(first ? 3 : 2, addr, _len, hash);
933189251Ssam		first = 0;
934189251Ssam
935189251Ssam		for (i = 0; i < MD5_MAC_LEN; i++)
936189251Ssam			*pos++ ^= hash[i];
937189251Ssam
938189251Ssam		len -= MD5_MAC_LEN;
939189251Ssam	}
940189251Ssam}
941189251Ssam
942189251Ssam
943189251Ssamstruct radius_ms_mppe_keys *
944189251Ssamradius_msg_get_ms_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
945189251Ssam		       const u8 *secret, size_t secret_len)
946189251Ssam{
947189251Ssam	u8 *key;
948189251Ssam	size_t keylen;
949189251Ssam	struct radius_ms_mppe_keys *keys;
950189251Ssam
951189251Ssam	if (msg == NULL || sent_msg == NULL)
952189251Ssam		return NULL;
953189251Ssam
954189251Ssam	keys = os_zalloc(sizeof(*keys));
955189251Ssam	if (keys == NULL)
956189251Ssam		return NULL;
957189251Ssam
958189251Ssam	key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT,
959189251Ssam					 RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY,
960189251Ssam					 &keylen);
961189251Ssam	if (key) {
962189251Ssam		keys->send = decrypt_ms_key(key, keylen,
963189251Ssam					    sent_msg->hdr->authenticator,
964189251Ssam					    secret, secret_len,
965189251Ssam					    &keys->send_len);
966189251Ssam		os_free(key);
967189251Ssam	}
968189251Ssam
969189251Ssam	key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT,
970189251Ssam					 RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY,
971189251Ssam					 &keylen);
972189251Ssam	if (key) {
973189251Ssam		keys->recv = decrypt_ms_key(key, keylen,
974189251Ssam					    sent_msg->hdr->authenticator,
975189251Ssam					    secret, secret_len,
976189251Ssam					    &keys->recv_len);
977189251Ssam		os_free(key);
978189251Ssam	}
979189251Ssam
980189251Ssam	return keys;
981189251Ssam}
982189251Ssam
983189251Ssam
984189251Ssamstruct radius_ms_mppe_keys *
985189251Ssamradius_msg_get_cisco_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
986189251Ssam			  const u8 *secret, size_t secret_len)
987189251Ssam{
988189251Ssam	u8 *key;
989189251Ssam	size_t keylen;
990189251Ssam	struct radius_ms_mppe_keys *keys;
991189251Ssam
992189251Ssam	if (msg == NULL || sent_msg == NULL)
993189251Ssam		return NULL;
994189251Ssam
995189251Ssam	keys = os_zalloc(sizeof(*keys));
996189251Ssam	if (keys == NULL)
997189251Ssam		return NULL;
998189251Ssam
999189251Ssam	key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_CISCO,
1000189251Ssam					 RADIUS_CISCO_AV_PAIR, &keylen);
1001189251Ssam	if (key && keylen == 51 &&
1002189251Ssam	    os_memcmp(key, "leap:session-key=", 17) == 0) {
1003189251Ssam		keys->recv = decrypt_ms_key(key + 17, keylen - 17,
1004189251Ssam					    sent_msg->hdr->authenticator,
1005189251Ssam					    secret, secret_len,
1006189251Ssam					    &keys->recv_len);
1007189251Ssam	}
1008189251Ssam	os_free(key);
1009189251Ssam
1010189251Ssam	return keys;
1011189251Ssam}
1012189251Ssam
1013189251Ssam
1014189251Ssamint radius_msg_add_mppe_keys(struct radius_msg *msg,
1015189251Ssam			     const u8 *req_authenticator,
1016189251Ssam			     const u8 *secret, size_t secret_len,
1017189251Ssam			     const u8 *send_key, size_t send_key_len,
1018189251Ssam			     const u8 *recv_key, size_t recv_key_len)
1019189251Ssam{
1020189251Ssam	struct radius_attr_hdr *attr;
1021189251Ssam	u32 vendor_id = htonl(RADIUS_VENDOR_ID_MICROSOFT);
1022189251Ssam	u8 *buf;
1023189251Ssam	struct radius_attr_vendor *vhdr;
1024189251Ssam	u8 *pos;
1025189251Ssam	size_t elen;
1026189251Ssam	int hlen;
1027189251Ssam	u16 salt;
1028189251Ssam
1029189251Ssam	hlen = sizeof(vendor_id) + sizeof(*vhdr) + 2;
1030189251Ssam
1031189251Ssam	/* MS-MPPE-Send-Key */
1032189251Ssam	buf = os_malloc(hlen + send_key_len + 16);
1033189251Ssam	if (buf == NULL) {
1034189251Ssam		return 0;
1035189251Ssam	}
1036189251Ssam	pos = buf;
1037189251Ssam	os_memcpy(pos, &vendor_id, sizeof(vendor_id));
1038189251Ssam	pos += sizeof(vendor_id);
1039189251Ssam	vhdr = (struct radius_attr_vendor *) pos;
1040189251Ssam	vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY;
1041189251Ssam	pos = (u8 *) (vhdr + 1);
1042189251Ssam	salt = os_random() | 0x8000;
1043189251Ssam	WPA_PUT_BE16(pos, salt);
1044189251Ssam	pos += 2;
1045189251Ssam	encrypt_ms_key(send_key, send_key_len, salt, req_authenticator, secret,
1046189251Ssam		       secret_len, pos, &elen);
1047189251Ssam	vhdr->vendor_length = hlen + elen - sizeof(vendor_id);
1048189251Ssam
1049189251Ssam	attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
1050189251Ssam				   buf, hlen + elen);
1051189251Ssam	os_free(buf);
1052189251Ssam	if (attr == NULL) {
1053189251Ssam		return 0;
1054189251Ssam	}
1055189251Ssam
1056189251Ssam	/* MS-MPPE-Recv-Key */
1057189251Ssam	buf = os_malloc(hlen + send_key_len + 16);
1058189251Ssam	if (buf == NULL) {
1059189251Ssam		return 0;
1060189251Ssam	}
1061189251Ssam	pos = buf;
1062189251Ssam	os_memcpy(pos, &vendor_id, sizeof(vendor_id));
1063189251Ssam	pos += sizeof(vendor_id);
1064189251Ssam	vhdr = (struct radius_attr_vendor *) pos;
1065189251Ssam	vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY;
1066189251Ssam	pos = (u8 *) (vhdr + 1);
1067189251Ssam	salt ^= 1;
1068189251Ssam	WPA_PUT_BE16(pos, salt);
1069189251Ssam	pos += 2;
1070189251Ssam	encrypt_ms_key(recv_key, recv_key_len, salt, req_authenticator, secret,
1071189251Ssam		       secret_len, pos, &elen);
1072189251Ssam	vhdr->vendor_length = hlen + elen - sizeof(vendor_id);
1073189251Ssam
1074189251Ssam	attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
1075189251Ssam				   buf, hlen + elen);
1076189251Ssam	os_free(buf);
1077189251Ssam	if (attr == NULL) {
1078189251Ssam		return 0;
1079189251Ssam	}
1080189251Ssam
1081189251Ssam	return 1;
1082189251Ssam}
1083189251Ssam
1084189251Ssam
1085189251Ssam/* Add User-Password attribute to a RADIUS message and encrypt it as specified
1086189251Ssam * in RFC 2865, Chap. 5.2 */
1087189251Ssamstruct radius_attr_hdr *
1088189251Ssamradius_msg_add_attr_user_password(struct radius_msg *msg,
1089189251Ssam				  const u8 *data, size_t data_len,
1090189251Ssam				  const u8 *secret, size_t secret_len)
1091189251Ssam{
1092189251Ssam	u8 buf[128];
1093189251Ssam	int padlen, i;
1094189251Ssam	size_t buf_len, pos;
1095189251Ssam	const u8 *addr[2];
1096189251Ssam	size_t len[2];
1097189251Ssam	u8 hash[16];
1098189251Ssam
1099189251Ssam	if (data_len > 128)
1100189251Ssam		return NULL;
1101189251Ssam
1102189251Ssam	os_memcpy(buf, data, data_len);
1103189251Ssam	buf_len = data_len;
1104189251Ssam
1105189251Ssam	padlen = data_len % 16;
1106189251Ssam	if (padlen) {
1107189251Ssam		padlen = 16 - padlen;
1108189251Ssam		os_memset(buf + data_len, 0, padlen);
1109189251Ssam		buf_len += padlen;
1110189251Ssam	}
1111189251Ssam
1112189251Ssam	addr[0] = secret;
1113189251Ssam	len[0] = secret_len;
1114189251Ssam	addr[1] = msg->hdr->authenticator;
1115189251Ssam	len[1] = 16;
1116189251Ssam	md5_vector(2, addr, len, hash);
1117189251Ssam
1118189251Ssam	for (i = 0; i < 16; i++)
1119189251Ssam		buf[i] ^= hash[i];
1120189251Ssam	pos = 16;
1121189251Ssam
1122189251Ssam	while (pos < buf_len) {
1123189251Ssam		addr[0] = secret;
1124189251Ssam		len[0] = secret_len;
1125189251Ssam		addr[1] = &buf[pos - 16];
1126189251Ssam		len[1] = 16;
1127189251Ssam		md5_vector(2, addr, len, hash);
1128189251Ssam
1129189251Ssam		for (i = 0; i < 16; i++)
1130189251Ssam			buf[pos + i] ^= hash[i];
1131189251Ssam
1132189251Ssam		pos += 16;
1133189251Ssam	}
1134189251Ssam
1135189251Ssam	return radius_msg_add_attr(msg, RADIUS_ATTR_USER_PASSWORD,
1136189251Ssam				   buf, buf_len);
1137189251Ssam}
1138189251Ssam
1139189251Ssam
1140189251Ssamint radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len)
1141189251Ssam{
1142189251Ssam	struct radius_attr_hdr *attr = NULL, *tmp;
1143189251Ssam	size_t i, dlen;
1144189251Ssam
1145189251Ssam	for (i = 0; i < msg->attr_used; i++) {
1146189251Ssam		tmp = radius_get_attr_hdr(msg, i);
1147189251Ssam		if (tmp->type == type) {
1148189251Ssam			attr = tmp;
1149189251Ssam			break;
1150189251Ssam		}
1151189251Ssam	}
1152189251Ssam
1153189251Ssam	if (!attr)
1154189251Ssam		return -1;
1155189251Ssam
1156189251Ssam	dlen = attr->length - sizeof(*attr);
1157189251Ssam	if (buf)
1158189251Ssam		os_memcpy(buf, (attr + 1), dlen > len ? len : dlen);
1159189251Ssam	return dlen;
1160189251Ssam}
1161189251Ssam
1162189251Ssam
1163189251Ssamint radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf,
1164189251Ssam			    size_t *len, const u8 *start)
1165189251Ssam{
1166189251Ssam	size_t i;
1167189251Ssam	struct radius_attr_hdr *attr = NULL, *tmp;
1168189251Ssam
1169189251Ssam	for (i = 0; i < msg->attr_used; i++) {
1170189251Ssam		tmp = radius_get_attr_hdr(msg, i);
1171189251Ssam		if (tmp->type == type &&
1172189251Ssam		    (start == NULL || (u8 *) tmp > start)) {
1173189251Ssam			attr = tmp;
1174189251Ssam			break;
1175189251Ssam		}
1176189251Ssam	}
1177189251Ssam
1178189251Ssam	if (!attr)
1179189251Ssam		return -1;
1180189251Ssam
1181189251Ssam	*buf = (u8 *) (attr + 1);
1182189251Ssam	*len = attr->length - sizeof(*attr);
1183189251Ssam	return 0;
1184189251Ssam}
1185189251Ssam
1186189251Ssam
1187189251Ssamint radius_msg_count_attr(struct radius_msg *msg, u8 type, int min_len)
1188189251Ssam{
1189189251Ssam	size_t i;
1190189251Ssam	int count;
1191189251Ssam
1192189251Ssam	for (count = 0, i = 0; i < msg->attr_used; i++) {
1193189251Ssam		struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
1194189251Ssam		if (attr->type == type &&
1195189251Ssam		    attr->length >= sizeof(struct radius_attr_hdr) + min_len)
1196189251Ssam			count++;
1197189251Ssam	}
1198189251Ssam
1199189251Ssam	return count;
1200189251Ssam}
1201189251Ssam
1202189251Ssam
1203189251Ssamstruct radius_tunnel_attrs {
1204189251Ssam	int tag_used;
1205189251Ssam	int type; /* Tunnel-Type */
1206189251Ssam	int medium_type; /* Tunnel-Medium-Type */
1207189251Ssam	int vlanid;
1208189251Ssam};
1209189251Ssam
1210189251Ssam
1211189251Ssam/**
1212189251Ssam * radius_msg_get_vlanid - Parse RADIUS attributes for VLAN tunnel information
1213189251Ssam * @msg: RADIUS message
1214189251Ssam * Returns: VLAN ID for the first tunnel configuration of -1 if none is found
1215189251Ssam */
1216189251Ssamint radius_msg_get_vlanid(struct radius_msg *msg)
1217189251Ssam{
1218189251Ssam	struct radius_tunnel_attrs tunnel[RADIUS_TUNNEL_TAGS], *tun;
1219189251Ssam	size_t i;
1220189251Ssam	struct radius_attr_hdr *attr = NULL;
1221189251Ssam	const u8 *data;
1222189251Ssam	char buf[10];
1223189251Ssam	size_t dlen;
1224189251Ssam
1225189251Ssam	os_memset(&tunnel, 0, sizeof(tunnel));
1226189251Ssam
1227189251Ssam	for (i = 0; i < msg->attr_used; i++) {
1228189251Ssam		attr = radius_get_attr_hdr(msg, i);
1229189251Ssam		data = (const u8 *) (attr + 1);
1230189251Ssam		dlen = attr->length - sizeof(*attr);
1231189251Ssam		if (attr->length < 3)
1232189251Ssam			continue;
1233189251Ssam		if (data[0] >= RADIUS_TUNNEL_TAGS)
1234189251Ssam			tun = &tunnel[0];
1235189251Ssam		else
1236189251Ssam			tun = &tunnel[data[0]];
1237189251Ssam
1238189251Ssam		switch (attr->type) {
1239189251Ssam		case RADIUS_ATTR_TUNNEL_TYPE:
1240189251Ssam			if (attr->length != 6)
1241189251Ssam				break;
1242189251Ssam			tun->tag_used++;
1243189251Ssam			tun->type = WPA_GET_BE24(data + 1);
1244189251Ssam			break;
1245189251Ssam		case RADIUS_ATTR_TUNNEL_MEDIUM_TYPE:
1246189251Ssam			if (attr->length != 6)
1247189251Ssam				break;
1248189251Ssam			tun->tag_used++;
1249189251Ssam			tun->medium_type = WPA_GET_BE24(data + 1);
1250189251Ssam			break;
1251189251Ssam		case RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID:
1252189251Ssam			if (data[0] < RADIUS_TUNNEL_TAGS) {
1253189251Ssam				data++;
1254189251Ssam				dlen--;
1255189251Ssam			}
1256189251Ssam			if (dlen >= sizeof(buf))
1257189251Ssam				break;
1258189251Ssam			os_memcpy(buf, data, dlen);
1259189251Ssam			buf[dlen] = '\0';
1260189251Ssam			tun->tag_used++;
1261189251Ssam			tun->vlanid = atoi(buf);
1262189251Ssam			break;
1263189251Ssam		}
1264189251Ssam	}
1265189251Ssam
1266189251Ssam	for (i = 0; i < RADIUS_TUNNEL_TAGS; i++) {
1267189251Ssam		tun = &tunnel[i];
1268189251Ssam		if (tun->tag_used &&
1269189251Ssam		    tun->type == RADIUS_TUNNEL_TYPE_VLAN &&
1270189251Ssam		    tun->medium_type == RADIUS_TUNNEL_MEDIUM_TYPE_802 &&
1271189251Ssam		    tun->vlanid > 0)
1272189251Ssam			return tun->vlanid;
1273189251Ssam	}
1274189251Ssam
1275189251Ssam	return -1;
1276189251Ssam}
1277214734Srpaulo
1278214734Srpaulo
1279214734Srpaulovoid radius_free_class(struct radius_class_data *c)
1280214734Srpaulo{
1281214734Srpaulo	size_t i;
1282214734Srpaulo	if (c == NULL)
1283214734Srpaulo		return;
1284214734Srpaulo	for (i = 0; i < c->count; i++)
1285214734Srpaulo		os_free(c->attr[i].data);
1286214734Srpaulo	os_free(c->attr);
1287214734Srpaulo	c->attr = NULL;
1288214734Srpaulo	c->count = 0;
1289214734Srpaulo}
1290214734Srpaulo
1291214734Srpaulo
1292214734Srpauloint radius_copy_class(struct radius_class_data *dst,
1293214734Srpaulo		      const struct radius_class_data *src)
1294214734Srpaulo{
1295214734Srpaulo	size_t i;
1296214734Srpaulo
1297214734Srpaulo	if (src->attr == NULL)
1298214734Srpaulo		return 0;
1299214734Srpaulo
1300214734Srpaulo	dst->attr = os_zalloc(src->count * sizeof(struct radius_attr_data));
1301214734Srpaulo	if (dst->attr == NULL)
1302214734Srpaulo		return -1;
1303214734Srpaulo
1304214734Srpaulo	dst->count = 0;
1305214734Srpaulo
1306214734Srpaulo	for (i = 0; i < src->count; i++) {
1307214734Srpaulo		dst->attr[i].data = os_malloc(src->attr[i].len);
1308214734Srpaulo		if (dst->attr[i].data == NULL)
1309214734Srpaulo			break;
1310214734Srpaulo		dst->count++;
1311214734Srpaulo		os_memcpy(dst->attr[i].data, src->attr[i].data,
1312214734Srpaulo			  src->attr[i].len);
1313214734Srpaulo		dst->attr[i].len = src->attr[i].len;
1314214734Srpaulo	}
1315214734Srpaulo
1316214734Srpaulo	return 0;
1317214734Srpaulo}
1318