1252190Srpaulo/*
2252190Srpaulo * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
3252190Srpaulo * Copyright (c) 2012, Jouni Malinen <j@w1.fi>
4252190Srpaulo *
5252190Srpaulo * This software may be distributed under the terms of the BSD license.
6252190Srpaulo * See README for more details.
7252190Srpaulo */
8252190Srpaulo
9252190Srpaulo#include "includes.h"
10252190Srpaulo#include <net/if.h>
11252190Srpaulo
12252190Srpaulo#include "utils/common.h"
13252190Srpaulo#include "utils/eloop.h"
14252190Srpaulo#include "utils/ip_addr.h"
15252190Srpaulo#include "radius.h"
16252190Srpaulo#include "radius_das.h"
17252190Srpaulo
18252190Srpaulo
19252190Srpauloextern int wpa_debug_level;
20252190Srpaulo
21252190Srpaulo
22252190Srpaulostruct radius_das_data {
23252190Srpaulo	int sock;
24252190Srpaulo	u8 *shared_secret;
25252190Srpaulo	size_t shared_secret_len;
26252190Srpaulo	struct hostapd_ip_addr client_addr;
27252190Srpaulo	unsigned int time_window;
28252190Srpaulo	int require_event_timestamp;
29252190Srpaulo	void *ctx;
30252190Srpaulo	enum radius_das_res (*disconnect)(void *ctx,
31252190Srpaulo					  struct radius_das_attrs *attr);
32252190Srpaulo};
33252190Srpaulo
34252190Srpaulo
35252190Srpaulostatic struct radius_msg * radius_das_disconnect(struct radius_das_data *das,
36252190Srpaulo						 struct radius_msg *msg,
37252190Srpaulo						 const char *abuf,
38252190Srpaulo						 int from_port)
39252190Srpaulo{
40252190Srpaulo	struct radius_hdr *hdr;
41252190Srpaulo	struct radius_msg *reply;
42252190Srpaulo	u8 allowed[] = {
43252190Srpaulo		RADIUS_ATTR_USER_NAME,
44252190Srpaulo		RADIUS_ATTR_CALLING_STATION_ID,
45252190Srpaulo		RADIUS_ATTR_ACCT_SESSION_ID,
46252190Srpaulo		RADIUS_ATTR_EVENT_TIMESTAMP,
47252190Srpaulo		RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
48252190Srpaulo		RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
49252190Srpaulo		0
50252190Srpaulo	};
51252190Srpaulo	int error = 405;
52252190Srpaulo	u8 attr;
53252190Srpaulo	enum radius_das_res res;
54252190Srpaulo	struct radius_das_attrs attrs;
55252190Srpaulo	u8 *buf;
56252190Srpaulo	size_t len;
57252190Srpaulo	char tmp[100];
58252190Srpaulo	u8 sta_addr[ETH_ALEN];
59252190Srpaulo
60252190Srpaulo	hdr = radius_msg_get_hdr(msg);
61252190Srpaulo
62252190Srpaulo	attr = radius_msg_find_unlisted_attr(msg, allowed);
63252190Srpaulo	if (attr) {
64252190Srpaulo		wpa_printf(MSG_INFO, "DAS: Unsupported attribute %u in "
65252190Srpaulo			   "Disconnect-Request from %s:%d", attr,
66252190Srpaulo			   abuf, from_port);
67252190Srpaulo		error = 401;
68252190Srpaulo		goto fail;
69252190Srpaulo	}
70252190Srpaulo
71252190Srpaulo	os_memset(&attrs, 0, sizeof(attrs));
72252190Srpaulo
73252190Srpaulo	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID,
74252190Srpaulo				    &buf, &len, NULL) == 0) {
75252190Srpaulo		if (len >= sizeof(tmp))
76252190Srpaulo			len = sizeof(tmp) - 1;
77252190Srpaulo		os_memcpy(tmp, buf, len);
78252190Srpaulo		tmp[len] = '\0';
79252190Srpaulo		if (hwaddr_aton2(tmp, sta_addr) < 0) {
80252190Srpaulo			wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id "
81252190Srpaulo				   "'%s' from %s:%d", tmp, abuf, from_port);
82252190Srpaulo			error = 407;
83252190Srpaulo			goto fail;
84252190Srpaulo		}
85252190Srpaulo		attrs.sta_addr = sta_addr;
86252190Srpaulo	}
87252190Srpaulo
88252190Srpaulo	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME,
89252190Srpaulo				    &buf, &len, NULL) == 0) {
90252190Srpaulo		attrs.user_name = buf;
91252190Srpaulo		attrs.user_name_len = len;
92252190Srpaulo	}
93252190Srpaulo
94252190Srpaulo	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
95252190Srpaulo				    &buf, &len, NULL) == 0) {
96252190Srpaulo		attrs.acct_session_id = buf;
97252190Srpaulo		attrs.acct_session_id_len = len;
98252190Srpaulo	}
99252190Srpaulo
100252190Srpaulo	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
101252190Srpaulo				    &buf, &len, NULL) == 0) {
102252190Srpaulo		attrs.cui = buf;
103252190Srpaulo		attrs.cui_len = len;
104252190Srpaulo	}
105252190Srpaulo
106252190Srpaulo	res = das->disconnect(das->ctx, &attrs);
107252190Srpaulo	switch (res) {
108252190Srpaulo	case RADIUS_DAS_NAS_MISMATCH:
109252190Srpaulo		wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d",
110252190Srpaulo			   abuf, from_port);
111252190Srpaulo		error = 403;
112252190Srpaulo		break;
113252190Srpaulo	case RADIUS_DAS_SESSION_NOT_FOUND:
114252190Srpaulo		wpa_printf(MSG_INFO, "DAS: Session not found for request from "
115252190Srpaulo			   "%s:%d", abuf, from_port);
116252190Srpaulo		error = 503;
117252190Srpaulo		break;
118252190Srpaulo	case RADIUS_DAS_SUCCESS:
119252190Srpaulo		error = 0;
120252190Srpaulo		break;
121252190Srpaulo	}
122252190Srpaulo
123252190Srpaulofail:
124252190Srpaulo	reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK :
125252190Srpaulo			       RADIUS_CODE_DISCONNECT_ACK, hdr->identifier);
126252190Srpaulo	if (reply == NULL)
127252190Srpaulo		return NULL;
128252190Srpaulo
129252190Srpaulo	if (error) {
130252190Srpaulo		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
131252190Srpaulo					       error)) {
132252190Srpaulo			radius_msg_free(reply);
133252190Srpaulo			return NULL;
134252190Srpaulo		}
135252190Srpaulo	}
136252190Srpaulo
137252190Srpaulo	return reply;
138252190Srpaulo}
139252190Srpaulo
140252190Srpaulo
141252190Srpaulostatic void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
142252190Srpaulo{
143252190Srpaulo	struct radius_das_data *das = eloop_ctx;
144252190Srpaulo	u8 buf[1500];
145252190Srpaulo	union {
146252190Srpaulo		struct sockaddr_storage ss;
147252190Srpaulo		struct sockaddr_in sin;
148252190Srpaulo#ifdef CONFIG_IPV6
149252190Srpaulo		struct sockaddr_in6 sin6;
150252190Srpaulo#endif /* CONFIG_IPV6 */
151252190Srpaulo	} from;
152252190Srpaulo	char abuf[50];
153252190Srpaulo	int from_port = 0;
154252190Srpaulo	socklen_t fromlen;
155252190Srpaulo	int len;
156252190Srpaulo	struct radius_msg *msg, *reply = NULL;
157252190Srpaulo	struct radius_hdr *hdr;
158252190Srpaulo	struct wpabuf *rbuf;
159252190Srpaulo	u32 val;
160252190Srpaulo	int res;
161252190Srpaulo	struct os_time now;
162252190Srpaulo
163252190Srpaulo	fromlen = sizeof(from);
164252190Srpaulo	len = recvfrom(sock, buf, sizeof(buf), 0,
165252190Srpaulo		       (struct sockaddr *) &from.ss, &fromlen);
166252190Srpaulo	if (len < 0) {
167252190Srpaulo		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
168252190Srpaulo		return;
169252190Srpaulo	}
170252190Srpaulo
171252190Srpaulo	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
172252190Srpaulo	from_port = ntohs(from.sin.sin_port);
173252190Srpaulo
174252190Srpaulo	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
175252190Srpaulo		   len, abuf, from_port);
176252190Srpaulo	if (das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
177252190Srpaulo		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
178252190Srpaulo		return;
179252190Srpaulo	}
180252190Srpaulo
181252190Srpaulo	msg = radius_msg_parse(buf, len);
182252190Srpaulo	if (msg == NULL) {
183252190Srpaulo		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
184252190Srpaulo			   "from %s:%d failed", abuf, from_port);
185252190Srpaulo		return;
186252190Srpaulo	}
187252190Srpaulo
188252190Srpaulo	if (wpa_debug_level <= MSG_MSGDUMP)
189252190Srpaulo		radius_msg_dump(msg);
190252190Srpaulo
191252190Srpaulo	if (radius_msg_verify_das_req(msg, das->shared_secret,
192252190Srpaulo				       das->shared_secret_len)) {
193252190Srpaulo		wpa_printf(MSG_DEBUG, "DAS: Invalid authenticator in packet "
194252190Srpaulo			   "from %s:%d - drop", abuf, from_port);
195252190Srpaulo		goto fail;
196252190Srpaulo	}
197252190Srpaulo
198252190Srpaulo	os_get_time(&now);
199252190Srpaulo	res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
200252190Srpaulo				  (u8 *) &val, 4);
201252190Srpaulo	if (res == 4) {
202252190Srpaulo		u32 timestamp = ntohl(val);
203252190Srpaulo		if (abs(now.sec - timestamp) > das->time_window) {
204252190Srpaulo			wpa_printf(MSG_DEBUG, "DAS: Unacceptable "
205252190Srpaulo				   "Event-Timestamp (%u; local time %u) in "
206252190Srpaulo				   "packet from %s:%d - drop",
207252190Srpaulo				   timestamp, (unsigned int) now.sec,
208252190Srpaulo				   abuf, from_port);
209252190Srpaulo			goto fail;
210252190Srpaulo		}
211252190Srpaulo	} else if (das->require_event_timestamp) {
212252190Srpaulo		wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet "
213252190Srpaulo			   "from %s:%d - drop", abuf, from_port);
214252190Srpaulo		goto fail;
215252190Srpaulo	}
216252190Srpaulo
217252190Srpaulo	hdr = radius_msg_get_hdr(msg);
218252190Srpaulo
219252190Srpaulo	switch (hdr->code) {
220252190Srpaulo	case RADIUS_CODE_DISCONNECT_REQUEST:
221252190Srpaulo		reply = radius_das_disconnect(das, msg, abuf, from_port);
222252190Srpaulo		break;
223252190Srpaulo	case RADIUS_CODE_COA_REQUEST:
224252190Srpaulo		/* TODO */
225252190Srpaulo		reply = radius_msg_new(RADIUS_CODE_COA_NAK,
226252190Srpaulo				       hdr->identifier);
227252190Srpaulo		if (reply == NULL)
228252190Srpaulo			break;
229252190Srpaulo
230252190Srpaulo		/* Unsupported Service */
231252190Srpaulo		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
232252190Srpaulo					       405)) {
233252190Srpaulo			radius_msg_free(reply);
234252190Srpaulo			reply = NULL;
235252190Srpaulo			break;
236252190Srpaulo		}
237252190Srpaulo		break;
238252190Srpaulo	default:
239252190Srpaulo		wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
240252190Srpaulo			   "packet from %s:%d",
241252190Srpaulo			   hdr->code, abuf, from_port);
242252190Srpaulo	}
243252190Srpaulo
244252190Srpaulo	if (reply) {
245252190Srpaulo		wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
246252190Srpaulo
247252190Srpaulo		if (!radius_msg_add_attr_int32(reply,
248252190Srpaulo					       RADIUS_ATTR_EVENT_TIMESTAMP,
249252190Srpaulo					       now.sec)) {
250252190Srpaulo			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
251252190Srpaulo				   "Event-Timestamp attribute");
252252190Srpaulo		}
253252190Srpaulo
254252190Srpaulo		if (radius_msg_finish_das_resp(reply, das->shared_secret,
255252190Srpaulo					       das->shared_secret_len, hdr) <
256252190Srpaulo		    0) {
257252190Srpaulo			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
258252190Srpaulo				   "Message-Authenticator attribute");
259252190Srpaulo		}
260252190Srpaulo
261252190Srpaulo		if (wpa_debug_level <= MSG_MSGDUMP)
262252190Srpaulo			radius_msg_dump(reply);
263252190Srpaulo
264252190Srpaulo		rbuf = radius_msg_get_buf(reply);
265252190Srpaulo		res = sendto(das->sock, wpabuf_head(rbuf),
266252190Srpaulo			     wpabuf_len(rbuf), 0,
267252190Srpaulo			     (struct sockaddr *) &from.ss, fromlen);
268252190Srpaulo		if (res < 0) {
269252190Srpaulo			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
270252190Srpaulo				   abuf, from_port, strerror(errno));
271252190Srpaulo		}
272252190Srpaulo	}
273252190Srpaulo
274252190Srpaulofail:
275252190Srpaulo	radius_msg_free(msg);
276252190Srpaulo	radius_msg_free(reply);
277252190Srpaulo}
278252190Srpaulo
279252190Srpaulo
280252190Srpaulostatic int radius_das_open_socket(int port)
281252190Srpaulo{
282252190Srpaulo	int s;
283252190Srpaulo	struct sockaddr_in addr;
284252190Srpaulo
285252190Srpaulo	s = socket(PF_INET, SOCK_DGRAM, 0);
286252190Srpaulo	if (s < 0) {
287252190Srpaulo		perror("socket");
288252190Srpaulo		return -1;
289252190Srpaulo	}
290252190Srpaulo
291252190Srpaulo	os_memset(&addr, 0, sizeof(addr));
292252190Srpaulo	addr.sin_family = AF_INET;
293252190Srpaulo	addr.sin_port = htons(port);
294252190Srpaulo	if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
295252190Srpaulo		perror("bind");
296252190Srpaulo		close(s);
297252190Srpaulo		return -1;
298252190Srpaulo	}
299252190Srpaulo
300252190Srpaulo	return s;
301252190Srpaulo}
302252190Srpaulo
303252190Srpaulo
304252190Srpaulostruct radius_das_data *
305252190Srpauloradius_das_init(struct radius_das_conf *conf)
306252190Srpaulo{
307252190Srpaulo	struct radius_das_data *das;
308252190Srpaulo
309252190Srpaulo	if (conf->port == 0 || conf->shared_secret == NULL ||
310252190Srpaulo	    conf->client_addr == NULL)
311252190Srpaulo		return NULL;
312252190Srpaulo
313252190Srpaulo	das = os_zalloc(sizeof(*das));
314252190Srpaulo	if (das == NULL)
315252190Srpaulo		return NULL;
316252190Srpaulo
317252190Srpaulo	das->time_window = conf->time_window;
318252190Srpaulo	das->require_event_timestamp = conf->require_event_timestamp;
319252190Srpaulo	das->ctx = conf->ctx;
320252190Srpaulo	das->disconnect = conf->disconnect;
321252190Srpaulo
322252190Srpaulo	os_memcpy(&das->client_addr, conf->client_addr,
323252190Srpaulo		  sizeof(das->client_addr));
324252190Srpaulo
325252190Srpaulo	das->shared_secret = os_malloc(conf->shared_secret_len);
326252190Srpaulo	if (das->shared_secret == NULL) {
327252190Srpaulo		radius_das_deinit(das);
328252190Srpaulo		return NULL;
329252190Srpaulo	}
330252190Srpaulo	os_memcpy(das->shared_secret, conf->shared_secret,
331252190Srpaulo		  conf->shared_secret_len);
332252190Srpaulo	das->shared_secret_len = conf->shared_secret_len;
333252190Srpaulo
334252190Srpaulo	das->sock = radius_das_open_socket(conf->port);
335252190Srpaulo	if (das->sock < 0) {
336252190Srpaulo		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
337252190Srpaulo			   "DAS");
338252190Srpaulo		radius_das_deinit(das);
339252190Srpaulo		return NULL;
340252190Srpaulo	}
341252190Srpaulo
342252190Srpaulo	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
343252190Srpaulo	{
344252190Srpaulo		radius_das_deinit(das);
345252190Srpaulo		return NULL;
346252190Srpaulo	}
347252190Srpaulo
348252190Srpaulo	return das;
349252190Srpaulo}
350252190Srpaulo
351252190Srpaulo
352252190Srpaulovoid radius_das_deinit(struct radius_das_data *das)
353252190Srpaulo{
354252190Srpaulo	if (das == NULL)
355252190Srpaulo		return;
356252190Srpaulo
357252190Srpaulo	if (das->sock >= 0) {
358252190Srpaulo		eloop_unregister_read_sock(das->sock);
359252190Srpaulo		close(das->sock);
360252190Srpaulo	}
361252190Srpaulo
362252190Srpaulo	os_free(das->shared_secret);
363252190Srpaulo	os_free(das);
364252190Srpaulo}
365