Updated to hostap_2_6
[mech_eap.git] / libeap / src / radius / radius_das.c
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 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,
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         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         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
75         os_memset(&attrs, 0, sizeof(attrs));
76
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
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         }
133
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
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
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;
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;
164         case RADIUS_DAS_SUCCESS:
165                 error = 0;
166                 break;
167         }
168
169 fail:
170         reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK :
171                                RADIUS_CODE_DISCONNECT_ACK, hdr->identifier);
172         if (reply == NULL)
173                 return NULL;
174
175         if (error) {
176                 if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
177                                                error)) {
178                         radius_msg_free(reply);
179                         return NULL;
180                 }
181         }
182
183         return reply;
184 }
185
186
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;
205         u32 val;
206         int res;
207         struct os_time now;
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,
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);
243                 goto fail;
244         }
245
246         os_get_time(&now);
247         res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
248                                   (u8 *) &val, 4);
249         if (res == 4) {
250                 u32 timestamp = ntohl(val);
251                 if ((unsigned int) abs((int) (now.sec - timestamp)) >
252                     das->time_window) {
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
266         hdr = radius_msg_get_hdr(msg);
267
268         switch (hdr->code) {
269         case RADIUS_CODE_DISCONNECT_REQUEST:
270                 reply = radius_das_disconnect(das, msg, abuf, from_port);
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 */
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                 }
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) {
294                 wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
295
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
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) {
336                 wpa_printf(MSG_INFO, "RADIUS DAS: socket: %s", strerror(errno));
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) {
344                 wpa_printf(MSG_INFO, "RADIUS DAS: bind: %s", strerror(errno));
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
366         das->time_window = conf->time_window;
367         das->require_event_timestamp = conf->require_event_timestamp;
368         das->require_message_authenticator =
369                 conf->require_message_authenticator;
370         das->ctx = conf->ctx;
371         das->disconnect = conf->disconnect;
372
373         os_memcpy(&das->client_addr, conf->client_addr,
374                   sizeof(das->client_addr));
375
376         das->shared_secret = os_malloc(conf->shared_secret_len);
377         if (das->shared_secret == NULL) {
378                 radius_das_deinit(das);
379                 return NULL;
380         }
381         os_memcpy(das->shared_secret, conf->shared_secret,
382                   conf->shared_secret_len);
383         das->shared_secret_len = conf->shared_secret_len;
384
385         das->sock = radius_das_open_socket(conf->port);
386         if (das->sock < 0) {
387                 wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
388                            "DAS");
389                 radius_das_deinit(das);
390                 return NULL;
391         }
392
393         if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
394         {
395                 radius_das_deinit(das);
396                 return NULL;
397         }
398
399         return das;
400 }
401
402
403 void radius_das_deinit(struct radius_das_data *das)
404 {
405         if (das == NULL)
406                 return;
407
408         if (das->sock >= 0) {
409                 eloop_unregister_read_sock(das->sock);
410                 close(das->sock);
411         }
412
413         os_free(das->shared_secret);
414         os_free(das);
415 }