13ff40c12SJohn Marino /*
23ff40c12SJohn Marino  * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
33ff40c12SJohn Marino  * Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
43ff40c12SJohn Marino  *
53ff40c12SJohn Marino  * This software may be distributed under the terms of the BSD license.
63ff40c12SJohn Marino  * See README for more details.
73ff40c12SJohn Marino  */
83ff40c12SJohn Marino 
93ff40c12SJohn Marino #include "includes.h"
103ff40c12SJohn Marino #include <net/if.h>
113ff40c12SJohn Marino 
123ff40c12SJohn Marino #include "utils/common.h"
133ff40c12SJohn Marino #include "utils/eloop.h"
143ff40c12SJohn Marino #include "utils/ip_addr.h"
153ff40c12SJohn Marino #include "radius.h"
163ff40c12SJohn Marino #include "radius_das.h"
173ff40c12SJohn Marino 
183ff40c12SJohn Marino 
193ff40c12SJohn Marino struct radius_das_data {
203ff40c12SJohn Marino 	int sock;
213ff40c12SJohn Marino 	u8 *shared_secret;
223ff40c12SJohn Marino 	size_t shared_secret_len;
233ff40c12SJohn Marino 	struct hostapd_ip_addr client_addr;
243ff40c12SJohn Marino 	unsigned int time_window;
253ff40c12SJohn Marino 	int require_event_timestamp;
26*a1157835SDaniel Fojt 	int require_message_authenticator;
273ff40c12SJohn Marino 	void *ctx;
283ff40c12SJohn Marino 	enum radius_das_res (*disconnect)(void *ctx,
293ff40c12SJohn Marino 					  struct radius_das_attrs *attr);
30*a1157835SDaniel Fojt 	enum radius_das_res (*coa)(void *ctx, struct radius_das_attrs *attr);
313ff40c12SJohn Marino };
323ff40c12SJohn Marino 
333ff40c12SJohn Marino 
radius_das_disconnect(struct radius_das_data * das,struct radius_msg * msg,const char * abuf,int from_port)343ff40c12SJohn Marino static struct radius_msg * radius_das_disconnect(struct radius_das_data *das,
353ff40c12SJohn Marino 						 struct radius_msg *msg,
363ff40c12SJohn Marino 						 const char *abuf,
373ff40c12SJohn Marino 						 int from_port)
383ff40c12SJohn Marino {
393ff40c12SJohn Marino 	struct radius_hdr *hdr;
403ff40c12SJohn Marino 	struct radius_msg *reply;
413ff40c12SJohn Marino 	u8 allowed[] = {
423ff40c12SJohn Marino 		RADIUS_ATTR_USER_NAME,
43*a1157835SDaniel Fojt 		RADIUS_ATTR_NAS_IP_ADDRESS,
443ff40c12SJohn Marino 		RADIUS_ATTR_CALLING_STATION_ID,
45*a1157835SDaniel Fojt 		RADIUS_ATTR_NAS_IDENTIFIER,
463ff40c12SJohn Marino 		RADIUS_ATTR_ACCT_SESSION_ID,
47*a1157835SDaniel Fojt 		RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
483ff40c12SJohn Marino 		RADIUS_ATTR_EVENT_TIMESTAMP,
493ff40c12SJohn Marino 		RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
503ff40c12SJohn Marino 		RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
51*a1157835SDaniel Fojt #ifdef CONFIG_IPV6
52*a1157835SDaniel Fojt 		RADIUS_ATTR_NAS_IPV6_ADDRESS,
53*a1157835SDaniel Fojt #endif /* CONFIG_IPV6 */
543ff40c12SJohn Marino 		0
553ff40c12SJohn Marino 	};
563ff40c12SJohn Marino 	int error = 405;
573ff40c12SJohn Marino 	u8 attr;
583ff40c12SJohn Marino 	enum radius_das_res res;
593ff40c12SJohn Marino 	struct radius_das_attrs attrs;
603ff40c12SJohn Marino 	u8 *buf;
613ff40c12SJohn Marino 	size_t len;
623ff40c12SJohn Marino 	char tmp[100];
633ff40c12SJohn Marino 	u8 sta_addr[ETH_ALEN];
643ff40c12SJohn Marino 
653ff40c12SJohn Marino 	hdr = radius_msg_get_hdr(msg);
663ff40c12SJohn Marino 
673ff40c12SJohn Marino 	attr = radius_msg_find_unlisted_attr(msg, allowed);
683ff40c12SJohn Marino 	if (attr) {
693ff40c12SJohn Marino 		wpa_printf(MSG_INFO, "DAS: Unsupported attribute %u in "
703ff40c12SJohn Marino 			   "Disconnect-Request from %s:%d", attr,
713ff40c12SJohn Marino 			   abuf, from_port);
723ff40c12SJohn Marino 		error = 401;
733ff40c12SJohn Marino 		goto fail;
743ff40c12SJohn Marino 	}
753ff40c12SJohn Marino 
763ff40c12SJohn Marino 	os_memset(&attrs, 0, sizeof(attrs));
773ff40c12SJohn Marino 
78*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
79*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
80*a1157835SDaniel Fojt 		if (len != 4) {
81*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO, "DAS: Invalid NAS-IP-Address from %s:%d",
82*a1157835SDaniel Fojt 				   abuf, from_port);
83*a1157835SDaniel Fojt 			error = 407;
84*a1157835SDaniel Fojt 			goto fail;
85*a1157835SDaniel Fojt 		}
86*a1157835SDaniel Fojt 		attrs.nas_ip_addr = buf;
87*a1157835SDaniel Fojt 	}
88*a1157835SDaniel Fojt 
89*a1157835SDaniel Fojt #ifdef CONFIG_IPV6
90*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
91*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
92*a1157835SDaniel Fojt 		if (len != 16) {
93*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO, "DAS: Invalid NAS-IPv6-Address from %s:%d",
94*a1157835SDaniel Fojt 				   abuf, from_port);
95*a1157835SDaniel Fojt 			error = 407;
96*a1157835SDaniel Fojt 			goto fail;
97*a1157835SDaniel Fojt 		}
98*a1157835SDaniel Fojt 		attrs.nas_ipv6_addr = buf;
99*a1157835SDaniel Fojt 	}
100*a1157835SDaniel Fojt #endif /* CONFIG_IPV6 */
101*a1157835SDaniel Fojt 
102*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
103*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
104*a1157835SDaniel Fojt 		attrs.nas_identifier = buf;
105*a1157835SDaniel Fojt 		attrs.nas_identifier_len = len;
106*a1157835SDaniel Fojt 	}
107*a1157835SDaniel Fojt 
1083ff40c12SJohn Marino 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID,
1093ff40c12SJohn Marino 				    &buf, &len, NULL) == 0) {
1103ff40c12SJohn Marino 		if (len >= sizeof(tmp))
1113ff40c12SJohn Marino 			len = sizeof(tmp) - 1;
1123ff40c12SJohn Marino 		os_memcpy(tmp, buf, len);
1133ff40c12SJohn Marino 		tmp[len] = '\0';
1143ff40c12SJohn Marino 		if (hwaddr_aton2(tmp, sta_addr) < 0) {
1153ff40c12SJohn Marino 			wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id "
1163ff40c12SJohn Marino 				   "'%s' from %s:%d", tmp, abuf, from_port);
1173ff40c12SJohn Marino 			error = 407;
1183ff40c12SJohn Marino 			goto fail;
1193ff40c12SJohn Marino 		}
1203ff40c12SJohn Marino 		attrs.sta_addr = sta_addr;
1213ff40c12SJohn Marino 	}
1223ff40c12SJohn Marino 
1233ff40c12SJohn Marino 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME,
1243ff40c12SJohn Marino 				    &buf, &len, NULL) == 0) {
1253ff40c12SJohn Marino 		attrs.user_name = buf;
1263ff40c12SJohn Marino 		attrs.user_name_len = len;
1273ff40c12SJohn Marino 	}
1283ff40c12SJohn Marino 
1293ff40c12SJohn Marino 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
1303ff40c12SJohn Marino 				    &buf, &len, NULL) == 0) {
1313ff40c12SJohn Marino 		attrs.acct_session_id = buf;
1323ff40c12SJohn Marino 		attrs.acct_session_id_len = len;
1333ff40c12SJohn Marino 	}
1343ff40c12SJohn Marino 
135*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
136*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
137*a1157835SDaniel Fojt 		attrs.acct_multi_session_id = buf;
138*a1157835SDaniel Fojt 		attrs.acct_multi_session_id_len = len;
139*a1157835SDaniel Fojt 	}
140*a1157835SDaniel Fojt 
1413ff40c12SJohn Marino 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
1423ff40c12SJohn Marino 				    &buf, &len, NULL) == 0) {
1433ff40c12SJohn Marino 		attrs.cui = buf;
1443ff40c12SJohn Marino 		attrs.cui_len = len;
1453ff40c12SJohn Marino 	}
1463ff40c12SJohn Marino 
1473ff40c12SJohn Marino 	res = das->disconnect(das->ctx, &attrs);
1483ff40c12SJohn Marino 	switch (res) {
1493ff40c12SJohn Marino 	case RADIUS_DAS_NAS_MISMATCH:
1503ff40c12SJohn Marino 		wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d",
1513ff40c12SJohn Marino 			   abuf, from_port);
1523ff40c12SJohn Marino 		error = 403;
1533ff40c12SJohn Marino 		break;
1543ff40c12SJohn Marino 	case RADIUS_DAS_SESSION_NOT_FOUND:
1553ff40c12SJohn Marino 		wpa_printf(MSG_INFO, "DAS: Session not found for request from "
1563ff40c12SJohn Marino 			   "%s:%d", abuf, from_port);
1573ff40c12SJohn Marino 		error = 503;
1583ff40c12SJohn Marino 		break;
159*a1157835SDaniel Fojt 	case RADIUS_DAS_MULTI_SESSION_MATCH:
160*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO,
161*a1157835SDaniel Fojt 			   "DAS: Multiple sessions match for request from %s:%d",
162*a1157835SDaniel Fojt 			   abuf, from_port);
163*a1157835SDaniel Fojt 		error = 508;
164*a1157835SDaniel Fojt 		break;
165*a1157835SDaniel Fojt 	case RADIUS_DAS_COA_FAILED:
166*a1157835SDaniel Fojt 		/* not used with Disconnect-Request */
167*a1157835SDaniel Fojt 		error = 405;
168*a1157835SDaniel Fojt 		break;
1693ff40c12SJohn Marino 	case RADIUS_DAS_SUCCESS:
1703ff40c12SJohn Marino 		error = 0;
1713ff40c12SJohn Marino 		break;
1723ff40c12SJohn Marino 	}
1733ff40c12SJohn Marino 
1743ff40c12SJohn Marino fail:
1753ff40c12SJohn Marino 	reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK :
1763ff40c12SJohn Marino 			       RADIUS_CODE_DISCONNECT_ACK, hdr->identifier);
1773ff40c12SJohn Marino 	if (reply == NULL)
1783ff40c12SJohn Marino 		return NULL;
1793ff40c12SJohn Marino 
1803ff40c12SJohn Marino 	if (error) {
1813ff40c12SJohn Marino 		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
1823ff40c12SJohn Marino 					       error)) {
1833ff40c12SJohn Marino 			radius_msg_free(reply);
1843ff40c12SJohn Marino 			return NULL;
1853ff40c12SJohn Marino 		}
1863ff40c12SJohn Marino 	}
1873ff40c12SJohn Marino 
1883ff40c12SJohn Marino 	return reply;
1893ff40c12SJohn Marino }
1903ff40c12SJohn Marino 
1913ff40c12SJohn Marino 
radius_das_coa(struct radius_das_data * das,struct radius_msg * msg,const char * abuf,int from_port)192*a1157835SDaniel Fojt static struct radius_msg * radius_das_coa(struct radius_das_data *das,
193*a1157835SDaniel Fojt 					  struct radius_msg *msg,
194*a1157835SDaniel Fojt 					  const char *abuf, int from_port)
195*a1157835SDaniel Fojt {
196*a1157835SDaniel Fojt 	struct radius_hdr *hdr;
197*a1157835SDaniel Fojt 	struct radius_msg *reply;
198*a1157835SDaniel Fojt 	u8 allowed[] = {
199*a1157835SDaniel Fojt 		RADIUS_ATTR_USER_NAME,
200*a1157835SDaniel Fojt 		RADIUS_ATTR_NAS_IP_ADDRESS,
201*a1157835SDaniel Fojt 		RADIUS_ATTR_CALLING_STATION_ID,
202*a1157835SDaniel Fojt 		RADIUS_ATTR_NAS_IDENTIFIER,
203*a1157835SDaniel Fojt 		RADIUS_ATTR_ACCT_SESSION_ID,
204*a1157835SDaniel Fojt 		RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
205*a1157835SDaniel Fojt 		RADIUS_ATTR_EVENT_TIMESTAMP,
206*a1157835SDaniel Fojt 		RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
207*a1157835SDaniel Fojt 		RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
208*a1157835SDaniel Fojt #ifdef CONFIG_HS20
209*a1157835SDaniel Fojt 		RADIUS_ATTR_VENDOR_SPECIFIC,
210*a1157835SDaniel Fojt #endif /* CONFIG_HS20 */
211*a1157835SDaniel Fojt #ifdef CONFIG_IPV6
212*a1157835SDaniel Fojt 		RADIUS_ATTR_NAS_IPV6_ADDRESS,
213*a1157835SDaniel Fojt #endif /* CONFIG_IPV6 */
214*a1157835SDaniel Fojt 		0
215*a1157835SDaniel Fojt 	};
216*a1157835SDaniel Fojt 	int error = 405;
217*a1157835SDaniel Fojt 	u8 attr;
218*a1157835SDaniel Fojt 	enum radius_das_res res;
219*a1157835SDaniel Fojt 	struct radius_das_attrs attrs;
220*a1157835SDaniel Fojt 	u8 *buf;
221*a1157835SDaniel Fojt 	size_t len;
222*a1157835SDaniel Fojt 	char tmp[100];
223*a1157835SDaniel Fojt 	u8 sta_addr[ETH_ALEN];
224*a1157835SDaniel Fojt 
225*a1157835SDaniel Fojt 	hdr = radius_msg_get_hdr(msg);
226*a1157835SDaniel Fojt 
227*a1157835SDaniel Fojt 	if (!das->coa) {
228*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO, "DAS: CoA not supported");
229*a1157835SDaniel Fojt 		goto fail;
230*a1157835SDaniel Fojt 	}
231*a1157835SDaniel Fojt 
232*a1157835SDaniel Fojt 	attr = radius_msg_find_unlisted_attr(msg, allowed);
233*a1157835SDaniel Fojt 	if (attr) {
234*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO,
235*a1157835SDaniel Fojt 			   "DAS: Unsupported attribute %u in CoA-Request from %s:%d",
236*a1157835SDaniel Fojt 			   attr, abuf, from_port);
237*a1157835SDaniel Fojt 		error = 401;
238*a1157835SDaniel Fojt 		goto fail;
239*a1157835SDaniel Fojt 	}
240*a1157835SDaniel Fojt 
241*a1157835SDaniel Fojt 	os_memset(&attrs, 0, sizeof(attrs));
242*a1157835SDaniel Fojt 
243*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
244*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
245*a1157835SDaniel Fojt 		if (len != 4) {
246*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO, "DAS: Invalid NAS-IP-Address from %s:%d",
247*a1157835SDaniel Fojt 				   abuf, from_port);
248*a1157835SDaniel Fojt 			error = 407;
249*a1157835SDaniel Fojt 			goto fail;
250*a1157835SDaniel Fojt 		}
251*a1157835SDaniel Fojt 		attrs.nas_ip_addr = buf;
252*a1157835SDaniel Fojt 	}
253*a1157835SDaniel Fojt 
254*a1157835SDaniel Fojt #ifdef CONFIG_IPV6
255*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
256*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
257*a1157835SDaniel Fojt 		if (len != 16) {
258*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO, "DAS: Invalid NAS-IPv6-Address from %s:%d",
259*a1157835SDaniel Fojt 				   abuf, from_port);
260*a1157835SDaniel Fojt 			error = 407;
261*a1157835SDaniel Fojt 			goto fail;
262*a1157835SDaniel Fojt 		}
263*a1157835SDaniel Fojt 		attrs.nas_ipv6_addr = buf;
264*a1157835SDaniel Fojt 	}
265*a1157835SDaniel Fojt #endif /* CONFIG_IPV6 */
266*a1157835SDaniel Fojt 
267*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
268*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
269*a1157835SDaniel Fojt 		attrs.nas_identifier = buf;
270*a1157835SDaniel Fojt 		attrs.nas_identifier_len = len;
271*a1157835SDaniel Fojt 	}
272*a1157835SDaniel Fojt 
273*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID,
274*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
275*a1157835SDaniel Fojt 		if (len >= sizeof(tmp))
276*a1157835SDaniel Fojt 			len = sizeof(tmp) - 1;
277*a1157835SDaniel Fojt 		os_memcpy(tmp, buf, len);
278*a1157835SDaniel Fojt 		tmp[len] = '\0';
279*a1157835SDaniel Fojt 		if (hwaddr_aton2(tmp, sta_addr) < 0) {
280*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id "
281*a1157835SDaniel Fojt 				   "'%s' from %s:%d", tmp, abuf, from_port);
282*a1157835SDaniel Fojt 			error = 407;
283*a1157835SDaniel Fojt 			goto fail;
284*a1157835SDaniel Fojt 		}
285*a1157835SDaniel Fojt 		attrs.sta_addr = sta_addr;
286*a1157835SDaniel Fojt 	}
287*a1157835SDaniel Fojt 
288*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME,
289*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
290*a1157835SDaniel Fojt 		attrs.user_name = buf;
291*a1157835SDaniel Fojt 		attrs.user_name_len = len;
292*a1157835SDaniel Fojt 	}
293*a1157835SDaniel Fojt 
294*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
295*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
296*a1157835SDaniel Fojt 		attrs.acct_session_id = buf;
297*a1157835SDaniel Fojt 		attrs.acct_session_id_len = len;
298*a1157835SDaniel Fojt 	}
299*a1157835SDaniel Fojt 
300*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
301*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
302*a1157835SDaniel Fojt 		attrs.acct_multi_session_id = buf;
303*a1157835SDaniel Fojt 		attrs.acct_multi_session_id_len = len;
304*a1157835SDaniel Fojt 	}
305*a1157835SDaniel Fojt 
306*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
307*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
308*a1157835SDaniel Fojt 		attrs.cui = buf;
309*a1157835SDaniel Fojt 		attrs.cui_len = len;
310*a1157835SDaniel Fojt 	}
311*a1157835SDaniel Fojt 
312*a1157835SDaniel Fojt #ifdef CONFIG_HS20
313*a1157835SDaniel Fojt 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
314*a1157835SDaniel Fojt 				    &buf, &len, NULL) == 0) {
315*a1157835SDaniel Fojt 		if (len < 10 || WPA_GET_BE32(buf) != RADIUS_VENDOR_ID_WFA ||
316*a1157835SDaniel Fojt 		    buf[4] != RADIUS_VENDOR_ATTR_WFA_HS20_T_C_FILTERING ||
317*a1157835SDaniel Fojt 		    buf[5] < 6) {
318*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO,
319*a1157835SDaniel Fojt 				   "DAS: Unsupported attribute %u in CoA-Request from %s:%d",
320*a1157835SDaniel Fojt 				   attr, abuf, from_port);
321*a1157835SDaniel Fojt 			error = 401;
322*a1157835SDaniel Fojt 			goto fail;
323*a1157835SDaniel Fojt 		}
324*a1157835SDaniel Fojt 		attrs.hs20_t_c_filtering = &buf[6];
325*a1157835SDaniel Fojt 	}
326*a1157835SDaniel Fojt 
327*a1157835SDaniel Fojt 	if (!attrs.hs20_t_c_filtering) {
328*a1157835SDaniel Fojt 			wpa_printf(MSG_INFO,
329*a1157835SDaniel Fojt 				   "DAS: No supported authorization change attribute in CoA-Request from %s:%d",
330*a1157835SDaniel Fojt 				   abuf, from_port);
331*a1157835SDaniel Fojt 			error = 402;
332*a1157835SDaniel Fojt 			goto fail;
333*a1157835SDaniel Fojt 	}
334*a1157835SDaniel Fojt #endif /* CONFIG_HS20 */
335*a1157835SDaniel Fojt 
336*a1157835SDaniel Fojt 	res = das->coa(das->ctx, &attrs);
337*a1157835SDaniel Fojt 	switch (res) {
338*a1157835SDaniel Fojt 	case RADIUS_DAS_NAS_MISMATCH:
339*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d",
340*a1157835SDaniel Fojt 			   abuf, from_port);
341*a1157835SDaniel Fojt 		error = 403;
342*a1157835SDaniel Fojt 		break;
343*a1157835SDaniel Fojt 	case RADIUS_DAS_SESSION_NOT_FOUND:
344*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO,
345*a1157835SDaniel Fojt 			   "DAS: Session not found for request from %s:%d",
346*a1157835SDaniel Fojt 			   abuf, from_port);
347*a1157835SDaniel Fojt 		error = 503;
348*a1157835SDaniel Fojt 		break;
349*a1157835SDaniel Fojt 	case RADIUS_DAS_MULTI_SESSION_MATCH:
350*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO,
351*a1157835SDaniel Fojt 			   "DAS: Multiple sessions match for request from %s:%d",
352*a1157835SDaniel Fojt 			   abuf, from_port);
353*a1157835SDaniel Fojt 		error = 508;
354*a1157835SDaniel Fojt 		break;
355*a1157835SDaniel Fojt 	case RADIUS_DAS_COA_FAILED:
356*a1157835SDaniel Fojt 		wpa_printf(MSG_INFO, "DAS: CoA failed for request from %s:%d",
357*a1157835SDaniel Fojt 			   abuf, from_port);
358*a1157835SDaniel Fojt 		error = 407;
359*a1157835SDaniel Fojt 		break;
360*a1157835SDaniel Fojt 	case RADIUS_DAS_SUCCESS:
361*a1157835SDaniel Fojt 		error = 0;
362*a1157835SDaniel Fojt 		break;
363*a1157835SDaniel Fojt 	}
364*a1157835SDaniel Fojt 
365*a1157835SDaniel Fojt fail:
366*a1157835SDaniel Fojt 	reply = radius_msg_new(error ? RADIUS_CODE_COA_NAK :
367*a1157835SDaniel Fojt 			       RADIUS_CODE_COA_ACK, hdr->identifier);
368*a1157835SDaniel Fojt 	if (!reply)
369*a1157835SDaniel Fojt 		return NULL;
370*a1157835SDaniel Fojt 
371*a1157835SDaniel Fojt 	if (error &&
372*a1157835SDaniel Fojt 	    !radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, error)) {
373*a1157835SDaniel Fojt 		radius_msg_free(reply);
374*a1157835SDaniel Fojt 		return NULL;
375*a1157835SDaniel Fojt 	}
376*a1157835SDaniel Fojt 
377*a1157835SDaniel Fojt 	return reply;
378*a1157835SDaniel Fojt }
379*a1157835SDaniel Fojt 
380*a1157835SDaniel Fojt 
radius_das_receive(int sock,void * eloop_ctx,void * sock_ctx)3813ff40c12SJohn Marino static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
3823ff40c12SJohn Marino {
3833ff40c12SJohn Marino 	struct radius_das_data *das = eloop_ctx;
3843ff40c12SJohn Marino 	u8 buf[1500];
3853ff40c12SJohn Marino 	union {
3863ff40c12SJohn Marino 		struct sockaddr_storage ss;
3873ff40c12SJohn Marino 		struct sockaddr_in sin;
3883ff40c12SJohn Marino #ifdef CONFIG_IPV6
3893ff40c12SJohn Marino 		struct sockaddr_in6 sin6;
3903ff40c12SJohn Marino #endif /* CONFIG_IPV6 */
3913ff40c12SJohn Marino 	} from;
3923ff40c12SJohn Marino 	char abuf[50];
3933ff40c12SJohn Marino 	int from_port = 0;
3943ff40c12SJohn Marino 	socklen_t fromlen;
3953ff40c12SJohn Marino 	int len;
3963ff40c12SJohn Marino 	struct radius_msg *msg, *reply = NULL;
3973ff40c12SJohn Marino 	struct radius_hdr *hdr;
3983ff40c12SJohn Marino 	struct wpabuf *rbuf;
3993ff40c12SJohn Marino 	u32 val;
4003ff40c12SJohn Marino 	int res;
4013ff40c12SJohn Marino 	struct os_time now;
4023ff40c12SJohn Marino 
4033ff40c12SJohn Marino 	fromlen = sizeof(from);
4043ff40c12SJohn Marino 	len = recvfrom(sock, buf, sizeof(buf), 0,
4053ff40c12SJohn Marino 		       (struct sockaddr *) &from.ss, &fromlen);
4063ff40c12SJohn Marino 	if (len < 0) {
4073ff40c12SJohn Marino 		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
4083ff40c12SJohn Marino 		return;
4093ff40c12SJohn Marino 	}
4103ff40c12SJohn Marino 
4113ff40c12SJohn Marino 	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
4123ff40c12SJohn Marino 	from_port = ntohs(from.sin.sin_port);
4133ff40c12SJohn Marino 
4143ff40c12SJohn Marino 	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
4153ff40c12SJohn Marino 		   len, abuf, from_port);
416*a1157835SDaniel Fojt 	if (das->client_addr.u.v4.s_addr &&
417*a1157835SDaniel Fojt 	    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
4183ff40c12SJohn Marino 		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
4193ff40c12SJohn Marino 		return;
4203ff40c12SJohn Marino 	}
4213ff40c12SJohn Marino 
4223ff40c12SJohn Marino 	msg = radius_msg_parse(buf, len);
4233ff40c12SJohn Marino 	if (msg == NULL) {
4243ff40c12SJohn Marino 		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
4253ff40c12SJohn Marino 			   "from %s:%d failed", abuf, from_port);
4263ff40c12SJohn Marino 		return;
4273ff40c12SJohn Marino 	}
4283ff40c12SJohn Marino 
4293ff40c12SJohn Marino 	if (wpa_debug_level <= MSG_MSGDUMP)
4303ff40c12SJohn Marino 		radius_msg_dump(msg);
4313ff40c12SJohn Marino 
4323ff40c12SJohn Marino 	if (radius_msg_verify_das_req(msg, das->shared_secret,
433*a1157835SDaniel Fojt 				       das->shared_secret_len,
434*a1157835SDaniel Fojt 				       das->require_message_authenticator)) {
435*a1157835SDaniel Fojt 		wpa_printf(MSG_DEBUG,
436*a1157835SDaniel Fojt 			   "DAS: Invalid authenticator or Message-Authenticator in packet from %s:%d - drop",
437*a1157835SDaniel Fojt 			   abuf, from_port);
4383ff40c12SJohn Marino 		goto fail;
4393ff40c12SJohn Marino 	}
4403ff40c12SJohn Marino 
4413ff40c12SJohn Marino 	os_get_time(&now);
4423ff40c12SJohn Marino 	res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
4433ff40c12SJohn Marino 				  (u8 *) &val, 4);
4443ff40c12SJohn Marino 	if (res == 4) {
4453ff40c12SJohn Marino 		u32 timestamp = ntohl(val);
446*a1157835SDaniel Fojt 		if ((unsigned int) abs((int) (now.sec - timestamp)) >
4473ff40c12SJohn Marino 		    das->time_window) {
4483ff40c12SJohn Marino 			wpa_printf(MSG_DEBUG, "DAS: Unacceptable "
4493ff40c12SJohn Marino 				   "Event-Timestamp (%u; local time %u) in "
4503ff40c12SJohn Marino 				   "packet from %s:%d - drop",
4513ff40c12SJohn Marino 				   timestamp, (unsigned int) now.sec,
4523ff40c12SJohn Marino 				   abuf, from_port);
4533ff40c12SJohn Marino 			goto fail;
4543ff40c12SJohn Marino 		}
4553ff40c12SJohn Marino 	} else if (das->require_event_timestamp) {
4563ff40c12SJohn Marino 		wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet "
4573ff40c12SJohn Marino 			   "from %s:%d - drop", abuf, from_port);
4583ff40c12SJohn Marino 		goto fail;
4593ff40c12SJohn Marino 	}
4603ff40c12SJohn Marino 
4613ff40c12SJohn Marino 	hdr = radius_msg_get_hdr(msg);
4623ff40c12SJohn Marino 
4633ff40c12SJohn Marino 	switch (hdr->code) {
4643ff40c12SJohn Marino 	case RADIUS_CODE_DISCONNECT_REQUEST:
4653ff40c12SJohn Marino 		reply = radius_das_disconnect(das, msg, abuf, from_port);
4663ff40c12SJohn Marino 		break;
4673ff40c12SJohn Marino 	case RADIUS_CODE_COA_REQUEST:
468*a1157835SDaniel Fojt 		reply = radius_das_coa(das, msg, abuf, from_port);
4693ff40c12SJohn Marino 		break;
4703ff40c12SJohn Marino 	default:
4713ff40c12SJohn Marino 		wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
4723ff40c12SJohn Marino 			   "packet from %s:%d",
4733ff40c12SJohn Marino 			   hdr->code, abuf, from_port);
4743ff40c12SJohn Marino 	}
4753ff40c12SJohn Marino 
4763ff40c12SJohn Marino 	if (reply) {
4773ff40c12SJohn Marino 		wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
4783ff40c12SJohn Marino 
4793ff40c12SJohn Marino 		if (!radius_msg_add_attr_int32(reply,
4803ff40c12SJohn Marino 					       RADIUS_ATTR_EVENT_TIMESTAMP,
4813ff40c12SJohn Marino 					       now.sec)) {
4823ff40c12SJohn Marino 			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
4833ff40c12SJohn Marino 				   "Event-Timestamp attribute");
4843ff40c12SJohn Marino 		}
4853ff40c12SJohn Marino 
4863ff40c12SJohn Marino 		if (radius_msg_finish_das_resp(reply, das->shared_secret,
4873ff40c12SJohn Marino 					       das->shared_secret_len, hdr) <
4883ff40c12SJohn Marino 		    0) {
4893ff40c12SJohn Marino 			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
4903ff40c12SJohn Marino 				   "Message-Authenticator attribute");
4913ff40c12SJohn Marino 		}
4923ff40c12SJohn Marino 
4933ff40c12SJohn Marino 		if (wpa_debug_level <= MSG_MSGDUMP)
4943ff40c12SJohn Marino 			radius_msg_dump(reply);
4953ff40c12SJohn Marino 
4963ff40c12SJohn Marino 		rbuf = radius_msg_get_buf(reply);
4973ff40c12SJohn Marino 		res = sendto(das->sock, wpabuf_head(rbuf),
4983ff40c12SJohn Marino 			     wpabuf_len(rbuf), 0,
4993ff40c12SJohn Marino 			     (struct sockaddr *) &from.ss, fromlen);
5003ff40c12SJohn Marino 		if (res < 0) {
5013ff40c12SJohn Marino 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
5023ff40c12SJohn Marino 				   abuf, from_port, strerror(errno));
5033ff40c12SJohn Marino 		}
5043ff40c12SJohn Marino 	}
5053ff40c12SJohn Marino 
5063ff40c12SJohn Marino fail:
5073ff40c12SJohn Marino 	radius_msg_free(msg);
5083ff40c12SJohn Marino 	radius_msg_free(reply);
5093ff40c12SJohn Marino }
5103ff40c12SJohn Marino 
5113ff40c12SJohn Marino 
radius_das_open_socket(int port)5123ff40c12SJohn Marino static int radius_das_open_socket(int port)
5133ff40c12SJohn Marino {
5143ff40c12SJohn Marino 	int s;
5153ff40c12SJohn Marino 	struct sockaddr_in addr;
5163ff40c12SJohn Marino 
5173ff40c12SJohn Marino 	s = socket(PF_INET, SOCK_DGRAM, 0);
5183ff40c12SJohn Marino 	if (s < 0) {
5193ff40c12SJohn Marino 		wpa_printf(MSG_INFO, "RADIUS DAS: socket: %s", strerror(errno));
5203ff40c12SJohn Marino 		return -1;
5213ff40c12SJohn Marino 	}
5223ff40c12SJohn Marino 
5233ff40c12SJohn Marino 	os_memset(&addr, 0, sizeof(addr));
5243ff40c12SJohn Marino 	addr.sin_family = AF_INET;
5253ff40c12SJohn Marino 	addr.sin_port = htons(port);
5263ff40c12SJohn Marino 	if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
5273ff40c12SJohn Marino 		wpa_printf(MSG_INFO, "RADIUS DAS: bind: %s", strerror(errno));
5283ff40c12SJohn Marino 		close(s);
5293ff40c12SJohn Marino 		return -1;
5303ff40c12SJohn Marino 	}
5313ff40c12SJohn Marino 
5323ff40c12SJohn Marino 	return s;
5333ff40c12SJohn Marino }
5343ff40c12SJohn Marino 
5353ff40c12SJohn Marino 
5363ff40c12SJohn Marino struct radius_das_data *
radius_das_init(struct radius_das_conf * conf)5373ff40c12SJohn Marino radius_das_init(struct radius_das_conf *conf)
5383ff40c12SJohn Marino {
5393ff40c12SJohn Marino 	struct radius_das_data *das;
5403ff40c12SJohn Marino 
5413ff40c12SJohn Marino 	if (conf->port == 0 || conf->shared_secret == NULL ||
5423ff40c12SJohn Marino 	    conf->client_addr == NULL)
5433ff40c12SJohn Marino 		return NULL;
5443ff40c12SJohn Marino 
5453ff40c12SJohn Marino 	das = os_zalloc(sizeof(*das));
5463ff40c12SJohn Marino 	if (das == NULL)
5473ff40c12SJohn Marino 		return NULL;
5483ff40c12SJohn Marino 
5493ff40c12SJohn Marino 	das->time_window = conf->time_window;
5503ff40c12SJohn Marino 	das->require_event_timestamp = conf->require_event_timestamp;
551*a1157835SDaniel Fojt 	das->require_message_authenticator =
552*a1157835SDaniel Fojt 		conf->require_message_authenticator;
5533ff40c12SJohn Marino 	das->ctx = conf->ctx;
5543ff40c12SJohn Marino 	das->disconnect = conf->disconnect;
555*a1157835SDaniel Fojt 	das->coa = conf->coa;
5563ff40c12SJohn Marino 
5573ff40c12SJohn Marino 	os_memcpy(&das->client_addr, conf->client_addr,
5583ff40c12SJohn Marino 		  sizeof(das->client_addr));
5593ff40c12SJohn Marino 
560*a1157835SDaniel Fojt 	das->shared_secret = os_memdup(conf->shared_secret,
561*a1157835SDaniel Fojt 				       conf->shared_secret_len);
5623ff40c12SJohn Marino 	if (das->shared_secret == NULL) {
5633ff40c12SJohn Marino 		radius_das_deinit(das);
5643ff40c12SJohn Marino 		return NULL;
5653ff40c12SJohn Marino 	}
5663ff40c12SJohn Marino 	das->shared_secret_len = conf->shared_secret_len;
5673ff40c12SJohn Marino 
5683ff40c12SJohn Marino 	das->sock = radius_das_open_socket(conf->port);
5693ff40c12SJohn Marino 	if (das->sock < 0) {
5703ff40c12SJohn Marino 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
5713ff40c12SJohn Marino 			   "DAS");
5723ff40c12SJohn Marino 		radius_das_deinit(das);
5733ff40c12SJohn Marino 		return NULL;
5743ff40c12SJohn Marino 	}
5753ff40c12SJohn Marino 
5763ff40c12SJohn Marino 	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
5773ff40c12SJohn Marino 	{
5783ff40c12SJohn Marino 		radius_das_deinit(das);
5793ff40c12SJohn Marino 		return NULL;
5803ff40c12SJohn Marino 	}
5813ff40c12SJohn Marino 
5823ff40c12SJohn Marino 	return das;
5833ff40c12SJohn Marino }
5843ff40c12SJohn Marino 
5853ff40c12SJohn Marino 
radius_das_deinit(struct radius_das_data * das)5863ff40c12SJohn Marino void radius_das_deinit(struct radius_das_data *das)
5873ff40c12SJohn Marino {
5883ff40c12SJohn Marino 	if (das == NULL)
5893ff40c12SJohn Marino 		return;
5903ff40c12SJohn Marino 
5913ff40c12SJohn Marino 	if (das->sock >= 0) {
5923ff40c12SJohn Marino 		eloop_unregister_read_sock(das->sock);
5933ff40c12SJohn Marino 		close(das->sock);
5943ff40c12SJohn Marino 	}
5953ff40c12SJohn Marino 
5963ff40c12SJohn Marino 	os_free(das->shared_secret);
5973ff40c12SJohn Marino 	os_free(das);
5983ff40c12SJohn Marino }
599