Remove need for direct driver calls from ieee802_11_auth.c
[libeap.git] / hostapd / ieee802_11_auth.c
1 /*
2  * hostapd / IEEE 802.11 authentication (ACL)
3  * Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  *
14  * Access control list for IEEE 802.11 authentication can uses statically
15  * configured ACL from configuration files or an external RADIUS server.
16  * Results from external RADIUS queries are cached to allow faster
17  * authentication frame processing.
18  */
19
20 #include "includes.h"
21
22 #ifndef CONFIG_NATIVE_WINDOWS
23
24 #include "common.h"
25 #include "hostapd.h"
26 #include "config.h"
27 #include "ieee802_11.h"
28 #include "ieee802_11_auth.h"
29 #include "radius/radius.h"
30 #include "radius/radius_client.h"
31 #include "eloop.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         int vlan_id;
44 };
45
46
47 struct hostapd_acl_query_data {
48         time_t timestamp;
49         u8 radius_id;
50         macaddr addr;
51         u8 *auth_msg; /* IEEE 802.11 authentication frame from station */
52         size_t auth_msg_len;
53         struct hostapd_acl_query_data *next;
54 };
55
56
57 #ifndef CONFIG_NO_RADIUS
58 static void hostapd_acl_cache_free(struct hostapd_cached_radius_acl *acl_cache)
59 {
60         struct hostapd_cached_radius_acl *prev;
61
62         while (acl_cache) {
63                 prev = acl_cache;
64                 acl_cache = acl_cache->next;
65                 os_free(prev);
66         }
67 }
68
69
70 static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr,
71                                  u32 *session_timeout,
72                                  u32 *acct_interim_interval, int *vlan_id)
73 {
74         struct hostapd_cached_radius_acl *entry;
75         time_t now;
76
77         time(&now);
78         entry = hapd->acl_cache;
79
80         while (entry) {
81                 if (os_memcmp(entry->addr, addr, ETH_ALEN) == 0) {
82                         if (now - entry->timestamp > RADIUS_ACL_TIMEOUT)
83                                 return -1; /* entry has expired */
84                         if (entry->accepted == HOSTAPD_ACL_ACCEPT_TIMEOUT)
85                                 if (session_timeout)
86                                         *session_timeout =
87                                                 entry->session_timeout;
88                         if (acct_interim_interval)
89                                 *acct_interim_interval =
90                                         entry->acct_interim_interval;
91                         if (vlan_id)
92                                 *vlan_id = entry->vlan_id;
93                         return entry->accepted;
94                 }
95
96                 entry = entry->next;
97         }
98
99         return -1;
100 }
101 #endif /* CONFIG_NO_RADIUS */
102
103
104 static void hostapd_acl_query_free(struct hostapd_acl_query_data *query)
105 {
106         if (query == NULL)
107                 return;
108         os_free(query->auth_msg);
109         os_free(query);
110 }
111
112
113 #ifndef CONFIG_NO_RADIUS
114 static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr,
115                                     struct hostapd_acl_query_data *query)
116 {
117         struct radius_msg *msg;
118         char buf[128];
119
120         query->radius_id = radius_client_get_id(hapd->radius);
121         msg = radius_msg_new(RADIUS_CODE_ACCESS_REQUEST, query->radius_id);
122         if (msg == NULL)
123                 return -1;
124
125         radius_msg_make_authenticator(msg, addr, ETH_ALEN);
126
127         os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT, MAC2STR(addr));
128         if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, (u8 *) buf,
129                                  os_strlen(buf))) {
130                 wpa_printf(MSG_DEBUG, "Could not add User-Name");
131                 goto fail;
132         }
133
134         if (!radius_msg_add_attr_user_password(
135                     msg, (u8 *) buf, os_strlen(buf),
136                     hapd->conf->radius->auth_server->shared_secret,
137                     hapd->conf->radius->auth_server->shared_secret_len)) {
138                 wpa_printf(MSG_DEBUG, "Could not add User-Password");
139                 goto fail;
140         }
141
142         if (hapd->conf->own_ip_addr.af == AF_INET &&
143             !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
144                                  (u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) {
145                 wpa_printf(MSG_DEBUG, "Could not add NAS-IP-Address");
146                 goto fail;
147         }
148
149 #ifdef CONFIG_IPV6
150         if (hapd->conf->own_ip_addr.af == AF_INET6 &&
151             !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
152                                  (u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) {
153                 wpa_printf(MSG_DEBUG, "Could not add NAS-IPv6-Address");
154                 goto fail;
155         }
156 #endif /* CONFIG_IPV6 */
157
158         if (hapd->conf->nas_identifier &&
159             !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
160                                  (u8 *) hapd->conf->nas_identifier,
161                                  os_strlen(hapd->conf->nas_identifier))) {
162                 wpa_printf(MSG_DEBUG, "Could not add NAS-Identifier");
163                 goto fail;
164         }
165
166         os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":%s",
167                     MAC2STR(hapd->own_addr), hapd->conf->ssid.ssid);
168         if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID,
169                                  (u8 *) buf, os_strlen(buf))) {
170                 wpa_printf(MSG_DEBUG, "Could not add Called-Station-Id");
171                 goto fail;
172         }
173
174         os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT,
175                     MAC2STR(addr));
176         if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLING_STATION_ID,
177                                  (u8 *) buf, os_strlen(buf))) {
178                 wpa_printf(MSG_DEBUG, "Could not add Calling-Station-Id");
179                 goto fail;
180         }
181
182         if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT_TYPE,
183                                        RADIUS_NAS_PORT_TYPE_IEEE_802_11)) {
184                 wpa_printf(MSG_DEBUG, "Could not add NAS-Port-Type");
185                 goto fail;
186         }
187
188         os_snprintf(buf, sizeof(buf), "CONNECT 11Mbps 802.11b");
189         if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO,
190                                  (u8 *) buf, os_strlen(buf))) {
191                 wpa_printf(MSG_DEBUG, "Could not add Connect-Info");
192                 goto fail;
193         }
194
195         radius_client_send(hapd->radius, msg, RADIUS_AUTH, addr);
196         return 0;
197
198  fail:
199         radius_msg_free(msg);
200         return -1;
201 }
202 #endif /* CONFIG_NO_RADIUS */
203
204
205 /**
206  * hostapd_allowed_address - Check whether a specified STA can be authenticated
207  * @hapd: hostapd BSS data
208  * @addr: MAC address of the STA
209  * @msg: Authentication message
210  * @len: Length of msg in octets
211  * @session_timeout: Buffer for returning session timeout (from RADIUS)
212  * @acct_interim_interval: Buffer for returning account interval (from RADIUS)
213  * @vlan_id: Buffer for returning VLAN ID
214  * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING
215  */
216 int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
217                             const u8 *msg, size_t len, u32 *session_timeout,
218                             u32 *acct_interim_interval, int *vlan_id)
219 {
220         if (session_timeout)
221                 *session_timeout = 0;
222         if (acct_interim_interval)
223                 *acct_interim_interval = 0;
224         if (vlan_id)
225                 *vlan_id = 0;
226
227         if (hostapd_maclist_found(hapd->conf->accept_mac,
228                                   hapd->conf->num_accept_mac, addr, vlan_id))
229                 return HOSTAPD_ACL_ACCEPT;
230
231         if (hostapd_maclist_found(hapd->conf->deny_mac,
232                                   hapd->conf->num_deny_mac, addr, vlan_id))
233                 return HOSTAPD_ACL_REJECT;
234
235         if (hapd->conf->macaddr_acl == ACCEPT_UNLESS_DENIED)
236                 return HOSTAPD_ACL_ACCEPT;
237         if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED)
238                 return HOSTAPD_ACL_REJECT;
239
240         if (hapd->conf->macaddr_acl == USE_EXTERNAL_RADIUS_AUTH) {
241 #ifdef CONFIG_NO_RADIUS
242                 return HOSTAPD_ACL_REJECT;
243 #else /* CONFIG_NO_RADIUS */
244                 struct hostapd_acl_query_data *query;
245
246                 /* Check whether ACL cache has an entry for this station */
247                 int res = hostapd_acl_cache_get(hapd, addr, session_timeout,
248                                                 acct_interim_interval,
249                                                 vlan_id);
250                 if (res == HOSTAPD_ACL_ACCEPT ||
251                     res == HOSTAPD_ACL_ACCEPT_TIMEOUT)
252                         return res;
253                 if (res == HOSTAPD_ACL_REJECT)
254                         return HOSTAPD_ACL_REJECT;
255
256                 query = hapd->acl_queries;
257                 while (query) {
258                         if (os_memcmp(query->addr, addr, ETH_ALEN) == 0) {
259                                 /* pending query in RADIUS retransmit queue;
260                                  * do not generate a new one */
261                                 return HOSTAPD_ACL_PENDING;
262                         }
263                         query = query->next;
264                 }
265
266                 if (!hapd->conf->radius->auth_server)
267                         return HOSTAPD_ACL_REJECT;
268
269                 /* No entry in the cache - query external RADIUS server */
270                 query = os_zalloc(sizeof(*query));
271                 if (query == NULL) {
272                         wpa_printf(MSG_ERROR, "malloc for query data failed");
273                         return HOSTAPD_ACL_REJECT;
274                 }
275                 time(&query->timestamp);
276                 os_memcpy(query->addr, addr, ETH_ALEN);
277                 if (hostapd_radius_acl_query(hapd, addr, query)) {
278                         wpa_printf(MSG_DEBUG, "Failed to send Access-Request "
279                                    "for ACL query.");
280                         hostapd_acl_query_free(query);
281                         return HOSTAPD_ACL_REJECT;
282                 }
283
284                 query->auth_msg = os_malloc(len);
285                 if (query->auth_msg == NULL) {
286                         wpa_printf(MSG_ERROR, "Failed to allocate memory for "
287                                    "auth frame.");
288                         hostapd_acl_query_free(query);
289                         return HOSTAPD_ACL_REJECT;
290                 }
291                 os_memcpy(query->auth_msg, msg, len);
292                 query->auth_msg_len = len;
293                 query->next = hapd->acl_queries;
294                 hapd->acl_queries = query;
295
296                 /* Queued data will be processed in hostapd_acl_recv_radius()
297                  * when RADIUS server replies to the sent Access-Request. */
298                 return HOSTAPD_ACL_PENDING;
299 #endif /* CONFIG_NO_RADIUS */
300         }
301
302         return HOSTAPD_ACL_REJECT;
303 }
304
305
306 #ifndef CONFIG_NO_RADIUS
307 static void hostapd_acl_expire_cache(struct hostapd_data *hapd, time_t now)
308 {
309         struct hostapd_cached_radius_acl *prev, *entry, *tmp;
310
311         prev = NULL;
312         entry = hapd->acl_cache;
313
314         while (entry) {
315                 if (now - entry->timestamp > RADIUS_ACL_TIMEOUT) {
316                         wpa_printf(MSG_DEBUG, "Cached ACL entry for " MACSTR
317                                    " has expired.", MAC2STR(entry->addr));
318                         if (prev)
319                                 prev->next = entry->next;
320                         else
321                                 hapd->acl_cache = entry->next;
322 #ifdef CONFIG_DRIVER_RADIUS_ACL
323                         hapd->drv.set_radius_acl_expire(hapd, entry->addr);
324 #endif /* CONFIG_DRIVER_RADIUS_ACL */
325                         tmp = entry;
326                         entry = entry->next;
327                         os_free(tmp);
328                         continue;
329                 }
330
331                 prev = entry;
332                 entry = entry->next;
333         }
334 }
335
336
337 static void hostapd_acl_expire_queries(struct hostapd_data *hapd, time_t now)
338 {
339         struct hostapd_acl_query_data *prev, *entry, *tmp;
340
341         prev = NULL;
342         entry = hapd->acl_queries;
343
344         while (entry) {
345                 if (now - entry->timestamp > RADIUS_ACL_TIMEOUT) {
346                         wpa_printf(MSG_DEBUG, "ACL query for " MACSTR
347                                    " has expired.", MAC2STR(entry->addr));
348                         if (prev)
349                                 prev->next = entry->next;
350                         else
351                                 hapd->acl_queries = entry->next;
352
353                         tmp = entry;
354                         entry = entry->next;
355                         hostapd_acl_query_free(tmp);
356                         continue;
357                 }
358
359                 prev = entry;
360                 entry = entry->next;
361         }
362 }
363
364
365 /**
366  * hostapd_acl_expire - ACL cache expiration callback
367  * @eloop_ctx: struct hostapd_data *
368  * @timeout_ctx: Not used
369  */
370 static void hostapd_acl_expire(void *eloop_ctx, void *timeout_ctx)
371 {
372         struct hostapd_data *hapd = eloop_ctx;
373         time_t now;
374
375         time(&now);
376         hostapd_acl_expire_cache(hapd, now);
377         hostapd_acl_expire_queries(hapd, now);
378
379         eloop_register_timeout(10, 0, hostapd_acl_expire, hapd, NULL);
380 }
381
382
383 /**
384  * hostapd_acl_recv_radius - Process incoming RADIUS Authentication messages
385  * @msg: RADIUS response message
386  * @req: RADIUS request message
387  * @shared_secret: RADIUS shared secret
388  * @shared_secret_len: Length of shared_secret in octets
389  * @data: Context data (struct hostapd_data *)
390  * Returns: RADIUS_RX_PROCESSED if RADIUS message was a reply to ACL query (and
391  * was processed here) or RADIUS_RX_UNKNOWN if not.
392  */
393 static RadiusRxResult
394 hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
395                         const u8 *shared_secret, size_t shared_secret_len,
396                         void *data)
397 {
398         struct hostapd_data *hapd = data;
399         struct hostapd_acl_query_data *query, *prev;
400         struct hostapd_cached_radius_acl *cache;
401         struct radius_hdr *hdr = radius_msg_get_hdr(msg);
402
403         query = hapd->acl_queries;
404         prev = NULL;
405         while (query) {
406                 if (query->radius_id == hdr->identifier)
407                         break;
408                 prev = query;
409                 query = query->next;
410         }
411         if (query == NULL)
412                 return RADIUS_RX_UNKNOWN;
413
414         wpa_printf(MSG_DEBUG, "Found matching Access-Request for RADIUS "
415                    "message (id=%d)", query->radius_id);
416
417         if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 0)) {
418                 wpa_printf(MSG_INFO, "Incoming RADIUS packet did not have "
419                            "correct authenticator - dropped\n");
420                 return RADIUS_RX_INVALID_AUTHENTICATOR;
421         }
422
423         if (hdr->code != RADIUS_CODE_ACCESS_ACCEPT &&
424             hdr->code != RADIUS_CODE_ACCESS_REJECT) {
425                 wpa_printf(MSG_DEBUG, "Unknown RADIUS message code %d to ACL "
426                            "query", hdr->code);
427                 return RADIUS_RX_UNKNOWN;
428         }
429
430         /* Insert Accept/Reject info into ACL cache */
431         cache = os_zalloc(sizeof(*cache));
432         if (cache == NULL) {
433                 wpa_printf(MSG_DEBUG, "Failed to add ACL cache entry");
434                 goto done;
435         }
436         time(&cache->timestamp);
437         os_memcpy(cache->addr, query->addr, sizeof(cache->addr));
438         if (hdr->code == RADIUS_CODE_ACCESS_ACCEPT) {
439                 if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_SESSION_TIMEOUT,
440                                               &cache->session_timeout) == 0)
441                         cache->accepted = HOSTAPD_ACL_ACCEPT_TIMEOUT;
442                 else
443                         cache->accepted = HOSTAPD_ACL_ACCEPT;
444
445                 if (radius_msg_get_attr_int32(
446                             msg, RADIUS_ATTR_ACCT_INTERIM_INTERVAL,
447                             &cache->acct_interim_interval) == 0 &&
448                     cache->acct_interim_interval < 60) {
449                         wpa_printf(MSG_DEBUG, "Ignored too small "
450                                    "Acct-Interim-Interval %d for STA " MACSTR,
451                                    cache->acct_interim_interval,
452                                    MAC2STR(query->addr));
453                         cache->acct_interim_interval = 0;
454                 }
455
456                 cache->vlan_id = radius_msg_get_vlanid(msg);
457         } else
458                 cache->accepted = HOSTAPD_ACL_REJECT;
459         cache->next = hapd->acl_cache;
460         hapd->acl_cache = cache;
461
462 #ifdef CONFIG_DRIVER_RADIUS_ACL
463         hapd->drv.set_radius_acl_auth(hapd, query->addr, cache->accepted,
464                                       cache->session_timeout);
465 #else /* CONFIG_DRIVER_RADIUS_ACL */
466 #ifdef NEED_AP_MLME
467         /* Re-send original authentication frame for 802.11 processing */
468         wpa_printf(MSG_DEBUG, "Re-sending authentication frame after "
469                    "successful RADIUS ACL query");
470         ieee802_11_mgmt(hapd, query->auth_msg, query->auth_msg_len, NULL);
471 #endif /* NEED_AP_MLME */
472 #endif /* CONFIG_DRIVER_RADIUS_ACL */
473
474  done:
475         if (prev == NULL)
476                 hapd->acl_queries = query->next;
477         else
478                 prev->next = query->next;
479
480         hostapd_acl_query_free(query);
481
482         return RADIUS_RX_PROCESSED;
483 }
484 #endif /* CONFIG_NO_RADIUS */
485
486
487 /**
488  * hostapd_acl_init: Initialize IEEE 802.11 ACL
489  * @hapd: hostapd BSS data
490  * Returns: 0 on success, -1 on failure
491  */
492 int hostapd_acl_init(struct hostapd_data *hapd)
493 {
494 #ifndef CONFIG_NO_RADIUS
495         if (radius_client_register(hapd->radius, RADIUS_AUTH,
496                                    hostapd_acl_recv_radius, hapd))
497                 return -1;
498
499         eloop_register_timeout(10, 0, hostapd_acl_expire, hapd, NULL);
500 #endif /* CONFIG_NO_RADIUS */
501
502         return 0;
503 }
504
505
506 /**
507  * hostapd_acl_deinit - Deinitialize IEEE 802.11 ACL
508  * @hapd: hostapd BSS data
509  */
510 void hostapd_acl_deinit(struct hostapd_data *hapd)
511 {
512         struct hostapd_acl_query_data *query, *prev;
513
514 #ifndef CONFIG_NO_RADIUS
515         eloop_cancel_timeout(hostapd_acl_expire, hapd, NULL);
516
517         hostapd_acl_cache_free(hapd->acl_cache);
518 #endif /* CONFIG_NO_RADIUS */
519
520         query = hapd->acl_queries;
521         while (query) {
522                 prev = query;
523                 query = query->next;
524                 hostapd_acl_query_free(prev);
525         }
526 }
527
528 #endif /* CONFIG_NATIVE_WINDOWS */