Merge from vendor branch BIND:
[dragonfly.git] / contrib / hostapd-0.4.9 / ieee802_11_auth.c
1 /*
2  * Host AP (software wireless LAN access point) user space daemon for
3  * Host AP kernel driver / IEEE 802.11 authentication (ACL)
4  * Copyright (c) 2003-2005, Jouni Malinen <jkmaline@cc.hut.fi>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  * Alternatively, this software may be distributed under the terms of BSD
11  * license.
12  *
13  * See README and COPYING for more details.
14  */
15
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <time.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24
25 #include "hostapd.h"
26 #include "ieee802_11.h"
27 #include "ieee802_11_auth.h"
28 #include "radius.h"
29 #include "radius_client.h"
30 #include "eloop.h"
31 #include "hostap_common.h"
32
33 #define RADIUS_ACL_TIMEOUT 30
34
35
36 struct hostapd_cached_radius_acl {
37         time_t timestamp;
38         macaddr addr;
39         int accepted; /* HOSTAPD_ACL_* */
40         struct hostapd_cached_radius_acl *next;
41         u32 session_timeout;
42         u32 acct_interim_interval;
43 };
44
45
46 struct hostapd_acl_query_data {
47         time_t timestamp;
48         u8 radius_id;
49         macaddr addr;
50         u8 *auth_msg; /* IEEE 802.11 authentication frame from station */
51         size_t auth_msg_len;
52         struct hostapd_acl_query_data *next;
53 };
54
55
56 static void hostapd_acl_cache_free(struct hostapd_cached_radius_acl *acl_cache)
57 {
58         struct hostapd_cached_radius_acl *prev;
59
60         while (acl_cache) {
61                 prev = acl_cache;
62                 acl_cache = acl_cache->next;
63                 free(prev);
64         }
65 }
66
67
68 static int hostapd_acl_cache_get(struct hostapd_data *hapd, u8 *addr,
69                                  u32 *session_timeout,
70                                  u32 *acct_interim_interval)
71 {
72         struct hostapd_cached_radius_acl *entry;
73         time_t now;
74
75         time(&now);
76         entry = hapd->acl_cache;
77
78         while (entry) {
79                 if (memcmp(entry->addr, addr, ETH_ALEN) == 0) {
80                         if (now - entry->timestamp > RADIUS_ACL_TIMEOUT)
81                                 return -1; /* entry has expired */
82                         if (entry->accepted == HOSTAPD_ACL_ACCEPT_TIMEOUT)
83                                 *session_timeout = entry->session_timeout;
84                         *acct_interim_interval = entry->acct_interim_interval;
85                         return entry->accepted;
86                 }
87
88                 entry = entry->next;
89         }
90
91         return -1;
92 }
93
94
95 static void hostapd_acl_query_free(struct hostapd_acl_query_data *query)
96 {
97         if (query == NULL)
98                 return;
99         free(query->auth_msg);
100         free(query);
101 }
102
103
104 static int hostapd_radius_acl_query(hostapd *hapd, u8 *addr,
105                                     struct hostapd_acl_query_data *query)
106 {
107         struct radius_msg *msg;
108         char buf[128];
109
110         query->radius_id = radius_client_get_id(hapd->radius);
111         msg = radius_msg_new(RADIUS_CODE_ACCESS_REQUEST, query->radius_id);
112         if (msg == NULL)
113                 return -1;
114
115         radius_msg_make_authenticator(msg, addr, ETH_ALEN);
116
117         snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT, MAC2STR(addr));
118         if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, (u8 *) buf,
119                                  strlen(buf))) {
120                 printf("Could not add User-Name\n");
121                 goto fail;
122         }
123
124         if (!radius_msg_add_attr_user_password(
125                     msg, (u8 *) buf, strlen(buf),
126                     hapd->conf->radius->auth_server->shared_secret,
127                     hapd->conf->radius->auth_server->shared_secret_len)) {
128                 printf("Could not add User-Password\n");
129                 goto fail;
130         }
131
132         if (hapd->conf->own_ip_addr.af == AF_INET &&
133             !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
134                                  (u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) {
135                 printf("Could not add NAS-IP-Address\n");
136                 goto fail;
137         }
138
139 #ifdef CONFIG_IPV6
140         if (hapd->conf->own_ip_addr.af == AF_INET6 &&
141             !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
142                                  (u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) {
143                 printf("Could not add NAS-IPv6-Address\n");
144                 goto fail;
145         }
146 #endif /* CONFIG_IPV6 */
147
148         if (hapd->conf->nas_identifier &&
149             !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
150                                  (u8 *) hapd->conf->nas_identifier,
151                                  strlen(hapd->conf->nas_identifier))) {
152                 printf("Could not add NAS-Identifier\n");
153                 goto fail;
154         }
155
156         snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":%s",
157                  MAC2STR(hapd->own_addr), hapd->conf->ssid);
158         if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID,
159                                  (u8 *) buf, strlen(buf))) {
160                 printf("Could not add Called-Station-Id\n");
161                 goto fail;
162         }
163
164         snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT,
165                  MAC2STR(addr));
166         if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLING_STATION_ID,
167                                  (u8 *) buf, strlen(buf))) {
168                 printf("Could not add Calling-Station-Id\n");
169                 goto fail;
170         }
171
172         if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT_TYPE,
173                                        RADIUS_NAS_PORT_TYPE_IEEE_802_11)) {
174                 printf("Could not add NAS-Port-Type\n");
175                 goto fail;
176         }
177
178         snprintf(buf, sizeof(buf), "CONNECT 11Mbps 802.11b");
179         if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO,
180                                  (u8 *) buf, strlen(buf))) {
181                 printf("Could not add Connect-Info\n");
182                 goto fail;
183         }
184
185         radius_client_send(hapd->radius, msg, RADIUS_AUTH, addr);
186         return 0;
187
188  fail:
189         radius_msg_free(msg);
190         free(msg);
191         return -1;
192 }
193
194
195 int hostapd_allowed_address(hostapd *hapd, u8 *addr, u8 *msg, size_t len,
196                             u32 *session_timeout, u32 *acct_interim_interval)
197 {
198         *session_timeout = 0;
199         *acct_interim_interval = 0;
200
201         if (hostapd_maclist_found(hapd->conf->accept_mac,
202                                   hapd->conf->num_accept_mac, addr))
203                 return HOSTAPD_ACL_ACCEPT;
204
205         if (hostapd_maclist_found(hapd->conf->deny_mac,
206                                   hapd->conf->num_deny_mac, addr))
207                 return HOSTAPD_ACL_REJECT;
208
209         if (hapd->conf->macaddr_acl == ACCEPT_UNLESS_DENIED)
210                 return HOSTAPD_ACL_ACCEPT;
211         if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED)
212                 return HOSTAPD_ACL_REJECT;
213
214         if (hapd->conf->macaddr_acl == USE_EXTERNAL_RADIUS_AUTH) {
215                 struct hostapd_acl_query_data *query;
216
217                 /* Check whether ACL cache has an entry for this station */
218                 int res = hostapd_acl_cache_get(hapd, addr, session_timeout,
219                                                 acct_interim_interval);
220                 if (res == HOSTAPD_ACL_ACCEPT ||
221                     res == HOSTAPD_ACL_ACCEPT_TIMEOUT)
222                         return res;
223                 if (res == HOSTAPD_ACL_REJECT)
224                         return HOSTAPD_ACL_REJECT;
225
226                 query = hapd->acl_queries;
227                 while (query) {
228                         if (memcmp(query->addr, addr, ETH_ALEN) == 0) {
229                                 /* pending query in RADIUS retransmit queue;
230                                  * do not generate a new one */
231                                 return HOSTAPD_ACL_PENDING;
232                         }
233                         query = query->next;
234                 }
235
236                 if (!hapd->conf->radius->auth_server)
237                         return HOSTAPD_ACL_REJECT;
238
239                 /* No entry in the cache - query external RADIUS server */
240                 query = malloc(sizeof(*query));
241                 if (query == NULL) {
242                         printf("malloc for query data failed\n");
243                         return HOSTAPD_ACL_REJECT;
244                 }
245                 memset(query, 0, sizeof(*query));
246                 time(&query->timestamp);
247                 memcpy(query->addr, addr, ETH_ALEN);
248                 if (hostapd_radius_acl_query(hapd, addr, query)) {
249                         printf("Failed to send Access-Request for ACL "
250                                "query.\n");
251                         hostapd_acl_query_free(query);
252                         return HOSTAPD_ACL_REJECT;
253                 }
254
255                 query->auth_msg = malloc(len);
256                 if (query->auth_msg == NULL) {
257                         printf("Failed to allocate memory for auth frame.\n");
258                         hostapd_acl_query_free(query);
259                         return HOSTAPD_ACL_REJECT;
260                 }
261                 memcpy(query->auth_msg, msg, len);
262                 query->auth_msg_len = len;
263                 query->next = hapd->acl_queries;
264                 hapd->acl_queries = query;
265
266                 /* Queued data will be processed in hostapd_acl_recv_radius()
267                  * when RADIUS server replies to the sent Access-Request. */
268                 return HOSTAPD_ACL_PENDING;
269         }
270
271         return HOSTAPD_ACL_REJECT;
272 }
273
274
275 static void hostapd_acl_expire_cache(hostapd *hapd, time_t now)
276 {
277         struct hostapd_cached_radius_acl *prev, *entry, *tmp;
278
279         prev = NULL;
280         entry = hapd->acl_cache;
281
282         while (entry) {
283                 if (now - entry->timestamp > RADIUS_ACL_TIMEOUT) {
284                         HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL,
285                                       "Cached ACL entry for " MACSTR
286                                       " has expired.\n", MAC2STR(entry->addr));
287                         if (prev)
288                                 prev->next = entry->next;
289                         else
290                                 hapd->acl_cache = entry->next;
291
292                         tmp = entry;
293                         entry = entry->next;
294                         free(tmp);
295                         continue;
296                 }
297
298                 prev = entry;
299                 entry = entry->next;
300         }
301 }
302
303
304 static void hostapd_acl_expire_queries(hostapd *hapd, time_t now)
305 {
306         struct hostapd_acl_query_data *prev, *entry, *tmp;
307
308         prev = NULL;
309         entry = hapd->acl_queries;
310
311         while (entry) {
312                 if (now - entry->timestamp > RADIUS_ACL_TIMEOUT) {
313                         HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL,
314                                       "ACL query for " MACSTR
315                                       " has expired.\n", MAC2STR(entry->addr));
316                         if (prev)
317                                 prev->next = entry->next;
318                         else
319                                 hapd->acl_queries = entry->next;
320
321                         tmp = entry;
322                         entry = entry->next;
323                         hostapd_acl_query_free(tmp);
324                         continue;
325                 }
326
327                 prev = entry;
328                 entry = entry->next;
329         }
330 }
331
332
333 static void hostapd_acl_expire(void *eloop_ctx, void *timeout_ctx)
334 {
335         hostapd *hapd = eloop_ctx;
336         time_t now;
337
338         time(&now);
339         hostapd_acl_expire_cache(hapd, now);
340         hostapd_acl_expire_queries(hapd, now);
341
342         eloop_register_timeout(10, 0, hostapd_acl_expire, hapd, NULL);
343 }
344
345
346 /* Return 0 if RADIUS message was a reply to ACL query (and was processed here)
347  * or -1 if not. */
348 static RadiusRxResult
349 hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
350                         u8 *shared_secret, size_t shared_secret_len,
351                         void *data)
352 {
353         struct hostapd_data *hapd = data;
354         struct hostapd_acl_query_data *query, *prev;
355         struct hostapd_cached_radius_acl *cache;
356
357         query = hapd->acl_queries;
358         prev = NULL;
359         while (query) {
360                 if (query->radius_id == msg->hdr->identifier)
361                         break;
362                 prev = query;
363                 query = query->next;
364         }
365         if (query == NULL)
366                 return RADIUS_RX_UNKNOWN;
367
368         HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL, "Found matching Access-Request "
369                       "for RADIUS message (id=%d)\n", query->radius_id);
370
371         if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 0)) {
372                 printf("Incoming RADIUS packet did not have correct "
373                        "authenticator - dropped\n");
374                 return RADIUS_RX_INVALID_AUTHENTICATOR;
375         }
376
377         if (msg->hdr->code != RADIUS_CODE_ACCESS_ACCEPT &&
378             msg->hdr->code != RADIUS_CODE_ACCESS_REJECT) {
379                 printf("Unknown RADIUS message code %d to ACL query\n",
380                        msg->hdr->code);
381                 return RADIUS_RX_UNKNOWN;
382         }
383
384         /* Insert Accept/Reject info into ACL cache */
385         cache = malloc(sizeof(*cache));
386         if (cache == NULL) {
387                 printf("Failed to add ACL cache entry\n");
388                 goto done;
389         }
390         memset(cache, 0, sizeof(*cache));
391         time(&cache->timestamp);
392         memcpy(cache->addr, query->addr, sizeof(cache->addr));
393         if (msg->hdr->code == RADIUS_CODE_ACCESS_ACCEPT) {
394                 if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_SESSION_TIMEOUT,
395                                               &cache->session_timeout) == 0)
396                         cache->accepted = HOSTAPD_ACL_ACCEPT_TIMEOUT;
397                 else
398                         cache->accepted = HOSTAPD_ACL_ACCEPT;
399
400                 if (radius_msg_get_attr_int32(
401                             msg, RADIUS_ATTR_ACCT_INTERIM_INTERVAL,
402                             &cache->acct_interim_interval) == 0 &&
403                     cache->acct_interim_interval < 60) {
404                         HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL, "Ignored too "
405                                       "small Acct-Interim-Interval %d for "
406                                       "STA " MACSTR "\n",
407                                       cache->acct_interim_interval,
408                                       MAC2STR(query->addr));
409                         cache->acct_interim_interval = 0;
410                 }
411         } else
412                 cache->accepted = HOSTAPD_ACL_REJECT;
413         cache->next = hapd->acl_cache;
414         hapd->acl_cache = cache;
415
416         /* Re-send original authentication frame for 802.11 processing */
417         HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL, "Re-sending authentication frame "
418                       "after successful RADIUS ACL query\n");
419         ieee802_11_mgmt(hapd, query->auth_msg, query->auth_msg_len,
420                         WLAN_FC_STYPE_AUTH);
421
422  done:
423         if (prev == NULL)
424                 hapd->acl_queries = query->next;
425         else
426                 prev->next = query->next;
427
428         hostapd_acl_query_free(query);
429
430         return RADIUS_RX_PROCESSED;
431 }
432
433
434 int hostapd_acl_init(hostapd *hapd)
435 {
436         if (radius_client_register(hapd->radius, RADIUS_AUTH,
437                                    hostapd_acl_recv_radius, hapd))
438                 return -1;
439
440         eloop_register_timeout(10, 0, hostapd_acl_expire, hapd, NULL);
441
442         return 0;
443 }
444
445
446 void hostapd_acl_deinit(hostapd *hapd)
447 {
448         struct hostapd_acl_query_data *query, *prev;
449
450         hostapd_acl_cache_free(hapd->acl_cache);
451
452         query = hapd->acl_queries;
453         while (query) {
454                 prev = query;
455                 query = query->next;
456                 hostapd_acl_query_free(prev);
457         }
458 }