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