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