1/*
2 * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
3 * Copyright (c) 2012, Jouni Malinen <j@w1.fi>
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#include "includes.h"
10#include <net/if.h>
11
12#include "utils/common.h"
13#include "utils/eloop.h"
14#include "utils/ip_addr.h"
15#include "radius.h"
16#include "radius_das.h"
17
18
19extern int wpa_debug_level;
20
21
22struct radius_das_data {
23	int sock;
24	u8 *shared_secret;
25	size_t shared_secret_len;
26	struct hostapd_ip_addr client_addr;
27	unsigned int time_window;
28	int require_event_timestamp;
29	void *ctx;
30	enum radius_das_res (*disconnect)(void *ctx,
31					  struct radius_das_attrs *attr);
32};
33
34
35static struct radius_msg * radius_das_disconnect(struct radius_das_data *das,
36						 struct radius_msg *msg,
37						 const char *abuf,
38						 int from_port)
39{
40	struct radius_hdr *hdr;
41	struct radius_msg *reply;
42	u8 allowed[] = {
43		RADIUS_ATTR_USER_NAME,
44		RADIUS_ATTR_CALLING_STATION_ID,
45		RADIUS_ATTR_ACCT_SESSION_ID,
46		RADIUS_ATTR_EVENT_TIMESTAMP,
47		RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
48		RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
49		0
50	};
51	int error = 405;
52	u8 attr;
53	enum radius_das_res res;
54	struct radius_das_attrs attrs;
55	u8 *buf;
56	size_t len;
57	char tmp[100];
58	u8 sta_addr[ETH_ALEN];
59
60	hdr = radius_msg_get_hdr(msg);
61
62	attr = radius_msg_find_unlisted_attr(msg, allowed);
63	if (attr) {
64		wpa_printf(MSG_INFO, "DAS: Unsupported attribute %u in "
65			   "Disconnect-Request from %s:%d", attr,
66			   abuf, from_port);
67		error = 401;
68		goto fail;
69	}
70
71	os_memset(&attrs, 0, sizeof(attrs));
72
73	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID,
74				    &buf, &len, NULL) == 0) {
75		if (len >= sizeof(tmp))
76			len = sizeof(tmp) - 1;
77		os_memcpy(tmp, buf, len);
78		tmp[len] = '\0';
79		if (hwaddr_aton2(tmp, sta_addr) < 0) {
80			wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id "
81				   "'%s' from %s:%d", tmp, abuf, from_port);
82			error = 407;
83			goto fail;
84		}
85		attrs.sta_addr = sta_addr;
86	}
87
88	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME,
89				    &buf, &len, NULL) == 0) {
90		attrs.user_name = buf;
91		attrs.user_name_len = len;
92	}
93
94	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
95				    &buf, &len, NULL) == 0) {
96		attrs.acct_session_id = buf;
97		attrs.acct_session_id_len = len;
98	}
99
100	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
101				    &buf, &len, NULL) == 0) {
102		attrs.cui = buf;
103		attrs.cui_len = len;
104	}
105
106	res = das->disconnect(das->ctx, &attrs);
107	switch (res) {
108	case RADIUS_DAS_NAS_MISMATCH:
109		wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d",
110			   abuf, from_port);
111		error = 403;
112		break;
113	case RADIUS_DAS_SESSION_NOT_FOUND:
114		wpa_printf(MSG_INFO, "DAS: Session not found for request from "
115			   "%s:%d", abuf, from_port);
116		error = 503;
117		break;
118	case RADIUS_DAS_SUCCESS:
119		error = 0;
120		break;
121	}
122
123fail:
124	reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK :
125			       RADIUS_CODE_DISCONNECT_ACK, hdr->identifier);
126	if (reply == NULL)
127		return NULL;
128
129	if (error) {
130		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
131					       error)) {
132			radius_msg_free(reply);
133			return NULL;
134		}
135	}
136
137	return reply;
138}
139
140
141static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
142{
143	struct radius_das_data *das = eloop_ctx;
144	u8 buf[1500];
145	union {
146		struct sockaddr_storage ss;
147		struct sockaddr_in sin;
148#ifdef CONFIG_IPV6
149		struct sockaddr_in6 sin6;
150#endif /* CONFIG_IPV6 */
151	} from;
152	char abuf[50];
153	int from_port = 0;
154	socklen_t fromlen;
155	int len;
156	struct radius_msg *msg, *reply = NULL;
157	struct radius_hdr *hdr;
158	struct wpabuf *rbuf;
159	u32 val;
160	int res;
161	struct os_time now;
162
163	fromlen = sizeof(from);
164	len = recvfrom(sock, buf, sizeof(buf), 0,
165		       (struct sockaddr *) &from.ss, &fromlen);
166	if (len < 0) {
167		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
168		return;
169	}
170
171	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
172	from_port = ntohs(from.sin.sin_port);
173
174	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
175		   len, abuf, from_port);
176	if (das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
177		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
178		return;
179	}
180
181	msg = radius_msg_parse(buf, len);
182	if (msg == NULL) {
183		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
184			   "from %s:%d failed", abuf, from_port);
185		return;
186	}
187
188	if (wpa_debug_level <= MSG_MSGDUMP)
189		radius_msg_dump(msg);
190
191	if (radius_msg_verify_das_req(msg, das->shared_secret,
192				       das->shared_secret_len)) {
193		wpa_printf(MSG_DEBUG, "DAS: Invalid authenticator in packet "
194			   "from %s:%d - drop", abuf, from_port);
195		goto fail;
196	}
197
198	os_get_time(&now);
199	res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
200				  (u8 *) &val, 4);
201	if (res == 4) {
202		u32 timestamp = ntohl(val);
203		if (abs(now.sec - timestamp) > das->time_window) {
204			wpa_printf(MSG_DEBUG, "DAS: Unacceptable "
205				   "Event-Timestamp (%u; local time %u) in "
206				   "packet from %s:%d - drop",
207				   timestamp, (unsigned int) now.sec,
208				   abuf, from_port);
209			goto fail;
210		}
211	} else if (das->require_event_timestamp) {
212		wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet "
213			   "from %s:%d - drop", abuf, from_port);
214		goto fail;
215	}
216
217	hdr = radius_msg_get_hdr(msg);
218
219	switch (hdr->code) {
220	case RADIUS_CODE_DISCONNECT_REQUEST:
221		reply = radius_das_disconnect(das, msg, abuf, from_port);
222		break;
223	case RADIUS_CODE_COA_REQUEST:
224		/* TODO */
225		reply = radius_msg_new(RADIUS_CODE_COA_NAK,
226				       hdr->identifier);
227		if (reply == NULL)
228			break;
229
230		/* Unsupported Service */
231		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
232					       405)) {
233			radius_msg_free(reply);
234			reply = NULL;
235			break;
236		}
237		break;
238	default:
239		wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
240			   "packet from %s:%d",
241			   hdr->code, abuf, from_port);
242	}
243
244	if (reply) {
245		wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
246
247		if (!radius_msg_add_attr_int32(reply,
248					       RADIUS_ATTR_EVENT_TIMESTAMP,
249					       now.sec)) {
250			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
251				   "Event-Timestamp attribute");
252		}
253
254		if (radius_msg_finish_das_resp(reply, das->shared_secret,
255					       das->shared_secret_len, hdr) <
256		    0) {
257			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
258				   "Message-Authenticator attribute");
259		}
260
261		if (wpa_debug_level <= MSG_MSGDUMP)
262			radius_msg_dump(reply);
263
264		rbuf = radius_msg_get_buf(reply);
265		res = sendto(das->sock, wpabuf_head(rbuf),
266			     wpabuf_len(rbuf), 0,
267			     (struct sockaddr *) &from.ss, fromlen);
268		if (res < 0) {
269			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
270				   abuf, from_port, strerror(errno));
271		}
272	}
273
274fail:
275	radius_msg_free(msg);
276	radius_msg_free(reply);
277}
278
279
280static int radius_das_open_socket(int port)
281{
282	int s;
283	struct sockaddr_in addr;
284
285	s = socket(PF_INET, SOCK_DGRAM, 0);
286	if (s < 0) {
287		perror("socket");
288		return -1;
289	}
290
291	os_memset(&addr, 0, sizeof(addr));
292	addr.sin_family = AF_INET;
293	addr.sin_port = htons(port);
294	if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
295		perror("bind");
296		close(s);
297		return -1;
298	}
299
300	return s;
301}
302
303
304struct radius_das_data *
305radius_das_init(struct radius_das_conf *conf)
306{
307	struct radius_das_data *das;
308
309	if (conf->port == 0 || conf->shared_secret == NULL ||
310	    conf->client_addr == NULL)
311		return NULL;
312
313	das = os_zalloc(sizeof(*das));
314	if (das == NULL)
315		return NULL;
316
317	das->time_window = conf->time_window;
318	das->require_event_timestamp = conf->require_event_timestamp;
319	das->ctx = conf->ctx;
320	das->disconnect = conf->disconnect;
321
322	os_memcpy(&das->client_addr, conf->client_addr,
323		  sizeof(das->client_addr));
324
325	das->shared_secret = os_malloc(conf->shared_secret_len);
326	if (das->shared_secret == NULL) {
327		radius_das_deinit(das);
328		return NULL;
329	}
330	os_memcpy(das->shared_secret, conf->shared_secret,
331		  conf->shared_secret_len);
332	das->shared_secret_len = conf->shared_secret_len;
333
334	das->sock = radius_das_open_socket(conf->port);
335	if (das->sock < 0) {
336		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
337			   "DAS");
338		radius_das_deinit(das);
339		return NULL;
340	}
341
342	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
343	{
344		radius_das_deinit(das);
345		return NULL;
346	}
347
348	return das;
349}
350
351
352void radius_das_deinit(struct radius_das_data *das)
353{
354	if (das == NULL)
355		return;
356
357	if (das->sock >= 0) {
358		eloop_unregister_read_sock(das->sock);
359		close(das->sock);
360	}
361
362	os_free(das->shared_secret);
363	os_free(das);
364}
365