1e28a4053SRui Paulo /* 2e28a4053SRui Paulo * hostapd / IEEE 802.11 authentication (ACL) 3*a90b9d01SCy Schubert * Copyright (c) 2003-2022, Jouni Malinen <j@w1.fi> 4e28a4053SRui Paulo * 5f05cddf9SRui Paulo * This software may be distributed under the terms of the BSD license. 6f05cddf9SRui Paulo * See README for more details. 7e28a4053SRui Paulo * 8e28a4053SRui Paulo * Access control list for IEEE 802.11 authentication can uses statically 9e28a4053SRui Paulo * configured ACL from configuration files or an external RADIUS server. 10e28a4053SRui Paulo * Results from external RADIUS queries are cached to allow faster 11e28a4053SRui Paulo * authentication frame processing. 12e28a4053SRui Paulo */ 13e28a4053SRui Paulo 14e28a4053SRui Paulo #include "utils/includes.h" 15e28a4053SRui Paulo 16e28a4053SRui Paulo #include "utils/common.h" 17e28a4053SRui Paulo #include "utils/eloop.h" 18e28a4053SRui Paulo #include "radius/radius.h" 19e28a4053SRui Paulo #include "radius/radius_client.h" 20e28a4053SRui Paulo #include "hostapd.h" 21e28a4053SRui Paulo #include "ap_config.h" 22f05cddf9SRui Paulo #include "ap_drv_ops.h" 23*a90b9d01SCy Schubert #include "sta_info.h" 24*a90b9d01SCy Schubert #include "wpa_auth.h" 25e28a4053SRui Paulo #include "ieee802_11.h" 26f05cddf9SRui Paulo #include "ieee802_1x.h" 27e28a4053SRui Paulo #include "ieee802_11_auth.h" 28e28a4053SRui Paulo 29e28a4053SRui Paulo #define RADIUS_ACL_TIMEOUT 30 30e28a4053SRui Paulo 31e28a4053SRui Paulo 32e28a4053SRui Paulo struct hostapd_cached_radius_acl { 335b9c547cSRui Paulo struct os_reltime timestamp; 34e28a4053SRui Paulo macaddr addr; 35e28a4053SRui Paulo int accepted; /* HOSTAPD_ACL_* */ 36e28a4053SRui Paulo struct hostapd_cached_radius_acl *next; 37c1d255d3SCy Schubert struct radius_sta info; 38e28a4053SRui Paulo }; 39e28a4053SRui Paulo 40e28a4053SRui Paulo 41e28a4053SRui Paulo struct hostapd_acl_query_data { 425b9c547cSRui Paulo struct os_reltime timestamp; 43e28a4053SRui Paulo u8 radius_id; 44e28a4053SRui Paulo macaddr addr; 45e28a4053SRui Paulo u8 *auth_msg; /* IEEE 802.11 authentication frame from station */ 46e28a4053SRui Paulo size_t auth_msg_len; 47e28a4053SRui Paulo struct hostapd_acl_query_data *next; 48*a90b9d01SCy Schubert bool radius_psk; 49*a90b9d01SCy Schubert int akm; 50*a90b9d01SCy Schubert u8 *anonce; 51*a90b9d01SCy Schubert u8 *eapol; 52*a90b9d01SCy Schubert size_t eapol_len; 53e28a4053SRui Paulo }; 54e28a4053SRui Paulo 55e28a4053SRui Paulo 56e28a4053SRui Paulo #ifndef CONFIG_NO_RADIUS 57f05cddf9SRui Paulo static void hostapd_acl_cache_free_entry(struct hostapd_cached_radius_acl *e) 58f05cddf9SRui Paulo { 59c1d255d3SCy Schubert os_free(e->info.identity); 60c1d255d3SCy Schubert os_free(e->info.radius_cui); 61c1d255d3SCy Schubert hostapd_free_psk_list(e->info.psk); 62f05cddf9SRui Paulo os_free(e); 63f05cddf9SRui Paulo } 64f05cddf9SRui Paulo 65f05cddf9SRui Paulo 66e28a4053SRui Paulo static void hostapd_acl_cache_free(struct hostapd_cached_radius_acl *acl_cache) 67e28a4053SRui Paulo { 68e28a4053SRui Paulo struct hostapd_cached_radius_acl *prev; 69e28a4053SRui Paulo 70e28a4053SRui Paulo while (acl_cache) { 71e28a4053SRui Paulo prev = acl_cache; 72e28a4053SRui Paulo acl_cache = acl_cache->next; 73f05cddf9SRui Paulo hostapd_acl_cache_free_entry(prev); 74e28a4053SRui Paulo } 75e28a4053SRui Paulo } 76e28a4053SRui Paulo 77e28a4053SRui Paulo 78e28a4053SRui Paulo static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr, 79c1d255d3SCy Schubert struct radius_sta *out) 80e28a4053SRui Paulo { 81e28a4053SRui Paulo struct hostapd_cached_radius_acl *entry; 825b9c547cSRui Paulo struct os_reltime now; 83e28a4053SRui Paulo 845b9c547cSRui Paulo os_get_reltime(&now); 85e28a4053SRui Paulo 86f05cddf9SRui Paulo for (entry = hapd->acl_cache; entry; entry = entry->next) { 87*a90b9d01SCy Schubert if (!ether_addr_equal(entry->addr, addr)) 88f05cddf9SRui Paulo continue; 89f05cddf9SRui Paulo 905b9c547cSRui Paulo if (os_reltime_expired(&now, &entry->timestamp, 915b9c547cSRui Paulo RADIUS_ACL_TIMEOUT)) 92e28a4053SRui Paulo return -1; /* entry has expired */ 93c1d255d3SCy Schubert *out = entry->info; 94c1d255d3SCy Schubert 95f05cddf9SRui Paulo return entry->accepted; 96e28a4053SRui Paulo } 97e28a4053SRui Paulo 98e28a4053SRui Paulo return -1; 99e28a4053SRui Paulo } 100e28a4053SRui Paulo #endif /* CONFIG_NO_RADIUS */ 101e28a4053SRui Paulo 102e28a4053SRui Paulo 103e28a4053SRui Paulo static void hostapd_acl_query_free(struct hostapd_acl_query_data *query) 104e28a4053SRui Paulo { 105*a90b9d01SCy Schubert if (!query) 106e28a4053SRui Paulo return; 107e28a4053SRui Paulo os_free(query->auth_msg); 108*a90b9d01SCy Schubert os_free(query->anonce); 109*a90b9d01SCy Schubert os_free(query->eapol); 110e28a4053SRui Paulo os_free(query); 111e28a4053SRui Paulo } 112e28a4053SRui Paulo 113e28a4053SRui Paulo 114e28a4053SRui Paulo #ifndef CONFIG_NO_RADIUS 115e28a4053SRui Paulo static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr, 116e28a4053SRui Paulo struct hostapd_acl_query_data *query) 117e28a4053SRui Paulo { 118e28a4053SRui Paulo struct radius_msg *msg; 119e28a4053SRui Paulo char buf[128]; 120e28a4053SRui Paulo 121e28a4053SRui Paulo query->radius_id = radius_client_get_id(hapd->radius); 122e28a4053SRui Paulo msg = radius_msg_new(RADIUS_CODE_ACCESS_REQUEST, query->radius_id); 123*a90b9d01SCy Schubert if (!msg) 124e28a4053SRui Paulo return -1; 125e28a4053SRui Paulo 126780fb4a2SCy Schubert if (radius_msg_make_authenticator(msg) < 0) { 127780fb4a2SCy Schubert wpa_printf(MSG_INFO, "Could not make Request Authenticator"); 128780fb4a2SCy Schubert goto fail; 129780fb4a2SCy Schubert } 130e28a4053SRui Paulo 131*a90b9d01SCy Schubert if (!radius_msg_add_msg_auth(msg)) 132*a90b9d01SCy Schubert goto fail; 133*a90b9d01SCy Schubert 134e28a4053SRui Paulo os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT, MAC2STR(addr)); 135e28a4053SRui Paulo if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, (u8 *) buf, 136e28a4053SRui Paulo os_strlen(buf))) { 137e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "Could not add User-Name"); 138e28a4053SRui Paulo goto fail; 139e28a4053SRui Paulo } 140e28a4053SRui Paulo 141e28a4053SRui Paulo if (!radius_msg_add_attr_user_password( 142e28a4053SRui Paulo msg, (u8 *) buf, os_strlen(buf), 143e28a4053SRui Paulo hapd->conf->radius->auth_server->shared_secret, 144e28a4053SRui Paulo hapd->conf->radius->auth_server->shared_secret_len)) { 145e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "Could not add User-Password"); 146e28a4053SRui Paulo goto fail; 147e28a4053SRui Paulo } 148e28a4053SRui Paulo 149f05cddf9SRui Paulo if (add_common_radius_attr(hapd, hapd->conf->radius_auth_req_attr, 150f05cddf9SRui Paulo NULL, msg) < 0) 151e28a4053SRui Paulo goto fail; 152e28a4053SRui Paulo 153e28a4053SRui Paulo os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT, 154e28a4053SRui Paulo MAC2STR(addr)); 155e28a4053SRui Paulo if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLING_STATION_ID, 156e28a4053SRui Paulo (u8 *) buf, os_strlen(buf))) { 157e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "Could not add Calling-Station-Id"); 158e28a4053SRui Paulo goto fail; 159e28a4053SRui Paulo } 160e28a4053SRui Paulo 161e28a4053SRui Paulo os_snprintf(buf, sizeof(buf), "CONNECT 11Mbps 802.11b"); 162e28a4053SRui Paulo if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO, 163e28a4053SRui Paulo (u8 *) buf, os_strlen(buf))) { 164e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "Could not add Connect-Info"); 165e28a4053SRui Paulo goto fail; 166e28a4053SRui Paulo } 167e28a4053SRui Paulo 168*a90b9d01SCy Schubert if (query->akm && 169*a90b9d01SCy Schubert !radius_msg_add_attr_int32(msg, RADIUS_ATTR_WLAN_AKM_SUITE, 170*a90b9d01SCy Schubert wpa_akm_to_suite(query->akm))) { 171*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "Could not add WLAN-AKM-Suite"); 172*a90b9d01SCy Schubert goto fail; 173*a90b9d01SCy Schubert } 174*a90b9d01SCy Schubert 175*a90b9d01SCy Schubert if (query->anonce && 176*a90b9d01SCy Schubert !radius_msg_add_ext_vs(msg, RADIUS_ATTR_EXT_VENDOR_SPECIFIC_5, 177*a90b9d01SCy Schubert RADIUS_VENDOR_ID_FREERADIUS, 178*a90b9d01SCy Schubert RADIUS_VENDOR_ATTR_FREERADIUS_802_1X_ANONCE, 179*a90b9d01SCy Schubert query->anonce, WPA_NONCE_LEN)) { 180*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "Could not add FreeRADIUS-802.1X-Anonce"); 181*a90b9d01SCy Schubert goto fail; 182*a90b9d01SCy Schubert } 183*a90b9d01SCy Schubert 184*a90b9d01SCy Schubert if (query->eapol && 185*a90b9d01SCy Schubert !radius_msg_add_ext_vs(msg, RADIUS_ATTR_EXT_VENDOR_SPECIFIC_5, 186*a90b9d01SCy Schubert RADIUS_VENDOR_ID_FREERADIUS, 187*a90b9d01SCy Schubert RADIUS_VENDOR_ATTR_FREERADIUS_802_1X_EAPOL_KEY_MSG, 188*a90b9d01SCy Schubert query->eapol, query->eapol_len)) { 189*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "Could not add FreeRADIUS-802.1X-EAPoL-Key-Msg"); 190*a90b9d01SCy Schubert goto fail; 191*a90b9d01SCy Schubert } 192*a90b9d01SCy Schubert 193f05cddf9SRui Paulo if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, addr) < 0) 194f05cddf9SRui Paulo goto fail; 195e28a4053SRui Paulo return 0; 196e28a4053SRui Paulo 197e28a4053SRui Paulo fail: 198e28a4053SRui Paulo radius_msg_free(msg); 199e28a4053SRui Paulo return -1; 200e28a4053SRui Paulo } 201e28a4053SRui Paulo #endif /* CONFIG_NO_RADIUS */ 202e28a4053SRui Paulo 203e28a4053SRui Paulo 204e28a4053SRui Paulo /** 205780fb4a2SCy Schubert * hostapd_check_acl - Check a specified STA against accept/deny ACLs 206780fb4a2SCy Schubert * @hapd: hostapd BSS data 207780fb4a2SCy Schubert * @addr: MAC address of the STA 208780fb4a2SCy Schubert * @vlan_id: Buffer for returning VLAN ID 209780fb4a2SCy Schubert * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING 210780fb4a2SCy Schubert */ 211780fb4a2SCy Schubert int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, 212780fb4a2SCy Schubert struct vlan_description *vlan_id) 213780fb4a2SCy Schubert { 214780fb4a2SCy Schubert if (hostapd_maclist_found(hapd->conf->accept_mac, 215780fb4a2SCy Schubert hapd->conf->num_accept_mac, addr, vlan_id)) 216780fb4a2SCy Schubert return HOSTAPD_ACL_ACCEPT; 217780fb4a2SCy Schubert 218780fb4a2SCy Schubert if (hostapd_maclist_found(hapd->conf->deny_mac, 219780fb4a2SCy Schubert hapd->conf->num_deny_mac, addr, vlan_id)) 220780fb4a2SCy Schubert return HOSTAPD_ACL_REJECT; 221780fb4a2SCy Schubert 222780fb4a2SCy Schubert if (hapd->conf->macaddr_acl == ACCEPT_UNLESS_DENIED) 223780fb4a2SCy Schubert return HOSTAPD_ACL_ACCEPT; 224780fb4a2SCy Schubert if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED) 225780fb4a2SCy Schubert return HOSTAPD_ACL_REJECT; 226780fb4a2SCy Schubert 227780fb4a2SCy Schubert return HOSTAPD_ACL_PENDING; 228780fb4a2SCy Schubert } 229780fb4a2SCy Schubert 230780fb4a2SCy Schubert 231780fb4a2SCy Schubert /** 232e28a4053SRui Paulo * hostapd_allowed_address - Check whether a specified STA can be authenticated 233e28a4053SRui Paulo * @hapd: hostapd BSS data 234e28a4053SRui Paulo * @addr: MAC address of the STA 235e28a4053SRui Paulo * @msg: Authentication message 236e28a4053SRui Paulo * @len: Length of msg in octets 237c1d255d3SCy Schubert * @out.session_timeout: Buffer for returning session timeout (from RADIUS) 238c1d255d3SCy Schubert * @out.acct_interim_interval: Buffer for returning account interval (from 239c1d255d3SCy Schubert * RADIUS) 240c1d255d3SCy Schubert * @out.vlan_id: Buffer for returning VLAN ID 241c1d255d3SCy Schubert * @out.psk: Linked list buffer for returning WPA PSK 242c1d255d3SCy Schubert * @out.identity: Buffer for returning identity (from RADIUS) 243c1d255d3SCy Schubert * @out.radius_cui: Buffer for returning CUI (from RADIUS) 24485732ac8SCy Schubert * @is_probe_req: Whether this query for a Probe Request frame 245e28a4053SRui Paulo * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING 246f05cddf9SRui Paulo * 247c1d255d3SCy Schubert * The caller is responsible for properly cloning the returned out->identity and 248c1d255d3SCy Schubert * out->radius_cui and out->psk values. 249e28a4053SRui Paulo */ 250e28a4053SRui Paulo int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, 251c1d255d3SCy Schubert const u8 *msg, size_t len, struct radius_sta *out, 25285732ac8SCy Schubert int is_probe_req) 253e28a4053SRui Paulo { 254780fb4a2SCy Schubert int res; 255780fb4a2SCy Schubert 256c1d255d3SCy Schubert os_memset(out, 0, sizeof(*out)); 257e28a4053SRui Paulo 258c1d255d3SCy Schubert res = hostapd_check_acl(hapd, addr, &out->vlan_id); 259780fb4a2SCy Schubert if (res != HOSTAPD_ACL_PENDING) 260780fb4a2SCy Schubert return res; 261e28a4053SRui Paulo 262e28a4053SRui Paulo if (hapd->conf->macaddr_acl == USE_EXTERNAL_RADIUS_AUTH) { 263e28a4053SRui Paulo #ifdef CONFIG_NO_RADIUS 264e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 265e28a4053SRui Paulo #else /* CONFIG_NO_RADIUS */ 266e28a4053SRui Paulo struct hostapd_acl_query_data *query; 267e28a4053SRui Paulo 26885732ac8SCy Schubert if (is_probe_req) { 26985732ac8SCy Schubert /* Skip RADIUS queries for Probe Request frames to avoid 27085732ac8SCy Schubert * excessive load on the authentication server. */ 27185732ac8SCy Schubert return HOSTAPD_ACL_ACCEPT; 27285732ac8SCy Schubert }; 27385732ac8SCy Schubert 2744bc52338SCy Schubert if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED) 275c1d255d3SCy Schubert os_memset(&out->vlan_id, 0, sizeof(out->vlan_id)); 2764bc52338SCy Schubert 277e28a4053SRui Paulo /* Check whether ACL cache has an entry for this station */ 278c1d255d3SCy Schubert res = hostapd_acl_cache_get(hapd, addr, out); 279e28a4053SRui Paulo if (res == HOSTAPD_ACL_ACCEPT || 280e28a4053SRui Paulo res == HOSTAPD_ACL_ACCEPT_TIMEOUT) 281e28a4053SRui Paulo return res; 282e28a4053SRui Paulo if (res == HOSTAPD_ACL_REJECT) 283e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 284e28a4053SRui Paulo 285e28a4053SRui Paulo query = hapd->acl_queries; 286e28a4053SRui Paulo while (query) { 287*a90b9d01SCy Schubert if (ether_addr_equal(query->addr, addr)) { 288e28a4053SRui Paulo /* pending query in RADIUS retransmit queue; 289e28a4053SRui Paulo * do not generate a new one */ 290e28a4053SRui Paulo return HOSTAPD_ACL_PENDING; 291e28a4053SRui Paulo } 292e28a4053SRui Paulo query = query->next; 293e28a4053SRui Paulo } 294e28a4053SRui Paulo 295e28a4053SRui Paulo if (!hapd->conf->radius->auth_server) 296e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 297e28a4053SRui Paulo 298e28a4053SRui Paulo /* No entry in the cache - query external RADIUS server */ 299e28a4053SRui Paulo query = os_zalloc(sizeof(*query)); 300*a90b9d01SCy Schubert if (!query) { 301e28a4053SRui Paulo wpa_printf(MSG_ERROR, "malloc for query data failed"); 302e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 303e28a4053SRui Paulo } 3045b9c547cSRui Paulo os_get_reltime(&query->timestamp); 305e28a4053SRui Paulo os_memcpy(query->addr, addr, ETH_ALEN); 306e28a4053SRui Paulo if (hostapd_radius_acl_query(hapd, addr, query)) { 307*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 308*a90b9d01SCy Schubert "Failed to send Access-Request for ACL query."); 309e28a4053SRui Paulo hostapd_acl_query_free(query); 310e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 311e28a4053SRui Paulo } 312e28a4053SRui Paulo 31385732ac8SCy Schubert query->auth_msg = os_memdup(msg, len); 314*a90b9d01SCy Schubert if (!query->auth_msg) { 315*a90b9d01SCy Schubert wpa_printf(MSG_ERROR, 316*a90b9d01SCy Schubert "Failed to allocate memory for auth frame."); 317e28a4053SRui Paulo hostapd_acl_query_free(query); 318e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 319e28a4053SRui Paulo } 320e28a4053SRui Paulo query->auth_msg_len = len; 321e28a4053SRui Paulo query->next = hapd->acl_queries; 322e28a4053SRui Paulo hapd->acl_queries = query; 323e28a4053SRui Paulo 324e28a4053SRui Paulo /* Queued data will be processed in hostapd_acl_recv_radius() 325e28a4053SRui Paulo * when RADIUS server replies to the sent Access-Request. */ 326e28a4053SRui Paulo return HOSTAPD_ACL_PENDING; 327e28a4053SRui Paulo #endif /* CONFIG_NO_RADIUS */ 328e28a4053SRui Paulo } 329e28a4053SRui Paulo 330e28a4053SRui Paulo return HOSTAPD_ACL_REJECT; 331e28a4053SRui Paulo } 332e28a4053SRui Paulo 333e28a4053SRui Paulo 334e28a4053SRui Paulo #ifndef CONFIG_NO_RADIUS 3355b9c547cSRui Paulo static void hostapd_acl_expire_cache(struct hostapd_data *hapd, 3365b9c547cSRui Paulo struct os_reltime *now) 337e28a4053SRui Paulo { 338e28a4053SRui Paulo struct hostapd_cached_radius_acl *prev, *entry, *tmp; 339e28a4053SRui Paulo 340e28a4053SRui Paulo prev = NULL; 341e28a4053SRui Paulo entry = hapd->acl_cache; 342e28a4053SRui Paulo 343e28a4053SRui Paulo while (entry) { 3445b9c547cSRui Paulo if (os_reltime_expired(now, &entry->timestamp, 3455b9c547cSRui Paulo RADIUS_ACL_TIMEOUT)) { 346e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "Cached ACL entry for " MACSTR 347e28a4053SRui Paulo " has expired.", MAC2STR(entry->addr)); 348e28a4053SRui Paulo if (prev) 349e28a4053SRui Paulo prev->next = entry->next; 350e28a4053SRui Paulo else 351e28a4053SRui Paulo hapd->acl_cache = entry->next; 352f05cddf9SRui Paulo hostapd_drv_set_radius_acl_expire(hapd, entry->addr); 353e28a4053SRui Paulo tmp = entry; 354e28a4053SRui Paulo entry = entry->next; 355f05cddf9SRui Paulo hostapd_acl_cache_free_entry(tmp); 356e28a4053SRui Paulo continue; 357e28a4053SRui Paulo } 358e28a4053SRui Paulo 359e28a4053SRui Paulo prev = entry; 360e28a4053SRui Paulo entry = entry->next; 361e28a4053SRui Paulo } 362e28a4053SRui Paulo } 363e28a4053SRui Paulo 364e28a4053SRui Paulo 365f05cddf9SRui Paulo static void hostapd_acl_expire_queries(struct hostapd_data *hapd, 3665b9c547cSRui Paulo struct os_reltime *now) 367e28a4053SRui Paulo { 368e28a4053SRui Paulo struct hostapd_acl_query_data *prev, *entry, *tmp; 369e28a4053SRui Paulo 370e28a4053SRui Paulo prev = NULL; 371e28a4053SRui Paulo entry = hapd->acl_queries; 372e28a4053SRui Paulo 373e28a4053SRui Paulo while (entry) { 3745b9c547cSRui Paulo if (os_reltime_expired(now, &entry->timestamp, 3755b9c547cSRui Paulo RADIUS_ACL_TIMEOUT)) { 376e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "ACL query for " MACSTR 377e28a4053SRui Paulo " has expired.", MAC2STR(entry->addr)); 378e28a4053SRui Paulo if (prev) 379e28a4053SRui Paulo prev->next = entry->next; 380e28a4053SRui Paulo else 381e28a4053SRui Paulo hapd->acl_queries = entry->next; 382e28a4053SRui Paulo 383e28a4053SRui Paulo tmp = entry; 384e28a4053SRui Paulo entry = entry->next; 385e28a4053SRui Paulo hostapd_acl_query_free(tmp); 386e28a4053SRui Paulo continue; 387e28a4053SRui Paulo } 388e28a4053SRui Paulo 389e28a4053SRui Paulo prev = entry; 390e28a4053SRui Paulo entry = entry->next; 391e28a4053SRui Paulo } 392e28a4053SRui Paulo } 393e28a4053SRui Paulo 394e28a4053SRui Paulo 395e28a4053SRui Paulo /** 396e28a4053SRui Paulo * hostapd_acl_expire - ACL cache expiration callback 397325151a3SRui Paulo * @hapd: struct hostapd_data * 398e28a4053SRui Paulo */ 399325151a3SRui Paulo void hostapd_acl_expire(struct hostapd_data *hapd) 400e28a4053SRui Paulo { 4015b9c547cSRui Paulo struct os_reltime now; 402e28a4053SRui Paulo 4035b9c547cSRui Paulo os_get_reltime(&now); 4045b9c547cSRui Paulo hostapd_acl_expire_cache(hapd, &now); 4055b9c547cSRui Paulo hostapd_acl_expire_queries(hapd, &now); 406e28a4053SRui Paulo } 407e28a4053SRui Paulo 408e28a4053SRui Paulo 409f05cddf9SRui Paulo static void decode_tunnel_passwords(struct hostapd_data *hapd, 410f05cddf9SRui Paulo const u8 *shared_secret, 411f05cddf9SRui Paulo size_t shared_secret_len, 412f05cddf9SRui Paulo struct radius_msg *msg, 413f05cddf9SRui Paulo struct radius_msg *req, 414f05cddf9SRui Paulo struct hostapd_cached_radius_acl *cache) 415f05cddf9SRui Paulo { 416f05cddf9SRui Paulo int passphraselen; 417780fb4a2SCy Schubert char *passphrase; 418f05cddf9SRui Paulo size_t i; 419f05cddf9SRui Paulo struct hostapd_sta_wpa_psk_short *psk; 420f05cddf9SRui Paulo 421f05cddf9SRui Paulo /* 422f05cddf9SRui Paulo * Decode all tunnel passwords as PSK and save them into a linked list. 423f05cddf9SRui Paulo */ 424f05cddf9SRui Paulo for (i = 0; ; i++) { 425f05cddf9SRui Paulo passphrase = radius_msg_get_tunnel_password( 426f05cddf9SRui Paulo msg, &passphraselen, shared_secret, shared_secret_len, 427f05cddf9SRui Paulo req, i); 428f05cddf9SRui Paulo /* 429f05cddf9SRui Paulo * Passphrase is NULL iff there is no i-th Tunnel-Password 430f05cddf9SRui Paulo * attribute in msg. 431f05cddf9SRui Paulo */ 432*a90b9d01SCy Schubert if (!passphrase) 433f05cddf9SRui Paulo break; 434780fb4a2SCy Schubert 435780fb4a2SCy Schubert /* 436780fb4a2SCy Schubert * Passphase should be 8..63 chars (to be hashed with SSID) 437780fb4a2SCy Schubert * or 64 chars hex string (no separate hashing with SSID). 438780fb4a2SCy Schubert */ 439780fb4a2SCy Schubert 440780fb4a2SCy Schubert if (passphraselen < MIN_PASSPHRASE_LEN || 441780fb4a2SCy Schubert passphraselen > MAX_PASSPHRASE_LEN + 1) 442780fb4a2SCy Schubert goto free_pass; 443780fb4a2SCy Schubert 444f05cddf9SRui Paulo /* 445f05cddf9SRui Paulo * passphrase does not contain the NULL termination. 446f05cddf9SRui Paulo * Add it here as pbkdf2_sha1() requires it. 447f05cddf9SRui Paulo */ 448f05cddf9SRui Paulo psk = os_zalloc(sizeof(struct hostapd_sta_wpa_psk_short)); 449780fb4a2SCy Schubert if (psk) { 450780fb4a2SCy Schubert if ((passphraselen == MAX_PASSPHRASE_LEN + 1) && 451780fb4a2SCy Schubert (hexstr2bin(passphrase, psk->psk, PMK_LEN) < 0)) { 452780fb4a2SCy Schubert hostapd_logger(hapd, cache->addr, 453780fb4a2SCy Schubert HOSTAPD_MODULE_RADIUS, 454780fb4a2SCy Schubert HOSTAPD_LEVEL_WARNING, 455780fb4a2SCy Schubert "invalid hex string (%d chars) in Tunnel-Password", 456780fb4a2SCy Schubert passphraselen); 457780fb4a2SCy Schubert goto skip; 458780fb4a2SCy Schubert } else if (passphraselen <= MAX_PASSPHRASE_LEN) { 459780fb4a2SCy Schubert os_memcpy(psk->passphrase, passphrase, 460780fb4a2SCy Schubert passphraselen); 461780fb4a2SCy Schubert psk->is_passphrase = 1; 462780fb4a2SCy Schubert } 463c1d255d3SCy Schubert psk->next = cache->info.psk; 464c1d255d3SCy Schubert cache->info.psk = psk; 465f05cddf9SRui Paulo psk = NULL; 466f05cddf9SRui Paulo } 467780fb4a2SCy Schubert skip: 468f05cddf9SRui Paulo os_free(psk); 469780fb4a2SCy Schubert free_pass: 470f05cddf9SRui Paulo os_free(passphrase); 471f05cddf9SRui Paulo } 472f05cddf9SRui Paulo } 473f05cddf9SRui Paulo 474f05cddf9SRui Paulo 475e28a4053SRui Paulo /** 476e28a4053SRui Paulo * hostapd_acl_recv_radius - Process incoming RADIUS Authentication messages 477e28a4053SRui Paulo * @msg: RADIUS response message 478e28a4053SRui Paulo * @req: RADIUS request message 479e28a4053SRui Paulo * @shared_secret: RADIUS shared secret 480e28a4053SRui Paulo * @shared_secret_len: Length of shared_secret in octets 481e28a4053SRui Paulo * @data: Context data (struct hostapd_data *) 482e28a4053SRui Paulo * Returns: RADIUS_RX_PROCESSED if RADIUS message was a reply to ACL query (and 483e28a4053SRui Paulo * was processed here) or RADIUS_RX_UNKNOWN if not. 484e28a4053SRui Paulo */ 485e28a4053SRui Paulo static RadiusRxResult 486e28a4053SRui Paulo hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req, 487e28a4053SRui Paulo const u8 *shared_secret, size_t shared_secret_len, 488e28a4053SRui Paulo void *data) 489e28a4053SRui Paulo { 490e28a4053SRui Paulo struct hostapd_data *hapd = data; 491e28a4053SRui Paulo struct hostapd_acl_query_data *query, *prev; 492e28a4053SRui Paulo struct hostapd_cached_radius_acl *cache; 493c1d255d3SCy Schubert struct radius_sta *info; 494e28a4053SRui Paulo struct radius_hdr *hdr = radius_msg_get_hdr(msg); 495e28a4053SRui Paulo 496e28a4053SRui Paulo query = hapd->acl_queries; 497e28a4053SRui Paulo prev = NULL; 498e28a4053SRui Paulo while (query) { 499e28a4053SRui Paulo if (query->radius_id == hdr->identifier) 500e28a4053SRui Paulo break; 501e28a4053SRui Paulo prev = query; 502e28a4053SRui Paulo query = query->next; 503e28a4053SRui Paulo } 504*a90b9d01SCy Schubert if (!query) 505e28a4053SRui Paulo return RADIUS_RX_UNKNOWN; 506e28a4053SRui Paulo 507*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 508*a90b9d01SCy Schubert "Found matching Access-Request for RADIUS message (id=%d)", 509*a90b9d01SCy Schubert query->radius_id); 510e28a4053SRui Paulo 511*a90b9d01SCy Schubert if (radius_msg_verify( 512*a90b9d01SCy Schubert msg, shared_secret, shared_secret_len, req, 513*a90b9d01SCy Schubert hapd->conf->radius_require_message_authenticator)) { 514*a90b9d01SCy Schubert wpa_printf(MSG_INFO, 515*a90b9d01SCy Schubert "Incoming RADIUS packet did not have correct authenticator - dropped"); 516e28a4053SRui Paulo return RADIUS_RX_INVALID_AUTHENTICATOR; 517e28a4053SRui Paulo } 518e28a4053SRui Paulo 519e28a4053SRui Paulo if (hdr->code != RADIUS_CODE_ACCESS_ACCEPT && 520e28a4053SRui Paulo hdr->code != RADIUS_CODE_ACCESS_REJECT) { 521*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 522*a90b9d01SCy Schubert "Unknown RADIUS message code %d to ACL query", 523*a90b9d01SCy Schubert hdr->code); 524e28a4053SRui Paulo return RADIUS_RX_UNKNOWN; 525e28a4053SRui Paulo } 526e28a4053SRui Paulo 527e28a4053SRui Paulo /* Insert Accept/Reject info into ACL cache */ 528e28a4053SRui Paulo cache = os_zalloc(sizeof(*cache)); 529*a90b9d01SCy Schubert if (!cache) { 530e28a4053SRui Paulo wpa_printf(MSG_DEBUG, "Failed to add ACL cache entry"); 531e28a4053SRui Paulo goto done; 532e28a4053SRui Paulo } 5335b9c547cSRui Paulo os_get_reltime(&cache->timestamp); 534e28a4053SRui Paulo os_memcpy(cache->addr, query->addr, sizeof(cache->addr)); 535c1d255d3SCy Schubert info = &cache->info; 536e28a4053SRui Paulo if (hdr->code == RADIUS_CODE_ACCESS_ACCEPT) { 537f05cddf9SRui Paulo u8 *buf; 538f05cddf9SRui Paulo size_t len; 539f05cddf9SRui Paulo 540e28a4053SRui Paulo if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_SESSION_TIMEOUT, 541c1d255d3SCy Schubert &info->session_timeout) == 0) 542e28a4053SRui Paulo cache->accepted = HOSTAPD_ACL_ACCEPT_TIMEOUT; 543e28a4053SRui Paulo else 544e28a4053SRui Paulo cache->accepted = HOSTAPD_ACL_ACCEPT; 545e28a4053SRui Paulo 546e28a4053SRui Paulo if (radius_msg_get_attr_int32( 547e28a4053SRui Paulo msg, RADIUS_ATTR_ACCT_INTERIM_INTERVAL, 548c1d255d3SCy Schubert &info->acct_interim_interval) == 0 && 549c1d255d3SCy Schubert info->acct_interim_interval < 60) { 550*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 551*a90b9d01SCy Schubert "Ignored too small Acct-Interim-Interval %d for STA " 552*a90b9d01SCy Schubert MACSTR, 553c1d255d3SCy Schubert info->acct_interim_interval, 554e28a4053SRui Paulo MAC2STR(query->addr)); 555c1d255d3SCy Schubert info->acct_interim_interval = 0; 556e28a4053SRui Paulo } 557e28a4053SRui Paulo 5584bc52338SCy Schubert if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED) 559c1d255d3SCy Schubert info->vlan_id.notempty = !!radius_msg_get_vlanid( 560c1d255d3SCy Schubert msg, &info->vlan_id.untagged, 561c1d255d3SCy Schubert MAX_NUM_TAGGED_VLAN, info->vlan_id.tagged); 562f05cddf9SRui Paulo 563f05cddf9SRui Paulo decode_tunnel_passwords(hapd, shared_secret, shared_secret_len, 564f05cddf9SRui Paulo msg, req, cache); 565f05cddf9SRui Paulo 566f05cddf9SRui Paulo if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, 567f05cddf9SRui Paulo &buf, &len, NULL) == 0) { 568c1d255d3SCy Schubert info->identity = os_zalloc(len + 1); 569c1d255d3SCy Schubert if (info->identity) 570c1d255d3SCy Schubert os_memcpy(info->identity, buf, len); 571f05cddf9SRui Paulo } 572f05cddf9SRui Paulo if (radius_msg_get_attr_ptr( 573f05cddf9SRui Paulo msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, 574f05cddf9SRui Paulo &buf, &len, NULL) == 0) { 575c1d255d3SCy Schubert info->radius_cui = os_zalloc(len + 1); 576c1d255d3SCy Schubert if (info->radius_cui) 577c1d255d3SCy Schubert os_memcpy(info->radius_cui, buf, len); 578f05cddf9SRui Paulo } 579f05cddf9SRui Paulo 580f05cddf9SRui Paulo if (hapd->conf->wpa_psk_radius == PSK_RADIUS_REQUIRED && 581c1d255d3SCy Schubert !info->psk) 582f05cddf9SRui Paulo cache->accepted = HOSTAPD_ACL_REJECT; 583325151a3SRui Paulo 584c1d255d3SCy Schubert if (info->vlan_id.notempty && 585c1d255d3SCy Schubert !hostapd_vlan_valid(hapd->conf->vlan, &info->vlan_id)) { 586325151a3SRui Paulo hostapd_logger(hapd, query->addr, 587325151a3SRui Paulo HOSTAPD_MODULE_RADIUS, 588325151a3SRui Paulo HOSTAPD_LEVEL_INFO, 589780fb4a2SCy Schubert "Invalid VLAN %d%s received from RADIUS server", 590c1d255d3SCy Schubert info->vlan_id.untagged, 591c1d255d3SCy Schubert info->vlan_id.tagged[0] ? "+" : ""); 592c1d255d3SCy Schubert os_memset(&info->vlan_id, 0, sizeof(info->vlan_id)); 593325151a3SRui Paulo } 594325151a3SRui Paulo if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED && 595c1d255d3SCy Schubert !info->vlan_id.notempty) 596325151a3SRui Paulo cache->accepted = HOSTAPD_ACL_REJECT; 597e28a4053SRui Paulo } else 598e28a4053SRui Paulo cache->accepted = HOSTAPD_ACL_REJECT; 599e28a4053SRui Paulo cache->next = hapd->acl_cache; 600e28a4053SRui Paulo hapd->acl_cache = cache; 601e28a4053SRui Paulo 602*a90b9d01SCy Schubert if (query->radius_psk) { 603*a90b9d01SCy Schubert struct sta_info *sta; 604*a90b9d01SCy Schubert bool success = cache->accepted == HOSTAPD_ACL_ACCEPT || 605*a90b9d01SCy Schubert cache->accepted == HOSTAPD_ACL_ACCEPT_TIMEOUT; 606*a90b9d01SCy Schubert 607*a90b9d01SCy Schubert sta = ap_get_sta(hapd, query->addr); 608*a90b9d01SCy Schubert if (!sta || !sta->wpa_sm) { 609*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 610*a90b9d01SCy Schubert "No STA/SM entry found for the RADIUS PSK response"); 611*a90b9d01SCy Schubert goto done; 612*a90b9d01SCy Schubert } 613*a90b9d01SCy Schubert #ifdef NEED_AP_MLME 614*a90b9d01SCy Schubert if (success && 615*a90b9d01SCy Schubert (ieee802_11_set_radius_info(hapd, sta, cache->accepted, 616*a90b9d01SCy Schubert info) < 0 || 617*a90b9d01SCy Schubert ap_sta_bind_vlan(hapd, sta) < 0)) 618*a90b9d01SCy Schubert success = false; 619*a90b9d01SCy Schubert #endif /* NEED_AP_MLME */ 620*a90b9d01SCy Schubert wpa_auth_sta_radius_psk_resp(sta->wpa_sm, success); 621*a90b9d01SCy Schubert } else { 622e28a4053SRui Paulo #ifdef CONFIG_DRIVER_RADIUS_ACL 623*a90b9d01SCy Schubert hostapd_drv_set_radius_acl_auth(hapd, query->addr, 624*a90b9d01SCy Schubert cache->accepted, 625c1d255d3SCy Schubert info->session_timeout); 626e28a4053SRui Paulo #else /* CONFIG_DRIVER_RADIUS_ACL */ 627e28a4053SRui Paulo #ifdef NEED_AP_MLME 628*a90b9d01SCy Schubert /* Re-send original authentication frame for 802.11 processing 629*a90b9d01SCy Schubert */ 630*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 631*a90b9d01SCy Schubert "Re-sending authentication frame after successful RADIUS ACL query"); 632*a90b9d01SCy Schubert ieee802_11_mgmt(hapd, query->auth_msg, query->auth_msg_len, 633*a90b9d01SCy Schubert NULL); 634e28a4053SRui Paulo #endif /* NEED_AP_MLME */ 635e28a4053SRui Paulo #endif /* CONFIG_DRIVER_RADIUS_ACL */ 636*a90b9d01SCy Schubert } 637e28a4053SRui Paulo 638e28a4053SRui Paulo done: 639*a90b9d01SCy Schubert if (!prev) 640e28a4053SRui Paulo hapd->acl_queries = query->next; 641e28a4053SRui Paulo else 642e28a4053SRui Paulo prev->next = query->next; 643e28a4053SRui Paulo 644e28a4053SRui Paulo hostapd_acl_query_free(query); 645e28a4053SRui Paulo 646e28a4053SRui Paulo return RADIUS_RX_PROCESSED; 647e28a4053SRui Paulo } 648e28a4053SRui Paulo #endif /* CONFIG_NO_RADIUS */ 649e28a4053SRui Paulo 650e28a4053SRui Paulo 651e28a4053SRui Paulo /** 652e28a4053SRui Paulo * hostapd_acl_init: Initialize IEEE 802.11 ACL 653e28a4053SRui Paulo * @hapd: hostapd BSS data 654e28a4053SRui Paulo * Returns: 0 on success, -1 on failure 655e28a4053SRui Paulo */ 656e28a4053SRui Paulo int hostapd_acl_init(struct hostapd_data *hapd) 657e28a4053SRui Paulo { 658e28a4053SRui Paulo #ifndef CONFIG_NO_RADIUS 659e28a4053SRui Paulo if (radius_client_register(hapd->radius, RADIUS_AUTH, 660e28a4053SRui Paulo hostapd_acl_recv_radius, hapd)) 661e28a4053SRui Paulo return -1; 662e28a4053SRui Paulo #endif /* CONFIG_NO_RADIUS */ 663e28a4053SRui Paulo 664e28a4053SRui Paulo return 0; 665e28a4053SRui Paulo } 666e28a4053SRui Paulo 667e28a4053SRui Paulo 668e28a4053SRui Paulo /** 669e28a4053SRui Paulo * hostapd_acl_deinit - Deinitialize IEEE 802.11 ACL 670e28a4053SRui Paulo * @hapd: hostapd BSS data 671e28a4053SRui Paulo */ 672e28a4053SRui Paulo void hostapd_acl_deinit(struct hostapd_data *hapd) 673e28a4053SRui Paulo { 674e28a4053SRui Paulo struct hostapd_acl_query_data *query, *prev; 675e28a4053SRui Paulo 676e28a4053SRui Paulo #ifndef CONFIG_NO_RADIUS 677e28a4053SRui Paulo hostapd_acl_cache_free(hapd->acl_cache); 67885732ac8SCy Schubert hapd->acl_cache = NULL; 679e28a4053SRui Paulo #endif /* CONFIG_NO_RADIUS */ 680e28a4053SRui Paulo 681e28a4053SRui Paulo query = hapd->acl_queries; 68285732ac8SCy Schubert hapd->acl_queries = NULL; 683e28a4053SRui Paulo while (query) { 684e28a4053SRui Paulo prev = query; 685e28a4053SRui Paulo query = query->next; 686e28a4053SRui Paulo hostapd_acl_query_free(prev); 687e28a4053SRui Paulo } 688e28a4053SRui Paulo } 689f05cddf9SRui Paulo 690f05cddf9SRui Paulo 691c1d255d3SCy Schubert void hostapd_copy_psk_list(struct hostapd_sta_wpa_psk_short **psk, 692c1d255d3SCy Schubert struct hostapd_sta_wpa_psk_short *src) 693c1d255d3SCy Schubert { 694c1d255d3SCy Schubert if (!psk) 695c1d255d3SCy Schubert return; 696c1d255d3SCy Schubert 697c1d255d3SCy Schubert if (src) 698c1d255d3SCy Schubert src->ref++; 699c1d255d3SCy Schubert 700c1d255d3SCy Schubert *psk = src; 701c1d255d3SCy Schubert } 702c1d255d3SCy Schubert 703c1d255d3SCy Schubert 704f05cddf9SRui Paulo void hostapd_free_psk_list(struct hostapd_sta_wpa_psk_short *psk) 705f05cddf9SRui Paulo { 706780fb4a2SCy Schubert if (psk && psk->ref) { 707780fb4a2SCy Schubert /* This will be freed when the last reference is dropped. */ 708780fb4a2SCy Schubert psk->ref--; 709780fb4a2SCy Schubert return; 710780fb4a2SCy Schubert } 711780fb4a2SCy Schubert 712f05cddf9SRui Paulo while (psk) { 713f05cddf9SRui Paulo struct hostapd_sta_wpa_psk_short *prev = psk; 714f05cddf9SRui Paulo psk = psk->next; 715*a90b9d01SCy Schubert bin_clear_free(prev, sizeof(*prev)); 716f05cddf9SRui Paulo } 717f05cddf9SRui Paulo } 718*a90b9d01SCy Schubert 719*a90b9d01SCy Schubert 720*a90b9d01SCy Schubert #ifndef CONFIG_NO_RADIUS 721*a90b9d01SCy Schubert void hostapd_acl_req_radius_psk(struct hostapd_data *hapd, const u8 *addr, 722*a90b9d01SCy Schubert int key_mgmt, const u8 *anonce, 723*a90b9d01SCy Schubert const u8 *eapol, size_t eapol_len) 724*a90b9d01SCy Schubert { 725*a90b9d01SCy Schubert struct hostapd_acl_query_data *query; 726*a90b9d01SCy Schubert 727*a90b9d01SCy Schubert query = os_zalloc(sizeof(*query)); 728*a90b9d01SCy Schubert if (!query) 729*a90b9d01SCy Schubert return; 730*a90b9d01SCy Schubert 731*a90b9d01SCy Schubert query->radius_psk = true; 732*a90b9d01SCy Schubert query->akm = key_mgmt; 733*a90b9d01SCy Schubert os_get_reltime(&query->timestamp); 734*a90b9d01SCy Schubert os_memcpy(query->addr, addr, ETH_ALEN); 735*a90b9d01SCy Schubert if (anonce) 736*a90b9d01SCy Schubert query->anonce = os_memdup(anonce, WPA_NONCE_LEN); 737*a90b9d01SCy Schubert if (eapol) { 738*a90b9d01SCy Schubert query->eapol = os_memdup(eapol, eapol_len); 739*a90b9d01SCy Schubert query->eapol_len = eapol_len; 740*a90b9d01SCy Schubert } 741*a90b9d01SCy Schubert if (hostapd_radius_acl_query(hapd, addr, query)) { 742*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 743*a90b9d01SCy Schubert "Failed to send Access-Request for RADIUS PSK/ACL query"); 744*a90b9d01SCy Schubert hostapd_acl_query_free(query); 745*a90b9d01SCy Schubert return; 746*a90b9d01SCy Schubert } 747*a90b9d01SCy Schubert 748*a90b9d01SCy Schubert query->next = hapd->acl_queries; 749*a90b9d01SCy Schubert hapd->acl_queries = query; 750*a90b9d01SCy Schubert } 751*a90b9d01SCy Schubert #endif /* CONFIG_NO_RADIUS */ 752