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