blob: 9655f4cead23f77e4b629e11a1246a322ac50f01 [file] [log] [blame]
Dmitry Shmidt04949592012-07-19 12:16:46 -07001/*
2 * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
Dmitry Shmidtcce06662013-11-04 18:44:24 -08003 * Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
Dmitry Shmidt04949592012-07-19 12:16:46 -07004 *
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
Dmitry Shmidt04949592012-07-19 12:16:46 -070019struct 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,
Dmitry Shmidt13ca8d82014-02-20 10:18:40 -080041 RADIUS_ATTR_NAS_IP_ADDRESS,
Dmitry Shmidt04949592012-07-19 12:16:46 -070042 RADIUS_ATTR_CALLING_STATION_ID,
Dmitry Shmidt13ca8d82014-02-20 10:18:40 -080043 RADIUS_ATTR_NAS_IDENTIFIER,
Dmitry Shmidt04949592012-07-19 12:16:46 -070044 RADIUS_ATTR_ACCT_SESSION_ID,
45 RADIUS_ATTR_EVENT_TIMESTAMP,
46 RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
47 RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
Dmitry Shmidt13ca8d82014-02-20 10:18:40 -080048#ifdef CONFIG_IPV6
49 RADIUS_ATTR_NAS_IPV6_ADDRESS,
50#endif /* CONFIG_IPV6 */
Dmitry Shmidt04949592012-07-19 12:16:46 -070051 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
Dmitry Shmidt13ca8d82014-02-20 10:18:40 -080075 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
Dmitry Shmidt04949592012-07-19 12:16:46 -0700105 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) {
Dmitry Shmidt61d9df32012-08-29 16:22:06 -0700162 if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
163 error)) {
164 radius_msg_free(reply);
165 return NULL;
166 }
Dmitry Shmidt04949592012-07-19 12:16:46 -0700167 }
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);
Dmitry Shmidt54605472013-11-08 11:10:19 -0800235 if ((unsigned int) abs(now.sec - timestamp) >
236 das->time_window) {
Dmitry Shmidt04949592012-07-19 12:16:46 -0700237 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 */
Dmitry Shmidt61d9df32012-08-29 16:22:06 -0700264 if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
265 405)) {
266 radius_msg_free(reply);
267 reply = NULL;
268 break;
269 }
Dmitry Shmidt04949592012-07-19 12:16:46 -0700270 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) {
Dmitry Shmidtcce06662013-11-04 18:44:24 -0800320 wpa_printf(MSG_INFO, "RADIUS DAS: socket: %s", strerror(errno));
Dmitry Shmidt04949592012-07-19 12:16:46 -0700321 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) {
Dmitry Shmidtcce06662013-11-04 18:44:24 -0800328 wpa_printf(MSG_INFO, "RADIUS DAS: bind: %s", strerror(errno));
Dmitry Shmidt04949592012-07-19 12:16:46 -0700329 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}