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