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