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