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