radius_das.c revision 5460547a121207cf7a99eac45e05fcdd83be3161
1/*
2 * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
3 * Copyright (c) 2012-2013, 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 ((unsigned int) abs(now.sec - timestamp) >
204		    das->time_window) {
205			wpa_printf(MSG_DEBUG, "DAS: Unacceptable "
206				   "Event-Timestamp (%u; local time %u) in "
207				   "packet from %s:%d - drop",
208				   timestamp, (unsigned int) now.sec,
209				   abuf, from_port);
210			goto fail;
211		}
212	} else if (das->require_event_timestamp) {
213		wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet "
214			   "from %s:%d - drop", abuf, from_port);
215		goto fail;
216	}
217
218	hdr = radius_msg_get_hdr(msg);
219
220	switch (hdr->code) {
221	case RADIUS_CODE_DISCONNECT_REQUEST:
222		reply = radius_das_disconnect(das, msg, abuf, from_port);
223		break;
224	case RADIUS_CODE_COA_REQUEST:
225		/* TODO */
226		reply = radius_msg_new(RADIUS_CODE_COA_NAK,
227				       hdr->identifier);
228		if (reply == NULL)
229			break;
230
231		/* Unsupported Service */
232		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
233					       405)) {
234			radius_msg_free(reply);
235			reply = NULL;
236			break;
237		}
238		break;
239	default:
240		wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
241			   "packet from %s:%d",
242			   hdr->code, abuf, from_port);
243	}
244
245	if (reply) {
246		wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
247
248		if (!radius_msg_add_attr_int32(reply,
249					       RADIUS_ATTR_EVENT_TIMESTAMP,
250					       now.sec)) {
251			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
252				   "Event-Timestamp attribute");
253		}
254
255		if (radius_msg_finish_das_resp(reply, das->shared_secret,
256					       das->shared_secret_len, hdr) <
257		    0) {
258			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
259				   "Message-Authenticator attribute");
260		}
261
262		if (wpa_debug_level <= MSG_MSGDUMP)
263			radius_msg_dump(reply);
264
265		rbuf = radius_msg_get_buf(reply);
266		res = sendto(das->sock, wpabuf_head(rbuf),
267			     wpabuf_len(rbuf), 0,
268			     (struct sockaddr *) &from.ss, fromlen);
269		if (res < 0) {
270			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
271				   abuf, from_port, strerror(errno));
272		}
273	}
274
275fail:
276	radius_msg_free(msg);
277	radius_msg_free(reply);
278}
279
280
281static int radius_das_open_socket(int port)
282{
283	int s;
284	struct sockaddr_in addr;
285
286	s = socket(PF_INET, SOCK_DGRAM, 0);
287	if (s < 0) {
288		wpa_printf(MSG_INFO, "RADIUS DAS: socket: %s", strerror(errno));
289		return -1;
290	}
291
292	os_memset(&addr, 0, sizeof(addr));
293	addr.sin_family = AF_INET;
294	addr.sin_port = htons(port);
295	if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
296		wpa_printf(MSG_INFO, "RADIUS DAS: bind: %s", strerror(errno));
297		close(s);
298		return -1;
299	}
300
301	return s;
302}
303
304
305struct radius_das_data *
306radius_das_init(struct radius_das_conf *conf)
307{
308	struct radius_das_data *das;
309
310	if (conf->port == 0 || conf->shared_secret == NULL ||
311	    conf->client_addr == NULL)
312		return NULL;
313
314	das = os_zalloc(sizeof(*das));
315	if (das == NULL)
316		return NULL;
317
318	das->time_window = conf->time_window;
319	das->require_event_timestamp = conf->require_event_timestamp;
320	das->ctx = conf->ctx;
321	das->disconnect = conf->disconnect;
322
323	os_memcpy(&das->client_addr, conf->client_addr,
324		  sizeof(das->client_addr));
325
326	das->shared_secret = os_malloc(conf->shared_secret_len);
327	if (das->shared_secret == NULL) {
328		radius_das_deinit(das);
329		return NULL;
330	}
331	os_memcpy(das->shared_secret, conf->shared_secret,
332		  conf->shared_secret_len);
333	das->shared_secret_len = conf->shared_secret_len;
334
335	das->sock = radius_das_open_socket(conf->port);
336	if (das->sock < 0) {
337		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
338			   "DAS");
339		radius_das_deinit(das);
340		return NULL;
341	}
342
343	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
344	{
345		radius_das_deinit(das);
346		return NULL;
347	}
348
349	return das;
350}
351
352
353void radius_das_deinit(struct radius_das_data *das)
354{
355	if (das == NULL)
356		return;
357
358	if (das->sock >= 0) {
359		eloop_unregister_read_sock(das->sock);
360		close(das->sock);
361	}
362
363	os_free(das->shared_secret);
364	os_free(das);
365}
366